sillyspec 3.26.10 → 3.26.13

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.
Files changed (101) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +85 -85
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  3. package/.claude/skills/sillyspec-commit/SKILL.md +109 -109
  4. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  5. package/.claude/skills/sillyspec-doctor/SKILL.md +76 -76
  6. package/.claude/skills/sillyspec-execute/SKILL.md +200 -200
  7. package/.claude/skills/sillyspec-explore/SKILL.md +95 -95
  8. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  9. package/.claude/skills/sillyspec-plan/SKILL.md +148 -148
  10. package/.claude/skills/sillyspec-quick/SKILL.md +115 -111
  11. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  12. package/.claude/skills/sillyspec-scan/SKILL.md +79 -79
  13. package/.claude/skills/sillyspec-state/SKILL.md +64 -64
  14. package/.claude/skills/sillyspec-status/SKILL.md +54 -54
  15. package/.claude/skills/sillyspec-verify/SKILL.md +83 -83
  16. package/.claude/skills/sillyspec-workspace/SKILL.md +169 -169
  17. package/.gitattributes +2 -0
  18. package/.husky/pre-push +20 -20
  19. package/README.md +203 -201
  20. package/SKILL.md +91 -90
  21. package/package.json +39 -39
  22. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  23. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  24. package/packages/dashboard/dist/index.html +16 -16
  25. package/packages/dashboard/package.json +25 -25
  26. package/packages/dashboard/server/executor.js +95 -86
  27. package/packages/dashboard/server/index.js +663 -601
  28. package/packages/dashboard/server/parser.js +526 -526
  29. package/src/change-risk-profile.js +254 -254
  30. package/src/classify-change.js +109 -109
  31. package/src/config-schema.js +313 -310
  32. package/src/contract-matrix.js +388 -379
  33. package/src/db.js +370 -350
  34. package/src/dispatch/backends/local-agent.js +155 -155
  35. package/src/dispatch/backends/sillyhub-mcp.js +245 -245
  36. package/src/dispatch/probe.js +246 -210
  37. package/src/dispatch/strategy.js +121 -121
  38. package/src/docs-check.js +188 -16
  39. package/src/docs-debt.js +6 -2
  40. package/src/docs-gate.js +1 -0
  41. package/src/doctor-diagnostics.js +898 -711
  42. package/src/endpoint-extractor.js +401 -401
  43. package/src/fs-atomic.js +71 -69
  44. package/src/git-helper.js +82 -79
  45. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  46. package/src/hooks/worktree-guard.js +854 -848
  47. package/src/index.js +251 -20
  48. package/src/init.js +624 -623
  49. package/src/knowledge-match.js +130 -130
  50. package/src/local-detect.js +128 -120
  51. package/src/machine-interface.js +543 -543
  52. package/src/migrate.js +127 -127
  53. package/src/modules.js +7 -5
  54. package/src/progress/change-registry.js +297 -260
  55. package/src/progress/consistency-doctor.js +355 -355
  56. package/src/progress/shared.js +36 -36
  57. package/src/progress/stage-machine.js +589 -550
  58. package/src/progress/step-store.js +261 -261
  59. package/src/progress.js +1285 -1127
  60. package/src/quicklog.js +724 -693
  61. package/src/review-tier.js +77 -77
  62. package/src/run/command.js +1408 -1364
  63. package/src/run/complete-handlers.js +1450 -1399
  64. package/src/run/complete.js +1181 -920
  65. package/src/run/gates.js +878 -755
  66. package/src/run/prompt.js +765 -761
  67. package/src/run/quick-audit.js +155 -146
  68. package/src/run/shared.js +1348 -1215
  69. package/src/run/stage.js +527 -479
  70. package/src/scan-postcheck.js +371 -371
  71. package/src/setup.js +414 -416
  72. package/src/sillyhub-mcp/client.js +597 -529
  73. package/src/spec-dir-typo.js +3 -24
  74. package/src/spec-sync.js +358 -319
  75. package/src/stage-contract.js +871 -897
  76. package/src/stage-review.js +549 -549
  77. package/src/stages/archive.js +156 -155
  78. package/src/stages/brainstorm-auto.js +236 -236
  79. package/src/stages/brainstorm.js +597 -597
  80. package/src/stages/cmd-existence.js +145 -145
  81. package/src/stages/doctor.js +386 -371
  82. package/src/stages/execute.js +1014 -1003
  83. package/src/stages/explore.js +34 -34
  84. package/src/stages/index.js +29 -29
  85. package/src/stages/knowledge.js +498 -498
  86. package/src/stages/plan-postcheck.js +1323 -1302
  87. package/src/stages/plan.js +642 -623
  88. package/src/stages/quick.js +153 -151
  89. package/src/stages/scan.js +607 -607
  90. package/src/stages/status.js +64 -64
  91. package/src/stages/verify.js +264 -264
  92. package/src/sync.js +1325 -1135
  93. package/src/task-review.js +1204 -1019
  94. package/src/taskcard.js +178 -0
  95. package/src/verify-postcheck.js +1199 -1186
  96. package/src/workflow.js +600 -600
  97. package/src/worktree-apply.js +1237 -1136
  98. package/src/worktree-deps.js +453 -353
  99. package/src/worktree.js +1527 -1481
  100. package/templates/workflows/archive-impact.yaml +79 -79
  101. package/templates/workflows/scan-docs.yaml +132 -132
