@harness-engineering/cli 1.14.0 → 1.16.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/dist/agents/commands/codex/AGENTS.md +39 -0
- package/dist/agents/commands/codex/harness/add-harness-component/SKILL.md +195 -0
- package/dist/agents/commands/codex/harness/add-harness-component/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/cleanup-dead-code/SKILL.md +248 -0
- package/dist/agents/commands/codex/harness/cleanup-dead-code/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/detect-doc-drift/SKILL.md +182 -0
- package/dist/agents/commands/codex/harness/detect-doc-drift/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/enforce-architecture/SKILL.md +299 -0
- package/dist/agents/commands/codex/harness/enforce-architecture/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/SKILL.md +452 -0
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-autopilot/SKILL.md +919 -0
- package/dist/agents/commands/codex/harness/harness-autopilot/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-brainstorming/SKILL.md +409 -0
- package/dist/agents/commands/codex/harness/harness-brainstorming/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-code-review/SKILL.md +860 -0
- package/dist/agents/commands/codex/harness/harness-code-review/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/SKILL.md +227 -0
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-debugging/SKILL.md +369 -0
- package/dist/agents/commands/codex/harness/harness-debugging/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-dependency-health/SKILL.md +182 -0
- package/dist/agents/commands/codex/harness/harness-dependency-health/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/SKILL.md +463 -0
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-execution/SKILL.md +513 -0
- package/dist/agents/commands/codex/harness/harness-execution/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/SKILL.md +164 -0
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-impact-analysis/SKILL.md +187 -0
- package/dist/agents/commands/codex/harness/harness-impact-analysis/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-integrity/SKILL.md +170 -0
- package/dist/agents/commands/codex/harness/harness-integrity/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-onboarding/SKILL.md +291 -0
- package/dist/agents/commands/codex/harness/harness-onboarding/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-perf/SKILL.md +263 -0
- package/dist/agents/commands/codex/harness/harness-perf/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-planning/SKILL.md +582 -0
- package/dist/agents/commands/codex/harness/harness-planning/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-refactoring/SKILL.md +172 -0
- package/dist/agents/commands/codex/harness/harness-refactoring/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-release-readiness/SKILL.md +692 -0
- package/dist/agents/commands/codex/harness/harness-release-readiness/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-roadmap/SKILL.md +598 -0
- package/dist/agents/commands/codex/harness/harness-roadmap/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-security-scan/SKILL.md +157 -0
- package/dist/agents/commands/codex/harness/harness-security-scan/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-skill-authoring/SKILL.md +295 -0
- package/dist/agents/commands/codex/harness/harness-skill-authoring/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-soundness-review/SKILL.md +1270 -0
- package/dist/agents/commands/codex/harness/harness-soundness-review/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/SKILL.md +247 -0
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-tdd/SKILL.md +180 -0
- package/dist/agents/commands/codex/harness/harness-tdd/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-test-advisor/SKILL.md +163 -0
- package/dist/agents/commands/codex/harness/harness-test-advisor/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-verification/SKILL.md +424 -0
- package/dist/agents/commands/codex/harness/harness-verification/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/harness-verify/SKILL.md +162 -0
- package/dist/agents/commands/codex/harness/harness-verify/agents/openai.yaml +3 -0
- package/dist/agents/commands/codex/harness/initialize-harness-project/SKILL.md +235 -0
- package/dist/agents/commands/codex/harness/initialize-harness-project/agents/openai.yaml +3 -0
- package/dist/agents/commands/cursor/harness/add-harness-component.mdc +200 -0
- package/dist/agents/commands/cursor/harness/cleanup-dead-code.mdc +253 -0
- package/dist/agents/commands/cursor/harness/detect-doc-drift.mdc +187 -0
- package/dist/agents/commands/cursor/harness/enforce-architecture.mdc +304 -0
- package/dist/agents/commands/cursor/harness/harness-architecture-advisor.mdc +457 -0
- package/dist/agents/commands/cursor/harness/harness-autopilot.mdc +924 -0
- package/dist/agents/commands/cursor/harness/harness-brainstorming.mdc +414 -0
- package/dist/agents/commands/cursor/harness/harness-code-review.mdc +865 -0
- package/dist/agents/commands/cursor/harness/harness-codebase-cleanup.mdc +232 -0
- package/dist/agents/commands/cursor/harness/harness-debugging.mdc +374 -0
- package/dist/agents/commands/cursor/harness/harness-dependency-health.mdc +187 -0
- package/dist/agents/commands/cursor/harness/harness-docs-pipeline.mdc +468 -0
- package/dist/agents/commands/cursor/harness/harness-execution.mdc +518 -0
- package/dist/agents/commands/cursor/harness/harness-hotspot-detector.mdc +169 -0
- package/dist/agents/commands/cursor/harness/harness-impact-analysis.mdc +192 -0
- package/dist/agents/commands/cursor/harness/harness-integrity.mdc +175 -0
- package/dist/agents/commands/cursor/harness/harness-onboarding.mdc +296 -0
- package/dist/agents/commands/cursor/harness/harness-perf.mdc +268 -0
- package/dist/agents/commands/cursor/harness/harness-planning.mdc +587 -0
- package/dist/agents/commands/cursor/harness/harness-refactoring.mdc +177 -0
- package/dist/agents/commands/cursor/harness/harness-release-readiness.mdc +697 -0
- package/dist/agents/commands/cursor/harness/harness-roadmap.mdc +603 -0
- package/dist/agents/commands/cursor/harness/harness-security-scan.mdc +162 -0
- package/dist/agents/commands/cursor/harness/harness-skill-authoring.mdc +300 -0
- package/dist/agents/commands/cursor/harness/harness-soundness-review.mdc +1275 -0
- package/dist/agents/commands/cursor/harness/harness-supply-chain-audit.mdc +252 -0
- package/dist/agents/commands/cursor/harness/harness-tdd.mdc +185 -0
- package/dist/agents/commands/cursor/harness/harness-test-advisor.mdc +168 -0
- package/dist/agents/commands/cursor/harness/harness-verification.mdc +429 -0
- package/dist/agents/commands/cursor/harness/harness-verify.mdc +167 -0
- package/dist/agents/commands/cursor/harness/initialize-harness-project.mdc +240 -0
- package/dist/agents/skills/claude-code/enforce-architecture/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-api-design/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-auth/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +355 -45
- package/dist/agents/skills/claude-code/harness-autopilot/skill.yaml +12 -0
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +97 -3
- package/dist/agents/skills/claude-code/harness-code-review/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -4
- package/dist/agents/skills/claude-code/harness-database/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-deployment/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +99 -3
- package/dist/agents/skills/claude-code/harness-planning/skill.yaml +6 -0
- package/dist/agents/skills/claude-code/harness-pre-commit-review/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-product-spec/SKILL.md +5 -5
- package/dist/agents/skills/claude-code/harness-security-review/SKILL.md +27 -7
- package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +52 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/claude-code/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/codex/add-harness-component/SKILL.md +192 -0
- package/dist/agents/skills/codex/add-harness-component/skill.yaml +33 -0
- package/dist/agents/skills/codex/align-documentation/SKILL.md +213 -0
- package/dist/agents/skills/codex/align-documentation/skill.yaml +32 -0
- package/dist/agents/skills/codex/check-mechanical-constraints/SKILL.md +191 -0
- package/dist/agents/skills/codex/check-mechanical-constraints/skill.yaml +33 -0
- package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +245 -0
- package/dist/agents/skills/codex/cleanup-dead-code/skill.yaml +34 -0
- package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +179 -0
- package/dist/agents/skills/codex/detect-doc-drift/skill.yaml +31 -0
- package/dist/agents/skills/codex/enforce-architecture/SKILL.md +296 -0
- package/dist/agents/skills/codex/enforce-architecture/skill.yaml +35 -0
- package/dist/agents/skills/codex/harness-accessibility/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-accessibility/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-api-design/SKILL.md +356 -0
- package/dist/agents/skills/codex/harness-api-design/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +449 -0
- package/dist/agents/skills/codex/harness-architecture-advisor/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-auth/SKILL.md +331 -0
- package/dist/agents/skills/codex/harness-auth/skill.yaml +81 -0
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +916 -0
- package/dist/agents/skills/codex/harness-autopilot/skill.yaml +67 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +406 -0
- package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-caching/SKILL.md +309 -0
- package/dist/agents/skills/codex/harness-caching/skill.yaml +73 -0
- package/dist/agents/skills/codex/harness-chaos/SKILL.md +295 -0
- package/dist/agents/skills/codex/harness-chaos/skill.yaml +72 -0
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +857 -0
- package/dist/agents/skills/codex/harness-code-review/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +224 -0
- package/dist/agents/skills/codex/harness-codebase-cleanup/skill.yaml +65 -0
- package/dist/agents/skills/codex/harness-compliance/SKILL.md +303 -0
- package/dist/agents/skills/codex/harness-compliance/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-containerization/SKILL.md +284 -0
- package/dist/agents/skills/codex/harness-containerization/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-data-pipeline/SKILL.md +274 -0
- package/dist/agents/skills/codex/harness-data-pipeline/skill.yaml +81 -0
- package/dist/agents/skills/codex/harness-data-validation/SKILL.md +343 -0
- package/dist/agents/skills/codex/harness-data-validation/skill.yaml +75 -0
- package/dist/agents/skills/codex/harness-database/SKILL.md +310 -0
- package/dist/agents/skills/codex/harness-database/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-debugging/SKILL.md +366 -0
- package/dist/agents/skills/codex/harness-debugging/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +179 -0
- package/dist/agents/skills/codex/harness-dependency-health/skill.yaml +42 -0
- package/dist/agents/skills/codex/harness-deployment/SKILL.md +307 -0
- package/dist/agents/skills/codex/harness-deployment/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-design/SKILL.md +265 -0
- package/dist/agents/skills/codex/harness-design/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-design-mobile/SKILL.md +336 -0
- package/dist/agents/skills/codex/harness-design-mobile/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-design-system/SKILL.md +282 -0
- package/dist/agents/skills/codex/harness-design-system/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-design-web/SKILL.md +360 -0
- package/dist/agents/skills/codex/harness-design-web/skill.yaml +53 -0
- package/dist/agents/skills/codex/harness-diagnostics/SKILL.md +318 -0
- package/dist/agents/skills/codex/harness-diagnostics/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-docs-pipeline/SKILL.md +460 -0
- package/dist/agents/skills/codex/harness-docs-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/codex/harness-dx/SKILL.md +276 -0
- package/dist/agents/skills/codex/harness-dx/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-e2e/SKILL.md +245 -0
- package/dist/agents/skills/codex/harness-e2e/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-event-driven/SKILL.md +280 -0
- package/dist/agents/skills/codex/harness-event-driven/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-execution/SKILL.md +510 -0
- package/dist/agents/skills/codex/harness-execution/skill.yaml +52 -0
- package/dist/agents/skills/codex/harness-feature-flags/SKILL.md +287 -0
- package/dist/agents/skills/codex/harness-feature-flags/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-git-workflow/SKILL.md +268 -0
- package/dist/agents/skills/codex/harness-git-workflow/skill.yaml +32 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/SKILL.md +161 -0
- package/dist/agents/skills/codex/harness-hotspot-detector/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-i18n/SKILL.md +484 -0
- package/dist/agents/skills/codex/harness-i18n/skill.yaml +55 -0
- package/dist/agents/skills/codex/harness-i18n-process/SKILL.md +388 -0
- package/dist/agents/skills/codex/harness-i18n-process/skill.yaml +44 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/SKILL.md +512 -0
- package/dist/agents/skills/codex/harness-i18n-workflow/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +184 -0
- package/dist/agents/skills/codex/harness-impact-analysis/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-incident-response/SKILL.md +223 -0
- package/dist/agents/skills/codex/harness-incident-response/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-infrastructure-as-code/SKILL.md +279 -0
- package/dist/agents/skills/codex/harness-infrastructure-as-code/skill.yaml +80 -0
- package/dist/agents/skills/codex/harness-integration-test/SKILL.md +271 -0
- package/dist/agents/skills/codex/harness-integration-test/skill.yaml +73 -0
- package/dist/agents/skills/codex/harness-integrity/SKILL.md +167 -0
- package/dist/agents/skills/codex/harness-integrity/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-knowledge-mapper/SKILL.md +195 -0
- package/dist/agents/skills/codex/harness-knowledge-mapper/skill.yaml +50 -0
- package/dist/agents/skills/codex/harness-load-testing/SKILL.md +274 -0
- package/dist/agents/skills/codex/harness-load-testing/skill.yaml +79 -0
- package/dist/agents/skills/codex/harness-ml-ops/SKILL.md +341 -0
- package/dist/agents/skills/codex/harness-ml-ops/skill.yaml +79 -0
- package/dist/agents/skills/codex/harness-mobile-patterns/SKILL.md +326 -0
- package/dist/agents/skills/codex/harness-mobile-patterns/skill.yaml +82 -0
- package/dist/agents/skills/codex/harness-mutation-test/SKILL.md +251 -0
- package/dist/agents/skills/codex/harness-mutation-test/skill.yaml +70 -0
- package/dist/agents/skills/codex/harness-observability/SKILL.md +283 -0
- package/dist/agents/skills/codex/harness-observability/skill.yaml +78 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +288 -0
- package/dist/agents/skills/codex/harness-onboarding/skill.yaml +31 -0
- package/dist/agents/skills/codex/harness-parallel-agents/SKILL.md +256 -0
- package/dist/agents/skills/codex/harness-parallel-agents/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-perf/SKILL.md +260 -0
- package/dist/agents/skills/codex/harness-perf/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-perf-tdd/SKILL.md +249 -0
- package/dist/agents/skills/codex/harness-perf-tdd/skill.yaml +48 -0
- package/dist/agents/skills/codex/harness-planning/SKILL.md +579 -0
- package/dist/agents/skills/codex/harness-planning/skill.yaml +56 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/SKILL.md +324 -0
- package/dist/agents/skills/codex/harness-pre-commit-review/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-product-spec/SKILL.md +285 -0
- package/dist/agents/skills/codex/harness-product-spec/skill.yaml +72 -0
- package/dist/agents/skills/codex/harness-property-test/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-property-test/skill.yaml +71 -0
- package/dist/agents/skills/codex/harness-refactoring/SKILL.md +169 -0
- package/dist/agents/skills/codex/harness-refactoring/skill.yaml +34 -0
- package/dist/agents/skills/codex/harness-release-readiness/SKILL.md +689 -0
- package/dist/agents/skills/codex/harness-release-readiness/skill.yaml +58 -0
- package/dist/agents/skills/codex/harness-resilience/SKILL.md +255 -0
- package/dist/agents/skills/codex/harness-resilience/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +595 -0
- package/dist/agents/skills/codex/harness-roadmap/skill.yaml +44 -0
- package/dist/agents/skills/codex/harness-secrets/SKILL.md +293 -0
- package/dist/agents/skills/codex/harness-secrets/skill.yaml +76 -0
- package/dist/agents/skills/codex/harness-security-review/SKILL.md +260 -0
- package/dist/agents/skills/codex/harness-security-review/skill.yaml +53 -0
- package/dist/agents/skills/codex/harness-security-scan/SKILL.md +154 -0
- package/dist/agents/skills/codex/harness-security-scan/skill.yaml +42 -0
- package/dist/agents/skills/codex/harness-skill-authoring/SKILL.md +292 -0
- package/dist/agents/skills/codex/harness-skill-authoring/skill.yaml +33 -0
- package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +1267 -0
- package/dist/agents/skills/codex/harness-soundness-review/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-sql-review/SKILL.md +315 -0
- package/dist/agents/skills/codex/harness-sql-review/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-state-management/SKILL.md +309 -0
- package/dist/agents/skills/codex/harness-state-management/skill.yaml +33 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/codex/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/codex/harness-tdd/SKILL.md +177 -0
- package/dist/agents/skills/codex/harness-tdd/skill.yaml +49 -0
- package/dist/agents/skills/codex/harness-test-advisor/SKILL.md +160 -0
- package/dist/agents/skills/codex/harness-test-advisor/skill.yaml +45 -0
- package/dist/agents/skills/codex/harness-test-data/SKILL.md +268 -0
- package/dist/agents/skills/codex/harness-test-data/skill.yaml +74 -0
- package/dist/agents/skills/codex/harness-ux-copy/SKILL.md +271 -0
- package/dist/agents/skills/codex/harness-ux-copy/skill.yaml +77 -0
- package/dist/agents/skills/codex/harness-verification/SKILL.md +421 -0
- package/dist/agents/skills/codex/harness-verification/skill.yaml +43 -0
- package/dist/agents/skills/codex/harness-verify/SKILL.md +159 -0
- package/dist/agents/skills/codex/harness-verify/skill.yaml +41 -0
- package/dist/agents/skills/codex/harness-visual-regression/SKILL.md +257 -0
- package/dist/agents/skills/codex/harness-visual-regression/skill.yaml +74 -0
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +232 -0
- package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +32 -0
- package/dist/agents/skills/codex/validate-context-engineering/SKILL.md +150 -0
- package/dist/agents/skills/codex/validate-context-engineering/skill.yaml +32 -0
- package/dist/agents/skills/cursor/add-harness-component/SKILL.md +192 -0
- package/dist/agents/skills/cursor/add-harness-component/skill.yaml +33 -0
- package/dist/agents/skills/cursor/align-documentation/SKILL.md +213 -0
- package/dist/agents/skills/cursor/align-documentation/skill.yaml +32 -0
- package/dist/agents/skills/cursor/check-mechanical-constraints/SKILL.md +191 -0
- package/dist/agents/skills/cursor/check-mechanical-constraints/skill.yaml +33 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +245 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/skill.yaml +34 -0
- package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +179 -0
- package/dist/agents/skills/cursor/detect-doc-drift/skill.yaml +31 -0
- package/dist/agents/skills/cursor/enforce-architecture/SKILL.md +296 -0
- package/dist/agents/skills/cursor/enforce-architecture/skill.yaml +35 -0
- package/dist/agents/skills/cursor/harness-accessibility/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-accessibility/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-api-design/SKILL.md +356 -0
- package/dist/agents/skills/cursor/harness-api-design/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +449 -0
- package/dist/agents/skills/cursor/harness-architecture-advisor/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-auth/SKILL.md +331 -0
- package/dist/agents/skills/cursor/harness-auth/skill.yaml +81 -0
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +916 -0
- package/dist/agents/skills/cursor/harness-autopilot/skill.yaml +67 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +406 -0
- package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-caching/SKILL.md +309 -0
- package/dist/agents/skills/cursor/harness-caching/skill.yaml +73 -0
- package/dist/agents/skills/cursor/harness-chaos/SKILL.md +295 -0
- package/dist/agents/skills/cursor/harness-chaos/skill.yaml +72 -0
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +857 -0
- package/dist/agents/skills/cursor/harness-code-review/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +224 -0
- package/dist/agents/skills/cursor/harness-codebase-cleanup/skill.yaml +65 -0
- package/dist/agents/skills/cursor/harness-compliance/SKILL.md +303 -0
- package/dist/agents/skills/cursor/harness-compliance/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-containerization/SKILL.md +284 -0
- package/dist/agents/skills/cursor/harness-containerization/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-data-pipeline/SKILL.md +274 -0
- package/dist/agents/skills/cursor/harness-data-pipeline/skill.yaml +81 -0
- package/dist/agents/skills/cursor/harness-data-validation/SKILL.md +343 -0
- package/dist/agents/skills/cursor/harness-data-validation/skill.yaml +75 -0
- package/dist/agents/skills/cursor/harness-database/SKILL.md +310 -0
- package/dist/agents/skills/cursor/harness-database/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-debugging/SKILL.md +366 -0
- package/dist/agents/skills/cursor/harness-debugging/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +179 -0
- package/dist/agents/skills/cursor/harness-dependency-health/skill.yaml +42 -0
- package/dist/agents/skills/cursor/harness-deployment/SKILL.md +307 -0
- package/dist/agents/skills/cursor/harness-deployment/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-design/SKILL.md +265 -0
- package/dist/agents/skills/cursor/harness-design/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-design-mobile/SKILL.md +336 -0
- package/dist/agents/skills/cursor/harness-design-mobile/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-design-system/SKILL.md +282 -0
- package/dist/agents/skills/cursor/harness-design-system/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-design-web/SKILL.md +360 -0
- package/dist/agents/skills/cursor/harness-design-web/skill.yaml +53 -0
- package/dist/agents/skills/cursor/harness-diagnostics/SKILL.md +318 -0
- package/dist/agents/skills/cursor/harness-diagnostics/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-docs-pipeline/SKILL.md +460 -0
- package/dist/agents/skills/cursor/harness-docs-pipeline/skill.yaml +70 -0
- package/dist/agents/skills/cursor/harness-dx/SKILL.md +276 -0
- package/dist/agents/skills/cursor/harness-dx/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-e2e/SKILL.md +245 -0
- package/dist/agents/skills/cursor/harness-e2e/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-event-driven/SKILL.md +280 -0
- package/dist/agents/skills/cursor/harness-event-driven/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +510 -0
- package/dist/agents/skills/cursor/harness-execution/skill.yaml +52 -0
- package/dist/agents/skills/cursor/harness-feature-flags/SKILL.md +287 -0
- package/dist/agents/skills/cursor/harness-feature-flags/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-git-workflow/SKILL.md +268 -0
- package/dist/agents/skills/cursor/harness-git-workflow/skill.yaml +32 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/SKILL.md +161 -0
- package/dist/agents/skills/cursor/harness-hotspot-detector/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-i18n/SKILL.md +484 -0
- package/dist/agents/skills/cursor/harness-i18n/skill.yaml +55 -0
- package/dist/agents/skills/cursor/harness-i18n-process/SKILL.md +388 -0
- package/dist/agents/skills/cursor/harness-i18n-process/skill.yaml +44 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/SKILL.md +512 -0
- package/dist/agents/skills/cursor/harness-i18n-workflow/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +184 -0
- package/dist/agents/skills/cursor/harness-impact-analysis/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-incident-response/SKILL.md +223 -0
- package/dist/agents/skills/cursor/harness-incident-response/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-infrastructure-as-code/SKILL.md +279 -0
- package/dist/agents/skills/cursor/harness-infrastructure-as-code/skill.yaml +80 -0
- package/dist/agents/skills/cursor/harness-integration-test/SKILL.md +271 -0
- package/dist/agents/skills/cursor/harness-integration-test/skill.yaml +73 -0
- package/dist/agents/skills/cursor/harness-integrity/SKILL.md +167 -0
- package/dist/agents/skills/cursor/harness-integrity/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-knowledge-mapper/SKILL.md +195 -0
- package/dist/agents/skills/cursor/harness-knowledge-mapper/skill.yaml +50 -0
- package/dist/agents/skills/cursor/harness-load-testing/SKILL.md +274 -0
- package/dist/agents/skills/cursor/harness-load-testing/skill.yaml +79 -0
- package/dist/agents/skills/cursor/harness-ml-ops/SKILL.md +341 -0
- package/dist/agents/skills/cursor/harness-ml-ops/skill.yaml +79 -0
- package/dist/agents/skills/cursor/harness-mobile-patterns/SKILL.md +326 -0
- package/dist/agents/skills/cursor/harness-mobile-patterns/skill.yaml +82 -0
- package/dist/agents/skills/cursor/harness-mutation-test/SKILL.md +251 -0
- package/dist/agents/skills/cursor/harness-mutation-test/skill.yaml +70 -0
- package/dist/agents/skills/cursor/harness-observability/SKILL.md +283 -0
- package/dist/agents/skills/cursor/harness-observability/skill.yaml +78 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +288 -0
- package/dist/agents/skills/cursor/harness-onboarding/skill.yaml +31 -0
- package/dist/agents/skills/cursor/harness-parallel-agents/SKILL.md +256 -0
- package/dist/agents/skills/cursor/harness-parallel-agents/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-perf/SKILL.md +260 -0
- package/dist/agents/skills/cursor/harness-perf/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/SKILL.md +249 -0
- package/dist/agents/skills/cursor/harness-perf-tdd/skill.yaml +48 -0
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +579 -0
- package/dist/agents/skills/cursor/harness-planning/skill.yaml +56 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/SKILL.md +324 -0
- package/dist/agents/skills/cursor/harness-pre-commit-review/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-product-spec/SKILL.md +285 -0
- package/dist/agents/skills/cursor/harness-product-spec/skill.yaml +72 -0
- package/dist/agents/skills/cursor/harness-property-test/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-property-test/skill.yaml +71 -0
- package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +169 -0
- package/dist/agents/skills/cursor/harness-refactoring/skill.yaml +34 -0
- package/dist/agents/skills/cursor/harness-release-readiness/SKILL.md +689 -0
- package/dist/agents/skills/cursor/harness-release-readiness/skill.yaml +58 -0
- package/dist/agents/skills/cursor/harness-resilience/SKILL.md +255 -0
- package/dist/agents/skills/cursor/harness-resilience/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +595 -0
- package/dist/agents/skills/cursor/harness-roadmap/skill.yaml +44 -0
- package/dist/agents/skills/cursor/harness-secrets/SKILL.md +293 -0
- package/dist/agents/skills/cursor/harness-secrets/skill.yaml +76 -0
- package/dist/agents/skills/cursor/harness-security-review/SKILL.md +260 -0
- package/dist/agents/skills/cursor/harness-security-review/skill.yaml +53 -0
- package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +154 -0
- package/dist/agents/skills/cursor/harness-security-scan/skill.yaml +42 -0
- package/dist/agents/skills/cursor/harness-skill-authoring/SKILL.md +292 -0
- package/dist/agents/skills/cursor/harness-skill-authoring/skill.yaml +33 -0
- package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +1267 -0
- package/dist/agents/skills/cursor/harness-soundness-review/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-sql-review/SKILL.md +315 -0
- package/dist/agents/skills/cursor/harness-sql-review/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-state-management/SKILL.md +309 -0
- package/dist/agents/skills/cursor/harness-state-management/skill.yaml +33 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/cursor/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/cursor/harness-tdd/SKILL.md +177 -0
- package/dist/agents/skills/cursor/harness-tdd/skill.yaml +49 -0
- package/dist/agents/skills/cursor/harness-test-advisor/SKILL.md +160 -0
- package/dist/agents/skills/cursor/harness-test-advisor/skill.yaml +45 -0
- package/dist/agents/skills/cursor/harness-test-data/SKILL.md +268 -0
- package/dist/agents/skills/cursor/harness-test-data/skill.yaml +74 -0
- package/dist/agents/skills/cursor/harness-ux-copy/SKILL.md +271 -0
- package/dist/agents/skills/cursor/harness-ux-copy/skill.yaml +77 -0
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +421 -0
- package/dist/agents/skills/cursor/harness-verification/skill.yaml +43 -0
- package/dist/agents/skills/cursor/harness-verify/SKILL.md +159 -0
- package/dist/agents/skills/cursor/harness-verify/skill.yaml +41 -0
- package/dist/agents/skills/cursor/harness-visual-regression/SKILL.md +257 -0
- package/dist/agents/skills/cursor/harness-visual-regression/skill.yaml +74 -0
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +232 -0
- package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +32 -0
- package/dist/agents/skills/cursor/validate-context-engineering/SKILL.md +150 -0
- package/dist/agents/skills/cursor/validate-context-engineering/skill.yaml +32 -0
- package/dist/agents/skills/gemini-cli/enforce-architecture/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-api-design/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-auth/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +355 -45
- package/dist/agents/skills/gemini-cli/harness-autopilot/skill.yaml +12 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +97 -3
- package/dist/agents/skills/gemini-cli/harness-code-review/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -4
- package/dist/agents/skills/gemini-cli/harness-database/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-deployment/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +99 -3
- package/dist/agents/skills/gemini-cli/harness-planning/skill.yaml +6 -0
- package/dist/agents/skills/gemini-cli/harness-pre-commit-review/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-product-spec/SKILL.md +5 -5
- package/dist/agents/skills/gemini-cli/harness-security-review/SKILL.md +27 -7
- package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +52 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/SKILL.md +281 -0
- package/dist/agents/skills/gemini-cli/harness-supply-chain-audit/skill.yaml +51 -0
- package/dist/agents/skills/package.json +1 -0
- package/dist/agents/skills/templates/discipline-template.md +49 -0
- package/dist/agents/skills/tests/schema.ts +1 -1
- package/dist/agents/skills/vitest.config.mts +5 -0
- package/dist/{agents-md-YTYQDA3P.js → agents-md-VYDFPIRW.js} +1 -1
- package/dist/{architecture-JQZYM4US.js → architecture-K5HSRBGB.js} +2 -2
- package/dist/bin/harness-mcp.js +13 -13
- package/dist/bin/harness.js +21 -19
- package/dist/{check-phase-gate-L3RADYWO.js → check-phase-gate-5AS6SXL6.js} +3 -3
- package/dist/{chunk-6KTUUFRN.js → chunk-5ZXHMCPL.js} +1 -1
- package/dist/{chunk-RCWZBSK5.js → chunk-6KWBH4EO.js} +1 -1
- package/dist/{chunk-ABQHQ6I5.js → chunk-ALFKNAZW.js} +2436 -233
- package/dist/{chunk-OXLLOSSR.js → chunk-AV6KMDO5.js} +2 -2
- package/dist/{chunk-7IP4JIFL.js → chunk-C7DTKLPW.js} +4 -4
- package/dist/{chunk-ZOAWBDWU.js → chunk-CJDVBBPB.js} +5 -1
- package/dist/{chunk-YPYGXRDR.js → chunk-DNDBFIZN.js} +18 -4
- package/dist/{chunk-XYLGHKG6.js → chunk-HKUX2X7O.js} +11 -2
- package/dist/{chunk-YZD2MRNQ.js → chunk-JOP2NDNB.js} +684 -142
- package/dist/{chunk-YBJ262QL.js → chunk-LRG3B43J.js} +1 -1
- package/dist/{chunk-AOZRDOIP.js → chunk-M6TIO6NF.js} +1 -1
- package/dist/{chunk-O5OJVPL6.js → chunk-OCDDCGDE.js} +9 -1
- package/dist/{chunk-OSXBPAMK.js → chunk-QDF7COPQ.js} +1 -1
- package/dist/{chunk-TPOTOBR7.js → chunk-RWZPHW4H.js} +3 -3
- package/dist/{chunk-3C2MLBPJ.js → chunk-SFRGPAK6.js} +1 -1
- package/dist/{chunk-XKECDXJS.js → chunk-SHYWICGA.js} +2184 -456
- package/dist/{chunk-S2FXOWOR.js → chunk-TF6ZLHJV.js} +2 -2
- package/dist/{chunk-NLVUVUGD.js → chunk-ZJMU7MEV.js} +1 -1
- package/dist/{ci-workflow-EQZFVX3P.js → ci-workflow-CRWU723U.js} +1 -1
- package/dist/{create-skill-XSWHMSM5.js → create-skill-NDXQSTIK.js} +2 -2
- package/dist/{dist-HWXF2C3R.js → dist-4LPXJYVZ.js} +105 -1
- package/dist/{docs-7ECGYMAV.js → docs-4JRHTLUZ.js} +3 -3
- package/dist/{engine-EG4EH4IX.js → engine-3G3VIM6L.js} +1 -1
- package/dist/{entropy-5USWKLVS.js → entropy-G6CZ2A6P.js} +2 -2
- package/dist/{feedback-UTBXZZHF.js → feedback-QYKQ65HB.js} +1 -1
- package/dist/{generate-agent-definitions-3PM5EU7V.js → generate-agent-definitions-SAAOAPT4.js} +3 -3
- package/dist/index.d.ts +25 -4
- package/dist/index.js +18 -18
- package/dist/{loader-ZPALXIVR.js → loader-VCOK3PF7.js} +1 -1
- package/dist/{mcp-362EZHF4.js → mcp-YENEPHBW.js} +13 -13
- package/dist/{performance-OQAFMJUD.js → performance-UBCFI2UP.js} +4 -2
- package/dist/{review-pipeline-C4GCFVGP.js → review-pipeline-IQAVCWAX.js} +1 -1
- package/dist/{runtime-7YLVK453.js → runtime-PYFFIESU.js} +1 -1
- package/dist/{security-PZOX7AQS.js → security-ZDADTPYW.js} +1 -1
- package/dist/{skill-executor-XZLYZYAK.js → skill-executor-XEVDGXUM.js} +2 -2
- package/dist/{validate-FD3Z6VJD.js → validate-VRTUHALQ.js} +2 -2
- package/dist/{validate-cross-check-WNJM6H2D.js → validate-cross-check-4Y6NHNK3.js} +1 -1
- package/package.json +8 -5
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
OutputMode,
|
|
7
7
|
createCheckPhaseGateCommand,
|
|
8
8
|
findFiles
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5ZXHMCPL.js";
|
|
10
10
|
import {
|
|
11
11
|
createGenerateAgentDefinitionsCommand,
|
|
12
12
|
generateAgentDefinitions
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6KWBH4EO.js";
|
|
14
14
|
import {
|
|
15
15
|
listPersonas,
|
|
16
16
|
loadPersona
|
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
} from "./chunk-TRAPF4IX.js";
|
|
21
21
|
import {
|
|
22
22
|
executeSkill
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-M6TIO6NF.js";
|
|
24
24
|
import {
|
|
25
25
|
ALLOWED_PERSONA_COMMANDS
|
|
26
26
|
} from "./chunk-TEFCFC4H.js";
|
|
27
27
|
import {
|
|
28
28
|
createCreateSkillCommand
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LRG3B43J.js";
|
|
30
30
|
import {
|
|
31
31
|
logger
|
|
32
32
|
} from "./chunk-EBJQ6N4M.js";
|
|
@@ -50,14 +50,14 @@ import {
|
|
|
50
50
|
handleGetImpact,
|
|
51
51
|
handleOrphanDeletion,
|
|
52
52
|
persistToolingConfig
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-JOP2NDNB.js";
|
|
54
54
|
import {
|
|
55
55
|
VALID_PLATFORMS
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-CJDVBBPB.js";
|
|
57
57
|
import {
|
|
58
58
|
findConfigFile,
|
|
59
59
|
resolveConfig
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-OCDDCGDE.js";
|
|
61
61
|
import {
|
|
62
62
|
resolveGlobalSkillsDir,
|
|
63
63
|
resolvePersonasDir,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
} from "./chunk-3WGJMBKH.js";
|
|
73
73
|
import {
|
|
74
74
|
SkillMetadataSchema
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-HKUX2X7O.js";
|
|
76
76
|
import {
|
|
77
77
|
CLI_VERSION
|
|
78
78
|
} from "./chunk-BM3PWGXQ.js";
|
|
@@ -96,6 +96,10 @@ import {
|
|
|
96
96
|
archiveStream,
|
|
97
97
|
buildSnapshot,
|
|
98
98
|
checkDocCoverage,
|
|
99
|
+
checkTaint,
|
|
100
|
+
clearTaint,
|
|
101
|
+
computeOverallSeverity,
|
|
102
|
+
computeScanExitCode,
|
|
99
103
|
createFixes,
|
|
100
104
|
createStream,
|
|
101
105
|
deepMergeConstraints,
|
|
@@ -107,8 +111,11 @@ import {
|
|
|
107
111
|
extractBundle,
|
|
108
112
|
generateSuggestions,
|
|
109
113
|
listStreams,
|
|
114
|
+
listTaintedSessions,
|
|
110
115
|
loadState,
|
|
111
116
|
loadStreamIndex,
|
|
117
|
+
mapInjectionFindings,
|
|
118
|
+
mapSecurityFindings,
|
|
112
119
|
parseDiff,
|
|
113
120
|
parseManifest,
|
|
114
121
|
parseSecurityConfig,
|
|
@@ -121,20 +128,21 @@ import {
|
|
|
121
128
|
runAll,
|
|
122
129
|
runCIChecks,
|
|
123
130
|
runReviewPipeline,
|
|
131
|
+
scanForInjection,
|
|
124
132
|
setActiveStream,
|
|
125
133
|
validateAgentsMap,
|
|
126
134
|
validateDependencies,
|
|
127
135
|
validateKnowledgeMap,
|
|
128
136
|
writeConfig,
|
|
129
137
|
writeLockfile
|
|
130
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-ALFKNAZW.js";
|
|
131
139
|
import {
|
|
132
140
|
Err,
|
|
133
141
|
Ok
|
|
134
142
|
} from "./chunk-ERS5EVUZ.js";
|
|
135
143
|
|
|
136
144
|
// src/index.ts
|
|
137
|
-
import { Command as
|
|
145
|
+
import { Command as Command70 } from "commander";
|
|
138
146
|
|
|
139
147
|
// src/commands/validate.ts
|
|
140
148
|
import { Command } from "commander";
|
|
@@ -213,7 +221,7 @@ function createValidateCommand() {
|
|
|
213
221
|
process.exit(result.error.exitCode);
|
|
214
222
|
}
|
|
215
223
|
if (opts.crossCheck) {
|
|
216
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
224
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-4Y6NHNK3.js");
|
|
217
225
|
const cwd = process.cwd();
|
|
218
226
|
const specsDir = path.join(cwd, "docs", "specs");
|
|
219
227
|
const plansDir = path.join(cwd, "docs", "plans");
|
|
@@ -481,10 +489,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
481
489
|
const projectRoot = path4.resolve(cwd);
|
|
482
490
|
let configData = {};
|
|
483
491
|
try {
|
|
484
|
-
const
|
|
492
|
+
const fs34 = await import("fs");
|
|
485
493
|
const configPath = path4.join(projectRoot, "harness.config.json");
|
|
486
|
-
if (
|
|
487
|
-
const raw =
|
|
494
|
+
if (fs34.existsSync(configPath)) {
|
|
495
|
+
const raw = fs34.readFileSync(configPath, "utf-8");
|
|
488
496
|
const parsed = JSON.parse(raw);
|
|
489
497
|
configData = parsed.security ?? {};
|
|
490
498
|
}
|
|
@@ -570,7 +578,7 @@ function registerBenchCommand(perf) {
|
|
|
570
578
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
571
579
|
const globalOpts = cmd.optsWithGlobals();
|
|
572
580
|
const cwd = process.cwd();
|
|
573
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
581
|
+
const { BenchmarkRunner } = await import("./dist-4LPXJYVZ.js");
|
|
574
582
|
const runner = new BenchmarkRunner();
|
|
575
583
|
const benchFiles = runner.discover(cwd, glob);
|
|
576
584
|
if (benchFiles.length === 0) {
|
|
@@ -638,7 +646,7 @@ function registerBaselinesCommands(perf) {
|
|
|
638
646
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
639
647
|
const globalOpts = cmd.optsWithGlobals();
|
|
640
648
|
const cwd = process.cwd();
|
|
641
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
649
|
+
const { BenchmarkRunner } = await import("./dist-4LPXJYVZ.js");
|
|
642
650
|
const runner = new BenchmarkRunner();
|
|
643
651
|
const manager = new BaselineManager(cwd);
|
|
644
652
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -671,7 +679,7 @@ function registerReportCommand(perf) {
|
|
|
671
679
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
672
680
|
const globalOpts = cmd.optsWithGlobals();
|
|
673
681
|
const cwd = process.cwd();
|
|
674
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
682
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-4LPXJYVZ.js");
|
|
675
683
|
const analyzer = new EntropyAnalyzer2({
|
|
676
684
|
rootDir: path5.resolve(cwd),
|
|
677
685
|
analyze: { complexity: true, coupling: true }
|
|
@@ -852,33 +860,248 @@ function createCheckDocsCommand() {
|
|
|
852
860
|
// src/commands/init.ts
|
|
853
861
|
import { Command as Command8 } from "commander";
|
|
854
862
|
import chalk2 from "chalk";
|
|
855
|
-
import * as
|
|
856
|
-
import * as
|
|
863
|
+
import * as fs4 from "fs";
|
|
864
|
+
import * as path10 from "path";
|
|
857
865
|
|
|
858
866
|
// src/commands/setup-mcp.ts
|
|
859
867
|
import { Command as Command7 } from "commander";
|
|
860
|
-
import * as
|
|
861
|
-
import * as
|
|
868
|
+
import * as fs3 from "fs";
|
|
869
|
+
import * as path9 from "path";
|
|
862
870
|
import * as os from "os";
|
|
863
871
|
import chalk from "chalk";
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
872
|
+
import * as clack from "@clack/prompts";
|
|
873
|
+
|
|
874
|
+
// src/integrations/config.ts
|
|
875
|
+
import * as fs from "fs";
|
|
876
|
+
import * as path7 from "path";
|
|
877
|
+
function readJsonSafe(filePath) {
|
|
868
878
|
if (!fs.existsSync(filePath)) return null;
|
|
869
879
|
try {
|
|
870
880
|
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
871
881
|
} catch {
|
|
872
|
-
fs.copyFileSync(filePath, filePath + ".bak");
|
|
873
882
|
return null;
|
|
874
883
|
}
|
|
875
884
|
}
|
|
876
|
-
function
|
|
885
|
+
function writeJson(filePath, data) {
|
|
877
886
|
const dir = path7.dirname(filePath);
|
|
878
887
|
if (!fs.existsSync(dir)) {
|
|
879
888
|
fs.mkdirSync(dir, { recursive: true });
|
|
880
889
|
}
|
|
881
|
-
|
|
890
|
+
const tmp = filePath + ".tmp";
|
|
891
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n");
|
|
892
|
+
fs.renameSync(tmp, filePath);
|
|
893
|
+
}
|
|
894
|
+
function readMcpConfig(filePath) {
|
|
895
|
+
const config = readJsonSafe(filePath);
|
|
896
|
+
if (!config) return { mcpServers: {} };
|
|
897
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
898
|
+
return config;
|
|
899
|
+
}
|
|
900
|
+
function writeMcpEntry(filePath, name, entry) {
|
|
901
|
+
const config = readMcpConfig(filePath);
|
|
902
|
+
config.mcpServers[name] = entry;
|
|
903
|
+
writeJson(filePath, config);
|
|
904
|
+
}
|
|
905
|
+
function removeMcpEntry(filePath, name) {
|
|
906
|
+
if (!fs.existsSync(filePath)) return;
|
|
907
|
+
const config = readMcpConfig(filePath);
|
|
908
|
+
delete config.mcpServers[name];
|
|
909
|
+
writeJson(filePath, config);
|
|
910
|
+
}
|
|
911
|
+
function readIntegrationsConfig(configPath) {
|
|
912
|
+
const raw = readJsonSafe(configPath);
|
|
913
|
+
if (!raw || !raw.integrations) return { enabled: [], dismissed: [] };
|
|
914
|
+
const integ = raw.integrations;
|
|
915
|
+
return {
|
|
916
|
+
enabled: Array.isArray(integ.enabled) ? integ.enabled : [],
|
|
917
|
+
dismissed: Array.isArray(integ.dismissed) ? integ.dismissed : []
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function writeIntegrationsConfig(configPath, integrations) {
|
|
921
|
+
const raw = readJsonSafe(configPath) ?? {};
|
|
922
|
+
raw.integrations = integrations;
|
|
923
|
+
writeJson(configPath, raw);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/integrations/toml.ts
|
|
927
|
+
import * as fs2 from "fs";
|
|
928
|
+
import * as path8 from "path";
|
|
929
|
+
function writeTomlMcpEntry(filePath, name, entry) {
|
|
930
|
+
const dir = path8.dirname(filePath);
|
|
931
|
+
if (!fs2.existsSync(dir)) {
|
|
932
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
933
|
+
}
|
|
934
|
+
const existing = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8") : "";
|
|
935
|
+
const blockHeader = `[mcp_servers.${name}]`;
|
|
936
|
+
const newBlock = serializeTomlMcpBlock(name, entry);
|
|
937
|
+
let updated;
|
|
938
|
+
if (existing.includes(blockHeader)) {
|
|
939
|
+
updated = replaceTomlBlock(existing, blockHeader, newBlock);
|
|
940
|
+
} else {
|
|
941
|
+
const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n" : "";
|
|
942
|
+
updated = existing + separator + newBlock;
|
|
943
|
+
}
|
|
944
|
+
const tmp = filePath + ".tmp";
|
|
945
|
+
fs2.writeFileSync(tmp, updated);
|
|
946
|
+
fs2.renameSync(tmp, filePath);
|
|
947
|
+
}
|
|
948
|
+
function serializeTomlMcpBlock(name, entry) {
|
|
949
|
+
const lines = [`[mcp_servers.${name}]`];
|
|
950
|
+
lines.push(`command = ${JSON.stringify(entry.command)}`);
|
|
951
|
+
if (entry.args !== void 0) {
|
|
952
|
+
const argsLiteral = "[" + entry.args.map((a) => JSON.stringify(a)).join(", ") + "]";
|
|
953
|
+
lines.push(`args = ${argsLiteral}`);
|
|
954
|
+
}
|
|
955
|
+
if (entry.enabled !== void 0) {
|
|
956
|
+
lines.push(`enabled = ${entry.enabled}`);
|
|
957
|
+
}
|
|
958
|
+
return lines.join("\n") + "\n";
|
|
959
|
+
}
|
|
960
|
+
function replaceTomlBlock(content, blockHeader, newBlock) {
|
|
961
|
+
const lines = content.split("\n");
|
|
962
|
+
const startIdx = lines.findIndex((l) => l.trim() === blockHeader);
|
|
963
|
+
if (startIdx === -1) return content + newBlock;
|
|
964
|
+
let endIdx = lines.length;
|
|
965
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
966
|
+
if (lines[i]?.match(/^\[(?!\[)/)) {
|
|
967
|
+
endIdx = i;
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
while (endIdx > startIdx + 1 && lines[endIdx - 1]?.trim() === "") {
|
|
972
|
+
endIdx--;
|
|
973
|
+
}
|
|
974
|
+
const newBlockLines = newBlock.trimEnd().split("\n");
|
|
975
|
+
const result = [
|
|
976
|
+
...lines.slice(0, startIdx),
|
|
977
|
+
...newBlockLines,
|
|
978
|
+
...endIdx < lines.length ? ["", ...lines.slice(endIdx)] : []
|
|
979
|
+
];
|
|
980
|
+
return result.join("\n") + (content.endsWith("\n") ? "\n" : "");
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/commands/setup-mcp.ts
|
|
984
|
+
var HARNESS_MCP_ENTRY = {
|
|
985
|
+
command: "harness-mcp"
|
|
986
|
+
};
|
|
987
|
+
var CURSOR_CURATED_TOOLS = [
|
|
988
|
+
"run_skill",
|
|
989
|
+
"validate_project",
|
|
990
|
+
"emit_interaction",
|
|
991
|
+
"check_docs",
|
|
992
|
+
"manage_roadmap",
|
|
993
|
+
"run_code_review",
|
|
994
|
+
"check_phase_gate",
|
|
995
|
+
"gather_context",
|
|
996
|
+
"find_context_for",
|
|
997
|
+
"get_impact",
|
|
998
|
+
"detect_entropy",
|
|
999
|
+
"run_security_scan",
|
|
1000
|
+
"assess_project",
|
|
1001
|
+
"manage_state",
|
|
1002
|
+
"create_self_review",
|
|
1003
|
+
"analyze_diff",
|
|
1004
|
+
"request_peer_review",
|
|
1005
|
+
"review_changes",
|
|
1006
|
+
"check_dependencies",
|
|
1007
|
+
"search_skills",
|
|
1008
|
+
"code_search",
|
|
1009
|
+
"code_outline",
|
|
1010
|
+
"ask_graph",
|
|
1011
|
+
"query_graph",
|
|
1012
|
+
"detect_anomalies"
|
|
1013
|
+
];
|
|
1014
|
+
var ALL_MCP_TOOLS = [
|
|
1015
|
+
"validate_project",
|
|
1016
|
+
"check_dependencies",
|
|
1017
|
+
"check_docs",
|
|
1018
|
+
"detect_entropy",
|
|
1019
|
+
"generate_linter",
|
|
1020
|
+
"validate_linter_config",
|
|
1021
|
+
"init_project",
|
|
1022
|
+
"list_personas",
|
|
1023
|
+
"generate_persona_artifacts",
|
|
1024
|
+
"run_persona",
|
|
1025
|
+
"add_component",
|
|
1026
|
+
"run_agent_task",
|
|
1027
|
+
"run_skill",
|
|
1028
|
+
"manage_state",
|
|
1029
|
+
"create_self_review",
|
|
1030
|
+
"analyze_diff",
|
|
1031
|
+
"request_peer_review",
|
|
1032
|
+
"check_phase_gate",
|
|
1033
|
+
"validate_cross_check",
|
|
1034
|
+
"create_skill",
|
|
1035
|
+
"generate_slash_commands",
|
|
1036
|
+
"query_graph",
|
|
1037
|
+
"search_similar",
|
|
1038
|
+
"find_context_for",
|
|
1039
|
+
"get_relationships",
|
|
1040
|
+
"get_impact",
|
|
1041
|
+
"ingest_source",
|
|
1042
|
+
"generate_agent_definitions",
|
|
1043
|
+
"run_security_scan",
|
|
1044
|
+
"check_performance",
|
|
1045
|
+
"get_perf_baselines",
|
|
1046
|
+
"update_perf_baselines",
|
|
1047
|
+
"get_critical_paths",
|
|
1048
|
+
"list_streams",
|
|
1049
|
+
"manage_roadmap",
|
|
1050
|
+
"emit_interaction",
|
|
1051
|
+
"run_code_review",
|
|
1052
|
+
"gather_context",
|
|
1053
|
+
"assess_project",
|
|
1054
|
+
"review_changes",
|
|
1055
|
+
"detect_anomalies",
|
|
1056
|
+
"ask_graph",
|
|
1057
|
+
"check_task_independence",
|
|
1058
|
+
"predict_conflicts",
|
|
1059
|
+
"detect_stale_constraints",
|
|
1060
|
+
"search_skills",
|
|
1061
|
+
"code_outline",
|
|
1062
|
+
"code_search",
|
|
1063
|
+
"code_unfold"
|
|
1064
|
+
];
|
|
1065
|
+
async function runCursorToolPicker() {
|
|
1066
|
+
try {
|
|
1067
|
+
const selected = await clack.multiselect({
|
|
1068
|
+
message: "Select tools to register for Cursor (25 recommended; Cursor supports ~40 across all servers)",
|
|
1069
|
+
options: ALL_MCP_TOOLS.map((tool) => {
|
|
1070
|
+
const opt = { value: tool, label: tool };
|
|
1071
|
+
if (CURSOR_CURATED_TOOLS.includes(tool)) opt.hint = "recommended";
|
|
1072
|
+
return opt;
|
|
1073
|
+
}),
|
|
1074
|
+
initialValues: CURSOR_CURATED_TOOLS
|
|
1075
|
+
});
|
|
1076
|
+
if (clack.isCancel(selected)) {
|
|
1077
|
+
return CURSOR_CURATED_TOOLS;
|
|
1078
|
+
}
|
|
1079
|
+
return selected;
|
|
1080
|
+
} catch {
|
|
1081
|
+
return CURSOR_CURATED_TOOLS;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function writeCursorMcpEntryWithTools(configPath, tools) {
|
|
1085
|
+
writeMcpEntry(configPath, "harness", {
|
|
1086
|
+
command: "harness",
|
|
1087
|
+
args: ["mcp", "--tools", ...tools]
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
function readJsonFile(filePath) {
|
|
1091
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
1092
|
+
try {
|
|
1093
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
1094
|
+
} catch {
|
|
1095
|
+
fs3.copyFileSync(filePath, filePath + ".bak");
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function writeJsonFile(filePath, data) {
|
|
1100
|
+
const dir = path9.dirname(filePath);
|
|
1101
|
+
if (!fs3.existsSync(dir)) {
|
|
1102
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1103
|
+
}
|
|
1104
|
+
fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
882
1105
|
}
|
|
883
1106
|
function configureMcpServer(configPath) {
|
|
884
1107
|
const config = readJsonFile(configPath) ?? {};
|
|
@@ -893,7 +1116,7 @@ function configureMcpServer(configPath) {
|
|
|
893
1116
|
return true;
|
|
894
1117
|
}
|
|
895
1118
|
function addGeminiTrustedFolder(cwd) {
|
|
896
|
-
const trustedPath =
|
|
1119
|
+
const trustedPath = path9.join(os.homedir(), ".gemini", "trustedFolders.json");
|
|
897
1120
|
const folders = readJsonFile(trustedPath) ?? {};
|
|
898
1121
|
if (folders[cwd] === "TRUST_FOLDER") {
|
|
899
1122
|
return false;
|
|
@@ -907,7 +1130,7 @@ function setupMcp(cwd, client) {
|
|
|
907
1130
|
const skipped = [];
|
|
908
1131
|
let trustedFolder = false;
|
|
909
1132
|
if (client === "all" || client === "claude") {
|
|
910
|
-
const configPath =
|
|
1133
|
+
const configPath = path9.join(cwd, ".mcp.json");
|
|
911
1134
|
if (configureMcpServer(configPath)) {
|
|
912
1135
|
configured.push("Claude Code");
|
|
913
1136
|
} else {
|
|
@@ -915,7 +1138,7 @@ function setupMcp(cwd, client) {
|
|
|
915
1138
|
}
|
|
916
1139
|
}
|
|
917
1140
|
if (client === "all" || client === "gemini") {
|
|
918
|
-
const configPath =
|
|
1141
|
+
const configPath = path9.join(cwd, ".gemini", "settings.json");
|
|
919
1142
|
if (configureMcpServer(configPath)) {
|
|
920
1143
|
configured.push("Gemini CLI");
|
|
921
1144
|
} else {
|
|
@@ -923,44 +1146,96 @@ function setupMcp(cwd, client) {
|
|
|
923
1146
|
}
|
|
924
1147
|
trustedFolder = addGeminiTrustedFolder(cwd);
|
|
925
1148
|
}
|
|
1149
|
+
if (client === "all" || client === "codex") {
|
|
1150
|
+
const configPath = path9.join(cwd, ".codex", "config.toml");
|
|
1151
|
+
const alreadyConfigured = (() => {
|
|
1152
|
+
if (!fs3.existsSync(configPath)) return false;
|
|
1153
|
+
const content = fs3.readFileSync(configPath, "utf-8");
|
|
1154
|
+
return content.includes("[mcp_servers.harness]");
|
|
1155
|
+
})();
|
|
1156
|
+
if (alreadyConfigured) {
|
|
1157
|
+
skipped.push("Codex CLI");
|
|
1158
|
+
} else {
|
|
1159
|
+
writeTomlMcpEntry(configPath, "harness", {
|
|
1160
|
+
command: "harness",
|
|
1161
|
+
args: ["mcp"],
|
|
1162
|
+
enabled: true
|
|
1163
|
+
});
|
|
1164
|
+
configured.push("Codex CLI");
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (client === "all" || client === "cursor") {
|
|
1168
|
+
const configPath = path9.join(cwd, ".cursor", "mcp.json");
|
|
1169
|
+
const existing = readJsonFile(configPath);
|
|
1170
|
+
if (existing?.mcpServers?.["harness"]) {
|
|
1171
|
+
skipped.push("Cursor");
|
|
1172
|
+
} else {
|
|
1173
|
+
writeMcpEntry(configPath, "harness", { command: "harness", args: ["mcp"] });
|
|
1174
|
+
configured.push("Cursor");
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
926
1177
|
return { configured, skipped, trustedFolder };
|
|
927
1178
|
}
|
|
1179
|
+
async function resolveCursorWithPicker(cwd, pick) {
|
|
1180
|
+
const configured = [];
|
|
1181
|
+
const skipped = [];
|
|
1182
|
+
const cursorConfigPath = path9.join(cwd, ".cursor", "mcp.json");
|
|
1183
|
+
const existing = readJsonFile(cursorConfigPath);
|
|
1184
|
+
if (existing?.mcpServers?.["harness"] && !pick) {
|
|
1185
|
+
skipped.push("Cursor");
|
|
1186
|
+
} else {
|
|
1187
|
+
const tools = pick ? await runCursorToolPicker() : CURSOR_CURATED_TOOLS;
|
|
1188
|
+
writeCursorMcpEntryWithTools(cursorConfigPath, tools);
|
|
1189
|
+
configured.push("Cursor");
|
|
1190
|
+
}
|
|
1191
|
+
return { configured, skipped };
|
|
1192
|
+
}
|
|
1193
|
+
function printMcpResult(configured, skipped, trustedFolder) {
|
|
1194
|
+
console.log("");
|
|
1195
|
+
if (configured.length > 0) {
|
|
1196
|
+
logger.success("MCP server configured!");
|
|
1197
|
+
console.log("");
|
|
1198
|
+
for (const name of configured) {
|
|
1199
|
+
console.log(` ${chalk.green("+")} ${name}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (trustedFolder) {
|
|
1203
|
+
console.log("");
|
|
1204
|
+
logger.info("Added project to Gemini trusted folders (~/.gemini/trustedFolders.json)");
|
|
1205
|
+
}
|
|
1206
|
+
if (skipped.length > 0) {
|
|
1207
|
+
console.log("");
|
|
1208
|
+
logger.info("Already configured:");
|
|
1209
|
+
for (const name of skipped) {
|
|
1210
|
+
console.log(` ${chalk.dim("-")} ${name}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
console.log("");
|
|
1214
|
+
console.log(chalk.bold("The harness MCP server provides:"));
|
|
1215
|
+
console.log(
|
|
1216
|
+
` - ${ALL_MCP_TOOLS.length} tools for validation, entropy detection, skill execution, graph querying, and more`
|
|
1217
|
+
);
|
|
1218
|
+
console.log(
|
|
1219
|
+
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
1220
|
+
);
|
|
1221
|
+
console.log("");
|
|
1222
|
+
console.log(`Run ${chalk.cyan("harness skill list")} to see available skills.`);
|
|
1223
|
+
console.log("");
|
|
1224
|
+
}
|
|
928
1225
|
function createSetupMcpCommand() {
|
|
929
|
-
return new Command7("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, all)", "all").action(async (opts, cmd) => {
|
|
1226
|
+
return new Command7("setup-mcp").description("Configure MCP server for AI agent integration").option("--client <client>", "Client to configure (claude, gemini, codex, cursor, all)", "all").option("--pick", "Launch interactive tool picker (Cursor only)").option("--yes", "Bypass interactive picker and use curated 25-tool set (Cursor only)").action(async (opts, cmd) => {
|
|
930
1227
|
const globalOpts = cmd.optsWithGlobals();
|
|
931
1228
|
const cwd = process.cwd();
|
|
932
|
-
|
|
1229
|
+
let configured;
|
|
1230
|
+
let skipped;
|
|
1231
|
+
let trustedFolder = false;
|
|
1232
|
+
if (opts.client === "cursor" && (opts.pick || opts.yes)) {
|
|
1233
|
+
({ configured, skipped } = await resolveCursorWithPicker(cwd, opts.pick));
|
|
1234
|
+
} else {
|
|
1235
|
+
({ configured, skipped, trustedFolder } = setupMcp(cwd, opts.client));
|
|
1236
|
+
}
|
|
933
1237
|
if (!globalOpts.quiet) {
|
|
934
|
-
|
|
935
|
-
if (configured.length > 0) {
|
|
936
|
-
logger.success("MCP server configured!");
|
|
937
|
-
console.log("");
|
|
938
|
-
for (const name of configured) {
|
|
939
|
-
console.log(` ${chalk.green("+")} ${name}`);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
if (trustedFolder) {
|
|
943
|
-
console.log("");
|
|
944
|
-
logger.info("Added project to Gemini trusted folders (~/.gemini/trustedFolders.json)");
|
|
945
|
-
}
|
|
946
|
-
if (skipped.length > 0) {
|
|
947
|
-
console.log("");
|
|
948
|
-
logger.info("Already configured:");
|
|
949
|
-
for (const name of skipped) {
|
|
950
|
-
console.log(` ${chalk.dim("-")} ${name}`);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
console.log("");
|
|
954
|
-
console.log(chalk.bold("The harness MCP server provides:"));
|
|
955
|
-
console.log(
|
|
956
|
-
" - 31 tools for validation, entropy detection, skill execution, graph querying, and more"
|
|
957
|
-
);
|
|
958
|
-
console.log(
|
|
959
|
-
" - 8 resources for project context, skills, rules, learnings, state, and graph data"
|
|
960
|
-
);
|
|
961
|
-
console.log("");
|
|
962
|
-
console.log(`Run ${chalk.cyan("harness skill list")} to see available skills.`);
|
|
963
|
-
console.log("");
|
|
1238
|
+
printMcpResult(configured, skipped, trustedFolder);
|
|
964
1239
|
}
|
|
965
1240
|
process.exit(ExitCode.SUCCESS);
|
|
966
1241
|
});
|
|
@@ -969,10 +1244,10 @@ function createSetupMcpCommand() {
|
|
|
969
1244
|
// src/commands/init.ts
|
|
970
1245
|
async function runInit(options) {
|
|
971
1246
|
const cwd = options.cwd ?? process.cwd();
|
|
972
|
-
const name = options.name ??
|
|
1247
|
+
const name = options.name ?? path10.basename(cwd);
|
|
973
1248
|
const force = options.force ?? false;
|
|
974
|
-
const configPath =
|
|
975
|
-
if (!force &&
|
|
1249
|
+
const configPath = path10.join(cwd, "harness.config.json");
|
|
1250
|
+
if (!force && fs4.existsSync(configPath)) {
|
|
976
1251
|
return Err(
|
|
977
1252
|
new CLIError("Project already initialized. Use --force to overwrite.", ExitCode.ERROR)
|
|
978
1253
|
);
|
|
@@ -1090,7 +1365,7 @@ function createInitCommand() {
|
|
|
1090
1365
|
|
|
1091
1366
|
// src/commands/cleanup.ts
|
|
1092
1367
|
import { Command as Command9 } from "commander";
|
|
1093
|
-
import * as
|
|
1368
|
+
import * as path11 from "path";
|
|
1094
1369
|
async function runCleanup(options) {
|
|
1095
1370
|
const cwd = options.cwd ?? process.cwd();
|
|
1096
1371
|
const type = options.type ?? "all";
|
|
@@ -1105,11 +1380,11 @@ async function runCleanup(options) {
|
|
|
1105
1380
|
patternViolations: [],
|
|
1106
1381
|
totalIssues: 0
|
|
1107
1382
|
};
|
|
1108
|
-
const rootDir =
|
|
1109
|
-
const docsDir =
|
|
1383
|
+
const rootDir = path11.resolve(cwd, config.rootDir);
|
|
1384
|
+
const docsDir = path11.resolve(cwd, config.docsDir);
|
|
1110
1385
|
const entropyConfig = {
|
|
1111
1386
|
rootDir,
|
|
1112
|
-
entryPoints: [
|
|
1387
|
+
entryPoints: [path11.join(rootDir, "src/index.ts")],
|
|
1113
1388
|
docPaths: [docsDir],
|
|
1114
1389
|
analyze: {
|
|
1115
1390
|
drift: type === "all" || type === "drift",
|
|
@@ -1209,7 +1484,7 @@ function createCleanupCommand() {
|
|
|
1209
1484
|
|
|
1210
1485
|
// src/commands/fix-drift.ts
|
|
1211
1486
|
import { Command as Command10 } from "commander";
|
|
1212
|
-
import * as
|
|
1487
|
+
import * as path12 from "path";
|
|
1213
1488
|
async function runFixDrift(options) {
|
|
1214
1489
|
const cwd = options.cwd ?? process.cwd();
|
|
1215
1490
|
const dryRun = options.dryRun !== false;
|
|
@@ -1218,11 +1493,11 @@ async function runFixDrift(options) {
|
|
|
1218
1493
|
return Err(configResult.error);
|
|
1219
1494
|
}
|
|
1220
1495
|
const config = configResult.value;
|
|
1221
|
-
const rootDir =
|
|
1222
|
-
const docsDir =
|
|
1496
|
+
const rootDir = path12.resolve(cwd, config.rootDir);
|
|
1497
|
+
const docsDir = path12.resolve(cwd, config.docsDir);
|
|
1223
1498
|
const entropyConfig = {
|
|
1224
1499
|
rootDir,
|
|
1225
|
-
entryPoints: [
|
|
1500
|
+
entryPoints: [path12.join(rootDir, "src/index.ts")],
|
|
1226
1501
|
docPaths: [docsDir],
|
|
1227
1502
|
analyze: {
|
|
1228
1503
|
drift: true,
|
|
@@ -1375,7 +1650,7 @@ import { Command as Command13 } from "commander";
|
|
|
1375
1650
|
|
|
1376
1651
|
// src/commands/agent/run.ts
|
|
1377
1652
|
import { Command as Command11 } from "commander";
|
|
1378
|
-
import * as
|
|
1653
|
+
import * as path13 from "path";
|
|
1379
1654
|
import * as childProcess from "child_process";
|
|
1380
1655
|
async function runAgentTask(task, options) {
|
|
1381
1656
|
const configResult = resolveConfig(options.configPath);
|
|
@@ -1447,7 +1722,7 @@ function createCommandExecutor() {
|
|
|
1447
1722
|
}
|
|
1448
1723
|
async function runPersonaMode(opts, quiet) {
|
|
1449
1724
|
const personasDir = resolvePersonasDir();
|
|
1450
|
-
const filePath =
|
|
1725
|
+
const filePath = path13.join(personasDir, `${opts.persona}.yaml`);
|
|
1451
1726
|
const personaResult = loadPersona(filePath);
|
|
1452
1727
|
if (!personaResult.ok) {
|
|
1453
1728
|
logger.error(personaResult.error.message);
|
|
@@ -1619,8 +1894,8 @@ function createAgentCommand() {
|
|
|
1619
1894
|
|
|
1620
1895
|
// src/commands/add.ts
|
|
1621
1896
|
import { Command as Command14 } from "commander";
|
|
1622
|
-
import * as
|
|
1623
|
-
import * as
|
|
1897
|
+
import * as fs5 from "fs";
|
|
1898
|
+
import * as path14 from "path";
|
|
1624
1899
|
var LAYER_INDEX_TEMPLATE = (name) => `// ${name} layer
|
|
1625
1900
|
// Add your ${name} exports here
|
|
1626
1901
|
|
|
@@ -1664,62 +1939,62 @@ async function runAdd(componentType, name, options) {
|
|
|
1664
1939
|
try {
|
|
1665
1940
|
switch (componentType) {
|
|
1666
1941
|
case "layer": {
|
|
1667
|
-
const layerDir =
|
|
1668
|
-
if (!
|
|
1669
|
-
|
|
1942
|
+
const layerDir = path14.join(cwd, "src", name);
|
|
1943
|
+
if (!fs5.existsSync(layerDir)) {
|
|
1944
|
+
fs5.mkdirSync(layerDir, { recursive: true });
|
|
1670
1945
|
created.push(`src/${name}/`);
|
|
1671
1946
|
}
|
|
1672
|
-
const indexPath =
|
|
1673
|
-
if (!
|
|
1674
|
-
|
|
1947
|
+
const indexPath = path14.join(layerDir, "index.ts");
|
|
1948
|
+
if (!fs5.existsSync(indexPath)) {
|
|
1949
|
+
fs5.writeFileSync(indexPath, LAYER_INDEX_TEMPLATE(name));
|
|
1675
1950
|
created.push(`src/${name}/index.ts`);
|
|
1676
1951
|
}
|
|
1677
1952
|
break;
|
|
1678
1953
|
}
|
|
1679
1954
|
case "module": {
|
|
1680
|
-
const modulePath =
|
|
1681
|
-
if (
|
|
1955
|
+
const modulePath = path14.join(cwd, "src", `${name}.ts`);
|
|
1956
|
+
if (fs5.existsSync(modulePath)) {
|
|
1682
1957
|
return Err(new CLIError(`Module ${name} already exists`, ExitCode.ERROR));
|
|
1683
1958
|
}
|
|
1684
|
-
|
|
1959
|
+
fs5.writeFileSync(modulePath, MODULE_TEMPLATE(name));
|
|
1685
1960
|
created.push(`src/${name}.ts`);
|
|
1686
1961
|
break;
|
|
1687
1962
|
}
|
|
1688
1963
|
case "doc": {
|
|
1689
1964
|
const configDocsDir = configResult.ok ? configResult.value.docsDir : "./docs";
|
|
1690
|
-
const docsDir =
|
|
1691
|
-
if (!
|
|
1692
|
-
|
|
1965
|
+
const docsDir = path14.resolve(cwd, configDocsDir);
|
|
1966
|
+
if (!fs5.existsSync(docsDir)) {
|
|
1967
|
+
fs5.mkdirSync(docsDir, { recursive: true });
|
|
1693
1968
|
}
|
|
1694
|
-
const docPath =
|
|
1695
|
-
if (
|
|
1969
|
+
const docPath = path14.join(docsDir, `${name}.md`);
|
|
1970
|
+
if (fs5.existsSync(docPath)) {
|
|
1696
1971
|
return Err(new CLIError(`Doc ${name} already exists`, ExitCode.ERROR));
|
|
1697
1972
|
}
|
|
1698
|
-
|
|
1973
|
+
fs5.writeFileSync(docPath, DOC_TEMPLATE(name));
|
|
1699
1974
|
created.push(`${configDocsDir.replace(/^\.[\\/]/, "")}/${name}.md`);
|
|
1700
1975
|
break;
|
|
1701
1976
|
}
|
|
1702
1977
|
case "skill": {
|
|
1703
|
-
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-
|
|
1978
|
+
const { generateSkillFiles: generateSkillFiles2 } = await import("./create-skill-NDXQSTIK.js");
|
|
1704
1979
|
generateSkillFiles2({
|
|
1705
1980
|
name,
|
|
1706
1981
|
description: `${name} skill`,
|
|
1707
|
-
outputDir:
|
|
1982
|
+
outputDir: path14.join(cwd, "agents", "skills", "claude-code")
|
|
1708
1983
|
});
|
|
1709
1984
|
created.push(`agents/skills/claude-code/${name}/skill.yaml`);
|
|
1710
1985
|
created.push(`agents/skills/claude-code/${name}/SKILL.md`);
|
|
1711
1986
|
break;
|
|
1712
1987
|
}
|
|
1713
1988
|
case "persona": {
|
|
1714
|
-
const personasDir =
|
|
1715
|
-
if (!
|
|
1716
|
-
|
|
1989
|
+
const personasDir = path14.join(cwd, "agents", "personas");
|
|
1990
|
+
if (!fs5.existsSync(personasDir)) {
|
|
1991
|
+
fs5.mkdirSync(personasDir, { recursive: true });
|
|
1717
1992
|
}
|
|
1718
|
-
const personaPath =
|
|
1719
|
-
if (
|
|
1993
|
+
const personaPath = path14.join(personasDir, `${name}.yaml`);
|
|
1994
|
+
if (fs5.existsSync(personaPath)) {
|
|
1720
1995
|
return Err(new CLIError(`Persona ${name} already exists`, ExitCode.ERROR));
|
|
1721
1996
|
}
|
|
1722
|
-
|
|
1997
|
+
fs5.writeFileSync(
|
|
1723
1998
|
personaPath,
|
|
1724
1999
|
`name: ${name}
|
|
1725
2000
|
description: ${name} persona
|
|
@@ -1905,46 +2180,46 @@ function createListCommand() {
|
|
|
1905
2180
|
|
|
1906
2181
|
// src/commands/persona/generate.ts
|
|
1907
2182
|
import { Command as Command19 } from "commander";
|
|
1908
|
-
import * as
|
|
1909
|
-
import * as
|
|
2183
|
+
import * as fs6 from "fs";
|
|
2184
|
+
import * as path15 from "path";
|
|
1910
2185
|
function createGenerateCommand2() {
|
|
1911
2186
|
return new Command19("generate").description("Generate artifacts from a persona config").argument("<name>", "Persona name (e.g., architecture-enforcer)").option("--output-dir <dir>", "Output directory", ".").option("--only <type>", "Generate only: ci, agents-md, runtime").action(async (name, opts, cmd) => {
|
|
1912
2187
|
const globalOpts = cmd.optsWithGlobals();
|
|
1913
2188
|
const personasDir = resolvePersonasDir();
|
|
1914
|
-
const filePath =
|
|
2189
|
+
const filePath = path15.join(personasDir, `${name}.yaml`);
|
|
1915
2190
|
const personaResult = loadPersona(filePath);
|
|
1916
2191
|
if (!personaResult.ok) {
|
|
1917
2192
|
logger.error(personaResult.error.message);
|
|
1918
2193
|
process.exit(ExitCode.ERROR);
|
|
1919
2194
|
}
|
|
1920
2195
|
const persona = personaResult.value;
|
|
1921
|
-
const outputDir =
|
|
2196
|
+
const outputDir = path15.resolve(opts.outputDir);
|
|
1922
2197
|
const slug = toKebabCase(persona.name);
|
|
1923
2198
|
const only = opts.only;
|
|
1924
2199
|
const generated = [];
|
|
1925
2200
|
if (!only || only === "runtime") {
|
|
1926
2201
|
const result = generateRuntime(persona);
|
|
1927
2202
|
if (result.ok) {
|
|
1928
|
-
const outPath =
|
|
1929
|
-
|
|
1930
|
-
|
|
2203
|
+
const outPath = path15.join(outputDir, `${slug}.runtime.json`);
|
|
2204
|
+
fs6.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
2205
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1931
2206
|
generated.push(outPath);
|
|
1932
2207
|
}
|
|
1933
2208
|
}
|
|
1934
2209
|
if (!only || only === "agents-md") {
|
|
1935
2210
|
const result = generateAgentsMd(persona);
|
|
1936
2211
|
if (result.ok) {
|
|
1937
|
-
const outPath =
|
|
1938
|
-
|
|
2212
|
+
const outPath = path15.join(outputDir, `${slug}.agents.md`);
|
|
2213
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1939
2214
|
generated.push(outPath);
|
|
1940
2215
|
}
|
|
1941
2216
|
}
|
|
1942
2217
|
if (!only || only === "ci") {
|
|
1943
2218
|
const result = generateCIWorkflow(persona, "github");
|
|
1944
2219
|
if (result.ok) {
|
|
1945
|
-
const outPath =
|
|
1946
|
-
|
|
1947
|
-
|
|
2220
|
+
const outPath = path15.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
2221
|
+
fs6.mkdirSync(path15.dirname(outPath), { recursive: true });
|
|
2222
|
+
fs6.writeFileSync(outPath, result.value);
|
|
1948
2223
|
generated.push(outPath);
|
|
1949
2224
|
}
|
|
1950
2225
|
}
|
|
@@ -1969,13 +2244,13 @@ import { Command as Command28 } from "commander";
|
|
|
1969
2244
|
|
|
1970
2245
|
// src/commands/skill/list.ts
|
|
1971
2246
|
import { Command as Command21 } from "commander";
|
|
1972
|
-
import * as
|
|
1973
|
-
import * as
|
|
2247
|
+
import * as fs8 from "fs";
|
|
2248
|
+
import * as path17 from "path";
|
|
1974
2249
|
import { parse } from "yaml";
|
|
1975
2250
|
|
|
1976
2251
|
// src/registry/lockfile.ts
|
|
1977
|
-
import * as
|
|
1978
|
-
import * as
|
|
2252
|
+
import * as fs7 from "fs";
|
|
2253
|
+
import * as path16 from "path";
|
|
1979
2254
|
function createEmptyLockfile() {
|
|
1980
2255
|
return { version: 1, skills: {} };
|
|
1981
2256
|
}
|
|
@@ -1995,10 +2270,10 @@ function sortedStringify(obj) {
|
|
|
1995
2270
|
);
|
|
1996
2271
|
}
|
|
1997
2272
|
function readLockfile2(filePath) {
|
|
1998
|
-
if (!
|
|
2273
|
+
if (!fs7.existsSync(filePath)) {
|
|
1999
2274
|
return createEmptyLockfile();
|
|
2000
2275
|
}
|
|
2001
|
-
const raw =
|
|
2276
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
2002
2277
|
let parsed;
|
|
2003
2278
|
try {
|
|
2004
2279
|
parsed = JSON.parse(raw);
|
|
@@ -2015,9 +2290,9 @@ function readLockfile2(filePath) {
|
|
|
2015
2290
|
return parsed;
|
|
2016
2291
|
}
|
|
2017
2292
|
function writeLockfile2(filePath, lockfile) {
|
|
2018
|
-
const dir =
|
|
2019
|
-
|
|
2020
|
-
|
|
2293
|
+
const dir = path16.dirname(filePath);
|
|
2294
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
2295
|
+
fs7.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
2021
2296
|
}
|
|
2022
2297
|
function updateLockfileEntry(lockfile, name, entry) {
|
|
2023
2298
|
return {
|
|
@@ -2041,14 +2316,14 @@ function removeLockfileEntry(lockfile, name) {
|
|
|
2041
2316
|
|
|
2042
2317
|
// src/commands/skill/list.ts
|
|
2043
2318
|
function scanDirectory(dirPath, source) {
|
|
2044
|
-
if (!
|
|
2045
|
-
const entries =
|
|
2319
|
+
if (!fs8.existsSync(dirPath)) return [];
|
|
2320
|
+
const entries = fs8.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2046
2321
|
const skills = [];
|
|
2047
2322
|
for (const name of entries) {
|
|
2048
|
-
const yamlPath =
|
|
2049
|
-
if (!
|
|
2323
|
+
const yamlPath = path17.join(dirPath, name, "skill.yaml");
|
|
2324
|
+
if (!fs8.existsSync(yamlPath)) continue;
|
|
2050
2325
|
try {
|
|
2051
|
-
const raw =
|
|
2326
|
+
const raw = fs8.readFileSync(yamlPath, "utf-8");
|
|
2052
2327
|
const parsed = parse(raw);
|
|
2053
2328
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2054
2329
|
if (result.success) {
|
|
@@ -2066,10 +2341,10 @@ function scanDirectory(dirPath, source) {
|
|
|
2066
2341
|
}
|
|
2067
2342
|
function collectCommunitySkills(seen, allSkills) {
|
|
2068
2343
|
const globalDir = resolveGlobalSkillsDir();
|
|
2069
|
-
const skillsDir =
|
|
2070
|
-
const communityBase =
|
|
2071
|
-
const communityPlatformDir =
|
|
2072
|
-
const lockfilePath =
|
|
2344
|
+
const skillsDir = path17.dirname(globalDir);
|
|
2345
|
+
const communityBase = path17.join(skillsDir, "community");
|
|
2346
|
+
const communityPlatformDir = path17.join(communityBase, "claude-code");
|
|
2347
|
+
const lockfilePath = path17.join(communityBase, "skills-lock.json");
|
|
2073
2348
|
const lockfile = readLockfile2(lockfilePath);
|
|
2074
2349
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
2075
2350
|
for (const skill of communitySkills) {
|
|
@@ -2155,20 +2430,20 @@ function createListCommand2() {
|
|
|
2155
2430
|
|
|
2156
2431
|
// src/commands/skill/run.ts
|
|
2157
2432
|
import { Command as Command22 } from "commander";
|
|
2158
|
-
import * as
|
|
2159
|
-
import * as
|
|
2433
|
+
import * as fs9 from "fs";
|
|
2434
|
+
import * as path18 from "path";
|
|
2160
2435
|
import { parse as parse2 } from "yaml";
|
|
2161
2436
|
|
|
2162
2437
|
// src/skill/complexity.ts
|
|
2163
2438
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2164
2439
|
function evaluateSignals(signals) {
|
|
2165
|
-
if (signals.fileCount >= 3) return "
|
|
2166
|
-
if (signals.newDir) return "
|
|
2167
|
-
if (signals.newDep) return "
|
|
2168
|
-
if (signals.fileCount <= 1) return "
|
|
2169
|
-
if (signals.testOnly) return "
|
|
2170
|
-
if (signals.docsOnly) return "
|
|
2171
|
-
return "
|
|
2440
|
+
if (signals.fileCount >= 3) return "thorough";
|
|
2441
|
+
if (signals.newDir) return "thorough";
|
|
2442
|
+
if (signals.newDep) return "thorough";
|
|
2443
|
+
if (signals.fileCount <= 1) return "fast";
|
|
2444
|
+
if (signals.testOnly) return "fast";
|
|
2445
|
+
if (signals.docsOnly) return "fast";
|
|
2446
|
+
return "thorough";
|
|
2172
2447
|
}
|
|
2173
2448
|
function detectComplexity(projectPath) {
|
|
2174
2449
|
try {
|
|
@@ -2201,7 +2476,7 @@ function detectComplexity(projectPath) {
|
|
|
2201
2476
|
};
|
|
2202
2477
|
return evaluateSignals(signals);
|
|
2203
2478
|
} catch {
|
|
2204
|
-
return "
|
|
2479
|
+
return "thorough";
|
|
2205
2480
|
}
|
|
2206
2481
|
}
|
|
2207
2482
|
|
|
@@ -2211,8 +2486,8 @@ function buildPreamble(options) {
|
|
|
2211
2486
|
if (options.complexity && options.phases && options.phases.length > 0) {
|
|
2212
2487
|
const lines = [`## Active Phases (complexity: ${options.complexity})`];
|
|
2213
2488
|
for (const phase of options.phases) {
|
|
2214
|
-
if (options.complexity === "
|
|
2215
|
-
lines.push(`- ~~${phase.name.toUpperCase()}~~ (skipped in
|
|
2489
|
+
if (options.complexity === "fast" && !phase.required) {
|
|
2490
|
+
lines.push(`- ~~${phase.name.toUpperCase()}~~ (skipped in fast mode)`);
|
|
2216
2491
|
} else {
|
|
2217
2492
|
lines.push(`- ${phase.name.toUpperCase()} (${phase.required ? "required" : "optional"})`);
|
|
2218
2493
|
}
|
|
@@ -2244,10 +2519,10 @@ ${options.priorState}`);
|
|
|
2244
2519
|
|
|
2245
2520
|
// src/commands/skill/run.ts
|
|
2246
2521
|
function loadSkillMetadata(skillDir) {
|
|
2247
|
-
const yamlPath =
|
|
2248
|
-
if (!
|
|
2522
|
+
const yamlPath = path18.join(skillDir, "skill.yaml");
|
|
2523
|
+
if (!fs9.existsSync(yamlPath)) return null;
|
|
2249
2524
|
try {
|
|
2250
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
2525
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs9.readFileSync(yamlPath, "utf-8")));
|
|
2251
2526
|
return result.success ? result.data : null;
|
|
2252
2527
|
} catch {
|
|
2253
2528
|
return null;
|
|
@@ -2255,26 +2530,26 @@ function loadSkillMetadata(skillDir) {
|
|
|
2255
2530
|
}
|
|
2256
2531
|
function resolveComplexity(metadata, requested, projectPath) {
|
|
2257
2532
|
if (!metadata?.phases || metadata.phases.length === 0) return void 0;
|
|
2258
|
-
if (requested === "
|
|
2533
|
+
if (requested === "standard") return detectComplexity(projectPath);
|
|
2259
2534
|
return requested;
|
|
2260
2535
|
}
|
|
2261
2536
|
function loadPrinciples(projectPath) {
|
|
2262
|
-
const principlesPath =
|
|
2263
|
-
return
|
|
2537
|
+
const principlesPath = path18.join(projectPath, "docs", "principles.md");
|
|
2538
|
+
return fs9.existsSync(principlesPath) ? fs9.readFileSync(principlesPath, "utf-8") : void 0;
|
|
2264
2539
|
}
|
|
2265
2540
|
function readMostRecentFileInDir(dirPath) {
|
|
2266
|
-
const files =
|
|
2267
|
-
if (files.length > 0) return
|
|
2541
|
+
const files = fs9.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs9.statSync(path18.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
2542
|
+
if (files.length > 0) return fs9.readFileSync(path18.join(dirPath, files[0].name), "utf-8");
|
|
2268
2543
|
return void 0;
|
|
2269
2544
|
}
|
|
2270
2545
|
function loadPriorState(metadata, projectPath) {
|
|
2271
2546
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
2272
2547
|
for (const stateFilePath of metadata.state.files) {
|
|
2273
|
-
const fullPath =
|
|
2274
|
-
if (!
|
|
2275
|
-
const stat =
|
|
2548
|
+
const fullPath = path18.join(projectPath, stateFilePath);
|
|
2549
|
+
if (!fs9.existsSync(fullPath)) continue;
|
|
2550
|
+
const stat = fs9.statSync(fullPath);
|
|
2276
2551
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
2277
|
-
return
|
|
2552
|
+
return fs9.readFileSync(fullPath, "utf-8");
|
|
2278
2553
|
}
|
|
2279
2554
|
return void 0;
|
|
2280
2555
|
}
|
|
@@ -2294,9 +2569,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
2294
2569
|
}
|
|
2295
2570
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
2296
2571
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
2297
|
-
const stateFile =
|
|
2298
|
-
if (!
|
|
2299
|
-
const stateContent =
|
|
2572
|
+
const stateFile = path18.join(projectPath, ".harness", "state.json");
|
|
2573
|
+
if (!fs9.existsSync(stateFile)) return content;
|
|
2574
|
+
const stateContent = fs9.readFileSync(stateFile, "utf-8");
|
|
2300
2575
|
return content + `
|
|
2301
2576
|
|
|
2302
2577
|
---
|
|
@@ -2307,19 +2582,19 @@ ${stateContent}
|
|
|
2307
2582
|
`;
|
|
2308
2583
|
}
|
|
2309
2584
|
function createRunCommand2() {
|
|
2310
|
-
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "
|
|
2585
|
+
return new Command22("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Rigor level: fast, standard, thorough", "standard").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts, _cmd) => {
|
|
2311
2586
|
const skillsDir = resolveSkillsDir();
|
|
2312
|
-
const skillDir =
|
|
2313
|
-
if (!
|
|
2587
|
+
const skillDir = path18.join(skillsDir, name);
|
|
2588
|
+
if (!fs9.existsSync(skillDir)) {
|
|
2314
2589
|
logger.error(`Skill not found: ${name}`);
|
|
2315
2590
|
process.exit(ExitCode.ERROR);
|
|
2316
2591
|
return;
|
|
2317
2592
|
}
|
|
2318
2593
|
const metadata = loadSkillMetadata(skillDir);
|
|
2319
|
-
const projectPath = opts.path ?
|
|
2594
|
+
const projectPath = opts.path ? path18.resolve(opts.path) : process.cwd();
|
|
2320
2595
|
const complexity = resolveComplexity(
|
|
2321
2596
|
metadata,
|
|
2322
|
-
opts.complexity ?? "
|
|
2597
|
+
opts.complexity ?? "standard",
|
|
2323
2598
|
projectPath
|
|
2324
2599
|
);
|
|
2325
2600
|
const principles = loadPrinciples(projectPath);
|
|
@@ -2343,14 +2618,14 @@ function createRunCommand2() {
|
|
|
2343
2618
|
...stateWarning !== void 0 && { stateWarning },
|
|
2344
2619
|
party: opts.party
|
|
2345
2620
|
});
|
|
2346
|
-
const skillMdPath =
|
|
2347
|
-
if (!
|
|
2621
|
+
const skillMdPath = path18.join(skillDir, "SKILL.md");
|
|
2622
|
+
if (!fs9.existsSync(skillMdPath)) {
|
|
2348
2623
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
2349
2624
|
process.exit(ExitCode.ERROR);
|
|
2350
2625
|
return;
|
|
2351
2626
|
}
|
|
2352
2627
|
const content = appendProjectState(
|
|
2353
|
-
|
|
2628
|
+
fs9.readFileSync(skillMdPath, "utf-8"),
|
|
2354
2629
|
metadata,
|
|
2355
2630
|
projectPath,
|
|
2356
2631
|
!!opts.path
|
|
@@ -2362,8 +2637,8 @@ function createRunCommand2() {
|
|
|
2362
2637
|
|
|
2363
2638
|
// src/commands/skill/validate.ts
|
|
2364
2639
|
import { Command as Command23 } from "commander";
|
|
2365
|
-
import * as
|
|
2366
|
-
import * as
|
|
2640
|
+
import * as fs10 from "fs";
|
|
2641
|
+
import * as path19 from "path";
|
|
2367
2642
|
import { parse as parse3 } from "yaml";
|
|
2368
2643
|
var REQUIRED_SECTIONS = [
|
|
2369
2644
|
"## When to Use",
|
|
@@ -2373,11 +2648,11 @@ var REQUIRED_SECTIONS = [
|
|
|
2373
2648
|
"## Examples"
|
|
2374
2649
|
];
|
|
2375
2650
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
2376
|
-
if (!
|
|
2651
|
+
if (!fs10.existsSync(skillMdPath)) {
|
|
2377
2652
|
errors.push(`${name}: missing SKILL.md`);
|
|
2378
2653
|
return;
|
|
2379
2654
|
}
|
|
2380
|
-
const mdContent =
|
|
2655
|
+
const mdContent = fs10.readFileSync(skillMdPath, "utf-8");
|
|
2381
2656
|
for (const section of REQUIRED_SECTIONS) {
|
|
2382
2657
|
if (!mdContent.includes(section)) {
|
|
2383
2658
|
errors.push(`${name}/SKILL.md: missing section "${section}"`);
|
|
@@ -2394,20 +2669,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
2394
2669
|
}
|
|
2395
2670
|
}
|
|
2396
2671
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
2397
|
-
const skillDir =
|
|
2398
|
-
const yamlPath =
|
|
2399
|
-
if (!
|
|
2672
|
+
const skillDir = path19.join(skillsDir, name);
|
|
2673
|
+
const yamlPath = path19.join(skillDir, "skill.yaml");
|
|
2674
|
+
if (!fs10.existsSync(yamlPath)) {
|
|
2400
2675
|
errors.push(`${name}: missing skill.yaml`);
|
|
2401
2676
|
return false;
|
|
2402
2677
|
}
|
|
2403
2678
|
try {
|
|
2404
|
-
const raw =
|
|
2679
|
+
const raw = fs10.readFileSync(yamlPath, "utf-8");
|
|
2405
2680
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
2406
2681
|
if (!result.success) {
|
|
2407
2682
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
2408
2683
|
return false;
|
|
2409
2684
|
}
|
|
2410
|
-
validateSkillMd(name,
|
|
2685
|
+
validateSkillMd(name, path19.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
2411
2686
|
return true;
|
|
2412
2687
|
} catch (e) {
|
|
2413
2688
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -2418,12 +2693,12 @@ function createValidateCommand3() {
|
|
|
2418
2693
|
return new Command23("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
2419
2694
|
const globalOpts = cmd.optsWithGlobals();
|
|
2420
2695
|
const skillsDir = resolveSkillsDir();
|
|
2421
|
-
if (!
|
|
2696
|
+
if (!fs10.existsSync(skillsDir)) {
|
|
2422
2697
|
logger.info("No skills directory found.");
|
|
2423
2698
|
process.exit(ExitCode.SUCCESS);
|
|
2424
2699
|
return;
|
|
2425
2700
|
}
|
|
2426
|
-
const entries =
|
|
2701
|
+
const entries = fs10.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2427
2702
|
const errors = [];
|
|
2428
2703
|
let validated = 0;
|
|
2429
2704
|
for (const name of entries) {
|
|
@@ -2446,27 +2721,27 @@ function createValidateCommand3() {
|
|
|
2446
2721
|
|
|
2447
2722
|
// src/commands/skill/info.ts
|
|
2448
2723
|
import { Command as Command24 } from "commander";
|
|
2449
|
-
import * as
|
|
2450
|
-
import * as
|
|
2724
|
+
import * as fs11 from "fs";
|
|
2725
|
+
import * as path20 from "path";
|
|
2451
2726
|
import { parse as parse4 } from "yaml";
|
|
2452
2727
|
function createInfoCommand() {
|
|
2453
2728
|
return new Command24("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
2454
2729
|
const globalOpts = cmd.optsWithGlobals();
|
|
2455
2730
|
const skillsDir = resolveSkillsDir();
|
|
2456
|
-
const skillDir =
|
|
2457
|
-
if (!
|
|
2731
|
+
const skillDir = path20.join(skillsDir, name);
|
|
2732
|
+
if (!fs11.existsSync(skillDir)) {
|
|
2458
2733
|
logger.error(`Skill not found: ${name}`);
|
|
2459
2734
|
process.exit(ExitCode.ERROR);
|
|
2460
2735
|
return;
|
|
2461
2736
|
}
|
|
2462
|
-
const yamlPath =
|
|
2463
|
-
if (!
|
|
2737
|
+
const yamlPath = path20.join(skillDir, "skill.yaml");
|
|
2738
|
+
if (!fs11.existsSync(yamlPath)) {
|
|
2464
2739
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
2465
2740
|
process.exit(ExitCode.ERROR);
|
|
2466
2741
|
return;
|
|
2467
2742
|
}
|
|
2468
2743
|
try {
|
|
2469
|
-
const raw =
|
|
2744
|
+
const raw = fs11.readFileSync(yamlPath, "utf-8");
|
|
2470
2745
|
const parsed = parse4(raw);
|
|
2471
2746
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2472
2747
|
if (!result.success) {
|
|
@@ -2509,8 +2784,8 @@ function createInfoCommand() {
|
|
|
2509
2784
|
import { Command as Command25 } from "commander";
|
|
2510
2785
|
|
|
2511
2786
|
// src/registry/npm-client.ts
|
|
2512
|
-
import * as
|
|
2513
|
-
import * as
|
|
2787
|
+
import * as fs12 from "fs";
|
|
2788
|
+
import * as path21 from "path";
|
|
2514
2789
|
import * as os2 from "os";
|
|
2515
2790
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2516
2791
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -2533,10 +2808,10 @@ function extractSkillName(packageName) {
|
|
|
2533
2808
|
function readNpmrcToken(registryUrl) {
|
|
2534
2809
|
const { hostname, pathname } = new URL(registryUrl);
|
|
2535
2810
|
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
2536
|
-
const candidates = [
|
|
2811
|
+
const candidates = [path21.join(process.cwd(), ".npmrc"), path21.join(os2.homedir(), ".npmrc")];
|
|
2537
2812
|
for (const npmrcPath of candidates) {
|
|
2538
2813
|
try {
|
|
2539
|
-
const content =
|
|
2814
|
+
const content = fs12.readFileSync(npmrcPath, "utf-8");
|
|
2540
2815
|
for (const line of content.split("\n")) {
|
|
2541
2816
|
const trimmed = line.trim();
|
|
2542
2817
|
if (trimmed.startsWith(registryPath)) {
|
|
@@ -2681,8 +2956,8 @@ Found ${results.length} skill(s):
|
|
|
2681
2956
|
|
|
2682
2957
|
// src/commands/skill/create.ts
|
|
2683
2958
|
import { Command as Command26 } from "commander";
|
|
2684
|
-
import * as
|
|
2685
|
-
import * as
|
|
2959
|
+
import * as path22 from "path";
|
|
2960
|
+
import * as fs13 from "fs";
|
|
2686
2961
|
import YAML from "yaml";
|
|
2687
2962
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2688
2963
|
function buildReadme(name, description) {
|
|
@@ -2766,22 +3041,22 @@ function runCreate(name, opts) {
|
|
|
2766
3041
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
2767
3042
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
2768
3043
|
}
|
|
2769
|
-
const baseDir = opts.outputDir ??
|
|
2770
|
-
const skillDir =
|
|
2771
|
-
if (
|
|
3044
|
+
const baseDir = opts.outputDir ?? path22.join(process.cwd(), "agents", "skills", "claude-code");
|
|
3045
|
+
const skillDir = path22.join(baseDir, name);
|
|
3046
|
+
if (fs13.existsSync(skillDir)) {
|
|
2772
3047
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
2773
3048
|
}
|
|
2774
|
-
|
|
3049
|
+
fs13.mkdirSync(skillDir, { recursive: true });
|
|
2775
3050
|
const description = opts.description || `A community skill: ${name}`;
|
|
2776
3051
|
const skillYaml = buildSkillYaml(name, opts);
|
|
2777
|
-
const skillYamlPath =
|
|
2778
|
-
|
|
3052
|
+
const skillYamlPath = path22.join(skillDir, "skill.yaml");
|
|
3053
|
+
fs13.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
2779
3054
|
const skillMd = buildSkillMd(name, description);
|
|
2780
|
-
const skillMdPath =
|
|
2781
|
-
|
|
3055
|
+
const skillMdPath = path22.join(skillDir, "SKILL.md");
|
|
3056
|
+
fs13.writeFileSync(skillMdPath, skillMd);
|
|
2782
3057
|
const readme = buildReadme(name, description);
|
|
2783
|
-
const readmePath =
|
|
2784
|
-
|
|
3058
|
+
const readmePath = path22.join(skillDir, "README.md");
|
|
3059
|
+
fs13.writeFileSync(readmePath, readme);
|
|
2785
3060
|
return {
|
|
2786
3061
|
name,
|
|
2787
3062
|
directory: skillDir,
|
|
@@ -2809,7 +3084,7 @@ function createCreateCommand() {
|
|
|
2809
3084
|
logger.info(`
|
|
2810
3085
|
Next steps:`);
|
|
2811
3086
|
logger.info(
|
|
2812
|
-
` 1. Edit ${
|
|
3087
|
+
` 1. Edit ${path22.join(result.directory, "SKILL.md")} with your skill content`
|
|
2813
3088
|
);
|
|
2814
3089
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
2815
3090
|
logger.info(` 3. Run: harness skills publish`);
|
|
@@ -2823,28 +3098,28 @@ Next steps:`);
|
|
|
2823
3098
|
|
|
2824
3099
|
// src/commands/skill/publish.ts
|
|
2825
3100
|
import { Command as Command27 } from "commander";
|
|
2826
|
-
import * as
|
|
2827
|
-
import * as
|
|
3101
|
+
import * as fs16 from "fs";
|
|
3102
|
+
import * as path25 from "path";
|
|
2828
3103
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2829
3104
|
|
|
2830
3105
|
// src/registry/validator.ts
|
|
2831
|
-
import * as
|
|
2832
|
-
import * as
|
|
3106
|
+
import * as fs15 from "fs";
|
|
3107
|
+
import * as path24 from "path";
|
|
2833
3108
|
import { parse as parse5 } from "yaml";
|
|
2834
3109
|
import semver from "semver";
|
|
2835
3110
|
|
|
2836
3111
|
// src/registry/bundled-skills.ts
|
|
2837
|
-
import * as
|
|
2838
|
-
import * as
|
|
3112
|
+
import * as fs14 from "fs";
|
|
3113
|
+
import * as path23 from "path";
|
|
2839
3114
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
2840
|
-
if (!
|
|
3115
|
+
if (!fs14.existsSync(bundledSkillsDir)) {
|
|
2841
3116
|
return /* @__PURE__ */ new Set();
|
|
2842
3117
|
}
|
|
2843
|
-
const entries =
|
|
3118
|
+
const entries = fs14.readdirSync(bundledSkillsDir);
|
|
2844
3119
|
const names = /* @__PURE__ */ new Set();
|
|
2845
3120
|
for (const entry of entries) {
|
|
2846
3121
|
try {
|
|
2847
|
-
const stat =
|
|
3122
|
+
const stat = fs14.statSync(path23.join(bundledSkillsDir, String(entry)));
|
|
2848
3123
|
if (stat.isDirectory()) {
|
|
2849
3124
|
names.add(String(entry));
|
|
2850
3125
|
}
|
|
@@ -2857,14 +3132,14 @@ function getBundledSkillNames(bundledSkillsDir) {
|
|
|
2857
3132
|
// src/registry/validator.ts
|
|
2858
3133
|
async function validateForPublish(skillDir, registryUrl) {
|
|
2859
3134
|
const errors = [];
|
|
2860
|
-
const skillYamlPath =
|
|
2861
|
-
if (!
|
|
3135
|
+
const skillYamlPath = path24.join(skillDir, "skill.yaml");
|
|
3136
|
+
if (!fs15.existsSync(skillYamlPath)) {
|
|
2862
3137
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
2863
3138
|
return { valid: false, errors };
|
|
2864
3139
|
}
|
|
2865
3140
|
let skillMeta;
|
|
2866
3141
|
try {
|
|
2867
|
-
const raw =
|
|
3142
|
+
const raw = fs15.readFileSync(skillYamlPath, "utf-8");
|
|
2868
3143
|
const parsed = parse5(raw);
|
|
2869
3144
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2870
3145
|
if (!result.success) {
|
|
@@ -2886,11 +3161,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
2886
3161
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
2887
3162
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
2888
3163
|
}
|
|
2889
|
-
const skillMdPath =
|
|
2890
|
-
if (!
|
|
3164
|
+
const skillMdPath = path24.join(skillDir, "SKILL.md");
|
|
3165
|
+
if (!fs15.existsSync(skillMdPath)) {
|
|
2891
3166
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
2892
3167
|
} else {
|
|
2893
|
-
const content =
|
|
3168
|
+
const content = fs15.readFileSync(skillMdPath, "utf-8");
|
|
2894
3169
|
if (!content.includes("## When to Use")) {
|
|
2895
3170
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
2896
3171
|
}
|
|
@@ -2970,11 +3245,11 @@ ${errorList}`);
|
|
|
2970
3245
|
}
|
|
2971
3246
|
const meta = validation.skillMeta;
|
|
2972
3247
|
const pkg = derivePackageJson(meta);
|
|
2973
|
-
const pkgPath =
|
|
2974
|
-
|
|
2975
|
-
const readmePath =
|
|
2976
|
-
if (!
|
|
2977
|
-
const skillMdContent =
|
|
3248
|
+
const pkgPath = path25.join(skillDir, "package.json");
|
|
3249
|
+
fs16.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
3250
|
+
const readmePath = path25.join(skillDir, "README.md");
|
|
3251
|
+
if (!fs16.existsSync(readmePath)) {
|
|
3252
|
+
const skillMdContent = fs16.readFileSync(path25.join(skillDir, "SKILL.md"), "utf-8");
|
|
2978
3253
|
const readme = `# ${pkg.name}
|
|
2979
3254
|
|
|
2980
3255
|
${meta.description}
|
|
@@ -2988,7 +3263,7 @@ harness install ${meta.name}
|
|
|
2988
3263
|
---
|
|
2989
3264
|
|
|
2990
3265
|
${skillMdContent}`;
|
|
2991
|
-
|
|
3266
|
+
fs16.writeFileSync(readmePath, readme);
|
|
2992
3267
|
}
|
|
2993
3268
|
if (opts.dryRun) {
|
|
2994
3269
|
return {
|
|
@@ -3055,11 +3330,11 @@ import { Command as Command33 } from "commander";
|
|
|
3055
3330
|
|
|
3056
3331
|
// src/commands/state/show.ts
|
|
3057
3332
|
import { Command as Command29 } from "commander";
|
|
3058
|
-
import * as
|
|
3333
|
+
import * as path26 from "path";
|
|
3059
3334
|
function createShowCommand() {
|
|
3060
3335
|
return new Command29("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
3061
3336
|
const globalOpts = cmd.optsWithGlobals();
|
|
3062
|
-
const projectPath =
|
|
3337
|
+
const projectPath = path26.resolve(opts.path);
|
|
3063
3338
|
const result = await loadState(projectPath, opts.stream);
|
|
3064
3339
|
if (!result.ok) {
|
|
3065
3340
|
logger.error(result.error.message);
|
|
@@ -3100,12 +3375,12 @@ Decisions: ${state.decisions.length}`);
|
|
|
3100
3375
|
|
|
3101
3376
|
// src/commands/state/reset.ts
|
|
3102
3377
|
import { Command as Command30 } from "commander";
|
|
3103
|
-
import * as
|
|
3104
|
-
import * as
|
|
3378
|
+
import * as fs17 from "fs";
|
|
3379
|
+
import * as path27 from "path";
|
|
3105
3380
|
import * as readline from "readline";
|
|
3106
3381
|
function createResetCommand() {
|
|
3107
3382
|
return new Command30("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
|
|
3108
|
-
const projectPath =
|
|
3383
|
+
const projectPath = path27.resolve(opts.path);
|
|
3109
3384
|
let statePath;
|
|
3110
3385
|
if (opts.stream) {
|
|
3111
3386
|
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
@@ -3114,19 +3389,19 @@ function createResetCommand() {
|
|
|
3114
3389
|
process.exit(ExitCode.ERROR);
|
|
3115
3390
|
return;
|
|
3116
3391
|
}
|
|
3117
|
-
statePath =
|
|
3392
|
+
statePath = path27.join(streamResult.value, "state.json");
|
|
3118
3393
|
} else {
|
|
3119
|
-
statePath =
|
|
3394
|
+
statePath = path27.join(projectPath, ".harness", "state.json");
|
|
3120
3395
|
}
|
|
3121
|
-
if (!
|
|
3396
|
+
if (!fs17.existsSync(statePath)) {
|
|
3122
3397
|
logger.info("No state file found. Nothing to reset.");
|
|
3123
3398
|
process.exit(ExitCode.SUCCESS);
|
|
3124
3399
|
return;
|
|
3125
3400
|
}
|
|
3126
3401
|
if (!opts.yes) {
|
|
3127
3402
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3128
|
-
const answer = await new Promise((
|
|
3129
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3403
|
+
const answer = await new Promise((resolve31) => {
|
|
3404
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve31);
|
|
3130
3405
|
});
|
|
3131
3406
|
rl.close();
|
|
3132
3407
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -3136,7 +3411,7 @@ function createResetCommand() {
|
|
|
3136
3411
|
}
|
|
3137
3412
|
}
|
|
3138
3413
|
try {
|
|
3139
|
-
|
|
3414
|
+
fs17.unlinkSync(statePath);
|
|
3140
3415
|
logger.success("Project state reset.");
|
|
3141
3416
|
} catch (e) {
|
|
3142
3417
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3149,10 +3424,10 @@ function createResetCommand() {
|
|
|
3149
3424
|
|
|
3150
3425
|
// src/commands/state/learn.ts
|
|
3151
3426
|
import { Command as Command31 } from "commander";
|
|
3152
|
-
import * as
|
|
3427
|
+
import * as path28 from "path";
|
|
3153
3428
|
function createLearnCommand() {
|
|
3154
3429
|
return new Command31("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
|
|
3155
|
-
const projectPath =
|
|
3430
|
+
const projectPath = path28.resolve(opts.path);
|
|
3156
3431
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
3157
3432
|
if (!result.ok) {
|
|
3158
3433
|
logger.error(result.error.message);
|
|
@@ -3166,12 +3441,12 @@ function createLearnCommand() {
|
|
|
3166
3441
|
|
|
3167
3442
|
// src/commands/state/streams.ts
|
|
3168
3443
|
import { Command as Command32 } from "commander";
|
|
3169
|
-
import * as
|
|
3444
|
+
import * as path29 from "path";
|
|
3170
3445
|
function createStreamsCommand() {
|
|
3171
3446
|
const command = new Command32("streams").description("Manage state streams");
|
|
3172
3447
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3173
3448
|
const globalOpts = cmd.optsWithGlobals();
|
|
3174
|
-
const projectPath =
|
|
3449
|
+
const projectPath = path29.resolve(opts.path);
|
|
3175
3450
|
const indexResult = await loadStreamIndex(projectPath);
|
|
3176
3451
|
const result = await listStreams(projectPath);
|
|
3177
3452
|
if (!result.ok) {
|
|
@@ -3195,7 +3470,7 @@ function createStreamsCommand() {
|
|
|
3195
3470
|
process.exit(ExitCode.SUCCESS);
|
|
3196
3471
|
});
|
|
3197
3472
|
command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
|
|
3198
|
-
const projectPath =
|
|
3473
|
+
const projectPath = path29.resolve(opts.path);
|
|
3199
3474
|
const result = await createStream(projectPath, name, opts.branch);
|
|
3200
3475
|
if (!result.ok) {
|
|
3201
3476
|
logger.error(result.error.message);
|
|
@@ -3206,7 +3481,7 @@ function createStreamsCommand() {
|
|
|
3206
3481
|
process.exit(ExitCode.SUCCESS);
|
|
3207
3482
|
});
|
|
3208
3483
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3209
|
-
const projectPath =
|
|
3484
|
+
const projectPath = path29.resolve(opts.path);
|
|
3210
3485
|
const result = await archiveStream(projectPath, name);
|
|
3211
3486
|
if (!result.ok) {
|
|
3212
3487
|
logger.error(result.error.message);
|
|
@@ -3217,7 +3492,7 @@ function createStreamsCommand() {
|
|
|
3217
3492
|
process.exit(ExitCode.SUCCESS);
|
|
3218
3493
|
});
|
|
3219
3494
|
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3220
|
-
const projectPath =
|
|
3495
|
+
const projectPath = path29.resolve(opts.path);
|
|
3221
3496
|
const result = await setActiveStream(projectPath, name);
|
|
3222
3497
|
if (!result.ok) {
|
|
3223
3498
|
logger.error(result.error.message);
|
|
@@ -3240,59 +3515,518 @@ function createStateCommand() {
|
|
|
3240
3515
|
return command;
|
|
3241
3516
|
}
|
|
3242
3517
|
|
|
3243
|
-
// src/commands/
|
|
3244
|
-
import { Command as Command36 } from "commander";
|
|
3245
|
-
|
|
3246
|
-
// src/commands/ci/check.ts
|
|
3518
|
+
// src/commands/setup.ts
|
|
3247
3519
|
import { Command as Command34 } from "commander";
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3520
|
+
import * as fs19 from "fs";
|
|
3521
|
+
import * as os4 from "os";
|
|
3522
|
+
import * as path31 from "path";
|
|
3523
|
+
import chalk3 from "chalk";
|
|
3524
|
+
|
|
3525
|
+
// src/utils/first-run.ts
|
|
3526
|
+
import * as fs18 from "fs";
|
|
3527
|
+
import * as os3 from "os";
|
|
3528
|
+
import * as path30 from "path";
|
|
3529
|
+
var HARNESS_DIR = path30.join(os3.homedir(), ".harness");
|
|
3530
|
+
var MARKER_FILE = path30.join(HARNESS_DIR, ".setup-complete");
|
|
3531
|
+
function isFirstRun() {
|
|
3532
|
+
return !fs18.existsSync(MARKER_FILE);
|
|
3533
|
+
}
|
|
3534
|
+
function markSetupComplete() {
|
|
3535
|
+
fs18.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
3536
|
+
fs18.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
3537
|
+
}
|
|
3538
|
+
function printFirstRunWelcome() {
|
|
3539
|
+
try {
|
|
3540
|
+
if (!isFirstRun()) return;
|
|
3541
|
+
if (process.env.CI) return;
|
|
3542
|
+
if (process.argv.includes("--quiet")) return;
|
|
3543
|
+
process.stderr.write("Welcome to harness! Run `harness setup` to get started.\n");
|
|
3544
|
+
} catch {
|
|
3262
3545
|
}
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
// src/utils/node-version.ts
|
|
3549
|
+
import semver2 from "semver";
|
|
3550
|
+
var REQUIRED_NODE_VERSION = ">=22.0.0";
|
|
3551
|
+
function checkNodeVersion() {
|
|
3552
|
+
return {
|
|
3553
|
+
satisfies: semver2.satisfies(process.version, REQUIRED_NODE_VERSION),
|
|
3554
|
+
current: process.version,
|
|
3555
|
+
required: ">=22"
|
|
3266
3556
|
};
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
// src/integrations/registry.ts
|
|
3560
|
+
var INTEGRATION_REGISTRY = [
|
|
3561
|
+
// --- Tier 0: zero-config (free, no API key) ---
|
|
3562
|
+
{
|
|
3563
|
+
name: "context7",
|
|
3564
|
+
displayName: "Context7",
|
|
3565
|
+
description: "Live version-pinned docs for 9,000+ libraries",
|
|
3566
|
+
tier: 0,
|
|
3567
|
+
mcpConfig: {
|
|
3568
|
+
command: "npx",
|
|
3569
|
+
args: ["-y", "@upstash/context7-mcp"]
|
|
3570
|
+
},
|
|
3571
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3572
|
+
},
|
|
3573
|
+
{
|
|
3574
|
+
name: "sequential-thinking",
|
|
3575
|
+
displayName: "Sequential Thinking",
|
|
3576
|
+
description: "Structured multi-step reasoning",
|
|
3577
|
+
tier: 0,
|
|
3578
|
+
mcpConfig: {
|
|
3579
|
+
command: "npx",
|
|
3580
|
+
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
|
3581
|
+
},
|
|
3582
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3583
|
+
},
|
|
3584
|
+
{
|
|
3585
|
+
name: "playwright",
|
|
3586
|
+
displayName: "Playwright",
|
|
3587
|
+
description: "Browser automation for E2E testing",
|
|
3588
|
+
tier: 0,
|
|
3589
|
+
mcpConfig: {
|
|
3590
|
+
command: "npx",
|
|
3591
|
+
args: ["-y", "@playwright/mcp"]
|
|
3592
|
+
},
|
|
3593
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3594
|
+
},
|
|
3595
|
+
// --- Tier 1: API-key required ---
|
|
3596
|
+
{
|
|
3597
|
+
name: "perplexity",
|
|
3598
|
+
displayName: "Perplexity",
|
|
3599
|
+
description: "Real-time web search and deep research",
|
|
3600
|
+
tier: 1,
|
|
3601
|
+
envVar: "PERPLEXITY_API_KEY",
|
|
3602
|
+
mcpConfig: {
|
|
3603
|
+
command: "npx",
|
|
3604
|
+
args: ["-y", "perplexity-mcp"],
|
|
3605
|
+
env: { PERPLEXITY_API_KEY: "${PERPLEXITY_API_KEY}" }
|
|
3606
|
+
// harness-ignore SEC-SEC-002: env var placeholder, not a hardcoded secret
|
|
3607
|
+
},
|
|
3608
|
+
installHint: "Get an API key at https://perplexity.ai/settings/api",
|
|
3609
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3610
|
+
},
|
|
3611
|
+
{
|
|
3612
|
+
name: "augment-code",
|
|
3613
|
+
displayName: "Augment Code",
|
|
3614
|
+
description: "Semantic code search across codebase",
|
|
3615
|
+
tier: 1,
|
|
3616
|
+
envVar: "AUGMENT_SESSION_AUTH",
|
|
3617
|
+
mcpConfig: {
|
|
3618
|
+
command: "auggie",
|
|
3619
|
+
args: ["--mcp", "--mcp-auto-workspace"],
|
|
3620
|
+
env: { AUGMENT_SESSION_AUTH: "${AUGMENT_SESSION_AUTH}" }
|
|
3621
|
+
},
|
|
3622
|
+
installHint: "Install auggie CLI: npm i -g @augmentcode/auggie@latest && auggie login",
|
|
3623
|
+
platforms: ["claude-code", "gemini-cli"]
|
|
3275
3624
|
}
|
|
3276
|
-
|
|
3625
|
+
];
|
|
3626
|
+
|
|
3627
|
+
// src/commands/setup.ts
|
|
3628
|
+
function checkNodeVersion2() {
|
|
3629
|
+
const result = checkNodeVersion();
|
|
3630
|
+
if (result.satisfies) {
|
|
3631
|
+
return { status: "pass", message: `Node.js ${result.current} (requires ${result.required})` };
|
|
3632
|
+
}
|
|
3633
|
+
return { status: "fail", message: `Node.js ${result.current} \u2014 requires ${result.required}` };
|
|
3277
3634
|
}
|
|
3278
|
-
function
|
|
3279
|
-
|
|
3280
|
-
|
|
3635
|
+
function runSlashCommandGeneration() {
|
|
3636
|
+
try {
|
|
3637
|
+
const results = generateSlashCommands({
|
|
3638
|
+
global: true,
|
|
3639
|
+
platforms: ["claude-code", "gemini-cli", "codex", "cursor"],
|
|
3640
|
+
yes: true,
|
|
3641
|
+
includeGlobal: false,
|
|
3642
|
+
skillsDir: "",
|
|
3643
|
+
dryRun: false
|
|
3644
|
+
});
|
|
3645
|
+
const outputDirs = results.map((r) => r.outputDir).join(", ");
|
|
3646
|
+
return { status: "pass", message: `Generated global slash commands -> ${outputDirs}` };
|
|
3647
|
+
} catch (error) {
|
|
3648
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3649
|
+
return { status: "fail", message: `Slash command generation failed \u2014 ${msg}` };
|
|
3650
|
+
}
|
|
3281
3651
|
}
|
|
3282
|
-
function
|
|
3283
|
-
|
|
3284
|
-
return "error";
|
|
3652
|
+
function detectClient(dirName) {
|
|
3653
|
+
return fs19.existsSync(path31.join(os4.homedir(), dirName));
|
|
3285
3654
|
}
|
|
3286
|
-
function
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3655
|
+
async function runMcpSetup(cwd) {
|
|
3656
|
+
const results = [];
|
|
3657
|
+
const clients = [
|
|
3658
|
+
{ name: "Claude Code", dir: ".claude", client: "claude", configTarget: ".mcp.json" },
|
|
3659
|
+
{
|
|
3660
|
+
name: "Gemini CLI",
|
|
3661
|
+
dir: ".gemini",
|
|
3662
|
+
client: "gemini",
|
|
3663
|
+
configTarget: ".gemini/settings.json"
|
|
3664
|
+
},
|
|
3665
|
+
{ name: "Codex CLI", dir: ".codex", client: "codex", configTarget: ".codex/config.toml" },
|
|
3666
|
+
{ name: "Cursor", dir: ".cursor", client: "cursor", configTarget: ".cursor/mcp.json" }
|
|
3667
|
+
];
|
|
3668
|
+
for (const { name, dir, client, configTarget } of clients) {
|
|
3669
|
+
if (!detectClient(dir)) {
|
|
3670
|
+
results.push({
|
|
3671
|
+
status: "warn",
|
|
3672
|
+
message: `${name} not detected \u2014 skipped MCP configuration`
|
|
3673
|
+
});
|
|
3674
|
+
continue;
|
|
3675
|
+
}
|
|
3676
|
+
try {
|
|
3677
|
+
setupMcp(cwd, client);
|
|
3678
|
+
results.push({ status: "pass", message: `Configured MCP for ${name} -> ${configTarget}` });
|
|
3679
|
+
} catch (error) {
|
|
3680
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3681
|
+
results.push({
|
|
3682
|
+
status: "fail",
|
|
3683
|
+
message: `MCP configuration failed for ${name} \u2014 ${msg}`
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
return results;
|
|
3688
|
+
}
|
|
3689
|
+
function formatStep(result) {
|
|
3690
|
+
const icon = result.status === "pass" ? chalk3.green("\u2713") : result.status === "warn" ? chalk3.yellow("\u26A0") : chalk3.red("\u2717");
|
|
3691
|
+
return ` ${icon} ${result.message}`;
|
|
3692
|
+
}
|
|
3693
|
+
function configureTier0Integrations(cwd) {
|
|
3694
|
+
try {
|
|
3695
|
+
const mcpPath = path31.join(cwd, ".mcp.json");
|
|
3696
|
+
const config = readMcpConfig(mcpPath);
|
|
3697
|
+
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
3698
|
+
const added = [];
|
|
3699
|
+
for (const integration of tier0) {
|
|
3700
|
+
if (config.mcpServers[integration.name]) continue;
|
|
3701
|
+
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
3702
|
+
added.push(integration.displayName);
|
|
3703
|
+
}
|
|
3704
|
+
const geminiDir = path31.join(cwd, ".gemini");
|
|
3705
|
+
if (fs19.existsSync(geminiDir)) {
|
|
3706
|
+
const geminiPath = path31.join(geminiDir, "settings.json");
|
|
3707
|
+
const geminiConfig = readMcpConfig(geminiPath);
|
|
3708
|
+
for (const integration of tier0) {
|
|
3709
|
+
if (!geminiConfig.mcpServers[integration.name]) {
|
|
3710
|
+
writeMcpEntry(geminiPath, integration.name, integration.mcpConfig);
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
if (added.length === 0) {
|
|
3715
|
+
return { status: "pass", message: "Tier 0 MCP integrations already configured" };
|
|
3716
|
+
}
|
|
3717
|
+
return {
|
|
3718
|
+
status: "pass",
|
|
3719
|
+
message: `Configured ${added.length} MCP integrations: ${added.join(", ")}`
|
|
3720
|
+
};
|
|
3721
|
+
} catch (error) {
|
|
3722
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3723
|
+
return { status: "fail", message: `Tier 0 integration configuration failed \u2014 ${msg}` };
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
async function runSetup(cwd) {
|
|
3727
|
+
const steps = [];
|
|
3728
|
+
const nodeResult = checkNodeVersion2();
|
|
3729
|
+
steps.push(nodeResult);
|
|
3730
|
+
if (nodeResult.status === "fail") {
|
|
3731
|
+
return { steps, success: false };
|
|
3732
|
+
}
|
|
3733
|
+
const slashResult = runSlashCommandGeneration();
|
|
3734
|
+
steps.push(slashResult);
|
|
3735
|
+
const mcpResults = await runMcpSetup(cwd);
|
|
3736
|
+
steps.push(...mcpResults);
|
|
3737
|
+
const tier0Result = configureTier0Integrations(cwd);
|
|
3738
|
+
steps.push(tier0Result);
|
|
3739
|
+
const success = steps.every((s) => s.status !== "fail");
|
|
3740
|
+
if (success) {
|
|
3741
|
+
markSetupComplete();
|
|
3742
|
+
}
|
|
3743
|
+
return { steps, success };
|
|
3744
|
+
}
|
|
3745
|
+
function createSetupCommand() {
|
|
3746
|
+
return new Command34("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
3747
|
+
const cwd = process.cwd();
|
|
3748
|
+
console.log("");
|
|
3749
|
+
console.log(` ${chalk3.bold("harness setup")}`);
|
|
3750
|
+
console.log("");
|
|
3751
|
+
const { steps, success } = await runSetup(cwd);
|
|
3752
|
+
for (const step of steps) {
|
|
3753
|
+
console.log(formatStep(step));
|
|
3754
|
+
}
|
|
3755
|
+
console.log("");
|
|
3756
|
+
if (success) {
|
|
3757
|
+
console.log(" Setup complete. Next steps:");
|
|
3758
|
+
console.log(" - Open a project directory and run /harness:initialize-project");
|
|
3759
|
+
console.log(" - Or run harness init --name my-project to scaffold a new one");
|
|
3760
|
+
console.log(" - Run harness doctor anytime to check your environment");
|
|
3761
|
+
console.log("");
|
|
3762
|
+
}
|
|
3763
|
+
process.exit(success ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
// src/commands/doctor.ts
|
|
3768
|
+
import { Command as Command35 } from "commander";
|
|
3769
|
+
import * as fs20 from "fs";
|
|
3770
|
+
import * as os5 from "os";
|
|
3771
|
+
import * as path32 from "path";
|
|
3772
|
+
import chalk4 from "chalk";
|
|
3773
|
+
function checkNodeVersion3() {
|
|
3774
|
+
const result = checkNodeVersion();
|
|
3775
|
+
if (result.satisfies) {
|
|
3776
|
+
return {
|
|
3777
|
+
name: "node",
|
|
3778
|
+
status: "pass",
|
|
3779
|
+
message: `Node.js ${result.current} (requires ${result.required})`
|
|
3780
|
+
};
|
|
3781
|
+
}
|
|
3782
|
+
return {
|
|
3783
|
+
name: "node",
|
|
3784
|
+
status: "fail",
|
|
3785
|
+
message: `Node.js ${result.current} (requires ${result.required})`,
|
|
3786
|
+
fix: "Install Node.js >= 22: https://nodejs.org/"
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
function countCommandFiles(dir, ext) {
|
|
3790
|
+
try {
|
|
3791
|
+
return fs20.readdirSync(dir).filter((f) => f.endsWith(ext)).length;
|
|
3792
|
+
} catch {
|
|
3793
|
+
return 0;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
function checkSlashCommands() {
|
|
3797
|
+
const platforms = [
|
|
3798
|
+
{
|
|
3799
|
+
name: "Claude Code",
|
|
3800
|
+
dir: path32.join(os5.homedir(), ".claude", "commands", "harness"),
|
|
3801
|
+
ext: ".md",
|
|
3802
|
+
client: "claude-code"
|
|
3803
|
+
},
|
|
3804
|
+
{
|
|
3805
|
+
name: "Gemini CLI",
|
|
3806
|
+
dir: path32.join(os5.homedir(), ".gemini", "commands", "harness"),
|
|
3807
|
+
ext: ".toml",
|
|
3808
|
+
client: "gemini-cli"
|
|
3809
|
+
}
|
|
3810
|
+
];
|
|
3811
|
+
return platforms.map(({ name, dir, ext, client }) => {
|
|
3812
|
+
const count = countCommandFiles(dir, ext);
|
|
3813
|
+
if (count > 0) {
|
|
3814
|
+
return {
|
|
3815
|
+
name: `slash-commands-${client}`,
|
|
3816
|
+
status: "pass",
|
|
3817
|
+
message: `Slash commands installed -> ${dir} (${count} commands)`
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
return {
|
|
3821
|
+
name: `slash-commands-${client}`,
|
|
3822
|
+
status: "fail",
|
|
3823
|
+
message: `No slash commands found for ${name}`,
|
|
3824
|
+
fix: "Run: harness setup"
|
|
3825
|
+
};
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
function checkMcpConfig(cwd) {
|
|
3829
|
+
const results = [];
|
|
3830
|
+
const claudeConfig = readMcpConfig(path32.join(cwd, ".mcp.json"));
|
|
3831
|
+
if (claudeConfig.mcpServers?.["harness"]) {
|
|
3832
|
+
results.push({
|
|
3833
|
+
name: "mcp-claude",
|
|
3834
|
+
status: "pass",
|
|
3835
|
+
message: "MCP configured for Claude Code"
|
|
3836
|
+
});
|
|
3837
|
+
} else {
|
|
3838
|
+
results.push({
|
|
3839
|
+
name: "mcp-claude",
|
|
3840
|
+
status: "fail",
|
|
3841
|
+
message: "MCP not configured for Claude Code",
|
|
3842
|
+
fix: "Run: harness setup-mcp --client claude"
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
const geminiDir = path32.join(cwd, ".gemini");
|
|
3846
|
+
if (fs20.existsSync(geminiDir)) {
|
|
3847
|
+
const geminiConfig = readMcpConfig(path32.join(geminiDir, "settings.json"));
|
|
3848
|
+
if (geminiConfig.mcpServers?.["harness"]) {
|
|
3849
|
+
results.push({
|
|
3850
|
+
name: "mcp-gemini",
|
|
3851
|
+
status: "pass",
|
|
3852
|
+
message: "MCP configured for Gemini CLI"
|
|
3853
|
+
});
|
|
3854
|
+
} else {
|
|
3855
|
+
results.push({
|
|
3856
|
+
name: "mcp-gemini",
|
|
3857
|
+
status: "fail",
|
|
3858
|
+
message: "MCP not configured for Gemini CLI",
|
|
3859
|
+
fix: "Run: harness setup-mcp --client gemini"
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
return results;
|
|
3864
|
+
}
|
|
3865
|
+
function loadMcpPresence(cwd) {
|
|
3866
|
+
const mcpPath = path32.join(cwd, ".mcp.json");
|
|
3867
|
+
const geminiDir = path32.join(cwd, ".gemini");
|
|
3868
|
+
const hasGemini = fs20.existsSync(geminiDir);
|
|
3869
|
+
return {
|
|
3870
|
+
mcpConfig: readMcpConfig(mcpPath),
|
|
3871
|
+
geminiConfig: hasGemini ? readMcpConfig(path32.join(geminiDir, "settings.json")) : null,
|
|
3872
|
+
hasGemini
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
function checkTier0Presence(def, presence) {
|
|
3876
|
+
const inClaude = !!presence.mcpConfig.mcpServers?.[def.name];
|
|
3877
|
+
const inGemini = !!presence.geminiConfig?.mcpServers?.[def.name];
|
|
3878
|
+
if (!inClaude) {
|
|
3879
|
+
return {
|
|
3880
|
+
name: `integration-${def.name}`,
|
|
3881
|
+
status: "fail",
|
|
3882
|
+
message: `${def.displayName} not configured. Run \`harness setup\` to fix.`,
|
|
3883
|
+
fix: "Run: harness setup"
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
if (presence.hasGemini && !inGemini) {
|
|
3887
|
+
return {
|
|
3888
|
+
name: `integration-${def.name}`,
|
|
3889
|
+
status: "warn",
|
|
3890
|
+
message: `${def.displayName} missing from Gemini CLI config. Run \`harness setup\` to fix.`,
|
|
3891
|
+
fix: "Run: harness setup"
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
return {
|
|
3895
|
+
name: `integration-${def.name}`,
|
|
3896
|
+
status: "pass",
|
|
3897
|
+
message: `${def.displayName} configured`
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
function checkIntegrations(cwd) {
|
|
3901
|
+
const results = [];
|
|
3902
|
+
const presence = loadMcpPresence(cwd);
|
|
3903
|
+
const configPath = path32.join(cwd, "harness.config.json");
|
|
3904
|
+
const integrationsConfig = readIntegrationsConfig(configPath);
|
|
3905
|
+
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 0)) {
|
|
3906
|
+
results.push(checkTier0Presence(def, presence));
|
|
3907
|
+
}
|
|
3908
|
+
for (const def of INTEGRATION_REGISTRY.filter((d) => d.tier === 1)) {
|
|
3909
|
+
const enabled = integrationsConfig.enabled.includes(def.name);
|
|
3910
|
+
const dismissed = integrationsConfig.dismissed.includes(def.name);
|
|
3911
|
+
if (enabled && def.envVar && !process.env[def.envVar]) {
|
|
3912
|
+
results.push({
|
|
3913
|
+
name: `integration-${def.name}-env`,
|
|
3914
|
+
status: "warn",
|
|
3915
|
+
message: `${def.displayName} enabled but ${def.envVar} not set.`,
|
|
3916
|
+
...def.installHint !== void 0 && { fix: def.installHint }
|
|
3917
|
+
});
|
|
3918
|
+
} else if (!enabled && !dismissed) {
|
|
3919
|
+
results.push({
|
|
3920
|
+
name: `integration-${def.name}`,
|
|
3921
|
+
status: "info",
|
|
3922
|
+
message: `${def.displayName} enables ${def.description.toLowerCase()}. Run \`harness integrations add ${def.name}\`.`
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
return results;
|
|
3927
|
+
}
|
|
3928
|
+
function runDoctor(cwd) {
|
|
3929
|
+
const checks = [];
|
|
3930
|
+
checks.push(checkNodeVersion3());
|
|
3931
|
+
checks.push(...checkSlashCommands());
|
|
3932
|
+
checks.push(...checkMcpConfig(cwd));
|
|
3933
|
+
checks.push(...checkIntegrations(cwd));
|
|
3934
|
+
const allPassed = checks.every((c) => c.status !== "fail");
|
|
3935
|
+
return { checks, allPassed };
|
|
3936
|
+
}
|
|
3937
|
+
function formatCheck(check) {
|
|
3938
|
+
const icons = {
|
|
3939
|
+
pass: chalk4.green("\u2713"),
|
|
3940
|
+
fail: chalk4.red("\u2717"),
|
|
3941
|
+
warn: chalk4.yellow("!"),
|
|
3942
|
+
info: chalk4.blue("\u2139")
|
|
3943
|
+
};
|
|
3944
|
+
const icon = icons[check.status];
|
|
3945
|
+
let line = ` ${icon} ${check.message}`;
|
|
3946
|
+
if ((check.status === "fail" || check.status === "warn") && check.fix) {
|
|
3947
|
+
line += `
|
|
3948
|
+
-> ${check.fix}`;
|
|
3949
|
+
}
|
|
3950
|
+
return line;
|
|
3951
|
+
}
|
|
3952
|
+
function createDoctorCommand() {
|
|
3953
|
+
return new Command35("doctor").description("Check environment health: Node version, slash commands, MCP configuration").action((_opts, cmd) => {
|
|
3954
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3955
|
+
const cwd = process.cwd();
|
|
3956
|
+
const useJson = globalOpts.json;
|
|
3957
|
+
const result = runDoctor(cwd);
|
|
3958
|
+
if (useJson) {
|
|
3959
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3960
|
+
} else {
|
|
3961
|
+
console.log("");
|
|
3962
|
+
console.log(` ${chalk4.bold("harness doctor")}`);
|
|
3963
|
+
console.log("");
|
|
3964
|
+
for (const check of result.checks) {
|
|
3965
|
+
console.log(formatCheck(check));
|
|
3966
|
+
}
|
|
3967
|
+
console.log("");
|
|
3968
|
+
const passed = result.checks.filter((c) => c.status === "pass").length;
|
|
3969
|
+
const total = result.checks.length;
|
|
3970
|
+
console.log(` ${passed}/${total} checks passed`);
|
|
3971
|
+
console.log("");
|
|
3972
|
+
}
|
|
3973
|
+
process.exit(result.allPassed ? ExitCode.SUCCESS : ExitCode.VALIDATION_FAILED);
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
// src/commands/ci/index.ts
|
|
3978
|
+
import { Command as Command38 } from "commander";
|
|
3979
|
+
|
|
3980
|
+
// src/commands/ci/check.ts
|
|
3981
|
+
import { Command as Command36 } from "commander";
|
|
3982
|
+
var VALID_CHECKS = [
|
|
3983
|
+
"validate",
|
|
3984
|
+
"deps",
|
|
3985
|
+
"docs",
|
|
3986
|
+
"entropy",
|
|
3987
|
+
"security",
|
|
3988
|
+
"perf",
|
|
3989
|
+
"phase-gate",
|
|
3990
|
+
"arch"
|
|
3991
|
+
];
|
|
3992
|
+
async function runCICheck(options) {
|
|
3993
|
+
const configResult = resolveConfig(options.configPath);
|
|
3994
|
+
if (!configResult.ok) {
|
|
3995
|
+
return configResult;
|
|
3996
|
+
}
|
|
3997
|
+
const input = {
|
|
3998
|
+
projectRoot: process.cwd(),
|
|
3999
|
+
config: configResult.value
|
|
4000
|
+
};
|
|
4001
|
+
if (options.skip) input.skip = options.skip;
|
|
4002
|
+
if (options.failOn) input.failOn = options.failOn;
|
|
4003
|
+
const result = await runCIChecks(input);
|
|
4004
|
+
if (!result.ok) {
|
|
4005
|
+
return {
|
|
4006
|
+
ok: false,
|
|
4007
|
+
error: new CLIError(result.error.message, ExitCode.ERROR)
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
return { ok: true, value: result.value };
|
|
4011
|
+
}
|
|
4012
|
+
function parseSkip(skip) {
|
|
4013
|
+
if (!skip) return [];
|
|
4014
|
+
return skip.split(",").map((s) => s.trim()).filter((s) => VALID_CHECKS.includes(s));
|
|
4015
|
+
}
|
|
4016
|
+
function parseFailOn(failOn) {
|
|
4017
|
+
if (failOn === "warning") return "warning";
|
|
4018
|
+
return "error";
|
|
4019
|
+
}
|
|
4020
|
+
function createCheckCommand() {
|
|
4021
|
+
return new Command36("check").description("Run all harness checks for CI (validate, deps, docs, entropy, phase-gate, arch)").option("--skip <checks>", "Comma-separated checks to skip (e.g., entropy,docs)").option("--fail-on <severity>", "Fail on severity level: error (default) or warning", "error").action(async (opts, cmd) => {
|
|
4022
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4023
|
+
const mode = resolveOutputMode(globalOpts);
|
|
4024
|
+
const skip = parseSkip(opts.skip);
|
|
4025
|
+
const failOn = parseFailOn(opts.failOn);
|
|
4026
|
+
const result = await runCICheck({
|
|
4027
|
+
configPath: globalOpts.config,
|
|
4028
|
+
skip,
|
|
4029
|
+
failOn
|
|
3296
4030
|
});
|
|
3297
4031
|
if (!result.ok) {
|
|
3298
4032
|
if (mode === OutputMode.JSON) {
|
|
@@ -3328,9 +4062,9 @@ function createCheckCommand() {
|
|
|
3328
4062
|
}
|
|
3329
4063
|
|
|
3330
4064
|
// src/commands/ci/init.ts
|
|
3331
|
-
import { Command as
|
|
3332
|
-
import * as
|
|
3333
|
-
import * as
|
|
4065
|
+
import { Command as Command37 } from "commander";
|
|
4066
|
+
import * as fs21 from "fs";
|
|
4067
|
+
import * as path33 from "path";
|
|
3334
4068
|
var ALL_CHECKS = [
|
|
3335
4069
|
"validate",
|
|
3336
4070
|
"deps",
|
|
@@ -3431,12 +4165,12 @@ function generateCIConfig(options) {
|
|
|
3431
4165
|
});
|
|
3432
4166
|
}
|
|
3433
4167
|
function detectPlatform() {
|
|
3434
|
-
if (
|
|
3435
|
-
if (
|
|
4168
|
+
if (fs21.existsSync(".github")) return "github";
|
|
4169
|
+
if (fs21.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
3436
4170
|
return null;
|
|
3437
4171
|
}
|
|
3438
4172
|
function createInitCommand2() {
|
|
3439
|
-
return new
|
|
4173
|
+
return new Command37("init").description("Generate CI configuration for harness checks").option("--platform <platform>", "CI platform: github, gitlab, or generic").option("--checks <list>", "Comma-separated list of checks to include").action(async (opts, cmd) => {
|
|
3440
4174
|
const globalOpts = cmd.optsWithGlobals();
|
|
3441
4175
|
const platform = opts.platform ?? detectPlatform() ?? "generic";
|
|
3442
4176
|
const checks = opts.checks ? opts.checks.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -3448,12 +4182,12 @@ function createInitCommand2() {
|
|
|
3448
4182
|
process.exit(result.error.exitCode);
|
|
3449
4183
|
}
|
|
3450
4184
|
const { filename, content } = result.value;
|
|
3451
|
-
const targetPath =
|
|
3452
|
-
const dir =
|
|
3453
|
-
|
|
3454
|
-
|
|
4185
|
+
const targetPath = path33.resolve(filename);
|
|
4186
|
+
const dir = path33.dirname(targetPath);
|
|
4187
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
4188
|
+
fs21.writeFileSync(targetPath, content);
|
|
3455
4189
|
if (platform === "generic" && process.platform !== "win32") {
|
|
3456
|
-
|
|
4190
|
+
fs21.chmodSync(targetPath, "755");
|
|
3457
4191
|
}
|
|
3458
4192
|
if (globalOpts.json) {
|
|
3459
4193
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -3466,18 +4200,359 @@ function createInitCommand2() {
|
|
|
3466
4200
|
|
|
3467
4201
|
// src/commands/ci/index.ts
|
|
3468
4202
|
function createCICommand() {
|
|
3469
|
-
const command = new
|
|
4203
|
+
const command = new Command38("ci").description("CI/CD integration commands");
|
|
3470
4204
|
command.addCommand(createCheckCommand());
|
|
3471
4205
|
command.addCommand(createInitCommand2());
|
|
3472
4206
|
return command;
|
|
3473
4207
|
}
|
|
3474
4208
|
|
|
4209
|
+
// src/commands/hooks/index.ts
|
|
4210
|
+
import { Command as Command43 } from "commander";
|
|
4211
|
+
|
|
4212
|
+
// src/commands/hooks/init.ts
|
|
4213
|
+
import { Command as Command39 } from "commander";
|
|
4214
|
+
import * as fs22 from "fs";
|
|
4215
|
+
import * as path34 from "path";
|
|
4216
|
+
import { fileURLToPath } from "url";
|
|
4217
|
+
|
|
4218
|
+
// src/hooks/profiles.ts
|
|
4219
|
+
var HOOK_SCRIPTS = [
|
|
4220
|
+
{ name: "block-no-verify", event: "PreToolUse", matcher: "Bash", minProfile: "minimal" },
|
|
4221
|
+
{ name: "protect-config", event: "PreToolUse", matcher: "Write|Edit", minProfile: "standard" },
|
|
4222
|
+
{ name: "quality-gate", event: "PostToolUse", matcher: "Edit|Write", minProfile: "standard" },
|
|
4223
|
+
{ name: "pre-compact-state", event: "PreCompact", matcher: "*", minProfile: "standard" },
|
|
4224
|
+
{ name: "cost-tracker", event: "Stop", matcher: "*", minProfile: "strict" },
|
|
4225
|
+
{ name: "sentinel-pre", event: "PreToolUse", matcher: "*", minProfile: "strict" },
|
|
4226
|
+
{ name: "sentinel-post", event: "PostToolUse", matcher: "*", minProfile: "strict" }
|
|
4227
|
+
];
|
|
4228
|
+
var PROFILE_ORDER = ["minimal", "standard", "strict"];
|
|
4229
|
+
function hooksForProfile(profile) {
|
|
4230
|
+
const profileIndex = PROFILE_ORDER.indexOf(profile);
|
|
4231
|
+
return HOOK_SCRIPTS.filter((h) => PROFILE_ORDER.indexOf(h.minProfile) <= profileIndex).map(
|
|
4232
|
+
(h) => h.name
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
var PROFILES = {
|
|
4236
|
+
minimal: hooksForProfile("minimal"),
|
|
4237
|
+
standard: hooksForProfile("standard"),
|
|
4238
|
+
strict: hooksForProfile("strict")
|
|
4239
|
+
};
|
|
4240
|
+
|
|
4241
|
+
// src/commands/hooks/init.ts
|
|
4242
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
4243
|
+
var __dirname = path34.dirname(__filename);
|
|
4244
|
+
var VALID_PROFILES = ["minimal", "standard", "strict"];
|
|
4245
|
+
function resolveHookSourceDir() {
|
|
4246
|
+
const candidate = path34.resolve(__dirname, "..", "..", "hooks");
|
|
4247
|
+
if (fs22.existsSync(candidate)) {
|
|
4248
|
+
return candidate;
|
|
4249
|
+
}
|
|
4250
|
+
throw new Error(`Cannot locate hook scripts directory. Expected at: ${candidate}`);
|
|
4251
|
+
}
|
|
4252
|
+
function buildSettingsHooks(profile) {
|
|
4253
|
+
const activeHookNames = PROFILES[profile];
|
|
4254
|
+
const activeScripts = HOOK_SCRIPTS.filter((h) => activeHookNames.includes(h.name));
|
|
4255
|
+
const hooks = {};
|
|
4256
|
+
for (const script of activeScripts) {
|
|
4257
|
+
if (!hooks[script.event]) {
|
|
4258
|
+
hooks[script.event] = [];
|
|
4259
|
+
}
|
|
4260
|
+
hooks[script.event].push({
|
|
4261
|
+
matcher: script.matcher,
|
|
4262
|
+
hooks: [{ type: "command", command: `node .harness/hooks/${script.name}.js` }]
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
return hooks;
|
|
4266
|
+
}
|
|
4267
|
+
function mergeSettings(existing, hooksConfig) {
|
|
4268
|
+
return {
|
|
4269
|
+
...existing,
|
|
4270
|
+
hooks: hooksConfig
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
function initHooks(options) {
|
|
4274
|
+
const { profile, projectDir } = options;
|
|
4275
|
+
const hooksDestDir = path34.join(projectDir, ".harness", "hooks");
|
|
4276
|
+
fs22.mkdirSync(hooksDestDir, { recursive: true });
|
|
4277
|
+
if (fs22.existsSync(hooksDestDir)) {
|
|
4278
|
+
for (const entry of fs22.readdirSync(hooksDestDir)) {
|
|
4279
|
+
if (entry.endsWith(".js")) {
|
|
4280
|
+
fs22.unlinkSync(path34.join(hooksDestDir, entry));
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
const sourceDir = resolveHookSourceDir();
|
|
4285
|
+
const copiedScripts = [];
|
|
4286
|
+
const activeNames = PROFILES[profile];
|
|
4287
|
+
const activeScripts = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name));
|
|
4288
|
+
for (const script of activeScripts) {
|
|
4289
|
+
const srcFile = path34.join(sourceDir, `${script.name}.js`);
|
|
4290
|
+
const destFile = path34.join(hooksDestDir, `${script.name}.js`);
|
|
4291
|
+
if (fs22.existsSync(srcFile)) {
|
|
4292
|
+
fs22.copyFileSync(srcFile, destFile);
|
|
4293
|
+
copiedScripts.push(script.name);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
const profilePath = path34.join(hooksDestDir, "profile.json");
|
|
4297
|
+
fs22.writeFileSync(profilePath, JSON.stringify({ profile }, null, 2) + "\n");
|
|
4298
|
+
const claudeDir = path34.join(projectDir, ".claude");
|
|
4299
|
+
fs22.mkdirSync(claudeDir, { recursive: true });
|
|
4300
|
+
const settingsPath = path34.join(claudeDir, "settings.json");
|
|
4301
|
+
let existing = {};
|
|
4302
|
+
if (fs22.existsSync(settingsPath)) {
|
|
4303
|
+
try {
|
|
4304
|
+
existing = JSON.parse(fs22.readFileSync(settingsPath, "utf-8"));
|
|
4305
|
+
} catch (e) {
|
|
4306
|
+
throw new Error(
|
|
4307
|
+
`Malformed .claude/settings.json \u2014 fix the JSON syntax before running hooks init. Parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
4308
|
+
{ cause: e }
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
const hooksConfig = buildSettingsHooks(profile);
|
|
4313
|
+
const merged = mergeSettings(existing, hooksConfig);
|
|
4314
|
+
fs22.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
4315
|
+
return { copiedScripts, settingsPath, profilePath };
|
|
4316
|
+
}
|
|
4317
|
+
function createInitCommand3() {
|
|
4318
|
+
return new Command39("init").description("Install Claude Code hook configurations into the current project").option("--profile <profile>", "Hook profile: minimal, standard, or strict", "standard").action(async (opts, cmd) => {
|
|
4319
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4320
|
+
const profile = opts.profile;
|
|
4321
|
+
if (!VALID_PROFILES.includes(profile)) {
|
|
4322
|
+
logger.error(`Invalid profile: ${profile}. Must be one of: ${VALID_PROFILES.join(", ")}`);
|
|
4323
|
+
process.exit(2);
|
|
4324
|
+
}
|
|
4325
|
+
const projectDir = process.cwd();
|
|
4326
|
+
try {
|
|
4327
|
+
const result = initHooks({ profile, projectDir });
|
|
4328
|
+
if (globalOpts.json) {
|
|
4329
|
+
console.log(
|
|
4330
|
+
JSON.stringify({
|
|
4331
|
+
profile,
|
|
4332
|
+
copiedScripts: result.copiedScripts,
|
|
4333
|
+
settingsPath: result.settingsPath,
|
|
4334
|
+
profilePath: result.profilePath
|
|
4335
|
+
})
|
|
4336
|
+
);
|
|
4337
|
+
} else {
|
|
4338
|
+
logger.success(
|
|
4339
|
+
`Installed ${result.copiedScripts.length} hook scripts to .harness/hooks/`
|
|
4340
|
+
);
|
|
4341
|
+
logger.info(`Profile: ${profile}`);
|
|
4342
|
+
logger.info(`Settings: ${path34.relative(projectDir, result.settingsPath)}`);
|
|
4343
|
+
logger.dim("Run 'harness hooks list' to see installed hooks");
|
|
4344
|
+
}
|
|
4345
|
+
} catch (err) {
|
|
4346
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4347
|
+
logger.error(`Failed to initialize hooks: ${message}`);
|
|
4348
|
+
process.exit(2);
|
|
4349
|
+
}
|
|
4350
|
+
});
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
// src/commands/hooks/list.ts
|
|
4354
|
+
import { Command as Command40 } from "commander";
|
|
4355
|
+
import * as fs23 from "fs";
|
|
4356
|
+
import * as path35 from "path";
|
|
4357
|
+
function listHooks(projectDir) {
|
|
4358
|
+
const hooksDir = path35.join(projectDir, ".harness", "hooks");
|
|
4359
|
+
const profilePath = path35.join(hooksDir, "profile.json");
|
|
4360
|
+
if (!fs23.existsSync(profilePath)) {
|
|
4361
|
+
return { installed: false, profile: null, hooks: [] };
|
|
4362
|
+
}
|
|
4363
|
+
let profile = "standard";
|
|
4364
|
+
let warning;
|
|
4365
|
+
try {
|
|
4366
|
+
const data = JSON.parse(fs23.readFileSync(profilePath, "utf-8"));
|
|
4367
|
+
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
4368
|
+
profile = data.profile;
|
|
4369
|
+
}
|
|
4370
|
+
} catch {
|
|
4371
|
+
warning = "Malformed profile.json \u2014 defaulting to standard profile";
|
|
4372
|
+
}
|
|
4373
|
+
const activeNames = PROFILES[profile];
|
|
4374
|
+
const hooks = HOOK_SCRIPTS.filter((h) => activeNames.includes(h.name)).map((h) => ({
|
|
4375
|
+
name: h.name,
|
|
4376
|
+
event: h.event,
|
|
4377
|
+
matcher: h.matcher,
|
|
4378
|
+
scriptPath: path35.join(".harness", "hooks", `${h.name}.js`)
|
|
4379
|
+
}));
|
|
4380
|
+
const result = { installed: true, profile, hooks };
|
|
4381
|
+
if (warning) {
|
|
4382
|
+
result.warning = warning;
|
|
4383
|
+
}
|
|
4384
|
+
return result;
|
|
4385
|
+
}
|
|
4386
|
+
function createListCommand3() {
|
|
4387
|
+
return new Command40("list").description("Show installed hooks and active profile").action(async (_opts, cmd) => {
|
|
4388
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4389
|
+
const projectDir = process.cwd();
|
|
4390
|
+
const result = listHooks(projectDir);
|
|
4391
|
+
if (globalOpts.json) {
|
|
4392
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4393
|
+
return;
|
|
4394
|
+
}
|
|
4395
|
+
if (!result.installed) {
|
|
4396
|
+
logger.info("No harness hooks installed. Run 'harness hooks init' to set up hooks.");
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4399
|
+
logger.info(`Profile: ${result.profile}`);
|
|
4400
|
+
logger.info(`Hooks (${result.hooks.length}):`);
|
|
4401
|
+
for (const hook of result.hooks) {
|
|
4402
|
+
console.log(` ${hook.name} ${hook.event}:${hook.matcher} ${hook.scriptPath}`);
|
|
4403
|
+
}
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
// src/commands/hooks/remove.ts
|
|
4408
|
+
import { Command as Command41 } from "commander";
|
|
4409
|
+
import * as fs24 from "fs";
|
|
4410
|
+
import * as path36 from "path";
|
|
4411
|
+
function removeHooks(projectDir) {
|
|
4412
|
+
const hooksDir = path36.join(projectDir, ".harness", "hooks");
|
|
4413
|
+
const settingsPath = path36.join(projectDir, ".claude", "settings.json");
|
|
4414
|
+
let removed = false;
|
|
4415
|
+
let settingsCleaned = false;
|
|
4416
|
+
if (fs24.existsSync(hooksDir)) {
|
|
4417
|
+
fs24.rmSync(hooksDir, { recursive: true, force: true });
|
|
4418
|
+
removed = true;
|
|
4419
|
+
}
|
|
4420
|
+
if (fs24.existsSync(settingsPath)) {
|
|
4421
|
+
try {
|
|
4422
|
+
const settings = JSON.parse(fs24.readFileSync(settingsPath, "utf-8"));
|
|
4423
|
+
if (settings.hooks !== void 0) {
|
|
4424
|
+
delete settings.hooks;
|
|
4425
|
+
settingsCleaned = true;
|
|
4426
|
+
if (Object.keys(settings).length === 0) {
|
|
4427
|
+
fs24.unlinkSync(settingsPath);
|
|
4428
|
+
} else {
|
|
4429
|
+
fs24.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
} catch {
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
return { removed, hooksDir, settingsCleaned };
|
|
4436
|
+
}
|
|
4437
|
+
function createRemoveCommand() {
|
|
4438
|
+
return new Command41("remove").description("Remove harness-managed hooks from the current project").action(async (_opts, cmd) => {
|
|
4439
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
4440
|
+
const projectDir = process.cwd();
|
|
4441
|
+
const result = removeHooks(projectDir);
|
|
4442
|
+
if (globalOpts.json) {
|
|
4443
|
+
console.log(JSON.stringify(result));
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
if (!result.removed && !result.settingsCleaned) {
|
|
4447
|
+
logger.info("No harness hooks found to remove.");
|
|
4448
|
+
return;
|
|
4449
|
+
}
|
|
4450
|
+
if (result.removed) {
|
|
4451
|
+
logger.success("Removed .harness/hooks/ directory");
|
|
4452
|
+
}
|
|
4453
|
+
if (result.settingsCleaned) {
|
|
4454
|
+
logger.success("Cleaned hook entries from .claude/settings.json");
|
|
4455
|
+
}
|
|
4456
|
+
});
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
// src/commands/hooks/add.ts
|
|
4460
|
+
import { Command as Command42 } from "commander";
|
|
4461
|
+
import * as fs25 from "fs";
|
|
4462
|
+
import * as path37 from "path";
|
|
4463
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4464
|
+
var __dirname2 = path37.dirname(fileURLToPath2(import.meta.url));
|
|
4465
|
+
var ALIASES = {
|
|
4466
|
+
sentinel: ["sentinel-pre", "sentinel-post"]
|
|
4467
|
+
};
|
|
4468
|
+
function hookSourceDir() {
|
|
4469
|
+
const d = path37.resolve(__dirname2, "..", "..", "hooks");
|
|
4470
|
+
if (fs25.existsSync(d)) return d;
|
|
4471
|
+
throw new Error(`Hook scripts not found: ${d}`);
|
|
4472
|
+
}
|
|
4473
|
+
function readJson(p) {
|
|
4474
|
+
return fs25.existsSync(p) ? JSON.parse(fs25.readFileSync(p, "utf-8")) : {};
|
|
4475
|
+
}
|
|
4476
|
+
function registerHook(s, ev, matcher, name) {
|
|
4477
|
+
if (!s.hooks[ev]) s.hooks[ev] = [];
|
|
4478
|
+
const cmd = `node .harness/hooks/${name}.js`;
|
|
4479
|
+
if (!s.hooks[ev].some((e) => e.hooks?.some((h) => h.command === cmd))) {
|
|
4480
|
+
s.hooks[ev].push({ matcher, hooks: [{ type: "command", command: cmd }] });
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function addHooks(hookName, projectDir) {
|
|
4484
|
+
const names = ALIASES[hookName] ?? [hookName];
|
|
4485
|
+
const result = { added: [], alreadyInstalled: [], notFound: [] };
|
|
4486
|
+
const srcDir = hookSourceDir();
|
|
4487
|
+
const destDir = path37.join(projectDir, ".harness", "hooks");
|
|
4488
|
+
fs25.mkdirSync(destDir, { recursive: true });
|
|
4489
|
+
const claudeDir = path37.join(projectDir, ".claude");
|
|
4490
|
+
fs25.mkdirSync(claudeDir, { recursive: true });
|
|
4491
|
+
const settingsPath = path37.join(claudeDir, "settings.json");
|
|
4492
|
+
const settings = readJson(settingsPath);
|
|
4493
|
+
if (!settings.hooks) settings.hooks = {};
|
|
4494
|
+
for (const name of names) {
|
|
4495
|
+
const def = HOOK_SCRIPTS.find((h) => h.name === name);
|
|
4496
|
+
if (!def) {
|
|
4497
|
+
result.notFound.push(name);
|
|
4498
|
+
continue;
|
|
4499
|
+
}
|
|
4500
|
+
const src = path37.join(srcDir, `${name}.js`);
|
|
4501
|
+
const dest = path37.join(destDir, `${name}.js`);
|
|
4502
|
+
if (fs25.existsSync(dest)) {
|
|
4503
|
+
result.alreadyInstalled.push(name);
|
|
4504
|
+
} else if (!fs25.existsSync(src)) {
|
|
4505
|
+
result.notFound.push(name);
|
|
4506
|
+
continue;
|
|
4507
|
+
} else {
|
|
4508
|
+
fs25.copyFileSync(src, dest);
|
|
4509
|
+
result.added.push(name);
|
|
4510
|
+
}
|
|
4511
|
+
registerHook(settings, def.event, def.matcher, name);
|
|
4512
|
+
}
|
|
4513
|
+
fs25.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4514
|
+
return result;
|
|
4515
|
+
}
|
|
4516
|
+
function createAddCommand2() {
|
|
4517
|
+
return new Command42("add").argument("<hook-name>", "Hook name or alias (e.g., sentinel)").description("Add a hook without changing the profile").action(async (hookName, _opts, cmd) => {
|
|
4518
|
+
const projectDir = process.cwd();
|
|
4519
|
+
try {
|
|
4520
|
+
const res = addHooks(hookName, projectDir);
|
|
4521
|
+
if (cmd.optsWithGlobals().json) {
|
|
4522
|
+
console.log(JSON.stringify(res));
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
if (res.notFound.length > 0) {
|
|
4526
|
+
logger.error(`Unknown hook(s): ${res.notFound.join(", ")}`);
|
|
4527
|
+
logger.info(`Available: ${HOOK_SCRIPTS.map((h) => h.name).join(", ")}`);
|
|
4528
|
+
logger.info(`Aliases: ${Object.keys(ALIASES).join(", ")}`);
|
|
4529
|
+
process.exit(2);
|
|
4530
|
+
}
|
|
4531
|
+
for (const n of res.added) logger.success(`Added hook: ${n}`);
|
|
4532
|
+
for (const n of res.alreadyInstalled) logger.info(`Already installed: ${n}`);
|
|
4533
|
+
} catch (err) {
|
|
4534
|
+
logger.error(`Failed to add hook: ${err instanceof Error ? err.message : String(err)}`);
|
|
4535
|
+
process.exit(2);
|
|
4536
|
+
}
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
// src/commands/hooks/index.ts
|
|
4541
|
+
function createHooksCommand() {
|
|
4542
|
+
const command = new Command43("hooks").description("Manage Claude Code hook configurations");
|
|
4543
|
+
command.addCommand(createInitCommand3());
|
|
4544
|
+
command.addCommand(createListCommand3());
|
|
4545
|
+
command.addCommand(createRemoveCommand());
|
|
4546
|
+
command.addCommand(createAddCommand2());
|
|
4547
|
+
return command;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
3475
4550
|
// src/commands/update.ts
|
|
3476
|
-
import { Command as
|
|
4551
|
+
import { Command as Command44 } from "commander";
|
|
3477
4552
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
3478
4553
|
import { realpathSync } from "fs";
|
|
3479
4554
|
import readline2 from "readline";
|
|
3480
|
-
import
|
|
4555
|
+
import chalk5 from "chalk";
|
|
3481
4556
|
function detectPackageManager() {
|
|
3482
4557
|
try {
|
|
3483
4558
|
const argv1 = process.argv[1];
|
|
@@ -3533,10 +4608,10 @@ function prompt(question) {
|
|
|
3533
4608
|
input: process.stdin,
|
|
3534
4609
|
output: process.stdout
|
|
3535
4610
|
});
|
|
3536
|
-
return new Promise((
|
|
4611
|
+
return new Promise((resolve31) => {
|
|
3537
4612
|
rl.question(question, (answer) => {
|
|
3538
4613
|
rl.close();
|
|
3539
|
-
|
|
4614
|
+
resolve31(answer.trim().toLowerCase());
|
|
3540
4615
|
});
|
|
3541
4616
|
});
|
|
3542
4617
|
}
|
|
@@ -3552,11 +4627,11 @@ async function offerRegeneration() {
|
|
|
3552
4627
|
});
|
|
3553
4628
|
} catch {
|
|
3554
4629
|
logger.warn("Generation failed. Run manually:");
|
|
3555
|
-
console.log(` ${
|
|
4630
|
+
console.log(` ${chalk5.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
3556
4631
|
}
|
|
3557
4632
|
}
|
|
3558
4633
|
function createUpdateCommand() {
|
|
3559
|
-
return new
|
|
4634
|
+
return new Command44("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").action(async (opts, cmd) => {
|
|
3560
4635
|
const globalOpts = cmd.optsWithGlobals();
|
|
3561
4636
|
const pm = detectPackageManager();
|
|
3562
4637
|
if (globalOpts.verbose) {
|
|
@@ -3578,8 +4653,8 @@ function createUpdateCommand() {
|
|
|
3578
4653
|
}
|
|
3579
4654
|
if (currentVersion) {
|
|
3580
4655
|
console.log("");
|
|
3581
|
-
logger.info(`Current CLI version: ${
|
|
3582
|
-
logger.info(`Latest CLI version: ${
|
|
4656
|
+
logger.info(`Current CLI version: ${chalk5.dim(`v${currentVersion}`)}`);
|
|
4657
|
+
logger.info(`Latest CLI version: ${chalk5.green(`v${latestCliVersion}`)}`);
|
|
3583
4658
|
console.log("");
|
|
3584
4659
|
}
|
|
3585
4660
|
}
|
|
@@ -3605,7 +4680,7 @@ function createUpdateCommand() {
|
|
|
3605
4680
|
} catch {
|
|
3606
4681
|
console.log("");
|
|
3607
4682
|
logger.error("Update failed. You can try manually:");
|
|
3608
|
-
console.log(` ${
|
|
4683
|
+
console.log(` ${chalk5.cyan(installCmd)}`);
|
|
3609
4684
|
process.exit(ExitCode.ERROR);
|
|
3610
4685
|
}
|
|
3611
4686
|
await offerRegeneration();
|
|
@@ -3614,9 +4689,9 @@ function createUpdateCommand() {
|
|
|
3614
4689
|
}
|
|
3615
4690
|
|
|
3616
4691
|
// src/commands/generate.ts
|
|
3617
|
-
import { Command as
|
|
4692
|
+
import { Command as Command45 } from "commander";
|
|
3618
4693
|
function createGenerateCommand3() {
|
|
3619
|
-
return new
|
|
4694
|
+
return new Command45("generate").description("Generate all platform integrations (slash commands + agent definitions)").option("--platforms <list>", "Target platforms (comma-separated)", "claude-code,gemini-cli").option("--global", "Write to global directories", false).option("--include-global", "Include built-in global skills", false).option("--output <dir>", "Custom output directory").option("--dry-run", "Show what would change without writing", false).option("--yes", "Skip deletion confirmation prompts", false).action(async (opts, cmd) => {
|
|
3620
4695
|
const globalOpts = cmd.optsWithGlobals();
|
|
3621
4696
|
const platforms = opts.platforms.split(",").map((p) => p.trim());
|
|
3622
4697
|
for (const p of platforms) {
|
|
@@ -3675,8 +4750,8 @@ function createGenerateCommand3() {
|
|
|
3675
4750
|
}
|
|
3676
4751
|
|
|
3677
4752
|
// src/commands/graph/scan.ts
|
|
3678
|
-
import { Command as
|
|
3679
|
-
import * as
|
|
4753
|
+
import { Command as Command46 } from "commander";
|
|
4754
|
+
import * as path38 from "path";
|
|
3680
4755
|
async function runScan(projectPath) {
|
|
3681
4756
|
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-B26DFXMP.js");
|
|
3682
4757
|
const store = new GraphStore();
|
|
@@ -3689,13 +4764,13 @@ async function runScan(projectPath) {
|
|
|
3689
4764
|
await new GitIngestor(store).ingest(projectPath);
|
|
3690
4765
|
} catch {
|
|
3691
4766
|
}
|
|
3692
|
-
const graphDir =
|
|
4767
|
+
const graphDir = path38.join(projectPath, ".harness", "graph");
|
|
3693
4768
|
await store.save(graphDir);
|
|
3694
4769
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
3695
4770
|
}
|
|
3696
4771
|
function createScanCommand() {
|
|
3697
|
-
return new
|
|
3698
|
-
const projectPath =
|
|
4772
|
+
return new Command46("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
4773
|
+
const projectPath = path38.resolve(inputPath);
|
|
3699
4774
|
const globalOpts = cmd.optsWithGlobals();
|
|
3700
4775
|
try {
|
|
3701
4776
|
const result = await runScan(projectPath);
|
|
@@ -3714,13 +4789,13 @@ function createScanCommand() {
|
|
|
3714
4789
|
}
|
|
3715
4790
|
|
|
3716
4791
|
// src/commands/graph/ingest.ts
|
|
3717
|
-
import { Command as
|
|
3718
|
-
import * as
|
|
4792
|
+
import { Command as Command47 } from "commander";
|
|
4793
|
+
import * as path39 from "path";
|
|
3719
4794
|
async function loadConnectorConfig(projectPath, source) {
|
|
3720
4795
|
try {
|
|
3721
|
-
const
|
|
3722
|
-
const configPath =
|
|
3723
|
-
const config = JSON.parse(await
|
|
4796
|
+
const fs34 = await import("fs/promises");
|
|
4797
|
+
const configPath = path39.join(projectPath, "harness.config.json");
|
|
4798
|
+
const config = JSON.parse(await fs34.readFile(configPath, "utf-8"));
|
|
3724
4799
|
const connector = config.graph?.connectors?.find(
|
|
3725
4800
|
(c) => c.source === source
|
|
3726
4801
|
);
|
|
@@ -3760,7 +4835,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3760
4835
|
JiraConnector,
|
|
3761
4836
|
SlackConnector
|
|
3762
4837
|
} = await import("./dist-B26DFXMP.js");
|
|
3763
|
-
const graphDir =
|
|
4838
|
+
const graphDir = path39.join(projectPath, ".harness", "graph");
|
|
3764
4839
|
const store = new GraphStore();
|
|
3765
4840
|
await store.load(graphDir);
|
|
3766
4841
|
if (opts?.all) {
|
|
@@ -3821,13 +4896,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3821
4896
|
return result;
|
|
3822
4897
|
}
|
|
3823
4898
|
function createIngestCommand() {
|
|
3824
|
-
return new
|
|
4899
|
+
return new Command47("ingest").description("Ingest data into the knowledge graph").option("--source <name>", "Source to ingest (code, knowledge, git, jira, slack)").option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
3825
4900
|
if (!opts.source && !opts.all) {
|
|
3826
4901
|
console.error("Error: --source or --all is required");
|
|
3827
4902
|
process.exit(1);
|
|
3828
4903
|
}
|
|
3829
4904
|
const globalOpts = cmd.optsWithGlobals();
|
|
3830
|
-
const projectPath =
|
|
4905
|
+
const projectPath = path39.resolve(globalOpts.config ? path39.dirname(globalOpts.config) : ".");
|
|
3831
4906
|
try {
|
|
3832
4907
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
3833
4908
|
full: opts.full,
|
|
@@ -3849,12 +4924,12 @@ function createIngestCommand() {
|
|
|
3849
4924
|
}
|
|
3850
4925
|
|
|
3851
4926
|
// src/commands/graph/query.ts
|
|
3852
|
-
import { Command as
|
|
3853
|
-
import * as
|
|
4927
|
+
import { Command as Command48 } from "commander";
|
|
4928
|
+
import * as path40 from "path";
|
|
3854
4929
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
3855
4930
|
const { GraphStore, ContextQL } = await import("./dist-B26DFXMP.js");
|
|
3856
4931
|
const store = new GraphStore();
|
|
3857
|
-
const graphDir =
|
|
4932
|
+
const graphDir = path40.join(projectPath, ".harness", "graph");
|
|
3858
4933
|
const loaded = await store.load(graphDir);
|
|
3859
4934
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
3860
4935
|
const params = {
|
|
@@ -3868,9 +4943,9 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
3868
4943
|
return cql.execute(params);
|
|
3869
4944
|
}
|
|
3870
4945
|
function createQueryCommand() {
|
|
3871
|
-
return new
|
|
4946
|
+
return new Command48("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
|
|
3872
4947
|
const globalOpts = cmd.optsWithGlobals();
|
|
3873
|
-
const projectPath =
|
|
4948
|
+
const projectPath = path40.resolve(globalOpts.config ? path40.dirname(globalOpts.config) : ".");
|
|
3874
4949
|
try {
|
|
3875
4950
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
3876
4951
|
depth: parseInt(opts.depth),
|
|
@@ -3896,21 +4971,21 @@ function createQueryCommand() {
|
|
|
3896
4971
|
}
|
|
3897
4972
|
|
|
3898
4973
|
// src/commands/graph/index.ts
|
|
3899
|
-
import { Command as
|
|
4974
|
+
import { Command as Command49 } from "commander";
|
|
3900
4975
|
|
|
3901
4976
|
// src/commands/graph/status.ts
|
|
3902
|
-
import * as
|
|
4977
|
+
import * as path41 from "path";
|
|
3903
4978
|
async function runGraphStatus(projectPath) {
|
|
3904
4979
|
const { GraphStore } = await import("./dist-B26DFXMP.js");
|
|
3905
|
-
const graphDir =
|
|
4980
|
+
const graphDir = path41.join(projectPath, ".harness", "graph");
|
|
3906
4981
|
const store = new GraphStore();
|
|
3907
4982
|
const loaded = await store.load(graphDir);
|
|
3908
4983
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
3909
|
-
const
|
|
3910
|
-
const metaPath =
|
|
4984
|
+
const fs34 = await import("fs/promises");
|
|
4985
|
+
const metaPath = path41.join(graphDir, "metadata.json");
|
|
3911
4986
|
let lastScan = "unknown";
|
|
3912
4987
|
try {
|
|
3913
|
-
const meta = JSON.parse(await
|
|
4988
|
+
const meta = JSON.parse(await fs34.readFile(metaPath, "utf-8"));
|
|
3914
4989
|
lastScan = meta.lastScanTimestamp;
|
|
3915
4990
|
} catch {
|
|
3916
4991
|
}
|
|
@@ -3921,8 +4996,8 @@ async function runGraphStatus(projectPath) {
|
|
|
3921
4996
|
}
|
|
3922
4997
|
let connectorSyncStatus = {};
|
|
3923
4998
|
try {
|
|
3924
|
-
const syncMetaPath =
|
|
3925
|
-
const syncMeta = JSON.parse(await
|
|
4999
|
+
const syncMetaPath = path41.join(graphDir, "sync-metadata.json");
|
|
5000
|
+
const syncMeta = JSON.parse(await fs34.readFile(syncMetaPath, "utf-8"));
|
|
3926
5001
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
3927
5002
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
3928
5003
|
}
|
|
@@ -3939,10 +5014,10 @@ async function runGraphStatus(projectPath) {
|
|
|
3939
5014
|
}
|
|
3940
5015
|
|
|
3941
5016
|
// src/commands/graph/export.ts
|
|
3942
|
-
import * as
|
|
5017
|
+
import * as path42 from "path";
|
|
3943
5018
|
async function runGraphExport(projectPath, format) {
|
|
3944
5019
|
const { GraphStore } = await import("./dist-B26DFXMP.js");
|
|
3945
|
-
const graphDir =
|
|
5020
|
+
const graphDir = path42.join(projectPath, ".harness", "graph");
|
|
3946
5021
|
const store = new GraphStore();
|
|
3947
5022
|
const loaded = await store.load(graphDir);
|
|
3948
5023
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -3971,13 +5046,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
3971
5046
|
}
|
|
3972
5047
|
|
|
3973
5048
|
// src/commands/graph/index.ts
|
|
3974
|
-
import * as
|
|
5049
|
+
import * as path43 from "path";
|
|
3975
5050
|
function createGraphCommand() {
|
|
3976
|
-
const graph = new
|
|
5051
|
+
const graph = new Command49("graph").description("Knowledge graph management");
|
|
3977
5052
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
3978
5053
|
try {
|
|
3979
5054
|
const globalOpts = cmd.optsWithGlobals();
|
|
3980
|
-
const projectPath =
|
|
5055
|
+
const projectPath = path43.resolve(globalOpts.config ? path43.dirname(globalOpts.config) : ".");
|
|
3981
5056
|
const result = await runGraphStatus(projectPath);
|
|
3982
5057
|
if (globalOpts.json) {
|
|
3983
5058
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -4004,7 +5079,7 @@ function createGraphCommand() {
|
|
|
4004
5079
|
});
|
|
4005
5080
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
4006
5081
|
const globalOpts = cmd.optsWithGlobals();
|
|
4007
|
-
const projectPath =
|
|
5082
|
+
const projectPath = path43.resolve(globalOpts.config ? path43.dirname(globalOpts.config) : ".");
|
|
4008
5083
|
try {
|
|
4009
5084
|
const output = await runGraphExport(projectPath, opts.format);
|
|
4010
5085
|
console.log(output);
|
|
@@ -4017,19 +5092,19 @@ function createGraphCommand() {
|
|
|
4017
5092
|
}
|
|
4018
5093
|
|
|
4019
5094
|
// src/commands/mcp.ts
|
|
4020
|
-
import { Command as
|
|
5095
|
+
import { Command as Command50 } from "commander";
|
|
4021
5096
|
function createMcpCommand() {
|
|
4022
|
-
return new
|
|
4023
|
-
const { startServer: startServer2 } = await import("./mcp-
|
|
4024
|
-
await startServer2();
|
|
5097
|
+
return new Command50("mcp").description("Start the MCP (Model Context Protocol) server on stdio").option("--tools <tools...>", "Only register the specified tools (used by Cursor integration)").action(async (opts) => {
|
|
5098
|
+
const { startServer: startServer2 } = await import("./mcp-YENEPHBW.js");
|
|
5099
|
+
await startServer2(opts.tools);
|
|
4025
5100
|
});
|
|
4026
5101
|
}
|
|
4027
5102
|
|
|
4028
5103
|
// src/commands/impact-preview.ts
|
|
4029
|
-
import { Command as
|
|
5104
|
+
import { Command as Command51 } from "commander";
|
|
4030
5105
|
import { execSync as execSync3 } from "child_process";
|
|
4031
|
-
import * as
|
|
4032
|
-
import * as
|
|
5106
|
+
import * as path44 from "path";
|
|
5107
|
+
import * as fs26 from "fs";
|
|
4033
5108
|
function getStagedFiles(cwd) {
|
|
4034
5109
|
try {
|
|
4035
5110
|
const output = execSync3("git diff --cached --name-only", {
|
|
@@ -4043,7 +5118,7 @@ function getStagedFiles(cwd) {
|
|
|
4043
5118
|
}
|
|
4044
5119
|
function graphExists(projectPath) {
|
|
4045
5120
|
try {
|
|
4046
|
-
return
|
|
5121
|
+
return fs26.existsSync(path44.join(projectPath, ".harness", "graph", "graph.json"));
|
|
4047
5122
|
} catch {
|
|
4048
5123
|
return false;
|
|
4049
5124
|
}
|
|
@@ -4052,7 +5127,7 @@ function extractNodeName(id) {
|
|
|
4052
5127
|
const parts = id.split(":");
|
|
4053
5128
|
if (parts.length > 1) {
|
|
4054
5129
|
const fullPath = parts.slice(1).join(":");
|
|
4055
|
-
return
|
|
5130
|
+
return path44.basename(fullPath);
|
|
4056
5131
|
}
|
|
4057
5132
|
return id;
|
|
4058
5133
|
}
|
|
@@ -4175,7 +5250,7 @@ function formatPerFile(perFileResults) {
|
|
|
4175
5250
|
return lines.join("\n");
|
|
4176
5251
|
}
|
|
4177
5252
|
async function runImpactPreview(options) {
|
|
4178
|
-
const projectPath =
|
|
5253
|
+
const projectPath = path44.resolve(options.path ?? process.cwd());
|
|
4179
5254
|
const stagedFiles = getStagedFiles(projectPath);
|
|
4180
5255
|
if (stagedFiles.length === 0) {
|
|
4181
5256
|
return "Impact Preview: no staged changes";
|
|
@@ -4222,7 +5297,7 @@ async function runImpactPreview(options) {
|
|
|
4222
5297
|
return formatCompact(stagedFiles.length, merged, aggregateCounts);
|
|
4223
5298
|
}
|
|
4224
5299
|
function createImpactPreviewCommand() {
|
|
4225
|
-
const command = new
|
|
5300
|
+
const command = new Command51("impact-preview").description("Show blast radius of staged changes using the knowledge graph").option("--detailed", "Show all affected files instead of top items").option("--per-file", "Show impact per staged file instead of aggregate").option("--path <dir>", "Project root (default: cwd)").action(async (opts) => {
|
|
4226
5301
|
const output = await runImpactPreview({
|
|
4227
5302
|
detailed: opts.detailed,
|
|
4228
5303
|
perFile: opts.perFile,
|
|
@@ -4235,7 +5310,7 @@ function createImpactPreviewCommand() {
|
|
|
4235
5310
|
}
|
|
4236
5311
|
|
|
4237
5312
|
// src/commands/check-arch.ts
|
|
4238
|
-
import { Command as
|
|
5313
|
+
import { Command as Command52 } from "commander";
|
|
4239
5314
|
import { execSync as execSync4 } from "child_process";
|
|
4240
5315
|
function getCommitHash2(cwd) {
|
|
4241
5316
|
try {
|
|
@@ -4330,7 +5405,7 @@ async function runCheckArch(options) {
|
|
|
4330
5405
|
});
|
|
4331
5406
|
}
|
|
4332
5407
|
function createCheckArchCommand() {
|
|
4333
|
-
const command = new
|
|
5408
|
+
const command = new Command52("check-arch").description("Check architecture assertions against baseline and thresholds").option("--update-baseline", "Capture current state as new baseline").option("--module <path>", "Check a single module").action(async (opts, cmd) => {
|
|
4334
5409
|
const globalOpts = cmd.optsWithGlobals();
|
|
4335
5410
|
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : globalOpts.verbose ? OutputMode.VERBOSE : OutputMode.TEXT;
|
|
4336
5411
|
const formatter = new OutputFormatter(mode);
|
|
@@ -4396,20 +5471,20 @@ function createCheckArchCommand() {
|
|
|
4396
5471
|
}
|
|
4397
5472
|
|
|
4398
5473
|
// src/commands/blueprint.ts
|
|
4399
|
-
import { Command as
|
|
4400
|
-
import * as
|
|
5474
|
+
import { Command as Command53 } from "commander";
|
|
5475
|
+
import * as path45 from "path";
|
|
4401
5476
|
function createBlueprintCommand() {
|
|
4402
|
-
return new
|
|
5477
|
+
return new Command53("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
|
|
4403
5478
|
try {
|
|
4404
|
-
const rootDir =
|
|
4405
|
-
const outputDir =
|
|
5479
|
+
const rootDir = path45.resolve(projectPath);
|
|
5480
|
+
const outputDir = path45.resolve(options.output);
|
|
4406
5481
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
4407
5482
|
const scanner = new ProjectScanner(rootDir);
|
|
4408
5483
|
const data = await scanner.scan();
|
|
4409
5484
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
4410
5485
|
const generator = new BlueprintGenerator();
|
|
4411
5486
|
await generator.generate(data, { outputDir });
|
|
4412
|
-
logger.success(`Blueprint generated successfully at ${
|
|
5487
|
+
logger.success(`Blueprint generated successfully at ${path45.join(outputDir, "index.html")}`);
|
|
4413
5488
|
} catch (error) {
|
|
4414
5489
|
logger.error(
|
|
4415
5490
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4420,16 +5495,16 @@ function createBlueprintCommand() {
|
|
|
4420
5495
|
}
|
|
4421
5496
|
|
|
4422
5497
|
// src/commands/share.ts
|
|
4423
|
-
import { Command as
|
|
4424
|
-
import * as
|
|
4425
|
-
import * as
|
|
5498
|
+
import { Command as Command54 } from "commander";
|
|
5499
|
+
import * as fs27 from "fs";
|
|
5500
|
+
import * as path46 from "path";
|
|
4426
5501
|
import { parse as parseYaml } from "yaml";
|
|
4427
5502
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
4428
5503
|
function createShareCommand() {
|
|
4429
|
-
return new
|
|
4430
|
-
const rootDir =
|
|
4431
|
-
const manifestPath =
|
|
4432
|
-
if (!
|
|
5504
|
+
return new Command54("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
|
|
5505
|
+
const rootDir = path46.resolve(projectPath);
|
|
5506
|
+
const manifestPath = path46.join(rootDir, MANIFEST_FILENAME);
|
|
5507
|
+
if (!fs27.existsSync(manifestPath)) {
|
|
4433
5508
|
logger.error(
|
|
4434
5509
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
4435
5510
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -4438,7 +5513,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4438
5513
|
}
|
|
4439
5514
|
let parsed;
|
|
4440
5515
|
try {
|
|
4441
|
-
const raw =
|
|
5516
|
+
const raw = fs27.readFileSync(manifestPath, "utf-8");
|
|
4442
5517
|
parsed = parseYaml(raw);
|
|
4443
5518
|
} catch (err) {
|
|
4444
5519
|
logger.error(
|
|
@@ -4452,7 +5527,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4452
5527
|
process.exit(1);
|
|
4453
5528
|
}
|
|
4454
5529
|
const manifest = manifestResult.value;
|
|
4455
|
-
const configResult = resolveConfig(
|
|
5530
|
+
const configResult = resolveConfig(path46.join(rootDir, "harness.config.json"));
|
|
4456
5531
|
if (!configResult.ok) {
|
|
4457
5532
|
logger.error(configResult.error.message);
|
|
4458
5533
|
process.exit(1);
|
|
@@ -4470,8 +5545,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4470
5545
|
);
|
|
4471
5546
|
process.exit(1);
|
|
4472
5547
|
}
|
|
4473
|
-
const outputDir =
|
|
4474
|
-
const outputPath =
|
|
5548
|
+
const outputDir = path46.resolve(options.output);
|
|
5549
|
+
const outputPath = path46.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
4475
5550
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
4476
5551
|
if (!writeResult.ok) {
|
|
4477
5552
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -4482,25 +5557,25 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4482
5557
|
}
|
|
4483
5558
|
|
|
4484
5559
|
// src/commands/install.ts
|
|
4485
|
-
import * as
|
|
4486
|
-
import * as
|
|
4487
|
-
import { Command as
|
|
5560
|
+
import * as fs29 from "fs";
|
|
5561
|
+
import * as path48 from "path";
|
|
5562
|
+
import { Command as Command55 } from "commander";
|
|
4488
5563
|
import { parse as yamlParse } from "yaml";
|
|
4489
5564
|
|
|
4490
5565
|
// src/registry/tarball.ts
|
|
4491
|
-
import * as
|
|
4492
|
-
import * as
|
|
4493
|
-
import * as
|
|
5566
|
+
import * as fs28 from "fs";
|
|
5567
|
+
import * as path47 from "path";
|
|
5568
|
+
import * as os6 from "os";
|
|
4494
5569
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
4495
5570
|
function extractTarball(tarballBuffer) {
|
|
4496
|
-
const tmpDir =
|
|
4497
|
-
const tarballPath =
|
|
5571
|
+
const tmpDir = fs28.mkdtempSync(path47.join(os6.tmpdir(), "harness-skill-install-"));
|
|
5572
|
+
const tarballPath = path47.join(tmpDir, "package.tgz");
|
|
4498
5573
|
try {
|
|
4499
|
-
|
|
5574
|
+
fs28.writeFileSync(tarballPath, tarballBuffer);
|
|
4500
5575
|
execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4501
5576
|
timeout: 3e4
|
|
4502
5577
|
});
|
|
4503
|
-
|
|
5578
|
+
fs28.unlinkSync(tarballPath);
|
|
4504
5579
|
} catch (err) {
|
|
4505
5580
|
cleanupTempDir(tmpDir);
|
|
4506
5581
|
throw new Error(
|
|
@@ -4511,43 +5586,43 @@ function extractTarball(tarballBuffer) {
|
|
|
4511
5586
|
return tmpDir;
|
|
4512
5587
|
}
|
|
4513
5588
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4514
|
-
const files =
|
|
5589
|
+
const files = fs28.readdirSync(extractedPkgDir);
|
|
4515
5590
|
for (const platform of platforms) {
|
|
4516
|
-
const targetDir =
|
|
4517
|
-
if (
|
|
4518
|
-
|
|
5591
|
+
const targetDir = path47.join(communityBaseDir, platform, skillName);
|
|
5592
|
+
if (fs28.existsSync(targetDir)) {
|
|
5593
|
+
fs28.rmSync(targetDir, { recursive: true, force: true });
|
|
4519
5594
|
}
|
|
4520
|
-
|
|
5595
|
+
fs28.mkdirSync(targetDir, { recursive: true });
|
|
4521
5596
|
for (const file of files) {
|
|
4522
5597
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4523
|
-
const srcPath =
|
|
4524
|
-
const destPath =
|
|
4525
|
-
const stat =
|
|
5598
|
+
const srcPath = path47.join(extractedPkgDir, file);
|
|
5599
|
+
const destPath = path47.join(targetDir, file);
|
|
5600
|
+
const stat = fs28.statSync(srcPath);
|
|
4526
5601
|
if (stat.isDirectory()) {
|
|
4527
|
-
|
|
5602
|
+
fs28.cpSync(srcPath, destPath, { recursive: true });
|
|
4528
5603
|
} else {
|
|
4529
|
-
|
|
5604
|
+
fs28.copyFileSync(srcPath, destPath);
|
|
4530
5605
|
}
|
|
4531
5606
|
}
|
|
4532
5607
|
}
|
|
4533
5608
|
}
|
|
4534
5609
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4535
5610
|
for (const platform of platforms) {
|
|
4536
|
-
const targetDir =
|
|
4537
|
-
if (
|
|
4538
|
-
|
|
5611
|
+
const targetDir = path47.join(communityBaseDir, platform, skillName);
|
|
5612
|
+
if (fs28.existsSync(targetDir)) {
|
|
5613
|
+
fs28.rmSync(targetDir, { recursive: true, force: true });
|
|
4539
5614
|
}
|
|
4540
5615
|
}
|
|
4541
5616
|
}
|
|
4542
5617
|
function cleanupTempDir(dirPath) {
|
|
4543
5618
|
try {
|
|
4544
|
-
|
|
5619
|
+
fs28.rmSync(dirPath, { recursive: true, force: true });
|
|
4545
5620
|
} catch {
|
|
4546
5621
|
}
|
|
4547
5622
|
}
|
|
4548
5623
|
|
|
4549
5624
|
// src/registry/resolver.ts
|
|
4550
|
-
import
|
|
5625
|
+
import semver3 from "semver";
|
|
4551
5626
|
function resolveVersion(metadata, versionRange) {
|
|
4552
5627
|
const versions = Object.keys(metadata.versions);
|
|
4553
5628
|
if (versions.length === 0) {
|
|
@@ -4559,13 +5634,13 @@ function resolveVersion(metadata, versionRange) {
|
|
|
4559
5634
|
const latestInfo = metadata.versions[latestTag];
|
|
4560
5635
|
if (latestInfo) return latestInfo;
|
|
4561
5636
|
}
|
|
4562
|
-
const highest =
|
|
5637
|
+
const highest = semver3.maxSatisfying(versions, "*");
|
|
4563
5638
|
if (!highest || !metadata.versions[highest]) {
|
|
4564
5639
|
throw new Error(`No versions available for ${metadata.name}.`);
|
|
4565
5640
|
}
|
|
4566
5641
|
return metadata.versions[highest];
|
|
4567
5642
|
}
|
|
4568
|
-
const matched =
|
|
5643
|
+
const matched = semver3.maxSatisfying(versions, versionRange);
|
|
4569
5644
|
if (!matched || !metadata.versions[matched]) {
|
|
4570
5645
|
throw new Error(
|
|
4571
5646
|
`No version of ${metadata.name} matches range ${versionRange}. Available: ${versions.join(", ")}`
|
|
@@ -4597,35 +5672,35 @@ function validateSkillYaml(parsed) {
|
|
|
4597
5672
|
};
|
|
4598
5673
|
}
|
|
4599
5674
|
async function runLocalInstall(fromPath, options) {
|
|
4600
|
-
const resolvedPath =
|
|
4601
|
-
if (!
|
|
5675
|
+
const resolvedPath = path48.resolve(fromPath);
|
|
5676
|
+
if (!fs29.existsSync(resolvedPath)) {
|
|
4602
5677
|
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4603
5678
|
}
|
|
4604
|
-
const stat =
|
|
5679
|
+
const stat = fs29.statSync(resolvedPath);
|
|
4605
5680
|
let extractDir = null;
|
|
4606
5681
|
let pkgDir;
|
|
4607
5682
|
if (stat.isDirectory()) {
|
|
4608
5683
|
pkgDir = resolvedPath;
|
|
4609
5684
|
} else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4610
|
-
const tarballBuffer =
|
|
5685
|
+
const tarballBuffer = fs29.readFileSync(resolvedPath);
|
|
4611
5686
|
extractDir = extractTarball(tarballBuffer);
|
|
4612
|
-
pkgDir =
|
|
5687
|
+
pkgDir = path48.join(extractDir, "package");
|
|
4613
5688
|
} else {
|
|
4614
5689
|
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4615
5690
|
}
|
|
4616
5691
|
try {
|
|
4617
|
-
const skillYamlPath =
|
|
4618
|
-
if (!
|
|
5692
|
+
const skillYamlPath = path48.join(pkgDir, "skill.yaml");
|
|
5693
|
+
if (!fs29.existsSync(skillYamlPath)) {
|
|
4619
5694
|
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4620
5695
|
}
|
|
4621
|
-
const rawYaml =
|
|
5696
|
+
const rawYaml = fs29.readFileSync(skillYamlPath, "utf-8");
|
|
4622
5697
|
const parsed = yamlParse(rawYaml);
|
|
4623
5698
|
const skillYaml = validateSkillYaml(parsed);
|
|
4624
5699
|
const shortName = skillYaml.name;
|
|
4625
5700
|
const globalDir = resolveGlobalSkillsDir();
|
|
4626
|
-
const skillsDir =
|
|
4627
|
-
const communityBase =
|
|
4628
|
-
const lockfilePath =
|
|
5701
|
+
const skillsDir = path48.dirname(globalDir);
|
|
5702
|
+
const communityBase = path48.join(skillsDir, "community");
|
|
5703
|
+
const lockfilePath = path48.join(communityBase, "skills-lock.json");
|
|
4629
5704
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
4630
5705
|
if (bundledNames.has(shortName)) {
|
|
4631
5706
|
throw new Error(
|
|
@@ -4666,9 +5741,9 @@ async function runInstall(skillName, options) {
|
|
|
4666
5741
|
const packageName = resolvePackageName(skillName);
|
|
4667
5742
|
const shortName = extractSkillName(packageName);
|
|
4668
5743
|
const globalDir = resolveGlobalSkillsDir();
|
|
4669
|
-
const skillsDir =
|
|
4670
|
-
const communityBase =
|
|
4671
|
-
const lockfilePath =
|
|
5744
|
+
const skillsDir = path48.dirname(globalDir);
|
|
5745
|
+
const communityBase = path48.join(skillsDir, "community");
|
|
5746
|
+
const lockfilePath = path48.join(communityBase, "skills-lock.json");
|
|
4672
5747
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
4673
5748
|
if (bundledNames.has(shortName)) {
|
|
4674
5749
|
throw new Error(
|
|
@@ -4694,12 +5769,12 @@ async function runInstall(skillName, options) {
|
|
|
4694
5769
|
const extractDir = extractTarball(tarballBuffer);
|
|
4695
5770
|
let skillYaml;
|
|
4696
5771
|
try {
|
|
4697
|
-
const extractedPkgDir =
|
|
4698
|
-
const skillYamlPath =
|
|
4699
|
-
if (!
|
|
5772
|
+
const extractedPkgDir = path48.join(extractDir, "package");
|
|
5773
|
+
const skillYamlPath = path48.join(extractedPkgDir, "skill.yaml");
|
|
5774
|
+
if (!fs29.existsSync(skillYamlPath)) {
|
|
4700
5775
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4701
5776
|
}
|
|
4702
|
-
const rawYaml =
|
|
5777
|
+
const rawYaml = fs29.readFileSync(skillYamlPath, "utf-8");
|
|
4703
5778
|
const parsed = yamlParse(rawYaml);
|
|
4704
5779
|
skillYaml = validateSkillYaml(parsed);
|
|
4705
5780
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4738,7 +5813,7 @@ async function runInstall(skillName, options) {
|
|
|
4738
5813
|
return result;
|
|
4739
5814
|
}
|
|
4740
5815
|
function createInstallCommand() {
|
|
4741
|
-
const cmd = new
|
|
5816
|
+
const cmd = new Command55("install");
|
|
4742
5817
|
cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option("--from <path>", "Install from a local directory or .tgz file").option("--registry <url>", "Use a custom npm registry URL").action(async (skill, opts) => {
|
|
4743
5818
|
try {
|
|
4744
5819
|
const result = await runInstall(skill, opts);
|
|
@@ -4762,15 +5837,15 @@ function createInstallCommand() {
|
|
|
4762
5837
|
}
|
|
4763
5838
|
|
|
4764
5839
|
// src/commands/install-constraints.ts
|
|
4765
|
-
import * as
|
|
4766
|
-
import * as
|
|
4767
|
-
import { Command as
|
|
4768
|
-
import
|
|
5840
|
+
import * as fs30 from "fs/promises";
|
|
5841
|
+
import * as path49 from "path";
|
|
5842
|
+
import { Command as Command56 } from "commander";
|
|
5843
|
+
import semver4 from "semver";
|
|
4769
5844
|
async function runInstallConstraints(options) {
|
|
4770
5845
|
const { source, configPath, lockfilePath } = options;
|
|
4771
5846
|
let rawBundle;
|
|
4772
5847
|
try {
|
|
4773
|
-
rawBundle = await
|
|
5848
|
+
rawBundle = await fs30.readFile(source, "utf-8");
|
|
4774
5849
|
} catch (err) {
|
|
4775
5850
|
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4776
5851
|
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
@@ -4793,9 +5868,9 @@ async function runInstallConstraints(options) {
|
|
|
4793
5868
|
}
|
|
4794
5869
|
const bundle = bundleResult.data;
|
|
4795
5870
|
if (bundle.minHarnessVersion) {
|
|
4796
|
-
const installed =
|
|
4797
|
-
const required =
|
|
4798
|
-
if (installed && required &&
|
|
5871
|
+
const installed = semver4.valid(semver4.coerce(CLI_VERSION));
|
|
5872
|
+
const required = semver4.valid(semver4.coerce(bundle.minHarnessVersion));
|
|
5873
|
+
if (installed && required && semver4.lt(installed, required)) {
|
|
4799
5874
|
return {
|
|
4800
5875
|
ok: false,
|
|
4801
5876
|
error: `Bundle requires harness version >= ${bundle.minHarnessVersion}, but installed version is ${CLI_VERSION}. Please upgrade.`
|
|
@@ -4813,7 +5888,7 @@ async function runInstallConstraints(options) {
|
|
|
4813
5888
|
}
|
|
4814
5889
|
let localConfig;
|
|
4815
5890
|
try {
|
|
4816
|
-
const raw = await
|
|
5891
|
+
const raw = await fs30.readFile(configPath, "utf-8");
|
|
4817
5892
|
localConfig = JSON.parse(raw);
|
|
4818
5893
|
} catch (err) {
|
|
4819
5894
|
return {
|
|
@@ -4961,7 +6036,7 @@ function isNodeError(err) {
|
|
|
4961
6036
|
return err instanceof Error && "code" in err;
|
|
4962
6037
|
}
|
|
4963
6038
|
function resolveConfigPath(opts) {
|
|
4964
|
-
if (opts.config) return
|
|
6039
|
+
if (opts.config) return path49.resolve(opts.config);
|
|
4965
6040
|
const found = findConfigFile();
|
|
4966
6041
|
if (!found.ok) {
|
|
4967
6042
|
logger.error(found.error.message);
|
|
@@ -4996,9 +6071,9 @@ function logInstallResult(val, opts) {
|
|
|
4996
6071
|
}
|
|
4997
6072
|
async function handleInstallConstraints(source, opts) {
|
|
4998
6073
|
const configPath = resolveConfigPath(opts);
|
|
4999
|
-
const projectRoot =
|
|
5000
|
-
const lockfilePath =
|
|
5001
|
-
const resolvedSource =
|
|
6074
|
+
const projectRoot = path49.dirname(configPath);
|
|
6075
|
+
const lockfilePath = path49.join(projectRoot, ".harness", "constraints.lock.json");
|
|
6076
|
+
const resolvedSource = path49.resolve(source);
|
|
5002
6077
|
if (opts.forceLocal && opts.forcePackage) {
|
|
5003
6078
|
logger.error("Cannot use both --force-local and --force-package.");
|
|
5004
6079
|
process.exit(1);
|
|
@@ -5018,15 +6093,15 @@ async function handleInstallConstraints(source, opts) {
|
|
|
5018
6093
|
logInstallResult(result.value, opts);
|
|
5019
6094
|
}
|
|
5020
6095
|
function createInstallConstraintsCommand() {
|
|
5021
|
-
const cmd = new
|
|
6096
|
+
const cmd = new Command56("install-constraints");
|
|
5022
6097
|
cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
|
|
5023
6098
|
return cmd;
|
|
5024
6099
|
}
|
|
5025
6100
|
|
|
5026
6101
|
// src/commands/uninstall-constraints.ts
|
|
5027
|
-
import * as
|
|
5028
|
-
import * as
|
|
5029
|
-
import { Command as
|
|
6102
|
+
import * as fs31 from "fs/promises";
|
|
6103
|
+
import * as path50 from "path";
|
|
6104
|
+
import { Command as Command57 } from "commander";
|
|
5030
6105
|
async function runUninstallConstraints(options) {
|
|
5031
6106
|
const { packageName, configPath, lockfilePath } = options;
|
|
5032
6107
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -5046,7 +6121,7 @@ async function runUninstallConstraints(options) {
|
|
|
5046
6121
|
}
|
|
5047
6122
|
let localConfig;
|
|
5048
6123
|
try {
|
|
5049
|
-
const raw = await
|
|
6124
|
+
const raw = await fs31.readFile(configPath, "utf-8");
|
|
5050
6125
|
localConfig = JSON.parse(raw);
|
|
5051
6126
|
} catch (err) {
|
|
5052
6127
|
return {
|
|
@@ -5083,11 +6158,11 @@ async function runUninstallConstraints(options) {
|
|
|
5083
6158
|
};
|
|
5084
6159
|
}
|
|
5085
6160
|
function createUninstallConstraintsCommand() {
|
|
5086
|
-
const cmd = new
|
|
6161
|
+
const cmd = new Command57("uninstall-constraints");
|
|
5087
6162
|
cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
|
|
5088
6163
|
let configPath;
|
|
5089
6164
|
if (opts.config) {
|
|
5090
|
-
configPath =
|
|
6165
|
+
configPath = path50.resolve(opts.config);
|
|
5091
6166
|
} else {
|
|
5092
6167
|
const found = findConfigFile();
|
|
5093
6168
|
if (!found.ok) {
|
|
@@ -5096,8 +6171,8 @@ function createUninstallConstraintsCommand() {
|
|
|
5096
6171
|
}
|
|
5097
6172
|
configPath = found.value;
|
|
5098
6173
|
}
|
|
5099
|
-
const projectRoot =
|
|
5100
|
-
const lockfilePath =
|
|
6174
|
+
const projectRoot = path50.dirname(configPath);
|
|
6175
|
+
const lockfilePath = path50.join(projectRoot, ".harness", "constraints.lock.json");
|
|
5101
6176
|
const result = await runUninstallConstraints({
|
|
5102
6177
|
packageName: name,
|
|
5103
6178
|
configPath,
|
|
@@ -5122,15 +6197,15 @@ function createUninstallConstraintsCommand() {
|
|
|
5122
6197
|
}
|
|
5123
6198
|
|
|
5124
6199
|
// src/commands/uninstall.ts
|
|
5125
|
-
import * as
|
|
5126
|
-
import { Command as
|
|
6200
|
+
import * as path51 from "path";
|
|
6201
|
+
import { Command as Command58 } from "commander";
|
|
5127
6202
|
async function runUninstall(skillName, options) {
|
|
5128
6203
|
const packageName = resolvePackageName(skillName);
|
|
5129
6204
|
const shortName = extractSkillName(packageName);
|
|
5130
6205
|
const globalDir = resolveGlobalSkillsDir();
|
|
5131
|
-
const skillsDir =
|
|
5132
|
-
const communityBase =
|
|
5133
|
-
const lockfilePath =
|
|
6206
|
+
const skillsDir = path51.dirname(globalDir);
|
|
6207
|
+
const communityBase = path51.join(skillsDir, "community");
|
|
6208
|
+
const lockfilePath = path51.join(communityBase, "skills-lock.json");
|
|
5134
6209
|
const lockfile = readLockfile2(lockfilePath);
|
|
5135
6210
|
const entry = lockfile.skills[packageName];
|
|
5136
6211
|
if (!entry) {
|
|
@@ -5160,7 +6235,7 @@ async function runUninstall(skillName, options) {
|
|
|
5160
6235
|
return result;
|
|
5161
6236
|
}
|
|
5162
6237
|
function createUninstallCommand() {
|
|
5163
|
-
const cmd = new
|
|
6238
|
+
const cmd = new Command58("uninstall");
|
|
5164
6239
|
cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
|
|
5165
6240
|
try {
|
|
5166
6241
|
const result = await runUninstall(skill, opts);
|
|
@@ -5179,13 +6254,13 @@ function createUninstallCommand() {
|
|
|
5179
6254
|
}
|
|
5180
6255
|
|
|
5181
6256
|
// src/commands/orchestrator.ts
|
|
5182
|
-
import { Command as
|
|
5183
|
-
import * as
|
|
6257
|
+
import { Command as Command59 } from "commander";
|
|
6258
|
+
import * as path52 from "path";
|
|
5184
6259
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5185
6260
|
function createOrchestratorCommand() {
|
|
5186
|
-
const orchestrator = new
|
|
6261
|
+
const orchestrator = new Command59("orchestrator");
|
|
5187
6262
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
5188
|
-
const workflowPath =
|
|
6263
|
+
const workflowPath = path52.resolve(process.cwd(), opts.workflow);
|
|
5189
6264
|
const loader = new WorkflowLoader();
|
|
5190
6265
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5191
6266
|
if (!result.ok) {
|
|
@@ -5209,13 +6284,13 @@ function createOrchestratorCommand() {
|
|
|
5209
6284
|
}
|
|
5210
6285
|
|
|
5211
6286
|
// src/commands/learnings/index.ts
|
|
5212
|
-
import { Command as
|
|
6287
|
+
import { Command as Command61 } from "commander";
|
|
5213
6288
|
|
|
5214
6289
|
// src/commands/learnings/prune.ts
|
|
5215
|
-
import { Command as
|
|
5216
|
-
import * as
|
|
6290
|
+
import { Command as Command60 } from "commander";
|
|
6291
|
+
import * as path53 from "path";
|
|
5217
6292
|
async function handlePrune(opts) {
|
|
5218
|
-
const projectPath =
|
|
6293
|
+
const projectPath = path53.resolve(opts.path);
|
|
5219
6294
|
const result = await pruneLearnings(projectPath, opts.stream);
|
|
5220
6295
|
if (!result.ok) {
|
|
5221
6296
|
logger.error(result.error.message);
|
|
@@ -5254,21 +6329,666 @@ function printPatternProposals(patterns) {
|
|
|
5254
6329
|
);
|
|
5255
6330
|
}
|
|
5256
6331
|
function createPruneCommand() {
|
|
5257
|
-
return new
|
|
6332
|
+
return new Command60("prune").description(
|
|
5258
6333
|
"Analyze global learnings for patterns, present improvement proposals, and archive old entries"
|
|
5259
6334
|
).option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(handlePrune);
|
|
5260
6335
|
}
|
|
5261
6336
|
|
|
5262
6337
|
// src/commands/learnings/index.ts
|
|
5263
6338
|
function createLearningsCommand() {
|
|
5264
|
-
const command = new
|
|
6339
|
+
const command = new Command61("learnings").description("Learnings management commands");
|
|
5265
6340
|
command.addCommand(createPruneCommand());
|
|
5266
6341
|
return command;
|
|
5267
6342
|
}
|
|
5268
6343
|
|
|
6344
|
+
// src/commands/integrations/index.ts
|
|
6345
|
+
import { Command as Command66 } from "commander";
|
|
6346
|
+
|
|
6347
|
+
// src/commands/integrations/add.ts
|
|
6348
|
+
import { Command as Command62 } from "commander";
|
|
6349
|
+
import * as fs32 from "fs";
|
|
6350
|
+
import * as path54 from "path";
|
|
6351
|
+
import chalk6 from "chalk";
|
|
6352
|
+
function addIntegration(cwd, name) {
|
|
6353
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6354
|
+
if (!def) {
|
|
6355
|
+
return Err(
|
|
6356
|
+
new CLIError(
|
|
6357
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6358
|
+
ExitCode.ERROR
|
|
6359
|
+
)
|
|
6360
|
+
);
|
|
6361
|
+
}
|
|
6362
|
+
if (def.tier === 0) {
|
|
6363
|
+
return Err(
|
|
6364
|
+
new CLIError(
|
|
6365
|
+
`${def.displayName} is a Tier 0 integration, already configured by 'harness setup'. Run 'harness setup' if missing.`,
|
|
6366
|
+
ExitCode.ERROR
|
|
6367
|
+
)
|
|
6368
|
+
);
|
|
6369
|
+
}
|
|
6370
|
+
const mcpPath = path54.join(cwd, ".mcp.json");
|
|
6371
|
+
const mcpEntry = {
|
|
6372
|
+
command: def.mcpConfig.command
|
|
6373
|
+
};
|
|
6374
|
+
if (def.mcpConfig.args.length > 0) mcpEntry.args = def.mcpConfig.args;
|
|
6375
|
+
if (def.mcpConfig.env) mcpEntry.env = def.mcpConfig.env;
|
|
6376
|
+
writeMcpEntry(mcpPath, def.name, mcpEntry);
|
|
6377
|
+
const geminiDir = path54.join(cwd, ".gemini");
|
|
6378
|
+
if (fs32.existsSync(geminiDir)) {
|
|
6379
|
+
const geminiPath = path54.join(geminiDir, "settings.json");
|
|
6380
|
+
writeMcpEntry(geminiPath, def.name, mcpEntry);
|
|
6381
|
+
}
|
|
6382
|
+
const configPath = path54.join(cwd, "harness.config.json");
|
|
6383
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6384
|
+
if (!integConfig.enabled.includes(def.name)) {
|
|
6385
|
+
integConfig.enabled.push(def.name);
|
|
6386
|
+
}
|
|
6387
|
+
integConfig.dismissed = integConfig.dismissed.filter((d) => d !== def.name);
|
|
6388
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6389
|
+
const envVarMissing = !!def.envVar && !process.env[def.envVar];
|
|
6390
|
+
return Ok({
|
|
6391
|
+
name: def.name,
|
|
6392
|
+
displayName: def.displayName,
|
|
6393
|
+
envVarMissing,
|
|
6394
|
+
...def.envVar !== void 0 && { envVar: def.envVar },
|
|
6395
|
+
...def.installHint !== void 0 && { installHint: def.installHint }
|
|
6396
|
+
});
|
|
6397
|
+
}
|
|
6398
|
+
function createAddIntegrationCommand() {
|
|
6399
|
+
return new Command62("add").description("Enable an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6400
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6401
|
+
const cwd = process.cwd();
|
|
6402
|
+
const result = addIntegration(cwd, name);
|
|
6403
|
+
if (!result.ok) {
|
|
6404
|
+
if (!globalOpts.quiet) {
|
|
6405
|
+
logger.error(result.error.message);
|
|
6406
|
+
}
|
|
6407
|
+
process.exit(result.error.exitCode);
|
|
6408
|
+
return;
|
|
6409
|
+
}
|
|
6410
|
+
const { displayName, envVarMissing, envVar, installHint } = result.value;
|
|
6411
|
+
if (!globalOpts.quiet) {
|
|
6412
|
+
console.log("");
|
|
6413
|
+
logger.success(`${displayName} integration enabled.`);
|
|
6414
|
+
console.log("");
|
|
6415
|
+
if (envVarMissing && envVar) {
|
|
6416
|
+
logger.warn(`Set ${chalk6.bold(envVar)} in your environment to activate.`);
|
|
6417
|
+
if (installHint) {
|
|
6418
|
+
console.log(` ${chalk6.dim(installHint)}`);
|
|
6419
|
+
}
|
|
6420
|
+
console.log("");
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
process.exit(ExitCode.SUCCESS);
|
|
6424
|
+
});
|
|
6425
|
+
}
|
|
6426
|
+
|
|
6427
|
+
// src/commands/integrations/list.ts
|
|
6428
|
+
import { Command as Command63 } from "commander";
|
|
6429
|
+
import * as path55 from "path";
|
|
6430
|
+
import chalk7 from "chalk";
|
|
6431
|
+
function createListIntegrationsCommand() {
|
|
6432
|
+
return new Command63("list").description("Show all MCP integrations with status").action(async (_opts, cmd) => {
|
|
6433
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6434
|
+
const cwd = process.cwd();
|
|
6435
|
+
const mcpPath = path55.join(cwd, ".mcp.json");
|
|
6436
|
+
const configPath = path55.join(cwd, "harness.config.json");
|
|
6437
|
+
const mcpConfig = readMcpConfig(mcpPath);
|
|
6438
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6439
|
+
const mcpServers = mcpConfig.mcpServers ?? {};
|
|
6440
|
+
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
6441
|
+
const tier1 = INTEGRATION_REGISTRY.filter((i) => i.tier === 1);
|
|
6442
|
+
if (globalOpts.json) {
|
|
6443
|
+
const entries = INTEGRATION_REGISTRY.map((i) => ({
|
|
6444
|
+
name: i.name,
|
|
6445
|
+
tier: i.tier,
|
|
6446
|
+
configured: i.name in mcpServers,
|
|
6447
|
+
enabled: integConfig.enabled.includes(i.name),
|
|
6448
|
+
dismissed: integConfig.dismissed.includes(i.name),
|
|
6449
|
+
envVar: i.envVar ?? null,
|
|
6450
|
+
envVarSet: i.envVar ? !!process.env[i.envVar] : null
|
|
6451
|
+
}));
|
|
6452
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
6453
|
+
process.exit(ExitCode.SUCCESS);
|
|
6454
|
+
return;
|
|
6455
|
+
}
|
|
6456
|
+
console.log("");
|
|
6457
|
+
console.log("MCP Integrations:");
|
|
6458
|
+
console.log("");
|
|
6459
|
+
console.log(" Tier 0 (zero-config):");
|
|
6460
|
+
for (const i of tier0) {
|
|
6461
|
+
const configured = i.name in mcpServers;
|
|
6462
|
+
const icon = configured ? chalk7.green("\u2713") : chalk7.dim("\u25CB");
|
|
6463
|
+
console.log(` ${icon} ${i.name.padEnd(22)} ${i.description}`);
|
|
6464
|
+
}
|
|
6465
|
+
console.log("");
|
|
6466
|
+
console.log(" Tier 1 (API key required):");
|
|
6467
|
+
for (const i of tier1) {
|
|
6468
|
+
const configured = i.name in mcpServers;
|
|
6469
|
+
const dismissed = integConfig.dismissed.includes(i.name);
|
|
6470
|
+
const icon = configured ? chalk7.green("\u2713") : chalk7.dim("\u25CB");
|
|
6471
|
+
let suffix = "";
|
|
6472
|
+
if (dismissed) {
|
|
6473
|
+
suffix = chalk7.dim("[dismissed]");
|
|
6474
|
+
} else if (i.envVar) {
|
|
6475
|
+
const envSet = !!process.env[i.envVar];
|
|
6476
|
+
suffix = `${i.envVar} ${envSet ? chalk7.green("\u2713") : chalk7.yellow("not set")}`;
|
|
6477
|
+
}
|
|
6478
|
+
console.log(` ${icon} ${i.name.padEnd(22)} ${i.description.padEnd(35)} ${suffix}`);
|
|
6479
|
+
}
|
|
6480
|
+
console.log("");
|
|
6481
|
+
console.log(
|
|
6482
|
+
` Run '${chalk7.cyan("harness integrations add <name>")}' to enable a Tier 1 integration.`
|
|
6483
|
+
);
|
|
6484
|
+
console.log("");
|
|
6485
|
+
process.exit(ExitCode.SUCCESS);
|
|
6486
|
+
});
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6489
|
+
// src/commands/integrations/remove.ts
|
|
6490
|
+
import { Command as Command64 } from "commander";
|
|
6491
|
+
import * as fs33 from "fs";
|
|
6492
|
+
import * as path56 from "path";
|
|
6493
|
+
function removeIntegration(cwd, name) {
|
|
6494
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6495
|
+
if (!def) {
|
|
6496
|
+
return Err(
|
|
6497
|
+
new CLIError(
|
|
6498
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6499
|
+
ExitCode.ERROR
|
|
6500
|
+
)
|
|
6501
|
+
);
|
|
6502
|
+
}
|
|
6503
|
+
const mcpPath = path56.join(cwd, ".mcp.json");
|
|
6504
|
+
removeMcpEntry(mcpPath, def.name);
|
|
6505
|
+
const geminiDir = path56.join(cwd, ".gemini");
|
|
6506
|
+
if (fs33.existsSync(geminiDir)) {
|
|
6507
|
+
const geminiPath = path56.join(geminiDir, "settings.json");
|
|
6508
|
+
removeMcpEntry(geminiPath, def.name);
|
|
6509
|
+
}
|
|
6510
|
+
const configPath = path56.join(cwd, "harness.config.json");
|
|
6511
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6512
|
+
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
6513
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6514
|
+
return Ok(def.displayName);
|
|
6515
|
+
}
|
|
6516
|
+
function createRemoveIntegrationCommand() {
|
|
6517
|
+
return new Command64("remove").description("Remove an MCP integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6518
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6519
|
+
const cwd = process.cwd();
|
|
6520
|
+
const result = removeIntegration(cwd, name);
|
|
6521
|
+
if (!result.ok) {
|
|
6522
|
+
if (!globalOpts.quiet) {
|
|
6523
|
+
logger.error(result.error.message);
|
|
6524
|
+
}
|
|
6525
|
+
process.exit(result.error.exitCode);
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
if (!globalOpts.quiet) {
|
|
6529
|
+
console.log("");
|
|
6530
|
+
logger.success(`${result.value} integration removed.`);
|
|
6531
|
+
console.log("");
|
|
6532
|
+
}
|
|
6533
|
+
process.exit(ExitCode.SUCCESS);
|
|
6534
|
+
});
|
|
6535
|
+
}
|
|
6536
|
+
|
|
6537
|
+
// src/commands/integrations/dismiss.ts
|
|
6538
|
+
import { Command as Command65 } from "commander";
|
|
6539
|
+
import * as path57 from "path";
|
|
6540
|
+
function dismissIntegration(cwd, name) {
|
|
6541
|
+
const def = INTEGRATION_REGISTRY.find((i) => i.name === name);
|
|
6542
|
+
if (!def) {
|
|
6543
|
+
return Err(
|
|
6544
|
+
new CLIError(
|
|
6545
|
+
`Integration '${name}' not found in registry. Run 'harness integrations list' to see available integrations.`,
|
|
6546
|
+
ExitCode.ERROR
|
|
6547
|
+
)
|
|
6548
|
+
);
|
|
6549
|
+
}
|
|
6550
|
+
const configPath = path57.join(cwd, "harness.config.json");
|
|
6551
|
+
const integConfig = readIntegrationsConfig(configPath);
|
|
6552
|
+
if (!integConfig.dismissed.includes(def.name)) {
|
|
6553
|
+
integConfig.dismissed.push(def.name);
|
|
6554
|
+
}
|
|
6555
|
+
integConfig.enabled = integConfig.enabled.filter((e) => e !== def.name);
|
|
6556
|
+
writeIntegrationsConfig(configPath, integConfig);
|
|
6557
|
+
return Ok(def.displayName);
|
|
6558
|
+
}
|
|
6559
|
+
function createDismissIntegrationCommand() {
|
|
6560
|
+
return new Command65("dismiss").description("Suppress doctor recommendations for an integration").argument("<name>", "Integration name (e.g. perplexity, augment-code)").action(async (name, _opts, cmd) => {
|
|
6561
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6562
|
+
const cwd = process.cwd();
|
|
6563
|
+
const result = dismissIntegration(cwd, name);
|
|
6564
|
+
if (!result.ok) {
|
|
6565
|
+
if (!globalOpts.quiet) {
|
|
6566
|
+
logger.error(result.error.message);
|
|
6567
|
+
}
|
|
6568
|
+
process.exit(result.error.exitCode);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6571
|
+
if (!globalOpts.quiet) {
|
|
6572
|
+
console.log("");
|
|
6573
|
+
logger.info(
|
|
6574
|
+
`${result.value} dismissed. It will no longer appear in 'harness doctor' suggestions.`
|
|
6575
|
+
);
|
|
6576
|
+
console.log("");
|
|
6577
|
+
}
|
|
6578
|
+
process.exit(ExitCode.SUCCESS);
|
|
6579
|
+
});
|
|
6580
|
+
}
|
|
6581
|
+
|
|
6582
|
+
// src/commands/integrations/index.ts
|
|
6583
|
+
function createIntegrationsCommand() {
|
|
6584
|
+
const command = new Command66("integrations").description(
|
|
6585
|
+
"Manage MCP peer integrations (add, list, remove, dismiss)"
|
|
6586
|
+
);
|
|
6587
|
+
command.addCommand(createListIntegrationsCommand());
|
|
6588
|
+
command.addCommand(createAddIntegrationCommand());
|
|
6589
|
+
command.addCommand(createRemoveIntegrationCommand());
|
|
6590
|
+
command.addCommand(createDismissIntegrationCommand());
|
|
6591
|
+
return command;
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
// src/commands/usage.ts
|
|
6595
|
+
import { Command as Command67 } from "commander";
|
|
6596
|
+
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
6597
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-4LPXJYVZ.js");
|
|
6598
|
+
const records = readCostRecords(cwd);
|
|
6599
|
+
if (includeClaudeSessions) {
|
|
6600
|
+
const ccRecords = parseCCRecords();
|
|
6601
|
+
records.push(...ccRecords);
|
|
6602
|
+
}
|
|
6603
|
+
if (records.length === 0) return records;
|
|
6604
|
+
const pricingData = await loadPricingData(cwd);
|
|
6605
|
+
for (const record of records) {
|
|
6606
|
+
if (record.model && record.costMicroUSD == null) {
|
|
6607
|
+
const cost = calculateCost(record, pricingData);
|
|
6608
|
+
if (cost != null) record.costMicroUSD = cost;
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
return records;
|
|
6612
|
+
}
|
|
6613
|
+
function formatMicroUSD(microUSD) {
|
|
6614
|
+
if (microUSD == null) return "N/A";
|
|
6615
|
+
return "$" + (microUSD / 1e6).toFixed(4);
|
|
6616
|
+
}
|
|
6617
|
+
function formatTokenCount(count) {
|
|
6618
|
+
if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
|
|
6619
|
+
if (count >= 1e3) return (count / 1e3).toFixed(1) + "K";
|
|
6620
|
+
return String(count);
|
|
6621
|
+
}
|
|
6622
|
+
function formatModels(models) {
|
|
6623
|
+
if (models.length === 0) return "unknown";
|
|
6624
|
+
if (models.length === 1) return models[0] ?? "unknown";
|
|
6625
|
+
return `${models[0] ?? "unknown"} and ${models.length - 1} other${models.length - 1 > 1 ? "s" : ""}`;
|
|
6626
|
+
}
|
|
6627
|
+
function registerDailyCommand(usage) {
|
|
6628
|
+
usage.command("daily").description("Show per-day token usage and cost").option("--days <n>", "Number of days to show (default: 7, max: 90)", "7").action(async (opts, cmd) => {
|
|
6629
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6630
|
+
const days = Math.min(Math.max(parseInt(opts.days, 10) || 7, 1), 90);
|
|
6631
|
+
const cwd = process.cwd();
|
|
6632
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6633
|
+
if (records.length === 0) {
|
|
6634
|
+
if (globalOpts.json) {
|
|
6635
|
+
console.log(JSON.stringify([]));
|
|
6636
|
+
} else {
|
|
6637
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6638
|
+
}
|
|
6639
|
+
return;
|
|
6640
|
+
}
|
|
6641
|
+
const { aggregateByDay } = await import("./dist-4LPXJYVZ.js");
|
|
6642
|
+
const dailyData = aggregateByDay(records);
|
|
6643
|
+
const limited = dailyData.slice(0, days);
|
|
6644
|
+
if (globalOpts.json) {
|
|
6645
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
const header = "Date | Sessions | Input | Output | Model(s) | Cost";
|
|
6649
|
+
const divider = "-------------|----------|-----------|-----------|------------------------------|--------";
|
|
6650
|
+
logger.info(header);
|
|
6651
|
+
logger.info(divider);
|
|
6652
|
+
for (const day of limited) {
|
|
6653
|
+
const date = day.date.padEnd(12);
|
|
6654
|
+
const sessions = String(day.sessionCount).padStart(8);
|
|
6655
|
+
const input = formatTokenCount(day.tokens.inputTokens).padStart(9);
|
|
6656
|
+
const output = formatTokenCount(day.tokens.outputTokens).padStart(9);
|
|
6657
|
+
const models = formatModels(day.models).padEnd(28);
|
|
6658
|
+
const cost = formatMicroUSD(day.costMicroUSD);
|
|
6659
|
+
logger.info(`${date} | ${sessions} | ${input} | ${output} | ${models} | ${cost}`);
|
|
6660
|
+
}
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
6663
|
+
function registerSessionsCommand(usage) {
|
|
6664
|
+
usage.command("sessions").description("List recent sessions with token usage and cost").option("--limit <n>", "Number of sessions to show (default: 10, max: 100)", "10").action(async (opts, cmd) => {
|
|
6665
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6666
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 10, 1), 100);
|
|
6667
|
+
const cwd = process.cwd();
|
|
6668
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6669
|
+
if (records.length === 0) {
|
|
6670
|
+
if (globalOpts.json) {
|
|
6671
|
+
console.log(JSON.stringify([]));
|
|
6672
|
+
} else {
|
|
6673
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6674
|
+
}
|
|
6675
|
+
return;
|
|
6676
|
+
}
|
|
6677
|
+
const { aggregateBySession } = await import("./dist-4LPXJYVZ.js");
|
|
6678
|
+
const sessionData = aggregateBySession(records);
|
|
6679
|
+
const limited = sessionData.slice(0, limit);
|
|
6680
|
+
if (globalOpts.json) {
|
|
6681
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
6682
|
+
return;
|
|
6683
|
+
}
|
|
6684
|
+
const header = "Session ID | Started | Duration | Tokens | Model | Cost";
|
|
6685
|
+
const divider = "---------------------|----------------------|-----------|-----------|----------------------|--------";
|
|
6686
|
+
logger.info(header);
|
|
6687
|
+
logger.info(divider);
|
|
6688
|
+
for (const s of limited) {
|
|
6689
|
+
const id = s.sessionId.slice(0, 20).padEnd(20);
|
|
6690
|
+
const started = s.firstTimestamp.slice(0, 19).padEnd(20);
|
|
6691
|
+
const durationMs = new Date(s.lastTimestamp).getTime() - new Date(s.firstTimestamp).getTime();
|
|
6692
|
+
const durationMin = Math.max(1, Math.round(durationMs / 6e4));
|
|
6693
|
+
const duration = `${durationMin}m`.padStart(9);
|
|
6694
|
+
const tokens = formatTokenCount(s.tokens.totalTokens).padStart(9);
|
|
6695
|
+
const model = (s.model ?? "unknown").slice(0, 20).padEnd(20);
|
|
6696
|
+
const cost = formatMicroUSD(s.costMicroUSD);
|
|
6697
|
+
logger.info(`${id} | ${started} | ${duration} | ${tokens} | ${model} | ${cost}`);
|
|
6698
|
+
}
|
|
6699
|
+
});
|
|
6700
|
+
}
|
|
6701
|
+
function registerSessionCommand(usage) {
|
|
6702
|
+
usage.command("session <id>").description("Show detailed token breakdown for a specific session").action(async (id, _opts, cmd) => {
|
|
6703
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6704
|
+
const cwd = process.cwd();
|
|
6705
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6706
|
+
const { aggregateBySession } = await import("./dist-4LPXJYVZ.js");
|
|
6707
|
+
const sessionData = aggregateBySession(records);
|
|
6708
|
+
const match = sessionData.find((s) => s.sessionId === id);
|
|
6709
|
+
if (!match) {
|
|
6710
|
+
const fuzzy = sessionData.filter(
|
|
6711
|
+
(s) => s.sessionId.includes(id) || id.includes(s.sessionId.slice(0, 8))
|
|
6712
|
+
);
|
|
6713
|
+
const errMsg = `Session "${id}" not found.`;
|
|
6714
|
+
if (fuzzy.length > 0) {
|
|
6715
|
+
const suggestions = fuzzy.slice(0, 3).map((s) => s.sessionId);
|
|
6716
|
+
if (globalOpts.json) {
|
|
6717
|
+
console.log(JSON.stringify({ error: errMsg, suggestions }));
|
|
6718
|
+
} else {
|
|
6719
|
+
logger.error(errMsg);
|
|
6720
|
+
logger.info("Did you mean:");
|
|
6721
|
+
for (const s of suggestions) {
|
|
6722
|
+
logger.info(` ${s}`);
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
} else {
|
|
6726
|
+
if (globalOpts.json) {
|
|
6727
|
+
console.log(JSON.stringify({ error: errMsg, suggestions: [] }));
|
|
6728
|
+
} else {
|
|
6729
|
+
logger.error(errMsg);
|
|
6730
|
+
}
|
|
6731
|
+
}
|
|
6732
|
+
process.exitCode = 1;
|
|
6733
|
+
return;
|
|
6734
|
+
}
|
|
6735
|
+
if (globalOpts.json) {
|
|
6736
|
+
console.log(JSON.stringify(match, null, 2));
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
logger.info(`Session: ${match.sessionId}`);
|
|
6740
|
+
logger.info(`Started: ${match.firstTimestamp}`);
|
|
6741
|
+
logger.info(`Ended: ${match.lastTimestamp}`);
|
|
6742
|
+
logger.info(`Model: ${match.model ?? "unknown"}`);
|
|
6743
|
+
logger.info(`Source: ${match.source}`);
|
|
6744
|
+
logger.info("");
|
|
6745
|
+
logger.info("Token Breakdown:");
|
|
6746
|
+
logger.info(` Input tokens: ${formatTokenCount(match.tokens.inputTokens)}`);
|
|
6747
|
+
logger.info(` Output tokens: ${formatTokenCount(match.tokens.outputTokens)}`);
|
|
6748
|
+
logger.info(` Total tokens: ${formatTokenCount(match.tokens.totalTokens)}`);
|
|
6749
|
+
if (match.cacheReadTokens != null) {
|
|
6750
|
+
logger.info(` Cache read tokens: ${formatTokenCount(match.cacheReadTokens)}`);
|
|
6751
|
+
}
|
|
6752
|
+
if (match.cacheCreationTokens != null) {
|
|
6753
|
+
logger.info(` Cache creation tokens: ${formatTokenCount(match.cacheCreationTokens)}`);
|
|
6754
|
+
}
|
|
6755
|
+
logger.info("");
|
|
6756
|
+
logger.info(`Cost: ${formatMicroUSD(match.costMicroUSD)}`);
|
|
6757
|
+
});
|
|
6758
|
+
}
|
|
6759
|
+
function registerLatestCommand(usage) {
|
|
6760
|
+
usage.command("latest").description("Show the most recently completed session cost summary").action(async (_opts, cmd) => {
|
|
6761
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6762
|
+
const cwd = process.cwd();
|
|
6763
|
+
const records = await loadAndPriceRecords(cwd, globalOpts.includeClaudeSessions);
|
|
6764
|
+
if (records.length === 0) {
|
|
6765
|
+
if (globalOpts.json) {
|
|
6766
|
+
console.log(JSON.stringify({ error: "No usage data found" }));
|
|
6767
|
+
} else {
|
|
6768
|
+
logger.info("No usage data found. Run some harness sessions first.");
|
|
6769
|
+
}
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6772
|
+
const { aggregateBySession } = await import("./dist-4LPXJYVZ.js");
|
|
6773
|
+
const sessionData = aggregateBySession(records);
|
|
6774
|
+
const latest = sessionData[0];
|
|
6775
|
+
if (!latest) {
|
|
6776
|
+
if (globalOpts.json) {
|
|
6777
|
+
console.log(JSON.stringify({ error: "No session data found" }));
|
|
6778
|
+
} else {
|
|
6779
|
+
logger.info("No session data found.");
|
|
6780
|
+
}
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
if (globalOpts.json) {
|
|
6784
|
+
console.log(JSON.stringify(latest, null, 2));
|
|
6785
|
+
return;
|
|
6786
|
+
}
|
|
6787
|
+
logger.info(`Session: ${latest.sessionId}`);
|
|
6788
|
+
logger.info(`Started: ${latest.firstTimestamp}`);
|
|
6789
|
+
logger.info(`Ended: ${latest.lastTimestamp}`);
|
|
6790
|
+
logger.info(`Model: ${latest.model ?? "unknown"}`);
|
|
6791
|
+
logger.info(
|
|
6792
|
+
`Tokens: ${formatTokenCount(latest.tokens.totalTokens)} (${formatTokenCount(latest.tokens.inputTokens)} in / ${formatTokenCount(latest.tokens.outputTokens)} out)`
|
|
6793
|
+
);
|
|
6794
|
+
logger.info(`Cost: ${formatMicroUSD(latest.costMicroUSD)}`);
|
|
6795
|
+
});
|
|
6796
|
+
}
|
|
6797
|
+
function createUsageCommand() {
|
|
6798
|
+
const usage = new Command67("usage").description("Token usage and cost tracking");
|
|
6799
|
+
usage.option(
|
|
6800
|
+
"--include-claude-sessions",
|
|
6801
|
+
"Include Claude Code session data from ~/.claude/projects/"
|
|
6802
|
+
);
|
|
6803
|
+
registerDailyCommand(usage);
|
|
6804
|
+
registerSessionsCommand(usage);
|
|
6805
|
+
registerSessionCommand(usage);
|
|
6806
|
+
registerLatestCommand(usage);
|
|
6807
|
+
return usage;
|
|
6808
|
+
}
|
|
6809
|
+
|
|
6810
|
+
// src/commands/taint.ts
|
|
6811
|
+
import { Command as Command68 } from "commander";
|
|
6812
|
+
function getProjectRoot() {
|
|
6813
|
+
return process.cwd();
|
|
6814
|
+
}
|
|
6815
|
+
function registerClearCommand(taint) {
|
|
6816
|
+
taint.command("clear [sessionId]").description(
|
|
6817
|
+
"Clear session taint \u2014 removes taint file(s) and re-enables destructive operations"
|
|
6818
|
+
).action((sessionId) => {
|
|
6819
|
+
const projectRoot = getProjectRoot();
|
|
6820
|
+
const count = clearTaint(projectRoot, sessionId);
|
|
6821
|
+
if (count === 0) {
|
|
6822
|
+
if (sessionId) {
|
|
6823
|
+
logger.info(`No taint found for session "${sessionId}".`);
|
|
6824
|
+
} else {
|
|
6825
|
+
logger.info("No active taint files found.");
|
|
6826
|
+
}
|
|
6827
|
+
} else if (sessionId) {
|
|
6828
|
+
logger.info(
|
|
6829
|
+
`Sentinel: taint cleared for session "${sessionId}". Destructive operations re-enabled.`
|
|
6830
|
+
);
|
|
6831
|
+
} else {
|
|
6832
|
+
logger.info(
|
|
6833
|
+
`Sentinel: cleared ${count} taint file${count === 1 ? "" : "s"}. Destructive operations re-enabled.`
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
});
|
|
6837
|
+
}
|
|
6838
|
+
function registerStatusCommand(taint) {
|
|
6839
|
+
taint.command("status [sessionId]").description("Show current taint status for a session or all sessions").action((sessionId) => {
|
|
6840
|
+
const projectRoot = getProjectRoot();
|
|
6841
|
+
if (sessionId) {
|
|
6842
|
+
const result = checkTaint(projectRoot, sessionId);
|
|
6843
|
+
if (result.expired) {
|
|
6844
|
+
logger.info(`Session "${sessionId}": taint expired (cleared).`);
|
|
6845
|
+
} else if (result.tainted && result.state) {
|
|
6846
|
+
const state = result.state;
|
|
6847
|
+
const expiresAt = new Date(state.expiresAt);
|
|
6848
|
+
const remaining = Math.max(0, Math.round((expiresAt.getTime() - Date.now()) / 1e3 / 60));
|
|
6849
|
+
logger.info(`Session "${sessionId}": TAINTED (${state.severity})`);
|
|
6850
|
+
logger.info(` Reason: ${state.reason}`);
|
|
6851
|
+
logger.info(` Expires in: ~${remaining} minute${remaining === 1 ? "" : "s"}`);
|
|
6852
|
+
logger.info(` Findings: ${state.findings.length}`);
|
|
6853
|
+
} else {
|
|
6854
|
+
logger.info(`Session "${sessionId}": clean (no taint).`);
|
|
6855
|
+
}
|
|
6856
|
+
} else {
|
|
6857
|
+
const sessions = listTaintedSessions(projectRoot);
|
|
6858
|
+
if (sessions.length === 0) {
|
|
6859
|
+
logger.info("No active taint sessions.");
|
|
6860
|
+
} else {
|
|
6861
|
+
logger.info(`Active taint sessions (${sessions.length}):`);
|
|
6862
|
+
for (const id of sessions) {
|
|
6863
|
+
const result = checkTaint(projectRoot, id);
|
|
6864
|
+
if (result.tainted && result.state) {
|
|
6865
|
+
const remaining = Math.max(
|
|
6866
|
+
0,
|
|
6867
|
+
Math.round((new Date(result.state.expiresAt).getTime() - Date.now()) / 1e3 / 60)
|
|
6868
|
+
);
|
|
6869
|
+
logger.info(` ${id}: ${result.state.severity} \u2014 expires in ~${remaining}m`);
|
|
6870
|
+
}
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
});
|
|
6875
|
+
}
|
|
6876
|
+
function createTaintCommand() {
|
|
6877
|
+
const taint = new Command68("taint").description("Manage sentinel session taint state");
|
|
6878
|
+
registerClearCommand(taint);
|
|
6879
|
+
registerStatusCommand(taint);
|
|
6880
|
+
return taint;
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
// src/commands/scan-config.ts
|
|
6884
|
+
import { Command as Command69 } from "commander";
|
|
6885
|
+
import { existsSync as existsSync30, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
|
|
6886
|
+
import { join as join44, relative as relative2 } from "path";
|
|
6887
|
+
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
6888
|
+
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
6889
|
+
const highLines = /* @__PURE__ */ new Set();
|
|
6890
|
+
for (const f of injectionFindings) {
|
|
6891
|
+
if (f.severity === "high" && f.line !== void 0) {
|
|
6892
|
+
highLines.add(f.line);
|
|
6893
|
+
}
|
|
6894
|
+
}
|
|
6895
|
+
if (highLines.size === 0) return { cleaned: content, linesStripped: 0 };
|
|
6896
|
+
const lines = content.split("\n");
|
|
6897
|
+
let linesStripped = 0;
|
|
6898
|
+
for (const lineNum of highLines) {
|
|
6899
|
+
const idx = lineNum - 1;
|
|
6900
|
+
if (idx >= 0 && idx < lines.length) {
|
|
6901
|
+
lines[idx] = "";
|
|
6902
|
+
linesStripped++;
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
return { cleaned: lines.join("\n"), linesStripped };
|
|
6906
|
+
}
|
|
6907
|
+
function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
6908
|
+
const hasHighSeverity = injectionFindings.some((f) => f.severity === "high");
|
|
6909
|
+
if (!hasHighSeverity) return;
|
|
6910
|
+
const { cleaned, linesStripped } = stripHighSeverityPatterns(content, injectionFindings);
|
|
6911
|
+
if (linesStripped > 0) {
|
|
6912
|
+
writeFileSync15(filePath, cleaned);
|
|
6913
|
+
logger.info(
|
|
6914
|
+
`scan-config --fix: stripped ${linesStripped} high-severity line(s) from ${relative2(targetDir, filePath).replaceAll("\\", "/")}`
|
|
6915
|
+
);
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
6919
|
+
if (!existsSync30(filePath)) return null;
|
|
6920
|
+
let content;
|
|
6921
|
+
try {
|
|
6922
|
+
content = readFileSync18(filePath, "utf8");
|
|
6923
|
+
} catch {
|
|
6924
|
+
return null;
|
|
6925
|
+
}
|
|
6926
|
+
const injectionFindings = scanForInjection(content);
|
|
6927
|
+
const findings = mapInjectionFindings(injectionFindings);
|
|
6928
|
+
const secFindings = scanner.scanContent(content, filePath);
|
|
6929
|
+
findings.push(...mapSecurityFindings(secFindings, findings));
|
|
6930
|
+
if (options.fix) {
|
|
6931
|
+
applyFix(filePath, targetDir, content, injectionFindings);
|
|
6932
|
+
}
|
|
6933
|
+
return {
|
|
6934
|
+
file: relative2(targetDir, filePath).replaceAll("\\", "/"),
|
|
6935
|
+
findings,
|
|
6936
|
+
overallSeverity: computeOverallSeverity(findings)
|
|
6937
|
+
};
|
|
6938
|
+
}
|
|
6939
|
+
async function runScanConfig(targetDir, options) {
|
|
6940
|
+
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
6941
|
+
const results = [];
|
|
6942
|
+
for (const configFile of CONFIG_FILES) {
|
|
6943
|
+
const result = scanSingleFile(join44(targetDir, configFile), targetDir, scanner, options);
|
|
6944
|
+
if (result) results.push(result);
|
|
6945
|
+
}
|
|
6946
|
+
return { exitCode: computeScanExitCode(results), results };
|
|
6947
|
+
}
|
|
6948
|
+
function formatTextOutput(result) {
|
|
6949
|
+
if (result.results.length === 0) {
|
|
6950
|
+
logger.info("scan-config: no config files found to scan.");
|
|
6951
|
+
return;
|
|
6952
|
+
}
|
|
6953
|
+
for (const fileResult of result.results) {
|
|
6954
|
+
if (fileResult.findings.length === 0) {
|
|
6955
|
+
logger.info(`${fileResult.file}: clean`);
|
|
6956
|
+
continue;
|
|
6957
|
+
}
|
|
6958
|
+
logger.info(
|
|
6959
|
+
`${fileResult.file}: ${fileResult.overallSeverity} (${fileResult.findings.length} finding(s))`
|
|
6960
|
+
);
|
|
6961
|
+
for (const f of fileResult.findings) {
|
|
6962
|
+
const lineInfo = f.line ? ` (line ${f.line})` : "";
|
|
6963
|
+
logger.info(` [${f.ruleId}] ${f.severity.toUpperCase()}: ${f.message}${lineInfo}`);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
if (result.exitCode === 2) {
|
|
6967
|
+
logger.error("scan-config: HIGH severity findings detected. Execution should be blocked.");
|
|
6968
|
+
} else if (result.exitCode === 1) {
|
|
6969
|
+
logger.warn("scan-config: MEDIUM severity findings detected. Session should be tainted.");
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
function createScanConfigCommand() {
|
|
6973
|
+
const command = new Command69("scan-config").description(
|
|
6974
|
+
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
6975
|
+
).option("--path <dir>", "Target directory to scan", process.cwd()).option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
6976
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
6977
|
+
const mode = globalOpts.json ? OutputMode.JSON : globalOpts.quiet ? OutputMode.QUIET : OutputMode.TEXT;
|
|
6978
|
+
const result = await runScanConfig(opts.path, { fix: opts.fix });
|
|
6979
|
+
if (mode === OutputMode.JSON) {
|
|
6980
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6981
|
+
} else if (mode !== OutputMode.QUIET) {
|
|
6982
|
+
formatTextOutput(result);
|
|
6983
|
+
}
|
|
6984
|
+
process.exit(result.exitCode);
|
|
6985
|
+
});
|
|
6986
|
+
return command;
|
|
6987
|
+
}
|
|
6988
|
+
|
|
5269
6989
|
// src/index.ts
|
|
5270
6990
|
function createProgram() {
|
|
5271
|
-
const program = new
|
|
6991
|
+
const program = new Command70();
|
|
5272
6992
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
5273
6993
|
program.addCommand(createValidateCommand());
|
|
5274
6994
|
program.addCommand(createCheckDepsCommand());
|
|
@@ -5289,10 +7009,13 @@ function createProgram() {
|
|
|
5289
7009
|
program.addCommand(createCheckPhaseGateCommand());
|
|
5290
7010
|
program.addCommand(createCreateSkillCommand());
|
|
5291
7011
|
program.addCommand(createSetupMcpCommand());
|
|
7012
|
+
program.addCommand(createSetupCommand());
|
|
7013
|
+
program.addCommand(createDoctorCommand());
|
|
5292
7014
|
program.addCommand(createGenerateSlashCommandsCommand());
|
|
5293
7015
|
program.addCommand(createGenerateAgentDefinitionsCommand());
|
|
5294
7016
|
program.addCommand(createGenerateCommand3());
|
|
5295
7017
|
program.addCommand(createCICommand());
|
|
7018
|
+
program.addCommand(createHooksCommand());
|
|
5296
7019
|
program.addCommand(createUpdateCommand());
|
|
5297
7020
|
program.addCommand(createScanCommand());
|
|
5298
7021
|
program.addCommand(createIngestCommand());
|
|
@@ -5308,11 +7031,16 @@ function createProgram() {
|
|
|
5308
7031
|
program.addCommand(createUninstallConstraintsCommand());
|
|
5309
7032
|
program.addCommand(createUninstallCommand());
|
|
5310
7033
|
program.addCommand(createOrchestratorCommand());
|
|
7034
|
+
program.addCommand(createIntegrationsCommand());
|
|
7035
|
+
program.addCommand(createUsageCommand());
|
|
7036
|
+
program.addCommand(createTaintCommand());
|
|
7037
|
+
program.addCommand(createScanConfigCommand());
|
|
5311
7038
|
return program;
|
|
5312
7039
|
}
|
|
5313
7040
|
|
|
5314
7041
|
export {
|
|
5315
7042
|
buildPreamble,
|
|
7043
|
+
printFirstRunWelcome,
|
|
5316
7044
|
runScan,
|
|
5317
7045
|
runIngest,
|
|
5318
7046
|
runQuery,
|