sillyspec 3.27.2 → 3.27.5
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/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/foreign-declared.js +78 -0
- package/src/index.js +89 -9
- 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 +122 -43
- package/src/run/complete-handlers.js +48 -19
- package/src/run/complete.js +5 -2
- package/src/run/gates.js +87 -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.js +2 -2
- package/src/scan-postcheck.js +30 -17
- package/src/stage-contract-spec.js +2 -1
- package/src/stage-contract.js +68 -4
- package/src/stages/archive.js +53 -12
- package/src/stages/brainstorm-auto.js +7 -0
- package/src/stages/brainstorm.js +11 -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 +1 -0
- package/src/stages/quick.js +6 -1
- package/src/stages/verify.js +31 -14
- package/src/sync.js +41 -3
- package/src/task-review.js +2 -2
- package/src/taskcard-placeholders.js +21 -0
- package/src/taskcard.js +10 -1
- package/src/verify-postcheck.js +315 -13
- package/src/verify-probes.js +46 -4
- package/src/worktree-apply.js +184 -7
- package/src/worktree-deps.js +57 -0
- package/src/worktree.js +201 -58
- package/templates/prompts/taskcard-rules.md +1 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MultiRepoContext — 跨仓 task 运行时多仓执行上下文(W1 task-01)。
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:design §7.1(接口权威定义)+ 决策 D-005/D-006/D-007/D-013。
|
|
5
|
+
*
|
|
6
|
+
* 收口 execute 链路 7 个单仓假设点(applyWorktree / validateTaskReviews /
|
|
7
|
+
* runVerifyTestCheck / generateTaskReviewDrafts 等)——把"主仓=唯一仓库"假设
|
|
8
|
+
* 抽成 Map<repoKey, RepoEntry>,单仓 change 退化为 {main:{...}} 单值 map 零回归
|
|
9
|
+
* (GOAL-2),跨仓 change 实时反映各仓 HEAD(约束①)+ 配置错误 fail-closed(约束②)。
|
|
10
|
+
*
|
|
11
|
+
* 关键约束:
|
|
12
|
+
* - 约束①(D-006):跨仓仓无 meta.json,base/head 不读 meta。resolveHead 实时
|
|
13
|
+
* `git -C <crossRepo> rev-parse HEAD`(不缓存,task 推进后过期);resolveBase
|
|
14
|
+
* 用 taskBaseCommit(task 卡 base_commit 锡点,跨仓必传)。
|
|
15
|
+
* - 约束②(D-007):declaredRepos 有 key 不在 repoRegistry → throw;跨仓仓
|
|
16
|
+
* `git -C <crossRepo> rev-parse HEAD` 失败 → throw。配置错误不降级,不沿用
|
|
17
|
+
* 主仓 verifyReviewGitEvidence 的 unavailable 降级(那是环境缺失容错;跨仓
|
|
18
|
+
* 不可用是用户 local.yaml 配错,性质不同,必须阻断)。
|
|
19
|
+
* - D-013:execute 启动时构造一次,进程级缓存贯穿 execute/apply/verify(本模块
|
|
20
|
+
* 不持有进程级缓存——ctx 实例本身就是进程级单例,由 execute 入口持有)。
|
|
21
|
+
*
|
|
22
|
+
* 不涉及生命周期契约(design §7.5):纯内存对象,不跨进程、不持久化、不入库。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { git, gitQuiet } from '../git-helper.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} RepoEntry
|
|
29
|
+
* @property {string} repoKey - repo 标识('main' 或 local.yaml repos: 的键名)
|
|
30
|
+
* @property {string} gitDir - git 工作目录(rev-parse/diff/apply 的 cwd)
|
|
31
|
+
* @property {string} worktreePath - 子代理工作目录(主仓=worktreePath 或 in-place-fallback 时=cwd;跨仓=跨仓仓根)
|
|
32
|
+
* @property {string} projectRoot - apply 目标根(主仓=主仓根;跨仓=跨仓仓根,但跨仓 apply=no-op 不用)
|
|
33
|
+
* @property {boolean} isMain - 是否主仓
|
|
34
|
+
* @property {function} resolveHead - () => string,实时 git rev-parse HEAD(跨仓不缓存)
|
|
35
|
+
* @property {function} resolveBase - (taskBaseCommit?) => string,base 锚点(主仓→meta.baseHash;跨仓→taskBaseCommit 必传)
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export class MultiRepoContext {
|
|
39
|
+
/**
|
|
40
|
+
* @param {Object} opts
|
|
41
|
+
* @param {string} opts.cwd - 主仓 cwd
|
|
42
|
+
* @param {string} opts.changeName - 当前 change 名(读 worktreeManager.getMeta)
|
|
43
|
+
* @param {Object} [opts.platformOpts] - 平台选项(specRoot 等,预留,本模块不消费)
|
|
44
|
+
* @param {string[]} opts.declaredRepos - 所有 task 卡片声明的 repo: 值(含 'main')
|
|
45
|
+
* @param {Map<string,string>} opts.repoRegistry - local.yaml repos: 段解析结果 Map<key, path>
|
|
46
|
+
* @param {Object} opts.worktreeManager - WorktreeManager 实例(主仓 meta 读取;in-place-fallback 兜底 cwd)
|
|
47
|
+
* @throws {Error} 约束②: declaredRepos 中有 repoKey 不在 repoRegistry → 抛错列已注册 repo,阻断 execute
|
|
48
|
+
* 约束②: 跨仓仓 git rev-parse 失败(路径不存在/非 git 仓)→ 抛错阻断 execute
|
|
49
|
+
*/
|
|
50
|
+
constructor({ cwd, changeName, platformOpts, declaredRepos, repoRegistry, worktreeManager }) {
|
|
51
|
+
if (!cwd) throw new Error('MultiRepoContext: cwd 必传');
|
|
52
|
+
if (!Array.isArray(declaredRepos)) throw new Error('MultiRepoContext: declaredRepos 必传为数组');
|
|
53
|
+
if (!(repoRegistry instanceof Map)) throw new Error('MultiRepoContext: repoRegistry 必传为 Map<key,path>');
|
|
54
|
+
if (!worktreeManager || typeof worktreeManager.getMeta !== 'function') {
|
|
55
|
+
throw new Error('MultiRepoContext: worktreeManager 必传(需 getMeta 方法)');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.cwd = cwd;
|
|
59
|
+
this.changeName = changeName;
|
|
60
|
+
this.platformOpts = platformOpts || {};
|
|
61
|
+
this.repoRegistry = repoRegistry;
|
|
62
|
+
this.worktreeManager = worktreeManager;
|
|
63
|
+
|
|
64
|
+
// dedupe + 保留声明顺序(同一 repo 多 task 声明只建一项)
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const orderedKeys = [];
|
|
67
|
+
for (const key of declaredRepos) {
|
|
68
|
+
if (!seen.has(key)) {
|
|
69
|
+
seen.add(key);
|
|
70
|
+
orderedKeys.push(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 约束② fail-closed 预检:declaredRepos ⊆ repoRegistry ∪ {'main'}
|
|
75
|
+
// main 隐式 = cwd,不要求注册;其余 key 必须在 repoRegistry 命中。
|
|
76
|
+
const unregistered = orderedKeys.filter(k => k !== 'main' && !repoRegistry.has(k));
|
|
77
|
+
if (unregistered.length > 0) {
|
|
78
|
+
const registered = ['main (隐式)', ...repoRegistry.keys()];
|
|
79
|
+
throw new Error(
|
|
80
|
+
`MultiRepoContext: 以下 repo 未在 local.yaml repos: 段注册:[${unregistered.join(', ')}]。` +
|
|
81
|
+
`当前已注册 repo:[${registered.join(', ')}]。` +
|
|
82
|
+
`一键注册(每个缺的 key 各跑一次,勿手编 YAML):` +
|
|
83
|
+
unregistered.map(k => `sillyspec local register-repo ${k} <${k} 仓根路径>`).join(';') +
|
|
84
|
+
`。补注册后再启动 execute(约束② fail-closed,跨仓 apply 走错仓=数据所有权事故)。`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const map = new Map();
|
|
89
|
+
for (const repoKey of orderedKeys) {
|
|
90
|
+
map.set(repoKey, repoKey === 'main'
|
|
91
|
+
? this._buildMainEntry()
|
|
92
|
+
: this._buildCrossRepoEntry(repoKey, repoRegistry.get(repoKey)));
|
|
93
|
+
}
|
|
94
|
+
this.map = map;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 主仓 RepoEntry:isMain=true,读 worktreeManager.getMeta 的 baseHash + worktreePath。
|
|
99
|
+
* in-place-fallback 模式(meta.mode==='in-place-fallback' 或 meta 缺失)worktreePath 兜底为 cwd。
|
|
100
|
+
* @returns {RepoEntry}
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
_buildMainEntry() {
|
|
104
|
+
const meta = this.changeName ? this.worktreeManager.getMeta(this.changeName) : null;
|
|
105
|
+
const isInPlace = !meta || meta.mode === 'in-place-fallback';
|
|
106
|
+
const worktreePath = isInPlace ? this.cwd : (meta.worktreePath || this.cwd);
|
|
107
|
+
const baseHash = meta?.baseHash || null;
|
|
108
|
+
const gitDir = worktreePath; // 主仓 git 操作 cwd = worktree(或 in-place 的 cwd)
|
|
109
|
+
const projectRoot = this.cwd;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
repoKey: 'main',
|
|
113
|
+
gitDir,
|
|
114
|
+
worktreePath,
|
|
115
|
+
projectRoot,
|
|
116
|
+
isMain: true,
|
|
117
|
+
// 主仓 HEAD:从 worktree/cwd 实时取(不缓存)。主仓单仓不变式下也可用 meta.baseHash 作 base,
|
|
118
|
+
// 但 HEAD 反映 worktree 当前态(子代理可能 commit 推进),故实时取与跨仓一致语义。
|
|
119
|
+
resolveHead: () => git(gitDir, ['rev-parse', 'HEAD']),
|
|
120
|
+
// 主仓 base:meta.baseHash(单仓不变式,CLI 创建 worktree 时锡点)。
|
|
121
|
+
// 跨仓 base 不走这里(resolveBase 必传 taskBaseCommit,由 _buildCrossRepoEntry 闭包消费)。
|
|
122
|
+
// 主仓忽略 taskBaseCommit 参数——主仓 base 永远锚 meta.baseHash,与单仓行为一致(零回归)。
|
|
123
|
+
resolveBase: (_taskBaseCommit) => {
|
|
124
|
+
if (!baseHash) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'MultiRepoContext: 主仓 meta.baseHash 缺失,无法解析 base。' +
|
|
127
|
+
'可能 worktree 未创建或 meta.json 损坏——请先 sillyspec worktree doctor 检查。'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return baseHash;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 跨仓 RepoEntry:isMain=false,gitDir=projectRoot=worktreePath=跨仓仓根。
|
|
137
|
+
* 构造时 `git -C <crossRepo> rev-parse HEAD` 必须 succeed(约束② fail-closed),
|
|
138
|
+
* 路径不存在/非 git 仓 → throw 阻断 execute。
|
|
139
|
+
* @param {string} repoKey
|
|
140
|
+
* @param {string} crossRepoPath - repoRegistry 解析出的跨仓仓绝对路径
|
|
141
|
+
* @returns {RepoEntry}
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_buildCrossRepoEntry(repoKey, crossRepoPath) {
|
|
145
|
+
if (!crossRepoPath) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`MultiRepoContext: repo "${repoKey}" 在 local.yaml repos: 段注册但路径为空。` +
|
|
148
|
+
`请补全路径(约束② fail-closed)。`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
// 约束②:跨仓 git 必须可达。失败 = 路径不存在 / 非 git 仓 / git 异常,一律阻断。
|
|
152
|
+
// 不沿用主仓 verifyReviewGitEvidence unavailable 降级(design §9 兼容策略表)。
|
|
153
|
+
let headVerify;
|
|
154
|
+
try {
|
|
155
|
+
headVerify = git(crossRepoPath, ['rev-parse', 'HEAD']);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`MultiRepoContext: 跨仓 repo "${repoKey}" git 不可达(路径:${crossRepoPath})。` +
|
|
159
|
+
`rev-parse HEAD 失败:${e.message}。` +
|
|
160
|
+
`请检查 local.yaml repos: 段路径是否正确、目录是否存在且为 git 仓(约束② fail-closed,配置错误不降级)。`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (!headVerify) {
|
|
164
|
+
// git() 返回空串属异常态(无 commit 的空仓 rev-parse HEAD 会非零退出,已被 catch 兜底),
|
|
165
|
+
// 此处仅作防御性双保险,触发同样走 fail-closed。
|
|
166
|
+
throw new Error(
|
|
167
|
+
`MultiRepoContext: 跨仓 repo "${repoKey}"(${crossRepoPath})rev-parse HEAD 返回空——` +
|
|
168
|
+
`疑似空 git 仓(无 commit)。跨仓 task 需仓库已有至少一个 commit(约束② fail-closed)。`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
repoKey,
|
|
174
|
+
gitDir: crossRepoPath,
|
|
175
|
+
worktreePath: crossRepoPath,
|
|
176
|
+
projectRoot: crossRepoPath,
|
|
177
|
+
isMain: false,
|
|
178
|
+
// 约束①:跨仓 HEAD 实时取,不缓存(task 推进后过期,每次 resolveHead 反映最新 HEAD)。
|
|
179
|
+
resolveHead: () => git(crossRepoPath, ['rev-parse', 'HEAD']),
|
|
180
|
+
// 约束①:跨仓 base = task 卡 base_commit 锡点(必传)。跨仓仓无 meta.json,
|
|
181
|
+
// 不读 meta;taskBaseCommit 由 task-02 DeclaredRepos 之外的 task 卡 frontmatter 提供。
|
|
182
|
+
resolveBase: (taskBaseCommit) => {
|
|
183
|
+
if (!taskBaseCommit) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`MultiRepoContext: 跨仓 repo "${repoKey}" resolveBase 必传 taskBaseCommit(task 卡 base_commit 锡点)。` +
|
|
186
|
+
`跨仓仓无 meta.json,base 只能由 task 卡片锡点提供(约束①)。`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return taskBaseCommit;
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @returns {RepoEntry|null} */
|
|
195
|
+
resolve(repoKey) {
|
|
196
|
+
return this.map.get(repoKey) || null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @returns {Map<string, RepoEntry>} */
|
|
200
|
+
get repos() {
|
|
201
|
+
return this.map;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @returns {boolean} 是否含跨仓 task(map.size>1,execute prompt 分叉用) */
|
|
205
|
+
hasCrossRepo() {
|
|
206
|
+
return this.map.size > 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* next.js — `sillyspec next` 项目状态探测(吸收 continue/resume 两个 skill 的手工探测表,
|
|
3
|
+
* 2026-08-21 agent-手工产出审计第二批 G7/G8)。
|
|
4
|
+
*
|
|
5
|
+
* 两个 skill 各自维护一套「文件存在性状态机」推断表且内容重复,agent 每次手工跑
|
|
6
|
+
* ls/cat 探测再查表——纯机械探测是 CLI 的本职。本模块输出「状态描述 + 下一步命令 +
|
|
7
|
+
* 依据文件」,agent 按输出执行即可;skill 保留作兜底(CLI 不可用/输出异常时)。
|
|
8
|
+
*
|
|
9
|
+
* 纯 fs 探测(零 DB 依赖、零 token、快):进度库的权威状态归 `sillyspec progress show`,
|
|
10
|
+
* 本命令管「没有活跃进度时从产物文件反推该干什么」。决策表(两个 skill 合并):
|
|
11
|
+
*
|
|
12
|
+
* 1. HANDOFF.json 存在 → 交接恢复
|
|
13
|
+
* 2. 活跃变更(changes/ 非 archive)逐个推断:
|
|
14
|
+
* 无文件→补 proposal / 无 design.md→brainstorm / 无 tasks.md→brainstorm 末步 /
|
|
15
|
+
* 无 plan.md→plan / tasks.md 有未勾→execute / 全勾无 verify-result.md→verify /
|
|
16
|
+
* 有 verify-result.md→archive
|
|
17
|
+
* 3. 有 docs/<p>/scan/ 无活跃变更 → brainstorm(已扫描待开变更)
|
|
18
|
+
* 4. 有 REQUIREMENTS.md/ROADMAP.md 无变更 → brainstorm(绿地已有需求)
|
|
19
|
+
* 5. 什么都没有 → init(新项目)或 scan(棕地)
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readdirSync, readFileSync } from 'fs'
|
|
22
|
+
import { join } from 'path'
|
|
23
|
+
import { resolveSpecDir } from './shared.js'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 数 tasks.md checkbox:返回 { checked, total }(无文件 → {checked:0,total:0})
|
|
27
|
+
*/
|
|
28
|
+
function countTasks(tasksPath) {
|
|
29
|
+
if (!existsSync(tasksPath)) return { checked: 0, total: 0 }
|
|
30
|
+
let checked = 0
|
|
31
|
+
let total = 0
|
|
32
|
+
for (const line of readFileSync(tasksPath, 'utf8').split('\n')) {
|
|
33
|
+
const m = line.match(/^[-*]\s*\[([ xX])\]\s*task-\d+\b/)
|
|
34
|
+
if (!m) continue
|
|
35
|
+
total++
|
|
36
|
+
if (m[1] !== ' ') checked++
|
|
37
|
+
}
|
|
38
|
+
return { checked, total }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 单个活跃变更的产物推断:返回 { state, next, evidence }
|
|
43
|
+
*/
|
|
44
|
+
function inferChange(changeDir, name) {
|
|
45
|
+
const has = (f) => existsSync(join(changeDir, f))
|
|
46
|
+
const ev = (f) => join('.sillyspec', 'changes', name, f)
|
|
47
|
+
const t = countTasks(join(changeDir, 'tasks.md'))
|
|
48
|
+
|
|
49
|
+
if (!has('proposal.md') && !has('design.md') && !has('tasks.md') && !has('plan.md')) {
|
|
50
|
+
return { state: `${name}:变更目录为空`, next: 'sillyspec run brainstorm(完善 proposal)或清理该空目录', evidence: [ev('(无产物)')] }
|
|
51
|
+
}
|
|
52
|
+
if (!has('design.md')) {
|
|
53
|
+
return { state: `${name}:有 proposal 缺 design.md`, next: `sillyspec run brainstorm --change ${name}(推进到设计产物)`, evidence: [ev('proposal.md')] }
|
|
54
|
+
}
|
|
55
|
+
if (!has('tasks.md')) {
|
|
56
|
+
return { state: `${name}:有 design 缺 tasks.md`, next: `sillyspec run brainstorm --change ${name}(末步生成规范文件)`, evidence: [ev('design.md')] }
|
|
57
|
+
}
|
|
58
|
+
if (!has('plan.md')) {
|
|
59
|
+
return { state: `${name}:有 tasks.md 缺 plan.md`, next: `sillyspec run plan --change ${name}`, evidence: [ev('tasks.md')] }
|
|
60
|
+
}
|
|
61
|
+
if (t.total > 0 && t.checked < t.total) {
|
|
62
|
+
return {
|
|
63
|
+
state: `${name}:执行中(task ${t.checked}/${t.total})`,
|
|
64
|
+
next: `sillyspec run execute --change ${name}`,
|
|
65
|
+
evidence: [ev('plan.md'), ev('tasks.md')],
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!has('verify-result.md')) {
|
|
69
|
+
return { state: `${name}:task 全勾,待验证`, next: `sillyspec run verify --change ${name}`, evidence: [ev('tasks.md'), ev('plan.md')] }
|
|
70
|
+
}
|
|
71
|
+
return { state: `${name}:已验证,待归档`, next: `sillyspec run archive --change ${name}`, evidence: [ev('verify-result.md')] }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 探测当前项目状态与下一步命令。
|
|
76
|
+
* @param {{ cwd: string, specDir?: string|null }} opts
|
|
77
|
+
* @returns {{ state: string, next: string, evidence: string[], activeChanges: Array<{name,checked,total}> }}
|
|
78
|
+
* activeChanges 附带各变更 task 勾选进度(供 --json 消费);evidence 为相对 spec 根的路径
|
|
79
|
+
*/
|
|
80
|
+
export function detectNextStep({ cwd, specDir = null }) {
|
|
81
|
+
const specBase = resolveSpecDir(cwd, { specDir })
|
|
82
|
+
const changesRoot = join(specBase, 'changes')
|
|
83
|
+
|
|
84
|
+
// 1. HANDOFF 交接
|
|
85
|
+
for (const handoff of [join(cwd, 'HANDOFF.json'), join(specBase, 'HANDOFF.json')]) {
|
|
86
|
+
if (existsSync(handoff)) {
|
|
87
|
+
return { state: '存在 HANDOFF.json(上次会话交接)', next: '读 HANDOFF.json 后从交接点继续(sillyspec progress show 看活跃进度)', evidence: [handoff], activeChanges: [] }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. 活跃变更(排除 archive)
|
|
92
|
+
let active = []
|
|
93
|
+
try {
|
|
94
|
+
active = readdirSync(changesRoot, { withFileTypes: true })
|
|
95
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
96
|
+
.map(e => e.name)
|
|
97
|
+
.sort()
|
|
98
|
+
} catch { /* 无 changes/ 目录 */ }
|
|
99
|
+
if (active.length > 0) {
|
|
100
|
+
// 多活跃时逐个推断全报告,next 取第一个(字典序最前)的;调用方输出含全部状态行
|
|
101
|
+
const inferences = active.map(name => inferChange(join(changesRoot, name), name))
|
|
102
|
+
const progress = active.map((name, i) => {
|
|
103
|
+
const t = countTasks(join(changesRoot, name, 'tasks.md'))
|
|
104
|
+
return { name, checked: t.checked, total: t.total, state: inferences[i].state }
|
|
105
|
+
})
|
|
106
|
+
return { state: inferences.map(i => i.state).join(';'), next: inferences[0].next, evidence: inferences[0].evidence, activeChanges: progress }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 3. 已扫描无变更
|
|
110
|
+
const docsDir = join(specBase, 'docs')
|
|
111
|
+
try {
|
|
112
|
+
const scanned = readdirSync(docsDir, { withFileTypes: true })
|
|
113
|
+
.filter(e => e.isDirectory() && existsSync(join(docsDir, e.name, 'scan')))
|
|
114
|
+
if (scanned.length > 0) {
|
|
115
|
+
return { state: `已扫描(${scanned.length} 个项目文档),无活跃变更`, next: 'sillyspec run brainstorm "你的想法"', evidence: [join('.sillyspec', 'docs', scanned[0].name, 'scan')], activeChanges: [] }
|
|
116
|
+
}
|
|
117
|
+
} catch { /* 无 docs/ */ }
|
|
118
|
+
|
|
119
|
+
// 4. 绿地已有需求
|
|
120
|
+
for (const f of ['REQUIREMENTS.md', 'ROADMAP.md']) {
|
|
121
|
+
if (existsSync(join(specBase, f))) {
|
|
122
|
+
return { state: `绿地项目已有 ${f},无活跃变更`, next: 'sillyspec run brainstorm "你的想法"', evidence: [join('.sillyspec', f)], activeChanges: [] }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 5. 什么都没有
|
|
127
|
+
return { state: '未初始化(无 .sillyspec 产物)', next: 'sillyspec init(新项目)或 sillyspec scan(棕地项目)', evidence: [], activeChanges: [] }
|
|
128
|
+
}
|