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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SillySpec spec 目录拼写变体检测
|
|
3
|
+
*
|
|
4
|
+
* review.json missing 时调用:检测 SPEC_ROOT 父目录下是否存在 .sillyspec 的
|
|
5
|
+
* 近似拼写变体目录(如 .silyspec / .sillyspc),命中则提示「路径疑似拼错」。
|
|
6
|
+
*
|
|
7
|
+
* 触发场景:agent/用户手动创建 review.json 时把 .sillyspec 拼错(少 l / 多 k 等),
|
|
8
|
+
* 文件落到变体目录,CLI 按规范名找不到。给一条模糊匹配提示比静默报 missing 友好。
|
|
9
|
+
*
|
|
10
|
+
* 放独立模块(而非 stage-review.js / task-review.js 内联)的原因:两者已存在
|
|
11
|
+
* stage-review → task-review 的常量依赖,反向 import 会形成循环依赖。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { dirname, basename, join } from 'path'
|
|
15
|
+
import { readdirSync } from 'fs'
|
|
16
|
+
// levenshtein 单一实现收敛到 run/shared.js(原本地副本与 shared.js 逐行相同);
|
|
17
|
+
// shared.js 不静态 import review 族,此处引入不成环
|
|
18
|
+
import { levenshtein } from './run/shared.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 检测 spec 目录拼写变体。
|
|
22
|
+
* @param {string} runtimeRoot — .runtime 目录绝对路径(= <SPEC_ROOT>/.runtime)
|
|
23
|
+
* @returns {{ typoDir: string, canonical: string } | null} 命中返回变体目录与规范名,否则 null
|
|
24
|
+
*/
|
|
25
|
+
export function detectSpecDirTypo(runtimeRoot) {
|
|
26
|
+
if (!runtimeRoot) return null
|
|
27
|
+
// runtimeRoot = <SPEC_ROOT>/.runtime → SPEC_ROOT = dirname(runtimeRoot)
|
|
28
|
+
const specRoot = dirname(runtimeRoot)
|
|
29
|
+
const parent = dirname(specRoot)
|
|
30
|
+
const canonical = basename(specRoot) // 期望 '.sillyspec'
|
|
31
|
+
if (!canonical.startsWith('.')) return null // 非隐藏目录命名,跳过(避免误报)
|
|
32
|
+
|
|
33
|
+
let siblings = []
|
|
34
|
+
try {
|
|
35
|
+
siblings = readdirSync(parent, { withFileTypes: true })
|
|
36
|
+
.filter((d) => d.isDirectory() && d.name.startsWith('.') && !d.name.startsWith('..') && d.name !== canonical)
|
|
37
|
+
.map((d) => d.name)
|
|
38
|
+
} catch {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
if (siblings.length === 0) return null
|
|
42
|
+
|
|
43
|
+
// 编辑距离 ≤2 视为拼写变体(覆盖少 l / 多 k / 漏字母等常见笔误)
|
|
44
|
+
const typo = siblings.find((name) => {
|
|
45
|
+
const dist = levenshtein(name, canonical)
|
|
46
|
+
return dist > 0 && dist <= 2
|
|
47
|
+
})
|
|
48
|
+
if (!typo) return null
|
|
49
|
+
return { typoDir: join(parent, typo), canonical }
|
|
50
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 直跑 spec 文件增量同步模块。
|
|
3
|
+
*
|
|
4
|
+
* Change 2026-08-17-spec-file-incremental-sync:daemon↔后端已有增量同步协议,但 CLI
|
|
5
|
+
* 直跑场景(本地 agent 直接跑 sillyspec 无 daemon)只有四件套直推到 documents 列。
|
|
6
|
+
* 本模块补齐 CLI 直跑的文件树增量同步:以服务器清单为锚,walk/hash/diff 后只推送
|
|
7
|
+
* 变化文件(add/update/delete/rename),无差异时短路不发请求。
|
|
8
|
+
*
|
|
9
|
+
* 复用 daemon 已验证的排除口径(UPLOAD_EXCLUDE_TOP_BASE / UPLOAD_PRUNE_NAMES_BASE),
|
|
10
|
+
* 与 daemon 共用同一套清单语义(base_version 乐观锁、conflict 提示但不阻塞)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createHash } from 'crypto';
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
15
|
+
import { join, relative, sep } from 'path';
|
|
16
|
+
|
|
17
|
+
// 与 sillyhub-daemon/src/spec-sync.ts 共用排除口径(task-07 / FR-06 / D-008@v2):
|
|
18
|
+
// .runtime(有点)/ runtime(无点)/ projects(本地环境文件)在顶层排除;
|
|
19
|
+
// worktrees 在任意深度按 basename 剪枝(可能嵌在 .runtime/worktrees 下)。
|
|
20
|
+
const UPLOAD_EXCLUDE_TOP_BASE = new Set(['.runtime', 'runtime', 'projects']);
|
|
21
|
+
const UPLOAD_PRUNE_NAMES_BASE = new Set(['worktrees']);
|
|
22
|
+
// ql-20260818-002:local.yaml 是本机连接配置(platform/mcp 段含 shpsync_ token),
|
|
23
|
+
// 不上传——与服务器侧过滤(backend spec_workspace SERVER_EXCLUDED_FILENAMES)双侧
|
|
24
|
+
// 对齐。服务器清单残留 local.yaml 行时,本地 walk 不含它 → 生成 delete op,
|
|
25
|
+
// 服务器放行 delete 清存量(token 不落 landing 树、不随 bundle 跨机分发)。
|
|
26
|
+
const UPLOAD_EXCLUDE_FILENAMES = new Set(['local.yaml']);
|
|
27
|
+
|
|
28
|
+
function isUploadExcludedPath(relPath) {
|
|
29
|
+
const segs = relPath.split('/');
|
|
30
|
+
if (UPLOAD_EXCLUDE_TOP_BASE.has(segs[0] ?? '')) return true;
|
|
31
|
+
if (UPLOAD_EXCLUDE_FILENAMES.has(segs[segs.length - 1] ?? '')) return true;
|
|
32
|
+
return segs.some((s) => UPLOAD_PRUNE_NAMES_BASE.has(s));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toPosix(p) {
|
|
36
|
+
return p.replace(/\\/g, '/');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function debugLog(msg) {
|
|
40
|
+
if (process.env.SILLYSPEC_DEBUG_SYNC) console.warn(msg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _hashBuffer(buf) {
|
|
44
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 单遍 walk 本地 .sillyspec 树。
|
|
49
|
+
*
|
|
50
|
+
* 返回 { path, absPath, mtimeMs } 列表;path 已统一为 POSIX。
|
|
51
|
+
* 不收集目录项(只关心文件);命中排除规则的路径直接跳过不递归。
|
|
52
|
+
*/
|
|
53
|
+
export function walkSpecTree(specRoot) {
|
|
54
|
+
const out = [];
|
|
55
|
+
if (!existsSync(specRoot)) return out;
|
|
56
|
+
|
|
57
|
+
function recurse(dir) {
|
|
58
|
+
let names;
|
|
59
|
+
try {
|
|
60
|
+
names = readdirSync(dir);
|
|
61
|
+
} catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const name of names) {
|
|
65
|
+
const abs = join(dir, name);
|
|
66
|
+
let st;
|
|
67
|
+
try {
|
|
68
|
+
st = statSync(abs);
|
|
69
|
+
} catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const relToRoot = toPosix(relative(specRoot, abs));
|
|
73
|
+
if (isUploadExcludedPath(relToRoot)) continue;
|
|
74
|
+
// pruneNames:任意深度按 basename 命中即不收集/不递归
|
|
75
|
+
if (UPLOAD_PRUNE_NAMES_BASE.has(name)) continue;
|
|
76
|
+
|
|
77
|
+
if (st.isDirectory()) {
|
|
78
|
+
recurse(abs);
|
|
79
|
+
} else if (st.isFile()) {
|
|
80
|
+
out.push({ path: relToRoot, absPath: abs, mtimeMs: st.mtimeMs });
|
|
81
|
+
}
|
|
82
|
+
// symlink / 其他:跳过
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
recurse(specRoot);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 计算本地文件哈希。
|
|
92
|
+
*
|
|
93
|
+
* 返回 { path, hash, mtime, absPath }(mtime 为 Unix 秒,供 os.utime;absPath
|
|
94
|
+
* 保留给 computeSpecOps 读 content 用,避免按相对路径回退错位)。
|
|
95
|
+
*/
|
|
96
|
+
export function hashFiles(entries) {
|
|
97
|
+
return entries.map((e) => {
|
|
98
|
+
const buf = readFileSync(e.absPath);
|
|
99
|
+
return {
|
|
100
|
+
path: e.path,
|
|
101
|
+
hash: _hashBuffer(buf),
|
|
102
|
+
mtime: Math.floor(e.mtimeMs / 1000),
|
|
103
|
+
absPath: e.absPath,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 根据服务器清单与本地文件差异生成 FileOp[]。
|
|
110
|
+
*
|
|
111
|
+
* 服务器清单格式:{ [path]: { hash, version, exists } }
|
|
112
|
+
* 本地文件格式:hashFiles 结果 { path, hash, mtime }
|
|
113
|
+
*
|
|
114
|
+
* 规则(与 daemon computeIncrementalOps 对齐):
|
|
115
|
+
* - 本地有、服务器无 → add(base_version=0)
|
|
116
|
+
* - 服务器有 exists=true、本地无 → delete(base_version=服务器 version)
|
|
117
|
+
* - 服务器有 exists=false、本地无 → 不生成 delete(已软删)
|
|
118
|
+
* - 同路径 hash 不同 → update(base_version=服务器 version)
|
|
119
|
+
* - 服务器旧路径有、本地新路径有且 hash 相同 → rename(base_version=服务器旧路径 version)
|
|
120
|
+
*/
|
|
121
|
+
export function computeSpecOps(serverManifest, localFiles) {
|
|
122
|
+
const server = serverManifest || {};
|
|
123
|
+
const localMap = new Map(localFiles.map((f) => [f.path, f]));
|
|
124
|
+
const localSet = new Set(localMap.keys());
|
|
125
|
+
|
|
126
|
+
// 只拿服务器 exists=true 的行参与 diff;exists=false 视为已删除,不生成 delete
|
|
127
|
+
const serverPaths = Object.keys(server).filter((p) => server[p]?.exists !== false);
|
|
128
|
+
const serverSet = new Set(serverPaths);
|
|
129
|
+
|
|
130
|
+
// fail-closed 护栏(2026-08-20 全量体检 BUG-01):本地树为空而服务器非空时,
|
|
131
|
+
// 全量 delete 几乎必然是「锚错了目录」(如平台模式 cwd/.sillyspec 只剩 local.yaml
|
|
132
|
+
// 被 walk 排除),而不是用户真的删光了所有文件——一旦发出就是服务器整树清空。
|
|
133
|
+
// 唯一放行:服务器只剩 local.yaml 存量(顶层单文件),保留「delete 清 token 存量」
|
|
134
|
+
// 的既定语义(见 UPLOAD_EXCLUDE_FILENAMES 注释)。真实清空需求需本地树非空逐轮收敛。
|
|
135
|
+
const nonTrivialServer = serverPaths.filter((p) => p !== 'local.yaml');
|
|
136
|
+
if (localFiles.length === 0 && nonTrivialServer.length > 0) {
|
|
137
|
+
console.warn(
|
|
138
|
+
`[spec-sync] 本地 spec 树为空但服务器有 ${serverPaths.length} 个文件,跳过同步(防误清空;请检查 spec 目录锚点)`
|
|
139
|
+
);
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// rename 检测:旧路径(服务器有、本地无)↔ 新路径(本地有、服务器无)hash 相同
|
|
144
|
+
const renames = [];
|
|
145
|
+
const consumedNew = new Set();
|
|
146
|
+
for (const oldPath of serverPaths) {
|
|
147
|
+
if (localSet.has(oldPath)) continue;
|
|
148
|
+
const cachedEntry = server[oldPath];
|
|
149
|
+
for (const newPath of localMap.keys()) {
|
|
150
|
+
if (serverSet.has(newPath)) continue;
|
|
151
|
+
if (consumedNew.has(newPath)) continue;
|
|
152
|
+
if (localMap.get(newPath).hash === cachedEntry.hash) {
|
|
153
|
+
renames.push({ oldPath, newPath });
|
|
154
|
+
consumedNew.add(newPath);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const renamedOld = new Set(renames.map((r) => r.oldPath));
|
|
160
|
+
const renamedNew = new Set(renames.map((r) => r.newPath));
|
|
161
|
+
|
|
162
|
+
const ops = [];
|
|
163
|
+
|
|
164
|
+
for (const r of renames) {
|
|
165
|
+
ops.push({
|
|
166
|
+
op: 'rename',
|
|
167
|
+
path: r.oldPath,
|
|
168
|
+
new_path: r.newPath,
|
|
169
|
+
base_version: server[r.oldPath].version,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const p of serverPaths) {
|
|
174
|
+
if (renamedOld.has(p)) continue;
|
|
175
|
+
if (localSet.has(p)) {
|
|
176
|
+
const localEntry = localMap.get(p);
|
|
177
|
+
if (localEntry.hash !== server[p].hash) {
|
|
178
|
+
const content = readFileSync(localEntry.absPath ?? join('.', p)).toString('base64');
|
|
179
|
+
ops.push({
|
|
180
|
+
op: 'update',
|
|
181
|
+
path: p,
|
|
182
|
+
hash: localEntry.hash,
|
|
183
|
+
content,
|
|
184
|
+
base_version: server[p].version,
|
|
185
|
+
mtime: localEntry.mtime,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
ops.push({
|
|
190
|
+
op: 'delete',
|
|
191
|
+
path: p,
|
|
192
|
+
base_version: server[p].version,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const p of localMap.keys()) {
|
|
198
|
+
if (renamedNew.has(p)) continue;
|
|
199
|
+
if (!serverSet.has(p)) {
|
|
200
|
+
const localEntry = localMap.get(p);
|
|
201
|
+
const content = readFileSync(localEntry.absPath ?? join('.', p)).toString('base64');
|
|
202
|
+
ops.push({
|
|
203
|
+
op: 'add',
|
|
204
|
+
path: p,
|
|
205
|
+
hash: localEntry.hash,
|
|
206
|
+
content,
|
|
207
|
+
base_version: 0,
|
|
208
|
+
mtime: localEntry.mtime,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return ops;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 从 ops 提取本次涉及的变更目录名集合(change_dirs 标注,D-005@v1)。
|
|
218
|
+
*
|
|
219
|
+
* 对每个 op 的 path(rename 含新旧 path),取 `changes/<name>/` 或
|
|
220
|
+
* `changes/archive/<name>/` 前缀分组的 key(去掉前缀、取第一段目录名),去重。
|
|
221
|
+
* 非 changes 前缀路径不进结果。best-effort,失败降级 []。
|
|
222
|
+
*/
|
|
223
|
+
export function extractChangeDirs(ops) {
|
|
224
|
+
const dirs = new Set();
|
|
225
|
+
for (const op of ops) {
|
|
226
|
+
collectChangeDir(dirs, op.path);
|
|
227
|
+
if (op.new_path) collectChangeDir(dirs, op.new_path);
|
|
228
|
+
}
|
|
229
|
+
return [...dirs];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function collectChangeDir(dirs, p) {
|
|
233
|
+
const norm = p.split(/[\\/]/).join('/');
|
|
234
|
+
let rest = null;
|
|
235
|
+
if (norm.startsWith('changes/')) {
|
|
236
|
+
rest = norm.slice('changes/'.length);
|
|
237
|
+
} else if (norm.startsWith('changes\\')) {
|
|
238
|
+
rest = norm.slice('changes\\'.length).replace(/\\/g, '/');
|
|
239
|
+
}
|
|
240
|
+
if (!rest) return;
|
|
241
|
+
|
|
242
|
+
if (rest.startsWith('archive/')) {
|
|
243
|
+
rest = rest.slice('archive/'.length);
|
|
244
|
+
}
|
|
245
|
+
const first = rest.split('/', 1)[0];
|
|
246
|
+
if (first && first !== 'archive' && !first.includes('/')) {
|
|
247
|
+
dirs.add(first);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 同步本地 .sillyspec 树到平台(CLI 直跑增量同步入口)。
|
|
253
|
+
*
|
|
254
|
+
* @param {string} specRoot - 本地 .sillyspec 目录绝对路径
|
|
255
|
+
* @param {object} platform - { url, token }
|
|
256
|
+
* @param {string} changeName - 当前变更名
|
|
257
|
+
* @returns {Promise<{synced: number, conflict?: boolean, serverVersions?: object}>}
|
|
258
|
+
*/
|
|
259
|
+
export async function syncSpecTree(specRoot, platform, changeName, opts = {}) {
|
|
260
|
+
if (!platform || !platform.url || !platform.token) {
|
|
261
|
+
debugLog('[spec-sync] 未连接平台(本地合法状态);跳过 spec 树增量同步');
|
|
262
|
+
return { synced: 0 };
|
|
263
|
+
}
|
|
264
|
+
// HUB-09:单请求超时与外部熔断 signal 合并——trigger* 熔断时在飞请求被真实取消
|
|
265
|
+
const withSignal = (timeoutMs) => (opts.signal ? AbortSignal.any([AbortSignal.timeout(timeoutMs), opts.signal]) : AbortSignal.timeout(timeoutMs));
|
|
266
|
+
|
|
267
|
+
const manifestUrl = `${platform.url.replace(/\/$/, '')}/api/changes/-/spec-manifest`;
|
|
268
|
+
const syncUrl = `${platform.url.replace(/\/$/, '')}/api/changes/-/spec-sync`;
|
|
269
|
+
|
|
270
|
+
// 1. GET 服务器清单
|
|
271
|
+
let serverManifest = {};
|
|
272
|
+
try {
|
|
273
|
+
const res = await fetch(manifestUrl, {
|
|
274
|
+
headers: { Authorization: `Bearer ${platform.token}` },
|
|
275
|
+
signal: withSignal(10000),
|
|
276
|
+
});
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
// ql-20260818-008:debugLog → console.warn。树同步失败原本完全静默(要
|
|
279
|
+
// SILLYSPEC_DEBUG_SYNC=1 才可见),文件迟到平台且无任何线索(multi-agent-platform
|
|
280
|
+
// 2026-08-18-workspace-file-browser 实证:design/decisions 迟到 27 分钟、plan.md 迟到
|
|
281
|
+
// 8 分钟才被后续步骤的同步补上)。失败可见、成功不打扰。
|
|
282
|
+
console.warn(`[spec-sync] 拉取清单失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${changeName}`);
|
|
283
|
+
return { synced: 0 };
|
|
284
|
+
}
|
|
285
|
+
const body = await res.json().catch(() => ({}));
|
|
286
|
+
serverManifest = body.files || {};
|
|
287
|
+
} catch (err) {
|
|
288
|
+
console.warn(`[spec-sync] 拉取清单异常(文件树本次未同步,下次自动重试): ${changeName}: ${err.message}`);
|
|
289
|
+
return { synced: 0 };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 2. walk/hash/diff
|
|
293
|
+
const entries = walkSpecTree(specRoot);
|
|
294
|
+
const localFiles = hashFiles(entries);
|
|
295
|
+
const ops = computeSpecOps(serverManifest, localFiles);
|
|
296
|
+
|
|
297
|
+
if (ops.length === 0) {
|
|
298
|
+
debugLog(`[spec-sync] 无差异,跳过同步: ${changeName}`);
|
|
299
|
+
return { synced: 0 };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 3. POST ops
|
|
303
|
+
try {
|
|
304
|
+
const res = await fetch(syncUrl, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: {
|
|
307
|
+
'Content-Type': 'application/json',
|
|
308
|
+
Authorization: `Bearer ${platform.token}`,
|
|
309
|
+
},
|
|
310
|
+
body: JSON.stringify({ ops }),
|
|
311
|
+
signal: withSignal(30000),
|
|
312
|
+
});
|
|
313
|
+
if (!res.ok) {
|
|
314
|
+
console.warn(`[spec-sync] 同步请求失败 HTTP ${res.status}(文件树本次未同步,下次自动重试): ${changeName}`);
|
|
315
|
+
return { synced: 0 };
|
|
316
|
+
}
|
|
317
|
+
const body = await res.json().catch(() => ({}));
|
|
318
|
+
if (body.conflict) {
|
|
319
|
+
// HUB-08 冲突闭环:落 spec-sync-conflict-<change>.json(与进度 sync-conflict-* 同目录),
|
|
320
|
+
// 供 platform status 列出 + platform resolve 三态处置。此前只 warn 返回,下次 sync 用
|
|
321
|
+
// 同一 base_version 继续冲突循环,无人工裁决入口。
|
|
322
|
+
const serverVersions = body.server_versions || {};
|
|
323
|
+
let conflictPath = null;
|
|
324
|
+
try {
|
|
325
|
+
const runtimeDir = join(specRoot, '.runtime');
|
|
326
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
327
|
+
conflictPath = join(runtimeDir, `spec-sync-conflict-${changeName}.json`);
|
|
328
|
+
writeFileSync(conflictPath, JSON.stringify({
|
|
329
|
+
change: changeName,
|
|
330
|
+
kind: 'spec-tree',
|
|
331
|
+
created_at: new Date().toISOString(),
|
|
332
|
+
server_versions: serverVersions,
|
|
333
|
+
conflicting_paths: Object.keys(serverVersions),
|
|
334
|
+
note: 'spec 树文件冲突(服务器版本领先于本地 diff 基线)。resolve --keep-local 以本地为准重推;--take-platform 暂不支持(平台无文件下载端点)',
|
|
335
|
+
}, null, 2) + '\n', 'utf8');
|
|
336
|
+
} catch (e) {
|
|
337
|
+
console.warn(`[spec-sync] 冲突文件写入失败(冲突信息仅打印): ${e.message}`);
|
|
338
|
+
}
|
|
339
|
+
console.warn('');
|
|
340
|
+
console.warn(`⚠️ [spec-sync] 检测到 spec 树冲突(${Object.keys(serverVersions).length} 个文件,服务器版本: ${JSON.stringify(serverVersions)})`);
|
|
341
|
+
console.warn(`⚠️ 处置:sillyspec platform resolve ${changeName} --keep-local | --abort(冲突详情: ${conflictPath || '(写入失败)'})`);
|
|
342
|
+
return { synced: 0, conflict: true, serverVersions, conflictPath };
|
|
343
|
+
}
|
|
344
|
+
console.log(`[spec-sync] 已同步 ${ops.length} 个文件变更: ${changeName}`);
|
|
345
|
+
return { synced: ops.length };
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.warn(`[spec-sync] 同步异常(文件树本次未同步,下次自动重试): ${changeName}: ${err.message}`);
|
|
348
|
+
return { synced: 0 };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export default {
|
|
353
|
+
walkSpecTree,
|
|
354
|
+
hashFiles,
|
|
355
|
+
computeSpecOps,
|
|
356
|
+
extractChangeDirs,
|
|
357
|
+
syncSpecTree,
|
|
358
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stage-contract-engine.js — 产物字面校验通用引擎
|
|
3
|
+
*
|
|
4
|
+
* 消费 stage-contract-spec.js 的 manifest 规则,按 kind dispatch 判定,产出 {errors, warnings}。
|
|
5
|
+
* 哲学对齐 workflow.js 的 checkOutput(声明式 check + type 分发),但独立:支持 severity(error/warning)、
|
|
6
|
+
* 更丰富的纯 kind、custom kind(引擎 skip,留 validator 保留算法)。
|
|
7
|
+
*
|
|
8
|
+
* 设计原则:
|
|
9
|
+
* - 引擎不直接碰 fs:readFile/readDir 由调用方注入(默认原生)。CRLF 归一化在调用方收敛
|
|
10
|
+
* (plan-postcheck 注入 LF-normalizing reader),引擎拿到什么判什么,不在引擎层二次引入差异。
|
|
11
|
+
* - regex 每次 new RegExp:manifest 存 {pattern, flags} 字符串,规避 lastIndex 状态坑
|
|
12
|
+
* (旧代码多处 /g 正则跨调用复用需手写 lastIndex=0,是潜在 bug)。
|
|
13
|
+
* - 内容规则(literal-any、literal-all、regex、contains-section、field-present、header-field、list-non-empty、
|
|
14
|
+
* min-lines/no-placeholder-line)在 target 文件不存在时自动 skip(不报错)——存在性由独立的
|
|
15
|
+
* file-exists 规则保证,避免"文件缺失"被内容规则重复报。
|
|
16
|
+
* - custom kind 引擎 skip(不计 errors/warnings),由调用方 validator 保留判定算法,
|
|
17
|
+
* 但 import manifest 的 data + failMessage(同源)。
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readdirSync, readFileSync } from 'fs'
|
|
20
|
+
import { join } from 'path'
|
|
21
|
+
import { getRulesFor, isCustomKind } from './stage-contract-spec.js'
|
|
22
|
+
import { hasAllSections, hasNoPlaceholderLine, meetsMinLines } from './check-primitives.js'
|
|
23
|
+
|
|
24
|
+
// ============ IO 注入(默认原生 fs,调用方可替换为 normalized reader)============
|
|
25
|
+
|
|
26
|
+
function defaultReadFile(absPath) {
|
|
27
|
+
if (!existsSync(absPath)) return { exists: false, content: '' }
|
|
28
|
+
return { exists: true, content: readFileSync(absPath, 'utf8') }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultReadDir(absPath) {
|
|
32
|
+
if (!existsSync(absPath)) return { exists: false, files: [] }
|
|
33
|
+
try {
|
|
34
|
+
return { exists: true, files: readdirSync(absPath) }
|
|
35
|
+
} catch {
|
|
36
|
+
return { exists: false, files: [] }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_IO = { readFile: defaultReadFile, readDir: defaultReadDir }
|
|
41
|
+
|
|
42
|
+
// ============ target 解析 + scope 截取 ============
|
|
43
|
+
|
|
44
|
+
function resolveTargetPath(target, ctx) {
|
|
45
|
+
if (!target || !target.path) return null
|
|
46
|
+
switch (target.root) {
|
|
47
|
+
case 'change': return ctx.changeDir ? join(ctx.changeDir, target.path) : null
|
|
48
|
+
case 'docs': return ctx.docsRoot ? join(ctx.docsRoot, target.path) : null
|
|
49
|
+
case 'archive': return ctx.archiveDir ? join(ctx.archiveDir, target.path) : null
|
|
50
|
+
case 'specBase': return ctx.specBase ? join(ctx.specBase, target.path) : null
|
|
51
|
+
default: return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 按 scope 截取文本:full=全文 / head=前 512 字节(对齐 scan-postcheck header 切片)/ fm=frontmatter 块内容 */
|
|
56
|
+
function sliceScope(content, scope) {
|
|
57
|
+
if (!scope || scope === 'full') return content
|
|
58
|
+
if (scope === 'head') return content.slice(0, 512)
|
|
59
|
+
if (scope === 'fm') {
|
|
60
|
+
const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
61
|
+
return m ? m[1] : ''
|
|
62
|
+
}
|
|
63
|
+
return content
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============ 纯 kind dispatch ============
|
|
67
|
+
|
|
68
|
+
function dispatchPure(rule, entry) {
|
|
69
|
+
const { exists, content } = entry
|
|
70
|
+
// 存在性 kind:不存在即 fail
|
|
71
|
+
if (rule.kind === 'file-exists' || rule.kind === 'dir-exists') return exists
|
|
72
|
+
// 其余为内容规则:文件不存在 → skip(通过,存在性由 file-exists 规则保证)
|
|
73
|
+
if (!exists) return true
|
|
74
|
+
const scoped = sliceScope(content, rule.target && rule.target.scope)
|
|
75
|
+
switch (rule.kind) {
|
|
76
|
+
case 'literal-any': return (rule.data.literals || []).some(s => scoped.includes(s))
|
|
77
|
+
case 'literal-all': return (rule.data.literals || []).every(s => scoped.includes(s))
|
|
78
|
+
case 'regex': return new RegExp(rule.data.pattern, rule.data.flags || '').test(scoped)
|
|
79
|
+
case 'contains-section': return hasAllSections(scoped, rule.data.sections || [])
|
|
80
|
+
case 'no-placeholder-line': return hasNoPlaceholderLine(scoped, rule.data.patterns)
|
|
81
|
+
case 'field-present':
|
|
82
|
+
// frontmatter 字段:行首 `<field>:`(与 plan-postcheck /^goal:/m 一致,字段名为固定标识符无正则元字符)
|
|
83
|
+
return (rule.data.fields || []).every(f => new RegExp(`^${f}:`, 'm').test(scoped))
|
|
84
|
+
case 'header-field':
|
|
85
|
+
// 头部 N 字节内含 `<field>:`(对齐 scan-postcheck/complete.js author:/created_at: 检查)
|
|
86
|
+
return (rule.data.fields || []).every(f => scoped.includes(`${f}:`))
|
|
87
|
+
case 'list-non-empty':
|
|
88
|
+
// 至少一行 trim 后以 - / * / 数字. 开头(逐字对齐 validateBrainstormOutputs tasks.md 检查)
|
|
89
|
+
return scoped.split(/\r?\n/).some(l => {
|
|
90
|
+
const t = l.trim()
|
|
91
|
+
return t.startsWith('-') || t.startsWith('*') || /^\d+\./.test(t)
|
|
92
|
+
})
|
|
93
|
+
case 'min-lines': return meetsMinLines(scoped, rule.data.min || 1)
|
|
94
|
+
default: return true // 未知纯 kind,放过(不阻断),避免新 kind 上线时误杀
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function checkDirNonEmpty(rule, absPath, readDir) {
|
|
99
|
+
const { exists, files } = readDir(absPath)
|
|
100
|
+
if (!exists) return false
|
|
101
|
+
const glob = rule.data.glob || '.md'
|
|
102
|
+
const min = rule.data.min || 1
|
|
103
|
+
return files.filter(f => f.endsWith(glob)).length >= min
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============ 报错文案格式化 ============
|
|
107
|
+
|
|
108
|
+
/** failMessage 里的 ${path} 占位 → 替换为解析后的绝对路径(与旧 validator join(changeDir, file) 文案一致)。 */
|
|
109
|
+
function formatFail(failMessage, absPath) {
|
|
110
|
+
return failMessage && failMessage.includes('${path}')
|
|
111
|
+
? failMessage.replace('${path}', absPath)
|
|
112
|
+
: failMessage
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 规则条件判定:condition 不满足时规则跳过(同 enabled=false)。
|
|
117
|
+
* 用于「某规则仅在特定上下文生效」——如 brainstorm 的 proposal/requirements/tasks 仅在 scale≠small 时必需
|
|
118
|
+
* (小变更 scale=small 只产 design.md,见 BRAINSTORM_RULES)。
|
|
119
|
+
*
|
|
120
|
+
* condition schema: { ctxField: '<ctx 键名>', ne|eq: <值> }
|
|
121
|
+
* - ne: ctx[ctxField] !== ne 时条件成立(规则适用);相等则跳过
|
|
122
|
+
* - eq: ctx[ctxField] === eq 时条件成立;否则跳过
|
|
123
|
+
* fail-safe:ctx 或 ctxField 缺失 → ne 判定为「undefined !== ne 值」成立(eq 不成立),
|
|
124
|
+
* 即 scale 读不到时四件套仍全要求(保守默认,走重流程),不会因 condition 误放开必填项。
|
|
125
|
+
*/
|
|
126
|
+
function conditionHolds(condition, ctx) {
|
|
127
|
+
if (!condition) return true
|
|
128
|
+
const actual = ctx ? ctx[condition.ctxField] : undefined
|
|
129
|
+
if ('ne' in condition) return actual !== condition.ne
|
|
130
|
+
if ('eq' in condition) return actual === condition.eq
|
|
131
|
+
return true // 未知操作符,放过(不阻断),避免新操作符上线时误杀
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============ 主入口 ============
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 对某 stage 的全部纯 kind 规则跑判定。custom kind / target 基缺失 / enabled=false 的规则计入 skipped。
|
|
138
|
+
*
|
|
139
|
+
* @param {string} stage
|
|
140
|
+
* @param {{ changeDir?: string, docsRoot?: string, archiveDir?: string, specBase?: string, scale?: string }} ctx
|
|
141
|
+
* 规则 target.root 解析基目录;额外字段(如 scale)供 rule.condition 按 ctxField 判定(见 conditionHolds)
|
|
142
|
+
* @param {{ readFile?: Function, readDir?: Function }} [io] 默认原生 fs;plan-postcheck 可注入 CRLF-normalizing reader
|
|
143
|
+
* @returns {{ ok: boolean, errors: string[], warnings: string[], applied: string[], skipped: string[] }}
|
|
144
|
+
*/
|
|
145
|
+
export function evaluateRules(stage, ctx, io, opts = {}) {
|
|
146
|
+
const readFile = (io || DEFAULT_IO).readFile || defaultReadFile
|
|
147
|
+
const readDir = (io || DEFAULT_IO).readDir || defaultReadDir
|
|
148
|
+
const errors = []
|
|
149
|
+
const warnings = []
|
|
150
|
+
const applied = []
|
|
151
|
+
const skipped = []
|
|
152
|
+
|
|
153
|
+
for (const rule of getRulesFor(stage, { source: opts.source })) {
|
|
154
|
+
if (rule.enabled === false) { skipped.push(rule.id); continue }
|
|
155
|
+
if (rule.condition && !conditionHolds(rule.condition, ctx)) { skipped.push(rule.id); continue }
|
|
156
|
+
if (isCustomKind(rule.kind)) { skipped.push(rule.id); continue }
|
|
157
|
+
const absPath = resolveTargetPath(rule.target, ctx)
|
|
158
|
+
if (absPath == null) { skipped.push(rule.id); continue }
|
|
159
|
+
|
|
160
|
+
const ok = rule.kind === 'dir-non-empty'
|
|
161
|
+
? checkDirNonEmpty(rule, absPath, readDir)
|
|
162
|
+
: dispatchPure(rule, readFile(absPath))
|
|
163
|
+
|
|
164
|
+
applied.push(rule.id)
|
|
165
|
+
if (!ok) {
|
|
166
|
+
(rule.severity === 'error' ? errors : warnings).push(formatFail(rule.failMessage, absPath))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { ok: errors.length === 0, errors, warnings, applied, skipped }
|
|
171
|
+
}
|