@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
|
@@ -135,17 +135,17 @@ function resolveFileToLayer(file, layers) {
|
|
|
135
135
|
}
|
|
136
136
|
var accessAsync = promisify(access);
|
|
137
137
|
var readFileAsync = promisify(readFile);
|
|
138
|
-
async function fileExists(
|
|
138
|
+
async function fileExists(path26) {
|
|
139
139
|
try {
|
|
140
|
-
await accessAsync(
|
|
140
|
+
await accessAsync(path26, constants.F_OK);
|
|
141
141
|
return true;
|
|
142
142
|
} catch {
|
|
143
143
|
return false;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
async function readFileContent(
|
|
146
|
+
async function readFileContent(path26) {
|
|
147
147
|
try {
|
|
148
|
-
const content = await readFileAsync(
|
|
148
|
+
const content = await readFileAsync(path26, "utf-8");
|
|
149
149
|
return Ok(content);
|
|
150
150
|
} catch (error) {
|
|
151
151
|
return Err(error);
|
|
@@ -1832,6 +1832,7 @@ import * as fs6 from "fs";
|
|
|
1832
1832
|
import * as path3 from "path";
|
|
1833
1833
|
import * as fs9 from "fs";
|
|
1834
1834
|
import * as path6 from "path";
|
|
1835
|
+
import * as crypto from "crypto";
|
|
1835
1836
|
import * as fs10 from "fs";
|
|
1836
1837
|
import * as path7 from "path";
|
|
1837
1838
|
import * as fs11 from "fs";
|
|
@@ -1845,26 +1846,40 @@ import * as fs14 from "fs";
|
|
|
1845
1846
|
import * as path11 from "path";
|
|
1846
1847
|
import * as fs15 from "fs";
|
|
1847
1848
|
import * as path12 from "path";
|
|
1848
|
-
import * as fs17 from "fs/promises";
|
|
1849
|
-
import { z as z5 } from "zod";
|
|
1850
1849
|
import * as fs16 from "fs";
|
|
1851
1850
|
import * as path13 from "path";
|
|
1851
|
+
import { z as z5 } from "zod";
|
|
1852
|
+
import * as fs18 from "fs/promises";
|
|
1853
|
+
import { minimatch as minimatch4 } from "minimatch";
|
|
1854
|
+
import { z as z6 } from "zod";
|
|
1855
|
+
import * as fs17 from "fs";
|
|
1852
1856
|
import * as path14 from "path";
|
|
1857
|
+
import { readFileSync as readFileSync142, writeFileSync as writeFileSync11, unlinkSync, mkdirSync as mkdirSync11, readdirSync as readdirSync3 } from "fs";
|
|
1858
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
1853
1859
|
import * as path15 from "path";
|
|
1854
1860
|
import * as path16 from "path";
|
|
1855
1861
|
import * as path17 from "path";
|
|
1856
|
-
import * as fs18 from "fs";
|
|
1857
1862
|
import * as path18 from "path";
|
|
1858
|
-
import
|
|
1859
|
-
import * as fs19 from "fs/promises";
|
|
1863
|
+
import * as fs19 from "fs";
|
|
1860
1864
|
import * as path19 from "path";
|
|
1865
|
+
import { z as z7 } from "zod";
|
|
1861
1866
|
import * as fs20 from "fs/promises";
|
|
1862
1867
|
import * as path20 from "path";
|
|
1863
|
-
import * as
|
|
1864
|
-
import * as fs21 from "fs";
|
|
1868
|
+
import * as fs21 from "fs/promises";
|
|
1865
1869
|
import * as path21 from "path";
|
|
1870
|
+
import * as ejs from "ejs";
|
|
1871
|
+
import * as fs22 from "fs";
|
|
1872
|
+
import * as path22 from "path";
|
|
1866
1873
|
import * as os from "os";
|
|
1867
1874
|
import { spawn } from "child_process";
|
|
1875
|
+
import Parser from "web-tree-sitter";
|
|
1876
|
+
import * as fs23 from "fs/promises";
|
|
1877
|
+
import * as path23 from "path";
|
|
1878
|
+
import * as fs24 from "fs";
|
|
1879
|
+
import * as path24 from "path";
|
|
1880
|
+
import * as fs25 from "fs";
|
|
1881
|
+
import * as path25 from "path";
|
|
1882
|
+
import * as os2 from "os";
|
|
1868
1883
|
async function validateFileStructure(projectPath, conventions) {
|
|
1869
1884
|
const missing = [];
|
|
1870
1885
|
const unexpected = [];
|
|
@@ -1900,15 +1915,15 @@ function validateConfig(data, schema) {
|
|
|
1900
1915
|
let message = "Configuration validation failed";
|
|
1901
1916
|
const suggestions = [];
|
|
1902
1917
|
if (firstError) {
|
|
1903
|
-
const
|
|
1904
|
-
const pathDisplay =
|
|
1918
|
+
const path26 = firstError.path.join(".");
|
|
1919
|
+
const pathDisplay = path26 ? ` at "${path26}"` : "";
|
|
1905
1920
|
if (firstError.code === "invalid_type") {
|
|
1906
1921
|
const received = firstError.received;
|
|
1907
1922
|
const expected = firstError.expected;
|
|
1908
1923
|
if (received === "undefined") {
|
|
1909
1924
|
code = "MISSING_FIELD";
|
|
1910
1925
|
message = `Missing required field${pathDisplay}: ${firstError.message}`;
|
|
1911
|
-
suggestions.push(`Field "${
|
|
1926
|
+
suggestions.push(`Field "${path26}" is required and must be of type "${expected}"`);
|
|
1912
1927
|
} else {
|
|
1913
1928
|
code = "INVALID_TYPE";
|
|
1914
1929
|
message = `Invalid type${pathDisplay}: ${firstError.message}`;
|
|
@@ -2117,27 +2132,27 @@ function extractSections(content) {
|
|
|
2117
2132
|
}
|
|
2118
2133
|
return sections.map((section) => buildAgentMapSection(section, lines));
|
|
2119
2134
|
}
|
|
2120
|
-
function isExternalLink(
|
|
2121
|
-
return
|
|
2135
|
+
function isExternalLink(path26) {
|
|
2136
|
+
return path26.startsWith("http://") || path26.startsWith("https://") || path26.startsWith("#") || path26.startsWith("mailto:");
|
|
2122
2137
|
}
|
|
2123
2138
|
function resolveLinkPath(linkPath, baseDir) {
|
|
2124
2139
|
return linkPath.startsWith(".") ? join4(baseDir, linkPath) : linkPath;
|
|
2125
2140
|
}
|
|
2126
|
-
async function validateAgentsMap(
|
|
2127
|
-
const contentResult = await readFileContent(
|
|
2141
|
+
async function validateAgentsMap(path26 = "./AGENTS.md") {
|
|
2142
|
+
const contentResult = await readFileContent(path26);
|
|
2128
2143
|
if (!contentResult.ok) {
|
|
2129
2144
|
return Err(
|
|
2130
2145
|
createError(
|
|
2131
2146
|
"PARSE_ERROR",
|
|
2132
2147
|
`Failed to read AGENTS.md: ${contentResult.error.message}`,
|
|
2133
|
-
{ path:
|
|
2148
|
+
{ path: path26 },
|
|
2134
2149
|
["Ensure the file exists", "Check file permissions"]
|
|
2135
2150
|
)
|
|
2136
2151
|
);
|
|
2137
2152
|
}
|
|
2138
2153
|
const content = contentResult.value;
|
|
2139
2154
|
const sections = extractSections(content);
|
|
2140
|
-
const baseDir = dirname4(
|
|
2155
|
+
const baseDir = dirname4(path26);
|
|
2141
2156
|
const sectionTitles = sections.map((s) => s.title);
|
|
2142
2157
|
const missingSections = REQUIRED_SECTIONS.filter(
|
|
2143
2158
|
(required) => !sectionTitles.some((title) => title.toLowerCase().includes(required.toLowerCase()))
|
|
@@ -2271,8 +2286,8 @@ async function checkDocCoverage(domain, options = {}) {
|
|
|
2271
2286
|
);
|
|
2272
2287
|
}
|
|
2273
2288
|
}
|
|
2274
|
-
function suggestFix(
|
|
2275
|
-
const targetName = basename2(
|
|
2289
|
+
function suggestFix(path26, existingFiles) {
|
|
2290
|
+
const targetName = basename2(path26).toLowerCase();
|
|
2276
2291
|
const similar = existingFiles.find((file) => {
|
|
2277
2292
|
const fileName = basename2(file).toLowerCase();
|
|
2278
2293
|
return fileName.includes(targetName) || targetName.includes(fileName);
|
|
@@ -2280,7 +2295,7 @@ function suggestFix(path22, existingFiles) {
|
|
|
2280
2295
|
if (similar) {
|
|
2281
2296
|
return `Did you mean "${similar}"?`;
|
|
2282
2297
|
}
|
|
2283
|
-
return `Create the file "${
|
|
2298
|
+
return `Create the file "${path26}" or remove the link`;
|
|
2284
2299
|
}
|
|
2285
2300
|
async function validateKnowledgeMap(rootDir = process.cwd()) {
|
|
2286
2301
|
const agentsPath = join22(rootDir, "AGENTS.md");
|
|
@@ -2623,8 +2638,8 @@ function createBoundaryValidator(schema, name) {
|
|
|
2623
2638
|
return Ok(result.data);
|
|
2624
2639
|
}
|
|
2625
2640
|
const suggestions = result.error.issues.map((issue) => {
|
|
2626
|
-
const
|
|
2627
|
-
return
|
|
2641
|
+
const path26 = issue.path.join(".");
|
|
2642
|
+
return path26 ? `${path26}: ${issue.message}` : issue.message;
|
|
2628
2643
|
});
|
|
2629
2644
|
return Err(
|
|
2630
2645
|
createError(
|
|
@@ -3232,11 +3247,11 @@ function processExportListSpecifiers(exportDecl, exports) {
|
|
|
3232
3247
|
var TypeScriptParser = class {
|
|
3233
3248
|
name = "typescript";
|
|
3234
3249
|
extensions = [".ts", ".tsx", ".mts", ".cts"];
|
|
3235
|
-
async parseFile(
|
|
3236
|
-
const contentResult = await readFileContent(
|
|
3250
|
+
async parseFile(path26) {
|
|
3251
|
+
const contentResult = await readFileContent(path26);
|
|
3237
3252
|
if (!contentResult.ok) {
|
|
3238
3253
|
return Err(
|
|
3239
|
-
createParseError("NOT_FOUND", `File not found: ${
|
|
3254
|
+
createParseError("NOT_FOUND", `File not found: ${path26}`, { path: path26 }, [
|
|
3240
3255
|
"Check that the file exists",
|
|
3241
3256
|
"Verify the path is correct"
|
|
3242
3257
|
])
|
|
@@ -3246,7 +3261,7 @@ var TypeScriptParser = class {
|
|
|
3246
3261
|
const ast = parse(contentResult.value, {
|
|
3247
3262
|
loc: true,
|
|
3248
3263
|
range: true,
|
|
3249
|
-
jsx:
|
|
3264
|
+
jsx: path26.endsWith(".tsx"),
|
|
3250
3265
|
errorOnUnknownASTType: false
|
|
3251
3266
|
});
|
|
3252
3267
|
return Ok({
|
|
@@ -3257,7 +3272,7 @@ var TypeScriptParser = class {
|
|
|
3257
3272
|
} catch (e) {
|
|
3258
3273
|
const error = e;
|
|
3259
3274
|
return Err(
|
|
3260
|
-
createParseError("SYNTAX_ERROR", `Failed to parse ${
|
|
3275
|
+
createParseError("SYNTAX_ERROR", `Failed to parse ${path26}: ${error.message}`, { path: path26 }, [
|
|
3261
3276
|
"Check for syntax errors in the file",
|
|
3262
3277
|
"Ensure valid TypeScript syntax"
|
|
3263
3278
|
])
|
|
@@ -3438,22 +3453,22 @@ function extractInlineRefs(content) {
|
|
|
3438
3453
|
}
|
|
3439
3454
|
return refs;
|
|
3440
3455
|
}
|
|
3441
|
-
async function parseDocumentationFile(
|
|
3442
|
-
const contentResult = await readFileContent(
|
|
3456
|
+
async function parseDocumentationFile(path26) {
|
|
3457
|
+
const contentResult = await readFileContent(path26);
|
|
3443
3458
|
if (!contentResult.ok) {
|
|
3444
3459
|
return Err(
|
|
3445
3460
|
createEntropyError(
|
|
3446
3461
|
"PARSE_ERROR",
|
|
3447
|
-
`Failed to read documentation file: ${
|
|
3448
|
-
{ file:
|
|
3462
|
+
`Failed to read documentation file: ${path26}`,
|
|
3463
|
+
{ file: path26 },
|
|
3449
3464
|
["Check that the file exists"]
|
|
3450
3465
|
)
|
|
3451
3466
|
);
|
|
3452
3467
|
}
|
|
3453
3468
|
const content = contentResult.value;
|
|
3454
|
-
const type =
|
|
3469
|
+
const type = path26.endsWith(".md") ? "markdown" : "text";
|
|
3455
3470
|
return Ok({
|
|
3456
|
-
path:
|
|
3471
|
+
path: path26,
|
|
3457
3472
|
type,
|
|
3458
3473
|
content,
|
|
3459
3474
|
codeBlocks: extractCodeBlocks(content),
|
|
@@ -4711,7 +4726,7 @@ var EntropyAnalyzer = class {
|
|
|
4711
4726
|
};
|
|
4712
4727
|
var readFile32 = promisify2(fs3.readFile);
|
|
4713
4728
|
var writeFile32 = promisify2(fs3.writeFile);
|
|
4714
|
-
var
|
|
4729
|
+
var unlink22 = promisify2(fs3.unlink);
|
|
4715
4730
|
var mkdir22 = promisify2(fs3.mkdir);
|
|
4716
4731
|
var copyFile2 = promisify2(fs3.copyFile);
|
|
4717
4732
|
var DEFAULT_FIX_CONFIG = {
|
|
@@ -4854,7 +4869,7 @@ async function applySingleFix(fix, config) {
|
|
|
4854
4869
|
return Err({ fix, error: backupResult.error.message });
|
|
4855
4870
|
}
|
|
4856
4871
|
}
|
|
4857
|
-
await
|
|
4872
|
+
await unlink22(fix.file);
|
|
4858
4873
|
break;
|
|
4859
4874
|
case "delete-lines":
|
|
4860
4875
|
if (fix.line !== void 0) {
|
|
@@ -6490,6 +6505,8 @@ var SESSION_INDEX_FILE = "index.md";
|
|
|
6490
6505
|
var SUMMARY_FILE = "summary.md";
|
|
6491
6506
|
var SESSION_STATE_FILE = "session-state.json";
|
|
6492
6507
|
var ARCHIVE_DIR = "archive";
|
|
6508
|
+
var CONTENT_HASHES_FILE = "content-hashes.json";
|
|
6509
|
+
var EVENTS_FILE = "events.jsonl";
|
|
6493
6510
|
var STREAMS_DIR = "streams";
|
|
6494
6511
|
var STREAM_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
6495
6512
|
function streamsDir(projectPath) {
|
|
@@ -6818,6 +6835,84 @@ async function saveState(projectPath, state, stream, session) {
|
|
|
6818
6835
|
);
|
|
6819
6836
|
}
|
|
6820
6837
|
}
|
|
6838
|
+
function parseFrontmatter(line) {
|
|
6839
|
+
const match = line.match(/^<!--\s+hash:([a-f0-9]+)(?:\s+tags:([^\s]+))?\s+-->/);
|
|
6840
|
+
if (!match) return null;
|
|
6841
|
+
const hash = match[1];
|
|
6842
|
+
const tags = match[2] ? match[2].split(",").filter(Boolean) : [];
|
|
6843
|
+
return { hash, tags };
|
|
6844
|
+
}
|
|
6845
|
+
function computeEntryHash(text) {
|
|
6846
|
+
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 8);
|
|
6847
|
+
}
|
|
6848
|
+
function normalizeLearningContent(text) {
|
|
6849
|
+
let normalized = text;
|
|
6850
|
+
normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, "");
|
|
6851
|
+
normalized = normalized.replace(/\[skill:[^\]]*\]/g, "");
|
|
6852
|
+
normalized = normalized.replace(/\[outcome:[^\]]*\]/g, "");
|
|
6853
|
+
normalized = normalized.replace(/^[\s]*[-*]\s+/gm, "");
|
|
6854
|
+
normalized = normalized.replace(/\*\*/g, "");
|
|
6855
|
+
normalized = normalized.replace(/:\s*/g, " ");
|
|
6856
|
+
normalized = normalized.toLowerCase();
|
|
6857
|
+
normalized = normalized.replace(/\s+/g, " ").trim();
|
|
6858
|
+
return normalized;
|
|
6859
|
+
}
|
|
6860
|
+
function computeContentHash(text) {
|
|
6861
|
+
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
6862
|
+
}
|
|
6863
|
+
function loadContentHashes(stateDir) {
|
|
6864
|
+
const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
|
|
6865
|
+
if (!fs9.existsSync(hashesPath)) return {};
|
|
6866
|
+
try {
|
|
6867
|
+
const raw = fs9.readFileSync(hashesPath, "utf-8");
|
|
6868
|
+
const parsed = JSON.parse(raw);
|
|
6869
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
6870
|
+
return parsed;
|
|
6871
|
+
} catch {
|
|
6872
|
+
return {};
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
function saveContentHashes(stateDir, index) {
|
|
6876
|
+
const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
|
|
6877
|
+
fs9.writeFileSync(hashesPath, JSON.stringify(index, null, 2) + "\n");
|
|
6878
|
+
}
|
|
6879
|
+
function rebuildContentHashes(stateDir) {
|
|
6880
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
6881
|
+
if (!fs9.existsSync(learningsPath)) return {};
|
|
6882
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
6883
|
+
const lines = content.split("\n");
|
|
6884
|
+
const index = {};
|
|
6885
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6886
|
+
const line = lines[i];
|
|
6887
|
+
const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
|
|
6888
|
+
if (isDatedBullet) {
|
|
6889
|
+
const learningMatch = line.match(/:\*\*\s*(.+)$/);
|
|
6890
|
+
if (learningMatch?.[1]) {
|
|
6891
|
+
const normalized = normalizeLearningContent(learningMatch[1]);
|
|
6892
|
+
const hash = computeContentHash(normalized);
|
|
6893
|
+
const dateMatch = line.match(/(\d{4}-\d{2}-\d{2})/);
|
|
6894
|
+
index[hash] = { date: dateMatch?.[1] ?? "", line: i + 1 };
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
saveContentHashes(stateDir, index);
|
|
6899
|
+
return index;
|
|
6900
|
+
}
|
|
6901
|
+
function extractIndexEntry(entry) {
|
|
6902
|
+
const lines = entry.split("\n");
|
|
6903
|
+
const summary = lines[0] ?? entry;
|
|
6904
|
+
const tags = [];
|
|
6905
|
+
const skillMatch = entry.match(/\[skill:([^\]]+)\]/);
|
|
6906
|
+
if (skillMatch?.[1]) tags.push(skillMatch[1]);
|
|
6907
|
+
const outcomeMatch = entry.match(/\[outcome:([^\]]+)\]/);
|
|
6908
|
+
if (outcomeMatch?.[1]) tags.push(outcomeMatch[1]);
|
|
6909
|
+
return {
|
|
6910
|
+
hash: computeEntryHash(entry),
|
|
6911
|
+
tags,
|
|
6912
|
+
summary,
|
|
6913
|
+
fullText: entry
|
|
6914
|
+
};
|
|
6915
|
+
}
|
|
6821
6916
|
var learningsCacheMap = /* @__PURE__ */ new Map();
|
|
6822
6917
|
function clearLearningsCache() {
|
|
6823
6918
|
learningsCacheMap.clear();
|
|
@@ -6829,27 +6924,55 @@ async function appendLearning(projectPath, learning, skillName, outcome, stream,
|
|
|
6829
6924
|
const stateDir = dirResult.value;
|
|
6830
6925
|
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
6831
6926
|
fs9.mkdirSync(stateDir, { recursive: true });
|
|
6927
|
+
const normalizedContent = normalizeLearningContent(learning);
|
|
6928
|
+
const contentHash = computeContentHash(normalizedContent);
|
|
6929
|
+
const hashesPath = path6.join(stateDir, CONTENT_HASHES_FILE);
|
|
6930
|
+
let contentHashes;
|
|
6931
|
+
if (fs9.existsSync(hashesPath)) {
|
|
6932
|
+
contentHashes = loadContentHashes(stateDir);
|
|
6933
|
+
if (Object.keys(contentHashes).length === 0 && fs9.existsSync(learningsPath)) {
|
|
6934
|
+
contentHashes = rebuildContentHashes(stateDir);
|
|
6935
|
+
}
|
|
6936
|
+
} else if (fs9.existsSync(learningsPath)) {
|
|
6937
|
+
contentHashes = rebuildContentHashes(stateDir);
|
|
6938
|
+
} else {
|
|
6939
|
+
contentHashes = {};
|
|
6940
|
+
}
|
|
6941
|
+
if (contentHashes[contentHash]) {
|
|
6942
|
+
return Ok(void 0);
|
|
6943
|
+
}
|
|
6832
6944
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6833
|
-
|
|
6945
|
+
const fmTags = [];
|
|
6946
|
+
if (skillName) fmTags.push(skillName);
|
|
6947
|
+
if (outcome) fmTags.push(outcome);
|
|
6948
|
+
let bulletLine;
|
|
6834
6949
|
if (skillName && outcome) {
|
|
6835
|
-
|
|
6836
|
-
- **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}
|
|
6837
|
-
`;
|
|
6950
|
+
bulletLine = `- **${timestamp} [skill:${skillName}] [outcome:${outcome}]:** ${learning}`;
|
|
6838
6951
|
} else if (skillName) {
|
|
6839
|
-
|
|
6840
|
-
- **${timestamp} [skill:${skillName}]:** ${learning}
|
|
6841
|
-
`;
|
|
6952
|
+
bulletLine = `- **${timestamp} [skill:${skillName}]:** ${learning}`;
|
|
6842
6953
|
} else {
|
|
6843
|
-
|
|
6844
|
-
- **${timestamp}:** ${learning}
|
|
6845
|
-
`;
|
|
6954
|
+
bulletLine = `- **${timestamp}:** ${learning}`;
|
|
6846
6955
|
}
|
|
6956
|
+
const hash = crypto.createHash("sha256").update(bulletLine).digest("hex").slice(0, 8);
|
|
6957
|
+
const tagsStr = fmTags.length > 0 ? ` tags:${fmTags.join(",")}` : "";
|
|
6958
|
+
const frontmatter = `<!-- hash:${hash}${tagsStr} -->`;
|
|
6959
|
+
const entry = `
|
|
6960
|
+
${frontmatter}
|
|
6961
|
+
${bulletLine}
|
|
6962
|
+
`;
|
|
6963
|
+
let existingLineCount;
|
|
6847
6964
|
if (!fs9.existsSync(learningsPath)) {
|
|
6848
6965
|
fs9.writeFileSync(learningsPath, `# Learnings
|
|
6849
6966
|
${entry}`);
|
|
6967
|
+
existingLineCount = 1;
|
|
6850
6968
|
} else {
|
|
6969
|
+
const existingContent = fs9.readFileSync(learningsPath, "utf-8");
|
|
6970
|
+
existingLineCount = existingContent.split("\n").length;
|
|
6851
6971
|
fs9.appendFileSync(learningsPath, entry);
|
|
6852
6972
|
}
|
|
6973
|
+
const bulletLine_lineNum = existingLineCount + 2;
|
|
6974
|
+
contentHashes[contentHash] = { date: timestamp ?? "", line: bulletLine_lineNum };
|
|
6975
|
+
saveContentHashes(stateDir, contentHashes);
|
|
6853
6976
|
learningsCacheMap.delete(learningsPath);
|
|
6854
6977
|
return Ok(void 0);
|
|
6855
6978
|
} catch (error) {
|
|
@@ -6897,7 +7020,30 @@ function analyzeLearningPatterns(entries) {
|
|
|
6897
7020
|
return patterns.sort((a, b) => b.count - a.count);
|
|
6898
7021
|
}
|
|
6899
7022
|
async function loadBudgetedLearnings(projectPath, options) {
|
|
6900
|
-
const { intent, tokenBudget = 1e3, skill, session, stream } = options;
|
|
7023
|
+
const { intent, tokenBudget = 1e3, skill, session, stream, depth = "summary" } = options;
|
|
7024
|
+
if (depth === "index") {
|
|
7025
|
+
const indexEntries = [];
|
|
7026
|
+
if (session) {
|
|
7027
|
+
const sessionResult = await loadIndexEntries(projectPath, skill, stream, session);
|
|
7028
|
+
if (sessionResult.ok) indexEntries.push(...sessionResult.value);
|
|
7029
|
+
}
|
|
7030
|
+
const globalResult2 = await loadIndexEntries(projectPath, skill, stream);
|
|
7031
|
+
if (globalResult2.ok) {
|
|
7032
|
+
const sessionHashes = new Set(indexEntries.map((e) => e.hash));
|
|
7033
|
+
const uniqueGlobal = globalResult2.value.filter((e) => !sessionHashes.has(e.hash));
|
|
7034
|
+
indexEntries.push(...uniqueGlobal);
|
|
7035
|
+
}
|
|
7036
|
+
const budgeted2 = [];
|
|
7037
|
+
let totalTokens2 = 0;
|
|
7038
|
+
for (const entry of indexEntries) {
|
|
7039
|
+
const separator = budgeted2.length > 0 ? "\n" : "";
|
|
7040
|
+
const entryCost = estimateTokens(entry.summary + separator);
|
|
7041
|
+
if (totalTokens2 + entryCost > tokenBudget) break;
|
|
7042
|
+
budgeted2.push(entry.summary);
|
|
7043
|
+
totalTokens2 += entryCost;
|
|
7044
|
+
}
|
|
7045
|
+
return Ok(budgeted2);
|
|
7046
|
+
}
|
|
6901
7047
|
const sortByRecencyAndRelevance = (entries) => {
|
|
6902
7048
|
return [...entries].sort((a, b) => {
|
|
6903
7049
|
const dateA = parseDateFromEntry(a) ?? "0000-00-00";
|
|
@@ -6916,7 +7062,9 @@ async function loadBudgetedLearnings(projectPath, options) {
|
|
|
6916
7062
|
}
|
|
6917
7063
|
const globalResult = await loadRelevantLearnings(projectPath, skill, stream);
|
|
6918
7064
|
if (globalResult.ok) {
|
|
6919
|
-
allEntries.
|
|
7065
|
+
const sessionSet = new Set(allEntries.map((e) => e.trim()));
|
|
7066
|
+
const uniqueGlobal = globalResult.value.filter((e) => !sessionSet.has(e.trim()));
|
|
7067
|
+
allEntries.push(...sortByRecencyAndRelevance(uniqueGlobal));
|
|
6920
7068
|
}
|
|
6921
7069
|
const budgeted = [];
|
|
6922
7070
|
let totalTokens = 0;
|
|
@@ -6929,6 +7077,68 @@ async function loadBudgetedLearnings(projectPath, options) {
|
|
|
6929
7077
|
}
|
|
6930
7078
|
return Ok(budgeted);
|
|
6931
7079
|
}
|
|
7080
|
+
async function loadIndexEntries(projectPath, skillName, stream, session) {
|
|
7081
|
+
try {
|
|
7082
|
+
const dirResult = await getStateDir(projectPath, stream, session);
|
|
7083
|
+
if (!dirResult.ok) return dirResult;
|
|
7084
|
+
const stateDir = dirResult.value;
|
|
7085
|
+
const learningsPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7086
|
+
if (!fs9.existsSync(learningsPath)) {
|
|
7087
|
+
return Ok([]);
|
|
7088
|
+
}
|
|
7089
|
+
const content = fs9.readFileSync(learningsPath, "utf-8");
|
|
7090
|
+
const lines = content.split("\n");
|
|
7091
|
+
const indexEntries = [];
|
|
7092
|
+
let pendingFrontmatter = null;
|
|
7093
|
+
let currentBlock = [];
|
|
7094
|
+
for (const line of lines) {
|
|
7095
|
+
if (line.startsWith("# ")) continue;
|
|
7096
|
+
const fm = parseFrontmatter(line);
|
|
7097
|
+
if (fm) {
|
|
7098
|
+
pendingFrontmatter = fm;
|
|
7099
|
+
continue;
|
|
7100
|
+
}
|
|
7101
|
+
const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
|
|
7102
|
+
const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
|
|
7103
|
+
if (isDatedBullet || isHeading) {
|
|
7104
|
+
if (pendingFrontmatter) {
|
|
7105
|
+
indexEntries.push({
|
|
7106
|
+
hash: pendingFrontmatter.hash,
|
|
7107
|
+
tags: pendingFrontmatter.tags,
|
|
7108
|
+
summary: line,
|
|
7109
|
+
fullText: ""
|
|
7110
|
+
// Placeholder — full text not loaded in index mode
|
|
7111
|
+
});
|
|
7112
|
+
pendingFrontmatter = null;
|
|
7113
|
+
} else {
|
|
7114
|
+
const idx = extractIndexEntry(line);
|
|
7115
|
+
indexEntries.push({
|
|
7116
|
+
hash: idx.hash,
|
|
7117
|
+
tags: idx.tags,
|
|
7118
|
+
summary: line,
|
|
7119
|
+
fullText: ""
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
currentBlock = [line];
|
|
7123
|
+
} else if (line.trim() !== "" && currentBlock.length > 0) {
|
|
7124
|
+
currentBlock.push(line);
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
if (skillName) {
|
|
7128
|
+
const filtered = indexEntries.filter(
|
|
7129
|
+
(e) => e.tags.includes(skillName) || e.summary.includes(`[skill:${skillName}]`)
|
|
7130
|
+
);
|
|
7131
|
+
return Ok(filtered);
|
|
7132
|
+
}
|
|
7133
|
+
return Ok(indexEntries);
|
|
7134
|
+
} catch (error) {
|
|
7135
|
+
return Err(
|
|
7136
|
+
new Error(
|
|
7137
|
+
`Failed to load index entries: ${error instanceof Error ? error.message : String(error)}`
|
|
7138
|
+
)
|
|
7139
|
+
);
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
6932
7142
|
async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
6933
7143
|
try {
|
|
6934
7144
|
const dirResult = await getStateDir(projectPath, stream, session);
|
|
@@ -6951,6 +7161,7 @@ async function loadRelevantLearnings(projectPath, skillName, stream, session) {
|
|
|
6951
7161
|
let currentBlock = [];
|
|
6952
7162
|
for (const line of lines) {
|
|
6953
7163
|
if (line.startsWith("# ")) continue;
|
|
7164
|
+
if (/^<!--\s+hash:[a-f0-9]+/.test(line)) continue;
|
|
6954
7165
|
const isDatedBullet = /^- \*\*\d{4}-\d{2}-\d{2}/.test(line);
|
|
6955
7166
|
const isHeading = /^## \d{4}-\d{2}-\d{2}/.test(line);
|
|
6956
7167
|
if (isDatedBullet || isHeading) {
|
|
@@ -7060,6 +7271,68 @@ async function pruneLearnings(projectPath, stream) {
|
|
|
7060
7271
|
);
|
|
7061
7272
|
}
|
|
7062
7273
|
}
|
|
7274
|
+
var PROMOTABLE_OUTCOMES = ["gotcha", "decision", "observation"];
|
|
7275
|
+
function isGeneralizable(entry) {
|
|
7276
|
+
for (const outcome of PROMOTABLE_OUTCOMES) {
|
|
7277
|
+
if (entry.includes(`[outcome:${outcome}]`)) return true;
|
|
7278
|
+
}
|
|
7279
|
+
return false;
|
|
7280
|
+
}
|
|
7281
|
+
async function promoteSessionLearnings(projectPath, sessionSlug, stream) {
|
|
7282
|
+
try {
|
|
7283
|
+
const sessionResult = await loadRelevantLearnings(projectPath, void 0, stream, sessionSlug);
|
|
7284
|
+
if (!sessionResult.ok) return sessionResult;
|
|
7285
|
+
const sessionEntries = sessionResult.value;
|
|
7286
|
+
if (sessionEntries.length === 0) {
|
|
7287
|
+
return Ok({ promoted: 0, skipped: 0 });
|
|
7288
|
+
}
|
|
7289
|
+
const toPromote = [];
|
|
7290
|
+
let skipped = 0;
|
|
7291
|
+
for (const entry of sessionEntries) {
|
|
7292
|
+
if (isGeneralizable(entry)) {
|
|
7293
|
+
toPromote.push(entry);
|
|
7294
|
+
} else {
|
|
7295
|
+
skipped++;
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
if (toPromote.length === 0) {
|
|
7299
|
+
return Ok({ promoted: 0, skipped });
|
|
7300
|
+
}
|
|
7301
|
+
const dirResult = await getStateDir(projectPath, stream);
|
|
7302
|
+
if (!dirResult.ok) return dirResult;
|
|
7303
|
+
const stateDir = dirResult.value;
|
|
7304
|
+
const globalPath = path6.join(stateDir, LEARNINGS_FILE);
|
|
7305
|
+
const existingGlobal = fs9.existsSync(globalPath) ? fs9.readFileSync(globalPath, "utf-8") : "";
|
|
7306
|
+
const newEntries = toPromote.filter((entry) => !existingGlobal.includes(entry.trim()));
|
|
7307
|
+
if (newEntries.length === 0) {
|
|
7308
|
+
return Ok({ promoted: 0, skipped: skipped + toPromote.length });
|
|
7309
|
+
}
|
|
7310
|
+
const promotedContent = newEntries.join("\n\n") + "\n";
|
|
7311
|
+
if (!existingGlobal) {
|
|
7312
|
+
fs9.writeFileSync(globalPath, `# Learnings
|
|
7313
|
+
|
|
7314
|
+
${promotedContent}`);
|
|
7315
|
+
} else {
|
|
7316
|
+
fs9.appendFileSync(globalPath, "\n\n" + promotedContent);
|
|
7317
|
+
}
|
|
7318
|
+
learningsCacheMap.delete(globalPath);
|
|
7319
|
+
return Ok({
|
|
7320
|
+
promoted: newEntries.length,
|
|
7321
|
+
skipped: skipped + (toPromote.length - newEntries.length)
|
|
7322
|
+
});
|
|
7323
|
+
} catch (error) {
|
|
7324
|
+
return Err(
|
|
7325
|
+
new Error(
|
|
7326
|
+
`Failed to promote session learnings: ${error instanceof Error ? error.message : String(error)}`
|
|
7327
|
+
)
|
|
7328
|
+
);
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
async function countLearningEntries(projectPath, stream) {
|
|
7332
|
+
const loadResult = await loadRelevantLearnings(projectPath, void 0, stream);
|
|
7333
|
+
if (!loadResult.ok) return 0;
|
|
7334
|
+
return loadResult.value.length;
|
|
7335
|
+
}
|
|
7063
7336
|
var failuresCacheMap = /* @__PURE__ */ new Map();
|
|
7064
7337
|
function clearFailuresCache() {
|
|
7065
7338
|
failuresCacheMap.clear();
|
|
@@ -7494,6 +7767,146 @@ async function archiveSession(projectPath, sessionSlug) {
|
|
|
7494
7767
|
);
|
|
7495
7768
|
}
|
|
7496
7769
|
}
|
|
7770
|
+
var SkillEventSchema = z5.object({
|
|
7771
|
+
timestamp: z5.string(),
|
|
7772
|
+
skill: z5.string(),
|
|
7773
|
+
session: z5.string().optional(),
|
|
7774
|
+
type: z5.enum(["phase_transition", "decision", "gate_result", "handoff", "error", "checkpoint"]),
|
|
7775
|
+
summary: z5.string(),
|
|
7776
|
+
data: z5.record(z5.unknown()).optional(),
|
|
7777
|
+
refs: z5.array(z5.string()).optional(),
|
|
7778
|
+
contentHash: z5.string().optional()
|
|
7779
|
+
});
|
|
7780
|
+
function computeEventHash(event, session) {
|
|
7781
|
+
const identity = `${event.skill}|${event.type}|${event.summary}|${session ?? ""}`;
|
|
7782
|
+
return computeContentHash(identity);
|
|
7783
|
+
}
|
|
7784
|
+
var knownHashesCache = /* @__PURE__ */ new Map();
|
|
7785
|
+
function loadKnownHashes(eventsPath) {
|
|
7786
|
+
const cached = knownHashesCache.get(eventsPath);
|
|
7787
|
+
if (cached) return cached;
|
|
7788
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
7789
|
+
if (fs16.existsSync(eventsPath)) {
|
|
7790
|
+
const content = fs16.readFileSync(eventsPath, "utf-8");
|
|
7791
|
+
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7792
|
+
for (const line of lines) {
|
|
7793
|
+
try {
|
|
7794
|
+
const existing = JSON.parse(line);
|
|
7795
|
+
if (existing.contentHash) {
|
|
7796
|
+
hashes.add(existing.contentHash);
|
|
7797
|
+
}
|
|
7798
|
+
} catch {
|
|
7799
|
+
}
|
|
7800
|
+
}
|
|
7801
|
+
}
|
|
7802
|
+
knownHashesCache.set(eventsPath, hashes);
|
|
7803
|
+
return hashes;
|
|
7804
|
+
}
|
|
7805
|
+
function clearEventHashCache() {
|
|
7806
|
+
knownHashesCache.clear();
|
|
7807
|
+
}
|
|
7808
|
+
async function emitEvent(projectPath, event, options) {
|
|
7809
|
+
try {
|
|
7810
|
+
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7811
|
+
if (!dirResult.ok) return dirResult;
|
|
7812
|
+
const stateDir = dirResult.value;
|
|
7813
|
+
const eventsPath = path13.join(stateDir, EVENTS_FILE);
|
|
7814
|
+
fs16.mkdirSync(stateDir, { recursive: true });
|
|
7815
|
+
const contentHash = computeEventHash(event, options?.session);
|
|
7816
|
+
const knownHashes = loadKnownHashes(eventsPath);
|
|
7817
|
+
if (knownHashes.has(contentHash)) {
|
|
7818
|
+
return Ok({ written: false, reason: "duplicate" });
|
|
7819
|
+
}
|
|
7820
|
+
const fullEvent = {
|
|
7821
|
+
...event,
|
|
7822
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7823
|
+
contentHash
|
|
7824
|
+
};
|
|
7825
|
+
if (options?.session) {
|
|
7826
|
+
fullEvent.session = options.session;
|
|
7827
|
+
}
|
|
7828
|
+
fs16.appendFileSync(eventsPath, JSON.stringify(fullEvent) + "\n");
|
|
7829
|
+
knownHashes.add(contentHash);
|
|
7830
|
+
return Ok({ written: true });
|
|
7831
|
+
} catch (error) {
|
|
7832
|
+
return Err(
|
|
7833
|
+
new Error(`Failed to emit event: ${error instanceof Error ? error.message : String(error)}`)
|
|
7834
|
+
);
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
async function loadEvents(projectPath, options) {
|
|
7838
|
+
try {
|
|
7839
|
+
const dirResult = await getStateDir(projectPath, options?.stream, options?.session);
|
|
7840
|
+
if (!dirResult.ok) return dirResult;
|
|
7841
|
+
const stateDir = dirResult.value;
|
|
7842
|
+
const eventsPath = path13.join(stateDir, EVENTS_FILE);
|
|
7843
|
+
if (!fs16.existsSync(eventsPath)) {
|
|
7844
|
+
return Ok([]);
|
|
7845
|
+
}
|
|
7846
|
+
const content = fs16.readFileSync(eventsPath, "utf-8");
|
|
7847
|
+
const lines = content.split("\n").filter((line) => line.trim() !== "");
|
|
7848
|
+
const events = [];
|
|
7849
|
+
for (const line of lines) {
|
|
7850
|
+
try {
|
|
7851
|
+
const parsed = JSON.parse(line);
|
|
7852
|
+
const result = SkillEventSchema.safeParse(parsed);
|
|
7853
|
+
if (result.success) {
|
|
7854
|
+
events.push(result.data);
|
|
7855
|
+
}
|
|
7856
|
+
} catch {
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7859
|
+
return Ok(events);
|
|
7860
|
+
} catch (error) {
|
|
7861
|
+
return Err(
|
|
7862
|
+
new Error(`Failed to load events: ${error instanceof Error ? error.message : String(error)}`)
|
|
7863
|
+
);
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
function formatPhaseTransition(event) {
|
|
7867
|
+
const data = event.data;
|
|
7868
|
+
const suffix = data?.taskCount ? ` (${data.taskCount} tasks)` : "";
|
|
7869
|
+
return `phase: ${data?.from ?? "?"} -> ${data?.to ?? "?"}${suffix}`;
|
|
7870
|
+
}
|
|
7871
|
+
function formatGateResult(event) {
|
|
7872
|
+
const data = event.data;
|
|
7873
|
+
const status = data?.passed ? "passed" : "failed";
|
|
7874
|
+
const checks = data?.checks?.map((c) => `${c.name} ${c.passed ? "Y" : "N"}`).join(", ");
|
|
7875
|
+
return checks ? `gate: ${status} (${checks})` : `gate: ${status}`;
|
|
7876
|
+
}
|
|
7877
|
+
function formatHandoffDetail(event) {
|
|
7878
|
+
const data = event.data;
|
|
7879
|
+
const direction = data?.toSkill ? ` -> ${data.toSkill}` : "";
|
|
7880
|
+
return `handoff: ${event.summary}${direction}`;
|
|
7881
|
+
}
|
|
7882
|
+
var EVENT_FORMATTERS = {
|
|
7883
|
+
phase_transition: formatPhaseTransition,
|
|
7884
|
+
gate_result: formatGateResult,
|
|
7885
|
+
decision: (event) => `decision: ${event.summary}`,
|
|
7886
|
+
handoff: formatHandoffDetail,
|
|
7887
|
+
error: (event) => `error: ${event.summary}`,
|
|
7888
|
+
checkpoint: (event) => `checkpoint: ${event.summary}`
|
|
7889
|
+
};
|
|
7890
|
+
function formatEventTimeline(events, limit = 20) {
|
|
7891
|
+
if (events.length === 0) return "";
|
|
7892
|
+
const recent = events.slice(-limit);
|
|
7893
|
+
return recent.map((event) => {
|
|
7894
|
+
const time = formatTime(event.timestamp);
|
|
7895
|
+
const formatter = EVENT_FORMATTERS[event.type];
|
|
7896
|
+
const detail = formatter ? formatter(event) : event.summary;
|
|
7897
|
+
return `- ${time} [${event.skill}] ${detail}`;
|
|
7898
|
+
}).join("\n");
|
|
7899
|
+
}
|
|
7900
|
+
function formatTime(timestamp) {
|
|
7901
|
+
try {
|
|
7902
|
+
const date = new Date(timestamp);
|
|
7903
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
7904
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
7905
|
+
return `${hours}:${minutes}`;
|
|
7906
|
+
} catch {
|
|
7907
|
+
return "??:??";
|
|
7908
|
+
}
|
|
7909
|
+
}
|
|
7497
7910
|
async function executeWorkflow(workflow, executor) {
|
|
7498
7911
|
const stepResults = [];
|
|
7499
7912
|
const startTime = Date.now();
|
|
@@ -7670,19 +8083,19 @@ var DEFAULT_SECURITY_CONFIG = {
|
|
|
7670
8083
|
rules: {},
|
|
7671
8084
|
exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]
|
|
7672
8085
|
};
|
|
7673
|
-
var RuleOverrideSchema =
|
|
7674
|
-
var SecurityConfigSchema =
|
|
7675
|
-
enabled:
|
|
7676
|
-
strict:
|
|
7677
|
-
rules:
|
|
7678
|
-
exclude:
|
|
7679
|
-
external:
|
|
7680
|
-
semgrep:
|
|
7681
|
-
enabled:
|
|
7682
|
-
rulesets:
|
|
8086
|
+
var RuleOverrideSchema = z6.enum(["off", "error", "warning", "info"]);
|
|
8087
|
+
var SecurityConfigSchema = z6.object({
|
|
8088
|
+
enabled: z6.boolean().default(true),
|
|
8089
|
+
strict: z6.boolean().default(false),
|
|
8090
|
+
rules: z6.record(z6.string(), RuleOverrideSchema).optional().default({}),
|
|
8091
|
+
exclude: z6.array(z6.string()).optional().default(["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/fixtures/**"]),
|
|
8092
|
+
external: z6.object({
|
|
8093
|
+
semgrep: z6.object({
|
|
8094
|
+
enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto"),
|
|
8095
|
+
rulesets: z6.array(z6.string()).optional()
|
|
7683
8096
|
}).optional(),
|
|
7684
|
-
gitleaks:
|
|
7685
|
-
enabled:
|
|
8097
|
+
gitleaks: z6.object({
|
|
8098
|
+
enabled: z6.union([z6.literal("auto"), z6.boolean()]).default("auto")
|
|
7686
8099
|
}).optional()
|
|
7687
8100
|
}).optional()
|
|
7688
8101
|
});
|
|
@@ -7715,11 +8128,11 @@ function resolveRuleSeverity(ruleId, defaultSeverity, overrides, strict) {
|
|
|
7715
8128
|
}
|
|
7716
8129
|
function detectStack(projectRoot) {
|
|
7717
8130
|
const stacks = [];
|
|
7718
|
-
const pkgJsonPath =
|
|
7719
|
-
if (
|
|
8131
|
+
const pkgJsonPath = path14.join(projectRoot, "package.json");
|
|
8132
|
+
if (fs17.existsSync(pkgJsonPath)) {
|
|
7720
8133
|
stacks.push("node");
|
|
7721
8134
|
try {
|
|
7722
|
-
const pkgJson = JSON.parse(
|
|
8135
|
+
const pkgJson = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
|
|
7723
8136
|
const allDeps = {
|
|
7724
8137
|
...pkgJson.dependencies,
|
|
7725
8138
|
...pkgJson.devDependencies
|
|
@@ -7734,13 +8147,13 @@ function detectStack(projectRoot) {
|
|
|
7734
8147
|
} catch {
|
|
7735
8148
|
}
|
|
7736
8149
|
}
|
|
7737
|
-
const goModPath =
|
|
7738
|
-
if (
|
|
8150
|
+
const goModPath = path14.join(projectRoot, "go.mod");
|
|
8151
|
+
if (fs17.existsSync(goModPath)) {
|
|
7739
8152
|
stacks.push("go");
|
|
7740
8153
|
}
|
|
7741
|
-
const requirementsPath =
|
|
7742
|
-
const pyprojectPath =
|
|
7743
|
-
if (
|
|
8154
|
+
const requirementsPath = path14.join(projectRoot, "requirements.txt");
|
|
8155
|
+
const pyprojectPath = path14.join(projectRoot, "pyproject.toml");
|
|
8156
|
+
if (fs17.existsSync(requirementsPath) || fs17.existsSync(pyprojectPath)) {
|
|
7744
8157
|
stacks.push("python");
|
|
7745
8158
|
}
|
|
7746
8159
|
return stacks;
|
|
@@ -7802,6 +8215,72 @@ var secretRules = [
|
|
|
7802
8215
|
message: "Hardcoded JWT token detected",
|
|
7803
8216
|
remediation: "Tokens should be fetched at runtime, not embedded in source",
|
|
7804
8217
|
references: ["CWE-798"]
|
|
8218
|
+
},
|
|
8219
|
+
{
|
|
8220
|
+
id: "SEC-SEC-006",
|
|
8221
|
+
name: "Anthropic API Key",
|
|
8222
|
+
category: "secrets",
|
|
8223
|
+
severity: "error",
|
|
8224
|
+
confidence: "high",
|
|
8225
|
+
patterns: [/sk-ant-api\d{2}-[A-Za-z0-9_-]{20,}/],
|
|
8226
|
+
message: "Hardcoded Anthropic API key detected",
|
|
8227
|
+
remediation: "Use environment variables: process.env.ANTHROPIC_API_KEY",
|
|
8228
|
+
references: ["CWE-798"]
|
|
8229
|
+
},
|
|
8230
|
+
{
|
|
8231
|
+
id: "SEC-SEC-007",
|
|
8232
|
+
name: "OpenAI API Key",
|
|
8233
|
+
category: "secrets",
|
|
8234
|
+
severity: "error",
|
|
8235
|
+
confidence: "high",
|
|
8236
|
+
patterns: [/sk-proj-[A-Za-z0-9_-]{20,}/],
|
|
8237
|
+
message: "Hardcoded OpenAI API key detected",
|
|
8238
|
+
remediation: "Use environment variables: process.env.OPENAI_API_KEY",
|
|
8239
|
+
references: ["CWE-798"]
|
|
8240
|
+
},
|
|
8241
|
+
{
|
|
8242
|
+
id: "SEC-SEC-008",
|
|
8243
|
+
name: "Google API Key",
|
|
8244
|
+
category: "secrets",
|
|
8245
|
+
severity: "error",
|
|
8246
|
+
confidence: "high",
|
|
8247
|
+
patterns: [/AIza[A-Za-z0-9_-]{35}/],
|
|
8248
|
+
message: "Hardcoded Google API key detected",
|
|
8249
|
+
remediation: "Use environment variables or a secrets manager for Google API keys",
|
|
8250
|
+
references: ["CWE-798"]
|
|
8251
|
+
},
|
|
8252
|
+
{
|
|
8253
|
+
id: "SEC-SEC-009",
|
|
8254
|
+
name: "GitHub Personal Access Token",
|
|
8255
|
+
category: "secrets",
|
|
8256
|
+
severity: "error",
|
|
8257
|
+
confidence: "high",
|
|
8258
|
+
patterns: [/gh[pous]_[A-Za-z0-9_]{36,}/],
|
|
8259
|
+
message: "Hardcoded GitHub personal access token detected",
|
|
8260
|
+
remediation: "Use environment variables: process.env.GITHUB_TOKEN",
|
|
8261
|
+
references: ["CWE-798"]
|
|
8262
|
+
},
|
|
8263
|
+
{
|
|
8264
|
+
id: "SEC-SEC-010",
|
|
8265
|
+
name: "Stripe Live Key",
|
|
8266
|
+
category: "secrets",
|
|
8267
|
+
severity: "error",
|
|
8268
|
+
confidence: "high",
|
|
8269
|
+
patterns: [/\b[spr]k_live_[A-Za-z0-9]{24,}/],
|
|
8270
|
+
message: "Hardcoded Stripe live key detected",
|
|
8271
|
+
remediation: "Use environment variables for Stripe keys; never commit live keys",
|
|
8272
|
+
references: ["CWE-798"]
|
|
8273
|
+
},
|
|
8274
|
+
{
|
|
8275
|
+
id: "SEC-SEC-011",
|
|
8276
|
+
name: "Database Connection String with Credentials",
|
|
8277
|
+
category: "secrets",
|
|
8278
|
+
severity: "error",
|
|
8279
|
+
confidence: "high",
|
|
8280
|
+
patterns: [/(?:postgres|mysql|mongodb|redis|amqp|mssql)(?:\+\w+)?:\/\/[^/\s:]+:[^@/\s]+@/i],
|
|
8281
|
+
message: "Database connection string with embedded credentials detected",
|
|
8282
|
+
remediation: "Use environment variables for connection strings; separate credentials from URIs",
|
|
8283
|
+
references: ["CWE-798"]
|
|
7805
8284
|
}
|
|
7806
8285
|
];
|
|
7807
8286
|
var injectionRules = [
|
|
@@ -7975,99 +8454,445 @@ var deserializationRules = [
|
|
|
7975
8454
|
references: ["CWE-502"]
|
|
7976
8455
|
}
|
|
7977
8456
|
];
|
|
7978
|
-
var
|
|
8457
|
+
var agentConfigRules = [
|
|
7979
8458
|
{
|
|
7980
|
-
id: "SEC-
|
|
7981
|
-
name: "
|
|
7982
|
-
category: "
|
|
8459
|
+
id: "SEC-AGT-001",
|
|
8460
|
+
name: "Hidden Unicode Characters",
|
|
8461
|
+
category: "agent-config",
|
|
8462
|
+
severity: "error",
|
|
8463
|
+
confidence: "high",
|
|
8464
|
+
patterns: [/\u200B|\u200C|\u200D|\uFEFF|\u2060/],
|
|
8465
|
+
fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/*.yaml",
|
|
8466
|
+
message: "Hidden zero-width Unicode characters detected in agent configuration",
|
|
8467
|
+
remediation: "Remove invisible Unicode characters; they may hide malicious instructions",
|
|
8468
|
+
references: ["CWE-116"]
|
|
8469
|
+
},
|
|
8470
|
+
{
|
|
8471
|
+
id: "SEC-AGT-002",
|
|
8472
|
+
name: "URL Execution Directives",
|
|
8473
|
+
category: "agent-config",
|
|
7983
8474
|
severity: "warning",
|
|
7984
8475
|
confidence: "medium",
|
|
7985
|
-
patterns: [
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
],
|
|
7991
|
-
stack: ["node"],
|
|
7992
|
-
message: "Potential prototype pollution via __proto__, constructor, or Object.assign with untrusted input",
|
|
7993
|
-
remediation: "Validate keys against a whitelist, use Object.create(null), or use Map instead of plain objects",
|
|
7994
|
-
references: ["CWE-1321"]
|
|
8476
|
+
patterns: [/\b(?:curl|wget)\s+\S+/i, /\bfetch\s*\(/i],
|
|
8477
|
+
fileGlob: "**/CLAUDE.md,**/AGENTS.md",
|
|
8478
|
+
message: "URL execution directive found in agent configuration",
|
|
8479
|
+
remediation: "Avoid instructing agents to download and execute remote content",
|
|
8480
|
+
references: ["CWE-94"]
|
|
7995
8481
|
},
|
|
7996
8482
|
{
|
|
7997
|
-
id: "SEC-
|
|
7998
|
-
name: "
|
|
7999
|
-
category: "
|
|
8483
|
+
id: "SEC-AGT-003",
|
|
8484
|
+
name: "Wildcard Tool Permissions",
|
|
8485
|
+
category: "agent-config",
|
|
8486
|
+
severity: "warning",
|
|
8487
|
+
confidence: "high",
|
|
8488
|
+
patterns: [/(?:Bash|Write|Edit)\s*\(\s*\*\s*\)/],
|
|
8489
|
+
fileGlob: "**/.claude/**,**/settings*.json",
|
|
8490
|
+
message: "Wildcard tool permissions grant unrestricted access",
|
|
8491
|
+
remediation: "Scope tool permissions to specific patterns instead of wildcards",
|
|
8492
|
+
references: ["CWE-250"]
|
|
8493
|
+
},
|
|
8494
|
+
{
|
|
8495
|
+
id: "SEC-AGT-004",
|
|
8496
|
+
name: "Auto-approve Patterns",
|
|
8497
|
+
category: "agent-config",
|
|
8498
|
+
severity: "warning",
|
|
8499
|
+
confidence: "high",
|
|
8500
|
+
patterns: [/\bautoApprove\b/i, /\bauto_approve\b/i],
|
|
8501
|
+
fileGlob: "**/.claude/**,**/.mcp.json",
|
|
8502
|
+
message: "Auto-approve configuration bypasses human review of tool calls",
|
|
8503
|
+
remediation: "Review auto-approved tools carefully; prefer explicit approval for destructive operations",
|
|
8504
|
+
references: ["CWE-862"]
|
|
8505
|
+
},
|
|
8506
|
+
{
|
|
8507
|
+
id: "SEC-AGT-005",
|
|
8508
|
+
name: "Prompt Injection Surface",
|
|
8509
|
+
category: "agent-config",
|
|
8000
8510
|
severity: "warning",
|
|
8001
8511
|
confidence: "medium",
|
|
8002
|
-
patterns: [
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
remediation: "Sanitize input by stripping keys starting with $ before using in queries",
|
|
8009
|
-
references: ["CWE-943"]
|
|
8010
|
-
}
|
|
8011
|
-
];
|
|
8012
|
-
var expressRules = [
|
|
8512
|
+
patterns: [/\$\{[^}]*\}/, /\{\{[^}]*\}\}/],
|
|
8513
|
+
fileGlob: "**/skill.yaml",
|
|
8514
|
+
message: "Template interpolation syntax in skill YAML may enable prompt injection",
|
|
8515
|
+
remediation: "Avoid dynamic interpolation in skill descriptions; use static text",
|
|
8516
|
+
references: ["CWE-94"]
|
|
8517
|
+
},
|
|
8013
8518
|
{
|
|
8014
|
-
id: "SEC-
|
|
8015
|
-
name: "
|
|
8016
|
-
category: "
|
|
8017
|
-
severity: "
|
|
8018
|
-
confidence: "
|
|
8019
|
-
patterns: [/
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
references: ["CWE-693"]
|
|
8519
|
+
id: "SEC-AGT-006",
|
|
8520
|
+
name: "Permission Bypass Flags",
|
|
8521
|
+
category: "agent-config",
|
|
8522
|
+
severity: "error",
|
|
8523
|
+
confidence: "high",
|
|
8524
|
+
patterns: [/--dangerously-skip-permissions/, /--no-verify/],
|
|
8525
|
+
fileGlob: "**/CLAUDE.md,**/AGENTS.md,**/.claude/**",
|
|
8526
|
+
message: "Permission bypass flag detected in agent configuration",
|
|
8527
|
+
remediation: "Remove flags that bypass safety checks; they undermine enforcement",
|
|
8528
|
+
references: ["CWE-863"]
|
|
8025
8529
|
},
|
|
8026
8530
|
{
|
|
8027
|
-
id: "SEC-
|
|
8028
|
-
name: "
|
|
8029
|
-
category: "
|
|
8030
|
-
severity: "
|
|
8531
|
+
id: "SEC-AGT-007",
|
|
8532
|
+
name: "Hook Injection Surface",
|
|
8533
|
+
category: "agent-config",
|
|
8534
|
+
severity: "error",
|
|
8031
8535
|
confidence: "low",
|
|
8032
|
-
patterns: [
|
|
8033
|
-
|
|
8034
|
-
message: "
|
|
8035
|
-
remediation: "
|
|
8036
|
-
references: ["CWE-
|
|
8536
|
+
patterns: [/\$\(/, /`[^`]+`/, /\s&&\s/, /\s\|\|\s/],
|
|
8537
|
+
fileGlob: "**/settings*.json,**/hooks.json",
|
|
8538
|
+
message: "Shell metacharacters in hook commands may enable command injection",
|
|
8539
|
+
remediation: "Use simple, single-command hooks without shell operators; chain logic inside the script",
|
|
8540
|
+
references: ["CWE-78"]
|
|
8037
8541
|
}
|
|
8038
8542
|
];
|
|
8039
|
-
var
|
|
8543
|
+
var mcpRules = [
|
|
8040
8544
|
{
|
|
8041
|
-
id: "SEC-
|
|
8042
|
-
name: "
|
|
8043
|
-
category: "
|
|
8044
|
-
severity: "
|
|
8545
|
+
id: "SEC-MCP-001",
|
|
8546
|
+
name: "Hardcoded MCP Secrets",
|
|
8547
|
+
category: "mcp",
|
|
8548
|
+
severity: "error",
|
|
8045
8549
|
confidence: "medium",
|
|
8046
|
-
patterns: [
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
remediation: "Use httpOnly cookies for auth tokens instead of localStorage",
|
|
8053
|
-
references: ["CWE-922"]
|
|
8054
|
-
}
|
|
8055
|
-
];
|
|
8056
|
-
var goRules = [
|
|
8550
|
+
patterns: [/(?:API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*["']?\s*:\s*["'][^"']{8,}["']/i],
|
|
8551
|
+
fileGlob: "**/.mcp.json",
|
|
8552
|
+
message: "Hardcoded secret detected in MCP server configuration",
|
|
8553
|
+
remediation: "Use environment variable references instead of inline secrets in .mcp.json",
|
|
8554
|
+
references: ["CWE-798"]
|
|
8555
|
+
},
|
|
8057
8556
|
{
|
|
8058
|
-
id: "SEC-
|
|
8059
|
-
name: "
|
|
8060
|
-
category: "
|
|
8061
|
-
severity: "
|
|
8557
|
+
id: "SEC-MCP-002",
|
|
8558
|
+
name: "Shell Injection in MCP Args",
|
|
8559
|
+
category: "mcp",
|
|
8560
|
+
severity: "error",
|
|
8062
8561
|
confidence: "medium",
|
|
8063
|
-
patterns: [
|
|
8064
|
-
|
|
8065
|
-
message: "
|
|
8066
|
-
remediation: "
|
|
8067
|
-
references: ["CWE-
|
|
8562
|
+
patterns: [/\$\(/, /`[^`]+`/],
|
|
8563
|
+
fileGlob: "**/.mcp.json",
|
|
8564
|
+
message: "Shell metacharacters detected in MCP server arguments",
|
|
8565
|
+
remediation: "Use literal argument values; avoid shell interpolation in MCP args",
|
|
8566
|
+
references: ["CWE-78"]
|
|
8068
8567
|
},
|
|
8069
8568
|
{
|
|
8070
|
-
id: "SEC-
|
|
8569
|
+
id: "SEC-MCP-003",
|
|
8570
|
+
name: "Network Exposure",
|
|
8571
|
+
category: "mcp",
|
|
8572
|
+
severity: "warning",
|
|
8573
|
+
confidence: "high",
|
|
8574
|
+
patterns: [/0\.0\.0\.0/, /["']\*["']\s*:\s*\d/, /host["']?\s*:\s*["']\*["']/i],
|
|
8575
|
+
fileGlob: "**/.mcp.json",
|
|
8576
|
+
message: "MCP server binding to all network interfaces (0.0.0.0 or wildcard *)",
|
|
8577
|
+
remediation: "Bind to 127.0.0.1 or localhost to restrict access to local machine",
|
|
8578
|
+
references: ["CWE-668"]
|
|
8579
|
+
},
|
|
8580
|
+
{
|
|
8581
|
+
id: "SEC-MCP-004",
|
|
8582
|
+
name: "Typosquatting Vector",
|
|
8583
|
+
category: "mcp",
|
|
8584
|
+
severity: "warning",
|
|
8585
|
+
confidence: "medium",
|
|
8586
|
+
patterns: [/\bnpx\s+(?:-y|--yes)\b/],
|
|
8587
|
+
fileGlob: "**/.mcp.json",
|
|
8588
|
+
message: "npx -y auto-installs packages without confirmation, enabling typosquatting",
|
|
8589
|
+
remediation: "Pin exact package versions or install packages explicitly before use",
|
|
8590
|
+
references: ["CWE-427"]
|
|
8591
|
+
},
|
|
8592
|
+
{
|
|
8593
|
+
id: "SEC-MCP-005",
|
|
8594
|
+
name: "Unencrypted Transport",
|
|
8595
|
+
category: "mcp",
|
|
8596
|
+
severity: "warning",
|
|
8597
|
+
confidence: "medium",
|
|
8598
|
+
patterns: [/http:\/\/(?!localhost\b|127\.0\.0\.1\b)/],
|
|
8599
|
+
fileGlob: "**/.mcp.json",
|
|
8600
|
+
message: "Unencrypted HTTP transport detected for MCP server connection",
|
|
8601
|
+
remediation: "Use https:// for all non-localhost MCP server connections",
|
|
8602
|
+
references: ["CWE-319"]
|
|
8603
|
+
}
|
|
8604
|
+
];
|
|
8605
|
+
var insecureDefaultsRules = [
|
|
8606
|
+
{
|
|
8607
|
+
id: "SEC-DEF-001",
|
|
8608
|
+
name: "Security-Sensitive Fallback to Hardcoded Default",
|
|
8609
|
+
category: "insecure-defaults",
|
|
8610
|
+
severity: "warning",
|
|
8611
|
+
confidence: "medium",
|
|
8612
|
+
patterns: [
|
|
8613
|
+
/(?:SECRET|KEY|TOKEN|PASSWORD|SALT|PEPPER|SIGNING|ENCRYPTION|AUTH|JWT|SESSION).*(?:\|\||\?\?)\s*['"][^'"]+['"]/i
|
|
8614
|
+
],
|
|
8615
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8616
|
+
message: "Security-sensitive variable falls back to a hardcoded default when env var is missing",
|
|
8617
|
+
remediation: "Throw an error if the env var is missing instead of falling back to a default. Use a startup validation check.",
|
|
8618
|
+
references: ["CWE-1188"]
|
|
8619
|
+
},
|
|
8620
|
+
{
|
|
8621
|
+
id: "SEC-DEF-002",
|
|
8622
|
+
name: "TLS/SSL Disabled by Default",
|
|
8623
|
+
category: "insecure-defaults",
|
|
8624
|
+
severity: "warning",
|
|
8625
|
+
confidence: "medium",
|
|
8626
|
+
patterns: [
|
|
8627
|
+
/(?:tls|ssl|https|secure)\s*(?:=|:)\s*(?:false|config\??\.\w+\s*(?:\?\?|&&|\|\|)\s*false)/i
|
|
8628
|
+
],
|
|
8629
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
8630
|
+
message: "Security feature defaults to disabled; missing configuration degrades to insecure mode",
|
|
8631
|
+
remediation: "Default security features to enabled (true). Require explicit opt-out, not opt-in.",
|
|
8632
|
+
references: ["CWE-1188"]
|
|
8633
|
+
},
|
|
8634
|
+
{
|
|
8635
|
+
id: "SEC-DEF-003",
|
|
8636
|
+
name: "Swallowed Authentication/Authorization Error",
|
|
8637
|
+
category: "insecure-defaults",
|
|
8638
|
+
severity: "warning",
|
|
8639
|
+
confidence: "low",
|
|
8640
|
+
patterns: [
|
|
8641
|
+
// Matches single-line empty catch: catch(e) { } or catch(e) { // ignore }
|
|
8642
|
+
// Note: multi-line catch blocks are handled by AI review, not this rule
|
|
8643
|
+
/catch\s*\([^)]*\)\s*\{\s*(?:\/\/\s*(?:ignore|skip|noop|todo)\b.*)?\s*\}/
|
|
8644
|
+
],
|
|
8645
|
+
fileGlob: "**/*auth*.{ts,js,mjs,cjs},**/*session*.{ts,js,mjs,cjs},**/*token*.{ts,js,mjs,cjs}",
|
|
8646
|
+
message: "Single-line empty catch block in authentication/authorization code may silently allow unauthorized access. Note: multi-line empty catch blocks are detected by AI review, not this mechanical rule.",
|
|
8647
|
+
remediation: "Re-throw the error or return an explicit denial. Never silently swallow auth errors.",
|
|
8648
|
+
references: ["CWE-754", "CWE-390"]
|
|
8649
|
+
},
|
|
8650
|
+
{
|
|
8651
|
+
id: "SEC-DEF-004",
|
|
8652
|
+
name: "Permissive CORS Fallback",
|
|
8653
|
+
category: "insecure-defaults",
|
|
8654
|
+
severity: "warning",
|
|
8655
|
+
confidence: "medium",
|
|
8656
|
+
patterns: [
|
|
8657
|
+
/(?:origin|cors)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*['"]\*/
|
|
8658
|
+
],
|
|
8659
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8660
|
+
message: "CORS origin falls back to wildcard (*) when configuration is missing",
|
|
8661
|
+
remediation: "Default to a restrictive origin list. Require explicit configuration for permissive CORS.",
|
|
8662
|
+
references: ["CWE-942"]
|
|
8663
|
+
},
|
|
8664
|
+
{
|
|
8665
|
+
id: "SEC-DEF-005",
|
|
8666
|
+
name: "Rate Limiting Disabled by Default",
|
|
8667
|
+
category: "insecure-defaults",
|
|
8668
|
+
severity: "info",
|
|
8669
|
+
confidence: "low",
|
|
8670
|
+
patterns: [
|
|
8671
|
+
/(?:rateLimit|rateLimiting|throttle)\s*(?:=|:)\s*(?:config|options|env)\??\.\w+\s*(?:\?\?|\|\|)\s*(?:false|0|null|undefined)/i
|
|
8672
|
+
],
|
|
8673
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8674
|
+
message: "Rate limiting defaults to disabled when configuration is missing",
|
|
8675
|
+
remediation: "Default to a sensible rate limit. Require explicit opt-out for disabling.",
|
|
8676
|
+
references: ["CWE-770"]
|
|
8677
|
+
}
|
|
8678
|
+
];
|
|
8679
|
+
var sharpEdgesRules = [
|
|
8680
|
+
// --- Deprecated Crypto APIs ---
|
|
8681
|
+
{
|
|
8682
|
+
id: "SEC-EDGE-001",
|
|
8683
|
+
name: "Deprecated createCipher API",
|
|
8684
|
+
category: "sharp-edges",
|
|
8685
|
+
severity: "error",
|
|
8686
|
+
confidence: "high",
|
|
8687
|
+
patterns: [/crypto\.createCipher\s*\(/],
|
|
8688
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8689
|
+
message: "crypto.createCipher is deprecated \u2014 uses weak key derivation (no IV)",
|
|
8690
|
+
remediation: "Use crypto.createCipheriv with a random IV and proper key derivation (scrypt/pbkdf2)",
|
|
8691
|
+
references: ["CWE-327"]
|
|
8692
|
+
},
|
|
8693
|
+
{
|
|
8694
|
+
id: "SEC-EDGE-002",
|
|
8695
|
+
name: "Deprecated createDecipher API",
|
|
8696
|
+
category: "sharp-edges",
|
|
8697
|
+
severity: "error",
|
|
8698
|
+
confidence: "high",
|
|
8699
|
+
patterns: [/crypto\.createDecipher\s*\(/],
|
|
8700
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8701
|
+
message: "crypto.createDecipher is deprecated \u2014 uses weak key derivation (no IV)",
|
|
8702
|
+
remediation: "Use crypto.createDecipheriv with the same IV used for encryption",
|
|
8703
|
+
references: ["CWE-327"]
|
|
8704
|
+
},
|
|
8705
|
+
{
|
|
8706
|
+
id: "SEC-EDGE-003",
|
|
8707
|
+
name: "ECB Mode Selection",
|
|
8708
|
+
category: "sharp-edges",
|
|
8709
|
+
severity: "warning",
|
|
8710
|
+
confidence: "high",
|
|
8711
|
+
patterns: [/-ecb['"]/],
|
|
8712
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
8713
|
+
message: "ECB mode does not provide semantic security \u2014 identical plaintext blocks produce identical ciphertext",
|
|
8714
|
+
remediation: "Use CBC, CTR, or GCM mode instead of ECB",
|
|
8715
|
+
references: ["CWE-327"]
|
|
8716
|
+
},
|
|
8717
|
+
// --- Unsafe Deserialization ---
|
|
8718
|
+
{
|
|
8719
|
+
id: "SEC-EDGE-004",
|
|
8720
|
+
name: "yaml.load Without Safe Loader",
|
|
8721
|
+
category: "sharp-edges",
|
|
8722
|
+
severity: "error",
|
|
8723
|
+
confidence: "high",
|
|
8724
|
+
patterns: [
|
|
8725
|
+
/yaml\.load\s*\(/
|
|
8726
|
+
// Python: yaml.load() without SafeLoader
|
|
8727
|
+
],
|
|
8728
|
+
fileGlob: "**/*.py",
|
|
8729
|
+
message: "yaml.load() executes arbitrary Python objects \u2014 use yaml.safe_load() instead",
|
|
8730
|
+
remediation: "Replace yaml.load() with yaml.safe_load() or yaml.load(data, Loader=SafeLoader). Note: this rule will flag yaml.load(data, Loader=SafeLoader) \u2014 suppress with # harness-ignore SEC-EDGE-004: safe usage with SafeLoader",
|
|
8731
|
+
references: ["CWE-502"]
|
|
8732
|
+
},
|
|
8733
|
+
{
|
|
8734
|
+
id: "SEC-EDGE-005",
|
|
8735
|
+
name: "Pickle/Marshal Deserialization",
|
|
8736
|
+
category: "sharp-edges",
|
|
8737
|
+
severity: "error",
|
|
8738
|
+
confidence: "high",
|
|
8739
|
+
patterns: [/pickle\.loads?\s*\(/, /marshal\.loads?\s*\(/],
|
|
8740
|
+
fileGlob: "**/*.py",
|
|
8741
|
+
message: "pickle/marshal deserialization executes arbitrary code \u2014 never use on untrusted data",
|
|
8742
|
+
remediation: "Use JSON, MessagePack, or Protocol Buffers for untrusted data serialization",
|
|
8743
|
+
references: ["CWE-502"]
|
|
8744
|
+
},
|
|
8745
|
+
// --- TOCTOU (Time-of-Check to Time-of-Use) ---
|
|
8746
|
+
{
|
|
8747
|
+
id: "SEC-EDGE-006",
|
|
8748
|
+
name: "Check-Then-Act File Operation",
|
|
8749
|
+
category: "sharp-edges",
|
|
8750
|
+
severity: "warning",
|
|
8751
|
+
confidence: "medium",
|
|
8752
|
+
// Patterns use .{0,N} since scanner matches single lines only (no multiline mode)
|
|
8753
|
+
patterns: [
|
|
8754
|
+
/(?:existsSync|accessSync|statSync)\s*\([^)]+\).{0,50}(?:readFileSync|writeFileSync|unlinkSync|mkdirSync)\s*\(/
|
|
8755
|
+
],
|
|
8756
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8757
|
+
message: "Check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
|
|
8758
|
+
remediation: "Use the operation directly and handle ENOENT/EEXIST errors instead of checking first",
|
|
8759
|
+
references: ["CWE-367"]
|
|
8760
|
+
},
|
|
8761
|
+
{
|
|
8762
|
+
id: "SEC-EDGE-007",
|
|
8763
|
+
name: "Check-Then-Act File Operation (Async)",
|
|
8764
|
+
category: "sharp-edges",
|
|
8765
|
+
severity: "warning",
|
|
8766
|
+
confidence: "medium",
|
|
8767
|
+
// Uses .{0,N} since scanner matches single lines only (no multiline mode)
|
|
8768
|
+
patterns: [/(?:access|stat)\s*\([^)]+\).{0,80}(?:readFile|writeFile|unlink|mkdir)\s*\(/],
|
|
8769
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8770
|
+
message: "Async check-then-act pattern on filesystem is vulnerable to TOCTOU race conditions",
|
|
8771
|
+
remediation: "Use the operation directly with try/catch instead of checking existence first",
|
|
8772
|
+
references: ["CWE-367"]
|
|
8773
|
+
},
|
|
8774
|
+
// --- Stringly-Typed Security ---
|
|
8775
|
+
{
|
|
8776
|
+
id: "SEC-EDGE-008",
|
|
8777
|
+
name: 'JWT Algorithm "none"',
|
|
8778
|
+
category: "sharp-edges",
|
|
8779
|
+
severity: "error",
|
|
8780
|
+
confidence: "high",
|
|
8781
|
+
patterns: [
|
|
8782
|
+
/algorithm[s]?\s*[:=]\s*\[?\s*['"]none['"]/i,
|
|
8783
|
+
/alg(?:orithm)?\s*[:=]\s*['"]none['"]/i
|
|
8784
|
+
],
|
|
8785
|
+
fileGlob: "**/*.{ts,js,mjs,cjs}",
|
|
8786
|
+
message: 'JWT "none" algorithm disables signature verification entirely',
|
|
8787
|
+
remediation: 'Specify an explicit algorithm (e.g., "HS256", "RS256") and set algorithms allowlist in verify options',
|
|
8788
|
+
references: ["CWE-345"]
|
|
8789
|
+
},
|
|
8790
|
+
{
|
|
8791
|
+
id: "SEC-EDGE-009",
|
|
8792
|
+
name: "DES/RC4 Algorithm Selection",
|
|
8793
|
+
category: "sharp-edges",
|
|
8794
|
+
severity: "error",
|
|
8795
|
+
confidence: "high",
|
|
8796
|
+
patterns: [/['"]\s*(?:des|des-ede|des-ede3|des3|rc4|rc2|blowfish)\s*['"]/i],
|
|
8797
|
+
fileGlob: "**/*.{ts,js,mjs,cjs,go,py}",
|
|
8798
|
+
message: "Weak/deprecated cipher algorithm selected \u2014 DES, RC4, RC2, and Blowfish are broken or deprecated",
|
|
8799
|
+
remediation: "Use AES-256-GCM or ChaCha20-Poly1305",
|
|
8800
|
+
references: ["CWE-327"]
|
|
8801
|
+
}
|
|
8802
|
+
];
|
|
8803
|
+
var nodeRules = [
|
|
8804
|
+
{
|
|
8805
|
+
id: "SEC-NODE-001",
|
|
8806
|
+
name: "Prototype Pollution",
|
|
8807
|
+
category: "injection",
|
|
8808
|
+
severity: "warning",
|
|
8809
|
+
confidence: "medium",
|
|
8810
|
+
patterns: [
|
|
8811
|
+
/__proto__/,
|
|
8812
|
+
/\bconstructor\s*\[/,
|
|
8813
|
+
/\bprototype\s*\[/,
|
|
8814
|
+
/Object\.assign\s*\(\s*\w+\s*,\s*(?:req|request|body|input|params|query)\b/
|
|
8815
|
+
],
|
|
8816
|
+
stack: ["node"],
|
|
8817
|
+
message: "Potential prototype pollution via __proto__, constructor, or Object.assign with untrusted input",
|
|
8818
|
+
remediation: "Validate keys against a whitelist, use Object.create(null), or use Map instead of plain objects",
|
|
8819
|
+
references: ["CWE-1321"]
|
|
8820
|
+
},
|
|
8821
|
+
{
|
|
8822
|
+
id: "SEC-NODE-002",
|
|
8823
|
+
name: "NoSQL Injection",
|
|
8824
|
+
category: "injection",
|
|
8825
|
+
severity: "warning",
|
|
8826
|
+
confidence: "medium",
|
|
8827
|
+
patterns: [
|
|
8828
|
+
/\.find\s*\(\s*\{[^}]*\$(?:gt|gte|lt|lte|ne|in|nin|regex|where|exists)/,
|
|
8829
|
+
/\.find\s*\(\s*(?:req|request)\.(?:body|query|params)/
|
|
8830
|
+
],
|
|
8831
|
+
stack: ["node"],
|
|
8832
|
+
message: "Potential NoSQL injection: MongoDB query operators in user input",
|
|
8833
|
+
remediation: "Sanitize input by stripping keys starting with $ before using in queries",
|
|
8834
|
+
references: ["CWE-943"]
|
|
8835
|
+
}
|
|
8836
|
+
];
|
|
8837
|
+
var expressRules = [
|
|
8838
|
+
{
|
|
8839
|
+
id: "SEC-EXPRESS-001",
|
|
8840
|
+
name: "Missing Helmet",
|
|
8841
|
+
category: "network",
|
|
8842
|
+
severity: "info",
|
|
8843
|
+
confidence: "low",
|
|
8844
|
+
patterns: [/app\s*=\s*express\s*\(\)/],
|
|
8845
|
+
stack: ["express"],
|
|
8846
|
+
fileGlob: "**/app.{ts,js}",
|
|
8847
|
+
message: "Express app initialization detected \u2014 ensure helmet middleware is applied for security headers",
|
|
8848
|
+
remediation: "Add helmet middleware: app.use(helmet())",
|
|
8849
|
+
references: ["CWE-693"]
|
|
8850
|
+
},
|
|
8851
|
+
{
|
|
8852
|
+
id: "SEC-EXPRESS-002",
|
|
8853
|
+
name: "Unprotected Route with Body Parsing",
|
|
8854
|
+
category: "network",
|
|
8855
|
+
severity: "info",
|
|
8856
|
+
confidence: "low",
|
|
8857
|
+
patterns: [/app\.(?:post|put|patch)\s*\([^)]*,\s*(?:req|request)\s*(?:,|\))/],
|
|
8858
|
+
stack: ["express"],
|
|
8859
|
+
message: "Express route accepts request body \u2014 ensure input validation and rate limiting are applied",
|
|
8860
|
+
remediation: "Add express-rate-limit and validate request body with Zod/joi",
|
|
8861
|
+
references: ["CWE-770"]
|
|
8862
|
+
}
|
|
8863
|
+
];
|
|
8864
|
+
var reactRules = [
|
|
8865
|
+
{
|
|
8866
|
+
id: "SEC-REACT-001",
|
|
8867
|
+
name: "Sensitive Data in Client Storage",
|
|
8868
|
+
category: "secrets",
|
|
8869
|
+
severity: "warning",
|
|
8870
|
+
confidence: "medium",
|
|
8871
|
+
patterns: [
|
|
8872
|
+
/localStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i,
|
|
8873
|
+
/sessionStorage\.setItem\s*\(\s*['"](?:token|jwt|auth|session|password|secret|key|credential)/i
|
|
8874
|
+
],
|
|
8875
|
+
stack: ["react"],
|
|
8876
|
+
message: "Storing sensitive data in browser storage is accessible to XSS attacks",
|
|
8877
|
+
remediation: "Use httpOnly cookies for auth tokens instead of localStorage",
|
|
8878
|
+
references: ["CWE-922"]
|
|
8879
|
+
}
|
|
8880
|
+
];
|
|
8881
|
+
var goRules = [
|
|
8882
|
+
{
|
|
8883
|
+
id: "SEC-GO-001",
|
|
8884
|
+
name: "Unsafe Pointer Usage",
|
|
8885
|
+
category: "injection",
|
|
8886
|
+
severity: "warning",
|
|
8887
|
+
confidence: "medium",
|
|
8888
|
+
patterns: [/unsafe\.Pointer/],
|
|
8889
|
+
stack: ["go"],
|
|
8890
|
+
message: "unsafe.Pointer bypasses Go type safety",
|
|
8891
|
+
remediation: "Avoid unsafe.Pointer unless absolutely necessary; document justification",
|
|
8892
|
+
references: ["CWE-119"]
|
|
8893
|
+
},
|
|
8894
|
+
{
|
|
8895
|
+
id: "SEC-GO-002",
|
|
8071
8896
|
name: "Format String Injection",
|
|
8072
8897
|
category: "injection",
|
|
8073
8898
|
severity: "warning",
|
|
@@ -8079,6 +8904,14 @@ var goRules = [
|
|
|
8079
8904
|
references: ["CWE-134"]
|
|
8080
8905
|
}
|
|
8081
8906
|
];
|
|
8907
|
+
function parseHarnessIgnore(line, ruleId) {
|
|
8908
|
+
if (!line.includes("harness-ignore")) return null;
|
|
8909
|
+
if (!line.includes(ruleId)) return null;
|
|
8910
|
+
const match = line.match(/(?:\/\/|#)\s*harness-ignore\s+(SEC-[A-Z]+-\d+)(?::\s*(.+))?/);
|
|
8911
|
+
if (match?.[1] !== ruleId) return null;
|
|
8912
|
+
const text = match[2]?.trim();
|
|
8913
|
+
return { ruleId, justification: text || null };
|
|
8914
|
+
}
|
|
8082
8915
|
var SecurityScanner = class {
|
|
8083
8916
|
registry;
|
|
8084
8917
|
config;
|
|
@@ -8093,7 +8926,11 @@ var SecurityScanner = class {
|
|
|
8093
8926
|
...cryptoRules,
|
|
8094
8927
|
...pathTraversalRules,
|
|
8095
8928
|
...networkRules,
|
|
8096
|
-
...deserializationRules
|
|
8929
|
+
...deserializationRules,
|
|
8930
|
+
...agentConfigRules,
|
|
8931
|
+
...mcpRules,
|
|
8932
|
+
...insecureDefaultsRules,
|
|
8933
|
+
...sharpEdgesRules
|
|
8097
8934
|
]);
|
|
8098
8935
|
this.registry.registerAll([...nodeRules, ...expressRules, ...reactRules, ...goRules]);
|
|
8099
8936
|
this.activeRules = this.registry.getAll();
|
|
@@ -8102,11 +8939,40 @@ var SecurityScanner = class {
|
|
|
8102
8939
|
const stacks = detectStack(projectRoot);
|
|
8103
8940
|
this.activeRules = this.registry.getForStacks(stacks.length > 0 ? stacks : []);
|
|
8104
8941
|
}
|
|
8942
|
+
/**
|
|
8943
|
+
* Scan raw content against all active rules. Note: this method does NOT apply
|
|
8944
|
+
* fileGlob filtering — every active rule is evaluated regardless of filePath.
|
|
8945
|
+
* If you are scanning a specific file and want fileGlob-based rule filtering,
|
|
8946
|
+
* use {@link scanFile} instead.
|
|
8947
|
+
*/
|
|
8105
8948
|
scanContent(content, filePath, startLine = 1) {
|
|
8106
8949
|
if (!this.config.enabled) return [];
|
|
8107
|
-
const findings = [];
|
|
8108
8950
|
const lines = content.split("\n");
|
|
8109
|
-
|
|
8951
|
+
return this.scanLinesWithRules(lines, this.activeRules, filePath, startLine);
|
|
8952
|
+
}
|
|
8953
|
+
async scanFile(filePath) {
|
|
8954
|
+
if (!this.config.enabled) return [];
|
|
8955
|
+
const content = await fs18.readFile(filePath, "utf-8");
|
|
8956
|
+
return this.scanContentForFile(content, filePath, 1);
|
|
8957
|
+
}
|
|
8958
|
+
scanContentForFile(content, filePath, startLine = 1) {
|
|
8959
|
+
if (!this.config.enabled) return [];
|
|
8960
|
+
const lines = content.split("\n");
|
|
8961
|
+
const applicableRules = this.activeRules.filter((rule) => {
|
|
8962
|
+
if (!rule.fileGlob) return true;
|
|
8963
|
+
const globs = rule.fileGlob.split(",").map((g) => g.trim());
|
|
8964
|
+
return globs.some((glob2) => minimatch4(filePath, glob2, { dot: true }));
|
|
8965
|
+
});
|
|
8966
|
+
return this.scanLinesWithRules(lines, applicableRules, filePath, startLine);
|
|
8967
|
+
}
|
|
8968
|
+
/**
|
|
8969
|
+
* Core scanning loop shared by scanContent and scanContentForFile.
|
|
8970
|
+
* Evaluates each rule against each line, handling suppression (FP gate)
|
|
8971
|
+
* and pattern matching uniformly.
|
|
8972
|
+
*/
|
|
8973
|
+
scanLinesWithRules(lines, rules, filePath, startLine) {
|
|
8974
|
+
const findings = [];
|
|
8975
|
+
for (const rule of rules) {
|
|
8110
8976
|
const resolved = resolveRuleSeverity(
|
|
8111
8977
|
rule.id,
|
|
8112
8978
|
rule.severity,
|
|
@@ -8116,7 +8982,25 @@ var SecurityScanner = class {
|
|
|
8116
8982
|
if (resolved === "off") continue;
|
|
8117
8983
|
for (let i = 0; i < lines.length; i++) {
|
|
8118
8984
|
const line = lines[i] ?? "";
|
|
8119
|
-
|
|
8985
|
+
const suppressionMatch = parseHarnessIgnore(line, rule.id);
|
|
8986
|
+
if (suppressionMatch) {
|
|
8987
|
+
if (!suppressionMatch.justification) {
|
|
8988
|
+
findings.push({
|
|
8989
|
+
ruleId: rule.id,
|
|
8990
|
+
ruleName: rule.name,
|
|
8991
|
+
category: rule.category,
|
|
8992
|
+
severity: this.config.strict ? "error" : "warning",
|
|
8993
|
+
confidence: "high",
|
|
8994
|
+
file: filePath,
|
|
8995
|
+
line: startLine + i,
|
|
8996
|
+
match: line.trim(),
|
|
8997
|
+
context: line,
|
|
8998
|
+
message: `Suppression of ${rule.id} requires justification: // harness-ignore ${rule.id}: <reason>`,
|
|
8999
|
+
remediation: `Add justification after colon: // harness-ignore ${rule.id}: false positive because ...`
|
|
9000
|
+
});
|
|
9001
|
+
}
|
|
9002
|
+
continue;
|
|
9003
|
+
}
|
|
8120
9004
|
for (const pattern of rule.patterns) {
|
|
8121
9005
|
pattern.lastIndex = 0;
|
|
8122
9006
|
if (pattern.test(line)) {
|
|
@@ -8141,31 +9025,426 @@ var SecurityScanner = class {
|
|
|
8141
9025
|
}
|
|
8142
9026
|
return findings;
|
|
8143
9027
|
}
|
|
8144
|
-
async
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
9028
|
+
async scanFiles(filePaths) {
|
|
9029
|
+
const allFindings = [];
|
|
9030
|
+
let scannedCount = 0;
|
|
9031
|
+
for (const filePath of filePaths) {
|
|
9032
|
+
try {
|
|
9033
|
+
const findings = await this.scanFile(filePath);
|
|
9034
|
+
allFindings.push(...findings);
|
|
9035
|
+
scannedCount++;
|
|
9036
|
+
} catch {
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
return {
|
|
9040
|
+
findings: allFindings,
|
|
9041
|
+
scannedFiles: scannedCount,
|
|
9042
|
+
rulesApplied: this.activeRules.length,
|
|
9043
|
+
externalToolsUsed: [],
|
|
9044
|
+
coverage: "baseline"
|
|
9045
|
+
};
|
|
9046
|
+
}
|
|
9047
|
+
};
|
|
9048
|
+
var hiddenUnicodePatterns = [
|
|
9049
|
+
{
|
|
9050
|
+
ruleId: "INJ-UNI-001",
|
|
9051
|
+
severity: "high",
|
|
9052
|
+
category: "hidden-unicode",
|
|
9053
|
+
description: "Zero-width characters that can hide malicious instructions",
|
|
9054
|
+
// eslint-disable-next-line no-misleading-character-class -- intentional: regex detects zero-width chars for security scanning
|
|
9055
|
+
pattern: /[\u200B\u200C\u200D\uFEFF\u2060]/
|
|
9056
|
+
},
|
|
9057
|
+
{
|
|
9058
|
+
ruleId: "INJ-UNI-002",
|
|
9059
|
+
severity: "high",
|
|
9060
|
+
category: "hidden-unicode",
|
|
9061
|
+
description: "RTL/LTR override characters that can disguise text direction",
|
|
9062
|
+
pattern: /[\u202A-\u202E\u2066-\u2069]/
|
|
9063
|
+
}
|
|
9064
|
+
];
|
|
9065
|
+
var reRolingPatterns = [
|
|
9066
|
+
{
|
|
9067
|
+
ruleId: "INJ-REROL-001",
|
|
9068
|
+
severity: "high",
|
|
9069
|
+
category: "explicit-re-roling",
|
|
9070
|
+
description: "Instruction to ignore/disregard/forget previous instructions",
|
|
9071
|
+
pattern: /(?:ignore|disregard|forget)\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+(?:instructions?|prompts?|context|rules?|guidelines?)/i
|
|
9072
|
+
},
|
|
9073
|
+
{
|
|
9074
|
+
ruleId: "INJ-REROL-002",
|
|
9075
|
+
severity: "high",
|
|
9076
|
+
category: "explicit-re-roling",
|
|
9077
|
+
description: "Attempt to reassign the AI role",
|
|
9078
|
+
pattern: /you\s+are\s+now\s+(?:a\s+|an\s+)?(?:new\s+)?(?:helpful\s+)?(?:my\s+)?(?:\w+\s+)?(?:assistant|agent|AI|bot|chatbot|system|persona)\b/i
|
|
9079
|
+
},
|
|
9080
|
+
{
|
|
9081
|
+
ruleId: "INJ-REROL-003",
|
|
9082
|
+
severity: "high",
|
|
9083
|
+
category: "explicit-re-roling",
|
|
9084
|
+
description: "Direct instruction override attempt",
|
|
9085
|
+
pattern: /(?:new\s+)?(?:system\s+)?(?:instruction|directive|role|persona)\s*[:=]\s*/i
|
|
9086
|
+
}
|
|
9087
|
+
];
|
|
9088
|
+
var permissionEscalationPatterns = [
|
|
9089
|
+
{
|
|
9090
|
+
ruleId: "INJ-PERM-001",
|
|
9091
|
+
severity: "high",
|
|
9092
|
+
category: "permission-escalation",
|
|
9093
|
+
description: "Attempt to enable all tools or grant unrestricted access",
|
|
9094
|
+
pattern: /(?:allow|enable|grant)\s+all\s+(?:tools?|permissions?|access)/i
|
|
9095
|
+
},
|
|
9096
|
+
{
|
|
9097
|
+
ruleId: "INJ-PERM-002",
|
|
9098
|
+
severity: "high",
|
|
9099
|
+
category: "permission-escalation",
|
|
9100
|
+
description: "Attempt to disable safety or security features",
|
|
9101
|
+
pattern: /(?:disable|turn\s+off|remove|bypass)\s+(?:all\s+)?(?:safety|security|restrictions?|guardrails?|protections?|checks?)/i
|
|
9102
|
+
},
|
|
9103
|
+
{
|
|
9104
|
+
ruleId: "INJ-PERM-003",
|
|
9105
|
+
severity: "high",
|
|
9106
|
+
category: "permission-escalation",
|
|
9107
|
+
description: "Auto-approve directive that bypasses human review",
|
|
9108
|
+
pattern: /(?:auto[- ]?approve|--no-verify|--dangerously-skip-permissions)/i
|
|
9109
|
+
}
|
|
9110
|
+
];
|
|
9111
|
+
var encodedPayloadPatterns = [
|
|
9112
|
+
{
|
|
9113
|
+
ruleId: "INJ-ENC-001",
|
|
9114
|
+
severity: "high",
|
|
9115
|
+
category: "encoded-payloads",
|
|
9116
|
+
description: "Base64-encoded string long enough to contain instructions (>=28 chars)",
|
|
9117
|
+
// Match base64 strings of 28+ chars (7+ groups of 4).
|
|
9118
|
+
// Excludes JWT tokens (eyJ prefix) and Bearer-prefixed tokens.
|
|
9119
|
+
pattern: /(?<!Bearer\s)(?<![:])(?<![A-Za-z0-9/])(?!eyJ)(?:[A-Za-z0-9+/]{4}){7,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?(?![A-Za-z0-9/])/
|
|
9120
|
+
},
|
|
9121
|
+
{
|
|
9122
|
+
ruleId: "INJ-ENC-002",
|
|
9123
|
+
severity: "high",
|
|
9124
|
+
category: "encoded-payloads",
|
|
9125
|
+
description: "Hex-encoded string long enough to contain directives (>=20 hex chars)",
|
|
9126
|
+
// Excludes hash-prefixed hex (sha256:, sha512:, md5:, etc.) and hex preceded by 0x.
|
|
9127
|
+
// Note: 40-char git SHA hashes (e.g. in `git log` output) may match — downstream
|
|
9128
|
+
// callers should filter matches of exactly 40 hex chars if scanning git output.
|
|
9129
|
+
pattern: /(?<![:x])(?<![A-Fa-f0-9])(?:[0-9a-fA-F]{2}){10,}(?![A-Fa-f0-9])/
|
|
9130
|
+
}
|
|
9131
|
+
];
|
|
9132
|
+
var indirectInjectionPatterns = [
|
|
9133
|
+
{
|
|
9134
|
+
ruleId: "INJ-IND-001",
|
|
9135
|
+
severity: "medium",
|
|
9136
|
+
category: "indirect-injection",
|
|
9137
|
+
description: "Instruction to influence future responses",
|
|
9138
|
+
pattern: /(?:when\s+the\s+user\s+asks|if\s+(?:the\s+user|someone|anyone)\s+asks)\s*,?\s*(?:say|respond|reply|answer|tell)/i
|
|
9139
|
+
},
|
|
9140
|
+
{
|
|
9141
|
+
ruleId: "INJ-IND-002",
|
|
9142
|
+
severity: "medium",
|
|
9143
|
+
category: "indirect-injection",
|
|
9144
|
+
description: "Directive to include content in responses",
|
|
9145
|
+
pattern: /(?:include|insert|add|embed|put)\s+(?:this|the\s+following)\s+(?:in|into|to)\s+(?:your|the)\s+(?:response|output|reply|answer)/i
|
|
9146
|
+
},
|
|
9147
|
+
{
|
|
9148
|
+
ruleId: "INJ-IND-003",
|
|
9149
|
+
severity: "medium",
|
|
9150
|
+
category: "indirect-injection",
|
|
9151
|
+
description: "Standing instruction to always respond a certain way",
|
|
9152
|
+
pattern: /always\s+(?:respond|reply|answer|say|output)\s+(?:with|that|by)/i
|
|
9153
|
+
}
|
|
9154
|
+
];
|
|
9155
|
+
var contextManipulationPatterns = [
|
|
9156
|
+
{
|
|
9157
|
+
ruleId: "INJ-CTX-001",
|
|
9158
|
+
severity: "medium",
|
|
9159
|
+
category: "context-manipulation",
|
|
9160
|
+
description: "Claim about system prompt content",
|
|
9161
|
+
pattern: /(?:the\s+)?(?:system\s+prompt|system\s+message|hidden\s+instructions?)\s+(?:says?|tells?|instructs?|contains?|is)/i
|
|
9162
|
+
},
|
|
9163
|
+
{
|
|
9164
|
+
ruleId: "INJ-CTX-002",
|
|
9165
|
+
severity: "medium",
|
|
9166
|
+
category: "context-manipulation",
|
|
9167
|
+
description: "Claim about AI instructions",
|
|
9168
|
+
pattern: /your\s+(?:instructions?|directives?|guidelines?|rules?)\s+(?:are|say|tell|state)/i
|
|
9169
|
+
},
|
|
9170
|
+
{
|
|
9171
|
+
ruleId: "INJ-CTX-003",
|
|
9172
|
+
severity: "medium",
|
|
9173
|
+
category: "context-manipulation",
|
|
9174
|
+
description: "Fake XML/HTML system or instruction tags",
|
|
9175
|
+
// Case-sensitive: only match lowercase tags to avoid false positives on
|
|
9176
|
+
// React components like <User>, <Context>, <Role> etc.
|
|
9177
|
+
pattern: /<\/?(?:system|instruction|prompt|role|context|tool_call|function_call|assistant|human|user)[^>]*>/
|
|
9178
|
+
},
|
|
9179
|
+
{
|
|
9180
|
+
ruleId: "INJ-CTX-004",
|
|
9181
|
+
severity: "medium",
|
|
9182
|
+
category: "context-manipulation",
|
|
9183
|
+
description: "Fake JSON role assignment mimicking chat format",
|
|
9184
|
+
pattern: /[{,]\s*"role"\s*:\s*"(?:system|assistant|function)"/i
|
|
9185
|
+
}
|
|
9186
|
+
];
|
|
9187
|
+
var socialEngineeringPatterns = [
|
|
9188
|
+
{
|
|
9189
|
+
ruleId: "INJ-SOC-001",
|
|
9190
|
+
severity: "medium",
|
|
9191
|
+
category: "social-engineering",
|
|
9192
|
+
description: "Urgency pressure to bypass checks",
|
|
9193
|
+
pattern: /(?:this\s+is\s+(?:very\s+)?urgent|this\s+is\s+(?:an?\s+)?emergency|do\s+(?:this|it)\s+(?:now|immediately))\b/i
|
|
9194
|
+
},
|
|
9195
|
+
{
|
|
9196
|
+
ruleId: "INJ-SOC-002",
|
|
9197
|
+
severity: "medium",
|
|
9198
|
+
category: "social-engineering",
|
|
9199
|
+
description: "False authority claim",
|
|
9200
|
+
pattern: /(?:the\s+)?(?:admin|administrator|manager|CEO|CTO|owner|supervisor)\s+(?:authorized|approved|said|told|confirmed|requested)/i
|
|
9201
|
+
},
|
|
9202
|
+
{
|
|
9203
|
+
ruleId: "INJ-SOC-003",
|
|
9204
|
+
severity: "medium",
|
|
9205
|
+
category: "social-engineering",
|
|
9206
|
+
description: "Testing pretext to bypass safety",
|
|
9207
|
+
pattern: /(?:for\s+testing\s+purposes?|this\s+is\s+(?:just\s+)?a\s+test|in\s+test\s+mode)\b/i
|
|
9208
|
+
}
|
|
9209
|
+
];
|
|
9210
|
+
var suspiciousPatterns = [
|
|
9211
|
+
{
|
|
9212
|
+
ruleId: "INJ-SUS-001",
|
|
9213
|
+
severity: "low",
|
|
9214
|
+
category: "suspicious-patterns",
|
|
9215
|
+
description: "Excessive consecutive whitespace (>10 chars) mid-line that may hide content",
|
|
9216
|
+
// Only match whitespace runs not at the start of a line (indentation is normal)
|
|
9217
|
+
pattern: /\S\s{11,}/
|
|
9218
|
+
},
|
|
9219
|
+
{
|
|
9220
|
+
ruleId: "INJ-SUS-002",
|
|
9221
|
+
severity: "low",
|
|
9222
|
+
category: "suspicious-patterns",
|
|
9223
|
+
description: "Repeated delimiters (>5) that may indicate obfuscation",
|
|
9224
|
+
pattern: /([|;,=\-_~`])\1{5,}/
|
|
9225
|
+
},
|
|
9226
|
+
{
|
|
9227
|
+
ruleId: "INJ-SUS-003",
|
|
9228
|
+
severity: "low",
|
|
9229
|
+
category: "suspicious-patterns",
|
|
9230
|
+
description: "Mathematical alphanumeric symbols used as Latin character substitutes",
|
|
9231
|
+
// Mathematical bold/italic/script Unicode ranges (U+1D400-U+1D7FF)
|
|
9232
|
+
pattern: /[\uD835][\uDC00-\uDFFF]/
|
|
9233
|
+
}
|
|
9234
|
+
];
|
|
9235
|
+
var ALL_PATTERNS = [
|
|
9236
|
+
...hiddenUnicodePatterns,
|
|
9237
|
+
...reRolingPatterns,
|
|
9238
|
+
...permissionEscalationPatterns,
|
|
9239
|
+
...encodedPayloadPatterns,
|
|
9240
|
+
...indirectInjectionPatterns,
|
|
9241
|
+
...contextManipulationPatterns,
|
|
9242
|
+
...socialEngineeringPatterns,
|
|
9243
|
+
...suspiciousPatterns
|
|
9244
|
+
];
|
|
9245
|
+
function scanForInjection(text) {
|
|
9246
|
+
const findings = [];
|
|
9247
|
+
const lines = text.split("\n");
|
|
9248
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
9249
|
+
const line = lines[lineIdx];
|
|
9250
|
+
for (const rule of ALL_PATTERNS) {
|
|
9251
|
+
if (rule.pattern.test(line)) {
|
|
9252
|
+
findings.push({
|
|
9253
|
+
severity: rule.severity,
|
|
9254
|
+
ruleId: rule.ruleId,
|
|
9255
|
+
match: line.trim(),
|
|
9256
|
+
line: lineIdx + 1
|
|
9257
|
+
});
|
|
9258
|
+
}
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
const severityOrder = {
|
|
9262
|
+
high: 0,
|
|
9263
|
+
medium: 1,
|
|
9264
|
+
low: 2
|
|
9265
|
+
};
|
|
9266
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
9267
|
+
return findings;
|
|
9268
|
+
}
|
|
9269
|
+
function getInjectionPatterns() {
|
|
9270
|
+
return ALL_PATTERNS;
|
|
9271
|
+
}
|
|
9272
|
+
var DESTRUCTIVE_BASH = [
|
|
9273
|
+
/\bgit\s+push\b/,
|
|
9274
|
+
/\bgit\s+commit\b/,
|
|
9275
|
+
/\brm\s+-rf?\b/,
|
|
9276
|
+
/\brm\s+-r\b/
|
|
9277
|
+
];
|
|
9278
|
+
var TAINT_DURATION_MS = 30 * 60 * 1e3;
|
|
9279
|
+
var DEFAULT_SESSION_ID = "default";
|
|
9280
|
+
function getTaintFilePath(projectRoot, sessionId) {
|
|
9281
|
+
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9282
|
+
return join21(projectRoot, ".harness", `session-taint-${id}.json`);
|
|
9283
|
+
}
|
|
9284
|
+
function readTaint(projectRoot, sessionId) {
|
|
9285
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9286
|
+
let content;
|
|
9287
|
+
try {
|
|
9288
|
+
content = readFileSync142(filePath, "utf8");
|
|
9289
|
+
} catch {
|
|
9290
|
+
return null;
|
|
9291
|
+
}
|
|
9292
|
+
let state;
|
|
9293
|
+
try {
|
|
9294
|
+
state = JSON.parse(content);
|
|
9295
|
+
} catch {
|
|
9296
|
+
try {
|
|
9297
|
+
unlinkSync(filePath);
|
|
9298
|
+
} catch {
|
|
9299
|
+
}
|
|
9300
|
+
return null;
|
|
9301
|
+
}
|
|
9302
|
+
if (!state.sessionId || !state.taintedAt || !state.expiresAt || !state.findings) {
|
|
9303
|
+
try {
|
|
9304
|
+
unlinkSync(filePath);
|
|
9305
|
+
} catch {
|
|
9306
|
+
}
|
|
9307
|
+
return null;
|
|
9308
|
+
}
|
|
9309
|
+
return state;
|
|
9310
|
+
}
|
|
9311
|
+
function checkTaint(projectRoot, sessionId) {
|
|
9312
|
+
const state = readTaint(projectRoot, sessionId);
|
|
9313
|
+
if (!state) {
|
|
9314
|
+
return { tainted: false, expired: false, state: null };
|
|
9315
|
+
}
|
|
9316
|
+
const now = /* @__PURE__ */ new Date();
|
|
9317
|
+
const expiresAt = new Date(state.expiresAt);
|
|
9318
|
+
if (now >= expiresAt) {
|
|
9319
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9320
|
+
try {
|
|
9321
|
+
unlinkSync(filePath);
|
|
9322
|
+
} catch {
|
|
9323
|
+
}
|
|
9324
|
+
return { tainted: false, expired: true, state };
|
|
9325
|
+
}
|
|
9326
|
+
return { tainted: true, expired: false, state };
|
|
9327
|
+
}
|
|
9328
|
+
function writeTaint(projectRoot, sessionId, reason, findings, source) {
|
|
9329
|
+
const id = sessionId || DEFAULT_SESSION_ID;
|
|
9330
|
+
const filePath = getTaintFilePath(projectRoot, id);
|
|
9331
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9332
|
+
const dir = dirname8(filePath);
|
|
9333
|
+
mkdirSync11(dir, { recursive: true });
|
|
9334
|
+
const existing = readTaint(projectRoot, id);
|
|
9335
|
+
const maxSeverity = findings.some((f) => f.severity === "high") ? "high" : "medium";
|
|
9336
|
+
const taintFindings = findings.map((f) => ({
|
|
9337
|
+
ruleId: f.ruleId,
|
|
9338
|
+
severity: f.severity,
|
|
9339
|
+
match: f.match,
|
|
9340
|
+
source,
|
|
9341
|
+
detectedAt: now
|
|
9342
|
+
}));
|
|
9343
|
+
const state = {
|
|
9344
|
+
sessionId: id,
|
|
9345
|
+
taintedAt: existing?.taintedAt || now,
|
|
9346
|
+
expiresAt: new Date(Date.now() + TAINT_DURATION_MS).toISOString(),
|
|
9347
|
+
reason,
|
|
9348
|
+
severity: existing?.severity === "high" || maxSeverity === "high" ? "high" : "medium",
|
|
9349
|
+
findings: [...existing?.findings || [], ...taintFindings]
|
|
9350
|
+
};
|
|
9351
|
+
writeFileSync11(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
9352
|
+
return state;
|
|
9353
|
+
}
|
|
9354
|
+
function clearTaint(projectRoot, sessionId) {
|
|
9355
|
+
if (sessionId) {
|
|
9356
|
+
const filePath = getTaintFilePath(projectRoot, sessionId);
|
|
9357
|
+
try {
|
|
9358
|
+
unlinkSync(filePath);
|
|
9359
|
+
return 1;
|
|
9360
|
+
} catch {
|
|
9361
|
+
return 0;
|
|
9362
|
+
}
|
|
9363
|
+
}
|
|
9364
|
+
const harnessDir = join21(projectRoot, ".harness");
|
|
9365
|
+
let count = 0;
|
|
9366
|
+
try {
|
|
9367
|
+
const files = readdirSync3(harnessDir);
|
|
9368
|
+
for (const file of files) {
|
|
9369
|
+
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9370
|
+
try {
|
|
9371
|
+
unlinkSync(join21(harnessDir, file));
|
|
9372
|
+
count++;
|
|
9373
|
+
} catch {
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
} catch {
|
|
9378
|
+
}
|
|
9379
|
+
return count;
|
|
9380
|
+
}
|
|
9381
|
+
function listTaintedSessions(projectRoot) {
|
|
9382
|
+
const harnessDir = join21(projectRoot, ".harness");
|
|
9383
|
+
const sessions = [];
|
|
9384
|
+
try {
|
|
9385
|
+
const files = readdirSync3(harnessDir);
|
|
9386
|
+
for (const file of files) {
|
|
9387
|
+
if (file.startsWith("session-taint-") && file.endsWith(".json")) {
|
|
9388
|
+
const sessionId = file.replace("session-taint-", "").replace(".json", "");
|
|
9389
|
+
const result = checkTaint(projectRoot, sessionId);
|
|
9390
|
+
if (result.tainted) {
|
|
9391
|
+
sessions.push(sessionId);
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
}
|
|
9395
|
+
} catch {
|
|
9396
|
+
}
|
|
9397
|
+
return sessions;
|
|
9398
|
+
}
|
|
9399
|
+
function mapSecuritySeverity(severity) {
|
|
9400
|
+
if (severity === "error") return "high";
|
|
9401
|
+
if (severity === "warning") return "medium";
|
|
9402
|
+
return "low";
|
|
9403
|
+
}
|
|
9404
|
+
function computeOverallSeverity(findings) {
|
|
9405
|
+
if (findings.length === 0) return "clean";
|
|
9406
|
+
if (findings.some((f) => f.severity === "high")) return "high";
|
|
9407
|
+
if (findings.some((f) => f.severity === "medium")) return "medium";
|
|
9408
|
+
return "low";
|
|
9409
|
+
}
|
|
9410
|
+
function computeScanExitCode(results) {
|
|
9411
|
+
for (const r of results) {
|
|
9412
|
+
if (r.overallSeverity === "high") return 2;
|
|
8148
9413
|
}
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
9414
|
+
for (const r of results) {
|
|
9415
|
+
if (r.overallSeverity === "medium") return 1;
|
|
9416
|
+
}
|
|
9417
|
+
return 0;
|
|
9418
|
+
}
|
|
9419
|
+
function mapInjectionFindings(injectionFindings) {
|
|
9420
|
+
return injectionFindings.map((f) => ({
|
|
9421
|
+
ruleId: f.ruleId,
|
|
9422
|
+
severity: f.severity,
|
|
9423
|
+
message: `Injection pattern detected: ${f.ruleId}`,
|
|
9424
|
+
match: f.match,
|
|
9425
|
+
...f.line !== void 0 ? { line: f.line } : {}
|
|
9426
|
+
}));
|
|
9427
|
+
}
|
|
9428
|
+
function isDuplicateFinding(existing, secFinding) {
|
|
9429
|
+
return existing.some(
|
|
9430
|
+
(e) => e.line === secFinding.line && e.match === secFinding.match.trim() && e.ruleId.split("-")[0] === secFinding.ruleId.split("-")[0]
|
|
9431
|
+
);
|
|
9432
|
+
}
|
|
9433
|
+
function mapSecurityFindings(secFindings, existing) {
|
|
9434
|
+
const result = [];
|
|
9435
|
+
for (const f of secFindings) {
|
|
9436
|
+
if (!isDuplicateFinding(existing, f)) {
|
|
9437
|
+
result.push({
|
|
9438
|
+
ruleId: f.ruleId,
|
|
9439
|
+
severity: mapSecuritySeverity(f.severity),
|
|
9440
|
+
message: f.message,
|
|
9441
|
+
match: f.match,
|
|
9442
|
+
...f.line !== void 0 ? { line: f.line } : {}
|
|
9443
|
+
});
|
|
8159
9444
|
}
|
|
8160
|
-
return {
|
|
8161
|
-
findings: allFindings,
|
|
8162
|
-
scannedFiles: scannedCount,
|
|
8163
|
-
rulesApplied: this.activeRules.length,
|
|
8164
|
-
externalToolsUsed: [],
|
|
8165
|
-
coverage: "baseline"
|
|
8166
|
-
};
|
|
8167
9445
|
}
|
|
8168
|
-
|
|
9446
|
+
return result;
|
|
9447
|
+
}
|
|
8169
9448
|
var ALL_CHECKS = [
|
|
8170
9449
|
"validate",
|
|
8171
9450
|
"deps",
|
|
@@ -8178,7 +9457,7 @@ var ALL_CHECKS = [
|
|
|
8178
9457
|
];
|
|
8179
9458
|
async function runValidateCheck(projectRoot, config) {
|
|
8180
9459
|
const issues = [];
|
|
8181
|
-
const agentsPath =
|
|
9460
|
+
const agentsPath = path15.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8182
9461
|
const result = await validateAgentsMap(agentsPath);
|
|
8183
9462
|
if (!result.ok) {
|
|
8184
9463
|
issues.push({ severity: "error", message: result.error.message });
|
|
@@ -8235,7 +9514,7 @@ async function runDepsCheck(projectRoot, config) {
|
|
|
8235
9514
|
}
|
|
8236
9515
|
async function runDocsCheck(projectRoot, config) {
|
|
8237
9516
|
const issues = [];
|
|
8238
|
-
const docsDir =
|
|
9517
|
+
const docsDir = path15.join(projectRoot, config.docsDir ?? "docs");
|
|
8239
9518
|
const entropyConfig = config.entropy || {};
|
|
8240
9519
|
const result = await checkDocCoverage("project", {
|
|
8241
9520
|
docsDir,
|
|
@@ -8347,7 +9626,7 @@ async function runPerfCheck(projectRoot, config) {
|
|
|
8347
9626
|
if (perfReport.complexity) {
|
|
8348
9627
|
for (const v of perfReport.complexity.violations) {
|
|
8349
9628
|
issues.push({
|
|
8350
|
-
severity:
|
|
9629
|
+
severity: "warning",
|
|
8351
9630
|
message: `[Tier ${v.tier}] ${v.metric}: ${v.function} in ${v.file} (${v.value} > ${v.threshold})`,
|
|
8352
9631
|
file: v.file,
|
|
8353
9632
|
line: v.line
|
|
@@ -8522,7 +9801,7 @@ async function runMechanicalChecks(options) {
|
|
|
8522
9801
|
};
|
|
8523
9802
|
if (!skip.includes("validate")) {
|
|
8524
9803
|
try {
|
|
8525
|
-
const agentsPath =
|
|
9804
|
+
const agentsPath = path16.join(projectRoot, config.agentsMapPath ?? "AGENTS.md");
|
|
8526
9805
|
const result = await validateAgentsMap(agentsPath);
|
|
8527
9806
|
if (!result.ok) {
|
|
8528
9807
|
statuses.validate = "fail";
|
|
@@ -8559,7 +9838,7 @@ async function runMechanicalChecks(options) {
|
|
|
8559
9838
|
statuses.validate = "fail";
|
|
8560
9839
|
findings.push({
|
|
8561
9840
|
tool: "validate",
|
|
8562
|
-
file:
|
|
9841
|
+
file: path16.join(projectRoot, "AGENTS.md"),
|
|
8563
9842
|
message: err instanceof Error ? err.message : String(err),
|
|
8564
9843
|
severity: "error"
|
|
8565
9844
|
});
|
|
@@ -8623,7 +9902,7 @@ async function runMechanicalChecks(options) {
|
|
|
8623
9902
|
(async () => {
|
|
8624
9903
|
const localFindings = [];
|
|
8625
9904
|
try {
|
|
8626
|
-
const docsDir =
|
|
9905
|
+
const docsDir = path16.join(projectRoot, config.docsDir ?? "docs");
|
|
8627
9906
|
const result = await checkDocCoverage("project", { docsDir });
|
|
8628
9907
|
if (!result.ok) {
|
|
8629
9908
|
statuses["check-docs"] = "warn";
|
|
@@ -8650,7 +9929,7 @@ async function runMechanicalChecks(options) {
|
|
|
8650
9929
|
statuses["check-docs"] = "warn";
|
|
8651
9930
|
localFindings.push({
|
|
8652
9931
|
tool: "check-docs",
|
|
8653
|
-
file:
|
|
9932
|
+
file: path16.join(projectRoot, "docs"),
|
|
8654
9933
|
message: err instanceof Error ? err.message : String(err),
|
|
8655
9934
|
severity: "warning"
|
|
8656
9935
|
});
|
|
@@ -8799,18 +10078,18 @@ function computeContextBudget(diffLines) {
|
|
|
8799
10078
|
return diffLines;
|
|
8800
10079
|
}
|
|
8801
10080
|
function isWithinProject(absPath, projectRoot) {
|
|
8802
|
-
const resolvedRoot =
|
|
8803
|
-
const resolvedPath =
|
|
8804
|
-
return resolvedPath.startsWith(resolvedRoot) || resolvedPath ===
|
|
10081
|
+
const resolvedRoot = path17.resolve(projectRoot) + path17.sep;
|
|
10082
|
+
const resolvedPath = path17.resolve(absPath);
|
|
10083
|
+
return resolvedPath.startsWith(resolvedRoot) || resolvedPath === path17.resolve(projectRoot);
|
|
8805
10084
|
}
|
|
8806
10085
|
async function readContextFile(projectRoot, filePath, reason) {
|
|
8807
|
-
const absPath =
|
|
10086
|
+
const absPath = path17.isAbsolute(filePath) ? filePath : path17.join(projectRoot, filePath);
|
|
8808
10087
|
if (!isWithinProject(absPath, projectRoot)) return null;
|
|
8809
10088
|
const result = await readFileContent(absPath);
|
|
8810
10089
|
if (!result.ok) return null;
|
|
8811
10090
|
const content = result.value;
|
|
8812
10091
|
const lines = content.split("\n").length;
|
|
8813
|
-
const relPath =
|
|
10092
|
+
const relPath = path17.isAbsolute(filePath) ? relativePosix(projectRoot, filePath) : filePath;
|
|
8814
10093
|
return { path: relPath, content, reason, lines };
|
|
8815
10094
|
}
|
|
8816
10095
|
function extractImportSources2(content) {
|
|
@@ -8825,18 +10104,18 @@ function extractImportSources2(content) {
|
|
|
8825
10104
|
}
|
|
8826
10105
|
async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
8827
10106
|
if (!importSource.startsWith(".")) return null;
|
|
8828
|
-
const fromDir =
|
|
8829
|
-
const basePath =
|
|
10107
|
+
const fromDir = path17.dirname(path17.join(projectRoot, fromFile));
|
|
10108
|
+
const basePath = path17.resolve(fromDir, importSource);
|
|
8830
10109
|
if (!isWithinProject(basePath, projectRoot)) return null;
|
|
8831
10110
|
const relBase = relativePosix(projectRoot, basePath);
|
|
8832
10111
|
const candidates = [
|
|
8833
10112
|
relBase + ".ts",
|
|
8834
10113
|
relBase + ".tsx",
|
|
8835
10114
|
relBase + ".mts",
|
|
8836
|
-
|
|
10115
|
+
path17.join(relBase, "index.ts")
|
|
8837
10116
|
];
|
|
8838
10117
|
for (const candidate of candidates) {
|
|
8839
|
-
const absCandidate =
|
|
10118
|
+
const absCandidate = path17.join(projectRoot, candidate);
|
|
8840
10119
|
if (await fileExists(absCandidate)) {
|
|
8841
10120
|
return candidate;
|
|
8842
10121
|
}
|
|
@@ -8844,7 +10123,7 @@ async function resolveImportPath2(projectRoot, fromFile, importSource) {
|
|
|
8844
10123
|
return null;
|
|
8845
10124
|
}
|
|
8846
10125
|
async function findTestFiles(projectRoot, sourceFile) {
|
|
8847
|
-
const baseName =
|
|
10126
|
+
const baseName = path17.basename(sourceFile, path17.extname(sourceFile));
|
|
8848
10127
|
const pattern = `**/${baseName}.{test,spec}.{ts,tsx,mts}`;
|
|
8849
10128
|
const results = await findFiles(pattern, projectRoot);
|
|
8850
10129
|
return results.map((f) => relativePosix(projectRoot, f));
|
|
@@ -9657,7 +10936,7 @@ function normalizePath(filePath, projectRoot) {
|
|
|
9657
10936
|
let normalized = filePath;
|
|
9658
10937
|
normalized = normalized.replace(/\\/g, "/");
|
|
9659
10938
|
const normalizedRoot = projectRoot.replace(/\\/g, "/");
|
|
9660
|
-
if (
|
|
10939
|
+
if (path18.isAbsolute(normalized)) {
|
|
9661
10940
|
const root = normalizedRoot.endsWith("/") ? normalizedRoot : normalizedRoot + "/";
|
|
9662
10941
|
if (normalized.startsWith(root)) {
|
|
9663
10942
|
normalized = normalized.slice(root.length);
|
|
@@ -9682,12 +10961,12 @@ function followImportChain(fromFile, fileContents, maxDepth = 2) {
|
|
|
9682
10961
|
while ((match = importRegex.exec(content)) !== null) {
|
|
9683
10962
|
const importPath = match[1];
|
|
9684
10963
|
if (!importPath.startsWith(".")) continue;
|
|
9685
|
-
const dir =
|
|
9686
|
-
let resolved =
|
|
10964
|
+
const dir = path18.dirname(current.file);
|
|
10965
|
+
let resolved = path18.join(dir, importPath).replace(/\\/g, "/");
|
|
9687
10966
|
if (!resolved.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
9688
10967
|
resolved += ".ts";
|
|
9689
10968
|
}
|
|
9690
|
-
resolved =
|
|
10969
|
+
resolved = path18.normalize(resolved).replace(/\\/g, "/");
|
|
9691
10970
|
if (!visited.has(resolved) && current.depth + 1 <= maxDepth) {
|
|
9692
10971
|
queue.push({ file: resolved, depth: current.depth + 1 });
|
|
9693
10972
|
}
|
|
@@ -9704,7 +10983,7 @@ async function validateFindings(options) {
|
|
|
9704
10983
|
if (exclusionSet.isExcluded(normalizedFile, finding.lineRange) || exclusionSet.isExcluded(finding.file, finding.lineRange)) {
|
|
9705
10984
|
continue;
|
|
9706
10985
|
}
|
|
9707
|
-
const absoluteFile =
|
|
10986
|
+
const absoluteFile = path18.isAbsolute(finding.file) ? finding.file : path18.join(projectRoot, finding.file).replace(/\\/g, "/");
|
|
9708
10987
|
if (exclusionSet.isExcluded(absoluteFile, finding.lineRange)) {
|
|
9709
10988
|
continue;
|
|
9710
10989
|
}
|
|
@@ -10313,7 +11592,7 @@ function parseRoadmap(markdown) {
|
|
|
10313
11592
|
if (!fmMatch) {
|
|
10314
11593
|
return Err(new Error("Missing or malformed YAML frontmatter"));
|
|
10315
11594
|
}
|
|
10316
|
-
const fmResult =
|
|
11595
|
+
const fmResult = parseFrontmatter2(fmMatch[1]);
|
|
10317
11596
|
if (!fmResult.ok) return fmResult;
|
|
10318
11597
|
const body = markdown.slice(fmMatch[0].length);
|
|
10319
11598
|
const milestonesResult = parseMilestones(body);
|
|
@@ -10323,7 +11602,7 @@ function parseRoadmap(markdown) {
|
|
|
10323
11602
|
milestones: milestonesResult.value
|
|
10324
11603
|
});
|
|
10325
11604
|
}
|
|
10326
|
-
function
|
|
11605
|
+
function parseFrontmatter2(raw) {
|
|
10327
11606
|
const lines = raw.split("\n");
|
|
10328
11607
|
const map = /* @__PURE__ */ new Map();
|
|
10329
11608
|
for (const line of lines) {
|
|
@@ -10498,10 +11777,10 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10498
11777
|
const featuresWithPlans = allFeatures.filter((f) => f.plans.length > 0);
|
|
10499
11778
|
const useRootState = featuresWithPlans.length <= 1;
|
|
10500
11779
|
if (useRootState) {
|
|
10501
|
-
const rootStatePath =
|
|
10502
|
-
if (
|
|
11780
|
+
const rootStatePath = path19.join(projectPath, ".harness", "state.json");
|
|
11781
|
+
if (fs19.existsSync(rootStatePath)) {
|
|
10503
11782
|
try {
|
|
10504
|
-
const raw =
|
|
11783
|
+
const raw = fs19.readFileSync(rootStatePath, "utf-8");
|
|
10505
11784
|
const state = JSON.parse(raw);
|
|
10506
11785
|
if (state.progress) {
|
|
10507
11786
|
for (const status of Object.values(state.progress)) {
|
|
@@ -10512,16 +11791,16 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10512
11791
|
}
|
|
10513
11792
|
}
|
|
10514
11793
|
}
|
|
10515
|
-
const sessionsDir =
|
|
10516
|
-
if (
|
|
11794
|
+
const sessionsDir = path19.join(projectPath, ".harness", "sessions");
|
|
11795
|
+
if (fs19.existsSync(sessionsDir)) {
|
|
10517
11796
|
try {
|
|
10518
|
-
const sessionDirs =
|
|
11797
|
+
const sessionDirs = fs19.readdirSync(sessionsDir, { withFileTypes: true });
|
|
10519
11798
|
for (const entry of sessionDirs) {
|
|
10520
11799
|
if (!entry.isDirectory()) continue;
|
|
10521
|
-
const autopilotPath =
|
|
10522
|
-
if (!
|
|
11800
|
+
const autopilotPath = path19.join(sessionsDir, entry.name, "autopilot-state.json");
|
|
11801
|
+
if (!fs19.existsSync(autopilotPath)) continue;
|
|
10523
11802
|
try {
|
|
10524
|
-
const raw =
|
|
11803
|
+
const raw = fs19.readFileSync(autopilotPath, "utf-8");
|
|
10525
11804
|
const autopilot = JSON.parse(raw);
|
|
10526
11805
|
if (!autopilot.phases) continue;
|
|
10527
11806
|
const linkedPhases = autopilot.phases.filter(
|
|
@@ -10551,17 +11830,26 @@ function inferStatus(feature, projectPath, allFeatures) {
|
|
|
10551
11830
|
if (anyStarted) return "in-progress";
|
|
10552
11831
|
return null;
|
|
10553
11832
|
}
|
|
11833
|
+
var STATUS_RANK = {
|
|
11834
|
+
backlog: 0,
|
|
11835
|
+
planned: 1,
|
|
11836
|
+
blocked: 1,
|
|
11837
|
+
// lateral to planned — sync can move to/from blocked freely
|
|
11838
|
+
"in-progress": 2,
|
|
11839
|
+
done: 3
|
|
11840
|
+
};
|
|
11841
|
+
function isRegression(from, to) {
|
|
11842
|
+
return STATUS_RANK[to] < STATUS_RANK[from];
|
|
11843
|
+
}
|
|
10554
11844
|
function syncRoadmap(options) {
|
|
10555
11845
|
const { projectPath, roadmap, forceSync } = options;
|
|
10556
|
-
const isManuallyEdited = new Date(roadmap.frontmatter.lastManualEdit) > new Date(roadmap.frontmatter.lastSynced);
|
|
10557
|
-
const skipOverride = isManuallyEdited && !forceSync;
|
|
10558
11846
|
const allFeatures = roadmap.milestones.flatMap((m) => m.features);
|
|
10559
11847
|
const changes = [];
|
|
10560
11848
|
for (const feature of allFeatures) {
|
|
10561
|
-
if (skipOverride) continue;
|
|
10562
11849
|
const inferred = inferStatus(feature, projectPath, allFeatures);
|
|
10563
11850
|
if (inferred === null) continue;
|
|
10564
11851
|
if (inferred === feature.status) continue;
|
|
11852
|
+
if (!forceSync && isRegression(feature.status, inferred)) continue;
|
|
10565
11853
|
changes.push({
|
|
10566
11854
|
feature: feature.name,
|
|
10567
11855
|
from: feature.status,
|
|
@@ -10570,28 +11858,40 @@ function syncRoadmap(options) {
|
|
|
10570
11858
|
}
|
|
10571
11859
|
return Ok(changes);
|
|
10572
11860
|
}
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
11861
|
+
function applySyncChanges(roadmap, changes) {
|
|
11862
|
+
for (const change of changes) {
|
|
11863
|
+
for (const m of roadmap.milestones) {
|
|
11864
|
+
const feature = m.features.find((f) => f.name.toLowerCase() === change.feature.toLowerCase());
|
|
11865
|
+
if (feature) {
|
|
11866
|
+
feature.status = change.to;
|
|
11867
|
+
break;
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
roadmap.frontmatter.lastSynced = (/* @__PURE__ */ new Date()).toISOString();
|
|
11872
|
+
}
|
|
11873
|
+
var InteractionTypeSchema = z7.enum(["question", "confirmation", "transition"]);
|
|
11874
|
+
var QuestionSchema = z7.object({
|
|
11875
|
+
text: z7.string(),
|
|
11876
|
+
options: z7.array(z7.string()).optional(),
|
|
11877
|
+
default: z7.string().optional()
|
|
10578
11878
|
});
|
|
10579
|
-
var ConfirmationSchema =
|
|
10580
|
-
text:
|
|
10581
|
-
context:
|
|
11879
|
+
var ConfirmationSchema = z7.object({
|
|
11880
|
+
text: z7.string(),
|
|
11881
|
+
context: z7.string()
|
|
10582
11882
|
});
|
|
10583
|
-
var TransitionSchema =
|
|
10584
|
-
completedPhase:
|
|
10585
|
-
suggestedNext:
|
|
10586
|
-
reason:
|
|
10587
|
-
artifacts:
|
|
10588
|
-
requiresConfirmation:
|
|
10589
|
-
summary:
|
|
11883
|
+
var TransitionSchema = z7.object({
|
|
11884
|
+
completedPhase: z7.string(),
|
|
11885
|
+
suggestedNext: z7.string(),
|
|
11886
|
+
reason: z7.string(),
|
|
11887
|
+
artifacts: z7.array(z7.string()),
|
|
11888
|
+
requiresConfirmation: z7.boolean(),
|
|
11889
|
+
summary: z7.string()
|
|
10590
11890
|
});
|
|
10591
|
-
var EmitInteractionInputSchema =
|
|
10592
|
-
path:
|
|
11891
|
+
var EmitInteractionInputSchema = z7.object({
|
|
11892
|
+
path: z7.string(),
|
|
10593
11893
|
type: InteractionTypeSchema,
|
|
10594
|
-
stream:
|
|
11894
|
+
stream: z7.string().optional(),
|
|
10595
11895
|
question: QuestionSchema.optional(),
|
|
10596
11896
|
confirmation: ConfirmationSchema.optional(),
|
|
10597
11897
|
transition: TransitionSchema.optional()
|
|
@@ -10601,10 +11901,10 @@ var ProjectScanner = class {
|
|
|
10601
11901
|
this.rootDir = rootDir;
|
|
10602
11902
|
}
|
|
10603
11903
|
async scan() {
|
|
10604
|
-
let projectName =
|
|
11904
|
+
let projectName = path20.basename(this.rootDir);
|
|
10605
11905
|
try {
|
|
10606
|
-
const pkgPath =
|
|
10607
|
-
const pkgRaw = await
|
|
11906
|
+
const pkgPath = path20.join(this.rootDir, "package.json");
|
|
11907
|
+
const pkgRaw = await fs20.readFile(pkgPath, "utf-8");
|
|
10608
11908
|
const pkg = JSON.parse(pkgRaw);
|
|
10609
11909
|
if (pkg.name) projectName = pkg.name;
|
|
10610
11910
|
} catch {
|
|
@@ -10717,13 +12017,13 @@ var BlueprintGenerator = class {
|
|
|
10717
12017
|
styles: STYLES,
|
|
10718
12018
|
scripts: SCRIPTS
|
|
10719
12019
|
});
|
|
10720
|
-
await
|
|
10721
|
-
await
|
|
12020
|
+
await fs21.mkdir(options.outputDir, { recursive: true });
|
|
12021
|
+
await fs21.writeFile(path21.join(options.outputDir, "index.html"), html);
|
|
10722
12022
|
}
|
|
10723
12023
|
};
|
|
10724
12024
|
function getStatePath() {
|
|
10725
12025
|
const home = process.env["HOME"] || os.homedir();
|
|
10726
|
-
return
|
|
12026
|
+
return path22.join(home, ".harness", "update-check.json");
|
|
10727
12027
|
}
|
|
10728
12028
|
function isUpdateCheckEnabled(configInterval) {
|
|
10729
12029
|
if (process.env["HARNESS_NO_UPDATE_CHECK"] === "1") return false;
|
|
@@ -10736,7 +12036,7 @@ function shouldRunCheck(state, intervalMs) {
|
|
|
10736
12036
|
}
|
|
10737
12037
|
function readCheckState() {
|
|
10738
12038
|
try {
|
|
10739
|
-
const raw =
|
|
12039
|
+
const raw = fs22.readFileSync(getStatePath(), "utf-8");
|
|
10740
12040
|
const parsed = JSON.parse(raw);
|
|
10741
12041
|
if (typeof parsed === "object" && parsed !== null && "lastCheckTime" in parsed && typeof parsed.lastCheckTime === "number" && "currentVersion" in parsed && typeof parsed.currentVersion === "string") {
|
|
10742
12042
|
const state = parsed;
|
|
@@ -10753,7 +12053,7 @@ function readCheckState() {
|
|
|
10753
12053
|
}
|
|
10754
12054
|
function spawnBackgroundCheck(currentVersion) {
|
|
10755
12055
|
const statePath = getStatePath();
|
|
10756
|
-
const stateDir =
|
|
12056
|
+
const stateDir = path22.dirname(statePath);
|
|
10757
12057
|
const script = `
|
|
10758
12058
|
const { execSync } = require('child_process');
|
|
10759
12059
|
const fs = require('fs');
|
|
@@ -10805,7 +12105,858 @@ function getUpdateNotification(currentVersion) {
|
|
|
10805
12105
|
return `Update available: v${currentVersion} -> v${state.latestVersion}
|
|
10806
12106
|
Run "harness update" to upgrade.`;
|
|
10807
12107
|
}
|
|
10808
|
-
var
|
|
12108
|
+
var EXTENSION_MAP = {
|
|
12109
|
+
".ts": "typescript",
|
|
12110
|
+
".tsx": "typescript",
|
|
12111
|
+
".mts": "typescript",
|
|
12112
|
+
".cts": "typescript",
|
|
12113
|
+
".js": "javascript",
|
|
12114
|
+
".jsx": "javascript",
|
|
12115
|
+
".mjs": "javascript",
|
|
12116
|
+
".cjs": "javascript",
|
|
12117
|
+
".py": "python"
|
|
12118
|
+
};
|
|
12119
|
+
function detectLanguage(filePath) {
|
|
12120
|
+
const ext = filePath.slice(filePath.lastIndexOf("."));
|
|
12121
|
+
return EXTENSION_MAP[ext] ?? null;
|
|
12122
|
+
}
|
|
12123
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
12124
|
+
var initialized = false;
|
|
12125
|
+
var GRAMMAR_MAP = {
|
|
12126
|
+
typescript: "tree-sitter-typescript",
|
|
12127
|
+
javascript: "tree-sitter-javascript",
|
|
12128
|
+
python: "tree-sitter-python"
|
|
12129
|
+
};
|
|
12130
|
+
async function ensureInit() {
|
|
12131
|
+
if (!initialized) {
|
|
12132
|
+
await Parser.init();
|
|
12133
|
+
initialized = true;
|
|
12134
|
+
}
|
|
12135
|
+
}
|
|
12136
|
+
async function resolveWasmPath(grammarName) {
|
|
12137
|
+
const { createRequire } = await import("module");
|
|
12138
|
+
const require2 = createRequire(import.meta.url ?? __filename);
|
|
12139
|
+
const pkgPath = require2.resolve("tree-sitter-wasms/package.json");
|
|
12140
|
+
const path26 = await import("path");
|
|
12141
|
+
const pkgDir = path26.dirname(pkgPath);
|
|
12142
|
+
return path26.join(pkgDir, "out", `${grammarName}.wasm`);
|
|
12143
|
+
}
|
|
12144
|
+
async function loadLanguage(lang) {
|
|
12145
|
+
const grammarName = GRAMMAR_MAP[lang];
|
|
12146
|
+
const wasmPath = await resolveWasmPath(grammarName);
|
|
12147
|
+
return Parser.Language.load(wasmPath);
|
|
12148
|
+
}
|
|
12149
|
+
async function getParser(lang) {
|
|
12150
|
+
const cached = parserCache.get(lang);
|
|
12151
|
+
if (cached) return cached;
|
|
12152
|
+
await ensureInit();
|
|
12153
|
+
const parser = new Parser();
|
|
12154
|
+
const language = await loadLanguage(lang);
|
|
12155
|
+
parser.setLanguage(language);
|
|
12156
|
+
parserCache.set(lang, parser);
|
|
12157
|
+
return parser;
|
|
12158
|
+
}
|
|
12159
|
+
async function parseFile(filePath) {
|
|
12160
|
+
const lang = detectLanguage(filePath);
|
|
12161
|
+
if (!lang) {
|
|
12162
|
+
return Err({
|
|
12163
|
+
code: "UNSUPPORTED_LANGUAGE",
|
|
12164
|
+
message: `Unsupported file extension: ${filePath}`
|
|
12165
|
+
});
|
|
12166
|
+
}
|
|
12167
|
+
const contentResult = await readFileContent(filePath);
|
|
12168
|
+
if (!contentResult.ok) {
|
|
12169
|
+
return Err({
|
|
12170
|
+
code: "FILE_NOT_FOUND",
|
|
12171
|
+
message: `Cannot read file: ${filePath}`
|
|
12172
|
+
});
|
|
12173
|
+
}
|
|
12174
|
+
try {
|
|
12175
|
+
const parser = await getParser(lang);
|
|
12176
|
+
const tree = parser.parse(contentResult.value);
|
|
12177
|
+
return Ok({ tree, language: lang, source: contentResult.value, filePath });
|
|
12178
|
+
} catch (e) {
|
|
12179
|
+
return Err({
|
|
12180
|
+
code: "PARSE_FAILED",
|
|
12181
|
+
message: `Tree-sitter parse failed for ${filePath}: ${e.message}`
|
|
12182
|
+
});
|
|
12183
|
+
}
|
|
12184
|
+
}
|
|
12185
|
+
function resetParserCache() {
|
|
12186
|
+
parserCache.clear();
|
|
12187
|
+
initialized = false;
|
|
12188
|
+
}
|
|
12189
|
+
var TOP_LEVEL_TYPES = {
|
|
12190
|
+
typescript: {
|
|
12191
|
+
function_declaration: "function",
|
|
12192
|
+
class_declaration: "class",
|
|
12193
|
+
interface_declaration: "interface",
|
|
12194
|
+
type_alias_declaration: "type",
|
|
12195
|
+
lexical_declaration: "variable",
|
|
12196
|
+
variable_declaration: "variable",
|
|
12197
|
+
export_statement: "export",
|
|
12198
|
+
import_statement: "import",
|
|
12199
|
+
enum_declaration: "type"
|
|
12200
|
+
},
|
|
12201
|
+
javascript: {
|
|
12202
|
+
function_declaration: "function",
|
|
12203
|
+
class_declaration: "class",
|
|
12204
|
+
lexical_declaration: "variable",
|
|
12205
|
+
variable_declaration: "variable",
|
|
12206
|
+
export_statement: "export",
|
|
12207
|
+
import_statement: "import"
|
|
12208
|
+
},
|
|
12209
|
+
python: {
|
|
12210
|
+
function_definition: "function",
|
|
12211
|
+
class_definition: "class",
|
|
12212
|
+
assignment: "variable",
|
|
12213
|
+
import_statement: "import",
|
|
12214
|
+
import_from_statement: "import"
|
|
12215
|
+
}
|
|
12216
|
+
};
|
|
12217
|
+
var METHOD_TYPES = {
|
|
12218
|
+
typescript: ["method_definition", "public_field_definition"],
|
|
12219
|
+
javascript: ["method_definition"],
|
|
12220
|
+
python: ["function_definition"]
|
|
12221
|
+
};
|
|
12222
|
+
var IDENTIFIER_TYPES = /* @__PURE__ */ new Set(["identifier", "property_identifier", "type_identifier"]);
|
|
12223
|
+
function findIdentifier(node) {
|
|
12224
|
+
return node.childForFieldName("name") ?? node.children.find((c) => IDENTIFIER_TYPES.has(c.type)) ?? null;
|
|
12225
|
+
}
|
|
12226
|
+
function getVariableDeclarationName(node) {
|
|
12227
|
+
const declarator = node.children.find((c) => c.type === "variable_declarator");
|
|
12228
|
+
if (!declarator) return null;
|
|
12229
|
+
const id = findIdentifier(declarator);
|
|
12230
|
+
return id?.text ?? null;
|
|
12231
|
+
}
|
|
12232
|
+
function getExportName(node, source) {
|
|
12233
|
+
const decl = node.children.find(
|
|
12234
|
+
(c) => c.type !== "export" && c.type !== "default" && c.type !== "comment"
|
|
12235
|
+
);
|
|
12236
|
+
return decl ? getNodeName(decl, source) : "<anonymous>";
|
|
12237
|
+
}
|
|
12238
|
+
function getAssignmentName(node) {
|
|
12239
|
+
const left = node.childForFieldName("left") ?? node.children[0];
|
|
12240
|
+
return left?.text ?? "<anonymous>";
|
|
12241
|
+
}
|
|
12242
|
+
function getNodeName(node, source) {
|
|
12243
|
+
const id = findIdentifier(node);
|
|
12244
|
+
if (id) return id.text;
|
|
12245
|
+
const isVarDecl = node.type === "lexical_declaration" || node.type === "variable_declaration";
|
|
12246
|
+
if (isVarDecl) return getVariableDeclarationName(node) ?? "<anonymous>";
|
|
12247
|
+
if (node.type === "export_statement") return getExportName(node, source);
|
|
12248
|
+
if (node.type === "assignment") return getAssignmentName(node);
|
|
12249
|
+
return "<anonymous>";
|
|
12250
|
+
}
|
|
12251
|
+
function getSignature(node, source) {
|
|
12252
|
+
const startLine = node.startPosition.row;
|
|
12253
|
+
const lines = source.split("\n");
|
|
12254
|
+
return (lines[startLine] ?? "").trim();
|
|
12255
|
+
}
|
|
12256
|
+
function extractMethods(classNode, language, source, filePath) {
|
|
12257
|
+
const methodTypes = METHOD_TYPES[language] ?? [];
|
|
12258
|
+
const body = classNode.childForFieldName("body") ?? classNode.children.find((c) => c.type === "class_body" || c.type === "block");
|
|
12259
|
+
if (!body) return [];
|
|
12260
|
+
return body.children.filter((child) => methodTypes.includes(child.type)).map((child) => ({
|
|
12261
|
+
name: getNodeName(child, source),
|
|
12262
|
+
kind: "method",
|
|
12263
|
+
file: filePath,
|
|
12264
|
+
line: child.startPosition.row + 1,
|
|
12265
|
+
endLine: child.endPosition.row + 1,
|
|
12266
|
+
signature: getSignature(child, source)
|
|
12267
|
+
}));
|
|
12268
|
+
}
|
|
12269
|
+
function nodeToSymbol(node, kind, source, filePath) {
|
|
12270
|
+
return {
|
|
12271
|
+
name: getNodeName(node, source),
|
|
12272
|
+
kind,
|
|
12273
|
+
file: filePath,
|
|
12274
|
+
line: node.startPosition.row + 1,
|
|
12275
|
+
endLine: node.endPosition.row + 1,
|
|
12276
|
+
signature: getSignature(node, source)
|
|
12277
|
+
};
|
|
12278
|
+
}
|
|
12279
|
+
function processExportStatement(child, topLevelTypes, lang, source, filePath) {
|
|
12280
|
+
const declaration = child.children.find(
|
|
12281
|
+
(c) => c.type !== "export" && c.type !== "default" && c.type !== ";" && c.type !== "comment"
|
|
12282
|
+
);
|
|
12283
|
+
const kind = declaration ? topLevelTypes[declaration.type] : void 0;
|
|
12284
|
+
if (declaration && kind) {
|
|
12285
|
+
const sym = nodeToSymbol(child, kind, source, filePath);
|
|
12286
|
+
sym.name = getNodeName(declaration, source);
|
|
12287
|
+
if (kind === "class") {
|
|
12288
|
+
sym.children = extractMethods(declaration, lang, source, filePath);
|
|
12289
|
+
}
|
|
12290
|
+
return sym;
|
|
12291
|
+
}
|
|
12292
|
+
return nodeToSymbol(child, "export", source, filePath);
|
|
12293
|
+
}
|
|
12294
|
+
function extractSymbols(rootNode, lang, source, filePath) {
|
|
12295
|
+
const symbols = [];
|
|
12296
|
+
const topLevelTypes = TOP_LEVEL_TYPES[lang] ?? {};
|
|
12297
|
+
for (const child of rootNode.children) {
|
|
12298
|
+
if (child.type === "export_statement") {
|
|
12299
|
+
symbols.push(processExportStatement(child, topLevelTypes, lang, source, filePath));
|
|
12300
|
+
continue;
|
|
12301
|
+
}
|
|
12302
|
+
const kind = topLevelTypes[child.type];
|
|
12303
|
+
if (!kind || kind === "import") continue;
|
|
12304
|
+
const sym = nodeToSymbol(child, kind, source, filePath);
|
|
12305
|
+
if (kind === "class") {
|
|
12306
|
+
sym.children = extractMethods(child, lang, source, filePath);
|
|
12307
|
+
}
|
|
12308
|
+
symbols.push(sym);
|
|
12309
|
+
}
|
|
12310
|
+
return symbols;
|
|
12311
|
+
}
|
|
12312
|
+
function buildFailedResult(filePath, lang) {
|
|
12313
|
+
return { file: filePath, language: lang, totalLines: 0, symbols: [], error: "[parse-failed]" };
|
|
12314
|
+
}
|
|
12315
|
+
async function getOutline(filePath) {
|
|
12316
|
+
const lang = detectLanguage(filePath);
|
|
12317
|
+
if (!lang) return buildFailedResult(filePath, "unknown");
|
|
12318
|
+
const result = await parseFile(filePath);
|
|
12319
|
+
if (!result.ok) return buildFailedResult(filePath, lang);
|
|
12320
|
+
const { tree, source } = result.value;
|
|
12321
|
+
const totalLines = source.split("\n").length;
|
|
12322
|
+
const symbols = extractSymbols(tree.rootNode, lang, source, filePath);
|
|
12323
|
+
return { file: filePath, language: lang, totalLines, symbols };
|
|
12324
|
+
}
|
|
12325
|
+
function formatOutline(outline) {
|
|
12326
|
+
if (outline.error) {
|
|
12327
|
+
return `${outline.file} ${outline.error}`;
|
|
12328
|
+
}
|
|
12329
|
+
const lines = [`${outline.file} (${outline.totalLines} lines)`];
|
|
12330
|
+
const last = outline.symbols.length - 1;
|
|
12331
|
+
outline.symbols.forEach((sym, i) => {
|
|
12332
|
+
const prefix = i === last ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
12333
|
+
lines.push(`${prefix} ${sym.signature} :${sym.line}`);
|
|
12334
|
+
if (sym.children) {
|
|
12335
|
+
const childLast = sym.children.length - 1;
|
|
12336
|
+
sym.children.forEach((child, j) => {
|
|
12337
|
+
const childConnector = i === last ? " " : "\u2502 ";
|
|
12338
|
+
const childPrefix = j === childLast ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
|
|
12339
|
+
lines.push(`${childConnector}${childPrefix} ${child.signature} :${child.line}`);
|
|
12340
|
+
});
|
|
12341
|
+
}
|
|
12342
|
+
});
|
|
12343
|
+
return lines.join("\n");
|
|
12344
|
+
}
|
|
12345
|
+
function buildGlob(directory, fileGlob) {
|
|
12346
|
+
const dir = directory.replaceAll("\\", "/");
|
|
12347
|
+
if (fileGlob) {
|
|
12348
|
+
return `${dir}/**/${fileGlob}`;
|
|
12349
|
+
}
|
|
12350
|
+
const exts = Object.keys(EXTENSION_MAP).map((e) => e.slice(1));
|
|
12351
|
+
return `${dir}/**/*.{${exts.join(",")}}`;
|
|
12352
|
+
}
|
|
12353
|
+
function matchesQuery(name, query) {
|
|
12354
|
+
return name.toLowerCase().includes(query.toLowerCase());
|
|
12355
|
+
}
|
|
12356
|
+
function flattenSymbols(symbols) {
|
|
12357
|
+
const flat = [];
|
|
12358
|
+
for (const sym of symbols) {
|
|
12359
|
+
flat.push(sym);
|
|
12360
|
+
if (sym.children) {
|
|
12361
|
+
flat.push(...sym.children);
|
|
12362
|
+
}
|
|
12363
|
+
}
|
|
12364
|
+
return flat;
|
|
12365
|
+
}
|
|
12366
|
+
async function searchSymbols(query, directory, fileGlob) {
|
|
12367
|
+
const pattern = buildGlob(directory, fileGlob);
|
|
12368
|
+
let files;
|
|
12369
|
+
try {
|
|
12370
|
+
files = await findFiles(pattern, directory);
|
|
12371
|
+
} catch {
|
|
12372
|
+
files = [];
|
|
12373
|
+
}
|
|
12374
|
+
const matches = [];
|
|
12375
|
+
const skipped = [];
|
|
12376
|
+
for (const file of files) {
|
|
12377
|
+
const lang = detectLanguage(file);
|
|
12378
|
+
if (!lang) {
|
|
12379
|
+
skipped.push(file);
|
|
12380
|
+
continue;
|
|
12381
|
+
}
|
|
12382
|
+
const outline = await getOutline(file);
|
|
12383
|
+
if (outline.error) {
|
|
12384
|
+
skipped.push(file);
|
|
12385
|
+
continue;
|
|
12386
|
+
}
|
|
12387
|
+
const allSymbols = flattenSymbols(outline.symbols);
|
|
12388
|
+
for (const sym of allSymbols) {
|
|
12389
|
+
if (matchesQuery(sym.name, query)) {
|
|
12390
|
+
matches.push({
|
|
12391
|
+
symbol: sym,
|
|
12392
|
+
context: sym.signature
|
|
12393
|
+
});
|
|
12394
|
+
}
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
return { query, matches, skipped };
|
|
12398
|
+
}
|
|
12399
|
+
function findSymbolInList(symbols, name) {
|
|
12400
|
+
for (const sym of symbols) {
|
|
12401
|
+
if (sym.name === name) return sym;
|
|
12402
|
+
if (sym.children) {
|
|
12403
|
+
const found = findSymbolInList(sym.children, name);
|
|
12404
|
+
if (found) return found;
|
|
12405
|
+
}
|
|
12406
|
+
}
|
|
12407
|
+
return null;
|
|
12408
|
+
}
|
|
12409
|
+
function extractLines(source, startLine, endLine) {
|
|
12410
|
+
const lines = source.split("\n");
|
|
12411
|
+
const start = Math.max(0, startLine - 1);
|
|
12412
|
+
const end = Math.min(lines.length, endLine);
|
|
12413
|
+
return lines.slice(start, end).join("\n");
|
|
12414
|
+
}
|
|
12415
|
+
function buildFallbackResult(filePath, symbolName, content, language) {
|
|
12416
|
+
const totalLines = content ? content.split("\n").length : 0;
|
|
12417
|
+
return {
|
|
12418
|
+
file: filePath,
|
|
12419
|
+
symbolName,
|
|
12420
|
+
startLine: content ? 1 : 0,
|
|
12421
|
+
endLine: totalLines,
|
|
12422
|
+
content,
|
|
12423
|
+
language,
|
|
12424
|
+
fallback: true,
|
|
12425
|
+
warning: "[fallback: raw content]"
|
|
12426
|
+
};
|
|
12427
|
+
}
|
|
12428
|
+
async function readContentSafe(filePath) {
|
|
12429
|
+
const result = await readFileContent(filePath);
|
|
12430
|
+
return result.ok ? result.value : "";
|
|
12431
|
+
}
|
|
12432
|
+
async function unfoldSymbol(filePath, symbolName) {
|
|
12433
|
+
const lang = detectLanguage(filePath);
|
|
12434
|
+
if (!lang) {
|
|
12435
|
+
const content2 = await readContentSafe(filePath);
|
|
12436
|
+
return buildFallbackResult(filePath, symbolName, content2, "unknown");
|
|
12437
|
+
}
|
|
12438
|
+
const outline = await getOutline(filePath);
|
|
12439
|
+
if (outline.error) {
|
|
12440
|
+
const content2 = await readContentSafe(filePath);
|
|
12441
|
+
return buildFallbackResult(filePath, symbolName, content2, lang);
|
|
12442
|
+
}
|
|
12443
|
+
const symbol = findSymbolInList(outline.symbols, symbolName);
|
|
12444
|
+
if (!symbol) {
|
|
12445
|
+
const content2 = await readContentSafe(filePath);
|
|
12446
|
+
return buildFallbackResult(filePath, symbolName, content2, lang);
|
|
12447
|
+
}
|
|
12448
|
+
const parseResult = await parseFile(filePath);
|
|
12449
|
+
if (!parseResult.ok) {
|
|
12450
|
+
const content2 = await readContentSafe(filePath);
|
|
12451
|
+
return {
|
|
12452
|
+
...buildFallbackResult(
|
|
12453
|
+
filePath,
|
|
12454
|
+
symbolName,
|
|
12455
|
+
extractLines(content2, symbol.line, symbol.endLine),
|
|
12456
|
+
lang
|
|
12457
|
+
),
|
|
12458
|
+
startLine: symbol.line,
|
|
12459
|
+
endLine: symbol.endLine
|
|
12460
|
+
};
|
|
12461
|
+
}
|
|
12462
|
+
const content = extractLines(parseResult.value.source, symbol.line, symbol.endLine);
|
|
12463
|
+
return {
|
|
12464
|
+
file: filePath,
|
|
12465
|
+
symbolName,
|
|
12466
|
+
startLine: symbol.line,
|
|
12467
|
+
endLine: symbol.endLine,
|
|
12468
|
+
content,
|
|
12469
|
+
language: lang,
|
|
12470
|
+
fallback: false
|
|
12471
|
+
};
|
|
12472
|
+
}
|
|
12473
|
+
async function unfoldRange(filePath, startLine, endLine) {
|
|
12474
|
+
const lang = detectLanguage(filePath) ?? "unknown";
|
|
12475
|
+
const contentResult = await readFileContent(filePath);
|
|
12476
|
+
if (!contentResult.ok) {
|
|
12477
|
+
return {
|
|
12478
|
+
file: filePath,
|
|
12479
|
+
startLine: 0,
|
|
12480
|
+
endLine: 0,
|
|
12481
|
+
content: "",
|
|
12482
|
+
language: lang,
|
|
12483
|
+
fallback: true,
|
|
12484
|
+
warning: "[fallback: raw content]"
|
|
12485
|
+
};
|
|
12486
|
+
}
|
|
12487
|
+
const totalLines = contentResult.value.split("\n").length;
|
|
12488
|
+
const clampedEnd = Math.min(endLine, totalLines);
|
|
12489
|
+
const content = extractLines(contentResult.value, startLine, clampedEnd);
|
|
12490
|
+
return {
|
|
12491
|
+
file: filePath,
|
|
12492
|
+
startLine,
|
|
12493
|
+
endLine: clampedEnd,
|
|
12494
|
+
content,
|
|
12495
|
+
language: lang,
|
|
12496
|
+
fallback: false
|
|
12497
|
+
};
|
|
12498
|
+
}
|
|
12499
|
+
var TOKENS_PER_MILLION = 1e6;
|
|
12500
|
+
function parseLiteLLMData(raw) {
|
|
12501
|
+
const dataset = /* @__PURE__ */ new Map();
|
|
12502
|
+
for (const [modelName, entry] of Object.entries(raw)) {
|
|
12503
|
+
if (modelName === "sample_spec") continue;
|
|
12504
|
+
if (entry.mode && entry.mode !== "chat") continue;
|
|
12505
|
+
const inputCost = entry.input_cost_per_token;
|
|
12506
|
+
const outputCost = entry.output_cost_per_token;
|
|
12507
|
+
if (inputCost == null || outputCost == null) continue;
|
|
12508
|
+
const pricing = {
|
|
12509
|
+
inputPer1M: inputCost * TOKENS_PER_MILLION,
|
|
12510
|
+
outputPer1M: outputCost * TOKENS_PER_MILLION
|
|
12511
|
+
};
|
|
12512
|
+
if (entry.cache_read_input_token_cost != null) {
|
|
12513
|
+
pricing.cacheReadPer1M = entry.cache_read_input_token_cost * TOKENS_PER_MILLION;
|
|
12514
|
+
}
|
|
12515
|
+
if (entry.cache_creation_input_token_cost != null) {
|
|
12516
|
+
pricing.cacheWritePer1M = entry.cache_creation_input_token_cost * TOKENS_PER_MILLION;
|
|
12517
|
+
}
|
|
12518
|
+
dataset.set(modelName, pricing);
|
|
12519
|
+
}
|
|
12520
|
+
return dataset;
|
|
12521
|
+
}
|
|
12522
|
+
function getModelPrice(model, dataset) {
|
|
12523
|
+
if (!model) {
|
|
12524
|
+
console.warn("[harness pricing] No model specified \u2014 cannot look up pricing.");
|
|
12525
|
+
return null;
|
|
12526
|
+
}
|
|
12527
|
+
const pricing = dataset.get(model);
|
|
12528
|
+
if (!pricing) {
|
|
12529
|
+
console.warn(
|
|
12530
|
+
`[harness pricing] No pricing data for model "${model}". Consider updating pricing data.`
|
|
12531
|
+
);
|
|
12532
|
+
return null;
|
|
12533
|
+
}
|
|
12534
|
+
return pricing;
|
|
12535
|
+
}
|
|
12536
|
+
var fallback_default = {
|
|
12537
|
+
_generatedAt: "2026-03-31",
|
|
12538
|
+
_source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
12539
|
+
models: {
|
|
12540
|
+
"claude-opus-4-20250514": {
|
|
12541
|
+
inputPer1M: 15,
|
|
12542
|
+
outputPer1M: 75,
|
|
12543
|
+
cacheReadPer1M: 1.5,
|
|
12544
|
+
cacheWritePer1M: 18.75
|
|
12545
|
+
},
|
|
12546
|
+
"claude-sonnet-4-20250514": {
|
|
12547
|
+
inputPer1M: 3,
|
|
12548
|
+
outputPer1M: 15,
|
|
12549
|
+
cacheReadPer1M: 0.3,
|
|
12550
|
+
cacheWritePer1M: 3.75
|
|
12551
|
+
},
|
|
12552
|
+
"claude-3-5-haiku-20241022": {
|
|
12553
|
+
inputPer1M: 0.8,
|
|
12554
|
+
outputPer1M: 4,
|
|
12555
|
+
cacheReadPer1M: 0.08,
|
|
12556
|
+
cacheWritePer1M: 1
|
|
12557
|
+
},
|
|
12558
|
+
"gpt-4o": {
|
|
12559
|
+
inputPer1M: 2.5,
|
|
12560
|
+
outputPer1M: 10,
|
|
12561
|
+
cacheReadPer1M: 1.25
|
|
12562
|
+
},
|
|
12563
|
+
"gpt-4o-mini": {
|
|
12564
|
+
inputPer1M: 0.15,
|
|
12565
|
+
outputPer1M: 0.6,
|
|
12566
|
+
cacheReadPer1M: 0.075
|
|
12567
|
+
},
|
|
12568
|
+
"gemini-2.0-flash": {
|
|
12569
|
+
inputPer1M: 0.1,
|
|
12570
|
+
outputPer1M: 0.4,
|
|
12571
|
+
cacheReadPer1M: 0.025
|
|
12572
|
+
},
|
|
12573
|
+
"gemini-2.5-pro": {
|
|
12574
|
+
inputPer1M: 1.25,
|
|
12575
|
+
outputPer1M: 10,
|
|
12576
|
+
cacheReadPer1M: 0.3125
|
|
12577
|
+
}
|
|
12578
|
+
}
|
|
12579
|
+
};
|
|
12580
|
+
var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
12581
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12582
|
+
var STALENESS_WARNING_DAYS = 7;
|
|
12583
|
+
function getCachePath(projectRoot) {
|
|
12584
|
+
return path23.join(projectRoot, ".harness", "cache", "pricing.json");
|
|
12585
|
+
}
|
|
12586
|
+
function getStalenessMarkerPath(projectRoot) {
|
|
12587
|
+
return path23.join(projectRoot, ".harness", "cache", "staleness-marker.json");
|
|
12588
|
+
}
|
|
12589
|
+
async function readDiskCache(projectRoot) {
|
|
12590
|
+
try {
|
|
12591
|
+
const raw = await fs23.readFile(getCachePath(projectRoot), "utf-8");
|
|
12592
|
+
return JSON.parse(raw);
|
|
12593
|
+
} catch {
|
|
12594
|
+
return null;
|
|
12595
|
+
}
|
|
12596
|
+
}
|
|
12597
|
+
async function writeDiskCache(projectRoot, data) {
|
|
12598
|
+
const cachePath = getCachePath(projectRoot);
|
|
12599
|
+
await fs23.mkdir(path23.dirname(cachePath), { recursive: true });
|
|
12600
|
+
await fs23.writeFile(cachePath, JSON.stringify(data, null, 2));
|
|
12601
|
+
}
|
|
12602
|
+
async function fetchFromNetwork() {
|
|
12603
|
+
try {
|
|
12604
|
+
const response = await fetch(LITELLM_PRICING_URL);
|
|
12605
|
+
if (!response.ok) return null;
|
|
12606
|
+
const data = await response.json();
|
|
12607
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return null;
|
|
12608
|
+
return {
|
|
12609
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12610
|
+
data
|
|
12611
|
+
};
|
|
12612
|
+
} catch {
|
|
12613
|
+
return null;
|
|
12614
|
+
}
|
|
12615
|
+
}
|
|
12616
|
+
function loadFallbackDataset() {
|
|
12617
|
+
const fb = fallback_default;
|
|
12618
|
+
const dataset = /* @__PURE__ */ new Map();
|
|
12619
|
+
for (const [model, pricing] of Object.entries(fb.models)) {
|
|
12620
|
+
dataset.set(model, pricing);
|
|
12621
|
+
}
|
|
12622
|
+
return dataset;
|
|
12623
|
+
}
|
|
12624
|
+
async function checkAndWarnStaleness(projectRoot) {
|
|
12625
|
+
const markerPath = getStalenessMarkerPath(projectRoot);
|
|
12626
|
+
try {
|
|
12627
|
+
const raw = await fs23.readFile(markerPath, "utf-8");
|
|
12628
|
+
const marker = JSON.parse(raw);
|
|
12629
|
+
const firstUse = new Date(marker.firstFallbackUse).getTime();
|
|
12630
|
+
const now = Date.now();
|
|
12631
|
+
const daysSinceFirstUse = (now - firstUse) / (24 * 60 * 60 * 1e3);
|
|
12632
|
+
if (daysSinceFirstUse > STALENESS_WARNING_DAYS) {
|
|
12633
|
+
console.warn(
|
|
12634
|
+
`[harness pricing] Pricing data is stale \u2014 using bundled fallback for ${Math.floor(daysSinceFirstUse)} days. Connect to the internet to refresh pricing data.`
|
|
12635
|
+
);
|
|
12636
|
+
}
|
|
12637
|
+
} catch {
|
|
12638
|
+
try {
|
|
12639
|
+
await fs23.mkdir(path23.dirname(markerPath), { recursive: true });
|
|
12640
|
+
await fs23.writeFile(
|
|
12641
|
+
markerPath,
|
|
12642
|
+
JSON.stringify({ firstFallbackUse: (/* @__PURE__ */ new Date()).toISOString() })
|
|
12643
|
+
);
|
|
12644
|
+
} catch {
|
|
12645
|
+
}
|
|
12646
|
+
}
|
|
12647
|
+
}
|
|
12648
|
+
async function clearStalenessMarker(projectRoot) {
|
|
12649
|
+
try {
|
|
12650
|
+
await fs23.unlink(getStalenessMarkerPath(projectRoot));
|
|
12651
|
+
} catch {
|
|
12652
|
+
}
|
|
12653
|
+
}
|
|
12654
|
+
async function loadPricingData(projectRoot) {
|
|
12655
|
+
const cache = await readDiskCache(projectRoot);
|
|
12656
|
+
if (cache) {
|
|
12657
|
+
const cacheAge = Date.now() - new Date(cache.fetchedAt).getTime();
|
|
12658
|
+
if (cacheAge < CACHE_TTL_MS) {
|
|
12659
|
+
await clearStalenessMarker(projectRoot);
|
|
12660
|
+
return parseLiteLLMData(cache.data);
|
|
12661
|
+
}
|
|
12662
|
+
}
|
|
12663
|
+
const fetched = await fetchFromNetwork();
|
|
12664
|
+
if (fetched) {
|
|
12665
|
+
await writeDiskCache(projectRoot, fetched);
|
|
12666
|
+
await clearStalenessMarker(projectRoot);
|
|
12667
|
+
return parseLiteLLMData(fetched.data);
|
|
12668
|
+
}
|
|
12669
|
+
if (cache) {
|
|
12670
|
+
return parseLiteLLMData(cache.data);
|
|
12671
|
+
}
|
|
12672
|
+
await checkAndWarnStaleness(projectRoot);
|
|
12673
|
+
return loadFallbackDataset();
|
|
12674
|
+
}
|
|
12675
|
+
var MICRODOLLARS_PER_DOLLAR = 1e6;
|
|
12676
|
+
var TOKENS_PER_MILLION2 = 1e6;
|
|
12677
|
+
function calculateCost(record, dataset) {
|
|
12678
|
+
if (!record.model) return null;
|
|
12679
|
+
const pricing = getModelPrice(record.model, dataset);
|
|
12680
|
+
if (!pricing) return null;
|
|
12681
|
+
let costUSD = 0;
|
|
12682
|
+
costUSD += record.tokens.inputTokens / TOKENS_PER_MILLION2 * pricing.inputPer1M;
|
|
12683
|
+
costUSD += record.tokens.outputTokens / TOKENS_PER_MILLION2 * pricing.outputPer1M;
|
|
12684
|
+
if (record.cacheReadTokens != null && pricing.cacheReadPer1M != null) {
|
|
12685
|
+
costUSD += record.cacheReadTokens / TOKENS_PER_MILLION2 * pricing.cacheReadPer1M;
|
|
12686
|
+
}
|
|
12687
|
+
if (record.cacheCreationTokens != null && pricing.cacheWritePer1M != null) {
|
|
12688
|
+
costUSD += record.cacheCreationTokens / TOKENS_PER_MILLION2 * pricing.cacheWritePer1M;
|
|
12689
|
+
}
|
|
12690
|
+
return Math.round(costUSD * MICRODOLLARS_PER_DOLLAR);
|
|
12691
|
+
}
|
|
12692
|
+
function aggregateBySession(records) {
|
|
12693
|
+
if (records.length === 0) return [];
|
|
12694
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
12695
|
+
for (const record of records) {
|
|
12696
|
+
const tagged = record;
|
|
12697
|
+
const id = record.sessionId;
|
|
12698
|
+
if (!sessionMap.has(id)) {
|
|
12699
|
+
sessionMap.set(id, { harnessRecords: [], ccRecords: [], allRecords: [] });
|
|
12700
|
+
}
|
|
12701
|
+
const bucket = sessionMap.get(id);
|
|
12702
|
+
if (tagged._source === "claude-code") {
|
|
12703
|
+
bucket.ccRecords.push(tagged);
|
|
12704
|
+
} else {
|
|
12705
|
+
bucket.harnessRecords.push(tagged);
|
|
12706
|
+
}
|
|
12707
|
+
bucket.allRecords.push(tagged);
|
|
12708
|
+
}
|
|
12709
|
+
const results = [];
|
|
12710
|
+
for (const [sessionId, bucket] of sessionMap) {
|
|
12711
|
+
const hasHarness = bucket.harnessRecords.length > 0;
|
|
12712
|
+
const hasCC = bucket.ccRecords.length > 0;
|
|
12713
|
+
const isMerged = hasHarness && hasCC;
|
|
12714
|
+
const tokenSource = hasHarness ? bucket.harnessRecords : bucket.ccRecords;
|
|
12715
|
+
const tokens = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
12716
|
+
let cacheCreation;
|
|
12717
|
+
let cacheRead;
|
|
12718
|
+
let costMicroUSD = 0;
|
|
12719
|
+
let model;
|
|
12720
|
+
for (const r of tokenSource) {
|
|
12721
|
+
tokens.inputTokens += r.tokens.inputTokens;
|
|
12722
|
+
tokens.outputTokens += r.tokens.outputTokens;
|
|
12723
|
+
tokens.totalTokens += r.tokens.totalTokens;
|
|
12724
|
+
if (r.cacheCreationTokens != null) {
|
|
12725
|
+
cacheCreation = (cacheCreation ?? 0) + r.cacheCreationTokens;
|
|
12726
|
+
}
|
|
12727
|
+
if (r.cacheReadTokens != null) {
|
|
12728
|
+
cacheRead = (cacheRead ?? 0) + r.cacheReadTokens;
|
|
12729
|
+
}
|
|
12730
|
+
if (r.costMicroUSD != null && costMicroUSD != null) {
|
|
12731
|
+
costMicroUSD += r.costMicroUSD;
|
|
12732
|
+
} else if (r.costMicroUSD == null) {
|
|
12733
|
+
costMicroUSD = null;
|
|
12734
|
+
}
|
|
12735
|
+
if (!model && r.model) {
|
|
12736
|
+
model = r.model;
|
|
12737
|
+
}
|
|
12738
|
+
}
|
|
12739
|
+
if (!model && hasCC) {
|
|
12740
|
+
for (const r of bucket.ccRecords) {
|
|
12741
|
+
if (r.model) {
|
|
12742
|
+
model = r.model;
|
|
12743
|
+
break;
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
const timestamps = bucket.allRecords.map((r) => r.timestamp).sort();
|
|
12748
|
+
const source = isMerged ? "merged" : hasCC ? "claude-code" : "harness";
|
|
12749
|
+
const session = {
|
|
12750
|
+
sessionId,
|
|
12751
|
+
firstTimestamp: timestamps[0] ?? "",
|
|
12752
|
+
lastTimestamp: timestamps[timestamps.length - 1] ?? "",
|
|
12753
|
+
tokens,
|
|
12754
|
+
costMicroUSD,
|
|
12755
|
+
source
|
|
12756
|
+
};
|
|
12757
|
+
if (model) session.model = model;
|
|
12758
|
+
if (cacheCreation != null) session.cacheCreationTokens = cacheCreation;
|
|
12759
|
+
if (cacheRead != null) session.cacheReadTokens = cacheRead;
|
|
12760
|
+
results.push(session);
|
|
12761
|
+
}
|
|
12762
|
+
results.sort((a, b) => b.firstTimestamp.localeCompare(a.firstTimestamp));
|
|
12763
|
+
return results;
|
|
12764
|
+
}
|
|
12765
|
+
function aggregateByDay(records) {
|
|
12766
|
+
if (records.length === 0) return [];
|
|
12767
|
+
const dayMap = /* @__PURE__ */ new Map();
|
|
12768
|
+
for (const record of records) {
|
|
12769
|
+
const date = record.timestamp.slice(0, 10);
|
|
12770
|
+
if (!dayMap.has(date)) {
|
|
12771
|
+
dayMap.set(date, {
|
|
12772
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
12773
|
+
tokens: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
12774
|
+
costMicroUSD: 0,
|
|
12775
|
+
models: /* @__PURE__ */ new Set()
|
|
12776
|
+
});
|
|
12777
|
+
}
|
|
12778
|
+
const day = dayMap.get(date);
|
|
12779
|
+
day.sessions.add(record.sessionId);
|
|
12780
|
+
day.tokens.inputTokens += record.tokens.inputTokens;
|
|
12781
|
+
day.tokens.outputTokens += record.tokens.outputTokens;
|
|
12782
|
+
day.tokens.totalTokens += record.tokens.totalTokens;
|
|
12783
|
+
if (record.cacheCreationTokens != null) {
|
|
12784
|
+
day.cacheCreation = (day.cacheCreation ?? 0) + record.cacheCreationTokens;
|
|
12785
|
+
}
|
|
12786
|
+
if (record.cacheReadTokens != null) {
|
|
12787
|
+
day.cacheRead = (day.cacheRead ?? 0) + record.cacheReadTokens;
|
|
12788
|
+
}
|
|
12789
|
+
if (record.costMicroUSD != null && day.costMicroUSD != null) {
|
|
12790
|
+
day.costMicroUSD += record.costMicroUSD;
|
|
12791
|
+
} else if (record.costMicroUSD == null) {
|
|
12792
|
+
day.costMicroUSD = null;
|
|
12793
|
+
}
|
|
12794
|
+
if (record.model) {
|
|
12795
|
+
day.models.add(record.model);
|
|
12796
|
+
}
|
|
12797
|
+
}
|
|
12798
|
+
const results = [];
|
|
12799
|
+
for (const [date, day] of dayMap) {
|
|
12800
|
+
const entry = {
|
|
12801
|
+
date,
|
|
12802
|
+
sessionCount: day.sessions.size,
|
|
12803
|
+
tokens: day.tokens,
|
|
12804
|
+
costMicroUSD: day.costMicroUSD,
|
|
12805
|
+
models: Array.from(day.models).sort()
|
|
12806
|
+
};
|
|
12807
|
+
if (day.cacheCreation != null) entry.cacheCreationTokens = day.cacheCreation;
|
|
12808
|
+
if (day.cacheRead != null) entry.cacheReadTokens = day.cacheRead;
|
|
12809
|
+
results.push(entry);
|
|
12810
|
+
}
|
|
12811
|
+
results.sort((a, b) => b.date.localeCompare(a.date));
|
|
12812
|
+
return results;
|
|
12813
|
+
}
|
|
12814
|
+
function parseLine(line, lineNumber) {
|
|
12815
|
+
let entry;
|
|
12816
|
+
try {
|
|
12817
|
+
entry = JSON.parse(line);
|
|
12818
|
+
} catch {
|
|
12819
|
+
console.warn(`[harness usage] Skipping malformed JSONL line ${lineNumber}`);
|
|
12820
|
+
return null;
|
|
12821
|
+
}
|
|
12822
|
+
const tokenUsage = entry.token_usage;
|
|
12823
|
+
if (!tokenUsage || typeof tokenUsage !== "object") {
|
|
12824
|
+
console.warn(
|
|
12825
|
+
`[harness usage] Skipping malformed JSONL line ${lineNumber}: missing token_usage`
|
|
12826
|
+
);
|
|
12827
|
+
return null;
|
|
12828
|
+
}
|
|
12829
|
+
const inputTokens = tokenUsage.input_tokens ?? 0;
|
|
12830
|
+
const outputTokens = tokenUsage.output_tokens ?? 0;
|
|
12831
|
+
const record = {
|
|
12832
|
+
sessionId: entry.session_id ?? "unknown",
|
|
12833
|
+
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12834
|
+
tokens: {
|
|
12835
|
+
inputTokens,
|
|
12836
|
+
outputTokens,
|
|
12837
|
+
totalTokens: inputTokens + outputTokens
|
|
12838
|
+
}
|
|
12839
|
+
};
|
|
12840
|
+
if (entry.cache_creation_tokens != null) {
|
|
12841
|
+
record.cacheCreationTokens = entry.cache_creation_tokens;
|
|
12842
|
+
}
|
|
12843
|
+
if (entry.cache_read_tokens != null) {
|
|
12844
|
+
record.cacheReadTokens = entry.cache_read_tokens;
|
|
12845
|
+
}
|
|
12846
|
+
if (entry.model != null) {
|
|
12847
|
+
record.model = entry.model;
|
|
12848
|
+
}
|
|
12849
|
+
return record;
|
|
12850
|
+
}
|
|
12851
|
+
function readCostRecords(projectRoot) {
|
|
12852
|
+
const costsFile = path24.join(projectRoot, ".harness", "metrics", "costs.jsonl");
|
|
12853
|
+
let raw;
|
|
12854
|
+
try {
|
|
12855
|
+
raw = fs24.readFileSync(costsFile, "utf-8");
|
|
12856
|
+
} catch {
|
|
12857
|
+
return [];
|
|
12858
|
+
}
|
|
12859
|
+
const records = [];
|
|
12860
|
+
const lines = raw.split("\n");
|
|
12861
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12862
|
+
const line = lines[i]?.trim();
|
|
12863
|
+
if (!line) continue;
|
|
12864
|
+
const record = parseLine(line, i + 1);
|
|
12865
|
+
if (record) {
|
|
12866
|
+
records.push(record);
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
return records;
|
|
12870
|
+
}
|
|
12871
|
+
function extractUsage(entry) {
|
|
12872
|
+
if (entry.type !== "assistant") return null;
|
|
12873
|
+
const message = entry.message;
|
|
12874
|
+
if (!message || typeof message !== "object") return null;
|
|
12875
|
+
const usage = message.usage;
|
|
12876
|
+
return usage && typeof usage === "object" && !Array.isArray(usage) ? usage : null;
|
|
12877
|
+
}
|
|
12878
|
+
function buildRecord(entry, usage) {
|
|
12879
|
+
const inputTokens = Number(usage.input_tokens) || 0;
|
|
12880
|
+
const outputTokens = Number(usage.output_tokens) || 0;
|
|
12881
|
+
const message = entry.message;
|
|
12882
|
+
const record = {
|
|
12883
|
+
sessionId: entry.sessionId ?? "unknown",
|
|
12884
|
+
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
12885
|
+
tokens: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens },
|
|
12886
|
+
_source: "claude-code"
|
|
12887
|
+
};
|
|
12888
|
+
const model = message.model;
|
|
12889
|
+
if (model) record.model = model;
|
|
12890
|
+
const cacheCreate = usage.cache_creation_input_tokens;
|
|
12891
|
+
const cacheRead = usage.cache_read_input_tokens;
|
|
12892
|
+
if (typeof cacheCreate === "number" && cacheCreate > 0) record.cacheCreationTokens = cacheCreate;
|
|
12893
|
+
if (typeof cacheRead === "number" && cacheRead > 0) record.cacheReadTokens = cacheRead;
|
|
12894
|
+
return record;
|
|
12895
|
+
}
|
|
12896
|
+
function parseCCLine(line, filePath, lineNumber) {
|
|
12897
|
+
let entry;
|
|
12898
|
+
try {
|
|
12899
|
+
entry = JSON.parse(line);
|
|
12900
|
+
} catch {
|
|
12901
|
+
console.warn(
|
|
12902
|
+
`[harness usage] Skipping malformed CC JSONL line ${lineNumber} in ${path25.basename(filePath)}`
|
|
12903
|
+
);
|
|
12904
|
+
return null;
|
|
12905
|
+
}
|
|
12906
|
+
const usage = extractUsage(entry);
|
|
12907
|
+
if (!usage) return null;
|
|
12908
|
+
return {
|
|
12909
|
+
record: buildRecord(entry, usage),
|
|
12910
|
+
requestId: entry.requestId ?? null
|
|
12911
|
+
};
|
|
12912
|
+
}
|
|
12913
|
+
function readCCFile(filePath) {
|
|
12914
|
+
let raw;
|
|
12915
|
+
try {
|
|
12916
|
+
raw = fs25.readFileSync(filePath, "utf-8");
|
|
12917
|
+
} catch {
|
|
12918
|
+
return [];
|
|
12919
|
+
}
|
|
12920
|
+
const byRequestId = /* @__PURE__ */ new Map();
|
|
12921
|
+
const noRequestId = [];
|
|
12922
|
+
const lines = raw.split("\n");
|
|
12923
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12924
|
+
const line = lines[i]?.trim();
|
|
12925
|
+
if (!line) continue;
|
|
12926
|
+
const parsed = parseCCLine(line, filePath, i + 1);
|
|
12927
|
+
if (!parsed) continue;
|
|
12928
|
+
if (parsed.requestId) {
|
|
12929
|
+
byRequestId.set(parsed.requestId, parsed.record);
|
|
12930
|
+
} else {
|
|
12931
|
+
noRequestId.push(parsed.record);
|
|
12932
|
+
}
|
|
12933
|
+
}
|
|
12934
|
+
return [...byRequestId.values(), ...noRequestId];
|
|
12935
|
+
}
|
|
12936
|
+
function parseCCRecords() {
|
|
12937
|
+
const homeDir = process.env.HOME ?? os2.homedir();
|
|
12938
|
+
const projectsDir = path25.join(homeDir, ".claude", "projects");
|
|
12939
|
+
let projectDirs;
|
|
12940
|
+
try {
|
|
12941
|
+
projectDirs = fs25.readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path25.join(projectsDir, d.name));
|
|
12942
|
+
} catch {
|
|
12943
|
+
return [];
|
|
12944
|
+
}
|
|
12945
|
+
const records = [];
|
|
12946
|
+
for (const dir of projectDirs) {
|
|
12947
|
+
let files;
|
|
12948
|
+
try {
|
|
12949
|
+
files = fs25.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path25.join(dir, f));
|
|
12950
|
+
} catch {
|
|
12951
|
+
continue;
|
|
12952
|
+
}
|
|
12953
|
+
for (const file of files) {
|
|
12954
|
+
records.push(...readCCFile(file));
|
|
12955
|
+
}
|
|
12956
|
+
}
|
|
12957
|
+
return records;
|
|
12958
|
+
}
|
|
12959
|
+
var VERSION = "0.15.0";
|
|
10809
12960
|
|
|
10810
12961
|
export {
|
|
10811
12962
|
ArchMetricCategorySchema,
|
|
@@ -10947,14 +13098,19 @@ export {
|
|
|
10947
13098
|
updateSessionIndex,
|
|
10948
13099
|
loadState,
|
|
10949
13100
|
saveState,
|
|
13101
|
+
parseFrontmatter,
|
|
13102
|
+
extractIndexEntry,
|
|
10950
13103
|
clearLearningsCache,
|
|
10951
13104
|
appendLearning,
|
|
10952
13105
|
parseDateFromEntry,
|
|
10953
13106
|
analyzeLearningPatterns,
|
|
10954
13107
|
loadBudgetedLearnings,
|
|
13108
|
+
loadIndexEntries,
|
|
10955
13109
|
loadRelevantLearnings,
|
|
10956
13110
|
archiveLearnings,
|
|
10957
13111
|
pruneLearnings,
|
|
13112
|
+
promoteSessionLearnings,
|
|
13113
|
+
countLearningEntries,
|
|
10958
13114
|
clearFailuresCache,
|
|
10959
13115
|
appendFailure,
|
|
10960
13116
|
loadFailures,
|
|
@@ -10970,6 +13126,11 @@ export {
|
|
|
10970
13126
|
appendSessionEntry,
|
|
10971
13127
|
updateSessionEntryStatus,
|
|
10972
13128
|
archiveSession,
|
|
13129
|
+
SkillEventSchema,
|
|
13130
|
+
clearEventHashCache,
|
|
13131
|
+
emitEvent,
|
|
13132
|
+
loadEvents,
|
|
13133
|
+
formatEventTimeline,
|
|
10973
13134
|
executeWorkflow,
|
|
10974
13135
|
runPipeline,
|
|
10975
13136
|
runMultiTurnPipeline,
|
|
@@ -10986,11 +13147,31 @@ export {
|
|
|
10986
13147
|
pathTraversalRules,
|
|
10987
13148
|
networkRules,
|
|
10988
13149
|
deserializationRules,
|
|
13150
|
+
agentConfigRules,
|
|
13151
|
+
mcpRules,
|
|
13152
|
+
insecureDefaultsRules,
|
|
13153
|
+
sharpEdgesRules,
|
|
10989
13154
|
nodeRules,
|
|
10990
13155
|
expressRules,
|
|
10991
13156
|
reactRules,
|
|
10992
13157
|
goRules,
|
|
13158
|
+
parseHarnessIgnore,
|
|
10993
13159
|
SecurityScanner,
|
|
13160
|
+
scanForInjection,
|
|
13161
|
+
getInjectionPatterns,
|
|
13162
|
+
DESTRUCTIVE_BASH,
|
|
13163
|
+
getTaintFilePath,
|
|
13164
|
+
readTaint,
|
|
13165
|
+
checkTaint,
|
|
13166
|
+
writeTaint,
|
|
13167
|
+
clearTaint,
|
|
13168
|
+
listTaintedSessions,
|
|
13169
|
+
mapSecuritySeverity,
|
|
13170
|
+
computeOverallSeverity,
|
|
13171
|
+
computeScanExitCode,
|
|
13172
|
+
mapInjectionFindings,
|
|
13173
|
+
isDuplicateFinding,
|
|
13174
|
+
mapSecurityFindings,
|
|
10994
13175
|
runCIChecks,
|
|
10995
13176
|
runMechanicalChecks,
|
|
10996
13177
|
ExclusionSet,
|
|
@@ -11025,6 +13206,7 @@ export {
|
|
|
11025
13206
|
parseRoadmap,
|
|
11026
13207
|
serializeRoadmap,
|
|
11027
13208
|
syncRoadmap,
|
|
13209
|
+
applySyncChanges,
|
|
11028
13210
|
InteractionTypeSchema,
|
|
11029
13211
|
QuestionSchema,
|
|
11030
13212
|
ConfirmationSchema,
|
|
@@ -11038,5 +13220,26 @@ export {
|
|
|
11038
13220
|
readCheckState,
|
|
11039
13221
|
spawnBackgroundCheck,
|
|
11040
13222
|
getUpdateNotification,
|
|
13223
|
+
EXTENSION_MAP,
|
|
13224
|
+
detectLanguage,
|
|
13225
|
+
getParser,
|
|
13226
|
+
parseFile,
|
|
13227
|
+
resetParserCache,
|
|
13228
|
+
getOutline,
|
|
13229
|
+
formatOutline,
|
|
13230
|
+
searchSymbols,
|
|
13231
|
+
unfoldSymbol,
|
|
13232
|
+
unfoldRange,
|
|
13233
|
+
parseLiteLLMData,
|
|
13234
|
+
getModelPrice,
|
|
13235
|
+
LITELLM_PRICING_URL,
|
|
13236
|
+
CACHE_TTL_MS,
|
|
13237
|
+
STALENESS_WARNING_DAYS,
|
|
13238
|
+
loadPricingData,
|
|
13239
|
+
calculateCost,
|
|
13240
|
+
aggregateBySession,
|
|
13241
|
+
aggregateByDay,
|
|
13242
|
+
readCostRecords,
|
|
13243
|
+
parseCCRecords,
|
|
11041
13244
|
VERSION
|
|
11042
13245
|
};
|