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,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* endpoint-extractor.js — 从代码中提取 HTTP 端点定义和调用
|
|
3
|
+
*
|
|
4
|
+
* provider 端:扫描 router 文件,提取注册的 API 路径
|
|
5
|
+
* consumer 端:扫描前端文件,提取 apiFetch/request 调用路径
|
|
6
|
+
*
|
|
7
|
+
* 契约对账时:provider 产出 ≠ consumer 消费 → gap
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
|
|
11
|
+
import { join, extname, basename, dirname, resolve } from 'path'
|
|
12
|
+
import { execSync } from 'child_process'
|
|
13
|
+
|
|
14
|
+
// ─── Provider: 扫描后端 router 注册的端点 ───────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 从单个文件提取 FastAPI router 端点
|
|
18
|
+
* 支持 APIRouter(prefix=...) 和 @router.get/post/put/delete/patch("/path")
|
|
19
|
+
*
|
|
20
|
+
* @param {string} filePath - 文件绝对路径
|
|
21
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number }>}
|
|
22
|
+
*/
|
|
23
|
+
export function extractFastApiEndpoints(filePath) {
|
|
24
|
+
const content = readFileSync(filePath, 'utf8')
|
|
25
|
+
const lines = content.split('\n')
|
|
26
|
+
const endpoints = []
|
|
27
|
+
|
|
28
|
+
// 1. 提取 router prefix
|
|
29
|
+
let routerPrefix = ''
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const prefixMatch = line.match(/(?:APIRouter|router)\s*\(\s*(?:prefix\s*=\s*)?["'`]([^"'`]+)["'`]/)
|
|
32
|
+
|| line.match(/\.include_router\s*\([^)]*prefix\s*=\s*["'`]([^"'`]+)["'`]/)
|
|
33
|
+
if (prefixMatch) {
|
|
34
|
+
routerPrefix = prefixMatch[1]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. 提取 @router.method("/path") 或分散式定义
|
|
39
|
+
// FastAPI 支持两种写法:
|
|
40
|
+
// a) @router.get("/path", ...) 下一行 def func():
|
|
41
|
+
// b) @router.get 下一行 ("/path", ...)
|
|
42
|
+
for (let i = 0; i < lines.length; i++) {
|
|
43
|
+
const line = lines[i]
|
|
44
|
+
const decoratorMatch = line.match(
|
|
45
|
+
/@(?:router|api_router)\.(get|post|put|delete|patch)\s*\(\s*["'`]([^"'`]+)["'`]/
|
|
46
|
+
)
|
|
47
|
+
if (decoratorMatch) {
|
|
48
|
+
const method = decoratorMatch[1].toUpperCase()
|
|
49
|
+
const rawPath = decoratorMatch[2]
|
|
50
|
+
endpoints.push({
|
|
51
|
+
method,
|
|
52
|
+
path: routerPrefix + rawPath,
|
|
53
|
+
source: filePath,
|
|
54
|
+
line: i + 1,
|
|
55
|
+
})
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
// 分散式: @router.get\n ("/path",
|
|
59
|
+
const splitMatch = line.match(/@(?:router|api_router)\.(get|post|put|delete|patch)\s*$/)
|
|
60
|
+
if (splitMatch && i + 1 < lines.length) {
|
|
61
|
+
const nextLine = lines[i + 1]
|
|
62
|
+
const pathMatch = nextLine.match(/\(\s*["'`]([^"'`]+)["'`]/)
|
|
63
|
+
if (pathMatch) {
|
|
64
|
+
endpoints.push({
|
|
65
|
+
method: splitMatch[1].toUpperCase(),
|
|
66
|
+
path: routerPrefix + pathMatch[1],
|
|
67
|
+
source: filePath,
|
|
68
|
+
line: i + 1,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return endpoints
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 从单个文件提取 Express(Node)路由端点。
|
|
79
|
+
* 支持 router/app.get|post|put|delete|patch("path") 与链式 router.route("path").<method>()。
|
|
80
|
+
* app.use("prefix", router) 的前缀只做同文件近似(跨文件追踪不在首版范围)。
|
|
81
|
+
*
|
|
82
|
+
* @param {string} filePath - 文件绝对路径
|
|
83
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number }>}
|
|
84
|
+
*/
|
|
85
|
+
export function extractExpressEndpoints(filePath) {
|
|
86
|
+
const content = readFileSync(filePath, 'utf8')
|
|
87
|
+
const lines = content.split('\n')
|
|
88
|
+
const endpoints = []
|
|
89
|
+
|
|
90
|
+
// 同文件内最后一个 app.use("prefix", ...) 作为前缀近似
|
|
91
|
+
let routerPrefix = ''
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
const useMatch = line.match(/\bapp\.use\s*\(\s*["'`]([^"'`]+)["'`]/)
|
|
94
|
+
if (useMatch) routerPrefix = useMatch[1]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < lines.length; i++) {
|
|
98
|
+
const line = lines[i]
|
|
99
|
+
const m = line.match(/\b(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*["'`]([^"'`]+)["'`]/i)
|
|
100
|
+
if (m) {
|
|
101
|
+
endpoints.push({ method: m[1].toUpperCase(), path: routerPrefix + m[2], source: filePath, line: i + 1 })
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
const routeChain = line.match(/\.route\s*\(\s*["'`]([^"'`]+)["'`]/)
|
|
105
|
+
if (routeChain) {
|
|
106
|
+
// 链式 .route(path).get(h).put(h) —— 同行所有 method 配上 path
|
|
107
|
+
const chainPath = routerPrefix + routeChain[1]
|
|
108
|
+
for (const mm of line.matchAll(/\.(get|post|put|delete|patch)\s*\(/gi)) {
|
|
109
|
+
endpoints.push({ method: mm[1].toUpperCase(), path: chainPath, source: filePath, line: i + 1 })
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return endpoints
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 从单个文件提取 Spring(Java)端点。
|
|
119
|
+
* 支持 @GetMapping/@PostMapping/.../@PatchMapping 与旧式 @RequestMapping(value="path", method=RequestMethod.X);
|
|
120
|
+
* 类级 @RequestMapping("api") 前缀合并(取文件内第一个作类前缀,近似)。
|
|
121
|
+
*
|
|
122
|
+
* @param {string} filePath - 文件绝对路径
|
|
123
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number }>}
|
|
124
|
+
*/
|
|
125
|
+
export function extractSpringEndpoints(filePath) {
|
|
126
|
+
const content = readFileSync(filePath, 'utf8')
|
|
127
|
+
const lines = content.split('\n')
|
|
128
|
+
const endpoints = []
|
|
129
|
+
|
|
130
|
+
let classPrefix = ''
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const cm = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["'`]([^"'`]+)["'`]/)
|
|
133
|
+
if (cm) { classPrefix = cm[1]; break }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const methodMap = { get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE', patch: 'PATCH' }
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
139
|
+
const line = lines[i]
|
|
140
|
+
const shortMatch = line.match(/@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*(?:value\s*=\s*)?["'`]([^"'`]+)["'`]/i)
|
|
141
|
+
if (shortMatch) {
|
|
142
|
+
endpoints.push({ method: methodMap[shortMatch[1].toLowerCase()], path: classPrefix + shortMatch[2], source: filePath, line: i + 1 })
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
const oldMatch = line.match(/@RequestMapping\s*\(\s*(?:value\s*=\s*)?["'`]([^"'`]+)["'`][^)]*?method\s*=\s*RequestMethod\.(\w+)/i)
|
|
146
|
+
if (oldMatch) {
|
|
147
|
+
endpoints.push({ method: oldMatch[2].toUpperCase(), path: classPrefix + oldMatch[1], source: filePath, line: i + 1 })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return endpoints
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 从目录递归扫描后端端点(多框架)。
|
|
156
|
+
* 按扩展名分派:.py→FastAPI / .js,.ts→Express / .java→Spring。
|
|
157
|
+
* 保留 excludePatterns(向后兼容 opts);filePattern 在多框架分派下不再适用。
|
|
158
|
+
* @param {string} dir
|
|
159
|
+
* @param {{ excludePatterns?: RegExp[] }} opts
|
|
160
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number }>}
|
|
161
|
+
*/
|
|
162
|
+
export function scanBackendEndpoints(dir, opts = {}) {
|
|
163
|
+
// 排除清单对齐 scanFrontendApiCalls(坑 parity-scan-stale-dirs:.claude/worktrees 陈旧检出
|
|
164
|
+
// 与各类 build 产物混入端点扫描全是噪音)——backend 侧另含 __pycache__/.gradle/maven target
|
|
165
|
+
const excludePatterns = opts.excludePatterns || [
|
|
166
|
+
/^__pycache__$/, /^node_modules$/, /^\.venv$/, /^venv$/, /^\.git$/, /^\.gradle$/,
|
|
167
|
+
/^\.claude$/, /^\.vscode$/, /^\.idea$/, /^\.cursor$/, /^\.next$/, /^\.nuxt$/,
|
|
168
|
+
/^\.output$/, /^\.turbo$/, /^\.parcel-cache$/, /^coverage$/,
|
|
169
|
+
/^\.sillyspec$/, /^\.worktrees$/, /^\.d\.ts$/,
|
|
170
|
+
/^(dist|build|target|out)$/i,
|
|
171
|
+
/test/i,
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
const results = []
|
|
175
|
+
if (!existsSync(dir)) return results
|
|
176
|
+
|
|
177
|
+
function walk(d) {
|
|
178
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
179
|
+
const full = join(d, entry.name)
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
if (excludePatterns.some(p => p.test(entry.name))) continue
|
|
182
|
+
walk(full)
|
|
183
|
+
} else if (entry.isFile()) {
|
|
184
|
+
const ext = extname(entry.name).toLowerCase()
|
|
185
|
+
try {
|
|
186
|
+
if (ext === '.py') results.push(...extractFastApiEndpoints(full))
|
|
187
|
+
else if (ext === '.js' || ext === '.ts') results.push(...extractExpressEndpoints(full))
|
|
188
|
+
else if (ext === '.java') results.push(...extractSpringEndpoints(full))
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
walk(dir)
|
|
194
|
+
return results
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Consumer: 扫描前端 API 调用路径 ─────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 从前端文件提取 API 调用路径
|
|
201
|
+
* 支持:
|
|
202
|
+
* apiFetch("/api/xxx")
|
|
203
|
+
* request("/api/xxx")
|
|
204
|
+
* axios.get("/api/xxx")
|
|
205
|
+
* axios.post("/api/xxx")
|
|
206
|
+
* fetch("/api/xxx")
|
|
207
|
+
*
|
|
208
|
+
* @param {string} filePath
|
|
209
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number, raw: string }>}
|
|
210
|
+
*/
|
|
211
|
+
export function extractFrontendApiCalls(filePath) {
|
|
212
|
+
const content = readFileSync(filePath, 'utf8')
|
|
213
|
+
const lines = content.split('\n')
|
|
214
|
+
const results = []
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < lines.length; i++) {
|
|
217
|
+
const line = lines[i]
|
|
218
|
+
|
|
219
|
+
// Pattern 1: apiFetch<T>("/path", { method: "POST" }) — default GET
|
|
220
|
+
const apiFetchMatch = line.match(/apiFetch\s*(?:<[^>]*>)?\s*\(\s*["'`]([^"'`]+)["'`]/)
|
|
221
|
+
if (apiFetchMatch) {
|
|
222
|
+
// 检查是否有 method 字段(在后续行或同一行)
|
|
223
|
+
let method = 'GET'
|
|
224
|
+
const snippet = lines.slice(i, Math.min(i + 3, lines.length)).join(' ')
|
|
225
|
+
const methodMatch = snippet.match(/method\s*:\s*["'`](GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)["'`]/i)
|
|
226
|
+
if (methodMatch) method = methodMatch[1].toUpperCase()
|
|
227
|
+
|
|
228
|
+
results.push({
|
|
229
|
+
method,
|
|
230
|
+
path: normalizePath(apiFetchMatch[1]),
|
|
231
|
+
source: filePath,
|
|
232
|
+
line: i + 1,
|
|
233
|
+
raw: apiFetchMatch[1],
|
|
234
|
+
})
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Pattern 2: axios.get/post/put/delete("/path")
|
|
239
|
+
const axiosMatch = line.match(/axios\.(get|post|put|delete|patch)\s*\(\s*["'`]([^"'`]+)["'`]/i)
|
|
240
|
+
if (axiosMatch) {
|
|
241
|
+
results.push({
|
|
242
|
+
method: axiosMatch[1].toUpperCase(),
|
|
243
|
+
path: normalizePath(axiosMatch[2]),
|
|
244
|
+
source: filePath,
|
|
245
|
+
line: i + 1,
|
|
246
|
+
raw: axiosMatch[2],
|
|
247
|
+
})
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Pattern 3: fetch("/api/xxx", { method: "POST" })
|
|
252
|
+
const fetchMatch = line.match(/(?:api_?)?fetch\s*\(\s*["'`]([^"'`]+)["'`]/)
|
|
253
|
+
if (fetchMatch && !apiFetchMatch) {
|
|
254
|
+
let method = 'GET'
|
|
255
|
+
const snippet = lines.slice(i, Math.min(i + 3, lines.length)).join(' ')
|
|
256
|
+
const methodMatch = snippet.match(/method\s*:\s*["'`](GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)["'`]/i)
|
|
257
|
+
if (methodMatch) method = methodMatch[1].toUpperCase()
|
|
258
|
+
|
|
259
|
+
results.push({
|
|
260
|
+
method,
|
|
261
|
+
path: normalizePath(fetchMatch[1]),
|
|
262
|
+
source: filePath,
|
|
263
|
+
line: i + 1,
|
|
264
|
+
raw: fetchMatch[1],
|
|
265
|
+
})
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return results
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 递归扫描前端目录的 API 调用
|
|
275
|
+
* @param {string} dir
|
|
276
|
+
* @param {{ filePattern?: RegExp, excludePatterns?: RegExp[] }} opts
|
|
277
|
+
* @returns {Array<{ method: string, path: string, source: string, line: number, raw: string }>}
|
|
278
|
+
*/
|
|
279
|
+
export function scanFrontendApiCalls(dir, opts = {}) {
|
|
280
|
+
const filePattern = opts.filePattern || /\.(ts|tsx|js|jsx)$/
|
|
281
|
+
// 排除清单(坑 parity-scan-stale-dirs,2026-08-21 实证:扫进 .claude/worktrees/agent-* 陈旧
|
|
282
|
+
// 检出与 build 产物,872 条调用全是噪音)——目录名匹配,覆盖:
|
|
283
|
+
// 版本控制/编辑器:.git .claude(含 worktrees/agent-* 陈旧检出).vscode .idea .cursor
|
|
284
|
+
// 构建产物:dist build out .next .nuxt .output .turbo .parcel-cache coverage
|
|
285
|
+
// 依赖/运行时:node_modules .venv venv target(rust/java)__pycache__
|
|
286
|
+
// 本工具自身:.sillyspec .sillyspec-platform.json 所在 .worktrees
|
|
287
|
+
// 文件级:.d.ts 声明、*.min.js / *.bundle.js 压缩产物(正则命中即跳)
|
|
288
|
+
const excludePatterns = opts.excludePatterns || [
|
|
289
|
+
/^node_modules$/, /^\.git$/, /^\.claude$/, /^\.vscode$/, /^\.idea$/, /^\.cursor$/,
|
|
290
|
+
/^dist$/, /^build$/, /^out$/, /^\.next$/, /^\.nuxt$/, /^\.output$/, /^\.turbo$/,
|
|
291
|
+
/^\.parcel-cache$/, /^coverage$/, /^\.venv$/, /^venv$/, /^target$/, /^__pycache__$/,
|
|
292
|
+
/^\.sillyspec$/, /^\.worktrees$/, /^__tests__$/, /^\.d\.ts$/,
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
const results = []
|
|
296
|
+
if (!existsSync(dir)) return results
|
|
297
|
+
|
|
298
|
+
function walk(d) {
|
|
299
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
300
|
+
const full = join(d, entry.name)
|
|
301
|
+
if (entry.isDirectory()) {
|
|
302
|
+
if (excludePatterns.some(p => p.test(entry.name))) continue
|
|
303
|
+
walk(full)
|
|
304
|
+
} else if (entry.isFile() && filePattern.test(entry.name)) {
|
|
305
|
+
if (/\.d\.ts$/.test(entry.name)) continue
|
|
306
|
+
if (/\.min\.(js|ts)$/.test(entry.name) || /\.bundle\.(js|ts)$/.test(entry.name)) continue
|
|
307
|
+
try {
|
|
308
|
+
results.push(...extractFrontendApiCalls(full))
|
|
309
|
+
} catch {}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
walk(dir)
|
|
314
|
+
return results
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── 路径归一化 ─────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 将动态路径归一化为参数占位符
|
|
321
|
+
* /api/ppm/project-plan/${id}/plan-nodes → /api/ppm/project-plan/{param}/plan-nodes
|
|
322
|
+
* /api/ppm/project-plan/:planId/plan-nodes → /api/ppm/project-plan/{param}/plan-nodes
|
|
323
|
+
* @param {string} rawPath
|
|
324
|
+
* @returns {string}
|
|
325
|
+
*/
|
|
326
|
+
export function normalizePath(rawPath) {
|
|
327
|
+
return rawPath
|
|
328
|
+
.replace(/\$\{[^}]+\}/g, '{param}')
|
|
329
|
+
.replace(/:\w+/g, '{param}')
|
|
330
|
+
.replace(/\$\w+/g, '{param}')
|
|
331
|
+
.replace(/\{[^}]+\}/g, '{param}') // Spring {planId} / FastAPI {plan_id} → {param}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ─── 对账 ───────────────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 比较前端调用的路径和后端注册的端点,返回差异
|
|
338
|
+
*
|
|
339
|
+
* @param {Array<{ path: string, method: string, source: string }>} frontendCalls
|
|
340
|
+
* @param {Array<{ path: string, method: string, source: string }>} backendEndpoints
|
|
341
|
+
* @returns {{
|
|
342
|
+
* missingBackend: Array<{ path: string, method: string, consumerFile: string, consumerLine: number }>,
|
|
343
|
+
* unusedBackend: Array<{ path: string, method: string, providerFile: string }>
|
|
344
|
+
* }}
|
|
345
|
+
*/
|
|
346
|
+
export function diffApiParity(frontendCalls, backendEndpoints) {
|
|
347
|
+
// 构建 backend 注册表:归一化 path + method → endpoint
|
|
348
|
+
const backendMap = new Map()
|
|
349
|
+
for (const ep of backendEndpoints) {
|
|
350
|
+
const key = `${ep.method}:${normalizePath(ep.path)}`
|
|
351
|
+
if (!backendMap.has(key)) backendMap.set(key, ep)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const missingBackend = []
|
|
355
|
+
for (const call of frontendCalls) {
|
|
356
|
+
const key = `${call.method}:${normalizePath(call.path)}`
|
|
357
|
+
if (!backendMap.has(key)) {
|
|
358
|
+
missingBackend.push({
|
|
359
|
+
path: normalizePath(call.path),
|
|
360
|
+
method: call.method,
|
|
361
|
+
consumerFile: call.source,
|
|
362
|
+
consumerLine: call.line,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 构建 frontend 调用表
|
|
368
|
+
const frontendSet = new Set(
|
|
369
|
+
frontendCalls.map(c => `${c.method}:${normalizePath(c.path)}`)
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
const unusedBackend = []
|
|
373
|
+
for (const ep of backendEndpoints) {
|
|
374
|
+
const key = `${ep.method}:${normalizePath(ep.path)}`
|
|
375
|
+
if (!frontendSet.has(key)) {
|
|
376
|
+
unusedBackend.push({
|
|
377
|
+
path: normalizePath(ep.path),
|
|
378
|
+
method: ep.method,
|
|
379
|
+
providerFile: ep.source,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return { missingBackend, unusedBackend, ok: missingBackend.length === 0 }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ─── CLI 入口 ────────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* CLI 子命令入口:sillyspec contract scan [--backend dir] [--frontend dir]
|
|
391
|
+
* 输出 JSON 格式的端点清单和对账结果
|
|
392
|
+
*/
|
|
393
|
+
export async function contractScan(args, cwd) {
|
|
394
|
+
const backendIdx = args.indexOf('--backend')
|
|
395
|
+
const frontendIdx = args.indexOf('--frontend')
|
|
396
|
+
const backendDir = backendIdx !== -1 && args[backendIdx + 1]
|
|
397
|
+
? resolve(cwd, args[backendIdx + 1])
|
|
398
|
+
: resolve(cwd, 'backend')
|
|
399
|
+
const frontendDir = frontendIdx !== -1 && args[frontendIdx + 1]
|
|
400
|
+
? resolve(cwd, args[frontendIdx + 1])
|
|
401
|
+
: resolve(cwd, 'frontend')
|
|
402
|
+
|
|
403
|
+
const backendEndpoints = scanBackendEndpoints(backendDir)
|
|
404
|
+
const frontendCalls = scanFrontendApiCalls(frontendDir)
|
|
405
|
+
const { missingBackend, unusedBackend } = diffApiParity(frontendCalls, backendEndpoints)
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
backend: backendEndpoints.map(e => ({ method: e.method, path: normalizePath(e.path), file: e.source })),
|
|
409
|
+
frontend: frontendCalls.map(c => ({ method: c.method, path: normalizePath(c.path), file: c.source })),
|
|
410
|
+
missingBackend,
|
|
411
|
+
unusedBackend,
|
|
412
|
+
summary: {
|
|
413
|
+
backendEndpointCount: backendEndpoints.length,
|
|
414
|
+
frontendCallCount: frontendCalls.length,
|
|
415
|
+
missingBackendCount: missingBackend.length,
|
|
416
|
+
unusedBackendCount: unusedBackend.length,
|
|
417
|
+
ok: missingBackend.length === 0,
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 他者会话显式声明文件集(坑 verify-reconcile-foreign-wip,2026-08-23 实证:verify 实测
|
|
3
|
+
* 对账在主仓共享工作区取 git diff,无 meta 回退时把并行会话在途 WIP 全量算进本变更——误
|
|
4
|
+
* 命中他者模块跑他者测试 / 他者删除进本变更删除对账误报)。
|
|
5
|
+
*
|
|
6
|
+
* 归属口径与坑 35(quick 审计豁免,run/shared 的 collectOtherQuickSessionDeclarations)
|
|
7
|
+
* 同款:只认**显式声明**——其他 quick 会话 guard.allowedFiles + 其他变更 design §6 清单;
|
|
8
|
+
* 无主文件保留参与判定(fail-closed,不放松「未声明删除/越界」的抓取底线)。
|
|
9
|
+
*
|
|
10
|
+
* 独立零环模块:被 verify-postcheck / verify-probes / contract-matrix 共用(verify-
|
|
11
|
+
* postcheck import contract-matrix,parity 侧不能反向 import verify-postcheck)。
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, readdirSync } from 'fs'
|
|
14
|
+
import { join } from 'path'
|
|
15
|
+
import { parseFileChangeListDetailed } from './change-list.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} cwd 项目根
|
|
19
|
+
* @param {string|null} currentChangeName 本变更名(quick 会话名或 change 名,排除自身)
|
|
20
|
+
* @returns {Map<string, string[]>} file → 声明者列表(quick-<hex> 或变更名)
|
|
21
|
+
*/
|
|
22
|
+
export function collectForeignDeclaredFiles(cwd, currentChangeName) {
|
|
23
|
+
const foreign = new Map()
|
|
24
|
+
const add = (file, owner) => {
|
|
25
|
+
const f = String(file || '').replace(/\\/g, '/')
|
|
26
|
+
if (!f) return
|
|
27
|
+
if (!foreign.has(f)) foreign.set(f, [])
|
|
28
|
+
if (!foreign.get(f).includes(owner)) foreign.get(f).push(owner)
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const specBase = join(cwd, '.sillyspec')
|
|
32
|
+
// ① 其他 quick 会话的 --files 显式声明(guard.json)
|
|
33
|
+
const sessionsDir = join(specBase, '.runtime', 'quick-sessions')
|
|
34
|
+
let sessionDirs = []
|
|
35
|
+
try { sessionDirs = readdirSync(sessionsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name) } catch {}
|
|
36
|
+
for (const sid of sessionDirs) {
|
|
37
|
+
if (sid === currentChangeName) continue
|
|
38
|
+
try {
|
|
39
|
+
const guard = JSON.parse(readFileSync(join(sessionsDir, sid, 'guard.json'), 'utf8'))
|
|
40
|
+
for (const f of Array.isArray(guard.allowedFiles) ? guard.allowedFiles : []) add(f, sid)
|
|
41
|
+
} catch { /* 损坏/缺失跳过 */ }
|
|
42
|
+
}
|
|
43
|
+
// ② 其他变更的 design §6 清单(目录在即视为在途变更;archive/ 下不算)
|
|
44
|
+
const changesDir = join(specBase, 'changes')
|
|
45
|
+
let changeDirs = []
|
|
46
|
+
try { changeDirs = readdirSync(changesDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name) } catch {}
|
|
47
|
+
for (const cn of changeDirs) {
|
|
48
|
+
if (cn === currentChangeName || cn === 'archive') continue
|
|
49
|
+
const designPath = join(changesDir, cn, 'design.md')
|
|
50
|
+
if (!existsSync(designPath)) continue
|
|
51
|
+
try {
|
|
52
|
+
for (const e of parseFileChangeListDetailed(designPath)) add(e.path, cn)
|
|
53
|
+
} catch { /* 清单解析失败跳过该变更 */ }
|
|
54
|
+
}
|
|
55
|
+
} catch { /* 整体异常 → 空集(等价不过滤,回到原行为) */ }
|
|
56
|
+
return foreign
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 按他者声明集切分 diff 文件(collectForeignDeclaredFiles 的消费端快捷封装)。
|
|
61
|
+
* @param {string} cwd 项目根
|
|
62
|
+
* @param {string|null} currentChangeName 本变更名
|
|
63
|
+
* @param {string[]|null} diffFiles 待切分文件列表(null 透传,保持调用方 null 语义)
|
|
64
|
+
* @returns {{ own: string[]|null, foreign: Array<{file: string, owners: string[]}> }}
|
|
65
|
+
*/
|
|
66
|
+
export function splitOwnVsForeignDiffFiles(cwd, currentChangeName, diffFiles) {
|
|
67
|
+
if (!Array.isArray(diffFiles)) return { own: diffFiles, foreign: [] }
|
|
68
|
+
const foreignMap = collectForeignDeclaredFiles(cwd, currentChangeName)
|
|
69
|
+
if (foreignMap.size === 0) return { own: diffFiles, foreign: [] }
|
|
70
|
+
const own = []
|
|
71
|
+
const foreign = []
|
|
72
|
+
for (const f of diffFiles) {
|
|
73
|
+
const owners = foreignMap.get(String(f).replace(/\\/g, '/'))
|
|
74
|
+
if (owners) foreign.push({ file: f, owners })
|
|
75
|
+
else own.push(f)
|
|
76
|
+
}
|
|
77
|
+
return { own, foreign }
|
|
78
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fs-atomic.js — 原子文件写 + Windows 友好的 rename 重试
|
|
3
|
+
*
|
|
4
|
+
* 用于 .runtime/*.json、pointer、guard.json 等「会被其他进程 / hook 读取」的配置文件。
|
|
5
|
+
* 保证读者要么看到旧完整内容、要么看到新完整内容,不会读到半截 JSON。
|
|
6
|
+
*
|
|
7
|
+
* 注意:DB 持久化由 node:sqlite 引擎承担(提交即落盘 sillyspec.db + WAL 侧车),
|
|
8
|
+
* 不经此处的 writeAtomicSync;sillyspec.db 不走原子写改名。
|
|
9
|
+
*/
|
|
10
|
+
import { writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
11
|
+
import { randomBytes } from 'node:crypto';
|
|
12
|
+
import { dirname, basename, join } from 'path';
|
|
13
|
+
|
|
14
|
+
// Windows 上杀毒 / 索引 / IDE 占用文件时,rename 偶发这些错误,短退避重试即可
|
|
15
|
+
const RENAME_RETRY_CODES = ['EPERM', 'EBUSY', 'EACCES', 'ENOTEMPTY'];
|
|
16
|
+
|
|
17
|
+
/** 同步退避:优先 Atomics.wait,不可用时退化忙等(仅在 rename 冲突的极端情况触发)。 */
|
|
18
|
+
function sleepSync(ms) {
|
|
19
|
+
try {
|
|
20
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
21
|
+
} catch {
|
|
22
|
+
const end = Date.now() + ms;
|
|
23
|
+
while (Date.now() < end) { /* busy wait */ }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* rename 带退避重试,覆盖 Windows 偶发的 EPERM/EBUSY/EACCES/ENOTEMPTY。
|
|
29
|
+
* 目录 rename 同样适用(归档移动目录时可复用)。
|
|
30
|
+
* @param {string} from
|
|
31
|
+
* @param {string} to
|
|
32
|
+
* @param {number} [retries=5]
|
|
33
|
+
*/
|
|
34
|
+
export function renameSyncRetry(from, to, retries = 5) {
|
|
35
|
+
let lastErr;
|
|
36
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
renameSync(from, to);
|
|
39
|
+
return;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
lastErr = err;
|
|
42
|
+
const code = err && err.code;
|
|
43
|
+
// 非可重试错误码立即抛出,不浪费时间
|
|
44
|
+
if (!code || !RENAME_RETRY_CODES.includes(code)) throw err;
|
|
45
|
+
if (attempt < retries) sleepSync(15 * (attempt + 1));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw lastErr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 原子写文本文件:同目录 tmp 写入 → rename 覆盖目标(带 Windows 退避重试)。
|
|
53
|
+
* - tmp 名含 pid,避免多进程并发写同一目标时 tmp 名碰撞(如多会话写 local.yaml)。
|
|
54
|
+
* - rename 失败时清理 tmp,避免留下孤儿 .tmp 文件。
|
|
55
|
+
* - Node 的 rename 在 Windows 上用 MoveFileEx(REPLACE_EXISTING),可覆盖已存在目标;
|
|
56
|
+
* EPERM 仅在文件被其他进程锁定时出现,由 renameSyncRetry 退避重试。
|
|
57
|
+
* @param {string} filePath - 目标文件绝对路径
|
|
58
|
+
* @param {string} content - 文件内容
|
|
59
|
+
*/
|
|
60
|
+
export function writeAtomicSync(filePath, content) {
|
|
61
|
+
const dir = dirname(filePath);
|
|
62
|
+
// pid + 随机段双因子:Windows PID 重用激进,两进程可能撞同 pid,tmp 名碰撞 rename 互相覆盖。
|
|
63
|
+
// crypto 随机源(Math.random 可预测,tmp 名被预猜中有 symlink 预放置理论面)
|
|
64
|
+
const rnd = randomBytes(6).toString('hex');
|
|
65
|
+
const tmpPath = join(dir, `.${basename(filePath)}.${process.pid}.${rnd}.tmp`);
|
|
66
|
+
writeFileSync(tmpPath, content);
|
|
67
|
+
try {
|
|
68
|
+
renameSyncRetry(tmpPath, filePath);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
try { unlinkSync(tmpPath); } catch { /* tmp 可能已被 rename 走,忽略 */ }
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|