sillyspec 3.27.2 → 3.27.6
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/.claude/skills/sillyspec-execute/SKILL.md +8 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
- package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
- package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
- package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
- package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
- package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
- package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
- package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-archive/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-auto/SKILL.md +93 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-commit/SKILL.md +67 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-continue/SKILL.md +47 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-execute/SKILL.md +214 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-explore/SKILL.md +102 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-export/SKILL.md +57 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-init/SKILL.md +177 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-plan/SKILL.md +156 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-propose/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-quick/SKILL.md +116 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-resume/SKILL.md +70 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-scan/SKILL.md +79 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-state/SKILL.md +64 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-status/SKILL.md +54 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-verify/SKILL.md +87 -0
- package/.worktrees/8d2d0392/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
- package/.worktrees/8d2d0392/.claude/skills/verify-per-user/SKILL.md +6 -0
- package/.worktrees/8d2d0392/.gitattributes +2 -0
- package/.worktrees/8d2d0392/.husky/pre-push +20 -0
- package/.worktrees/8d2d0392/README.md +203 -0
- package/.worktrees/8d2d0392/SKILL.md +91 -0
- package/.worktrees/8d2d0392/architecture-analysis.md +448 -0
- package/.worktrees/8d2d0392/bin/sillyspec.js +2 -0
- package/.worktrees/8d2d0392/eval/README.md +86 -0
- package/.worktrees/8d2d0392/eval/eval.config.json +28 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/cli.mjs +63 -0
- package/.worktrees/8d2d0392/eval/lib/adapters/dryrun.mjs +27 -0
- package/.worktrees/8d2d0392/eval/lib/config.mjs +82 -0
- package/.worktrees/8d2d0392/eval/lib/db.mjs +76 -0
- package/.worktrees/8d2d0392/eval/lib/report.mjs +120 -0
- package/.worktrees/8d2d0392/eval/lib/runner.mjs +157 -0
- package/.worktrees/8d2d0392/eval/lib/tasks.mjs +73 -0
- package/.worktrees/8d2d0392/eval/lib/util.mjs +107 -0
- package/.worktrees/8d2d0392/eval/lib/workdir.mjs +30 -0
- package/.worktrees/8d2d0392/eval/run.mjs +83 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/001-slugify/verify.mjs +28 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/002-each-day/verify.mjs +31 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/package.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/task.json +6 -0
- package/.worktrees/8d2d0392/eval/tasks/003-word-report/verify.mjs +38 -0
- package/.worktrees/8d2d0392/logo.jpg +0 -0
- package/.worktrees/8d2d0392/package-lock.json +977 -0
- package/.worktrees/8d2d0392/package.json +41 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/favicon.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/index.html +16 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/logo.jpg +0 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-overview.html +256 -0
- package/.worktrees/8d2d0392/packages/dashboard/package.json +25 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/executor.js +95 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/index.js +679 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/parser.js +526 -0
- package/.worktrees/8d2d0392/packages/dashboard/server/watcher.js +357 -0
- package/.worktrees/8d2d0392/src/agent-session-log.js +803 -0
- package/.worktrees/8d2d0392/src/change-list.js +290 -0
- package/.worktrees/8d2d0392/src/change-risk-profile.js +339 -0
- package/.worktrees/8d2d0392/src/check-primitives.js +65 -0
- package/.worktrees/8d2d0392/src/classify-change.js +109 -0
- package/.worktrees/8d2d0392/src/commit-suggest.js +202 -0
- package/.worktrees/8d2d0392/src/config-cat.js +88 -0
- package/.worktrees/8d2d0392/src/config-schema.js +328 -0
- package/.worktrees/8d2d0392/src/constants.js +97 -0
- package/.worktrees/8d2d0392/src/contract-matrix.js +533 -0
- package/.worktrees/8d2d0392/src/datetime.js +20 -0
- package/.worktrees/8d2d0392/src/db-engine.js +55 -0
- package/.worktrees/8d2d0392/src/db.js +381 -0
- package/.worktrees/8d2d0392/src/decision-distill.js +517 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/local-agent.js +155 -0
- package/.worktrees/8d2d0392/src/dispatch/backends/sillyhub-mcp.js +245 -0
- package/.worktrees/8d2d0392/src/dispatch/probe.js +246 -0
- package/.worktrees/8d2d0392/src/dispatch/strategy.js +121 -0
- package/.worktrees/8d2d0392/src/docs-check.js +825 -0
- package/.worktrees/8d2d0392/src/docs-debt.js +393 -0
- package/.worktrees/8d2d0392/src/docs-gate.js +107 -0
- package/.worktrees/8d2d0392/src/doctor-diagnostics.js +1099 -0
- package/.worktrees/8d2d0392/src/endpoint-extractor.js +409 -0
- package/.worktrees/8d2d0392/src/foreign-declared.js +78 -0
- package/.worktrees/8d2d0392/src/fs-atomic.js +73 -0
- package/.worktrees/8d2d0392/src/git-helper.js +150 -0
- package/.worktrees/8d2d0392/src/hooks/claude-pre-tool-use.cjs +130 -0
- package/.worktrees/8d2d0392/src/hooks/worktree-guard.js +892 -0
- package/.worktrees/8d2d0392/src/index.js +2854 -0
- package/.worktrees/8d2d0392/src/init.js +626 -0
- package/.worktrees/8d2d0392/src/knowledge-match.js +231 -0
- package/.worktrees/8d2d0392/src/local-detect.js +128 -0
- package/.worktrees/8d2d0392/src/local-register.js +104 -0
- package/.worktrees/8d2d0392/src/machine-interface.js +543 -0
- package/.worktrees/8d2d0392/src/migrate.js +127 -0
- package/.worktrees/8d2d0392/src/module-impact.js +166 -0
- package/.worktrees/8d2d0392/src/module-resolve.js +222 -0
- package/.worktrees/8d2d0392/src/modules.js +580 -0
- package/.worktrees/8d2d0392/src/plan-adopt-waves.js +126 -0
- package/.worktrees/8d2d0392/src/progress/change-registry.js +336 -0
- package/.worktrees/8d2d0392/src/progress/consistency-doctor.js +355 -0
- package/.worktrees/8d2d0392/src/progress/shared.js +36 -0
- package/.worktrees/8d2d0392/src/progress/stage-machine.js +594 -0
- package/.worktrees/8d2d0392/src/progress/step-store.js +265 -0
- package/.worktrees/8d2d0392/src/progress.js +1304 -0
- package/.worktrees/8d2d0392/src/quick-recommend.js +115 -0
- package/.worktrees/8d2d0392/src/quicklog.js +883 -0
- package/.worktrees/8d2d0392/src/review-tier.js +77 -0
- package/.worktrees/8d2d0392/src/run/command.js +1641 -0
- package/.worktrees/8d2d0392/src/run/complete-handlers.js +1552 -0
- package/.worktrees/8d2d0392/src/run/complete.js +1259 -0
- package/.worktrees/8d2d0392/src/run/concurrent-detect.js +156 -0
- package/.worktrees/8d2d0392/src/run/gates.js +1094 -0
- package/.worktrees/8d2d0392/src/run/multi-repo-context.js +208 -0
- package/.worktrees/8d2d0392/src/run/next.js +128 -0
- package/.worktrees/8d2d0392/src/run/prompt.js +843 -0
- package/.worktrees/8d2d0392/src/run/quick-audit.js +186 -0
- package/.worktrees/8d2d0392/src/run/scan-profile.js +269 -0
- package/.worktrees/8d2d0392/src/run/shared.js +1562 -0
- package/.worktrees/8d2d0392/src/run/stage.js +587 -0
- package/.worktrees/8d2d0392/src/run.js +23 -0
- package/.worktrees/8d2d0392/src/scan-diff.js +388 -0
- package/.worktrees/8d2d0392/src/scan-postcheck.js +524 -0
- package/.worktrees/8d2d0392/src/scan-staleness.js +95 -0
- package/.worktrees/8d2d0392/src/setup.js +414 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/client.js +597 -0
- package/.worktrees/8d2d0392/src/sillyhub-mcp/config.js +62 -0
- package/.worktrees/8d2d0392/src/spec-dir-typo.js +50 -0
- package/.worktrees/8d2d0392/src/spec-sync.js +358 -0
- package/.worktrees/8d2d0392/src/stage-contract-engine.js +171 -0
- package/.worktrees/8d2d0392/src/stage-contract-spec.js +551 -0
- package/.worktrees/8d2d0392/src/stage-contract.js +948 -0
- package/.worktrees/8d2d0392/src/stage-review.js +597 -0
- package/.worktrees/8d2d0392/src/stages/archive.js +199 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm-auto.js +285 -0
- package/.worktrees/8d2d0392/src/stages/brainstorm.js +624 -0
- package/.worktrees/8d2d0392/src/stages/cmd-existence.js +145 -0
- package/.worktrees/8d2d0392/src/stages/doctor.js +540 -0
- package/.worktrees/8d2d0392/src/stages/execute.js +1277 -0
- package/.worktrees/8d2d0392/src/stages/explore.js +43 -0
- package/.worktrees/8d2d0392/src/stages/index.js +29 -0
- package/.worktrees/8d2d0392/src/stages/knowledge.js +514 -0
- package/.worktrees/8d2d0392/src/stages/plan-postcheck.js +1398 -0
- package/.worktrees/8d2d0392/src/stages/plan.js +651 -0
- package/.worktrees/8d2d0392/src/stages/quick.js +167 -0
- package/.worktrees/8d2d0392/src/stages/scan.js +608 -0
- package/.worktrees/8d2d0392/src/stages/status.js +64 -0
- package/.worktrees/8d2d0392/src/stages/verify.js +246 -0
- package/.worktrees/8d2d0392/src/sync.js +1470 -0
- package/.worktrees/8d2d0392/src/task-review.js +1498 -0
- package/.worktrees/8d2d0392/src/taskcard-placeholders.js +21 -0
- package/.worktrees/8d2d0392/src/taskcard.js +217 -0
- package/.worktrees/8d2d0392/src/verify-postcheck.js +1718 -0
- package/.worktrees/8d2d0392/src/verify-probes.js +327 -0
- package/.worktrees/8d2d0392/src/version.js +19 -0
- package/.worktrees/8d2d0392/src/workflow.js +600 -0
- package/.worktrees/8d2d0392/src/workspace.js +125 -0
- package/.worktrees/8d2d0392/src/worktree-apply.js +1611 -0
- package/.worktrees/8d2d0392/src/worktree-deps.js +576 -0
- package/.worktrees/8d2d0392/src/worktree.js +1838 -0
- package/.worktrees/8d2d0392/templates/claude-instruction.md +23 -0
- package/.worktrees/8d2d0392/templates/prompts/taskcard-rules.md +29 -0
- package/.worktrees/8d2d0392/templates/prompts/testcase-design.md +12 -0
- package/.worktrees/8d2d0392/templates/prompts/verify-probes.md +32 -0
- package/.worktrees/8d2d0392/templates/workflows/archive-impact.yaml +79 -0
- package/.worktrees/8d2d0392/templates/workflows/scan-docs.yaml +132 -0
- package/architecture-analysis.md +448 -0
- package/eval/README.md +86 -0
- package/eval/eval.config.json +28 -0
- package/eval/lib/adapters/cli.mjs +63 -0
- package/eval/lib/adapters/dryrun.mjs +27 -0
- package/eval/lib/config.mjs +82 -0
- package/eval/lib/db.mjs +76 -0
- package/eval/lib/report.mjs +120 -0
- package/eval/lib/runner.mjs +157 -0
- package/eval/lib/tasks.mjs +73 -0
- package/eval/lib/util.mjs +107 -0
- package/eval/lib/workdir.mjs +30 -0
- package/eval/run.mjs +83 -0
- package/eval/tasks/001-slugify/files/package.json +6 -0
- package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
- package/eval/tasks/001-slugify/task.json +6 -0
- package/eval/tasks/001-slugify/verify.mjs +28 -0
- package/eval/tasks/002-each-day/files/package.json +6 -0
- package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
- package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
- package/eval/tasks/002-each-day/task.json +6 -0
- package/eval/tasks/002-each-day/verify.mjs +31 -0
- package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
- package/eval/tasks/003-word-report/files/package.json +6 -0
- package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
- package/eval/tasks/003-word-report/task.json +6 -0
- package/eval/tasks/003-word-report/verify.mjs +38 -0
- package/package.json +4 -2
- package/src/agent-session-log.js +45 -5
- package/src/change-risk-profile.js +83 -14
- package/src/config-schema.js +18 -3
- package/src/contract-matrix.js +90 -29
- package/src/datetime.js +20 -0
- package/src/decision-distill.js +517 -0
- package/src/dispatch/backends/local-agent.js +4 -4
- package/src/docs-check.js +227 -0
- package/src/docs-debt.js +187 -24
- package/src/doctor-diagnostics.js +102 -2
- package/src/endpoint-extractor.js +41 -52
- package/src/foreign-declared.js +155 -0
- package/src/index.js +95 -13
- package/src/init.js +9 -7
- package/src/knowledge-match.js +107 -6
- package/src/local-register.js +7 -1
- package/src/module-resolve.js +12 -1
- package/src/modules.js +2 -1
- package/src/plan-adopt-waves.js +126 -0
- package/src/quicklog.js +50 -4
- package/src/run/command.js +124 -44
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +5 -2
- package/src/run/gates.js +104 -22
- package/src/run/prompt.js +84 -42
- package/src/run/quick-audit.js +11 -0
- package/src/run/shared.js +152 -10
- package/src/run/stage.js +8 -5
- package/src/run.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +3 -2
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +50 -1
- package/src/stages/brainstorm.js +13 -0
- package/src/stages/doctor.js +157 -1
- package/src/stages/execute.js +32 -6
- package/src/stages/plan-postcheck.js +76 -17
- package/src/stages/plan.js +9 -4
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +32 -15
- package/src/sync.js +41 -3
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +43 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +73 -10
- package/src/worktree-apply.js +569 -227
- package/src/worktree-deps.js +150 -1
- package/src/worktree.js +285 -77
- package/templates/prompts/taskcard-rules.md +2 -1
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contract-matrix.js — API Contract Matrix 生成与注入
|
|
3
|
+
*
|
|
4
|
+
* plan 阶段:识别 task 之间的 provider/consumer 关系,生成契约矩阵
|
|
5
|
+
* execute 阶段:
|
|
6
|
+
* - 后端 task 完成后自动提取 endpoint artifact
|
|
7
|
+
* - 前端 task 开始时注入上游契约
|
|
8
|
+
* verify 阶段:读取 artifact 做 parity check
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs'
|
|
12
|
+
import { join, resolve, basename, dirname, relative } from 'path'
|
|
13
|
+
import { gitQuiet } from './git-helper.js'
|
|
14
|
+
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
15
|
+
import {
|
|
16
|
+
scanBackendEndpoints,
|
|
17
|
+
scanFrontendApiCalls,
|
|
18
|
+
normalizePath,
|
|
19
|
+
diffApiParity,
|
|
20
|
+
} from './endpoint-extractor.js'
|
|
21
|
+
import { parseTaskContracts } from './stages/plan-postcheck.js'
|
|
22
|
+
|
|
23
|
+
// ─── 关键词检测 ─────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const PROVIDER_KEYWORDS = /router|routes|endpoint|api|backend|controller|fastapi|flask|express|koa|spring/i
|
|
26
|
+
const CONSUMER_KEYWORDS = /frontend|client|service|apiFetch|request|fetch|axios|http/i
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 判断一个 task 文档是 provider(产出 API)还是 consumer(消费 API)
|
|
30
|
+
* @param {string} taskContent - task markdown 内容
|
|
31
|
+
* @returns {{ isProvider: boolean, isConsumer: boolean, confidence: number }}
|
|
32
|
+
*/
|
|
33
|
+
export function classifyTask(taskContent) {
|
|
34
|
+
const isProvider = PROVIDER_KEYWORDS.test(taskContent)
|
|
35
|
+
const isConsumer = CONSUMER_KEYWORDS.test(taskContent)
|
|
36
|
+
// 避免所有 task 都被标记(因为几乎所有 task 都含 "api")
|
|
37
|
+
// 加强判定:provider 要命中 router/endpoint/backend/controller + api
|
|
38
|
+
// consumer 要命中 frontend/client/apiFetch + api
|
|
39
|
+
const providerStrong = /router|endpoint|backend|controller|fastapi|flask/i.test(taskContent)
|
|
40
|
+
const consumerStrong = /frontend|apiFetch|axios|api.*client/i.test(taskContent)
|
|
41
|
+
const providerConfidence = providerStrong ? 0.8 : (isProvider ? 0.4 : 0)
|
|
42
|
+
const consumerConfidence = consumerStrong ? 0.8 : (isConsumer ? 0.4 : 0)
|
|
43
|
+
return {
|
|
44
|
+
isProvider: providerConfidence >= 0.4,
|
|
45
|
+
isConsumer: consumerConfidence >= 0.4,
|
|
46
|
+
confidence: Math.max(providerConfidence, consumerConfidence),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 解析 task 依赖关系,识别 provider → consumer 对(2026-08-20-task-truth-unify D-003@v1:
|
|
52
|
+
* 行内 depends_on 标注源迁 tasks.md,函数内 tasks.md 优先、plan.md 回退拼接)
|
|
53
|
+
* @param {string} planContent - plan.md 内容(任务总表依赖列兜底源)
|
|
54
|
+
* @param {string} changeDir - changes/<name>/ 目录
|
|
55
|
+
* @returns {Array<{ provider: string, consumer: string, type: string }>}
|
|
56
|
+
*/
|
|
57
|
+
export function buildContractMatrix(planContent, changeDir) {
|
|
58
|
+
const contracts = []
|
|
59
|
+
|
|
60
|
+
// 解析 task 依赖关系(tasks.md 注册表行内标注优先;plan.md 总表列兜底——两源拼合去重)
|
|
61
|
+
// tasks.md 格式: - [ ] task-04: 名称 (depends_on: task-01,02);
|
|
62
|
+
// plan.md 总表: | task-04 | ... | 01 |
|
|
63
|
+
const tasksMdPath = changeDir ? join(changeDir, 'tasks.md') : null
|
|
64
|
+
let registryContent = ''
|
|
65
|
+
if (tasksMdPath && existsSync(tasksMdPath)) {
|
|
66
|
+
try { registryContent = readFileSync(tasksMdPath, 'utf8') } catch { registryContent = '' }
|
|
67
|
+
}
|
|
68
|
+
const taskDeps = parseTaskDependencies(registryContent + '\n' + String(planContent || ''))
|
|
69
|
+
|
|
70
|
+
// 读取各 task 文档,分类 provider/consumer
|
|
71
|
+
// classify consumers(taskDeps keys)+ providers(deps 值)。
|
|
72
|
+
// 旧实现只 classify consumers → provider task 不在 keys 里 → providerClass 恒 undefined
|
|
73
|
+
// → contracts 恒空 → 端点契约管线(buildConsumerInjection / extractArtifactsForChange)全失效。
|
|
74
|
+
const taskClasses = {}
|
|
75
|
+
const allTaskNames = new Set([...Object.keys(taskDeps), ...Object.values(taskDeps).flat()])
|
|
76
|
+
for (const taskName of allTaskNames) {
|
|
77
|
+
const taskFile = join(changeDir, 'tasks', `${taskName}.md`)
|
|
78
|
+
if (existsSync(taskFile)) {
|
|
79
|
+
taskClasses[taskName] = classifyTask(readFileSync(taskFile, 'utf8'))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 识别契约对:A depends_on B,且 A 是 consumer,B 是 provider
|
|
84
|
+
for (const [consumer, deps] of Object.entries(taskDeps)) {
|
|
85
|
+
const consumerClass = taskClasses[consumer]
|
|
86
|
+
if (!consumerClass?.isConsumer) continue
|
|
87
|
+
|
|
88
|
+
for (const provider of deps) {
|
|
89
|
+
const providerClass = taskClasses[provider]
|
|
90
|
+
if (!providerClass?.isProvider) continue
|
|
91
|
+
|
|
92
|
+
// 避免自引用和重复
|
|
93
|
+
if (consumer === provider) continue
|
|
94
|
+
const alreadyExists = contracts.some(
|
|
95
|
+
c => c.provider === provider && c.consumer === consumer
|
|
96
|
+
)
|
|
97
|
+
if (alreadyExists) continue
|
|
98
|
+
|
|
99
|
+
contracts.push({
|
|
100
|
+
provider,
|
|
101
|
+
consumer,
|
|
102
|
+
type: 'api',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return contracts
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 解析 task 依赖关系(2026-08-20-task-truth-unify D-003@v1)。
|
|
112
|
+
* 方式 2(depends_on 行内标注)的机器解析源随任务行迁 tasks.md——调用方 buildContractMatrix
|
|
113
|
+
* 读 tasks.md 传入(缺失回退 plan.md 内容,旧变更兼容);方式 1(任务总表依赖列)仍来自 plan.md。
|
|
114
|
+
* @param {string} registryContent - tasks.md 内容(优先)或 plan.md 内容(回退)
|
|
115
|
+
* @returns {Record<string, string[]>} task → depends_on list
|
|
116
|
+
*/
|
|
117
|
+
function parseTaskDependencies(registryContent) {
|
|
118
|
+
const deps = {}
|
|
119
|
+
|
|
120
|
+
// 方式 1: 任务总表表格形式 | task-04 | ... | 01,02 |
|
|
121
|
+
// 列前缀用 [^|\d]*(不吃数字):旧 [^|]* 贪婪会吃掉 deps 列前导 0(" 01" → g2=1 → task-1),
|
|
122
|
+
// 导致 provider task-1.md 不存在 → contracts 恒空,端点契约管线失效。
|
|
123
|
+
const tableRows = registryContent.matchAll(/\|[^|]*task-(\d+)[^|]*\|[^|]*\|[^|\d]*(?:task-)?(\d+(?:\s*[,,]\s*\d+)*)[^|]*\|/gi)
|
|
124
|
+
for (const match of tableRows) {
|
|
125
|
+
const task = `task-${match[1]}`
|
|
126
|
+
const depList = match[2].split(/[,,\s]+/).map(d => `task-${d.trim()}`).filter(d => d.startsWith('task-'))
|
|
127
|
+
deps[task] = depList
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 方式 2: tasks.md 行内标注 "- [ ] task-04: 名称 (depends_on: task-01,02)"(新家,D-003@v1;
|
|
131
|
+
// 旧 plan.md checkbox 行内标注同格式兼容)
|
|
132
|
+
const dependsPattern = registryContent.matchAll(/task-(\d+).*?depends_on.*?(\d+(?:\s*[,,]\s*\d+)*)/gi)
|
|
133
|
+
for (const match of dependsPattern) {
|
|
134
|
+
const task = `task-${match[1]}`
|
|
135
|
+
const depList = match[2].split(/[,,\s]+/).map(d => `task-${d.trim()}`).filter(d => d.startsWith('task-'))
|
|
136
|
+
if (!deps[task]) deps[task] = []
|
|
137
|
+
for (const d of depList) {
|
|
138
|
+
if (!deps[task].includes(d)) deps[task].push(d)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return deps
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── Execute 阶段:后端 task 完成后提取 artifact ───────────────────────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 后端 task 完成后,扫描变更文件提取 endpoint artifact
|
|
149
|
+
* @param {string} changeDir - changes/<name>/ 目录
|
|
150
|
+
* @param {string} worktreePath - worktree 路径(扫描源码用)
|
|
151
|
+
* @param {string} specBase - .sillyspec 目录
|
|
152
|
+
* @param {string} taskName - task-04
|
|
153
|
+
* @returns {{ ok: boolean, endpoints: Array, artifactPath: string|null }}
|
|
154
|
+
*/
|
|
155
|
+
export function extractProviderArtifact(changeDir, worktreePath, specBase, taskName, runtimeRoot) {
|
|
156
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
157
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
158
|
+
// 跨变更隔离:contract-artifacts/<changeName>/<taskName>/,避免不同变更同名 task 互相覆盖污染对账
|
|
159
|
+
const changeName = changeDir ? basename(changeDir) : '_unknown'
|
|
160
|
+
const artifactDir = join(artifactRoot, 'contract-artifacts', changeName, taskName)
|
|
161
|
+
const artifactPath = join(artifactDir, 'endpoints.json')
|
|
162
|
+
|
|
163
|
+
if (!worktreePath || !existsSync(worktreePath)) {
|
|
164
|
+
return { ok: false, endpoints: [], artifactPath: null, error: 'worktree not found' }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const endpoints = scanBackendEndpoints(worktreePath)
|
|
169
|
+
|
|
170
|
+
if (endpoints.length > 0) {
|
|
171
|
+
mkdirSync(artifactDir, { recursive: true })
|
|
172
|
+
const artifact = {
|
|
173
|
+
task: taskName,
|
|
174
|
+
type: 'backend_endpoints',
|
|
175
|
+
extractedAt: new Date().toISOString(),
|
|
176
|
+
endpoints: endpoints.map(e => ({
|
|
177
|
+
method: e.method,
|
|
178
|
+
path: normalizePath(e.path),
|
|
179
|
+
source: relative(worktreePath, e.source),
|
|
180
|
+
line: e.line,
|
|
181
|
+
})),
|
|
182
|
+
}
|
|
183
|
+
writeFileSync(artifactPath, JSON.stringify(artifact, null, 2) + '\n')
|
|
184
|
+
return { ok: true, endpoints: artifact.endpoints, artifactPath }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 无端点提取到 — 不算错误(可能不是 router task)
|
|
188
|
+
return { ok: true, endpoints: [], artifactPath: null }
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return { ok: false, endpoints: [], artifactPath: null, error: e.message }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 汇总提取一个变更内所有 provider task 的 endpoint artifact。
|
|
196
|
+
* run.js completeStep 在 execute Wave 完成时调用:扫 worktree → 按 buildContractMatrix 识别的
|
|
197
|
+
* provider task,各自落 contract-artifacts/<changeName>/<taskName>/endpoints.json。
|
|
198
|
+
* @param {{ changeDir: string, specBase: string, changeName: string, worktreePath: string|null }} args
|
|
199
|
+
* @returns {string|null} 日志摘要;无 provider/无 plan/worktree 缺失时返回 null(不打扰)
|
|
200
|
+
*/
|
|
201
|
+
export function extractArtifactsForChange({ changeDir, specBase, changeName, worktreePath }) {
|
|
202
|
+
if (!worktreePath || !changeDir || !existsSync(changeDir)) return null
|
|
203
|
+
const planFile = join(changeDir, 'plan.md')
|
|
204
|
+
if (!existsSync(planFile)) return null
|
|
205
|
+
const contracts = buildContractMatrix(readFileSync(planFile, 'utf8'), changeDir)
|
|
206
|
+
const providers = [...new Set(contracts.map(c => c.provider))]
|
|
207
|
+
if (providers.length === 0) return null
|
|
208
|
+
let withEndpoints = 0
|
|
209
|
+
for (const taskName of providers) {
|
|
210
|
+
const r = extractProviderArtifact(changeDir, worktreePath, specBase, taskName)
|
|
211
|
+
if (r.ok && r.endpoints.length > 0) withEndpoints++
|
|
212
|
+
}
|
|
213
|
+
return `📦 契约 artifact 提取: providers=${providers.join(',')}(${withEndpoints}/${providers.length} 含端点)`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Execute 阶段:前端 task 开始时注入契约 ─────────────────────────────
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 为 consumer task 构建上游契约注入文本
|
|
220
|
+
* @param {string} changeDir - changes/<name>/ 目录
|
|
221
|
+
* @param {string} specBase - .sillyspec 目录
|
|
222
|
+
* @param {string} taskName - 当前 task(consumer)
|
|
223
|
+
* @param {Array<{ provider: string, consumer: string, type: string }>} contracts
|
|
224
|
+
* @returns {string|null} 注入到 prompt 的契约文本,无契约时返回 null
|
|
225
|
+
*/
|
|
226
|
+
export function buildConsumerInjection(changeDir, specBase, taskName, contracts, runtimeRoot) {
|
|
227
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
228
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
229
|
+
const changeName = changeDir ? basename(changeDir) : '_unknown'
|
|
230
|
+
const myContracts = contracts.filter(c => c.consumer === taskName)
|
|
231
|
+
if (myContracts.length === 0) return null
|
|
232
|
+
|
|
233
|
+
const parts = []
|
|
234
|
+
for (const contract of myContracts) {
|
|
235
|
+
const artifactDir = join(artifactRoot, 'contract-artifacts', changeName, contract.provider)
|
|
236
|
+
const artifactFile = join(artifactDir, 'endpoints.json')
|
|
237
|
+
|
|
238
|
+
let endpoints = []
|
|
239
|
+
if (existsSync(artifactFile)) {
|
|
240
|
+
try {
|
|
241
|
+
const artifact = JSON.parse(readFileSync(artifactFile, 'utf8'))
|
|
242
|
+
endpoints = artifact.endpoints || []
|
|
243
|
+
} catch {}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
parts.push(`### Upstream Contract: ${contract.provider}`)
|
|
247
|
+
if (endpoints.length > 0) {
|
|
248
|
+
parts.push(`\nAvailable endpoints from **${contract.provider}**:`)
|
|
249
|
+
for (const ep of endpoints) {
|
|
250
|
+
parts.push(`- **${ep.method}** \`${ep.path}\``)
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
parts.push(`\n⚠️ No endpoint artifact found for ${contract.provider}. This may indicate a contract gap.`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (parts.length === 0) return null
|
|
258
|
+
|
|
259
|
+
parts.unshift('## Upstream API Contracts')
|
|
260
|
+
parts.push('')
|
|
261
|
+
parts.push('### Rules')
|
|
262
|
+
parts.push('1. Do not invent API paths. Use only endpoints listed above.')
|
|
263
|
+
parts.push('2. If a required endpoint is missing, **stop and report the contract gap** instead of coding around it.')
|
|
264
|
+
parts.push('3. If you need to add new endpoints, you must also update the backend provider task.')
|
|
265
|
+
|
|
266
|
+
return parts.join('\n')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 为 consumer task 构建字段级契约注入:对比 expects_from.needs vs provider.provides.fields
|
|
271
|
+
*
|
|
272
|
+
* 让 consumer 子代理带着明确的字段清单核验上游产出:
|
|
273
|
+
* - provider 已承诺 → 编码时只使用 provides.fields,运行时缺字段上报
|
|
274
|
+
* - provider 未承诺某 needs 字段 → 标 CONTRACT_GAP,要求 stop and report(禁止 fallback 编造)
|
|
275
|
+
*
|
|
276
|
+
* 命中场景:provider task 漏实现某字段(如 DaemonRuntimeRead 缺 daemon_instance_id),
|
|
277
|
+
* consumer 若 fallback 编造 → 运行时 403/500。此处把"缺字段"暴露在子代理启动前。
|
|
278
|
+
*
|
|
279
|
+
* 注:plan-postcheck 已对账 expects_from↔provides,此处是 execute 时的二次保险,
|
|
280
|
+
* 拦截 plan-postcheck 之后 task 文件被手改、或 provider 实际实现漏字段的情况。
|
|
281
|
+
*
|
|
282
|
+
* @param {string} changeDir - changes/<name>/ 目录
|
|
283
|
+
* @param {string} taskName - consumer task(如 task-11)
|
|
284
|
+
* @returns {string|null} 注入文本,无 expects_from 时返回 null
|
|
285
|
+
*/
|
|
286
|
+
export function buildContractFieldInjection(changeDir, taskName) {
|
|
287
|
+
const consumerFile = join(changeDir, 'tasks', `${taskName}.md`)
|
|
288
|
+
if (!existsSync(consumerFile)) return null
|
|
289
|
+
const { expectsFrom } = parseTaskContracts(readFileSync(consumerFile, 'utf8'))
|
|
290
|
+
const providers = Object.keys(expectsFrom)
|
|
291
|
+
if (providers.length === 0) return null
|
|
292
|
+
|
|
293
|
+
const lines = []
|
|
294
|
+
lines.push('## Upstream Contract Fields(字段级核验)')
|
|
295
|
+
lines.push('')
|
|
296
|
+
|
|
297
|
+
for (const providerTask of providers) {
|
|
298
|
+
const providerFile = join(changeDir, 'tasks', `${providerTask}.md`)
|
|
299
|
+
let providerProvides = []
|
|
300
|
+
if (existsSync(providerFile)) {
|
|
301
|
+
providerProvides = parseTaskContracts(readFileSync(providerFile, 'utf8')).provides
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const c of expectsFrom[providerTask]) {
|
|
305
|
+
const providerEntry = providerProvides.find(p => p.contract === c.contract)
|
|
306
|
+
if (!providerEntry) {
|
|
307
|
+
lines.push(`### ⚠️ CONTRACT_GAP: ${providerTask} → ${c.contract}`)
|
|
308
|
+
lines.push(`你需要字段 [${c.needs.join(', ')}],但 ${providerTask} 的 provides 未声明此契约。`)
|
|
309
|
+
lines.push(`**立即停止编码并上报**:不要 fallback、不要编造字段,先确认 ${providerTask} 是否应产出此契约。`)
|
|
310
|
+
} else {
|
|
311
|
+
const providerFields = new Set(providerEntry.fields)
|
|
312
|
+
const missing = c.needs.filter(f => !providerFields.has(f))
|
|
313
|
+
if (missing.length > 0) {
|
|
314
|
+
lines.push(`### ⚠️ CONTRACT_GAP: ${providerTask} → ${c.contract}`)
|
|
315
|
+
lines.push(`你需要字段 [${missing.join(', ')}],但 ${providerTask}.provides 仅承诺 [${providerEntry.fields.join(', ')}]。`)
|
|
316
|
+
lines.push(`**立即停止编码并上报**:不要 fallback、不要编造字段,先要求 ${providerTask} 在 provides.fields 补上 [${missing.join(', ')}]。`)
|
|
317
|
+
} else {
|
|
318
|
+
lines.push(`### ✅ ${providerTask} → ${c.contract}`)
|
|
319
|
+
lines.push(`你需要的字段 [${c.needs.join(', ')}] 均在 ${providerTask}.provides 承诺内:[${providerEntry.fields.join(', ')}]。`)
|
|
320
|
+
lines.push(`编码时**只使用上述字段**;若运行时实际返回缺字段,说明 provider 实现漏了 → 上报 CONTRACT_GAP,不要 fallback。`)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
lines.push('')
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
lines.push('### 字段级铁律')
|
|
328
|
+
lines.push('1. 禁止 fallback 编造:若上游返回缺字段,停止并上报 CONTRACT_GAP,不要用 `x || defaultValue` 之类的防御性回退掩盖契约破裂。')
|
|
329
|
+
lines.push('2. 只消费 provides 承诺的字段;需要新字段必须先让 provider 更新 provides。')
|
|
330
|
+
lines.push('3. 启动子代理前,先读 provider task 的 review.json / acceptance,确认其已声明完成上述契约字段。')
|
|
331
|
+
|
|
332
|
+
return lines.join('\n')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Verify 阶段:parity check ──────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 读 worktree meta(parity 双根扫描 / diff 收窄共用)。
|
|
339
|
+
* 优先 specBase/.runtime/worktrees/<change>/meta.json(主仓 spec 根——cwd=worktree 时 specBase 已被
|
|
340
|
+
* command.js 漂移守卫锚定主仓;旧实现硬编码 scanRoot/.sillyspec/.runtime,worktree 内运行永远读不到
|
|
341
|
+
* meta);兼容回退 scanRoot/.sillyspec/.runtime(旧路径)。
|
|
342
|
+
* @returns {{ meta: object, gitDir: string }|null} gitDir = diff/扫描用 git 根(真实 worktree 或 scanRoot)
|
|
343
|
+
*/
|
|
344
|
+
export function _readWorktreeMeta(specBase, scanRoot, changeName) {
|
|
345
|
+
if (!changeName) return null
|
|
346
|
+
const candidates = [
|
|
347
|
+
specBase ? join(specBase, '.runtime', 'worktrees', changeName, 'meta.json') : null,
|
|
348
|
+
join(scanRoot, '.sillyspec', '.runtime', 'worktrees', changeName, 'meta.json'),
|
|
349
|
+
].filter(Boolean)
|
|
350
|
+
for (const metaPath of candidates) {
|
|
351
|
+
try {
|
|
352
|
+
if (!existsSync(metaPath)) continue
|
|
353
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf8'))
|
|
354
|
+
const gitDir = (meta.worktreePath && meta.mode !== 'in-place-fallback' && existsSync(meta.worktreePath))
|
|
355
|
+
? meta.worktreePath : scanRoot
|
|
356
|
+
return { meta, gitDir }
|
|
357
|
+
} catch { /* 损坏 meta 试下一候选 */ }
|
|
358
|
+
}
|
|
359
|
+
return null
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 取本变更 diff 文件集(parity 前端调用收窄用,坑 probe5-fullrepo-frontend-noise)。
|
|
364
|
+
* 优先 worktree meta 锚点 diff(与 verify 同源);无 meta 退主仓 git diff HEAD。
|
|
365
|
+
* @returns {string[]|null}
|
|
366
|
+
*/
|
|
367
|
+
function _resolveDiffFilesForParity(specBase, scanRoot, changeName) {
|
|
368
|
+
try {
|
|
369
|
+
const wt = _readWorktreeMeta(specBase, scanRoot, changeName)
|
|
370
|
+
if (wt) {
|
|
371
|
+
const diffBase = wt.meta.baselineCommit || wt.meta.actualBaseHash || wt.meta.baseHash
|
|
372
|
+
if (diffBase) {
|
|
373
|
+
const out = gitQuiet(wt.gitDir, ['diff', '--name-only', `${diffBase}..HEAD`], { timeout: 30000 })
|
|
374
|
+
if (out !== null) {
|
|
375
|
+
const files = out.split('\n').filter(Boolean)
|
|
376
|
+
// 并入 worktree 未提交(子代理默认不 commit 的形态,与 resolveVerifyChangedFiles 同口径)。
|
|
377
|
+
// -uall:porcelain 默认把整目录折叠成 `dir/` 一项,文件级匹配(前端调用过滤)会漏新目录
|
|
378
|
+
// 里的文件(坑 probe5-worktree-single-root-noise 实证:web/ 折叠 → feature.js 漏匹配)
|
|
379
|
+
try {
|
|
380
|
+
const wtStatus = gitQuiet(wt.gitDir, ['status', '--porcelain', '--untracked-files=all'], { timeout: 30000, trim: false })
|
|
381
|
+
for (const line of String(wtStatus || '').split('\n')) {
|
|
382
|
+
if (!line || line.length < 4) continue
|
|
383
|
+
const p = line.slice(3).trim().split(' -> ').pop() || ''
|
|
384
|
+
const clean = p.replace(/^"|"$/g, '')
|
|
385
|
+
if (clean) files.push(clean)
|
|
386
|
+
}
|
|
387
|
+
} catch {}
|
|
388
|
+
return [...new Set(files)]
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// 无 meta(brownfield/已 cleanup):主仓未提交 diff。他者声明归属过滤(坑
|
|
393
|
+
// verify-reconcile-foreign-wip):并行会话在途 WIP 的前端文件混入会产出「他者调用 ×
|
|
394
|
+
// 本变更端点」的 missingBackend 误报——剔除他者显式声明文件(fail-closed:无主保留)。
|
|
395
|
+
const out = gitQuiet(scanRoot, ['diff', '--name-only', 'HEAD'], { timeout: 30000 })
|
|
396
|
+
if (out === null) return null
|
|
397
|
+
const { own, foreign } = splitOwnVsForeignDiffFiles(scanRoot, changeName, out.split('\n').filter(Boolean))
|
|
398
|
+
if (foreign.length > 0) {
|
|
399
|
+
console.warn(`⚠️ parity 对账已排除 ${foreign.length} 个并行会话声明的文件(${foreign.slice(0, 5).map(x => x.file).join(', ')}${foreign.length > 5 ? ' 等' : ''})`)
|
|
400
|
+
}
|
|
401
|
+
return own
|
|
402
|
+
} catch { return null }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* verify 阶段执行 API parity check
|
|
407
|
+
* @param {string} specBase - .sillyspec 目录
|
|
408
|
+
* @param {string} worktreePath - worktree 路径
|
|
409
|
+
* @returns {{ ok: boolean, missingBackend: Array, unusedBackend: Array, summary: string, backendCount: number, frontendCount: number }}
|
|
410
|
+
*/
|
|
411
|
+
export function verifyApiParity(specBase, scanRoot, runtimeRoot, changeName = null) {
|
|
412
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
413
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
414
|
+
// 读取该变更的所有 provider artifacts(contract-artifacts/<changeName>/*/endpoints.json)。
|
|
415
|
+
// changeName 缺失时回退扫顶层 contract-artifacts(CLI contractScan 跨变更场景兼容)。
|
|
416
|
+
const artifactBase = changeName
|
|
417
|
+
? join(artifactRoot, 'contract-artifacts', changeName)
|
|
418
|
+
: join(artifactRoot, 'contract-artifacts')
|
|
419
|
+
const allProviderEndpoints = []
|
|
420
|
+
|
|
421
|
+
if (existsSync(artifactBase)) {
|
|
422
|
+
for (const entry of readdirSync(artifactBase, { withFileTypes: true })) {
|
|
423
|
+
if (!entry.isDirectory()) continue
|
|
424
|
+
const epFile = join(artifactBase, entry.name, 'endpoints.json')
|
|
425
|
+
if (existsSync(epFile)) {
|
|
426
|
+
try {
|
|
427
|
+
const artifact = JSON.parse(readFileSync(epFile, 'utf8'))
|
|
428
|
+
for (const ep of (artifact.endpoints || [])) {
|
|
429
|
+
allProviderEndpoints.push({
|
|
430
|
+
method: ep.method,
|
|
431
|
+
path: ep.path,
|
|
432
|
+
source: `${entry.name}/${ep.source}`,
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
} catch {}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 扫描前端调用(scanRoot 通常 = 主工作区 cwd:verify 时代码已 apply 到主工作区)
|
|
441
|
+
if (!scanRoot || !existsSync(scanRoot)) {
|
|
442
|
+
return { ok: true, missingBackend: [], unusedBackend: [], summary: 'No scan root for parity check', backendCount: allProviderEndpoints.length, frontendCount: 0 }
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ── 双根并集现算(坑 probe5-worktree-single-root-noise,2026-08-24 用户实证:worktree 场景
|
|
446
|
+
// 只现算 scanRoot 单根,另一侧端点不在比对集——verify 从主仓跑(apply 前)漏 worktree 新增端点;
|
|
447
|
+
// verify 从 worktree 跑则既读不到 meta(旧路径硬编码 scanRoot/.sillyspec)也漏主仓既有 daemon
|
|
448
|
+
// 端点 → 前端调用全量误报 missing)。后端端点全集 = 主仓既有 ∪ worktree 新增 ∪ 存量 artifact:
|
|
449
|
+
// 现算根集 = [scanRoot, meta.worktreePath, 主仓根(cwd 自身在 linked worktree 内时经
|
|
450
|
+
// git-common-dir 解析)],逐根 scanBackendEndpoints 按 method+path 去重。
|
|
451
|
+
// 存量基线失配坑(probe5-endpoint-baseline-stale,2026-08-22)由本并集一并覆盖:现算当前
|
|
452
|
+
// 代码 ∪ 存量基线;unusedBackend 只对现算端点算(存量过期端点不再误报 unused)。
|
|
453
|
+
const wt = _readWorktreeMeta(specBase, scanRoot, changeName)
|
|
454
|
+
const liveRoots = []
|
|
455
|
+
const pushRoot = (root, label) => {
|
|
456
|
+
if (!root || !existsSync(root)) return
|
|
457
|
+
const abs = resolve(root)
|
|
458
|
+
if (liveRoots.some(r => resolve(r.root) === abs)) return
|
|
459
|
+
liveRoots.push({ root, label })
|
|
460
|
+
}
|
|
461
|
+
pushRoot(scanRoot, 'scan-root')
|
|
462
|
+
if (wt && wt.gitDir !== scanRoot) pushRoot(wt.gitDir, 'worktree')
|
|
463
|
+
// cwd 自身是 linked worktree 时补主仓根(主仓返回 .git → dirname=scanRoot 被 pushRoot 去重)
|
|
464
|
+
try {
|
|
465
|
+
const commonDir = gitQuiet(scanRoot, ['rev-parse', '--git-common-dir'])
|
|
466
|
+
if (commonDir) pushRoot(dirname(resolve(scanRoot, commonDir)), 'main')
|
|
467
|
+
} catch { /* 非 git 目录/解析失败:单根继续 */ }
|
|
468
|
+
|
|
469
|
+
const liveByRoot = []
|
|
470
|
+
for (const { root, label } of liveRoots) {
|
|
471
|
+
try { liveByRoot.push({ root, label, endpoints: scanBackendEndpoints(root) }) } catch { /* 单根失败不拖垮整体 */ }
|
|
472
|
+
}
|
|
473
|
+
const liveEndpoints = liveByRoot.flatMap(r => r.endpoints)
|
|
474
|
+
const liveKeys = new Set(liveEndpoints.map(e => `${e.method} ${e.path}`))
|
|
475
|
+
const mergedKeys = new Set(allProviderEndpoints.map(e => `${e.method} ${e.path}`))
|
|
476
|
+
const mergedProviderEndpoints = [...allProviderEndpoints]
|
|
477
|
+
for (const e of liveEndpoints) {
|
|
478
|
+
const key = `${e.method} ${e.path}`
|
|
479
|
+
if (!mergedKeys.has(key)) {
|
|
480
|
+
mergedKeys.add(key)
|
|
481
|
+
mergedProviderEndpoints.push(e)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── 前端调用收窄到本变更 diff(坑 probe5-fullrepo-frontend-noise,2026-08-23 实证:
|
|
486
|
+
// 「全仓前端调用 × 本变更局部登记」口径下,未受本变更影响的存量调用对不上局部端点集
|
|
487
|
+
// → 143 个 missing 全是误报噪音)。比对口径 = 本变更 diff 文件内的调用 ∪ 全仓(仅当
|
|
488
|
+
// 端点登记源也是全仓时——无 changeName 的 CLI contractScan 场景)。diff 不可得时退回
|
|
489
|
+
// 全仓(不静默跳过对账)。
|
|
490
|
+
// 扫描根优先真实 worktree(坑 probe5-worktree-single-root-noise):apply 前新代码只在
|
|
491
|
+
// worktree,scanRoot(主仓 cwd)里是旧版本——读旧版会漏新调用/多已删调用。
|
|
492
|
+
let frontendCalls = []
|
|
493
|
+
let frontendScope = 'full-repo'
|
|
494
|
+
const frontendRoot = (wt && wt.gitDir !== scanRoot) ? wt.gitDir : scanRoot
|
|
495
|
+
const frontendRootLabel = frontendRoot === scanRoot ? 'scan-root' : 'worktree'
|
|
496
|
+
if (changeName) {
|
|
497
|
+
try {
|
|
498
|
+
const changed = _resolveDiffFilesForParity(specBase, scanRoot, changeName)
|
|
499
|
+
if (changed && changed.length > 0) {
|
|
500
|
+
const changedSet = new Set(changed.map(f => f.replace(/\\/g, '/')))
|
|
501
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot).filter(c => {
|
|
502
|
+
const src = String(c.source || '').replace(/\\/g, '/')
|
|
503
|
+
return changedSet.has(src) || [...changedSet].some(cf => src.endsWith(cf) || cf.endsWith(src))
|
|
504
|
+
})
|
|
505
|
+
frontendScope = `change-diff (${changed.length} files @ ${frontendRootLabel})`
|
|
506
|
+
} else {
|
|
507
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot)
|
|
508
|
+
}
|
|
509
|
+
} catch { frontendCalls = scanFrontendApiCalls(frontendRoot) }
|
|
510
|
+
} else {
|
|
511
|
+
frontendCalls = scanFrontendApiCalls(frontendRoot)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const { missingBackend, unusedBackend } = diffApiParity(frontendCalls, mergedProviderEndpoints)
|
|
515
|
+
// unusedBackend 收窄到现算端点(存量 artifact 的过期端点不参与——它不在当前代码里,
|
|
516
|
+
// 「前端未调用」可能是端点已被重构掉而非真泄漏)
|
|
517
|
+
const narrowedUnused = unusedBackend.filter(u => {
|
|
518
|
+
const key = `${u.method} ${u.path}`
|
|
519
|
+
return liveKeys.has(key) || liveEndpoints.length === 0
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const ok = missingBackend.length === 0
|
|
523
|
+
const liveRootSummary = liveByRoot.map(r => `${r.label} ${r.endpoints.length}`).join(' + ')
|
|
524
|
+
let summary = ok
|
|
525
|
+
? `✅ API parity check passed: ${mergedProviderEndpoints.length} backend endpoints (live [${liveRootSummary}] + artifact ${allProviderEndpoints.length}), ${frontendCalls.length} frontend calls [scope: ${frontendScope}]`
|
|
526
|
+
: `❌ API parity check failed: ${missingBackend.length} frontend calls have no matching backend endpoint [scope: ${frontendScope}]`
|
|
527
|
+
|
|
528
|
+
if (narrowedUnused.length > 0) {
|
|
529
|
+
summary += ` | ${narrowedUnused.length} backend endpoints unused by frontend`
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return { ok, missingBackend, unusedBackend: narrowedUnused, summary, backendCount: mergedProviderEndpoints.length, frontendCount: frontendCalls.length, scanRoots: liveByRoot.map(r => r.root) }
|
|
533
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 人读墙钟时间工具(坑 taskcard-created-at-utc,2026-08-23 实证:taskcard 骨架 created_at 用
|
|
3
|
+
* toISOString() 落 UTC——本地 09:39 生成写 01:39,子代理两次手工改)。
|
|
4
|
+
*
|
|
5
|
+
* 项目内「人读的 markdown/YAML frontmatter 时间字段」统一走本函数:本地时区 +
|
|
6
|
+
* `YYYY-MM-DD HH:mm:ss` 形状(scan.js 文档示例形状),手工拼接零 locale/ICU 依赖(Node
|
|
7
|
+
* small-icu 构建下 toLocaleString('sv-SE') 不可靠)。机器可读处(JSON/DB 列/目录名)继续用
|
|
8
|
+
* toISOString()——那是 ISO 惯例不是坑。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 本地墙钟时间字符串(YYYY-MM-DD HH:mm:ss,各段补零)。
|
|
13
|
+
* @param {Date} [d] 可注入时钟(测试用),缺省当前时间
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export function nowWallClock(d = new Date()) {
|
|
17
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
18
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} `
|
|
19
|
+
+ `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// db-engine.js — node:sqlite DatabaseSync 引擎抽象层
|
|
2
|
+
// 封装 node:sqlite 内置模块 + 消解 better-sqlite3 → node:sqlite 三缺口:
|
|
3
|
+
// pragma → exec / transaction → 手写 SAVEPOINT / pluck → Object.values 取首列
|
|
4
|
+
// 供 db.js 与 doctor-diagnostics 共用(方案 B 单一换引擎点,D-002)。
|
|
5
|
+
// node:sqlite 在 node v22.13.0+ 无需 --experimental-sqlite flag(仍发 ExperimentalWarning)。
|
|
6
|
+
// Author: sillyspec execute W1 task-02
|
|
7
|
+
// Created: 2026-08-11
|
|
8
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
9
|
+
|
|
10
|
+
// 打开数据库。opts.readOnly(驼峰)透传 node:sqlite。
|
|
11
|
+
// fileMustExist 语义由调用方 existsSync 前置门实现(node:sqlite 不拒缺失文件,不凭空建库由调用方保证)。
|
|
12
|
+
export function openDatabase(dbPath, opts = {}) {
|
|
13
|
+
return new DatabaseSync(dbPath, { readOnly: !!opts.readOnly });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 逐条 exec PRAGMA。entries: [['journal_mode','WAL'], ['busy_timeout','5000'], ...]
|
|
17
|
+
// node:sqlite 无 .pragma(),统一走 .exec('PRAGMA key = value')。
|
|
18
|
+
export function applyPragmas(db, entries) {
|
|
19
|
+
for (const [key, value] of entries) {
|
|
20
|
+
db.exec(`PRAGMA ${key} = ${value}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 手写事务(node:sqlite 无 .transaction())。
|
|
25
|
+
// 统一用 SAVEPOINT/RELEASE/ROLLBACK TO —— SQLite 允许顶层 SAVEPOINT(等价事务),
|
|
26
|
+
// 嵌套调用自动形成 savepoint 栈(better-sqlite3 嵌套自动 SAVEPOINT 的兼容实现)。
|
|
27
|
+
// fn 抛错自动 ROLLBACK TO + RELEASE 且原错误上抛不吞。
|
|
28
|
+
// 本函数不含 BUSY 重试(由 db.js wrapper 外层包裹)。
|
|
29
|
+
export function runTransaction(db, fn) {
|
|
30
|
+
const sp = 'tx_sp_' + Math.random().toString(36).slice(2);
|
|
31
|
+
db.exec(`SAVEPOINT ${sp}`);
|
|
32
|
+
try {
|
|
33
|
+
const result = fn();
|
|
34
|
+
db.exec(`RELEASE ${sp}`);
|
|
35
|
+
return result;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
try {
|
|
38
|
+
db.exec(`ROLLBACK TO ${sp}`);
|
|
39
|
+
db.exec(`RELEASE ${sp}`);
|
|
40
|
+
} catch { /* 回滚失败不掩盖原错误 */ }
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// pluck 替代:取第一行第一列,无行 undefined。
|
|
46
|
+
// node:sqlite .get() 返回行对象(不像 better-sqlite3 pluck 返标量),故 Object.values 取首列。
|
|
47
|
+
export function pluckGet(db, sql, ...params) {
|
|
48
|
+
const row = db.prepare(sql).get(...params);
|
|
49
|
+
return row === undefined ? undefined : Object.values(row)[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// pluck 替代:取所有行第一列成数组。
|
|
53
|
+
export function pluckAll(db, sql, ...params) {
|
|
54
|
+
return db.prepare(sql).all(...params).map((row) => Object.values(row)[0]);
|
|
55
|
+
}
|