@dommaker/harness 0.13.0 → 0.13.2
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/CHANGELOG.md +169 -0
- package/bin/harness-knowledge-capture.js +99 -0
- package/bin/harness-knowledge-check.js +149 -0
- package/bin/harness-knowledge-track.js +92 -0
- package/bin/harness-knowledge-track.sh +47 -0
- package/bin/harness-sensitive-check.sh +67 -0
- package/dist/cli/commands/init.js +34 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/package.json +6 -3
- package/src/CONTEXT.md +34 -0
- package/src/__tests__/acceptance-gate.test.ts +364 -0
- package/src/__tests__/annotation-checker.test.ts +183 -0
- package/src/__tests__/architecture-engine.test.ts +619 -0
- package/src/__tests__/checker-extra.test.ts +1627 -0
- package/src/__tests__/checker.test.ts +1268 -0
- package/src/__tests__/checkpoint.test.ts +697 -0
- package/src/__tests__/clean-state-extra.test.ts +301 -0
- package/src/__tests__/clean-state.test.ts +169 -0
- package/src/__tests__/constraint-doctor.test.ts +410 -0
- package/src/__tests__/constraint-evolver.test.ts +404 -0
- package/src/__tests__/constraint-handler.test.ts +136 -0
- package/src/__tests__/context/progressive-loader.test.ts +131 -0
- package/src/__tests__/contract-gate.test.ts +321 -0
- package/src/__tests__/cross-project-checker.test.ts +318 -0
- package/src/__tests__/cso-validator.test.ts +205 -0
- package/src/__tests__/detect-source-roots.test.ts +98 -0
- package/src/__tests__/doctor-evolver.test.ts +381 -0
- package/src/__tests__/error-classifier.test.ts +304 -0
- package/src/__tests__/exec.test.ts +83 -0
- package/src/__tests__/extend-exceptions.test.ts +149 -0
- package/src/__tests__/failure-recorder.test.ts +335 -0
- package/src/__tests__/hooks-pipeline.test.ts +214 -0
- package/src/__tests__/interceptor.test.ts +89 -0
- package/src/__tests__/iron-laws.test.ts +246 -0
- package/src/__tests__/long-running.test.ts +17 -0
- package/src/__tests__/passes-gate-check.test.ts +138 -0
- package/src/__tests__/passes-gate-extension.test.ts +151 -0
- package/src/__tests__/passes-gate-extra.test.ts +238 -0
- package/src/__tests__/passes-gate.test.ts +231 -0
- package/src/__tests__/performance-analyzer.test.ts +501 -0
- package/src/__tests__/performance-collector.test.ts +276 -0
- package/src/__tests__/performance-gate.test.ts +74 -0
- package/src/__tests__/presets.test.ts +104 -0
- package/src/__tests__/project-config-loader-extra.test.ts +269 -0
- package/src/__tests__/project-config-loader.test.ts +127 -0
- package/src/__tests__/review-gate.test.ts +92 -0
- package/src/__tests__/security-gate.test.ts +100 -0
- package/src/__tests__/session-startup.test.ts +334 -0
- package/src/__tests__/spec-validator.test.ts +57 -0
- package/src/__tests__/token-budget.test.ts +252 -0
- package/src/__tests__/trace-analyzer-extra.test.ts +307 -0
- package/src/__tests__/trace-analyzer.test.ts +131 -0
- package/src/__tests__/trace-collector.test.ts +258 -0
- package/src/__tests__/traces.test.ts +347 -0
- package/src/agents/CONTEXT.md +21 -0
- package/src/agents/__tests__/lifecycle.test.ts +248 -0
- package/src/agents/index.ts +2 -0
- package/src/agents/lifecycle.ts +201 -0
- package/src/agents/types.ts +46 -0
- package/src/architecture/CONTEXT.md +22 -0
- package/src/architecture/constraint-engine.ts +356 -0
- package/src/architecture/cross-project-checker.ts +563 -0
- package/src/cli/commands/CONTEXT.md +24 -0
- package/src/cli/commands/__tests__/acceptance.test.ts +180 -0
- package/src/cli/commands/__tests__/check.test.ts +598 -0
- package/src/cli/commands/__tests__/command.test.ts +149 -0
- package/src/cli/commands/__tests__/contract.test.ts +151 -0
- package/src/cli/commands/__tests__/failure.test.ts +199 -0
- package/src/cli/commands/__tests__/flow.test.ts +343 -0
- package/src/cli/commands/__tests__/init.test.ts +306 -0
- package/src/cli/commands/__tests__/passes-gate.test.ts +256 -0
- package/src/cli/commands/__tests__/performance.test.ts +116 -0
- package/src/cli/commands/__tests__/posteval-plan.test.ts +201 -0
- package/src/cli/commands/__tests__/report-extra.test.ts +98 -0
- package/src/cli/commands/__tests__/report.test.ts +109 -0
- package/src/cli/commands/__tests__/review.test.ts +153 -0
- package/src/cli/commands/__tests__/security.test.ts +147 -0
- package/src/cli/commands/__tests__/spec.test.ts +193 -0
- package/src/cli/commands/__tests__/status-extra.test.ts +223 -0
- package/src/cli/commands/__tests__/status.test.ts +306 -0
- package/src/cli/commands/__tests__/sync-docs.test.ts +335 -0
- package/src/cli/commands/__tests__/validate.test.ts +139 -0
- package/src/cli/commands/acceptance.ts +111 -0
- package/src/cli/commands/analyze-sessions.ts +488 -0
- package/src/cli/commands/check.ts +441 -0
- package/src/cli/commands/command.ts +90 -0
- package/src/cli/commands/constraints.ts +86 -0
- package/src/cli/commands/contract.ts +168 -0
- package/src/cli/commands/failure.ts +117 -0
- package/src/cli/commands/flow.ts +275 -0
- package/src/cli/commands/index.ts +26 -0
- package/src/cli/commands/init.ts +832 -0
- package/src/cli/commands/knowledge.ts +391 -0
- package/src/cli/commands/passes-gate.ts +158 -0
- package/src/cli/commands/performance.ts +108 -0
- package/src/cli/commands/posteval-plan.ts +85 -0
- package/src/cli/commands/release.ts +159 -0
- package/src/cli/commands/report.ts +198 -0
- package/src/cli/commands/review.ts +118 -0
- package/src/cli/commands/security.ts +122 -0
- package/src/cli/commands/spec.ts +158 -0
- package/src/cli/commands/status.ts +169 -0
- package/src/cli/commands/sync-docs.ts +743 -0
- package/src/cli/commands/update-user-model.ts +527 -0
- package/src/cli/commands/validate.ts +222 -0
- package/src/constraints/__tests__/lifecycle-runner.test.ts +246 -0
- package/src/constraints/__tests__/registry.test.ts +196 -0
- package/src/constraints/index.ts +3 -0
- package/src/constraints/lifecycle-runner.ts +184 -0
- package/src/constraints/registry.ts +134 -0
- package/src/constraints/types.ts +59 -0
- package/src/context/CONTEXT.md +28 -0
- package/src/context/__tests__/compaction.test.ts +169 -0
- package/src/context/__tests__/file-budget.test.ts +142 -0
- package/src/context/__tests__/knowledge-injector.test.ts +148 -0
- package/src/context/__tests__/progressive-loader.test.ts +264 -0
- package/src/context/__tests__/session-manager.test.ts +300 -0
- package/src/context/__tests__/token-budget.test.ts +338 -0
- package/src/context/__tests__/token-pipeline.test.ts +178 -0
- package/src/context/__tests__/tool-output-budget.test.ts +95 -0
- package/src/context/compaction.ts +202 -0
- package/src/context/file-budget.ts +142 -0
- package/src/context/index.ts +15 -0
- package/src/context/knowledge-injector.ts +168 -0
- package/src/context/progressive-loader.ts +260 -0
- package/src/context/session-manager.ts +343 -0
- package/src/context/token-budget.ts +295 -0
- package/src/context/token-pipeline.ts +265 -0
- package/src/context/tool-output-budget.ts +178 -0
- package/src/context/types.ts +160 -0
- package/src/core/CONTEXT.md +26 -0
- package/src/core/constraints/__tests__/default-executors.test.ts +57 -0
- package/src/core/constraints/__tests__/interceptor.test.ts +251 -0
- package/src/core/constraints/__tests__/prompt-injection.test.ts +89 -0
- package/src/core/constraints/check-cache.ts +96 -0
- package/src/core/constraints/checker.ts +1169 -0
- package/src/core/constraints/default-executors.ts +92 -0
- package/src/core/constraints/definitions.ts +1119 -0
- package/src/core/constraints/doc-freshness/__tests__/runner.test.ts +551 -0
- package/src/core/constraints/doc-freshness/auto-fix.ts +69 -0
- package/src/core/constraints/doc-freshness/runner.ts +486 -0
- package/src/core/constraints/index.ts +78 -0
- package/src/core/constraints/interceptor.ts +220 -0
- package/src/core/constraints/prompt-injection.ts +73 -0
- package/src/core/index.ts +23 -0
- package/src/core/project-config-loader.ts +252 -0
- package/src/core/session/__tests__/startup.test.ts +281 -0
- package/src/core/session/clean-state.ts +178 -0
- package/src/core/session/index.ts +15 -0
- package/src/core/session/startup.ts +253 -0
- package/src/core/spec/__tests__/validator.test.ts +379 -0
- package/src/core/spec/index.ts +7 -0
- package/src/core/spec/validator.ts +383 -0
- package/src/core/validators/__tests__/passes-gate.test.ts +131 -0
- package/src/core/validators/checkpoint.ts +547 -0
- package/src/core/validators/cso.ts +124 -0
- package/src/core/validators/index.ts +25 -0
- package/src/core/validators/passes-gate.ts +613 -0
- package/src/dashboard/CONTEXT.md +21 -0
- package/src/dashboard/__tests__/data.test.ts +115 -0
- package/src/dashboard/__tests__/stats.test.ts +156 -0
- package/src/dashboard/data.ts +113 -0
- package/src/dashboard/index.ts +3 -0
- package/src/dashboard/stats.ts +127 -0
- package/src/dashboard/types.ts +113 -0
- package/src/evolution/CONTEXT.md +22 -0
- package/src/evolution/__tests__/auto-evolve.test.ts +320 -0
- package/src/evolution/auto-evolve.ts +98 -0
- package/src/evolution/index.ts +1 -0
- package/src/failure/CONTEXT.md +24 -0
- package/src/failure/classifier.ts +156 -0
- package/src/failure/constraint-handler.ts +173 -0
- package/src/failure/index.ts +35 -0
- package/src/failure/recorder.ts +201 -0
- package/src/failure/types.ts +165 -0
- package/src/gates/CONTEXT.md +27 -0
- package/src/gates/__tests__/acceptance.test.ts +608 -0
- package/src/gates/__tests__/command.test.ts +206 -0
- package/src/gates/__tests__/performance.test.ts +381 -0
- package/src/gates/__tests__/review.test.ts +302 -0
- package/src/gates/__tests__/security.test.ts +332 -0
- package/src/gates/acceptance.ts +493 -0
- package/src/gates/command.ts +444 -0
- package/src/gates/contract.ts +260 -0
- package/src/gates/index.ts +67 -0
- package/src/gates/performance.ts +359 -0
- package/src/gates/review.ts +169 -0
- package/src/gates/security.ts +244 -0
- package/src/gates/types.ts +159 -0
- package/src/governance/CONTEXT.md +22 -0
- package/src/governance/__tests__/executor.test.ts +249 -0
- package/src/governance/executor.ts +229 -0
- package/src/governance/index.ts +13 -0
- package/src/governance/types.ts +53 -0
- package/src/hooks/CONTEXT.md +23 -0
- package/src/hooks/__tests__/bootstrap.test.ts +184 -0
- package/src/hooks/bootstrap.ts +144 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/pipeline.ts +157 -0
- package/src/hooks/registry.ts +88 -0
- package/src/hooks/types.ts +80 -0
- package/src/index.ts +198 -0
- package/src/knowledge/CONTEXT.md +29 -0
- package/src/knowledge/__tests__/doctor.test.ts +272 -0
- package/src/knowledge/__tests__/import.test.ts +376 -0
- package/src/knowledge/__tests__/ingest.test.ts +125 -0
- package/src/knowledge/__tests__/lifecycle-hooks.test.ts +137 -0
- package/src/knowledge/__tests__/lifecycle.test.ts +248 -0
- package/src/knowledge/__tests__/lint.test.ts +401 -0
- package/src/knowledge/__tests__/query.test.ts +185 -0
- package/src/knowledge/__tests__/reference-tracker.test.ts +129 -0
- package/src/knowledge/__tests__/store.test.ts +238 -0
- package/src/knowledge/doctor.ts +104 -0
- package/src/knowledge/import.ts +505 -0
- package/src/knowledge/index.ts +16 -0
- package/src/knowledge/ingest.ts +126 -0
- package/src/knowledge/lifecycle-hooks.ts +199 -0
- package/src/knowledge/lifecycle.ts +175 -0
- package/src/knowledge/lint.ts +360 -0
- package/src/knowledge/query.ts +150 -0
- package/src/knowledge/reference-tracker.ts +99 -0
- package/src/knowledge/store.ts +264 -0
- package/src/knowledge/types.ts +139 -0
- package/src/llm/CONTEXT.md +21 -0
- package/src/llm/__tests__/adapter.test.ts +172 -0
- package/src/llm/adapter.ts +149 -0
- package/src/llm/index.ts +2 -0
- package/src/llm/types.ts +81 -0
- package/src/monitoring/CONTEXT.md +29 -0
- package/src/monitoring/__tests__/context-tracker.test.ts +172 -0
- package/src/monitoring/__tests__/knowledge-doctor.test.ts +276 -0
- package/src/monitoring/__tests__/knowledge-evolver.test.ts +365 -0
- package/src/monitoring/__tests__/performance-analyzer.test.ts +137 -0
- package/src/monitoring/__tests__/performance-collector.test.ts +332 -0
- package/src/monitoring/__tests__/traces.test.ts +190 -0
- package/src/monitoring/constraint-doctor.ts +499 -0
- package/src/monitoring/constraint-evolver.ts +573 -0
- package/src/monitoring/context-tracker.ts +148 -0
- package/src/monitoring/index.ts +23 -0
- package/src/monitoring/knowledge-doctor.ts +323 -0
- package/src/monitoring/knowledge-evolver.ts +289 -0
- package/src/monitoring/performance-analyzer.ts +450 -0
- package/src/monitoring/performance-collector.ts +348 -0
- package/src/monitoring/trace-analyzer.ts +470 -0
- package/src/monitoring/traces.ts +339 -0
- package/src/presets/__tests__/standard.test.ts +104 -0
- package/src/presets/index.ts +16 -0
- package/src/presets/standard.ts +163 -0
- package/src/safety/CONTEXT.md +23 -0
- package/src/safety/__tests__/input-guardrail.test.ts +105 -0
- package/src/safety/__tests__/output-guardrail.test.ts +108 -0
- package/src/safety/__tests__/sandbox.test.ts +158 -0
- package/src/safety/__tests__/tool-guardrail.test.ts +127 -0
- package/src/safety/index.ts +5 -0
- package/src/safety/input-guardrail.ts +133 -0
- package/src/safety/output-guardrail.ts +174 -0
- package/src/safety/sandbox.ts +148 -0
- package/src/safety/tool-guardrail.ts +176 -0
- package/src/safety/types.ts +124 -0
- package/src/spec/CONTEXT.md +19 -0
- package/src/spec/__tests__/annotation-checker.test.ts +289 -0
- package/src/spec/annotation-checker.ts +342 -0
- package/src/tools/CONTEXT.md +25 -0
- package/src/tools/__tests__/loader.test.ts +252 -0
- package/src/tools/__tests__/registry.test.ts +157 -0
- package/src/tools/core/__tests__/core.test.ts +122 -0
- package/src/tools/core/index.ts +201 -0
- package/src/tools/core/package.json +7 -0
- package/src/tools/core/pnpm-lock.yaml +232 -0
- package/src/tools/definitions/core/code/fingerprint.sh +273 -0
- package/src/tools/definitions/core/code/fingerprint.yml +72 -0
- package/src/tools/definitions/core/code/parse.yml +68 -0
- package/src/tools/definitions/core/code/register-language.sh +475 -0
- package/src/tools/definitions/core/code/register-language.yml +74 -0
- package/src/tools/definitions/core/docker/build.yml +73 -0
- package/src/tools/definitions/core/docker/run.yml +81 -0
- package/src/tools/definitions/core/file/copy.yml +88 -0
- package/src/tools/definitions/core/file/read.yml +69 -0
- package/src/tools/definitions/core/file/write.yml +75 -0
- package/src/tools/definitions/core/git/branch.yml +61 -0
- package/src/tools/definitions/core/git/cleanup-worktree.yml +50 -0
- package/src/tools/definitions/core/git/clone.yml +69 -0
- package/src/tools/definitions/core/git/commit.yml +65 -0
- package/src/tools/definitions/core/git/push.yml +79 -0
- package/src/tools/definitions/core/git/worktree.yml +96 -0
- package/src/tools/definitions/core/notification/notify.sh +152 -0
- package/src/tools/definitions/core/notification/notify.yml +60 -0
- package/src/tools/definitions/core/npm/build.yml +75 -0
- package/src/tools/definitions/core/npm/install.yml +58 -0
- package/src/tools/definitions/core/npm/test.yml +75 -0
- package/src/tools/definitions/core/validation/check-reuse.yml +170 -0
- package/src/tools/definitions/core/validation/validate-handlers.yml +118 -0
- package/src/tools/definitions/core/validation/validate-readme-stats.yml +155 -0
- package/src/tools/definitions/core/validation/validate-yaml-syntax.yml +134 -0
- package/src/tools/definitions/ext/browser/browser-automate.sh +255 -0
- package/src/tools/definitions/ext/browser/browser-automate.yml +166 -0
- package/src/tools/definitions/ext/browser/browser-close.sh +104 -0
- package/src/tools/definitions/ext/browser/browser-close.yml +55 -0
- package/src/tools/definitions/ext/browser/browser-mcp-debug.sh +682 -0
- package/src/tools/definitions/ext/browser/browser-mcp-debug.yml +167 -0
- package/src/tools/definitions/ext/browser/browser-start.sh +101 -0
- package/src/tools/definitions/ext/browser/browser-start.yml +62 -0
- package/src/tools/definitions/registry.json +795 -0
- package/src/tools/definitions/std/README.md +350 -0
- package/src/tools/definitions/std/analysis/analyze-codebase.yml +72 -0
- package/src/tools/definitions/std/analysis/analyze-diff.yml +182 -0
- package/src/tools/definitions/std/analysis/analyze-git-diff.yml +70 -0
- package/src/tools/definitions/std/analysis/analyze-impact.yml +78 -0
- package/src/tools/definitions/std/analysis/analyze-requirements.yml +105 -0
- package/src/tools/definitions/std/analysis/brainstorm-options.yml +106 -0
- package/src/tools/definitions/std/analysis/build-graph.yml +154 -0
- package/src/tools/definitions/std/analysis/clarify-requirements.yml +90 -0
- package/src/tools/definitions/std/analysis/detect-context.yml +109 -0
- package/src/tools/definitions/std/analysis/detect-layers.yml +125 -0
- package/src/tools/definitions/std/analysis/generate-tour.yml +102 -0
- package/src/tools/definitions/std/analysis/search-enhance.yml +105 -0
- package/src/tools/definitions/std/backlog/add.yml +51 -0
- package/src/tools/definitions/std/backlog/decide.yml +39 -0
- package/src/tools/definitions/std/backlog/github-sync.yml +161 -0
- package/src/tools/definitions/std/backlog/list.yml +53 -0
- package/src/tools/definitions/std/backlog/load.yml +46 -0
- package/src/tools/definitions/std/backlog/resolve.yml +37 -0
- package/src/tools/definitions/std/backlog/update.yml +42 -0
- package/src/tools/definitions/std/bugfix/diagnose.yml +68 -0
- package/src/tools/definitions/std/bugfix/fix.yml +66 -0
- package/src/tools/definitions/std/bugfix/verify.yml +69 -0
- package/src/tools/definitions/std/constraint/check-decomposition.yml +103 -0
- package/src/tools/definitions/std/constraint/check-feature-list.yml +77 -0
- package/src/tools/definitions/std/constraint/check-progress.yml +97 -0
- package/src/tools/definitions/std/constraint/generate-report.yml +147 -0
- package/src/tools/definitions/std/deploy/commit-push.yml +33 -0
- package/src/tools/definitions/std/deploy/deploy-backend.yml +37 -0
- package/src/tools/definitions/std/deploy/deploy-frontend.yml +37 -0
- package/src/tools/definitions/std/deploy/rollback.yml +148 -0
- package/src/tools/definitions/std/design/analyze-architecture-impact.yml +113 -0
- package/src/tools/definitions/std/design/design-api.yml +55 -0
- package/src/tools/definitions/std/design/design-architecture.yml +97 -0
- package/src/tools/definitions/std/design/design-db.yml +81 -0
- package/src/tools/definitions/std/design/generate-spec.yml +182 -0
- package/src/tools/definitions/std/development/develop-api.yml +85 -0
- package/src/tools/definitions/std/development/develop-backend.yml +88 -0
- package/src/tools/definitions/std/development/develop-frontend.yml +87 -0
- package/src/tools/definitions/std/development/develop-shared-types.yml +74 -0
- package/src/tools/definitions/std/development/develop-task.yml +122 -0
- package/src/tools/definitions/std/development/develop-tests.yml +76 -0
- package/src/tools/definitions/std/development/execute-batch.yml +152 -0
- package/src/tools/definitions/std/development/fix-issues.yml +100 -0
- package/src/tools/definitions/std/development/implement-task.yml +124 -0
- package/src/tools/definitions/std/development/review-fix-cycle.yml +232 -0
- package/src/tools/definitions/std/evolution/implement.yml +125 -0
- package/src/tools/definitions/std/evolution/prioritize.yml +112 -0
- package/src/tools/definitions/std/evolution/report-gap.yml +166 -0
- package/src/tools/definitions/std/file/read.yml +21 -0
- package/src/tools/definitions/std/governance/aggregate-opinions.sh +201 -0
- package/src/tools/definitions/std/governance/aggregate-opinions.yml +79 -0
- package/src/tools/definitions/std/governance/audit-task.yml +178 -0
- package/src/tools/definitions/std/governance/cast-vote.yml +129 -0
- package/src/tools/definitions/std/governance/collect-issues.yml +16 -0
- package/src/tools/definitions/std/governance/create-signed-document.yml +23 -0
- package/src/tools/definitions/std/governance/create-voting-session.yml +101 -0
- package/src/tools/definitions/std/governance/define-stance.sh +243 -0
- package/src/tools/definitions/std/governance/define-stance.yml +82 -0
- package/src/tools/definitions/std/governance/generate-review-report.yml +23 -0
- package/src/tools/definitions/std/governance/impeach.yml +179 -0
- package/src/tools/definitions/std/governance/multi-stance-review.yml +154 -0
- package/src/tools/definitions/std/governance/prepare-artifact.yml +20 -0
- package/src/tools/definitions/std/governance/review-spec-approval.yml +262 -0
- package/src/tools/definitions/std/governance/rollback.yml +186 -0
- package/src/tools/definitions/std/governance/stance-review.yml +149 -0
- package/src/tools/definitions/std/governance/track-effect.yml +199 -0
- package/src/tools/definitions/std/governance/vote-tally.yml +144 -0
- package/src/tools/definitions/std/patch/execute.yml +51 -0
- package/src/tools/definitions/std/planning/generate-tasks.yml +36 -0
- package/src/tools/definitions/std/planning/load-tasks.yml +44 -0
- package/src/tools/definitions/std/planning/split-batch.yml +481 -0
- package/src/tools/definitions/std/planning/split-tasks.yml +359 -0
- package/src/tools/definitions/std/planning/update-tasks.yml +51 -0
- package/src/tools/definitions/std/planning/validate-tasks.yml +46 -0
- package/src/tools/definitions/std/project/decide-next-workflow.yml +38 -0
- package/src/tools/definitions/std/project/load-state.yml +31 -0
- package/src/tools/definitions/std/project/save-state.yml +32 -0
- package/src/tools/definitions/std/quality/check-quality.yml +241 -0
- package/src/tools/definitions/std/quality/debug-systematic.yml +163 -0
- package/src/tools/definitions/std/quality/handle-feedback.yml +108 -0
- package/src/tools/definitions/std/quality/review-code.yml +128 -0
- package/src/tools/definitions/std/quality/review-spec.yml +140 -0
- package/src/tools/definitions/std/quality/review-task.yml +190 -0
- package/src/tools/definitions/std/quality/run-tests.yml +80 -0
- package/src/tools/definitions/std/quick/analyze.yml +56 -0
- package/src/tools/definitions/std/quick/implement.yml +67 -0
- package/src/tools/definitions/std/quick/verify.yml +71 -0
- package/src/tools/definitions/std/verification/verify_completion.yml +113 -0
- package/src/tools/definitions/std/verification/wait-condition.yml +107 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/loader.ts +216 -0
- package/src/tools/paths.ts +15 -0
- package/src/tools/registry.ts +128 -0
- package/src/tools/types.ts +46 -0
- package/src/types/__tests__/types.test.ts +86 -0
- package/src/types/checkpoint.ts +174 -0
- package/src/types/constraint.ts +335 -0
- package/src/types/cso.ts +37 -0
- package/src/types/enforcement.ts +48 -0
- package/src/types/index.ts +59 -0
- package/src/types/iron-law.ts +50 -0
- package/src/types/passes-gate.ts +185 -0
- package/src/types/performance.ts +295 -0
- package/src/types/project-config.ts +251 -0
- package/src/types/session.ts +132 -0
- package/src/types/spec.ts +91 -0
- package/src/types/trace.ts +228 -0
- package/src/utils/detect-source-roots.ts +114 -0
- package/src/utils/exec.ts +61 -0
- package/src/verification/CONTEXT.md +23 -0
- package/src/verification/__tests__/loop.test.ts +303 -0
- package/src/verification/__tests__/rules-based.test.ts +240 -0
- package/src/verification/index.ts +3 -0
- package/src/verification/loop.ts +137 -0
- package/src/verification/rules-based.ts +153 -0
- package/src/verification/types.ts +81 -0
|
@@ -0,0 +1,1169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 约束检查引擎
|
|
3
|
+
*
|
|
4
|
+
* 三层约束体系检查:
|
|
5
|
+
* - Iron Laws:检查失败立即抛出异常
|
|
6
|
+
* - Guidelines:检查失败记录警告
|
|
7
|
+
* - Tips:检查失败记录提示
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Constraint,
|
|
12
|
+
ConstraintContext,
|
|
13
|
+
ConstraintResult,
|
|
14
|
+
ConstraintCheckResult,
|
|
15
|
+
ConstraintTrigger,
|
|
16
|
+
ConstraintLevel,
|
|
17
|
+
ConstraintViolationError,
|
|
18
|
+
} from '../../types/constraint';
|
|
19
|
+
import type { ExecutionTrace } from '../../types/trace';
|
|
20
|
+
import { getTraceCollector } from '../../monitoring/traces';
|
|
21
|
+
import { IRON_LAWS, GUIDELINES, TIPS } from './definitions';
|
|
22
|
+
import type { MergedConstraintsConfig, DocFreshnessConfig, DocFreshnessCheck } from '../../types/project-config';
|
|
23
|
+
import { FreshnessRunner, type FreshnessCheckResult } from './doc-freshness/runner';
|
|
24
|
+
import { normalizeTriggers } from '../../utils/exec';
|
|
25
|
+
import { runCommand } from '../../utils/exec';
|
|
26
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
27
|
+
import { join, relative } from 'path';
|
|
28
|
+
import * as yaml from 'js-yaml';
|
|
29
|
+
import { CheckCache } from './check-cache';
|
|
30
|
+
import { detectSourceRoots } from '../../utils/detect-source-roots';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 例外名称 → ConstraintContext 中布尔字段的映射
|
|
34
|
+
*/
|
|
35
|
+
const EXCEPTION_FIELD_MAP: Record<string, keyof ConstraintContext> = {
|
|
36
|
+
scalability_required: 'scalabilityRequired',
|
|
37
|
+
security_required: 'securityRequired',
|
|
38
|
+
performance_required: 'performanceRequired',
|
|
39
|
+
reliability_required: 'reliabilityRequired',
|
|
40
|
+
simple_typo: 'isSimpleTypo',
|
|
41
|
+
config_value_error: 'isConfigValueError',
|
|
42
|
+
missing_config: 'isMissingConfig',
|
|
43
|
+
config_file: 'isConfigFile',
|
|
44
|
+
type_definition: 'isTypeDefinition',
|
|
45
|
+
simple_accessor: 'isSimpleAccessor',
|
|
46
|
+
pure_display_component: 'isPureDisplayComponent',
|
|
47
|
+
json_parse_result: 'isJsonParseResult',
|
|
48
|
+
third_party_no_types: 'isThirdPartyNoTypes',
|
|
49
|
+
legacy_migration: 'isLegacyMigration',
|
|
50
|
+
internal_refactor: 'isInternalRefactor',
|
|
51
|
+
bug_fix_only: 'isBugFixOnly',
|
|
52
|
+
performance_optimization: 'isPerformanceOptimization',
|
|
53
|
+
redundant_code_cleanup: 'isRedundantCodeCleanup',
|
|
54
|
+
same_effect_refactor: 'isSameEffectRefactor',
|
|
55
|
+
unused_code_removal: 'isUnusedCodeRemoval',
|
|
56
|
+
external_dependency: 'isExternalDependency',
|
|
57
|
+
explicit_instruction: 'isExplicitInstruction',
|
|
58
|
+
emergency_fix: 'isEmergencyFix',
|
|
59
|
+
existing_design: 'isExistingDesign',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 约束检查器
|
|
64
|
+
*/
|
|
65
|
+
export class ConstraintChecker {
|
|
66
|
+
private static instance: ConstraintChecker;
|
|
67
|
+
|
|
68
|
+
/** 自定义约束配置(项目级) */
|
|
69
|
+
private customConfig: MergedConstraintsConfig | null = null;
|
|
70
|
+
|
|
71
|
+
/** 检查结果缓存(S7:减少重复 git diff / src scan I/O,TTL=1s 防跨测试污染) */
|
|
72
|
+
private cache: CheckCache = new CheckCache({ ttlMs: 1000 });
|
|
73
|
+
|
|
74
|
+
private constructor() {}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 获取单例实例
|
|
78
|
+
*/
|
|
79
|
+
static getInstance(): ConstraintChecker {
|
|
80
|
+
if (!ConstraintChecker.instance) {
|
|
81
|
+
ConstraintChecker.instance = new ConstraintChecker();
|
|
82
|
+
}
|
|
83
|
+
return ConstraintChecker.instance;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 设置项目级约束配置
|
|
88
|
+
*
|
|
89
|
+
* @deprecated 修改单例状态,多项目并发时存在污染风险。
|
|
90
|
+
* 请改用 checkConstraints() / beforeExecution() 的 customConfig 参数实现 per-request 隔离。
|
|
91
|
+
*/
|
|
92
|
+
setCustomConfig(config: MergedConstraintsConfig): void {
|
|
93
|
+
this.customConfig = config;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 获取当前的约束集合(内置 + 自定义)
|
|
98
|
+
*
|
|
99
|
+
* @param customConfig 可选,per-request 自定义配置(优先级高于 setCustomConfig 的单例状态)
|
|
100
|
+
*/
|
|
101
|
+
getConstraints(customConfig?: MergedConstraintsConfig | null): {
|
|
102
|
+
ironLaws: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
|
|
103
|
+
guidelines: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
|
|
104
|
+
tips: Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
|
|
105
|
+
} {
|
|
106
|
+
const config = customConfig ?? this.customConfig;
|
|
107
|
+
const source = config
|
|
108
|
+
? { ironLaws: config.ironLaws, guidelines: config.guidelines, tips: config.tips }
|
|
109
|
+
: { ironLaws: IRON_LAWS, guidelines: GUIDELINES, tips: TIPS };
|
|
110
|
+
|
|
111
|
+
// Wire unified check() method on every constraint
|
|
112
|
+
const wire = (constraints: Record<string, Constraint>) => {
|
|
113
|
+
for (const c of Object.values(constraints)) {
|
|
114
|
+
(c as any).check = (ctx: ConstraintContext) => this.check(c, ctx);
|
|
115
|
+
}
|
|
116
|
+
return constraints as Record<string, Constraint & { check: (ctx: ConstraintContext) => Promise<ConstraintResult> }>;
|
|
117
|
+
};
|
|
118
|
+
return { ironLaws: wire(source.ironLaws), guidelines: wire(source.guidelines), tips: wire(source.tips) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 检查单个约束
|
|
123
|
+
*/
|
|
124
|
+
async check(
|
|
125
|
+
constraint: Constraint,
|
|
126
|
+
context: ConstraintContext
|
|
127
|
+
): Promise<ConstraintResult> {
|
|
128
|
+
// 检查例外条件(仅 Guidelines 有效)
|
|
129
|
+
if (constraint.level === 'guideline' && constraint.exceptions) {
|
|
130
|
+
if (this.checkException(constraint, context)) {
|
|
131
|
+
return {
|
|
132
|
+
id: constraint.id,
|
|
133
|
+
level: constraint.level,
|
|
134
|
+
satisfied: true,
|
|
135
|
+
constraint,
|
|
136
|
+
message: `指导原则 ${constraint.id} 因例外条件被豁免`,
|
|
137
|
+
checkedAt: new Date(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 检查前置条件
|
|
143
|
+
const satisfied = await this.checkPrecondition(constraint, context);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
id: constraint.id,
|
|
147
|
+
level: constraint.level,
|
|
148
|
+
satisfied,
|
|
149
|
+
constraint,
|
|
150
|
+
message: satisfied ? undefined : constraint.message,
|
|
151
|
+
requiredAction: satisfied ? undefined : constraint.enforcement,
|
|
152
|
+
checkedAt: new Date(),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 检查例外条件
|
|
158
|
+
*/
|
|
159
|
+
private checkException(constraint: Constraint, context: ConstraintContext): boolean {
|
|
160
|
+
if (!constraint.exceptions) return false;
|
|
161
|
+
return constraint.exceptions.some(
|
|
162
|
+
(ex) => context[EXCEPTION_FIELD_MAP[ex]] === true
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 根据约束层级获取严重性
|
|
168
|
+
*/
|
|
169
|
+
private getSeverity(level: ConstraintLevel): 'error' | 'warning' | 'info' {
|
|
170
|
+
switch (level) {
|
|
171
|
+
case 'iron_law':
|
|
172
|
+
return 'error';
|
|
173
|
+
case 'guideline':
|
|
174
|
+
return 'warning';
|
|
175
|
+
case 'tip':
|
|
176
|
+
return 'info';
|
|
177
|
+
default:
|
|
178
|
+
return 'warning';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 判断约束是否匹配当前触发条件
|
|
184
|
+
*/
|
|
185
|
+
private matchesTrigger(constraint: Constraint, operation: ConstraintTrigger): boolean {
|
|
186
|
+
const triggers = normalizeTriggers<ConstraintTrigger>(constraint.trigger);
|
|
187
|
+
return triggers.includes(operation);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 记录约束检查的 trace
|
|
192
|
+
*/
|
|
193
|
+
private recordTrace(
|
|
194
|
+
collector: ReturnType<typeof getTraceCollector>,
|
|
195
|
+
constraint: Constraint,
|
|
196
|
+
checkResult: ConstraintResult,
|
|
197
|
+
context: ConstraintContext
|
|
198
|
+
): void {
|
|
199
|
+
collector.record({
|
|
200
|
+
constraintId: constraint.id,
|
|
201
|
+
level: constraint.level,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
result: checkResult.satisfied ? 'pass' : 'fail',
|
|
204
|
+
operation: context.operation,
|
|
205
|
+
severity: this.getSeverity(constraint.level),
|
|
206
|
+
exceptionApplied: checkResult.message?.includes('豁免')
|
|
207
|
+
? constraint.exceptions?.[0]
|
|
208
|
+
: undefined,
|
|
209
|
+
projectPath: context.projectPath,
|
|
210
|
+
sessionId: context.sessionId,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 检查约束前置条件
|
|
216
|
+
*/
|
|
217
|
+
private async checkPrecondition(
|
|
218
|
+
constraint: Constraint,
|
|
219
|
+
context: ConstraintContext
|
|
220
|
+
): Promise<boolean> {
|
|
221
|
+
const projectPath = context.projectPath || process.cwd();
|
|
222
|
+
|
|
223
|
+
switch (constraint.id) {
|
|
224
|
+
// Iron Laws
|
|
225
|
+
case 'no_bypass_checkpoint':
|
|
226
|
+
// 检查代码中是否有 skip/bypass 关键词
|
|
227
|
+
return await this.checkNoBypassCheckpoint(context);
|
|
228
|
+
|
|
229
|
+
case 'no_self_approval':
|
|
230
|
+
// 检查是否有测试证据
|
|
231
|
+
return context.hasTest === true;
|
|
232
|
+
|
|
233
|
+
case 'no_completion_without_verification':
|
|
234
|
+
// 检查是否有验证证据
|
|
235
|
+
return context.hasVerificationEvidence === true;
|
|
236
|
+
|
|
237
|
+
case 'no_test_simplification':
|
|
238
|
+
// 检查是否删除了测试
|
|
239
|
+
return await this.checkNoTestSimplification(projectPath);
|
|
240
|
+
|
|
241
|
+
case 'incremental_progress':
|
|
242
|
+
// 检查是否一次做多个任务(需要 context 提供信息)
|
|
243
|
+
// undefined = 未验证 → Iron Law 必须显式确认
|
|
244
|
+
return context.hasSingleTask === true;
|
|
245
|
+
|
|
246
|
+
case 'verify_external_capability':
|
|
247
|
+
// 检查是否已验证外部能力
|
|
248
|
+
// undefined = 未验证 → Iron Law 必须显式确认
|
|
249
|
+
return context.hasExternalCapabilityVerification === true;
|
|
250
|
+
|
|
251
|
+
case 'no_implementation_without_requirement_review':
|
|
252
|
+
// 检查实现后是否对比需求验证
|
|
253
|
+
return context.hasRequirementReview === true;
|
|
254
|
+
|
|
255
|
+
case 'no_implementation_without_requirement':
|
|
256
|
+
// 检查是否有需求文档
|
|
257
|
+
return context.hasRequirement === true;
|
|
258
|
+
|
|
259
|
+
case 'must_use_worktree':
|
|
260
|
+
// 检查是否在 worktree 中执行
|
|
261
|
+
return context.hasWorktree === true;
|
|
262
|
+
|
|
263
|
+
case 'no_fuzzy_completion_claim':
|
|
264
|
+
// 检查完成声明是否包含模糊词(含"我记得""之前说""大部分"等无验证模式)
|
|
265
|
+
return this.checkNoFuzzyWords(context.completionClaimText || '');
|
|
266
|
+
|
|
267
|
+
case 'no_claim_without_evidence':
|
|
268
|
+
// 检查是否提供了验证证据(test output / file check / spec AC matrix)
|
|
269
|
+
return context.hasVerificationEvidence === true ||
|
|
270
|
+
(context.taskDescription || '').includes('test') ||
|
|
271
|
+
context.hasTest === true;
|
|
272
|
+
|
|
273
|
+
case 'no_delete_without_context':
|
|
274
|
+
// 检查删除前是否审查了设计文档
|
|
275
|
+
return context.hasRequirementReview === true ||
|
|
276
|
+
context.hasRequirement === true ||
|
|
277
|
+
context.isExistingDesign === true;
|
|
278
|
+
|
|
279
|
+
case 'no_performative_agreement':
|
|
280
|
+
// 检查是否有表演性同意(主要由 promptInjection 驱动,这里做辅助检查)
|
|
281
|
+
return this.checkNoPerformativePatterns(context.taskDescription || '');
|
|
282
|
+
|
|
283
|
+
case 'two_stage_review_required':
|
|
284
|
+
// 检查是否完成两阶段审查
|
|
285
|
+
return context.hasTwoStageReview === true;
|
|
286
|
+
|
|
287
|
+
// Guidelines
|
|
288
|
+
case 'no_fix_without_root_cause':
|
|
289
|
+
// 检查是否有根本原因调查
|
|
290
|
+
return context.hasRootCauseInvestigation === true;
|
|
291
|
+
|
|
292
|
+
case 'no_code_without_test':
|
|
293
|
+
// 检查是否有失败的测试
|
|
294
|
+
return context.hasFailingTest === true;
|
|
295
|
+
|
|
296
|
+
case 'no_any_type':
|
|
297
|
+
// 检查代码中是否有 any 类型
|
|
298
|
+
return await this.checkNoAnyType(projectPath, context.changedFiles);
|
|
299
|
+
|
|
300
|
+
case 'simplest_solution_first':
|
|
301
|
+
// 检查是否已检查本地数据源
|
|
302
|
+
return context.hasReuseCheck === true;
|
|
303
|
+
|
|
304
|
+
case 'no_creation_without_reuse_check':
|
|
305
|
+
// 检查是否已进行复用检查
|
|
306
|
+
return context.hasReuseCheck === true;
|
|
307
|
+
|
|
308
|
+
case 'capability_sync':
|
|
309
|
+
// 检查 CAPABILITIES.md 是否更新
|
|
310
|
+
return await this.checkCapabilitySync(projectPath);
|
|
311
|
+
|
|
312
|
+
case 'no_simplification_without_approval':
|
|
313
|
+
// 检查是否简化了逻辑
|
|
314
|
+
return await this.checkNoSimplificationWithoutApproval(projectPath);
|
|
315
|
+
|
|
316
|
+
case 'no_skill_without_test':
|
|
317
|
+
// 检查是否有测试
|
|
318
|
+
return context.hasTest === true;
|
|
319
|
+
|
|
320
|
+
case 'test_coverage_required':
|
|
321
|
+
// 检查测试覆盖率
|
|
322
|
+
return await this.checkTestCoverage(projectPath);
|
|
323
|
+
|
|
324
|
+
case 'context_doc_sync':
|
|
325
|
+
return await this.checkContextDocSync(projectPath);
|
|
326
|
+
|
|
327
|
+
case 'docs_freshness':
|
|
328
|
+
return await this.checkDocsFreshness(projectPath);
|
|
329
|
+
|
|
330
|
+
case 'no_excuse_patterns':
|
|
331
|
+
// 检查是否使用了借口模式
|
|
332
|
+
return this.checkNoExcusePatterns(context.completionClaimText || context.taskDescription || '');
|
|
333
|
+
|
|
334
|
+
case 'yagni_check':
|
|
335
|
+
// 检查是否有过度设计(主要由 promptInjection 驱动)
|
|
336
|
+
return this.checkYagni(projectPath, context.changedFiles);
|
|
337
|
+
|
|
338
|
+
// Mnilax guidelines + first-principles (injectPrompt=true, behavior-level, always pass — post-checked)
|
|
339
|
+
case 'surgical_changes_only':
|
|
340
|
+
case 'no_model_for_deterministic':
|
|
341
|
+
case 'no_conflict_blending':
|
|
342
|
+
case 'read_before_write':
|
|
343
|
+
case 'follow_conventions':
|
|
344
|
+
case 'first_principles_first':
|
|
345
|
+
case 'fix_the_problem_not_the_gate':
|
|
346
|
+
case 'diagnosis_to_fix_gate':
|
|
347
|
+
// Guideline: behavior constraints, enforced via promptInjection + post-check
|
|
348
|
+
return true;
|
|
349
|
+
|
|
350
|
+
// Tips - 总是返回 true(仅提示)
|
|
351
|
+
case 'readme_required':
|
|
352
|
+
return true;
|
|
353
|
+
|
|
354
|
+
case 'doc_required_for_public_api':
|
|
355
|
+
return true;
|
|
356
|
+
|
|
357
|
+
default:
|
|
358
|
+
// 未知约束,默认通过
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 检查文本中是否包含模糊词
|
|
365
|
+
*/
|
|
366
|
+
private checkNoFuzzyWords(text: string): boolean {
|
|
367
|
+
if (!text) return true; // 没有文本时默认通过(由其他检查覆盖)
|
|
368
|
+
const fuzzyPatterns = [
|
|
369
|
+
/应该没问题/,
|
|
370
|
+
/应该可以/,
|
|
371
|
+
/大概/,
|
|
372
|
+
/可能/,
|
|
373
|
+
/好像/,
|
|
374
|
+
/似乎/,
|
|
375
|
+
/差不多/,
|
|
376
|
+
/基本完成/,
|
|
377
|
+
/大部分/,
|
|
378
|
+
// audit-learned (2026-05-18): unverified completion claims
|
|
379
|
+
/我记得删[过掉]了/,
|
|
380
|
+
/之前说删了/,
|
|
381
|
+
/已删除.*但/,
|
|
382
|
+
/大部分实现/,
|
|
383
|
+
/大部分完成/,
|
|
384
|
+
/已修复.*但/,
|
|
385
|
+
];
|
|
386
|
+
return !fuzzyPatterns.some(p => p.test(text));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 检查文本中是否包含表演性同意模式
|
|
391
|
+
*/
|
|
392
|
+
private checkNoPerformativePatterns(text: string): boolean {
|
|
393
|
+
if (!text) return true;
|
|
394
|
+
// 检查是否只有"好的/明白了"等同意词,没有后续分析
|
|
395
|
+
const performativeStart = /^(好的[,,]*\s*我[来去]做|明白了[,,]*|没问题[,,]*|ok[,,]*\s*i.?ll)/i;
|
|
396
|
+
// 如果以表演性模式开头,且总长度很短(< 50 字符),视为表演性同意
|
|
397
|
+
if (performativeStart.test(text) && text.length < 100) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 检查文本中是否包含借口模式
|
|
405
|
+
*/
|
|
406
|
+
private checkNoExcusePatterns(text: string): boolean {
|
|
407
|
+
if (!text) return true;
|
|
408
|
+
const excusePatterns = [
|
|
409
|
+
/稍后修复/,
|
|
410
|
+
/小问题/,
|
|
411
|
+
/不影响功能/,
|
|
412
|
+
/以后再说/,
|
|
413
|
+
/先这样/,
|
|
414
|
+
/临时方案/,
|
|
415
|
+
/暂时这样/,
|
|
416
|
+
];
|
|
417
|
+
return !excusePatterns.some(p => p.test(text));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 检查 YAGNI:过度设计信号
|
|
422
|
+
*
|
|
423
|
+
* 检查是否存在只有一个实现的 interface/abstract class。
|
|
424
|
+
* 主要由 promptInjection 驱动,这里是辅助检查。
|
|
425
|
+
*/
|
|
426
|
+
private async checkYagni(
|
|
427
|
+
projectPath?: string,
|
|
428
|
+
changedFiles?: string[]
|
|
429
|
+
): Promise<boolean> {
|
|
430
|
+
// 无上下文时默认通过(主要由 promptInjection 驱动)
|
|
431
|
+
if (!projectPath || !changedFiles || changedFiles.length === 0) return true;
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const path = await import('path');
|
|
435
|
+
const fs = await import('fs');
|
|
436
|
+
|
|
437
|
+
for (const file of changedFiles) {
|
|
438
|
+
if (!file.endsWith('.ts') && !file.endsWith('.tsx')) continue;
|
|
439
|
+
|
|
440
|
+
const filePath = path.join(projectPath, file);
|
|
441
|
+
if (!fs.existsSync(filePath)) continue;
|
|
442
|
+
|
|
443
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
444
|
+
|
|
445
|
+
// 检查是否有 export interface/abstract class
|
|
446
|
+
const hasInterface = /export\s+(?:default\s+)?interface\s+(\w+)/g;
|
|
447
|
+
const hasAbstract = /export\s+(?:default\s+)?abstract\s+class\s+(\w+)/g;
|
|
448
|
+
|
|
449
|
+
const interfaces = [...content.matchAll(hasInterface)].map(m => m[1]);
|
|
450
|
+
const abstracts = [...content.matchAll(hasAbstract)].map(m => m[1]);
|
|
451
|
+
|
|
452
|
+
const exportedTypes = [...interfaces, ...abstracts];
|
|
453
|
+
|
|
454
|
+
for (const typeName of exportedTypes) {
|
|
455
|
+
// 检查同一个 typeName 的 implements/extends 出现次数
|
|
456
|
+
const implPattern = new RegExp(`(?:implements|extends)\\s+${typeName}\\b`, 'g');
|
|
457
|
+
// 在整个项目中搜索(简化版:只在 changedFiles 中搜索)
|
|
458
|
+
let implCount = 0;
|
|
459
|
+
for (const f of changedFiles) {
|
|
460
|
+
if (!f.endsWith('.ts') && !f.endsWith('.tsx')) continue;
|
|
461
|
+
const fp = path.join(projectPath, f);
|
|
462
|
+
if (!fs.existsSync(fp)) continue;
|
|
463
|
+
const fc = fs.readFileSync(fp, 'utf-8');
|
|
464
|
+
const matches = fc.match(implPattern);
|
|
465
|
+
if (matches) implCount += matches.length;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// 只有一个实现者 → YAGNI 违规
|
|
469
|
+
if (implCount <= 1) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
// 文件读取失败,默认通过
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 检查 no_bypass_checkpoint:检查代码中是否有 skip/bypass 关键词
|
|
484
|
+
*/
|
|
485
|
+
private async checkNoBypassCheckpoint(context: ConstraintContext): Promise<boolean> {
|
|
486
|
+
const changedFiles = context.changedFiles || [];
|
|
487
|
+
|
|
488
|
+
if (changedFiles.length === 0) {
|
|
489
|
+
return true; // 无变更文件,默认通过
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 检查文件内容是否有 skip/bypass 关键词
|
|
493
|
+
const bypassPatterns = [
|
|
494
|
+
/\.skip\(/, // Jest/Vitest skip
|
|
495
|
+
/\.bypass\s*=/, // bypass flag
|
|
496
|
+
/skip:\s*true/, // skip option
|
|
497
|
+
/bypass\s*checkpoint/, // explicit bypass
|
|
498
|
+
/\/\/\s*skip/, // comment skip
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
for (const file of changedFiles) {
|
|
502
|
+
if (!existsSync(file)) continue;
|
|
503
|
+
try {
|
|
504
|
+
const content = readFileSync(file, 'utf-8');
|
|
505
|
+
for (const pattern of bypassPatterns) {
|
|
506
|
+
if (pattern.test(content)) {
|
|
507
|
+
return false; // 发现 bypass 关键词
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
// 文件读取失败,忽略
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 检查 no_test_simplification:检查 git diff 是否删除了测试
|
|
520
|
+
*/
|
|
521
|
+
private async checkNoTestSimplification(projectPath: string): Promise<boolean> {
|
|
522
|
+
try {
|
|
523
|
+
// 检查是否有删除测试的 diff
|
|
524
|
+
const diff = await runCommand('git diff --cached', projectPath);
|
|
525
|
+
|
|
526
|
+
// 检查是否删除了测试行
|
|
527
|
+
const deletedTestPatterns = [
|
|
528
|
+
/^-\s*(test|it|describe)\s*\(/, // 删除 test/it/describe
|
|
529
|
+
/^-\s*expect\s*\(/, // 删除 expect
|
|
530
|
+
/^-\s*\/\/\s*test/, // 删除注释的 test
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
for (const pattern of deletedTestPatterns) {
|
|
534
|
+
if (pattern.test(diff)) {
|
|
535
|
+
return false; // 发现删除测试
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return true;
|
|
540
|
+
} catch {
|
|
541
|
+
return true; // git 命令失败,默认通过
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* 检查 no_any_type:grep any 类型
|
|
547
|
+
*/
|
|
548
|
+
private async checkNoAnyType(projectPath: string, changedFiles?: string[]): Promise<boolean> {
|
|
549
|
+
try {
|
|
550
|
+
const filesToCheck = changedFiles && changedFiles.length > 0
|
|
551
|
+
? changedFiles.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'))
|
|
552
|
+
: [];
|
|
553
|
+
|
|
554
|
+
if (filesToCheck.length === 0) {
|
|
555
|
+
return true; // 无 TS 文件变更
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 检查是否有 : any 类型
|
|
559
|
+
for (const file of filesToCheck) {
|
|
560
|
+
if (!existsSync(file)) continue;
|
|
561
|
+
try {
|
|
562
|
+
const content = readFileSync(file, 'utf-8');
|
|
563
|
+
// 检查 : any(排除注释和字符串)
|
|
564
|
+
const lines = content.split('\n');
|
|
565
|
+
for (const line of lines) {
|
|
566
|
+
// 跳过注释和字符串
|
|
567
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
|
|
568
|
+
if (line.includes(': any') && !line.includes('// ') && !line.includes('/*')) {
|
|
569
|
+
return false; // 发现 any 类型
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch {
|
|
573
|
+
// 文件读取失败,忽略
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return true;
|
|
578
|
+
} catch {
|
|
579
|
+
return true; // 检查失败,默认通过
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 检查 capability_sync:检查 CAPABILITIES.md 是否与代码同步
|
|
585
|
+
*
|
|
586
|
+
* 两步验证:
|
|
587
|
+
* 1. Git diff 检查 — 当前变更的非测试文件是否在文档中有记录(增量检查)
|
|
588
|
+
* 2. 全量扫描 — src/ 下的所有源文件是否都在 CAPABILITIES.md 中(完整性检查)
|
|
589
|
+
*/
|
|
590
|
+
private async checkCapabilitySync(projectPath: string): Promise<boolean> {
|
|
591
|
+
try {
|
|
592
|
+
const capabilitiesPath = join(projectPath, 'CAPABILITIES.md');
|
|
593
|
+
if (!existsSync(capabilitiesPath)) {
|
|
594
|
+
// 检查是否有代码变更,无变更则跳过
|
|
595
|
+
const diff = await runCommand('git diff --cached --name-only', projectPath);
|
|
596
|
+
const changedCodeFiles = diff.split('\n').filter(
|
|
597
|
+
(f: string) => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js')
|
|
598
|
+
);
|
|
599
|
+
return changedCodeFiles.length === 0;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const listedFiles = this.parseCapabilitiesFiles(capabilitiesPath);
|
|
603
|
+
|
|
604
|
+
// ── Step 1: Git diff 增量检查 ──
|
|
605
|
+
const diff = await runCommand('git diff --cached --name-only', projectPath);
|
|
606
|
+
const changedCodeFiles = diff.split('\n').filter(
|
|
607
|
+
(f: string) => f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js')
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
if (changedCodeFiles.length > 0 && listedFiles.length > 0) {
|
|
611
|
+
const significantChanges = changedCodeFiles.filter(
|
|
612
|
+
(f: string) => !f.includes('__tests__') && !f.includes('.test.') && !f.includes('.spec.')
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
if (significantChanges.length > 0) {
|
|
616
|
+
const covered = significantChanges.some((changed: string) =>
|
|
617
|
+
listedFiles.some((listed: string) => changed.endsWith(listed) || changed.includes(listed))
|
|
618
|
+
);
|
|
619
|
+
if (!covered) return false;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ── Step 2: 全量 src/ 扫描(T-058 修复核心)──
|
|
624
|
+
// 检查是否有 src/ 下的源文件未在 CAPABILITIES.md 中记录
|
|
625
|
+
if (listedFiles.length > 0) {
|
|
626
|
+
const sourceRoots = detectSourceRoots(projectPath);
|
|
627
|
+
for (const root of sourceRoots) {
|
|
628
|
+
const srcDir = join(projectPath, root);
|
|
629
|
+
if (existsSync(srcDir)) {
|
|
630
|
+
const actualFiles = this.cache.getSync(`src_scan_${root}`, projectPath, () =>
|
|
631
|
+
this.findSourceFiles(srcDir, projectPath)
|
|
632
|
+
);
|
|
633
|
+
for (const file of actualFiles) {
|
|
634
|
+
if (!listedFiles.includes(file)) {
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return true;
|
|
643
|
+
} catch {
|
|
644
|
+
return true; // 检查失败,默认通过
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* 解析 CAPABILITIES.md 中表格的文件路径列
|
|
650
|
+
*/
|
|
651
|
+
private parseCapabilitiesFiles(capabilitiesPath: string): string[] {
|
|
652
|
+
try {
|
|
653
|
+
const content = readFileSync(capabilitiesPath, 'utf-8');
|
|
654
|
+
const files: string[] = [];
|
|
655
|
+
|
|
656
|
+
// 匹配 markdown 表格行中的 .ts/.tsx/.js/.jsx 文件路径(第二列)
|
|
657
|
+
const tableRowRegex = /^\|[^|]+\|\s*([^|]+?\.(?:ts|tsx|js|jsx))\s*\|/gm;
|
|
658
|
+
let match;
|
|
659
|
+
while ((match = tableRowRegex.exec(content)) !== null) {
|
|
660
|
+
files.push(match[1].trim());
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return files;
|
|
664
|
+
} catch {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* 检查 no_simplification_without_approval:检查简化关键词
|
|
671
|
+
*/
|
|
672
|
+
private async checkNoSimplificationWithoutApproval(projectPath: string): Promise<boolean> {
|
|
673
|
+
try {
|
|
674
|
+
const diff = await runCommand('git diff --cached', projectPath);
|
|
675
|
+
|
|
676
|
+
// 检查是否有简化关键词
|
|
677
|
+
const simplificationPatterns = [
|
|
678
|
+
/removed\s*test/i,
|
|
679
|
+
/simplified\s*logic/i,
|
|
680
|
+
/removed\s*validation/i,
|
|
681
|
+
/skip\s*check/i,
|
|
682
|
+
];
|
|
683
|
+
|
|
684
|
+
for (const pattern of simplificationPatterns) {
|
|
685
|
+
if (pattern.test(diff)) {
|
|
686
|
+
return false; // 发现简化关键词
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return true;
|
|
691
|
+
} catch {
|
|
692
|
+
return true; // 检查失败,默认通过
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* 检查 test_coverage_required:读取 coverage 报告
|
|
698
|
+
*/
|
|
699
|
+
private async checkTestCoverage(projectPath: string): Promise<boolean> {
|
|
700
|
+
try {
|
|
701
|
+
// 检查 coverage 报告文件
|
|
702
|
+
const coverageJsonPath = join(projectPath, 'coverage', 'coverage-final.json');
|
|
703
|
+
const coverageSummaryPath = join(projectPath, 'coverage', 'coverage-summary.json');
|
|
704
|
+
|
|
705
|
+
if (!existsSync(coverageJsonPath) && !existsSync(coverageSummaryPath)) {
|
|
706
|
+
return true; // 无 coverage 报告,默认通过
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// 尝试读取 coverage-summary.json
|
|
710
|
+
if (existsSync(coverageSummaryPath)) {
|
|
711
|
+
const summary = JSON.parse(readFileSync(coverageSummaryPath, 'utf-8'));
|
|
712
|
+
const total = summary.total || {};
|
|
713
|
+
const linesCoverage = total.lines?.pct || 0;
|
|
714
|
+
|
|
715
|
+
// 默认要求 50% 覆盖率(可配置)
|
|
716
|
+
return linesCoverage >= 50;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return true;
|
|
720
|
+
} catch {
|
|
721
|
+
return true; // 检查失败,默认通过
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 检查 context_doc_sync:关键目录是否有 CONTEXT.md
|
|
727
|
+
*/
|
|
728
|
+
private async checkContextDocSync(projectPath: string): Promise<boolean> {
|
|
729
|
+
try {
|
|
730
|
+
const configPath = join(projectPath, '.harness', 'config.yml');
|
|
731
|
+
const configContent = readFileSync(configPath, 'utf-8');
|
|
732
|
+
const config = yaml.load(configContent) as Record<string, unknown>;
|
|
733
|
+
const governance = config.governance as Record<string, unknown> | undefined;
|
|
734
|
+
const contextFiles = governance?.context_files as Record<string, unknown> | undefined;
|
|
735
|
+
|
|
736
|
+
if (!contextFiles?.enabled || !Array.isArray(contextFiles.required_dirs)) {
|
|
737
|
+
return true; // 未配置,跳过
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const requiredDirs = contextFiles.required_dirs as string[];
|
|
741
|
+
for (const dir of requiredDirs) {
|
|
742
|
+
const contextPath = join(projectPath, dir, 'CONTEXT.md');
|
|
743
|
+
if (!existsSync(contextPath)) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return true;
|
|
749
|
+
} catch {
|
|
750
|
+
return true; // 配置不存在或解析失败,跳过
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* 检查 docs_freshness:CAPABILITIES.md 文件表 + 能力清单格式 + CLAUDE.md + CHANGELOG
|
|
756
|
+
*
|
|
757
|
+
* 1. 文件表格式:检查 CAPABILITIES.md 中列出的文件是否存在(防过期引用)
|
|
758
|
+
* 2. 能力清单格式:通过 FreshnessRunner 配置驱动检查计数/目录
|
|
759
|
+
* 3. 无配置时注入内置默认配置(等价旧硬编码行为)
|
|
760
|
+
*/
|
|
761
|
+
private async checkDocsFreshness(projectPath: string): Promise<boolean> {
|
|
762
|
+
// Step 1: 文件表格式 — 检查列出的文件是否还存在
|
|
763
|
+
const fileTableResult = await this.checkCapabilitiesFreshness(projectPath);
|
|
764
|
+
if (!fileTableResult) return false;
|
|
765
|
+
|
|
766
|
+
// Step 2: 能力清单格式 + CLAUDE.md + CHANGELOG — 通过 FreshnessRunner
|
|
767
|
+
try {
|
|
768
|
+
const configPath = join(projectPath, '.harness', 'config.yml');
|
|
769
|
+
let freshnessConfig: DocFreshnessConfig | undefined;
|
|
770
|
+
let requiredDirs: string[] | undefined;
|
|
771
|
+
|
|
772
|
+
if (existsSync(configPath)) {
|
|
773
|
+
try {
|
|
774
|
+
const raw = yaml.load(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;
|
|
775
|
+
const governance = raw.governance as Record<string, unknown> | undefined;
|
|
776
|
+
freshnessConfig = governance?.doc_freshness as DocFreshnessConfig | undefined;
|
|
777
|
+
|
|
778
|
+
const contextFiles = governance?.context_files as Record<string, unknown> | undefined;
|
|
779
|
+
if (contextFiles?.enabled && Array.isArray(contextFiles.required_dirs)) {
|
|
780
|
+
requiredDirs = contextFiles.required_dirs as string[];
|
|
781
|
+
}
|
|
782
|
+
} catch {
|
|
783
|
+
// 配置解析失败,使用默认
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const runner = new FreshnessRunner();
|
|
788
|
+
let results: FreshnessCheckResult[];
|
|
789
|
+
|
|
790
|
+
if (freshnessConfig?.checks && freshnessConfig.checks.length > 0) {
|
|
791
|
+
results = runner.runAll(freshnessConfig, projectPath, { requiredDirs });
|
|
792
|
+
} else {
|
|
793
|
+
const builtIn = this.getBuiltInDocFreshnessConfig(requiredDirs);
|
|
794
|
+
results = runner.runAll({ checks: builtIn }, projectPath, { requiredDirs });
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (!results.every(r => r.pass)) return false;
|
|
798
|
+
} catch {
|
|
799
|
+
// FreshnessRunner 失败不影响整体
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* 获取内置默认文档新鲜度检查配置
|
|
807
|
+
*
|
|
808
|
+
* 等价旧硬编码的 checkCapabilitiesFreshness + checkClaudeMdFreshness 行为。
|
|
809
|
+
* 仅当项目未提供 governance.doc_freshness 配置时使用。
|
|
810
|
+
*/
|
|
811
|
+
private getBuiltInDocFreshnessConfig(_requiredDirs?: string[]): DocFreshnessCheck[] {
|
|
812
|
+
return [
|
|
813
|
+
// CONTEXT.md 存在性 — key directories must have CONTEXT.md
|
|
814
|
+
{ type: 'context_docs' },
|
|
815
|
+
// CHANGELOG 版本 vs package.json 版本 — prevents version drift
|
|
816
|
+
{ type: 'changelog_version' },
|
|
817
|
+
];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* 检查 CAPABILITIES.md 是否与源码同步(文件表格式)
|
|
822
|
+
*
|
|
823
|
+
* 检查 CAPABILITIES.md 中列出的文件路径是否仍然存在于磁盘上(防过期引用)。
|
|
824
|
+
* 多根查找:支持不同项目的源码根(harness=src/, studio=apps/api/src/ 等)。
|
|
825
|
+
* 无表格行(能力清单格式)时跳过。
|
|
826
|
+
*/
|
|
827
|
+
private async checkCapabilitiesFreshness(projectPath: string): Promise<boolean> {
|
|
828
|
+
try {
|
|
829
|
+
const capabilitiesPath = join(projectPath, 'CAPABILITIES.md');
|
|
830
|
+
if (!existsSync(capabilitiesPath)) return true;
|
|
831
|
+
const content = readFileSync(capabilitiesPath, 'utf-8');
|
|
832
|
+
|
|
833
|
+
const listedFiles: string[] = [];
|
|
834
|
+
const tableRowRegex = /^\|[^|]+\|\s*([^|]+?\.(?:ts|tsx|js|jsx))\s*\|/gm;
|
|
835
|
+
let match;
|
|
836
|
+
while ((match = tableRowRegex.exec(content)) !== null) {
|
|
837
|
+
listedFiles.push(match[1].trim());
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (listedFiles.length === 0) return true;
|
|
841
|
+
|
|
842
|
+
const sourceRoots = detectSourceRoots(projectPath);
|
|
843
|
+
const fileExists = (file: string): boolean => {
|
|
844
|
+
if (existsSync(join(projectPath, file))) return true;
|
|
845
|
+
for (const root of sourceRoots) {
|
|
846
|
+
if (existsSync(join(projectPath, root, file))) return true;
|
|
847
|
+
}
|
|
848
|
+
return false;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
for (const file of listedFiles) {
|
|
852
|
+
if (!fileExists(file)) return false;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return true;
|
|
856
|
+
} catch {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* 查找 src/ 目录中的源文件
|
|
863
|
+
*/
|
|
864
|
+
private findSourceFiles(dir: string, projectPath: string): string[] {
|
|
865
|
+
const results: string[] = [];
|
|
866
|
+
let entries: string[];
|
|
867
|
+
try {
|
|
868
|
+
entries = readdirSync(dir);
|
|
869
|
+
} catch {
|
|
870
|
+
return [];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
for (const entry of entries) {
|
|
874
|
+
if (entry === 'node_modules' || entry === '__tests__' || entry === 'dist') continue;
|
|
875
|
+
const entryPath = join(dir, entry);
|
|
876
|
+
try {
|
|
877
|
+
const stat = statSync(entryPath);
|
|
878
|
+
if (stat.isDirectory()) {
|
|
879
|
+
results.push(...this.findSourceFiles(entryPath, projectPath));
|
|
880
|
+
} else if (entry.endsWith('.ts') && !entry.endsWith('.d.ts') && entry !== 'index.ts') {
|
|
881
|
+
results.push(relative(projectPath, entryPath));
|
|
882
|
+
}
|
|
883
|
+
} catch {
|
|
884
|
+
// ignore
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return results;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* 查找适用于当前操作的约束
|
|
892
|
+
*
|
|
893
|
+
* @param context 约束上下文
|
|
894
|
+
* @param customConfig 可选,per-request 自定义配置(避免多请求间的单例状态污染)
|
|
895
|
+
*/
|
|
896
|
+
findApplicableConstraints(
|
|
897
|
+
context: ConstraintContext,
|
|
898
|
+
customConfig?: MergedConstraintsConfig | null
|
|
899
|
+
): {
|
|
900
|
+
ironLaws: Constraint[];
|
|
901
|
+
guidelines: Constraint[];
|
|
902
|
+
tips: Constraint[];
|
|
903
|
+
} {
|
|
904
|
+
const trigger = context.operation;
|
|
905
|
+
const constraints = this.getConstraints(customConfig);
|
|
906
|
+
|
|
907
|
+
const filterByTrigger = (constraintSet: Record<string, Constraint>): Constraint[] => {
|
|
908
|
+
return Object.values(constraintSet).filter(constraint => {
|
|
909
|
+
const triggers = Array.isArray(constraint.trigger)
|
|
910
|
+
? constraint.trigger
|
|
911
|
+
: [constraint.trigger];
|
|
912
|
+
return triggers.includes(trigger);
|
|
913
|
+
});
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
return {
|
|
917
|
+
ironLaws: filterByTrigger(constraints.ironLaws),
|
|
918
|
+
guidelines: filterByTrigger(constraints.guidelines),
|
|
919
|
+
tips: filterByTrigger(constraints.tips),
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* 执行三层约束检查
|
|
925
|
+
*
|
|
926
|
+
* - Iron Laws:检查失败立即抛出异常
|
|
927
|
+
* - Guidelines:检查失败记录警告
|
|
928
|
+
* - Tips:检查失败记录提示
|
|
929
|
+
*
|
|
930
|
+
* @param context 约束上下文
|
|
931
|
+
* @param customConfig 可选,per-request 自定义配置(避免多请求间的单例状态污染)
|
|
932
|
+
*/
|
|
933
|
+
async checkConstraints(
|
|
934
|
+
context: ConstraintContext,
|
|
935
|
+
customConfig?: MergedConstraintsConfig | null
|
|
936
|
+
): Promise<ConstraintCheckResult> {
|
|
937
|
+
const result: ConstraintCheckResult = {
|
|
938
|
+
ironLaws: [],
|
|
939
|
+
guidelines: [],
|
|
940
|
+
tips: [],
|
|
941
|
+
passed: true,
|
|
942
|
+
warningCount: 0,
|
|
943
|
+
tipCount: 0,
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
const traceCollector = getTraceCollector();
|
|
947
|
+
const constraints = this.getConstraints(customConfig);
|
|
948
|
+
|
|
949
|
+
// 1. Iron Laws: 必须全部通过
|
|
950
|
+
for (const constraint of Object.values(constraints.ironLaws)) {
|
|
951
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
952
|
+
|
|
953
|
+
const checkResult = await this.check(constraint, context);
|
|
954
|
+
result.ironLaws.push(checkResult);
|
|
955
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
956
|
+
|
|
957
|
+
if (!checkResult.satisfied) {
|
|
958
|
+
result.passed = false;
|
|
959
|
+
throw new ConstraintViolationError(checkResult);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// 2. Guidelines: 记录警告
|
|
964
|
+
for (const constraint of Object.values(constraints.guidelines)) {
|
|
965
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
966
|
+
|
|
967
|
+
const checkResult = await this.check(constraint, context);
|
|
968
|
+
result.guidelines.push(checkResult);
|
|
969
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
970
|
+
|
|
971
|
+
if (!checkResult.satisfied) {
|
|
972
|
+
result.warningCount++;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 3. Tips: 仅记录
|
|
977
|
+
for (const constraint of Object.values(constraints.tips)) {
|
|
978
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
979
|
+
|
|
980
|
+
const checkResult = await this.check(constraint, context);
|
|
981
|
+
result.tips.push(checkResult);
|
|
982
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
983
|
+
|
|
984
|
+
if (!checkResult.satisfied) {
|
|
985
|
+
result.tipCount++;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* S11: 安全全量检查 — 全部三层约束检查,不抛异常
|
|
994
|
+
*
|
|
995
|
+
* 区别 checkConstraints(): Iron Law 违规收集在结果中而不是抛异常。
|
|
996
|
+
* 调用方通过 result.passed + result.warningCount 判断状态。
|
|
997
|
+
*/
|
|
998
|
+
async checkConstraintsSafe(
|
|
999
|
+
context: ConstraintContext,
|
|
1000
|
+
customConfig?: MergedConstraintsConfig | null
|
|
1001
|
+
): Promise<ConstraintCheckResult> {
|
|
1002
|
+
const result: ConstraintCheckResult = {
|
|
1003
|
+
ironLaws: [],
|
|
1004
|
+
guidelines: [],
|
|
1005
|
+
tips: [],
|
|
1006
|
+
passed: true,
|
|
1007
|
+
warningCount: 0,
|
|
1008
|
+
tipCount: 0,
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
const traceCollector = getTraceCollector();
|
|
1012
|
+
const constraints = this.getConstraints(customConfig);
|
|
1013
|
+
|
|
1014
|
+
for (const constraint of Object.values(constraints.ironLaws)) {
|
|
1015
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
1016
|
+
const checkResult = await this.check(constraint, context);
|
|
1017
|
+
result.ironLaws.push(checkResult);
|
|
1018
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
1019
|
+
if (!checkResult.satisfied) result.passed = false;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
for (const constraint of Object.values(constraints.guidelines)) {
|
|
1023
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
1024
|
+
const checkResult = await this.check(constraint, context);
|
|
1025
|
+
result.guidelines.push(checkResult);
|
|
1026
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
1027
|
+
if (!checkResult.satisfied) result.warningCount++;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
for (const constraint of Object.values(constraints.tips)) {
|
|
1031
|
+
if (!this.matchesTrigger(constraint, context.operation)) continue;
|
|
1032
|
+
const checkResult = await this.check(constraint, context);
|
|
1033
|
+
result.tips.push(checkResult);
|
|
1034
|
+
this.recordTrace(traceCollector, constraint, checkResult, context);
|
|
1035
|
+
if (!checkResult.satisfied) result.tipCount++;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* 执行前检查(仅检查 Iron Laws)
|
|
1043
|
+
*
|
|
1044
|
+
* @param context 约束上下文
|
|
1045
|
+
* @param customConfig 可选,per-request 自定义配置(避免多请求间的单例状态污染)
|
|
1046
|
+
* @throws ConstraintViolationError 如果有铁律违规
|
|
1047
|
+
*/
|
|
1048
|
+
async beforeExecution(
|
|
1049
|
+
context: ConstraintContext,
|
|
1050
|
+
customConfig?: MergedConstraintsConfig | null
|
|
1051
|
+
): Promise<void> {
|
|
1052
|
+
const constraints = this.getConstraints(customConfig);
|
|
1053
|
+
const operations = normalizeTriggers(context.operation);
|
|
1054
|
+
|
|
1055
|
+
for (const constraint of Object.values(constraints.ironLaws)) {
|
|
1056
|
+
if (!normalizeTriggers(constraint.trigger).some((t) => operations.includes(t))) continue;
|
|
1057
|
+
|
|
1058
|
+
const result = await this.check(constraint, context);
|
|
1059
|
+
if (!result.satisfied) {
|
|
1060
|
+
throw new ConstraintViolationError(result);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ========================================
|
|
1067
|
+
// 快捷函数
|
|
1068
|
+
// ========================================
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* 快捷函数:检查约束
|
|
1072
|
+
*/
|
|
1073
|
+
export async function checkConstraint(
|
|
1074
|
+
constraintId: string,
|
|
1075
|
+
context: ConstraintContext
|
|
1076
|
+
): Promise<ConstraintResult> {
|
|
1077
|
+
const checker = ConstraintChecker.getInstance();
|
|
1078
|
+
const constraints = checker.getConstraints();
|
|
1079
|
+
|
|
1080
|
+
const constraint =
|
|
1081
|
+
constraints.ironLaws[constraintId] ||
|
|
1082
|
+
constraints.guidelines[constraintId] ||
|
|
1083
|
+
constraints.tips[constraintId];
|
|
1084
|
+
|
|
1085
|
+
if (!constraint) {
|
|
1086
|
+
return {
|
|
1087
|
+
id: constraintId,
|
|
1088
|
+
level: 'tip',
|
|
1089
|
+
satisfied: false,
|
|
1090
|
+
message: `未知的约束: ${constraintId}`,
|
|
1091
|
+
checkedAt: new Date(),
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return checker.check(constraint, context);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* 快捷函数:执行三层检查
|
|
1100
|
+
*
|
|
1101
|
+
* @param context 约束上下文
|
|
1102
|
+
* @param customConfig 可选,per-request 自定义配置
|
|
1103
|
+
*/
|
|
1104
|
+
export async function checkConstraints(
|
|
1105
|
+
context: ConstraintContext,
|
|
1106
|
+
customConfig?: MergedConstraintsConfig | null
|
|
1107
|
+
): Promise<ConstraintCheckResult> {
|
|
1108
|
+
return ConstraintChecker.getInstance().checkConstraints(context, customConfig);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* S11: 快捷函数 — 安全全量检查(不抛异常)
|
|
1113
|
+
*/
|
|
1114
|
+
export async function checkConstraintsSafe(
|
|
1115
|
+
context: ConstraintContext,
|
|
1116
|
+
customConfig?: MergedConstraintsConfig | null
|
|
1117
|
+
): Promise<ConstraintCheckResult> {
|
|
1118
|
+
return ConstraintChecker.getInstance().checkConstraintsSafe(context, customConfig);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* 快捷函数:执行前检查
|
|
1123
|
+
*
|
|
1124
|
+
* @param context 约束上下文
|
|
1125
|
+
* @param customConfig 可选,per-request 自定义配置
|
|
1126
|
+
*/
|
|
1127
|
+
export async function checkBeforeExecution(
|
|
1128
|
+
context: ConstraintContext,
|
|
1129
|
+
customConfig?: MergedConstraintsConfig | null
|
|
1130
|
+
): Promise<void> {
|
|
1131
|
+
return ConstraintChecker.getInstance().beforeExecution(context, customConfig);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// 导出单例
|
|
1135
|
+
export const constraintChecker = ConstraintChecker.getInstance();
|
|
1136
|
+
|
|
1137
|
+
// ========================================
|
|
1138
|
+
// 向后兼容的类和函数
|
|
1139
|
+
// ========================================
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* @deprecated 使用 ConstraintChecker 代替
|
|
1143
|
+
*/
|
|
1144
|
+
export const IronLawChecker = ConstraintChecker;
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* @deprecated 使用 checkConstraint 代替
|
|
1148
|
+
*/
|
|
1149
|
+
export async function checkIronLaw(
|
|
1150
|
+
lawId: string,
|
|
1151
|
+
context: ConstraintContext
|
|
1152
|
+
): Promise<ConstraintResult> {
|
|
1153
|
+
return checkConstraint(lawId, context);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* @deprecated 使用 checkConstraints 代替
|
|
1158
|
+
*/
|
|
1159
|
+
export async function checkAllIronLaws(
|
|
1160
|
+
context: ConstraintContext
|
|
1161
|
+
): Promise<ConstraintResult[]> {
|
|
1162
|
+
const result = await checkConstraints(context);
|
|
1163
|
+
return [...result.ironLaws, ...result.guidelines, ...result.tips];
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* @deprecated 使用 constraintChecker 代替
|
|
1168
|
+
*/
|
|
1169
|
+
export const ironLawChecker = constraintChecker;
|