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,597 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SillyHub MCP 客户端 — streamable HTTP 连接(协议 2025-11-25)
|
|
3
|
+
*
|
|
4
|
+
* 设计参考 src/sync.js 的 best-effort 风格:
|
|
5
|
+
* - 网络失败 / 非 2xx / 异常一律 console.warn 不抛错,绝不抛穿到 execute(约束:HTTP 错误保守返回 unavailable/空/false)
|
|
6
|
+
* - 未配置(缺 SILLYHUB_MCP_URL 或 SILLYHUB_MCP_TOKEN)→ 所有方法降级,不发网络
|
|
7
|
+
*
|
|
8
|
+
* 配置来源(优先级):显式参数 > local.yaml mcp 段(via readMcpConfig)> 环境变量 SILLYHUB_MCP_URL/TOKEN(fallback);
|
|
9
|
+
* 构造函数可传 { cwd, url, token, timeoutMs },缺省经 readMcpConfig(cwd) 读 local.yaml mcp 段 + env fallback。
|
|
10
|
+
*
|
|
11
|
+
* 端点:POST ${url}/mcp/(尾斜杠必需,MCP streamable HTTP 协议要求),Bearer token 鉴权。
|
|
12
|
+
* 请求体:JSON-RPC 2.0(tools/call 调 tool;tools/list 列 tool schema,task-11 路径A 探测用)。
|
|
13
|
+
* 响应:application/json(直接 JSON-RPC)或 text/event-stream(SSE,取 data: 行拼装后 JSON.parse)。
|
|
14
|
+
* tool 返回值惯例:从 result.content[0].text 再 JSON.parse 得到(tools/list 例外:result 直接是 {tools:[...]})。
|
|
15
|
+
*
|
|
16
|
+
* 铁律:
|
|
17
|
+
* - converge_mission 不封装不调用(D-004 SillySpec 自己 apply)。
|
|
18
|
+
* - worktree_path 仅作 dispatch_worker 入参传递,本文件不碰 DB(不持久化为新列)。
|
|
19
|
+
* - 仅用 Node 原生 fetch(engine>=18),不引入新依赖。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readMcpConfig } from './config.js';
|
|
23
|
+
import { getVersion } from '../version.js';
|
|
24
|
+
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
26
|
+
// MCP streamable HTTP 协议版本(task 指定 2025-11-25)
|
|
27
|
+
const MCP_PROTOCOL_VERSION = '2025-11-25';
|
|
28
|
+
// 单次 RPC 返回哨兵:server 回 400 Missing session(session 未建立/过期)→ _sendRpc 重连重试
|
|
29
|
+
const SESSION_EXPIRED = Symbol('SESSION_EXPIRED');
|
|
30
|
+
|
|
31
|
+
export class SillyHubMcpClient {
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} [opts]
|
|
34
|
+
* @param {string} [opts.cwd] - local.yaml 所在主仓库根,默认 process.cwd()(design §7.3)
|
|
35
|
+
* @param {string} [opts.url] - 显式覆盖(优先级最高 > readMcpConfig > env);显式传空串视为未配置
|
|
36
|
+
* @param {string} [opts.token] - 显式覆盖(优先级最高 > readMcpConfig > env)
|
|
37
|
+
* @param {number} [opts.timeoutMs] - 请求超时毫秒,默认 10000
|
|
38
|
+
*/
|
|
39
|
+
constructor({ cwd, url, token, timeoutMs } = {}) {
|
|
40
|
+
const workCwd = cwd || process.cwd();
|
|
41
|
+
const cfg = readMcpConfig(workCwd);
|
|
42
|
+
const u = url !== undefined ? url : (cfg?.url ?? '');
|
|
43
|
+
const t = token !== undefined ? token : (cfg?.token ?? '');
|
|
44
|
+
// 去 url 尾部斜杠,便于后续稳定拼 /mcp/(跨平台:URL 一律用正斜杠,不用 path.join)
|
|
45
|
+
this._url = typeof u === 'string' ? u.replace(/\/+$/, '') : '';
|
|
46
|
+
this._token = typeof t === 'string' ? t : '';
|
|
47
|
+
// 安全提示(sec-e 缓解):url 来自 local.yaml(agent 可写),非 https 时 Bearer token 明文上线。
|
|
48
|
+
// 一次性 warn(构造时)不刷屏;本机 http://localhost 调试场景 warn 可接受。
|
|
49
|
+
if (this._url && !/^https:\/\//i.test(this._url) && !/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/i.test(this._url)) {
|
|
50
|
+
console.warn(`[sillyhub-mcp] mcp.url 非 https(${this._url.slice(0, 60)}),Bearer token 将明文传输——请确认为受控环境`);
|
|
51
|
+
}
|
|
52
|
+
this._timeoutMs = typeof timeoutMs === 'number' && timeoutMs > 0
|
|
53
|
+
? timeoutMs
|
|
54
|
+
: DEFAULT_TIMEOUT_MS;
|
|
55
|
+
// 缺 url 或 token → 视为未配置,所有方法降级不发网络
|
|
56
|
+
this._configured = Boolean(this._url && this._token);
|
|
57
|
+
// 端点必须带尾斜杠
|
|
58
|
+
this._endpoint = this._url ? `${this._url}/mcp/` : '';
|
|
59
|
+
this._rpcId = 0;
|
|
60
|
+
// MCP streamable HTTP session 状态:惰性 initialize 后才有(见 _ensureSession)
|
|
61
|
+
this._sessionId = null;
|
|
62
|
+
this._sessionPromise = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── 内部辅助 ──
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 调用 MCP tool(JSON-RPC tools/call)。best-effort:任何失败返回 null 不抛。
|
|
69
|
+
*
|
|
70
|
+
* task-11 抽出共享骨架:本方法现为薄封装 `_sendRpc('tools/call', {name,arguments})`,
|
|
71
|
+
* warn 文案经 label=toolName 与改前逐字一致(零回归现有调用方)。
|
|
72
|
+
* @param {string} toolName
|
|
73
|
+
* @param {object} args - tool 参数(snake_case 对齐 SillyHub schema)
|
|
74
|
+
* @param {object} [opts]
|
|
75
|
+
* @param {boolean} [opts.quiet=false] - 静默失败警告(killLease 等已知 best-effort 路径用)
|
|
76
|
+
* @returns {Promise<object|null>} MCP tool result({content:[{type,text}]}),失败返回 null
|
|
77
|
+
*/
|
|
78
|
+
async _callTool(toolName, args = {}, { quiet = false } = {}) {
|
|
79
|
+
return this._sendRpc('tools/call', { name: toolName, arguments: args }, { quiet, label: toolName });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 发 JSON-RPC 请求(fetch/SSE/鉴权/session 骨架,tools/call 与 tools/list 共用)。best-effort 不抛。
|
|
84
|
+
*
|
|
85
|
+
* MCP streamable HTTP(2025-11-25)要求先 initialize 拿 session,后续请求带
|
|
86
|
+
* Mcp-Session-Id header,否则 server 回 -32600 Missing session ID(2026-08-14 实测:
|
|
87
|
+
* SillyHub FastMCP v1.29 streamable_http_app 强制 session;此前本 client 直接发
|
|
88
|
+
* tools/call 无 session → 一律 400,probe 误判 daemon-unreachable)。本方法:
|
|
89
|
+
* 1. 惰性 _ensureSession(并发安全,仅 initialize 一次);
|
|
90
|
+
* 2. _rpcOnce 发单次请求;
|
|
91
|
+
* 3. 遇 session 过期(400 Missing session)→ 重置重连重试一次。
|
|
92
|
+
*
|
|
93
|
+
* task-11 抽出:原 _callTool 的网络骨架;method/params 泛化——tools/call 带 {name,arguments},
|
|
94
|
+
* tools/list 带空 params。warn 文案用 label 作 tag(tools/call 传 toolName 保文案不变,
|
|
95
|
+
* tools/list 传 'tools/list')。
|
|
96
|
+
* @param {string} method - JSON-RPC method('tools/call' | 'tools/list')
|
|
97
|
+
* @param {object} params - JSON-RPC params(tools/call: {name,arguments}; tools/list: {})
|
|
98
|
+
* @param {object} [opts]
|
|
99
|
+
* @param {boolean} [opts.quiet=false] - 静默失败警告
|
|
100
|
+
* @param {string} [opts.label] - warn 文案标签(默认 method;tools/call 传 toolName 保文案)
|
|
101
|
+
* @returns {Promise<object|null>} JSON-RPC result(tools/call: {content}; tools/list: {tools}),失败 null
|
|
102
|
+
*/
|
|
103
|
+
async _sendRpc(method, params, { quiet = false, label } = {}) {
|
|
104
|
+
const tag = label || method;
|
|
105
|
+
if (!this._configured) {
|
|
106
|
+
if (!quiet) console.warn(`[sillyhub-mcp] 未配置(缺 SILLYHUB_MCP_URL/TOKEN),${tag} 跳过`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 惰性 initialize + 过期重连(最多 2 次:首次 + 重试)
|
|
111
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
112
|
+
await this._ensureSession(quiet);
|
|
113
|
+
if (!this._sessionId) {
|
|
114
|
+
// initialize 失败(网络/非 2xx/无 session)→ best-effort 降级,不再发业务请求
|
|
115
|
+
if (!quiet) console.warn(`[sillyhub-mcp] ${tag} initialize 失败,跳过`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
this._rpcId += 1;
|
|
119
|
+
const result = await this._rpcOnce(method, params, { quiet, tag, id: this._rpcId });
|
|
120
|
+
if (result !== SESSION_EXPIRED) return result;
|
|
121
|
+
// session 过期(400 Missing session)→ 重置重连重试一次
|
|
122
|
+
if (!quiet) console.warn(`[sillyhub-mcp] ${tag} session 过期,重连重试`);
|
|
123
|
+
this._sessionId = null;
|
|
124
|
+
this._sessionPromise = null;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 发单次 JSON-RPC 请求(不管理 session)。返回 rpc.result;失败返回 null;
|
|
131
|
+
* 400 Missing session ID → 返回 SESSION_EXPIRED 哨兵(调用方 _sendRpc 据此重连重试)。
|
|
132
|
+
*/
|
|
133
|
+
async _rpcOnce(method, params, { quiet, tag, id }) {
|
|
134
|
+
const body = {
|
|
135
|
+
jsonrpc: '2.0',
|
|
136
|
+
id,
|
|
137
|
+
method,
|
|
138
|
+
params,
|
|
139
|
+
};
|
|
140
|
+
const headers = {
|
|
141
|
+
Authorization: `Bearer ${this._token}`,
|
|
142
|
+
'Content-Type': 'application/json',
|
|
143
|
+
Accept: 'application/json, text/event-stream',
|
|
144
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
145
|
+
};
|
|
146
|
+
if (this._sessionId) headers['Mcp-Session-Id'] = this._sessionId;
|
|
147
|
+
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
150
|
+
let res;
|
|
151
|
+
try {
|
|
152
|
+
res = await fetch(this._endpoint, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers,
|
|
155
|
+
body: JSON.stringify(body),
|
|
156
|
+
signal: controller.signal,
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (!quiet) {
|
|
160
|
+
if (err && err.name === 'AbortError') {
|
|
161
|
+
console.warn(`[sillyhub-mcp] ${tag} 请求超时 (${this._timeoutMs}ms)`);
|
|
162
|
+
} else {
|
|
163
|
+
const msg = err && err.message ? err.message : String(err);
|
|
164
|
+
console.warn(`[sillyhub-mcp] ${tag} 请求失败: ${msg}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
} finally {
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
let detail = '';
|
|
174
|
+
try { detail = await res.text(); } catch { /* best effort */ }
|
|
175
|
+
// session 未建立/过期:FastMCP 回 HTTP 400 + body 含 "Missing session ID"
|
|
176
|
+
if (res.status === 400 && detail.includes('Missing session')) {
|
|
177
|
+
return SESSION_EXPIRED;
|
|
178
|
+
}
|
|
179
|
+
if (!quiet) console.warn(`[sillyhub-mcp] ${tag} → HTTP ${res.status} ${String(detail).slice(0, 200)}`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 解析响应:JSON 或 SSE(跨平台兼容 \r\n / \n)
|
|
184
|
+
const ct = res.headers.get('content-type') || '';
|
|
185
|
+
let rpc = null;
|
|
186
|
+
try {
|
|
187
|
+
if (ct.includes('text/event-stream')) {
|
|
188
|
+
const text = await res.text();
|
|
189
|
+
rpc = this._parseSseResponse(text, id);
|
|
190
|
+
} else {
|
|
191
|
+
// application/json(或其他):直接 JSON.parse
|
|
192
|
+
rpc = await res.json();
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (!quiet) {
|
|
196
|
+
const msg = err && err.message ? err.message : String(err);
|
|
197
|
+
console.warn(`[sillyhub-mcp] ${tag} 响应解析失败: ${msg}`);
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!rpc) {
|
|
203
|
+
if (!quiet) console.warn(`[sillyhub-mcp] ${tag} 响应无 JSON-RPC 内容`);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (rpc.error) {
|
|
208
|
+
// HUB-11:session 过期第二种形态——HTTP 200 + JSON-RPC error -32600「Missing session」
|
|
209
|
+
// (api-reference 记载的形态;FastMCP 实测走 HTTP 400,两种都认)。落哨兵触发
|
|
210
|
+
// _sendRpc 重置重连;其余 error 维持 warn+null(如 -32602 Invalid params 是调用侧问题,
|
|
211
|
+
// 重连救不回,不误触发)
|
|
212
|
+
if (rpc.error.code === -32600 && /missing session/i.test(String(rpc.error.message || ''))) {
|
|
213
|
+
return SESSION_EXPIRED;
|
|
214
|
+
}
|
|
215
|
+
if (!quiet) console.warn(`[sillyhub-mcp] ${tag} JSON-RPC error: ${JSON.stringify(rpc.error)}`);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return rpc.result || null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 确保已建立 MCP session(惰性 initialize,并发安全)。已初始化直接返回 true;
|
|
224
|
+
* 初始化进行中则复用同一 promise。失败返回 false 且不留半成品状态。
|
|
225
|
+
* @param {boolean} [quiet] - 静默初始化失败警告(探测路径默认用)
|
|
226
|
+
* @returns {Promise<boolean>}
|
|
227
|
+
*/
|
|
228
|
+
async _ensureSession(quiet = false) {
|
|
229
|
+
if (this._sessionId) return true;
|
|
230
|
+
if (this._sessionPromise) return this._sessionPromise;
|
|
231
|
+
this._sessionPromise = this._initialize(quiet).finally(() => { this._sessionPromise = null; });
|
|
232
|
+
return this._sessionPromise;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* MCP initialize 握手:POST initialize → 从响应 header(或 body _meta)读 mcp-session-id。
|
|
237
|
+
* 成功存 this._sessionId 并补发 notifications/initialized(协议要求;FastMCP 实测不强制,
|
|
238
|
+
* best-effort 失败仅 warn);失败返回 false(best-effort,warn 不抛)。
|
|
239
|
+
* @returns {Promise<boolean>}
|
|
240
|
+
*/
|
|
241
|
+
async _initialize(quiet = false) {
|
|
242
|
+
this._rpcId += 1;
|
|
243
|
+
const id = this._rpcId;
|
|
244
|
+
const body = {
|
|
245
|
+
jsonrpc: '2.0',
|
|
246
|
+
id,
|
|
247
|
+
method: 'initialize',
|
|
248
|
+
params: {
|
|
249
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
250
|
+
capabilities: {},
|
|
251
|
+
// MCP 2025-11-25 协议要求 clientInfo 必含 name + version,缺 version FastMCP
|
|
252
|
+
// 回 -32602 Invalid request parameters(2026-08-14 实测锁定)
|
|
253
|
+
clientInfo: { name: 'sillyspec-cli', version: getVersion() },
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
258
|
+
let res;
|
|
259
|
+
try {
|
|
260
|
+
res = await fetch(this._endpoint, {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
headers: {
|
|
263
|
+
Authorization: `Bearer ${this._token}`,
|
|
264
|
+
'Content-Type': 'application/json',
|
|
265
|
+
Accept: 'application/json, text/event-stream',
|
|
266
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify(body),
|
|
269
|
+
signal: controller.signal,
|
|
270
|
+
});
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (!quiet) {
|
|
273
|
+
const msg = err && err.message ? err.message : String(err);
|
|
274
|
+
console.warn(`[sillyhub-mcp] initialize 请求失败: ${msg}`);
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
} finally {
|
|
278
|
+
clearTimeout(timer);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!res.ok) {
|
|
282
|
+
if (!quiet) console.warn(`[sillyhub-mcp] initialize → HTTP ${res.status}`);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 优先取 header mcp-session-id(FastMCP 实测返回);缺失再查 body _meta.sessionId。
|
|
287
|
+
// ⚠️ 无论 header 是否有 session,都必须读完 body(消费 SSE 流)——FastMCP 在
|
|
288
|
+
// initialize 响应流未消费完时 session 未就绪,后续 tools/call 回 -32602
|
|
289
|
+
// "Invalid request parameters"(2026-08-14 实测:node 只读 header 不读 body 必现,
|
|
290
|
+
// python 读完 body 则过)。
|
|
291
|
+
let sid = res.headers.get('mcp-session-id');
|
|
292
|
+
let text = '';
|
|
293
|
+
try { text = await res.text(); } catch { /* best effort */ }
|
|
294
|
+
if (!sid && text) {
|
|
295
|
+
const rpc = this._parseSseResponse(text, id);
|
|
296
|
+
if (rpc && rpc.result && rpc.result._meta && typeof rpc.result._meta.sessionId === 'string') {
|
|
297
|
+
sid = rpc.result._meta.sessionId;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!sid) {
|
|
301
|
+
if (!quiet) console.warn('[sillyhub-mcp] initialize 响应无 mcp-session-id');
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
this._sessionId = sid;
|
|
305
|
+
// MCP 2025-11-25 协议要求 initialize 后发 notifications/initialized(无 id 通知,server 不回包)。
|
|
306
|
+
// 2026-08-14 实测 FastMCP v1.29 不强制;补发是为防未来 server 强校验拒掉所有 tools/call。
|
|
307
|
+
// best-effort:失败仅 warn,不阻断(session 已建,宽松 server 仍可用)。
|
|
308
|
+
try {
|
|
309
|
+
const noteController = new AbortController();
|
|
310
|
+
const noteTimer = setTimeout(() => noteController.abort(), this._timeoutMs);
|
|
311
|
+
await fetch(this._endpoint, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: {
|
|
314
|
+
Authorization: `Bearer ${this._token}`,
|
|
315
|
+
'Content-Type': 'application/json',
|
|
316
|
+
Accept: 'application/json, text/event-stream',
|
|
317
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
318
|
+
'Mcp-Session-Id': this._sessionId,
|
|
319
|
+
},
|
|
320
|
+
body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),
|
|
321
|
+
signal: noteController.signal,
|
|
322
|
+
}).catch(() => { /* best-effort */ });
|
|
323
|
+
clearTimeout(noteTimer);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
if (!quiet) console.warn(`[sillyhub-mcp] notifications/initialized 发送失败(忽略): ${err && err.message ? err.message : err}`);
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 解析 SSE 响应:取含 JSON-RPC result/error 的事件;优先 id 精确匹配,否则取首个。
|
|
332
|
+
* 事件间空行分隔;事件内 data: 行可多行,拼装时用 \n 连接。
|
|
333
|
+
*/
|
|
334
|
+
_parseSseResponse(text, expectedId) {
|
|
335
|
+
const events = String(text).split(/\r?\n\r?\n/);
|
|
336
|
+
let fallback = null;
|
|
337
|
+
for (const evt of events) {
|
|
338
|
+
const dataLines = [];
|
|
339
|
+
for (const line of evt.split(/\r?\n/)) {
|
|
340
|
+
if (line.startsWith('data:')) {
|
|
341
|
+
// 去掉 "data:" 前缀与一个可选前导空格
|
|
342
|
+
dataLines.push(line.slice(5).replace(/^ /, ''));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (dataLines.length === 0) continue;
|
|
346
|
+
const data = dataLines.join('\n');
|
|
347
|
+
let parsed;
|
|
348
|
+
try {
|
|
349
|
+
parsed = JSON.parse(data);
|
|
350
|
+
} catch {
|
|
351
|
+
continue; // 非 JSON data(心跳/注释等),跳过
|
|
352
|
+
}
|
|
353
|
+
if (!parsed || typeof parsed !== 'object') continue;
|
|
354
|
+
if (parsed.result !== undefined || parsed.error !== undefined) {
|
|
355
|
+
if (expectedId !== undefined && parsed.id === expectedId) return parsed;
|
|
356
|
+
if (!fallback) fallback = parsed;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return fallback;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 从 MCP tool result 提取 content[0].text 并 JSON.parse 为 tool 返回值(MCP tool 结果惯例)。
|
|
364
|
+
* @returns {any|null} 解析后的 tool 返回值;非 JSON 字符串则原样返回;缺失返回 null
|
|
365
|
+
*/
|
|
366
|
+
_parseToolReturnValue(result) {
|
|
367
|
+
if (!result || !Array.isArray(result.content) || result.content.length === 0) return null;
|
|
368
|
+
const first = result.content[0];
|
|
369
|
+
if (!first || typeof first.text !== 'string') return null;
|
|
370
|
+
const text = first.text;
|
|
371
|
+
try {
|
|
372
|
+
return JSON.parse(text);
|
|
373
|
+
} catch {
|
|
374
|
+
return text; // tool 返回非 JSON 字符串,原样返回
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── 对外方法(均 async,best-effort 不抛) ──
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 探测 daemon 连通性 + token 有效性(调 list_agent_profiles tool)。
|
|
382
|
+
* 未配置 / HTTP 异常 / 非 2xx / JSON-RPC error → 返回 false(不阻塞调用方)。
|
|
383
|
+
* @returns {Promise<boolean>} 成功且 tool 返回非空 → true,否则 false
|
|
384
|
+
*/
|
|
385
|
+
async probeDaemon() {
|
|
386
|
+
if (!this._configured) return false;
|
|
387
|
+
const result = await this._callTool('list_agent_profiles', {});
|
|
388
|
+
return result !== null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 列出 daemon 暴露的 MCP tools(JSON-RPC `tools/list`,task-11 路径A 探测用)。
|
|
393
|
+
*
|
|
394
|
+
* 与 tools/call 的差异:method='tools/list',无 name/arguments;响应 result 是
|
|
395
|
+
* `{tools: [{name, description, inputSchema}, ...]}`(MCP 标准)。本方法返回 tools 数组
|
|
396
|
+
* (优先 result.tools,兼容 daemon 直接返回数组),调用方(probe.js 路径A 探测)据此查
|
|
397
|
+
* dispatch_worker.inputSchema.properties 是否含 worktree_path + worker_prompt。
|
|
398
|
+
*
|
|
399
|
+
* best-effort:未配置 / HTTP 异常 / 非 2xx / JSON-RPC error / 解析失败 → 返回 null 不抛
|
|
400
|
+
* (R-04 保守,探测失败由调用方降级 false)。默认 quiet=true(探测路径不刷失败噪音,
|
|
401
|
+
* 区别于业务 tool 调用默认 quiet=false)。
|
|
402
|
+
* @param {object} [opts]
|
|
403
|
+
* @param {boolean} [opts.quiet=true] - 静默失败警告(探测路径默认静默)
|
|
404
|
+
* @returns {Promise<Array<{name:string, inputSchema?:object}>|null>} tools 数组;失败/未配置 null
|
|
405
|
+
*/
|
|
406
|
+
async listTools({ quiet = true } = {}) {
|
|
407
|
+
if (!this._configured) return null;
|
|
408
|
+
const result = await this._sendRpc('tools/list', {}, { quiet, label: 'tools/list' });
|
|
409
|
+
if (!result) return null;
|
|
410
|
+
if (Array.isArray(result)) return result; // daemon 直接返回数组(兼容)
|
|
411
|
+
if (Array.isArray(result.tools)) return result.tools; // 标准 MCP {tools:[...]}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* HUB-12a:tools/list 的完整 result(一次请求同时喂路径A schema 预热与 root_path 越界
|
|
417
|
+
* 校验——此前 listTools + getRootPath 各发一次同 method 请求,probe 链路重复 RPC)。
|
|
418
|
+
* best-effort:未配置/失败 → null 不抛。形态:{tools:[...], root_path?} 或裸数组。
|
|
419
|
+
*/
|
|
420
|
+
async listToolsWithMeta({ quiet = true } = {}) {
|
|
421
|
+
if (!this._configured) return null;
|
|
422
|
+
return this._sendRpc('tools/list', {}, { quiet, label: 'tools/list' });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* HUB-12b:关闭 MCP session(streamable HTTP DELETE,2025-11-25 协议规定的会话终止方式)。
|
|
427
|
+
* CLI 短进程原本把 session 留给 server TTL 清理;长驻场景(未来 daemon 化)会积累半开
|
|
428
|
+
* session。best-effort:网络失败/无 session 返回 false 不抛;成功后清本地 session 态,
|
|
429
|
+
* 后续调用自动重新 initialize。
|
|
430
|
+
* @returns {Promise<boolean>} 是否发出了有效的 DELETE
|
|
431
|
+
*/
|
|
432
|
+
async close() {
|
|
433
|
+
if (!this._configured || !this._sessionId) return false;
|
|
434
|
+
const sid = this._sessionId;
|
|
435
|
+
this._sessionId = null; // 先清本地态:DELETE 失败也不留「以为还活着」的 session
|
|
436
|
+
try {
|
|
437
|
+
const controller = new AbortController();
|
|
438
|
+
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
439
|
+
try {
|
|
440
|
+
const res = await fetch(this._endpoint, {
|
|
441
|
+
method: 'DELETE',
|
|
442
|
+
headers: {
|
|
443
|
+
Authorization: `Bearer ${this._token}`,
|
|
444
|
+
'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
|
|
445
|
+
'Mcp-Session-Id': sid,
|
|
446
|
+
},
|
|
447
|
+
signal: controller.signal,
|
|
448
|
+
});
|
|
449
|
+
return res.ok;
|
|
450
|
+
} finally {
|
|
451
|
+
clearTimeout(timer);
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* best-effort 从 daemon 拿 workspace root_path(task-12 路径A 越界校验用)。
|
|
460
|
+
*
|
|
461
|
+
* 复用 tools/list RPC 机制(与 listTools 同 method,复用 _sendRpc 骨架,task-12 "复用 task-11
|
|
462
|
+
* listTools"):defensively 读 result.root_path(若 daemon 在 tools/list 响应顶层暴露)。
|
|
463
|
+
* 当前 SillyHub gateway 的 tools/list 仅返 `{tools:[...]}` 不含 root_path → 实际返回 null
|
|
464
|
+
* (probe 跳过越界校验,best-effort 语义)。**不臆造 daemon 接口**(无专用 get_workspace_root
|
|
465
|
+
* tool,见 task-12 constraints / spike-01 结论);后续 daemon 若在 tools/list 顶层暴露 root_path,
|
|
466
|
+
* 本方法自动适配,probe 无需改。
|
|
467
|
+
*
|
|
468
|
+
* best-effort:未配置 / 异常 / 无 root_path 字段 → 返回 null 不抛。
|
|
469
|
+
* @param {object} [opts]
|
|
470
|
+
* @param {boolean} [opts.quiet=true] - 静默失败警告
|
|
471
|
+
* @returns {Promise<string|null>} root_path 绝对路径;拿不到 null
|
|
472
|
+
*/
|
|
473
|
+
async getRootPath({ quiet = true } = {}) {
|
|
474
|
+
if (!this._configured) return null;
|
|
475
|
+
const result = await this._sendRpc('tools/list', {}, { quiet, label: 'tools/list' });
|
|
476
|
+
if (!result || typeof result !== 'object') return null;
|
|
477
|
+
const rp = result.root_path;
|
|
478
|
+
return typeof rp === 'string' && rp.length > 0 ? rp : null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* 创建 mission(调 create_mission tool)。
|
|
483
|
+
* @param {object} p
|
|
484
|
+
* @param {string} p.objective
|
|
485
|
+
* @param {string} p.changeId
|
|
486
|
+
* @param {number} [p.budgetUsd]
|
|
487
|
+
* @param {string} [p.orchestrationMode] - 'team'(默认,不传走原逻辑零回归,FR-05) |
|
|
488
|
+
* 'external'(路径A,SillySpec 外部调度:跳 orchestrator spawn + converge 跳 merge,FR-08/D-007)。
|
|
489
|
+
* 传入时 args.orchestration_mode = mode;不传/Null 不加字段(team 默认,零回归)。
|
|
490
|
+
* @returns {Promise<{missionId: string|null}>} 未配置/失败 → { missionId: null }
|
|
491
|
+
*/
|
|
492
|
+
async createMission({ objective, changeId, budgetUsd, orchestrationMode } = {}) {
|
|
493
|
+
if (!this._configured) return { missionId: null };
|
|
494
|
+
const args = {
|
|
495
|
+
objective,
|
|
496
|
+
change_id: changeId,
|
|
497
|
+
};
|
|
498
|
+
if (budgetUsd !== undefined && budgetUsd !== null) args.budget_usd = budgetUsd;
|
|
499
|
+
// task-12:路径A 传 'external'(跳 orchestrator spawn,FR-08);不传 → 不加字段,daemon 默认 team 零回归
|
|
500
|
+
if (orchestrationMode !== undefined && orchestrationMode !== null) {
|
|
501
|
+
args.orchestration_mode = orchestrationMode;
|
|
502
|
+
}
|
|
503
|
+
const result = await this._callTool('create_mission', args);
|
|
504
|
+
if (result === null) return { missionId: null };
|
|
505
|
+
const value = this._parseToolReturnValue(result);
|
|
506
|
+
const missionId = value && typeof value === 'object'
|
|
507
|
+
? (value.mission_id ?? value.missionId ?? null)
|
|
508
|
+
: null;
|
|
509
|
+
return { missionId };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 派发 worker(调 dispatch_worker tool,路径A 入参含 worktree_path/branch/worker_prompt)。
|
|
514
|
+
* worktree_path 仅作 tool 入参传递,不持久化为 DB 列。
|
|
515
|
+
* @param {object} p
|
|
516
|
+
* @param {string} p.missionId
|
|
517
|
+
* @param {string} p.objective
|
|
518
|
+
* @param {string} [p.worktreePath]
|
|
519
|
+
* @param {string} [p.branch]
|
|
520
|
+
* @param {boolean} [p.readOnly]
|
|
521
|
+
* @param {string} [p.model]
|
|
522
|
+
* @param {string} [p.agentProfileId]
|
|
523
|
+
* @param {string} [p.workerPrompt]
|
|
524
|
+
* @returns {Promise<{workerId: string|null, status: string}>} 未配置/失败 → { workerId: null, status: 'unavailable' }
|
|
525
|
+
*/
|
|
526
|
+
async dispatchWorker({
|
|
527
|
+
missionId, objective, worktreePath, branch, readOnly, model, agentProfileId, workerPrompt,
|
|
528
|
+
} = {}) {
|
|
529
|
+
if (!this._configured) return { workerId: null, status: 'unavailable' };
|
|
530
|
+
const args = {
|
|
531
|
+
mission_id: missionId,
|
|
532
|
+
objective,
|
|
533
|
+
};
|
|
534
|
+
if (readOnly !== undefined) args.read_only = readOnly;
|
|
535
|
+
if (worktreePath !== undefined && worktreePath !== null) args.worktree_path = worktreePath;
|
|
536
|
+
// task-12 / D-009:branch 字段名对齐跨仓契约(design §7.3,round-1 worktree_branch 已统一为 branch)
|
|
537
|
+
if (branch !== undefined && branch !== null) args.branch = branch;
|
|
538
|
+
if (model !== undefined && model !== null) args.model = model;
|
|
539
|
+
if (agentProfileId !== undefined && agentProfileId !== null) args.agent_profile_id = agentProfileId;
|
|
540
|
+
if (workerPrompt !== undefined && workerPrompt !== null) args.worker_prompt = workerPrompt;
|
|
541
|
+
const result = await this._callTool('dispatch_worker', args);
|
|
542
|
+
if (result === null) return { workerId: null, status: 'unavailable' };
|
|
543
|
+
const value = this._parseToolReturnValue(result);
|
|
544
|
+
const workerId = value && typeof value === 'object'
|
|
545
|
+
? (value.worker_id ?? value.workerId ?? null)
|
|
546
|
+
: null;
|
|
547
|
+
const status = value && typeof value === 'object'
|
|
548
|
+
? (value.status ?? 'unknown')
|
|
549
|
+
: 'unknown';
|
|
550
|
+
return { workerId, status };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* 列出 mission 下的 worker(调 list_workers tool)。
|
|
555
|
+
* @param {string} missionId
|
|
556
|
+
* @returns {Promise<Array>} 未配置/失败返回 [];兼容 tool 返回数组或 { workers: [...] }
|
|
557
|
+
*/
|
|
558
|
+
async listWorkers(missionId) {
|
|
559
|
+
if (!this._configured) return [];
|
|
560
|
+
const result = await this._callTool('list_workers', { mission_id: missionId });
|
|
561
|
+
if (result === null) return [];
|
|
562
|
+
const value = this._parseToolReturnValue(result);
|
|
563
|
+
if (Array.isArray(value)) return value;
|
|
564
|
+
if (value && Array.isArray(value.workers)) return value.workers;
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* 终止 worker lease 防双写(UB-6 超时 fallback 用)。
|
|
570
|
+
*
|
|
571
|
+
* 注:SillyHub 当前 8 个 tool 无显式 kill,路径A 未落地;best-effort 实现——
|
|
572
|
+
* 尝试 report_progress 带 kill 标记投递终止请求;tool 不存在/不支持/HTTP 异常时静默返回不抛。
|
|
573
|
+
* 由于无专用 kill tool,lease 实际终止不保证,结果保守报 killed=false
|
|
574
|
+
* (路径A 落地专用 kill tool 后再升级为 killed=true 语义)。
|
|
575
|
+
* @param {string} workerId
|
|
576
|
+
* @returns {Promise<{killed: boolean, reason?: string}>}
|
|
577
|
+
*/
|
|
578
|
+
async killLease(workerId) {
|
|
579
|
+
if (!this._configured) {
|
|
580
|
+
return { killed: false, reason: '未配置 SillyHub MCP(路径A 未落地/kill tool 未支持)' };
|
|
581
|
+
}
|
|
582
|
+
// best-effort:quiet 抑制已知未落地路径的失败噪音(静默返回不抛)
|
|
583
|
+
const result = await this._callTool('report_progress', {
|
|
584
|
+
worker_id: workerId,
|
|
585
|
+
kill: true,
|
|
586
|
+
marker: 'sillyspec-kill-lease',
|
|
587
|
+
}, { quiet: true });
|
|
588
|
+
if (result === null) {
|
|
589
|
+
return { killed: false, reason: '路径A 未落地/kill tool 未支持(report_progress kill 标记未生效或被拒)' };
|
|
590
|
+
}
|
|
591
|
+
// report_progress 调用成功 ≠ lease 已终止(其非专用 kill tool);保守报 killed=false
|
|
592
|
+
return {
|
|
593
|
+
killed: false,
|
|
594
|
+
reason: '路径A 未落地/kill tool 未支持(kill 标记已投递,lease 实际终止需 daemon 侧专用 kill tool)',
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* readMcpConfig — SillyHub MCP 凭据共享 helper(design §7.2 / §6 mcp 凭据数据流)。
|
|
3
|
+
*
|
|
4
|
+
* 统一读源,供三处消费点:① client.js 构造函数(_url/_token/_configured/_endpoint)
|
|
5
|
+
* ② dispatch/probe.js configFingerprint(缓存 key)+ no-config 快速路径 ③ execute.js
|
|
6
|
+
* getDispatchMode hasConfig(派发三态判定)。
|
|
7
|
+
*
|
|
8
|
+
* 优先级:local.yaml mcp 段(mcp.url + mcp.token 两键齐全)>
|
|
9
|
+
* process.env.SILLYHUB_MCP_URL/TOKEN fallback;
|
|
10
|
+
* mcp 段缺或任一键缺才回退 env。env 也缺 → 返回 null。
|
|
11
|
+
*
|
|
12
|
+
* 读法参考 src/dispatch/probe.js readProbeTtlFromLocalYaml 的 best-effort 风格:
|
|
13
|
+
* join(cwd,'.sillyspec','local.yaml') → existsSync 守卫 → jsYaml.load(readFileSync) → 取 doc.mcp。
|
|
14
|
+
*
|
|
15
|
+
* 铁律(design R-07):
|
|
16
|
+
* - best-effort 绝不抛:文件不存在 / jsYaml.load 抛 / doc 非对象 全 try/catch 回退 env
|
|
17
|
+
* - 纯 fs + env 读,绝不发网络(保 probe no-config 快速路径「不发网络」零回归)
|
|
18
|
+
* - 不引新依赖(js-yaml 复用 dispatch/probe.js:17 已引)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import jsYaml from 'js-yaml';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 读 SillyHub MCP 凭据(local.yaml mcp 段 + env fallback,best-effort 不抛不发网络)。
|
|
27
|
+
* @param {string} cwd - 主仓库根(local.yaml 在 <cwd>/.sillyspec/local.yaml)
|
|
28
|
+
* @returns {{url: string, token: string} | null}
|
|
29
|
+
* 凭据齐全 → { url(尾斜杠归一), token };local.yaml mcp 段缺/缺键 + env 缺 → null
|
|
30
|
+
*/
|
|
31
|
+
export function readMcpConfig(cwd) {
|
|
32
|
+
// 1. local.yaml mcp 段优先
|
|
33
|
+
try {
|
|
34
|
+
const p = join(cwd, '.sillyspec', 'local.yaml');
|
|
35
|
+
if (existsSync(p)) {
|
|
36
|
+
const doc = jsYaml.load(readFileSync(p, 'utf8'));
|
|
37
|
+
if (doc && typeof doc === 'object') {
|
|
38
|
+
const mcp = doc.mcp;
|
|
39
|
+
if (mcp && typeof mcp === 'object') {
|
|
40
|
+
const u = typeof mcp.url === 'string' ? mcp.url : '';
|
|
41
|
+
const t = typeof mcp.token === 'string' ? mcp.token : '';
|
|
42
|
+
// 两键齐全才视为有效源(design §7.2「缺键 → 回退 env」)
|
|
43
|
+
if (u && t) {
|
|
44
|
+
return { url: u.replace(/\/+$/, ''), token: t };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// best-effort:文件读 / js-yaml 解析失败 → 继续回退 env
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. env fallback(兼容旧部署 / 现有测试不设 env 场景,design §9)
|
|
54
|
+
const eu = process.env.SILLYHUB_MCP_URL;
|
|
55
|
+
const et = process.env.SILLYHUB_MCP_TOKEN;
|
|
56
|
+
if (typeof eu === 'string' && eu && typeof et === 'string' && et) {
|
|
57
|
+
return { url: eu.replace(/\/+$/, ''), token: et };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3. local.yaml mcp 段缺/缺键 + env 缺 → null(调用方 _configured=false 降级不发网络)
|
|
61
|
+
return null;
|
|
62
|
+
}
|