@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,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStartup - Session 启动检查点验证
|
|
3
|
+
*
|
|
4
|
+
* 确保 Agent 启动时必须先读上下文,防止"失忆"乱搞。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execAsync } from '../../utils/exec';
|
|
8
|
+
import * as fs from 'fs/promises';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as yaml from 'js-yaml';
|
|
11
|
+
import type {
|
|
12
|
+
StartupCheckpoints,
|
|
13
|
+
StartupCheckpointResult,
|
|
14
|
+
StartupCheckpointType,
|
|
15
|
+
TaskListJson,
|
|
16
|
+
} from '../../types/session';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TIMEOUT = 30000; // 30 seconds
|
|
20
|
+
|
|
21
|
+
export class SessionStartup {
|
|
22
|
+
private checkpoints: StartupCheckpoints;
|
|
23
|
+
private workDir: string;
|
|
24
|
+
|
|
25
|
+
constructor(workDir: string, checkpoints: StartupCheckpoints) {
|
|
26
|
+
this.workDir = workDir;
|
|
27
|
+
this.checkpoints = checkpoints;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async run(): Promise<{ success: boolean; results: StartupCheckpointResult[]; errors: string[] }> {
|
|
31
|
+
const results: StartupCheckpointResult[] = [];
|
|
32
|
+
const errors: string[] = [];
|
|
33
|
+
|
|
34
|
+
// 1. Run required checkpoints
|
|
35
|
+
for (const type of this.checkpoints.required) {
|
|
36
|
+
const result = await this.runCheckpoint(type);
|
|
37
|
+
results.push(result);
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
errors.push(`Required checkpoint failed: ${type} - ${result.error}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Run optional checkpoints (don't fail on error)
|
|
44
|
+
for (const type of this.checkpoints.optional || []) {
|
|
45
|
+
const result = await this.runCheckpoint(type);
|
|
46
|
+
results.push(result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
success: errors.length === 0,
|
|
51
|
+
results,
|
|
52
|
+
errors,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async runCheckpoint(type: StartupCheckpointType): Promise<StartupCheckpointResult> {
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
let data: any;
|
|
61
|
+
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 'pwd':
|
|
64
|
+
data = await this.checkPwd();
|
|
65
|
+
break;
|
|
66
|
+
case 'git_log':
|
|
67
|
+
data = await this.checkGitLog();
|
|
68
|
+
break;
|
|
69
|
+
case 'git_status':
|
|
70
|
+
data = await this.checkGitStatus();
|
|
71
|
+
break;
|
|
72
|
+
case 'read_progress':
|
|
73
|
+
data = await this.checkProgress();
|
|
74
|
+
break;
|
|
75
|
+
case 'read_task_list':
|
|
76
|
+
data = await this.checkTaskList();
|
|
77
|
+
break;
|
|
78
|
+
case 'init_sh':
|
|
79
|
+
data = await this.checkInitSh();
|
|
80
|
+
break;
|
|
81
|
+
case 'basic_verification':
|
|
82
|
+
data = await this.checkBasicVerification();
|
|
83
|
+
break;
|
|
84
|
+
case 'load_context':
|
|
85
|
+
data = await this.checkLoadContext();
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unknown checkpoint type: ${type}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { type, success: true, data, duration: Date.now() - start };
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
return { type, success: false, error: error.message, duration: Date.now() - start };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private async checkPwd(): Promise<string> {
|
|
98
|
+
return this.workDir;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async checkGitLog(): Promise<string[]> {
|
|
102
|
+
try {
|
|
103
|
+
const { stdout } = await execAsync('git log --oneline -10', { cwd: this.workDir });
|
|
104
|
+
return stdout.split('\n').filter(Boolean);
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async checkGitStatus(): Promise<{ staged: string[]; unstaged: string[]; untracked: string[] }> {
|
|
111
|
+
try {
|
|
112
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: this.workDir });
|
|
113
|
+
const staged: string[] = [], unstaged: string[] = [], untracked: string[] = [];
|
|
114
|
+
|
|
115
|
+
for (const line of stdout.split('\n').filter(Boolean)) {
|
|
116
|
+
const status = line.substring(0, 2);
|
|
117
|
+
const file = line.substring(3);
|
|
118
|
+
if (status.includes('??')) untracked.push(file);
|
|
119
|
+
else if (status.match(/[MADRC]/)) staged.push(file);
|
|
120
|
+
else unstaged.push(file);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { staged, unstaged, untracked };
|
|
124
|
+
} catch {
|
|
125
|
+
return { staged: [], unstaged: [], untracked: [] };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async checkProgress(): Promise<any> {
|
|
130
|
+
const progressPath = path.join(this.workDir, '.harness', 'progress.yml');
|
|
131
|
+
try {
|
|
132
|
+
const content = await fs.readFile(progressPath, 'utf-8');
|
|
133
|
+
return yaml.load(content) as any;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async checkTaskList(): Promise<TaskListJson | null> {
|
|
140
|
+
// Try tasks.yml first (existing format)
|
|
141
|
+
const tasksPath = path.join(this.workDir, 'tasks.yml');
|
|
142
|
+
try {
|
|
143
|
+
const content = await fs.readFile(tasksPath, 'utf-8');
|
|
144
|
+
return yaml.load(content) as TaskListJson;
|
|
145
|
+
} catch {
|
|
146
|
+
// Try tasks.json
|
|
147
|
+
const jsonPath = path.join(this.workDir, 'tasks.json');
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(jsonPath, 'utf-8');
|
|
150
|
+
return JSON.parse(content);
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async checkInitSh(): Promise<boolean> {
|
|
158
|
+
const initPath = path.join(this.workDir, 'init.sh');
|
|
159
|
+
try {
|
|
160
|
+
await fs.access(initPath);
|
|
161
|
+
return true;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async checkBasicVerification(): Promise<{ hasPackageJson: boolean; hasTests: boolean; hasReadme: boolean }> {
|
|
168
|
+
const [hasPackageJson, hasTests, hasReadme] = await Promise.all([
|
|
169
|
+
fs.access(path.join(this.workDir, 'package.json')).then(() => true).catch(() => false),
|
|
170
|
+
this.hasTestFiles(),
|
|
171
|
+
fs.access(path.join(this.workDir, 'README.md')).then(() => true).catch(() => false),
|
|
172
|
+
]);
|
|
173
|
+
return { hasPackageJson, hasTests, hasReadme };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async hasTestFiles(): Promise<boolean> {
|
|
177
|
+
const testPatterns = ['test', 'spec', '__tests__'];
|
|
178
|
+
try {
|
|
179
|
+
const files = await fs.readdir(this.workDir);
|
|
180
|
+
return files.some(f => testPatterns.some(p => f.toLowerCase().includes(p)));
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async checkLoadContext(): Promise<any> {
|
|
187
|
+
const context: Record<string, string> = {};
|
|
188
|
+
const files = this.checkpoints.contextFiles || [];
|
|
189
|
+
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
try {
|
|
192
|
+
context[file] = await fs.readFile(path.join(this.workDir, file), 'utf-8');
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return context;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get current task from task list
|
|
201
|
+
*/
|
|
202
|
+
async getCurrentTask(): Promise<{ task: any; index: number } | null> {
|
|
203
|
+
const taskList = await this.checkTaskList();
|
|
204
|
+
if (!taskList?.tasks) return null;
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < taskList.tasks.length; i++) {
|
|
207
|
+
const task = taskList.tasks[i];
|
|
208
|
+
if (task && !task.passes) {
|
|
209
|
+
return { task, index: i };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Generate startup report
|
|
218
|
+
*/
|
|
219
|
+
generateReport(results: StartupCheckpointResult[]): string {
|
|
220
|
+
const lines: string[] = ['## Session Startup Report\n'];
|
|
221
|
+
|
|
222
|
+
for (const result of results) {
|
|
223
|
+
const icon = result.success ? '✅' : '❌';
|
|
224
|
+
lines.push(`${icon} ${result.type} (${result.duration}ms)`);
|
|
225
|
+
if (result.error) {
|
|
226
|
+
lines.push(` Error: ${result.error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const createSessionStartup = (workDir: string, checkpoints: StartupCheckpoints) =>
|
|
235
|
+
new SessionStartup(workDir, checkpoints);
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Default startup checkpoints for code workflows
|
|
239
|
+
*/
|
|
240
|
+
export const DEFAULT_CODE_CHECKPOINTS: StartupCheckpoints = {
|
|
241
|
+
required: ['pwd', 'git_log', 'read_task_list'],
|
|
242
|
+
optional: ['read_progress', 'init_sh', 'basic_verification'],
|
|
243
|
+
timeout: DEFAULT_TIMEOUT,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Minimal startup checkpoints
|
|
248
|
+
*/
|
|
249
|
+
export const MINIMAL_CHECKPOINTS: StartupCheckpoints = {
|
|
250
|
+
required: ['pwd', 'read_task_list'],
|
|
251
|
+
optional: [],
|
|
252
|
+
timeout: DEFAULT_TIMEOUT,
|
|
253
|
+
};
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecValidator 测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SpecValidator, validateSpec, validateAllSpecs } from '../validator';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
|
|
8
|
+
// Mock fs
|
|
9
|
+
jest.mock('fs/promises', () => ({
|
|
10
|
+
readFile: jest.fn(),
|
|
11
|
+
writeFile: jest.fn(),
|
|
12
|
+
access: jest.fn(),
|
|
13
|
+
stat: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
17
|
+
|
|
18
|
+
describe('SpecValidator', () => {
|
|
19
|
+
let validator: SpecValidator;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
// Reset singleton
|
|
24
|
+
(SpecValidator as any).instance = undefined;
|
|
25
|
+
validator = SpecValidator.getInstance();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('getInstance()', () => {
|
|
29
|
+
it('should return singleton instance', () => {
|
|
30
|
+
const v1 = SpecValidator.getInstance();
|
|
31
|
+
const v2 = SpecValidator.getInstance();
|
|
32
|
+
expect(v1).toBe(v2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should accept custom config', () => {
|
|
36
|
+
(SpecValidator as any).instance = undefined;
|
|
37
|
+
const v = SpecValidator.getInstance({ enabled: false });
|
|
38
|
+
expect(v).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('setConfig()', () => {
|
|
43
|
+
it('should update config', () => {
|
|
44
|
+
validator.setConfig({ enabled: false });
|
|
45
|
+
expect(validator).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('detectSpecType()', () => {
|
|
50
|
+
it('should detect architecture type', () => {
|
|
51
|
+
const type = validator.detectSpecType('ARCHITECTURE.md');
|
|
52
|
+
expect(type).toBe('architecture');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should detect module type', () => {
|
|
56
|
+
const type = validator.detectSpecType('specs/modules/user.yml');
|
|
57
|
+
expect(type).toBe('module');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should detect api type', () => {
|
|
61
|
+
const type = validator.detectSpecType('specs/api/auth.yaml');
|
|
62
|
+
expect(type).toBe('api');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return custom for unknown files', () => {
|
|
66
|
+
const type = validator.detectSpecType('specs/custom.json');
|
|
67
|
+
expect(type).toBe('custom');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle lowercase architecture', () => {
|
|
71
|
+
const type = validator.detectSpecType('architecture.md');
|
|
72
|
+
expect(type).toBe('architecture');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('validateFile()', () => {
|
|
77
|
+
it('should return invalid for non-existent file', async () => {
|
|
78
|
+
mockFs.access.mockRejectedValueOnce(new Error('Not found'));
|
|
79
|
+
|
|
80
|
+
const result = await validator.validateFile('/non/existent.yml');
|
|
81
|
+
|
|
82
|
+
expect(result.valid).toBe(false);
|
|
83
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('loadSchema()', () => {
|
|
88
|
+
it('should return null for invalid path', async () => {
|
|
89
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
90
|
+
|
|
91
|
+
const schema = await validator.loadSchema('/non/existent');
|
|
92
|
+
|
|
93
|
+
expect(schema).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('validateAll()', () => {
|
|
98
|
+
it('should validate multiple files', async () => {
|
|
99
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
100
|
+
|
|
101
|
+
const result = await validator.validateAll('/test/project');
|
|
102
|
+
|
|
103
|
+
expect(result).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should validate staged files', async () => {
|
|
107
|
+
// Mock exec for git diff
|
|
108
|
+
const mockExec = jest.fn().mockResolvedValue({ stdout: 'specs/test.yml\nARCHITECTURE.md\n' });
|
|
109
|
+
jest.doMock('child_process', () => ({ exec: mockExec }));
|
|
110
|
+
jest.doMock('util', () => ({ promisify: () => mockExec }));
|
|
111
|
+
|
|
112
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
113
|
+
|
|
114
|
+
const result = await validator.validateAll('/test/project', true);
|
|
115
|
+
|
|
116
|
+
expect(result).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('validateFile() with YAML', () => {
|
|
121
|
+
it('should validate valid YAML file', async () => {
|
|
122
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
123
|
+
mockFs.readFile.mockResolvedValue('name: test\nversion: 1');
|
|
124
|
+
|
|
125
|
+
const result = await validator.validateFile('specs/test.yml');
|
|
126
|
+
|
|
127
|
+
expect(result.valid).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should detect invalid YAML', async () => {
|
|
131
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
132
|
+
mockFs.readFile.mockResolvedValue('invalid: yaml: content:');
|
|
133
|
+
|
|
134
|
+
const result = await validator.validateFile('specs/bad.yml');
|
|
135
|
+
|
|
136
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('validateFile() with ARCHITECTURE.md', () => {
|
|
141
|
+
it('should validate valid ARCHITECTURE.md', async () => {
|
|
142
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
143
|
+
mockFs.readFile.mockResolvedValue('# Architecture\n\n## Module A\n\nContent...');
|
|
144
|
+
|
|
145
|
+
const result = await validator.validateFile('ARCHITECTURE.md');
|
|
146
|
+
|
|
147
|
+
expect(result.type).toBe('architecture');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should warn on missing headers', async () => {
|
|
151
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
152
|
+
mockFs.readFile.mockResolvedValue('Just plain text without headers');
|
|
153
|
+
|
|
154
|
+
const result = await validator.validateFile('ARCHITECTURE.md');
|
|
155
|
+
|
|
156
|
+
// 警告可能被添加到 errors 或 warnings
|
|
157
|
+
const hasWarning = result.warnings.length > 0 || result.errors.some(e => e.severity === 'warning');
|
|
158
|
+
expect(hasWarning || result.valid).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('isSpecFile()', () => {
|
|
163
|
+
it('should identify ARCHITECTURE.md as spec file', () => {
|
|
164
|
+
// 通过 validateAll 间接测试 - ARCHITECTURE.md 会被过滤为 spec 文件
|
|
165
|
+
// 直接测试 isSpecFile (private, 通过 validateAll 间接验证)
|
|
166
|
+
expect(validator.detectSpecType('ARCHITECTURE.md')).toBe('architecture');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should identify specs directory files', async () => {
|
|
170
|
+
// specs/ 下的文件应该被识别
|
|
171
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
172
|
+
mockFs.readFile.mockResolvedValue('# Test\n\nContent');
|
|
173
|
+
const result = await validator.validateFile('specs/modules/test.md');
|
|
174
|
+
expect(result.file).toBe('specs/modules/test.md');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('basicValidation()', () => {
|
|
179
|
+
it('should return valid for empty content', async () => {
|
|
180
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
181
|
+
mockFs.readFile.mockResolvedValue('');
|
|
182
|
+
|
|
183
|
+
const result = await validator.validateFile('specs/empty.yml');
|
|
184
|
+
|
|
185
|
+
// 空 YAML 是合法的
|
|
186
|
+
expect(result.valid).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('fileExists()', () => {
|
|
191
|
+
it('should return true for existing file', async () => {
|
|
192
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
193
|
+
mockFs.readFile.mockResolvedValue('test');
|
|
194
|
+
|
|
195
|
+
const result = await validator.validateFile('specs/existing.yml');
|
|
196
|
+
|
|
197
|
+
expect(result).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should return false for non-existing file', async () => {
|
|
201
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
202
|
+
|
|
203
|
+
const result = await validator.validateFile('specs/nonexistent.yml');
|
|
204
|
+
|
|
205
|
+
expect(result.valid).toBe(false);
|
|
206
|
+
expect(result.errors[0]?.message).toContain('不存在');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('dynamicImport()', () => {
|
|
211
|
+
it('should handle import errors', async () => {
|
|
212
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
213
|
+
|
|
214
|
+
const schema = await validator.loadSchema('/invalid/module.ts');
|
|
215
|
+
|
|
216
|
+
expect(schema).toBeNull();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('loadSchema() with cache', () => {
|
|
221
|
+
it('should return cached schema on second call', async () => {
|
|
222
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
223
|
+
|
|
224
|
+
// First call - no cache
|
|
225
|
+
const schema1 = await validator.loadSchema('/cached/path');
|
|
226
|
+
// Second call - should hit cache (which is empty since first call failed)
|
|
227
|
+
const schema2 = await validator.loadSchema('/cached/path');
|
|
228
|
+
|
|
229
|
+
expect(schema1).toBeNull();
|
|
230
|
+
expect(schema2).toBeNull();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should clear cache on setConfig', () => {
|
|
234
|
+
validator.setConfig({ schemaPath: './new/path' });
|
|
235
|
+
// Cache should be cleared
|
|
236
|
+
expect(validator).toBeDefined();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('detectSpecType() edge cases', () => {
|
|
241
|
+
it('should detect modules (plural) path', () => {
|
|
242
|
+
const type = validator.detectSpecType('src/modules/auth.ts');
|
|
243
|
+
expect(type).toBe('module');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should detect apis (plural) path', () => {
|
|
247
|
+
const type = validator.detectSpecType('src/apis/users.ts');
|
|
248
|
+
expect(type).toBe('api');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should return custom for regular files', () => {
|
|
252
|
+
const type = validator.detectSpecType('src/utils/helper.ts');
|
|
253
|
+
expect(type).toBe('custom');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('getStagedFiles()', () => {
|
|
258
|
+
it('should handle git errors', async () => {
|
|
259
|
+
// git 命令失败时返回空数组
|
|
260
|
+
const result = await validator.validateAll('/test', true);
|
|
261
|
+
|
|
262
|
+
// 可能无法找到 spec 文件
|
|
263
|
+
expect(result).toBeDefined();
|
|
264
|
+
expect(result.total).toBeGreaterThanOrEqual(0);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('validate with custom schema', () => {
|
|
269
|
+
it('should use provided schema', async () => {
|
|
270
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
271
|
+
mockFs.readFile.mockResolvedValue('custom content');
|
|
272
|
+
|
|
273
|
+
const customSchema = {
|
|
274
|
+
name: 'custom',
|
|
275
|
+
validate: async () => ({
|
|
276
|
+
valid: true,
|
|
277
|
+
file: 'test.yml',
|
|
278
|
+
type: 'custom' as const,
|
|
279
|
+
errors: [],
|
|
280
|
+
warnings: [],
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const result = await validator.validateFile('test.yml', customSchema);
|
|
285
|
+
|
|
286
|
+
expect(result.valid).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should handle schema validation errors', async () => {
|
|
290
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
291
|
+
mockFs.readFile.mockResolvedValue('bad content');
|
|
292
|
+
|
|
293
|
+
const failingSchema = {
|
|
294
|
+
name: 'failing',
|
|
295
|
+
validate: async () => ({
|
|
296
|
+
valid: false,
|
|
297
|
+
file: 'test.yml',
|
|
298
|
+
type: 'custom' as const,
|
|
299
|
+
errors: [{ path: '', message: 'Schema error', severity: 'error' as const }],
|
|
300
|
+
warnings: [],
|
|
301
|
+
}),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const result = await validator.validateFile('test.yml', failingSchema);
|
|
305
|
+
|
|
306
|
+
expect(result.valid).toBe(false);
|
|
307
|
+
expect(result.errors[0]?.message).toBe('Schema error');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should handle schema throwing exception', async () => {
|
|
311
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
312
|
+
mockFs.readFile.mockResolvedValue('content');
|
|
313
|
+
|
|
314
|
+
const throwingSchema = {
|
|
315
|
+
name: 'throwing',
|
|
316
|
+
validate: async () => {
|
|
317
|
+
throw new Error('Validation exception');
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const result = await validator.validateFile('test.yml', throwingSchema);
|
|
322
|
+
|
|
323
|
+
expect(result.valid).toBe(false);
|
|
324
|
+
expect(result.errors[0]?.message).toContain('验证失败');
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('validateFile() without schema', () => {
|
|
329
|
+
it('should use basicValidation when no schema provided', async () => {
|
|
330
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
331
|
+
mockFs.readFile.mockResolvedValue('# Title\n\n## Section\n\nContent');
|
|
332
|
+
|
|
333
|
+
const result = await validator.validateFile('ARCHITECTURE.md');
|
|
334
|
+
|
|
335
|
+
expect(result.type).toBe('architecture');
|
|
336
|
+
expect(result.valid).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should detect missing headers in architecture file', async () => {
|
|
340
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
341
|
+
mockFs.readFile.mockResolvedValue('Plain text without any headers');
|
|
342
|
+
|
|
343
|
+
const result = await validator.validateFile('ARCHITECTURE.md');
|
|
344
|
+
|
|
345
|
+
expect(result.type).toBe('architecture');
|
|
346
|
+
// Should have a warning about missing headers
|
|
347
|
+
const hasWarning = result.errors.some(e => e.severity === 'warning');
|
|
348
|
+
expect(hasWarning).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('convenience functions', () => {
|
|
353
|
+
it('validateSpec should work without schemaPath', async () => {
|
|
354
|
+
mockFs.access.mockResolvedValue(undefined);
|
|
355
|
+
mockFs.readFile.mockResolvedValue('content');
|
|
356
|
+
|
|
357
|
+
const result = await validateSpec('specs/test.yml');
|
|
358
|
+
expect(result).toBeDefined();
|
|
359
|
+
expect(result.file).toBe('specs/test.yml');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('validateSpec should work with schemaPath', async () => {
|
|
363
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
364
|
+
|
|
365
|
+
const result = await validateSpec('specs/test.yml', '/non/existent/schema');
|
|
366
|
+
expect(result).toBeDefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('validateAllSpecs should return batch result', async () => {
|
|
370
|
+
mockFs.access.mockRejectedValue(new Error('Not found'));
|
|
371
|
+
|
|
372
|
+
const result = await validateAllSpecs('/test/project');
|
|
373
|
+
expect(result).toBeDefined();
|
|
374
|
+
expect(typeof result.total).toBe('number');
|
|
375
|
+
expect(typeof result.passed).toBe('number');
|
|
376
|
+
expect(typeof result.failed).toBe('number');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
});
|