@@ -1,310 +1,313 @@
1
- /**
2
- * config-schema.js — local.yaml 配置键的单一数据源。
3
- *
4
- * 定位:`.sillyspec/local.yaml` 是 gitignored 的机器+人混写配置,键散落在 ~10 个 reader
5
- * 里(detect / verify-postcheck / sync / dispatch / worktree-guard / classify-change …),
6
- * 外部项目的 agent 无从得知有哪些键、哪些生效。本模块把「全部已知键 + 生效状态 + 读取点」
7
- * 集中成一张表,供:
8
- * - `sillyspec config schema`(人类可读树 / --json 机读)打印;
9
- * - `sillyspec init` 调 renderExample() 落盘脱敏 local.yaml.example 给人/外部 agent 看。
10
- *
11
- * 单一真相 = LOCAL_YAML_SCHEMA。renderExample() 是策展模板(YAML 形态各异:嵌套 / inline
12
- * flow / 顶层标量,自动生成不如策展可读),由测试「每个 live 键路径必出现于 example 文本」
13
- * 耦合保证不漂移——加键忘 example 则测试红。
14
- *
15
- * reader 引用一律用符号名(readMcpConfig / extractModules),**不用行号**——行号随重构漂移,
16
- * 符号名稳定可 grep。
17
- *
18
- * status 语义:
19
- * - live 真有 reader 从 local.yaml 读并起效(配了即生效)。
20
- * - declared 代码/JSDoc/prompt 声明过,但无 reader 真读 local.yaml(或调用方未传),配了不生效。
21
- * 诚实标出,避免 agent 白配 + 暴露待接线债。
22
- */
23
-
24
- /**
25
- * @typedef {Object} SchemaKey
26
- * @property {string} path 点分路径(mcp.url / worktree-hook.readonlyCommands)
27
- * @property {string} type string|enum|integer|object|array|scalar
28
- * @property {string[]} [values] enum 取值
29
- * @property {boolean} [required] 凭据类必填(两键齐全才视为有效源)
30
- * @property {boolean} [secret] 敏感凭据,example 用占位符
31
- * @property {boolean} [optional] 可选键
32
- * @property {string} status live|declared
33
- * @property {string[]} readers 读取点符号名(稳定可 grep),declared 键留空数组
34
- * @property {string} desc 一句话说明
35
- * @property {string} example 脱敏示例值(secret 键用占位符)
36
- *
37
- * @typedef {Object} SchemaSection
38
- * @property {string} id
39
- * @property {string} title
40
- * @property {string} note 段说明(写入来源 / 注意事项)
41
- * @property {SchemaKey[]} keys
42
- */
43
-
44
- /** @type {{ file: string, note: string, sections: SchemaSection[] }} */
45
- export const LOCAL_YAML_SCHEMA = {
46
- file: '.sillyspec/local.yaml',
47
- note: 'gitignored(含敏感凭据勿提交);脱敏示例 local.yaml.example 由 sillyspec init 生成(可提交)。',
48
- sections: [
49
- {
50
- id: 'project',
51
- title: '项目类型',
52
- note: '由 sillyspec local detect 纯 fs 嗅探写入,一般无需手填。',
53
- keys: [
54
- {
55
- path: 'project.type',
56
- type: 'enum',
57
- values: ['nodejs', 'maven', 'gradle', 'make', 'generic'],
58
- status: 'live',
59
- readers: ['detectLocalYaml (src/local-detect.js)', 'extractProjectType (src/worktree-deps.js)'],
60
- desc: '项目类型,决定默认 commands 嗅探与 worktree 依赖策略。',
61
- example: 'nodejs',
62
- },
63
- ],
64
- },
65
- {
66
- id: 'commands',
67
- title: '构建/测试/lint 命令',
68
- note: 'detect 核验 package.json(或对应构建文件) scripts 存在性后才写对应键;缺失则不写。agent 可改。',
69
- keys: [
70
- { path: 'commands.build', type: 'string', optional: true, status: 'live', readers: ['detectLocalYaml (src/local-detect.js)', 'validateCommands (src/scan-postcheck.js)'], desc: '构建命令。', example: 'npm run build' },
71
- { path: 'commands.test', type: 'string', optional: true, status: 'live', readers: ['extractTestCommand (src/verify-postcheck.js)', 'validateCommands (src/scan-postcheck.js)'], desc: '测试命令——verify 阶段 CLI 亲自执行此命令与 verify-result.md 对账,实测失败即阻断。', example: 'npm test' },
72
- { path: 'commands.lint', type: 'string', optional: true, status: 'live', readers: ['detectLocalYaml (src/local-detect.js)', 'validateCommands (src/scan-postcheck.js)'], desc: 'lint 命令。', example: 'npm run lint' },
73
- { path: 'commands.install', type: 'string', optional: true, status: 'live', readers: ['extractInstallCommand (src/worktree-deps.js)'], desc: '依赖安装命令(worktree 依赖 provisioning 用)。', example: 'npm install' },
74
- ],
75
- },
76
- {
77
- id: 'mcp',
78
- title: 'SillyHub MCP 客户端凭据(派发用)',
79
- note: 'agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填(§7.4)。两键齐全才生效,否则回退 env SILLYHUB_MCP_URL/TOKEN。',
80
- keys: [
81
- { path: 'mcp.url', type: 'string', required: true, secret: true, status: 'live', readers: ['readMcpConfig (src/sillyhub-mcp/config.js)'], desc: 'SillyHub MCP 服务地址(尾斜杠自动归一)。', example: 'https://your-sillyhub.example.com' },
82
- { path: 'mcp.token', type: 'string', required: true, secret: true, status: 'live', readers: ['readMcpConfig (src/sillyhub-mcp/config.js)'], desc: 'SillyHub MCP 认证 token。', example: '<your-mcp-token>' },
83
- ],
84
- },
85
- {
86
- id: 'platform',
87
- title: 'SillyHub 平台同步',
88
- note: 'sillyspec platform connect 写入(含 ping 验活);手填亦可。user/last_connected 可空或自动写。',
89
- keys: [
90
- { path: 'platform.url', type: 'string', required: true, secret: true, status: 'live', readers: ['SyncManager._getPlatform (src/sync.js)'], desc: 'SillyHub 平台地址。', example: 'https://your-sillyhub.example.com' },
91
- { path: 'platform.token', type: 'string', required: true, secret: true, status: 'live', readers: ['SyncManager._getPlatform (src/sync.js)'], desc: '平台认证 token(Bearer)。', example: '<your-platform-token>' },
92
- { path: 'platform.user', type: 'string', optional: true, status: 'live', readers: ['resolvePlatformUser / SyncManager.connect (src/sync.js)'], desc: '推送者身份;缺则回退 git user.name / env。推送时进 X-SillySpec-User header。', example: 'alice' },
93
- { path: 'platform.last_connected', type: 'string', optional: true, status: 'live', readers: ['SyncManager.connect (src/sync.js)'], desc: 'connect 成功时间戳(ISO),自动写,勿手填。', example: '2026-08-11T00:00:00.000Z' },
94
- ],
95
- },
96
- {
97
- id: 'dispatch',
98
- title: '派发调参',
99
- note: '仅在确知调优值时填,否则留默认。',
100
- keys: [
101
- { path: 'dispatch.probe_ttl_ms', type: 'integer', optional: true, status: 'live', readers: ['readProbeTtlFromLocalYaml (src/dispatch/probe.js)'], desc: '派发探测负面缓存 TTL(毫秒,默认 60000)。daemon 抖动期避免反复探测。', example: '60000' },
102
- { path: 'dispatch.poll_interval_ms', type: 'integer', optional: true, status: 'declared', readers: [], desc: '【路径A 预留·未落地】renderSillyHubInstruction 注入的轮询文本提及,但 isPathASupported()=false 该指令当前不注入;路径A 落地后接线,配了暂不生效。', example: '15000' },
103
- { path: 'dispatch.worker_timeout_ms', type: 'integer', optional: true, status: 'declared', readers: [], desc: '【路径A 预留·未落地】同 poll_interval_ms,路径A 落地后接线,配了暂不生效。', example: '60000' },
104
- ],
105
- },
106
- {
107
- id: 'modules',
108
- title: 'monorepo 子模块映射',
109
- note: 'test_strategy: module 时,按 git diff 命中的模块子集收窄测试。只支持 inline flow 形态(extractModules 不解析嵌套展开式)。',
110
- keys: [
111
- { path: 'modules.<name>.path', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)', 'parseLocalYamlModules (src/plan-postcheck.js)', 'extractModulePaths (src/worktree-deps.js)'], desc: '子模块目录路径(相对仓库根)。', example: 'frontend/' },
112
- { path: 'modules.<name>.test', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)'], desc: '该子模块的测试命令。', example: 'cd frontend && pnpm test' },
113
- ],
114
- },
115
- {
116
- id: 'test_strategy',
117
- title: '测试策略',
118
- note: 'verify 阶段 CLI 对账的收窄策略。',
119
- keys: [
120
- { path: 'test_strategy', type: 'enum', values: ['full', 'module'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)'], desc: 'full=全量 commands.test;module=按 git diff 命中 modules 子集收窄(需配 modules)。缺省=全量。', example: 'full' },
121
- ],
122
- },
123
- {
124
- id: 'known_failures',
125
- title: '预存失败豁免清单',
126
- note: '变更前就失败的测试行,豁免后不计入 verify 阻断。支持块列表或 inline 数组。务必定期复核,避免误豁免本变更引入的真实失败。',
127
- keys: [
128
- { path: 'known_failures', type: 'array', optional: true, status: 'live', readers: ['extractKnownFailures (src/verify-postcheck.js)'], desc: '失败行模式列表(匹配测试输出失败行)。', example: 'tests/some-old-test' },
129
- ],
130
- },
131
- {
132
- id: 'worktree-hook',
133
- title: 'worktree-guard 扩展',
134
- note: 'execute worktree 隔离期内放行的额外只读命令。键名用 camelCase(parseSimpleYaml 原样保留)。',
135
- keys: [
136
- { path: 'worktree-hook.readonlyCommands', type: 'array', optional: true, status: 'live', readers: ['loadLocalConfig (src/hooks/worktree-guard.js)'], desc: '额外放行的只读命令名列表(如 rg/fd),绕过 worktree-guard 写操作拦截。', example: 'rg' },
137
- ],
138
- },
139
- {
140
- id: 'auto_mode',
141
- title: '变更规模自动分类',
142
- note: 'sillyspec run auto 时,readAutoModeFromLocalYaml 读本段传 classifyChange 的 localConfig,force_*_patterns 匹配需求描述则强制对应模式。',
143
- keys: [
144
- { path: 'auto_mode.force_full_patterns', type: 'array', optional: true, status: 'live', readers: ['readAutoModeFromLocalYaml + classifyChange (src/classify-change.js)', 'runCommand (src/run/command.js)'], desc: '需求描述匹配任一正则(i 大小写无关)→ 强制 full 流程。非法正则跳过不崩。', example: '数据库|migration' },
145
- { path: 'auto_mode.force_quick_patterns', type: 'array', optional: true, status: 'live', readers: ['readAutoModeFromLocalYaml + classifyChange (src/classify-change.js)', 'runCommand (src/run/command.js)'], desc: '需求描述匹配任一正则 → 强制 quick。非法正则跳过不崩。', example: 'fix typo' },
146
- ],
147
- },
148
- {
149
- id: 'docs-check',
150
- title: '文档引用校验',
151
- note: 'sillyspec docs check 的扫描与断言配置(2026-08-15 docs-check-productize)。glob 相对源码仓根展开,仅支持 目录/**/*.扩展、目录/*.扩展、字面路径 三形态。',
152
- keys: [
153
- { path: 'docs-check.paths', type: 'array', optional: true, status: 'live', readers: ['docs check (src/index.js case docs) + runDocsCheck (src/docs-check.js)'], desc: '扫描的文档 glob 列表,缺省 docs/**/*.md 与 .sillyspec/docs/**/*.md(scan/modules 产物同纳入)。', example: 'docs/**/*.md' },
154
- { path: 'docs-check.skip', type: 'array', optional: true, status: 'live', readers: ['walkGlob (src/docs-check.js)'], desc: '排除的路径/glob 列表,缺省空。', example: 'docs/sillyspec/archive' },
155
- { path: 'docs-check.keywordAssert', type: 'boolean', optional: true, status: 'live', readers: ['runDocsCheck (src/docs-check.js)'], desc: '层2 关键词断言开关,缺省 true(关闭时 warning 提示仅做存在性校验)。', example: 'true' },
156
- { path: 'docs-check.living-docs', type: 'array', optional: true, status: 'live', readers: ['resolveLivingDocs (src/run/shared.js) — 经 readLocalYamlRaw 直读'], desc: '活文档监控点追加列表(docs check 之外,quick 审计的 livingDocDrift 提示也监控)。只追加不覆盖缺省集合(缺省:docs/sillyspec/platform-interface-map.md)。配了是加哨兵,不该把缺省监控点挤掉。', example: 'docs/sillyspec/architecture.md' },
157
- ],
158
- },
159
- ],
160
- };
161
-
162
- /** 全部键(拍平),便于 JSON 输出与 example 耦合测试。 */
163
- export function flatKeys() {
164
- return LOCAL_YAML_SCHEMA.sections.flatMap((s) => s.keys.map((k) => ({ section: s.id, ...k })));
165
- }
166
-
167
- /** 人类可读树形 schema 文本。live 在前、declared 在后,段内逐键列 path/type/约束/读取点/说明。 */
168
- export function renderSchemaHuman() {
169
- const L = [];
170
- L.push('SillySpec local.yaml 配置清单');
171
- L.push(`文件:${LOCAL_YAML_SCHEMA.file} — ${LOCAL_YAML_SCHEMA.note}`);
172
- L.push('数据源:src/config-schema.js(本表唯一真相;reader 见各键「读取点」,符号名可 grep)');
173
- L.push('');
174
-
175
- const all = flatKeys();
176
- const live = all.filter((k) => k.status === 'live');
177
- const declared = all.filter((k) => k.status === 'declared');
178
-
179
- L.push(`━━━ 生效(${live.length} 键,配了即生效) ━━━`);
180
- L.push('');
181
- for (const s of LOCAL_YAML_SCHEMA.sections) {
182
- const keys = s.keys.filter((k) => k.status === 'live');
183
- if (keys.length === 0) continue;
184
- L.push(`[${s.id}] ${s.title}`);
185
- L.push(` ${s.note}`);
186
- for (const k of keys) {
187
- L.push(` ${k.path}${formatConstraint(k)} ${k.desc}`);
188
- if (k.readers.length) L.push(` 读取点:${k.readers.join(';')}`);
189
- }
190
- L.push('');
191
- }
192
-
193
- L.push(`━━━ 声明但未接线(${declared.length} 键,配了不生效 / 待接线债) ━━━`);
194
- L.push('');
195
- for (const s of LOCAL_YAML_SCHEMA.sections) {
196
- const keys = s.keys.filter((k) => k.status === 'declared');
197
- if (keys.length === 0) continue;
198
- L.push(`[${s.id}] ${s.title}`);
199
- L.push(` ${s.note}`);
200
- for (const k of keys) {
201
- L.push(` ${k.path}${formatConstraint(k)} ${k.desc}`);
202
- }
203
- L.push('');
204
- }
205
- return L.join('\n');
206
- }
207
-
208
- function formatConstraint(k) {
209
- const tags = [];
210
- if (k.required) tags.push('必填');
211
- if (k.optional) tags.push('可选');
212
- if (k.secret) tags.push('凭据');
213
- if (k.type === 'enum' && k.values) tags.push(k.values.join('|'));
214
- else tags.push(k.type);
215
- return tags.length ? ` [${tags.join(' · ')}]` : '';
216
- }
217
-
218
- /** 机读 JSON(程序化消费)。结构 = LOCAL_YAML_SCHEMA 本体 + flatKeys 视图。 */
219
- export function renderSchemaJson() {
220
- return JSON.stringify({
221
- file: LOCAL_YAML_SCHEMA.file,
222
- note: LOCAL_YAML_SCHEMA.note,
223
- sections: LOCAL_YAML_SCHEMA.sections,
224
- keys: flatKeys().map(({ section, path, type, values, required, optional, secret, status, readers, desc }) => ({
225
- section, path, type, values, required, optional, secret, status, readers, desc,
226
- })),
227
- }, null, 2);
228
- }
229
-
230
- /**
231
- * 脱敏 local.yaml 示例文本(sillyspec init 落盘用)。
232
- * 策展模板(非自动生成):YAML 形态各异(嵌套/inline flow/顶层标量),策展可读性远胜自动拼。
233
- * 与 SCHEMA 的耦合由测试保证:每个 live 键路径必出现于本文本。
234
- */
235
- export function renderExample() {
236
- return `# SillySpec local.yaml — 脱敏示例(由 sillyspec init 生成,可提交)
237
- # 真实配置文件 .sillyspec/local.yaml 是 gitignored(含敏感凭据)。
238
- # 用法:复制本文件为 .sillyspec/local.yaml,填真实值;或直接 sillyspec platform connect 自动写凭据段。
239
- # 全部键与生效状态:sillyspec config schema
240
-
241
- # ── 项目类型(sillyspec local detect 自动写入,一般无需手填)──
242
- project:
243
- type: nodejs # nodejs | maven | gradle | make | generic
244
-
245
- # ── 构建/测试/lint 命令(detect 核验 scripts 存在性后才写;缺失键省略)──
246
- commands:
247
- build: npm run build
248
- test: npm test
249
- lint: npm run lint
250
- # install: npm install # worktree 依赖安装命令
251
-
252
- # ── SillyHub MCP 客户端凭据(派发到 worker 用)──
253
- # agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填。
254
- # 两键齐全才生效;缺则回退环境变量 SILLYHUB_MCP_URL / SILLYHUB_MCP_TOKEN。
255
- mcp:
256
- url: https://your-sillyhub.example.com
257
- token: <your-mcp-token>
258
-
259
- # ── SillyHub 平台同步(sillyspec platform connect 写入,含 ping 验活)──
260
- platform:
261
- url: https://your-sillyhub.example.com
262
- token: <your-platform-token>
263
- user: alice # 推送者身份(可空,回退 git user.name / env)
264
- # last_connected: 2026-08-11T00:00:00.000Z # connect 自动写,勿手填
265
-
266
- # ── 派发调参(仅确知调优值时填,否则留默认)──
267
- dispatch:
268
- probe_ttl_ms: 60000 # 派发探测负面缓存 TTL(默认 60000)
269
- # poll_interval_ms: 15000 # ⚠ 路径A 预留·未落地,配了暂不生效
270
- # worker_timeout_ms: 60000 # ⚠ 路径A 预留·未落地,配了暂不生效
271
-
272
- # ── monorepo 子模块映射(test_strategy: module 时按 git diff 命中模块收窄测试)──
273
- # 只支持 inline flow 形态(嵌套展开式解析不出):
274
- modules:
275
- frontend: { path: "frontend/", test: "cd frontend && pnpm test" }
276
- backend: { path: "backend/", test: "cd backend && npm test" }
277
-
278
- # ── 测试策略:full=全量 commands.test | module=按命中模块收窄(需配 modules)──
279
- test_strategy: full
280
-
281
- # ── 预存失败豁免清单(变更前就失败的测试行;务必定期复核,防误豁免真实失败)──
282
- known_failures:
283
- - tests/some-old-stable-failure
284
-
285
- # ── worktree-guard 只读命令扩展(execute worktree 隔离期内放行额外只读命令)──
286
- worktree-hook:
287
- readonlyCommands:
288
- - rg
289
- - fd
290
-
291
- # ── 变更规模自动分类(sillyspec run auto 时按需求描述强制 quick/full;正则数组,i 大小写无关)──
292
- auto_mode:
293
- force_full_patterns:
294
- - 数据库|migration
295
- force_quick_patterns:
296
- - fix typo
297
-
298
- # ── 文档引用校验(sillyspec docs check;glob 相对源码仓根,三形态:递归/单层/字面路径)──
299
- # 缺省已扫 docs/**/*.md 与 .sillyspec/docs/**/*.md;本段仅在需要收窄/排除时配置
300
- docs-check:
301
- paths:
302
- - docs/**/*.md
303
- - .sillyspec/docs/**/*.md
304
- # skip:
305
- # - docs/sillyspec/archive
306
- keywordAssert: true
307
- # living-docs: # 活文档监控点追加(缺省已含 docs/sillyspec/platform-interface-map.md,只加哨兵不覆盖)
308
- # - docs/sillyspec/architecture.md
309
- `;
310
- }
1
+ /**
2
+ * config-schema.js — local.yaml 配置键的单一数据源。
3
+ *
4
+ * 定位:`.sillyspec/local.yaml` 是 gitignored 的机器+人混写配置,键散落在 ~10 个 reader
5
+ * 里(detect / verify-postcheck / sync / dispatch / worktree-guard / classify-change …),
6
+ * 外部项目的 agent 无从得知有哪些键、哪些生效。本模块把「全部已知键 + 生效状态 + 读取点」
7
+ * 集中成一张表,供:
8
+ * - `sillyspec config schema`(人类可读树 / --json 机读)打印;
9
+ * - `sillyspec init` 调 renderExample() 落盘脱敏 local.yaml.example 给人/外部 agent 看。
10
+ *
11
+ * 单一真相 = LOCAL_YAML_SCHEMA。renderExample() 是策展模板(YAML 形态各异:嵌套 / inline
12
+ * flow / 顶层标量,自动生成不如策展可读),由测试「每个 live 键路径必出现于 example 文本」
13
+ * 耦合保证不漂移——加键忘 example 则测试红。
14
+ *
15
+ * reader 引用一律用符号名(readMcpConfig / extractModules),**不用行号**——行号随重构漂移,
16
+ * 符号名稳定可 grep。
17
+ *
18
+ * status 语义:
19
+ * - live 真有 reader 从 local.yaml 读并起效(配了即生效)。
20
+ * - declared 代码/JSDoc/prompt 声明过,但无 reader 真读 local.yaml(或调用方未传),配了不生效。
21
+ * 诚实标出,避免 agent 白配 + 暴露待接线债。
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} SchemaKey
26
+ * @property {string} path 点分路径(mcp.url / worktree-hook.readonlyCommands)
27
+ * @property {string} type string|enum|integer|object|array|scalar
28
+ * @property {string[]} [values] enum 取值
29
+ * @property {boolean} [required] 凭据类必填(两键齐全才视为有效源)
30
+ * @property {boolean} [secret] 敏感凭据,example 用占位符
31
+ * @property {boolean} [optional] 可选键
32
+ * @property {string} status live|declared
33
+ * @property {string[]} readers 读取点符号名(稳定可 grep),declared 键留空数组
34
+ * @property {string} desc 一句话说明
35
+ * @property {string} example 脱敏示例值(secret 键用占位符)
36
+ *
37
+ * @typedef {Object} SchemaSection
38
+ * @property {string} id
39
+ * @property {string} title
40
+ * @property {string} note 段说明(写入来源 / 注意事项)
41
+ * @property {SchemaKey[]} keys
42
+ */
43
+
44
+ /** @type {{ file: string, note: string, sections: SchemaSection[] }} */
45
+ export const LOCAL_YAML_SCHEMA = {
46
+ file: '.sillyspec/local.yaml',
47
+ note: 'gitignored(含敏感凭据勿提交);脱敏示例 local.yaml.example 由 sillyspec init 生成(可提交)。',
48
+ sections: [
49
+ {
50
+ id: 'project',
51
+ title: '项目类型',
52
+ note: '由 sillyspec local detect 纯 fs 嗅探写入,一般无需手填。',
53
+ keys: [
54
+ {
55
+ path: 'project.type',
56
+ type: 'enum',
57
+ values: ['nodejs', 'maven', 'gradle', 'make', 'generic'],
58
+ status: 'live',
59
+ readers: ['detectLocalYaml (src/local-detect.js)', 'extractProjectType (src/worktree-deps.js)'],
60
+ desc: '项目类型,决定默认 commands 嗅探与 worktree 依赖策略。',
61
+ example: 'nodejs',
62
+ },
63
+ ],
64
+ },
65
+ {
66
+ id: 'commands',
67
+ title: '构建/测试/lint 命令',
68
+ note: 'detect 核验 package.json(或对应构建文件) scripts 存在性后才写对应键;缺失则不写。agent 可改。',
69
+ keys: [
70
+ { path: 'commands.build', type: 'string', optional: true, status: 'live', readers: ['detectLocalYaml (src/local-detect.js)', 'validateCommands (src/scan-postcheck.js)'], desc: '构建命令。', example: 'npm run build' },
71
+ { path: 'commands.test', type: 'string', optional: true, status: 'live', readers: ['extractTestCommand (src/verify-postcheck.js)', 'validateCommands (src/scan-postcheck.js)'], desc: '测试命令——verify 阶段 CLI 亲自执行此命令与 verify-result.md 对账,实测失败即阻断。', example: 'npm test' },
72
+ { path: 'commands.lint', type: 'string', optional: true, status: 'live', readers: ['detectLocalYaml (src/local-detect.js)', 'validateCommands (src/scan-postcheck.js)'], desc: 'lint 命令。', example: 'npm run lint' },
73
+ { path: 'commands.install', type: 'string', optional: true, status: 'live', readers: ['extractInstallCommand (src/worktree-deps.js)'], desc: '依赖安装命令(worktree 依赖 provisioning 用)。', example: 'npm install' },
74
+ ],
75
+ },
76
+ {
77
+ id: 'mcp',
78
+ title: 'SillyHub MCP 客户端凭据(派发用)',
79
+ note: 'agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填(§7.4)。两键齐全才生效,否则回退 env SILLYHUB_MCP_URL/TOKEN。',
80
+ keys: [
81
+ { path: 'mcp.url', type: 'string', required: true, secret: true, status: 'live', readers: ['readMcpConfig (src/sillyhub-mcp/config.js)'], desc: 'SillyHub MCP 服务地址(尾斜杠自动归一)。', example: 'https://your-sillyhub.example.com' },
82
+ { path: 'mcp.token', type: 'string', required: true, secret: true, status: 'live', readers: ['readMcpConfig (src/sillyhub-mcp/config.js)'], desc: 'SillyHub MCP 认证 token。', example: '<your-mcp-token>' },
83
+ ],
84
+ },
85
+ {
86
+ id: 'platform',
87
+ title: 'SillyHub 平台同步',
88
+ note: 'sillyspec platform connect 写入(含 ping 验活);手填亦可。user/last_connected 可空或自动写。',
89
+ keys: [
90
+ { path: 'platform.url', type: 'string', required: true, secret: true, status: 'live', readers: ['SyncManager._getPlatform (src/sync.js)'], desc: 'SillyHub 平台地址。', example: 'https://your-sillyhub.example.com' },
91
+ { path: 'platform.token', type: 'string', required: true, secret: true, status: 'live', readers: ['SyncManager._getPlatform (src/sync.js)'], desc: '平台认证 token(Bearer)。', example: '<your-platform-token>' },
92
+ { path: 'platform.user', type: 'string', optional: true, status: 'live', readers: ['resolvePlatformUser / SyncManager.connect (src/sync.js)'], desc: '推送者身份;缺则回退 git user.name / env。推送时进 X-SillySpec-User header。', example: 'alice' },
93
+ { path: 'platform.last_connected', type: 'string', optional: true, status: 'live', readers: ['SyncManager.connect (src/sync.js)'], desc: 'connect 成功时间戳(ISO),自动写,勿手填。', example: '2026-08-11T00:00:00.000Z' },
94
+ ],
95
+ },
96
+ {
97
+ id: 'dispatch',
98
+ title: '派发调参',
99
+ note: '仅在确知调优值时填,否则留默认。',
100
+ keys: [
101
+ { path: 'dispatch.probe_ttl_ms', type: 'integer', optional: true, status: 'live', readers: ['readProbeTtlFromLocalYaml (src/dispatch/probe.js)'], desc: '派发探测负面缓存 TTL(毫秒,默认 60000)。daemon 抖动期避免反复探测。', example: '60000' },
102
+ { path: 'dispatch.poll_interval_ms', type: 'integer', optional: true, status: 'declared', readers: [], desc: '【路径A 预留·未落地】renderSillyHubInstruction 注入的轮询文本提及,但 isPathASupported()=false 该指令当前不注入;路径A 落地后接线,配了暂不生效。', example: '15000' },
103
+ { path: 'dispatch.worker_timeout_ms', type: 'integer', optional: true, status: 'declared', readers: [], desc: '【路径A 预留·未落地】同 poll_interval_ms,路径A 落地后接线,配了暂不生效。', example: '60000' },
104
+ ],
105
+ },
106
+ {
107
+ id: 'modules',
108
+ title: 'monorepo 子模块映射',
109
+ note: 'test_strategy: module 时,按 git diff 命中的模块子集收窄测试。只支持 inline flow 形态(extractModules 不解析嵌套展开式)。',
110
+ keys: [
111
+ { path: 'modules.<name>.path', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)', 'parseLocalYamlModules (src/plan-postcheck.js)', 'extractModulePaths (src/worktree-deps.js)'], desc: '子模块目录路径(相对仓库根)。', example: 'frontend/' },
112
+ { path: 'modules.<name>.test', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)'], desc: '该子模块的测试命令。', example: 'cd frontend && pnpm test' },
113
+ ],
114
+ },
115
+ {
116
+ id: 'test_strategy',
117
+ title: '测试策略',
118
+ note: 'verify 阶段 CLI 对账的收窄策略。',
119
+ keys: [
120
+ { path: 'test_strategy', type: 'enum', values: ['full', 'module'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)'], desc: 'full=全量 commands.test;module=按 git diff 命中 modules 子集收窄(需配 modules)。缺省=全量。', example: 'full' },
121
+ ],
122
+ },
123
+ {
124
+ id: 'known_failures',
125
+ title: '预存失败豁免清单',
126
+ note: '变更前就失败的测试行,豁免后不计入 verify 阻断。支持块列表或 inline 数组。务必定期复核,避免误豁免本变更引入的真实失败。',
127
+ keys: [
128
+ { path: 'known_failures', type: 'array', optional: true, status: 'live', readers: ['extractKnownFailures (src/verify-postcheck.js)'], desc: '失败行模式列表(匹配测试输出失败行)。', example: 'tests/some-old-test' },
129
+ ],
130
+ },
131
+ {
132
+ id: 'worktree-hook',
133
+ title: 'worktree-guard 扩展',
134
+ note: 'execute worktree 隔离期内放行的额外只读命令。键名用 camelCase(parseSimpleYaml 原样保留)。',
135
+ keys: [
136
+ { path: 'worktree-hook.readonlyCommands', type: 'array', optional: true, status: 'live', readers: ['loadLocalConfig (src/hooks/worktree-guard.js)'], desc: '额外放行的只读命令名列表(如 rg/fd),绕过 worktree-guard 写操作拦截。', example: 'rg' },
137
+ ],
138
+ },
139
+ {
140
+ id: 'auto_mode',
141
+ title: '变更规模自动分类',
142
+ note: 'sillyspec run auto 时,readAutoModeFromLocalYaml 读本段传 classifyChange 的 localConfig,force_*_patterns 匹配需求描述则强制对应模式。',
143
+ keys: [
144
+ { path: 'auto_mode.force_full_patterns', type: 'array', optional: true, status: 'live', readers: ['readAutoModeFromLocalYaml + classifyChange (src/classify-change.js)', 'runCommand (src/run/command.js)'], desc: '需求描述匹配任一正则(i 大小写无关)→ 强制 full 流程。非法正则跳过不崩。', example: '数据库|migration' },
145
+ { path: 'auto_mode.force_quick_patterns', type: 'array', optional: true, status: 'live', readers: ['readAutoModeFromLocalYaml + classifyChange (src/classify-change.js)', 'runCommand (src/run/command.js)'], desc: '需求描述匹配任一正则 → 强制 quick。非法正则跳过不崩。', example: 'fix typo' },
146
+ ],
147
+ },
148
+ {
149
+ id: 'docs-check',
150
+ title: '文档引用校验',
151
+ note: 'sillyspec docs check 的扫描与断言配置(2026-08-15 docs-check-productize)。glob 相对源码仓根展开,仅支持 目录/**/*.扩展、目录/*.扩展、字面路径 三形态。',
152
+ keys: [
153
+ { path: 'docs-check.paths', type: 'array', optional: true, status: 'live', readers: ['docs check (src/index.js case docs) + runDocsCheck (src/docs-check.js)'], desc: '扫描的文档 glob 列表,缺省 docs/**/*.md 与 .sillyspec/docs/**/*.md(scan/modules 产物同纳入)。', example: 'docs/**/*.md' },
154
+ { path: 'docs-check.skip', type: 'array', optional: true, status: 'live', readers: ['walkGlob (src/docs-check.js)'], desc: '排除的路径/glob 列表,缺省空。', example: 'docs/sillyspec/archive' },
155
+ { path: 'docs-check.keywordAssert', type: 'boolean', optional: true, status: 'live', readers: ['runDocsCheck (src/docs-check.js)'], desc: '层2 关键词断言开关,缺省 true(关闭时 warning 提示仅做存在性校验)。', example: 'true' },
156
+ { path: 'docs-check.living-docs', type: 'array', optional: true, status: 'live', readers: ['resolveLivingDocs (src/run/shared.js) — 经 readLocalYamlRaw 直读'], desc: '活文档监控点追加列表(docs check 之外,quick 审计的 livingDocDrift 真失效提示也监控——只报校验真失败的引用,全过零输出)。只追加不覆盖缺省集合(缺省:docs/sillyspec/platform-interface-map.md)。配了是加哨兵,不该把缺省监控点挤掉。', example: 'docs/sillyspec/architecture.md' },
157
+ { path: 'docs-check.cross_repo_roots', type: 'object', optional: true, status: 'live', readers: ['readDocsCheckConfig (src/docs-check.js)'], desc: 'repo://<仓库名> 跨仓引用 → 本机仓库根目录映射。文档里 repo://name/src/x.js:12 形态的引用:未配映射默认跳过(不同设备仓库位置不同,防跨设备误报);配了映射走与本地引用相同的行号+关键词校验。local.yaml 是 gitignored,绝对路径不入库,每台设备各自配。', example: 'sillyspec: /c/Users/you/IdeaProjects/sillyspec' },
158
+ ],
159
+ },
160
+ ],
161
+ };
162
+
163
+ /** 全部键(拍平),便于 JSON 输出与 example 耦合测试。 */
164
+ export function flatKeys() {
165
+ return LOCAL_YAML_SCHEMA.sections.flatMap((s) => s.keys.map((k) => ({ section: s.id, ...k })));
166
+ }
167
+
168
+ /** 人类可读树形 schema 文本。live 在前、declared 在后,段内逐键列 path/type/约束/读取点/说明。 */
169
+ export function renderSchemaHuman() {
170
+ const L = [];
171
+ L.push('SillySpec local.yaml 配置清单');
172
+ L.push(`文件:${LOCAL_YAML_SCHEMA.file} — ${LOCAL_YAML_SCHEMA.note}`);
173
+ L.push('数据源:src/config-schema.js(本表唯一真相;reader 见各键「读取点」,符号名可 grep)');
174
+ L.push('');
175
+
176
+ const all = flatKeys();
177
+ const live = all.filter((k) => k.status === 'live');
178
+ const declared = all.filter((k) => k.status === 'declared');
179
+
180
+ L.push(`━━━ 生效(${live.length} 键,配了即生效) ━━━`);
181
+ L.push('');
182
+ for (const s of LOCAL_YAML_SCHEMA.sections) {
183
+ const keys = s.keys.filter((k) => k.status === 'live');
184
+ if (keys.length === 0) continue;
185
+ L.push(`[${s.id}] ${s.title}`);
186
+ L.push(` ${s.note}`);
187
+ for (const k of keys) {
188
+ L.push(` ${k.path}${formatConstraint(k)} ${k.desc}`);
189
+ if (k.readers.length) L.push(` 读取点:${k.readers.join(';')}`);
190
+ }
191
+ L.push('');
192
+ }
193
+
194
+ L.push(`━━━ 声明但未接线(${declared.length} 键,配了不生效 / 待接线债) ━━━`);
195
+ L.push('');
196
+ for (const s of LOCAL_YAML_SCHEMA.sections) {
197
+ const keys = s.keys.filter((k) => k.status === 'declared');
198
+ if (keys.length === 0) continue;
199
+ L.push(`[${s.id}] ${s.title}`);
200
+ L.push(` ${s.note}`);
201
+ for (const k of keys) {
202
+ L.push(` ${k.path}${formatConstraint(k)} ${k.desc}`);
203
+ }
204
+ L.push('');
205
+ }
206
+ return L.join('\n');
207
+ }
208
+
209
+ function formatConstraint(k) {
210
+ const tags = [];
211
+ if (k.required) tags.push('必填');
212
+ if (k.optional) tags.push('可选');
213
+ if (k.secret) tags.push('凭据');
214
+ if (k.type === 'enum' && k.values) tags.push(k.values.join('|'));
215
+ else tags.push(k.type);
216
+ return tags.length ? ` [${tags.join(' · ')}]` : '';
217
+ }
218
+
219
+ /** 机读 JSON(程序化消费)。结构 = LOCAL_YAML_SCHEMA 本体 + flatKeys 视图。 */
220
+ export function renderSchemaJson() {
221
+ return JSON.stringify({
222
+ file: LOCAL_YAML_SCHEMA.file,
223
+ note: LOCAL_YAML_SCHEMA.note,
224
+ sections: LOCAL_YAML_SCHEMA.sections,
225
+ keys: flatKeys().map(({ section, path, type, values, required, optional, secret, status, readers, desc }) => ({
226
+ section, path, type, values, required, optional, secret, status, readers, desc,
227
+ })),
228
+ }, null, 2);
229
+ }
230
+
231
+ /**
232
+ * 脱敏 local.yaml 示例文本(sillyspec init 落盘用)。
233
+ * 策展模板(非自动生成):YAML 形态各异(嵌套/inline flow/顶层标量),策展可读性远胜自动拼。
234
+ * 与 SCHEMA 的耦合由测试保证:每个 live 键路径必出现于本文本。
235
+ */
236
+ export function renderExample() {
237
+ return `# SillySpec local.yaml — 脱敏示例(由 sillyspec init 生成,可提交)
238
+ # 真实配置文件 .sillyspec/local.yaml 是 gitignored(含敏感凭据)。
239
+ # 用法:复制本文件为 .sillyspec/local.yaml,填真实值;或直接 sillyspec platform connect 自动写凭据段。
240
+ # 全部键与生效状态:sillyspec config schema
241
+
242
+ # ── 项目类型(sillyspec local detect 自动写入,一般无需手填)──
243
+ project:
244
+ type: nodejs # nodejs | maven | gradle | make | generic
245
+
246
+ # ── 构建/测试/lint 命令(detect 核验 scripts 存在性后才写;缺失键省略)──
247
+ commands:
248
+ build: npm run build
249
+ test: npm test
250
+ lint: npm run lint
251
+ # install: npm install # worktree 依赖安装命令
252
+
253
+ # ── SillyHub MCP 客户端凭据(派发到 worker 用)──
254
+ # agent 手填,或 sillyspec platform connect 在 mcp 段缺失时同源自动填。
255
+ # 两键齐全才生效;缺则回退环境变量 SILLYHUB_MCP_URL / SILLYHUB_MCP_TOKEN。
256
+ mcp:
257
+ url: https://your-sillyhub.example.com
258
+ token: <your-mcp-token>
259
+
260
+ # ── SillyHub 平台同步(sillyspec platform connect 写入,含 ping 验活)──
261
+ platform:
262
+ url: https://your-sillyhub.example.com
263
+ token: <your-platform-token>
264
+ user: alice # 推送者身份(可空,回退 git user.name / env)
265
+ # last_connected: 2026-08-11T00:00:00.000Z # connect 自动写,勿手填
266
+
267
+ # ── 派发调参(仅确知调优值时填,否则留默认)──
268
+ dispatch:
269
+ probe_ttl_ms: 60000 # 派发探测负面缓存 TTL(默认 60000)
270
+ # poll_interval_ms: 15000 # ⚠ 路径A 预留·未落地,配了暂不生效
271
+ # worker_timeout_ms: 60000 # ⚠ 路径A 预留·未落地,配了暂不生效
272
+
273
+ # ── monorepo 子模块映射(test_strategy: module 时按 git diff 命中模块收窄测试)──
274
+ # 只支持 inline flow 形态(嵌套展开式解析不出):
275
+ modules:
276
+ frontend: { path: "frontend/", test: "cd frontend && pnpm test" }
277
+ backend: { path: "backend/", test: "cd backend && npm test" }
278
+
279
+ # ── 测试策略:full=全量 commands.test | module=按命中模块收窄(需配 modules)──
280
+ test_strategy: full
281
+
282
+ # ── 预存失败豁免清单(变更前就失败的测试行;务必定期复核,防误豁免真实失败)──
283
+ known_failures:
284
+ - tests/some-old-stable-failure
285
+
286
+ # ── worktree-guard 只读命令扩展(execute worktree 隔离期内放行额外只读命令)──
287
+ worktree-hook:
288
+ readonlyCommands:
289
+ - rg
290
+ - fd
291
+
292
+ # ── 变更规模自动分类(sillyspec run auto 时按需求描述强制 quick/full;正则数组,i 大小写无关)──
293
+ auto_mode:
294
+ force_full_patterns:
295
+ - 数据库|migration
296
+ force_quick_patterns:
297
+ - fix typo
298
+
299
+ # ── 文档引用校验(sillyspec docs check;glob 相对源码仓根,三形态:递归/单层/字面路径)──
300
+ # 缺省已扫 docs/**/*.md 与 .sillyspec/docs/**/*.md;本段仅在需要收窄/排除时配置
301
+ docs-check:
302
+ paths:
303
+ - docs/**/*.md
304
+ - .sillyspec/docs/**/*.md
305
+ # skip:
306
+ # - docs/sillyspec/archive
307
+ keywordAssert: true
308
+ # living-docs: # 活文档监控点追加(缺省已含 docs/sillyspec/platform-interface-map.md,只加哨兵不覆盖)
309
+ # - docs/sillyspec/architecture.md
310
+ # cross_repo_roots: # repo://<仓库名> 跨仓引用 → 本机仓库根(每台设备各自配,绝对路径勿提交)
311
+ # sillyspec: /c/Users/you/IdeaProjects/sillyspec
312
+ `;
313
+ }