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,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decision-distill — 决策提炼纯函数(change: 2026-08-23-adopt-harness-practices,FR-02/03/04,D-007@v1)
|
|
3
|
+
*
|
|
4
|
+
* 职责:把变更 decisions.md 中「有实现影响」的决策幂等提炼进 knowledge/decisions/<模块域>.md,
|
|
5
|
+
* 并幂等维护 knowledge/INDEX.md 的 decisions 路由行。写入责任全部在本模块——task-03(archive
|
|
6
|
+
* 接线)只调用,task-04(knowledge-match)只消费,本模块不 import 它们(也不接 DB/网络)。
|
|
7
|
+
*
|
|
8
|
+
* 入选规则(FR-02,rejected 优先):任意 type 的 status=rejected → rejected(留痕防复潮);
|
|
9
|
+
* type∈{architecture,compatibility,boundary,definition,process} 且 status∈{confirmed,accepted}
|
|
10
|
+
* → implemented;type=scope 与 status=superseded 等其余组合不入选(解析保留在 entries 里)。
|
|
11
|
+
* 域三级兜底(FR-03):条目「模块域」字段优先 → 缺失按 impacts 文本中的路径与 _module-map.yaml
|
|
12
|
+
* paths/core_files 前缀匹配(moduleIndex 由调用方注入;未注入时按 knowledgeRoot 同级
|
|
13
|
+
* docs/<项目>/modules/_module-map.yaml 尽力发现)→ 仍未中归 unmapped 域。
|
|
14
|
+
* 条目契约(FR-04,provides decisions_entry 八字段):id/status/anchor/domains/last_confirmed/
|
|
15
|
+
* rationale/reject_reason/revisit_when——implemented/rejected 条目逐字落「状态:/锚点:/最近确认:/
|
|
16
|
+
* 理由:」字段行(rejected 追加「否决理由:/复潮条件:」),domains 由条目所在文件
|
|
17
|
+
* decisions/<域>.md 编码(多域 = 条目扇出到多个域文件)。
|
|
18
|
+
* 幂等:同 ID 同版本重写该条目段落不重复追加;@vN+1 整段替换旧版本段并注 supersedes;旧版本段
|
|
19
|
+
* 散落在其他域文件时同步移除(同号全局只留最高版本;高版本已落库时迟到的低版本不降级)。
|
|
20
|
+
* needsWait:rejected 条目缺否决理由/复潮条件 → 该条目不写盘,needsWait 返回缺失描述
|
|
21
|
+
* (其余条目照常提炼;步骤层把 needsWait 转 --wait 请用户裁决)。
|
|
22
|
+
* 零输出:无 decisions.md 或无入选条目 → skipped 带原因,不写任何文件、不动 INDEX。
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
25
|
+
import { dirname, join } from 'node:path'
|
|
26
|
+
|
|
27
|
+
/** FR-02:有实现影响的五类 type(scope 与未知 type 的 confirmed/accepted 不入选) */
|
|
28
|
+
const IMPLEMENTED_TYPES = new Set(['architecture', 'compatibility', 'boundary', 'definition', 'process'])
|
|
29
|
+
/** FR-02:implemented 入选的 status(superseded 等其余状态不入选) */
|
|
30
|
+
const IMPLEMENTED_STATUSES = new Set(['confirmed', 'accepted'])
|
|
31
|
+
/** 三级兜底终点域(仍不中归此;域文件由本模块自管) */
|
|
32
|
+
const UNMAPPED_DOMAIN = 'unmapped'
|
|
33
|
+
/** 旧格式容错占位值(FR-01:缺锚点/缺 headHash 不阻断) */
|
|
34
|
+
const NOT_RECORDED = '未记录'
|
|
35
|
+
|
|
36
|
+
/** 多行/带空白值压成单行字段值(条目行是单行契约) */
|
|
37
|
+
function oneLine(s) {
|
|
38
|
+
return String(s ?? '').replace(/\s*\r?\n\s*/g, ' ').trim()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 取列表字段首 token:`superseded(被 D-005@v2 取代…)` → `superseded` */
|
|
42
|
+
function firstToken(value) {
|
|
43
|
+
return value.split(/[\s((]/)[0] || ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 模块域值解析:`[a, b]` / `a、b` / `a b` → ['a','b'] */
|
|
47
|
+
function parseListValue(v) {
|
|
48
|
+
return v.replace(/^\[/, '').replace(/\]$/, '')
|
|
49
|
+
.split(/[,,、\s]+/)
|
|
50
|
+
.map(s => s.trim())
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 域名净化为安全文件名段(防路径注入;空值归 unmapped) */
|
|
55
|
+
function sanitizeDomain(d) {
|
|
56
|
+
const s = String(d || '').trim().replace(/\.md$/i, '').replace(/[^A-Za-z0-9_\-\u4e00-\u9fff]/g, '-')
|
|
57
|
+
return s || UNMAPPED_DOMAIN
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** FR-02 入选裁决:'implemented' | 'rejected' | null(null = 解析保留但不入选) */
|
|
61
|
+
function dispositionOf(entry) {
|
|
62
|
+
const t = (entry.type || '').toLowerCase()
|
|
63
|
+
const s = (entry.status || '').toLowerCase()
|
|
64
|
+
if (s === 'rejected') return 'rejected' // 任意 type(含 scope)的 rejected 都留痕
|
|
65
|
+
if (IMPLEMENTED_TYPES.has(t) && IMPLEMENTED_STATUSES.has(s)) return 'implemented'
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** decisions.md 列表字段标签 → entry 字段(兼容中英标签与中英冒号;其余字段仅留在 raw) */
|
|
70
|
+
function applyField(entry, label, value) {
|
|
71
|
+
switch (label) {
|
|
72
|
+
case 'type': entry.type = firstToken(value); break
|
|
73
|
+
case 'status': entry.status = firstToken(value); break
|
|
74
|
+
case 'question': case '问题': entry.question = value; break
|
|
75
|
+
case 'answer': case '答案': entry.answer = value; break
|
|
76
|
+
case '锚点': case 'anchor': entry.anchor = value; break
|
|
77
|
+
case '模块域': case '模块': case 'domains': case 'domain': entry.domains = parseListValue(value); break
|
|
78
|
+
case '否决理由': case 'reject_reason': case 'rejectreason': entry.rejectReason = value; break
|
|
79
|
+
case '复潮条件': case 'revisit_when': case 'revisitwhen': entry.revisitWhen = value; break
|
|
80
|
+
case 'supersedes': entry.supersedes = value; break
|
|
81
|
+
case 'impacts': case '影响': entry.impacts = value; break
|
|
82
|
+
case 'normalized_requirement': entry.normalizedRequirement = value; break
|
|
83
|
+
default: break
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 纯函数:解析变更 decisions.md 的 D-xxx@vN 决策条目(FR-01 四字段全可选容旧格式)。
|
|
89
|
+
* @param {string} changeDir 变更目录(含 decisions.md)
|
|
90
|
+
* @returns {{ entries: Array<{
|
|
91
|
+
* id: string, number: string, version: number, title: string,
|
|
92
|
+
* type?: string, status?: string, question?: string, answer?: string,
|
|
93
|
+
* anchor?: string, domains?: string[], rejectReason?: string, revisitWhen?: string,
|
|
94
|
+
* supersedes?: string, impacts?: string, normalizedRequirement?: string,
|
|
95
|
+
* selected: 'implemented'|'rejected'|null, raw: string }>, missing: boolean }}
|
|
96
|
+
* missing=true = decisions.md 不存在;selected = FR-02 入选裁决,null 表示解析保留但不入选。
|
|
97
|
+
*/
|
|
98
|
+
export function parseDecisions(changeDir) {
|
|
99
|
+
const filePath = join(changeDir, 'decisions.md')
|
|
100
|
+
if (!existsSync(filePath)) return { entries: [], missing: true }
|
|
101
|
+
let content
|
|
102
|
+
try { content = readFileSync(filePath, 'utf8') } catch { return { entries: [], missing: true } }
|
|
103
|
+
|
|
104
|
+
const entries = []
|
|
105
|
+
let cur = null
|
|
106
|
+
const flush = () => {
|
|
107
|
+
if (!cur) return
|
|
108
|
+
cur.selected = dispositionOf(cur)
|
|
109
|
+
cur.raw = cur.rawLines.join('\n')
|
|
110
|
+
delete cur.rawLines
|
|
111
|
+
entries.push(cur)
|
|
112
|
+
}
|
|
113
|
+
for (const line of content.replace(/\r\n/g, '\n').split('\n')) {
|
|
114
|
+
const h = line.match(/^##\s+(D-\d+)(?:@v(\d+))?\s*(.*)$/)
|
|
115
|
+
if (h) {
|
|
116
|
+
flush()
|
|
117
|
+
const version = h[2] ? parseInt(h[2], 10) : 1
|
|
118
|
+
cur = { id: `${h[1]}@v${version}`, number: h[1], version, title: h[3].trim(), rawLines: [line] }
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
if (!cur) continue
|
|
122
|
+
cur.rawLines.push(line)
|
|
123
|
+
const f = line.match(/^-\s+([^\s::]+)\s*[::]\s*(.*)$/)
|
|
124
|
+
if (f) applyField(cur, f[1].toLowerCase(), f[2].trim())
|
|
125
|
+
}
|
|
126
|
+
flush()
|
|
127
|
+
return { entries, missing: false }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// 域三级兜底(FR-03)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/** moduleIndex 形状归一:parseModuleMapSimple 平铺 {id:{paths}} 或包裹 {modules:{id:{paths}}} */
|
|
135
|
+
function normalizeModules(moduleIndex) {
|
|
136
|
+
if (!moduleIndex) return {}
|
|
137
|
+
return moduleIndex.modules ? moduleIndex.modules : moduleIndex
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** _module-map.yaml 子集解析(仅模块 id → paths/core_files;不复用 modules.js 以保零依赖纯函数) */
|
|
141
|
+
function parseModulePathsSubset(content) {
|
|
142
|
+
const modules = {}
|
|
143
|
+
let cur = null
|
|
144
|
+
let key = null
|
|
145
|
+
for (const line of content.replace(/\r\n/g, '\n').split('\n')) {
|
|
146
|
+
const mm = line.match(/^ ([a-zA-Z0-9_-]+):$/)
|
|
147
|
+
if (mm) { cur = mm[1]; modules[cur] = { paths: [] }; key = null; continue }
|
|
148
|
+
if (!cur) continue
|
|
149
|
+
const inline = line.match(/^ (paths|core_files): \[(.*)\]$/)
|
|
150
|
+
if (inline) {
|
|
151
|
+
modules[cur][inline[1]] = inline[2].split(',').map(s => s.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean)
|
|
152
|
+
key = null; continue
|
|
153
|
+
}
|
|
154
|
+
const block = line.match(/^ (paths|core_files):$/)
|
|
155
|
+
if (block) { key = block[1]; modules[cur][key] = modules[cur][key] || []; continue }
|
|
156
|
+
const item = line.match(/^ - (.+)$/)
|
|
157
|
+
if (item && key) modules[cur][key].push(item[1].trim().replace(/^['"]|['"]$/g, ''))
|
|
158
|
+
}
|
|
159
|
+
return modules
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 未注入 moduleIndex 时的尽力发现:knowledgeRoot 同级 docs/<项目>/modules/_module-map.yaml。
|
|
163
|
+
* 多子项目仓(如本仓 sillyspec+dashboard)各项目有自己的 map——dogfood 实证(2026-08-24):
|
|
164
|
+
* 只取首个命中会把主项目条目全部误落 unmapped(dashboard 的 map 按字母序先命中)。
|
|
165
|
+
* 改为合并全部项目的 map(同名模块合并 paths/core_files 去重),失败归 null → 全部 unmapped。 */
|
|
166
|
+
function discoverModuleIndex(knowledgeRoot) {
|
|
167
|
+
try {
|
|
168
|
+
const docsDir = join(dirname(knowledgeRoot), 'docs')
|
|
169
|
+
if (!existsSync(docsDir)) return null
|
|
170
|
+
const merged = {}
|
|
171
|
+
for (const d of readdirSync(docsDir, { withFileTypes: true })) {
|
|
172
|
+
if (!d.isDirectory()) continue
|
|
173
|
+
const mapPath = join(docsDir, d.name, 'modules', '_module-map.yaml')
|
|
174
|
+
if (!existsSync(mapPath)) continue
|
|
175
|
+
const sub = parseModulePathsSubset(readFileSync(mapPath, 'utf8'))
|
|
176
|
+
for (const [id, m] of Object.entries(sub)) {
|
|
177
|
+
if (!merged[id]) merged[id] = { paths: m.paths || [], core_files: m.core_files || [] }
|
|
178
|
+
else {
|
|
179
|
+
merged[id].paths = [...new Set([...(merged[id].paths || []), ...(m.paths || [])])]
|
|
180
|
+
merged[id].core_files = [...new Set([...(merged[id].core_files || []), ...(m.core_files || [])])]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return Object.keys(merged).length > 0 ? merged : null
|
|
185
|
+
} catch { /* 尽力而为,失败归 null */ }
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** impacts 文本中的路径 token:含 / 或带扩展名的词(`src/stages/archive.js、quicklog.js:486` → 前者 + `quicklog.js`) */
|
|
190
|
+
function extractPathTokens(text) {
|
|
191
|
+
if (!text) return []
|
|
192
|
+
const tokens = String(text).match(/[A-Za-z0-9_\-.\\/]+/g) || []
|
|
193
|
+
return tokens
|
|
194
|
+
.map(t => t.replace(/\\/g, '/').replace(/\/+$/, ''))
|
|
195
|
+
.filter(t => t && (t.includes('/') || /\.[A-Za-z0-9]+$/.test(t)) && t !== '.' && t !== '..')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** impacts 路径 token × module paths 前缀匹配(全路径优先,基名兜底——卡内裸文件名引用先例) */
|
|
199
|
+
function matchImpactsToModules(impacts, moduleIndex) {
|
|
200
|
+
const flat = normalizeModules(moduleIndex)
|
|
201
|
+
const modPaths = []
|
|
202
|
+
for (const [id, m] of Object.entries(flat)) {
|
|
203
|
+
for (const p of [...(m.paths || []), ...(m.core_files || [])]) {
|
|
204
|
+
const ps = String(p).replace(/\\/g, '/').replace(/\/+$/, '')
|
|
205
|
+
if (ps) modPaths.push({ id, p: ps })
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const tokens = extractPathTokens(impacts)
|
|
209
|
+
const hits = []
|
|
210
|
+
for (const t of tokens) {
|
|
211
|
+
for (const { id, p } of modPaths) {
|
|
212
|
+
if (t === p || t.startsWith(p + '/')) { if (!hits.includes(id)) hits.push(id); break }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (hits.length === 0) {
|
|
216
|
+
for (const t of tokens) {
|
|
217
|
+
const base = t.slice(t.lastIndexOf('/') + 1)
|
|
218
|
+
for (const { id, p } of modPaths) {
|
|
219
|
+
if (p.slice(p.lastIndexOf('/') + 1) === base && !hits.includes(id)) hits.push(id)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return hits
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 三级兜底:模块域字段 → impacts × moduleIndex 前缀 → unmapped */
|
|
227
|
+
function resolveDomains(entry, moduleIndex) {
|
|
228
|
+
if (entry.domains && entry.domains.length > 0) return entry.domains.map(sanitizeDomain)
|
|
229
|
+
const matched = moduleIndex ? matchImpactsToModules(entry.impacts, moduleIndex) : []
|
|
230
|
+
if (matched.length > 0) return matched.map(sanitizeDomain)
|
|
231
|
+
return [UNMAPPED_DOMAIN]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// knowledge/decisions/<域>.md 条目读写(FR-04 契约条目,幂等)
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
/** 新域文件头(preamble) */
|
|
239
|
+
function newDomainPreamble(domain) {
|
|
240
|
+
return [
|
|
241
|
+
`# 决策知识 — ${domain}`,
|
|
242
|
+
'',
|
|
243
|
+
'> decision-distill 从变更 decisions.md 幂等提炼(「最近确认」= 归档时 HEAD)。条目字段行为 docs-check 机械解析契约,勿手改。',
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** 单条目段落渲染(design 接口定义的条目格式,逐字对齐:状态/锚点/最近确认/理由 + rejected 专属两行) */
|
|
248
|
+
function renderBlockLines(entry, headHash, supersedesNote) {
|
|
249
|
+
const lines = [`## ${entry.id} ${oneLine(entry.title)}`.replace(/\s+$/, '')]
|
|
250
|
+
lines.push(`状态:${entry.selected}`)
|
|
251
|
+
lines.push(`锚点:${oneLine(entry.anchor) || NOT_RECORDED}`)
|
|
252
|
+
lines.push(`最近确认:${oneLine(headHash) || NOT_RECORDED}`)
|
|
253
|
+
lines.push(`理由:${oneLine(entry.answer || entry.normalizedRequirement || entry.question)}`)
|
|
254
|
+
if (supersedesNote) lines.push(`supersedes:${oneLine(supersedesNote)}`)
|
|
255
|
+
if (entry.selected === 'rejected') {
|
|
256
|
+
lines.push(`否决理由:${oneLine(entry.rejectReason)}`)
|
|
257
|
+
lines.push(`复潮条件:${oneLine(entry.revisitWhen)}`)
|
|
258
|
+
}
|
|
259
|
+
return lines
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 知识文件切段:preamble 行 + D-xxx@vN 条目段(段边界 = 下一 `## D-` 头) */
|
|
263
|
+
function splitKnowledgeSections(content) {
|
|
264
|
+
const sections = []
|
|
265
|
+
const preamble = []
|
|
266
|
+
let cur = null
|
|
267
|
+
for (const line of content.replace(/\r\n/g, '\n').split('\n')) {
|
|
268
|
+
const m = line.match(/^## (D-\d+)@v(\d+)\s*(.*)$/)
|
|
269
|
+
if (m) {
|
|
270
|
+
if (cur) sections.push(cur)
|
|
271
|
+
cur = { number: m[1], version: parseInt(m[2], 10), title: m[3].trim(), lines: [line] }
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
if (cur) cur.lines.push(line)
|
|
275
|
+
else preamble.push(line)
|
|
276
|
+
}
|
|
277
|
+
if (cur) sections.push(cur)
|
|
278
|
+
for (const s of sections) s.id = `${s.number}@v${s.version}`
|
|
279
|
+
return { preamble, sections }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 段落重组落盘文本(幂等归一:段间恰好一个空行 + 文件单一尾换行) */
|
|
283
|
+
function joinKnowledgeFile(preamble, sections) {
|
|
284
|
+
const parts = []
|
|
285
|
+
const pre = preamble.join('\n').replace(/\s+$/, '')
|
|
286
|
+
if (pre) parts.push(pre)
|
|
287
|
+
for (const s of sections) {
|
|
288
|
+
const block = s.lines.join('\n').replace(/\s+$/, '')
|
|
289
|
+
if (block) parts.push(block)
|
|
290
|
+
}
|
|
291
|
+
return parts.join('\n\n') + '\n'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 单条目写入一个域文件的段落集(就地修改 sections)。
|
|
296
|
+
* @returns {{ action: 'append'|'update'|'supersede' }|null} null = 高版本已落库,迟到低版本不降级。
|
|
297
|
+
*/
|
|
298
|
+
function applyEntryToSections(sections, entry, headHash) {
|
|
299
|
+
const sameNumber = sections.filter(s => s.number === entry.number)
|
|
300
|
+
if (sameNumber.some(s => s.version > entry.version)) return null
|
|
301
|
+
let action
|
|
302
|
+
let supersedesNote = oneLine(entry.supersedes) || null
|
|
303
|
+
if (sameNumber.length === 0) {
|
|
304
|
+
sections.push({ number: entry.number, version: entry.version, id: entry.id, title: entry.title, lines: renderBlockLines(entry, headHash, supersedesNote) })
|
|
305
|
+
action = 'append'
|
|
306
|
+
} else {
|
|
307
|
+
const same = sameNumber.find(s => s.version === entry.version)
|
|
308
|
+
const slot = same || sameNumber[0]
|
|
309
|
+
if (!same && !supersedesNote) supersedesNote = `${slot.number}@v${slot.version}`
|
|
310
|
+
slot.version = entry.version
|
|
311
|
+
slot.title = entry.title
|
|
312
|
+
slot.lines = renderBlockLines(entry, headHash, supersedesNote)
|
|
313
|
+
// 同号旧版本段(< 新版本)就地清除——同文件内同号只留一个段
|
|
314
|
+
for (let i = sections.length - 1; i >= 0; i--) {
|
|
315
|
+
if (sections[i].number === entry.number && sections[i] !== slot) sections.splice(i, 1)
|
|
316
|
+
}
|
|
317
|
+
action = same ? 'update' : 'supersede'
|
|
318
|
+
}
|
|
319
|
+
return { action }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// INDEX.md decisions 路由行幂等维护(FR-05:写入责任在本模块;只动 ## Decisions 段内指向 decisions/ 的行)
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
/** INDEX 路由行格式与 knowledge-match 解析口径一致:`- <域>|decision|决策 → [decisions/<域>.md](decisions/<域>.md)` */
|
|
327
|
+
function routingLine(domain) {
|
|
328
|
+
return `- ${domain}|decision|决策 → [decisions/${domain}.md](decisions/${domain}.md)`
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const ROUTING_TARGET_RE = /^-\s+.*→\s*\[[^\]]*\]\(decisions\/([^)#]+?)(?:\.md)?(?:#[^)]*)?\)\s*$/
|
|
332
|
+
|
|
333
|
+
/** 域文件实况 ↔ ## Decisions 段路由行对账:缺行补、失效行删、重复行去重;不动其他类别行 */
|
|
334
|
+
function syncIndexRoutingLines(knowledgeRoot) {
|
|
335
|
+
const decisionsDir = join(knowledgeRoot, 'decisions')
|
|
336
|
+
let domains
|
|
337
|
+
try {
|
|
338
|
+
domains = readdirSync(decisionsDir).filter(f => f.endsWith('.md')).map(f => f.replace(/\.md$/, ''))
|
|
339
|
+
} catch { return false }
|
|
340
|
+
if (domains.length === 0) return false
|
|
341
|
+
|
|
342
|
+
const indexPath = join(knowledgeRoot, 'INDEX.md')
|
|
343
|
+
let original = null
|
|
344
|
+
if (existsSync(indexPath)) {
|
|
345
|
+
try { original = readFileSync(indexPath, 'utf8') } catch { return false }
|
|
346
|
+
}
|
|
347
|
+
const lines = original ? original.replace(/\r\n/g, '\n').split('\n') : ['# Knowledge Index', '']
|
|
348
|
+
|
|
349
|
+
// 定位 ## Decisions 段边界
|
|
350
|
+
let secStart = -1
|
|
351
|
+
let secEnd = lines.length
|
|
352
|
+
for (let i = 0; i < lines.length; i++) {
|
|
353
|
+
const h = lines[i].match(/^##\s+(.+?)\s*$/)
|
|
354
|
+
if (!h) continue
|
|
355
|
+
if (secStart === -1) {
|
|
356
|
+
if (h[1] === 'Decisions') secStart = i + 1
|
|
357
|
+
} else { secEnd = i; break }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const desired = domains.map(routingLine)
|
|
361
|
+
if (secStart === -1) {
|
|
362
|
+
// 无段则追加(保留原文件其余内容)
|
|
363
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop()
|
|
364
|
+
lines.push('', '## Decisions', ...desired)
|
|
365
|
+
} else {
|
|
366
|
+
const body = lines.slice(secStart, secEnd)
|
|
367
|
+
const kept = []
|
|
368
|
+
const seen = new Set()
|
|
369
|
+
const domainSet = new Set(domains)
|
|
370
|
+
for (const l of body) {
|
|
371
|
+
const m = l.match(ROUTING_TARGET_RE)
|
|
372
|
+
if (!m) { kept.push(l); continue } // 非路由行(注释/空行)原样保留
|
|
373
|
+
// 目标域文件仍存在的路由行原样保留(关键词可能被人工扩充);失效行(文件已不存在)与重复行删
|
|
374
|
+
if (domainSet.has(m[1]) && !seen.has(m[1])) { seen.add(m[1]); kept.push(l) }
|
|
375
|
+
}
|
|
376
|
+
// 补行插在段尾空行之前(段尾空行是与下一 `## ` 的分隔,新行追加其后会出现行间空洞)
|
|
377
|
+
const tail = []
|
|
378
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === '') tail.unshift(kept.pop())
|
|
379
|
+
for (const d of desired) {
|
|
380
|
+
const m = d.match(ROUTING_TARGET_RE)
|
|
381
|
+
if (m && !seen.has(m[1])) kept.push(d) // 缺行补
|
|
382
|
+
}
|
|
383
|
+
kept.push(...tail)
|
|
384
|
+
const sepNeeded = secEnd < lines.length && kept.length > 0 && kept[kept.length - 1].trim() !== ''
|
|
385
|
+
lines.splice(secStart, secEnd - secStart, ...kept, ...(sepNeeded ? [''] : []))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let next = lines.join('\n').replace(/\n{3,}$/, '\n')
|
|
389
|
+
if (!next.endsWith('\n')) next += '\n'
|
|
390
|
+
if (next === original) return false
|
|
391
|
+
writeFileSync(indexPath, next)
|
|
392
|
+
return true
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
// IO 入口:提炼(FR-03)
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 纯函数入口:把 changeDir/decisions.md 的入选决策幂等提炼进 knowledgeRoot/decisions/<域>.md,
|
|
401
|
+
* 并幂等维护 knowledgeRoot/INDEX.md 的 decisions 路由行(仅在有实际写入时)。
|
|
402
|
+
* @param {string} changeDir 变更目录(含 decisions.md)
|
|
403
|
+
* @param {string} knowledgeRoot knowledge 目录(如 <specBase>/knowledge)
|
|
404
|
+
* @param {string} headHash 归档时 HEAD hash(落「最近确认」;空值容错为 未记录)
|
|
405
|
+
* @param {object|null} [moduleIndex] 域兜底用模块索引({modules:{id:{paths,core_files}}} 或平铺形;
|
|
406
|
+
* 缺省时按 knowledgeRoot 同级 docs/<项目>/modules/_module-map.yaml 尽力发现)
|
|
407
|
+
* @returns {{ written: Array<{file: string, id: string, action: 'append'|'update'|'supersede'}>,
|
|
408
|
+
* skipped: string|null, needsWait: string|null }}
|
|
409
|
+
* file 为 knowledgeRoot 相对 POSIX 路径(decisions/<域>.md);跨文件旧版本移除记
|
|
410
|
+
* { file: 所在文件, id: 被替换旧版本, action: 'supersede' }。
|
|
411
|
+
* skipped 非空 = 无 decisions.md / 无入选条目(零输出);needsWait 非空 = 有 rejected 条目
|
|
412
|
+
* 缺否决理由/复潮条件被拦下(该条目未写盘,其余条目照常提炼)。
|
|
413
|
+
*/
|
|
414
|
+
export function distillIntoKnowledge(changeDir, knowledgeRoot, headHash, moduleIndex = null) {
|
|
415
|
+
const parsed = parseDecisions(changeDir)
|
|
416
|
+
if (parsed.missing) {
|
|
417
|
+
return { written: [], skipped: `decisions.md 不存在(${join(changeDir, 'decisions.md')}),零输出`, needsWait: null }
|
|
418
|
+
}
|
|
419
|
+
const selected = parsed.entries.filter(e => e.selected)
|
|
420
|
+
if (selected.length === 0) {
|
|
421
|
+
return { written: [], skipped: `无入选条目(解析 ${parsed.entries.length} 条,0 条入选:scope/非五类 type/非 confirmed|accepted|rejected 状态均不入选),零输出`, needsWait: null }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// needsWait:rejected 缺否决理由/复潮条件 → 该条目不写盘(其余照常)
|
|
425
|
+
const waitParts = []
|
|
426
|
+
const distillable = []
|
|
427
|
+
for (const e of selected) {
|
|
428
|
+
if (e.selected === 'rejected' && (!e.rejectReason || !e.revisitWhen)) {
|
|
429
|
+
const missing = []
|
|
430
|
+
if (!e.rejectReason) missing.push('否决理由')
|
|
431
|
+
if (!e.revisitWhen) missing.push('复潮条件')
|
|
432
|
+
waitParts.push(`${e.id} 缺${missing.join('、')}`)
|
|
433
|
+
} else {
|
|
434
|
+
distillable.push(e)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const needsWait = waitParts.length > 0
|
|
438
|
+
? `${waitParts.join(';')}——rejected 条目必填,补齐后重跑提炼`
|
|
439
|
+
: null
|
|
440
|
+
if (distillable.length === 0) return { written: [], skipped: null, needsWait }
|
|
441
|
+
|
|
442
|
+
// 同批同号只提炼最高可写版本(decisions.md 里 v1+v2 并存时 v1 不先落后清——落了也会被 v2
|
|
443
|
+
// 整段替换,跳过避免空转写与日志抖动;v2 被 needsWait 拦下时 v1 仍是最高可写版,照常提炼)
|
|
444
|
+
const highestByNumber = new Map()
|
|
445
|
+
for (const e of distillable) {
|
|
446
|
+
const prev = highestByNumber.get(e.number)
|
|
447
|
+
if (!prev || e.version > prev.version) highestByNumber.set(e.number, e)
|
|
448
|
+
}
|
|
449
|
+
const batch = []
|
|
450
|
+
const taken = new Set()
|
|
451
|
+
for (const e of distillable) {
|
|
452
|
+
const keep = highestByNumber.get(e.number)
|
|
453
|
+
if (keep && !taken.has(e.number)) { taken.add(e.number); batch.push(keep) }
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const decisionsDir = join(knowledgeRoot, 'decisions')
|
|
457
|
+
mkdirSync(decisionsDir, { recursive: true })
|
|
458
|
+
const idx = moduleIndex || discoverModuleIndex(knowledgeRoot)
|
|
459
|
+
|
|
460
|
+
// 域文件 store:域 → { preamble, sections, dirty }(按需加载,批量写盘)
|
|
461
|
+
const store = new Map()
|
|
462
|
+
const loadDomain = (domain) => {
|
|
463
|
+
let st = store.get(domain)
|
|
464
|
+
if (st) return st
|
|
465
|
+
const p = join(decisionsDir, `${domain}.md`)
|
|
466
|
+
if (existsSync(p)) {
|
|
467
|
+
let parsedFile = { preamble: [], sections: [] }
|
|
468
|
+
try { parsedFile = splitKnowledgeSections(readFileSync(p, 'utf8')) } catch { /* 按新文件处理 */ }
|
|
469
|
+
st = { ...parsedFile, dirty: false }
|
|
470
|
+
} else {
|
|
471
|
+
st = { preamble: newDomainPreamble(domain), sections: [], dirty: false }
|
|
472
|
+
}
|
|
473
|
+
store.set(domain, st)
|
|
474
|
+
return st
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const written = []
|
|
478
|
+
const targetSets = new Map() // entry → Set(域)(跨文件清理时排除目标文件)
|
|
479
|
+
for (const entry of batch) {
|
|
480
|
+
const domains = resolveDomains(entry, idx)
|
|
481
|
+
targetSets.set(entry, new Set(domains))
|
|
482
|
+
for (const d of domains) {
|
|
483
|
+
const st = loadDomain(d)
|
|
484
|
+
const res = applyEntryToSections(st.sections, entry, headHash)
|
|
485
|
+
if (res) {
|
|
486
|
+
st.dirty = true
|
|
487
|
+
written.push({ file: `decisions/${d}.md`, id: entry.id, action: res.action })
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 跨文件清理:同号 ≤ 新版本的旧段散落其他域文件时移除(同号全局只留最高版本/目标文件)
|
|
493
|
+
let onDisk = []
|
|
494
|
+
try { onDisk = readdirSync(decisionsDir).filter(f => f.endsWith('.md')).map(f => f.replace(/\.md$/, '')) } catch { /* 刚创建,走 store */ }
|
|
495
|
+
const allDomains = [...new Set([...onDisk, ...store.keys()])]
|
|
496
|
+
for (const entry of batch) {
|
|
497
|
+
const targets = targetSets.get(entry)
|
|
498
|
+
for (const d of allDomains) {
|
|
499
|
+
if (targets.has(d)) continue
|
|
500
|
+
const st = loadDomain(d)
|
|
501
|
+
const removed = st.sections.filter(s => s.number === entry.number && s.version <= entry.version)
|
|
502
|
+
if (removed.length === 0) continue
|
|
503
|
+
st.sections = st.sections.filter(s => !removed.includes(s))
|
|
504
|
+
st.dirty = true
|
|
505
|
+
for (const r of removed) written.push({ file: `decisions/${d}.md`, id: r.id, action: 'supersede' })
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
for (const [domain, st] of store) {
|
|
510
|
+
if (st.dirty) writeFileSync(join(decisionsDir, `${domain}.md`), joinKnowledgeFile(st.preamble, st.sections))
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// INDEX 路由行对账(仅在实际有条目写入时;零输出场景不动 INDEX)
|
|
514
|
+
if (written.length > 0) syncIndexRoutingLines(knowledgeRoot)
|
|
515
|
+
|
|
516
|
+
return { written, skipped: null, needsWait }
|
|
517
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local 后端派发指令模板(task-dispatcher 抽象层)— task-03
|
|
3
|
+
*
|
|
4
|
+
* 定位(D-007):dispatcher **不是 JS 执行体**。本机 Agent tool(Claude Code Task 工具)
|
|
5
|
+
* 只有 agent 能调,CLI(Node)进程调不了。所以本文件 = **派发指令模板生成器**:
|
|
6
|
+
* 生成注入 execute prompt 的「派发指令文本」,告诉 agent 如何用本机 Agent tool 启动
|
|
7
|
+
* 子代理;实际 Agent tool 调用由 agent 执行,本模块不调任何 tool。
|
|
8
|
+
*
|
|
9
|
+
* Local 后端 = **默认与降级路径**(D-005),保留现有 buildWavePrompt(src/stages/execute.js)
|
|
10
|
+
* 的本机 Agent tool 派发行为:每任务一个独立子代理、workdir 强制 = worktreePath、
|
|
11
|
+
* worker 终态后 SillySpec 对 worktree 工作区 git diff 写 review.json(复用既有
|
|
12
|
+
* task-review 契约)。SillyHub MCP 不可用 / 未配置 / 路径 A 未落地时全程走此路径,零回归。
|
|
13
|
+
*
|
|
14
|
+
* Local 后端**忽略** contract 中的 modelHint / agentProfileHint:本机 Agent tool 不做
|
|
15
|
+
* 异构模型 / agent 分配,这两字段仅 SillyHub 后端消费。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Local 后端结果回收约定(worker 终态 → review.json)。
|
|
20
|
+
*
|
|
21
|
+
* 抽成独立常量供 strategy.js 在拼接「兜底 / 降级 / SillyHub 回收」段时复用,避免 Local
|
|
22
|
+
* 与 SillyHub 两份回收文案重复维护(R-07:回收统一走 review.json,屏蔽后端差异)。
|
|
23
|
+
*
|
|
24
|
+
* 含 `<runId>` 占位符,使用方按实际 contract.runId 替换(见 renderLocalInstruction)。
|
|
25
|
+
*/
|
|
26
|
+
export const LOCAL_RECYCLE_RULE = `### 回收约定(worker 终态 → review.json)
|
|
27
|
+
|
|
28
|
+
worker 子代理结束后,**回收不依赖 worker 自报结果,也不依赖 worker git commit**:
|
|
29
|
+
SillySpec 主体(你,调度 agent)对 worktree **工作区**做 git diff,按既有 task-review
|
|
30
|
+
契约写 review.json。
|
|
31
|
+
|
|
32
|
+
1. 读取该 task 在 worktree 工作区的 git diff(task 开始 → 完成的变更)
|
|
33
|
+
2. 对照 plan.md 与 tasks/task-XX.md 检查实现(不信任 implementer 自报,只看当前 task 的 diff)
|
|
34
|
+
3. 写入 review.json:\`{SPEC_ROOT}/.runtime/execute-runs/<runId>/tasks/task-XX/review.json\`({SPEC_ROOT} 由 CLI 渲染时替换为主仓绝对路径——调度者 cwd 无论在哪都写主仓 .runtime,不写 worktree 副本)
|
|
35
|
+
4. review.json 写入成功后,该 task 的派发回收完成
|
|
36
|
+
|
|
37
|
+
review.json 复用既有 task-review 契约(必填字段:schemaVersion / task / base / head /
|
|
38
|
+
changedFiles / specVerdict / qualityVerdict / reviewerNotes / requiredEvidence),不发明
|
|
39
|
+
新契约。回收屏蔽后端差异:无论 Local 还是 SillyHub worker,SillySpec 都自己 diff worktree
|
|
40
|
+
工作区写 review.json。`
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} DispatchContract
|
|
44
|
+
* @property {string} brief - 任务目标 + 蓝图路径(注入子代理 prompt)
|
|
45
|
+
* @property {string} worktreePath - SillySpec worktree 绝对路径(子代理 cwd)
|
|
46
|
+
* @property {string} branch - worktree 当前分支
|
|
47
|
+
* @property {string[]} allowedPaths - task 边界(子代理 prompt 里声明,SillySpec 侧 assess/apply 校验)
|
|
48
|
+
* @property {boolean} readOnly - execute 写模式=false;true=只读 QA(仅读/diff/写 review.json)
|
|
49
|
+
* @property {string} [modelHint] - tasks.md [model:xxx](Local 忽略,仅 SillyHub 消费)
|
|
50
|
+
* @property {string} [agentProfileHint] - agent profile(Local 忽略,仅 SillyHub 消费)
|
|
51
|
+
* @property {string} runId - SillySpec execute-run id(关联 review.json 路径)
|
|
52
|
+
* @property {string} [missionId] - Wave 级 mission id(D-008,SillyHub 专属,Local 忽略)
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 渲染 Local 后端派发指令文本。
|
|
57
|
+
*
|
|
58
|
+
* 纯模板生成器:不校验 contract 完整性、不调任何 tool,只把 contract 字段拼成可注入
|
|
59
|
+
* execute prompt 的中文指令字符串。contract 缺字段时回退到醒目占位符(`<未提供 ...>`),
|
|
60
|
+
* 让 agent 在 prompt 里一眼看到派发前必须补齐的参数,而非静默产出错位指令。
|
|
61
|
+
*
|
|
62
|
+
* @param {DispatchContract} [contract] - 派发契约(见 design.md DispatchContract 接口定义)
|
|
63
|
+
* @returns {string} 注入 execute prompt 的 Local 后端派发指令文本
|
|
64
|
+
*/
|
|
65
|
+
export function renderLocalInstruction(contract) {
|
|
66
|
+
const c = contract || {}
|
|
67
|
+
const worktreePath = c.worktreePath
|
|
68
|
+
const branch = c.branch
|
|
69
|
+
const allowedPaths = Array.isArray(c.allowedPaths) ? c.allowedPaths : null
|
|
70
|
+
const readOnly = !!c.readOnly
|
|
71
|
+
const brief = c.brief
|
|
72
|
+
const runId = c.runId
|
|
73
|
+
|
|
74
|
+
// 回收约定:替换 <runId> 占位符;runId 缺失保留占位符让 agent 警觉
|
|
75
|
+
const recycleRule = LOCAL_RECYCLE_RULE.replaceAll(
|
|
76
|
+
'<runId>',
|
|
77
|
+
runId ? String(runId) : '<未提供 runId>'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const workdirDisplay = worktreePath
|
|
81
|
+
? String(worktreePath)
|
|
82
|
+
: '<未提供 contract.worktreePath — 派发前必须补齐,否则破坏 worktree 隔离>'
|
|
83
|
+
|
|
84
|
+
const branchLine = branch
|
|
85
|
+
? `- worktree 当前分支:\`${branch}\``
|
|
86
|
+
: '- worktree 当前分支:未提供(沿用 worktree 检出时的既有分支)'
|
|
87
|
+
|
|
88
|
+
const allowedLine = allowedPaths && allowedPaths.length
|
|
89
|
+
? `- 本次任务边界(allowedPaths):${allowedPaths.map(p => `\`${p}\``).join(' / ')}`
|
|
90
|
+
: '- 本次任务边界(allowedPaths):未提供,按蓝图 tasks/task-XX.md 的 allowed_paths 为准'
|
|
91
|
+
|
|
92
|
+
const briefLine = brief
|
|
93
|
+
? String(brief)
|
|
94
|
+
: '<未提供 contract.brief — 派发前必须补齐任务目标与蓝图路径>'
|
|
95
|
+
|
|
96
|
+
const modeLine = readOnly
|
|
97
|
+
? '5. **只读模式(readOnly=true)**:本子代理只读 / 审查 / 写 review.json,**不改源码**'
|
|
98
|
+
: '5. **写模式(readOnly=false)**:按蓝图实现源码(TDD:先读后写 → 写测试 → 写实现)'
|
|
99
|
+
|
|
100
|
+
return `### 派发后端:Local(本机 Agent tool)— 默认与降级路径
|
|
101
|
+
|
|
102
|
+
本指令用**本机 Agent tool**(Claude Code Task 工具)启动子代理执行任务。这是默认与降级
|
|
103
|
+
路径:SillyHub MCP 不可用 / 未配置 / 路径 A 未落地时全程走此路径,派发行为与既有 execute
|
|
104
|
+
波次派发等价(零回归)。
|
|
105
|
+
|
|
106
|
+
**你的角色是调度者 + 审查者,不要自己写代码。** 每个任务由一个独立子代理执行,同 Wave 内
|
|
107
|
+
可并行启动多个子代理。
|
|
108
|
+
|
|
109
|
+
### 启动子代理(必须严格遵守)
|
|
110
|
+
|
|
111
|
+
调用本机 Agent tool(Task 工具)为该 task 启动一个独立子代理,参数结构范例:
|
|
112
|
+
|
|
113
|
+
\`\`\`json
|
|
114
|
+
{
|
|
115
|
+
"subagent_type": "general",
|
|
116
|
+
"workdir": "${workdirDisplay}",
|
|
117
|
+
"prompt": "在此编写任务描述:任务目标 + 蓝图路径 + 编码铁律(见下方要点)"
|
|
118
|
+
}
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
### workdir 强制必传 = contract.worktreePath(关键)
|
|
122
|
+
|
|
123
|
+
**workdir 参数是强制必传的,值必须为 \`${workdirDisplay}\`(contract.worktreePath)。**
|
|
124
|
+
|
|
125
|
+
- **不传 workdir,子代理会在主工作区(主仓库根)而非 worktree 里读写文件**,把改动写到
|
|
126
|
+
主工作区、**破坏 worktree 隔离**,后续 assess / apply / review.json 全部错位,并可能
|
|
127
|
+
撞其他并行 agent 的改动。
|
|
128
|
+
${branchLine}
|
|
129
|
+
- 蓝图文件(tasks.md / design.md / tasks/task-XX.md)在主工作区 \`{SPEC_ROOT}/changes/<change>/\`
|
|
130
|
+
下({SPEC_ROOT} 由 CLI 渲染时替换为主仓绝对路径),可能不在 worktree 中——**读蓝图用主工作区绝对路径,不要拼接到 worktree 路径下**;
|
|
131
|
+
写代码必须落在 workdir(worktree)内。
|
|
132
|
+
|
|
133
|
+
### 子代理 prompt 要点
|
|
134
|
+
|
|
135
|
+
在子代理 prompt 里写明:
|
|
136
|
+
1. 任务目标(brief):${briefLine}
|
|
137
|
+
2. 蓝图文件路径(让子代理按需读取详情,不要内联整份蓝图污染上下文)
|
|
138
|
+
3. 编码铁律:先读后写、TDD、不编造方法、只做蓝图里写的事、遵守边界处理规则、不超出 allowedPaths
|
|
139
|
+
4. 如存在模块文档(\`{SPEC_ROOT}/docs/*/modules/\`),按需读取涉及模块的 <module>.md 参考接口约定与数据流(读主仓路径,不读 worktree 副本)
|
|
140
|
+
5. 任务含测试代码时,把下方「测试用例设计」整段复制进子代理 prompt,要求子代理按此设计测试用例
|
|
141
|
+
|
|
142
|
+
{{include: testcase-design}}
|
|
143
|
+
${modeLine}
|
|
144
|
+
${allowedLine}
|
|
145
|
+
|
|
146
|
+
### Local 后端忽略的字段(不消费)
|
|
147
|
+
|
|
148
|
+
本机 Agent tool 不做异构模型 / agent 分配,故 Local 后端**忽略** contract 中的以下字段
|
|
149
|
+
(仅 SillyHub 后端消费,此处列出仅为避免 agent 误把它们塞进 Agent tool 参数):
|
|
150
|
+
- \`modelHint\` → SillyHub model 映射,Local 不传给本机 Agent tool
|
|
151
|
+
- \`agentProfileHint\` → SillyHub agent_profile_id 映射,Local 不传给本机 Agent tool
|
|
152
|
+
- \`missionId\` → SillyHub「一 Wave 一 mission」概念(D-008),Local 无 mission
|
|
153
|
+
|
|
154
|
+
${recycleRule}`
|
|
155
|
+
}
|