sillyspec 3.28.7 → 3.28.9
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-quick/SKILL.md +2 -0
- package/.husky/pre-push +3 -1
- package/AGENTS.md +3 -2
- package/README.md +35 -11
- package/SKILL.md +24 -10
- package/SillySpec-/350/203/275/345/212/233/344/272/256/347/202/271/345/205/250/346/231/257-2026-09-14.pptx +0 -0
- package/package.json +1 -1
- package/src/agent-session-log.js +446 -35
- package/src/change-risk-profile.js +20 -0
- package/src/config-cat.js +6 -3
- package/src/config-schema.js +32 -0
- package/src/constants.js +3 -0
- package/src/db.js +23 -6
- package/src/decision-distill.js +15 -3
- package/src/design-facts.js +33 -3
- package/src/docs-check.js +137 -12
- package/src/docs-gate.js +108 -6
- package/src/doctor-diagnostics.js +243 -9
- package/src/friction-ledger.js +171 -0
- package/src/friction-tally.js +7 -2
- package/src/git-helper.js +6 -4
- package/src/hooks/worktree-guard.js +72 -30
- package/src/index.js +356 -81
- package/src/knowledge-classify.js +363 -0
- package/src/knowledge-hits.js +74 -0
- package/src/knowledge-stats.js +180 -0
- package/src/local-detect.js +30 -15
- package/src/local-register.js +13 -4
- package/src/mcp-server.js +38 -8
- package/src/progress/change-registry.js +224 -43
- package/src/progress/consistency-doctor.js +11 -4
- package/src/progress/shared.js +3 -2
- package/src/progress/stage-machine.js +3 -1
- package/src/progress/step-store.js +30 -17
- package/src/progress.js +123 -8
- package/src/quick-gate-profile.js +251 -0
- package/src/quicklog.js +199 -32
- package/src/review-dispatch.js +2 -1
- package/src/run/command.js +124 -15
- package/src/run/complete-handlers.js +514 -24
- package/src/run/complete.js +82 -25
- package/src/run/concurrent-detect.js +99 -1
- package/src/run/gate-snapshot.js +115 -5
- package/src/run/gates.js +85 -14
- package/src/run/prompt.js +143 -6
- package/src/run/quick-audit.js +136 -15
- package/src/run/shared.js +174 -20
- package/src/run/stage.js +6 -0
- package/src/run/verify-quality-scan.js +14 -3
- package/src/scan-diff.js +61 -30
- package/src/scan-postcheck.js +74 -0
- package/src/scan-refresh.js +477 -0
- package/src/scan-staleness.js +25 -13
- package/src/scope-audit.js +365 -35
- package/src/spec-sync.js +19 -14
- package/src/stage-contract-spec.js +4 -3
- package/src/stage-contract.js +185 -2
- package/src/stage-review.js +29 -12
- package/src/stages/brainstorm.js +7 -1
- package/src/stages/execute.js +91 -4
- package/src/stages/knowledge.js +52 -3
- package/src/stages/plan-postcheck.js +57 -12
- package/src/stages/quick.js +1 -1
- package/src/stages/verify.js +1 -0
- package/src/sync-noise.js +114 -6
- package/src/sync.js +201 -11
- package/src/task-review.js +333 -27
- package/src/verify-facts-schema.js +10 -3
- package/src/verify-postcheck.js +41 -6
- package/src/verify-probes.js +348 -7
- package/src/worktree-apply.js +594 -62
- package/src/worktree-cross.js +12 -18
- package/src/worktree-deps.js +100 -2
- package/src/worktree.js +255 -30
- package/src/wt-commit.js +128 -0
- package/templates/agents-instruction.md +2 -2
- package/templates/prompts/testcase-design.md +2 -1
- package/templates/prompts/verify-probes.md +4 -1
- package/sa-rename-probe/src/new.js +0 -4
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
|
|
7
7
|
* 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
|
|
8
8
|
*/
|
|
9
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
|
|
9
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, unlinkSync } from 'fs';
|
|
10
10
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
11
11
|
import { basename, dirname, extname, join, resolve, isAbsolute, sep, relative } from 'path';
|
|
12
12
|
import { safeGit, git } from './git-helper.js';
|
|
@@ -60,6 +60,9 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
60
60
|
--force-rescan 覆盖已有 scan 文档保护
|
|
61
61
|
--diff [--base <commit>] [--full] [--report] scan 文档 vs 源码漂移清单(纯只读)
|
|
62
62
|
(子命令: sillyspec scan diff 等价 --diff)
|
|
63
|
+
refresh [--project <名>] [--force] [--json] 增量刷新①拍:门控+受影响文档手术工单
|
|
64
|
+
refresh --done [--docs a.md,b.md] [--force] 增量刷新②拍:内容比对+per-doc 基线推进+postcheck
|
|
65
|
+
(检出极限:refresh 只覆盖带 file:line 引用的检出项,不声称文档与源码一致)
|
|
63
66
|
archive: --confirm 归档确认(必须)
|
|
64
67
|
auto: --mode <模式> 显式指定流程模式
|
|
65
68
|
平台: --runtime-root <path> / --workspace-id <id> / --scan-run-id <id>
|
|
@@ -70,6 +73,9 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
70
73
|
check | repair [--apply] | validate | reset [--stage X] |
|
|
71
74
|
batch --total N --completed M [--failed F] [--skipped K] | batch --status
|
|
72
75
|
|
|
76
|
+
sillyspec wt-commit [--change <名>] -m <信息> [--pathspec-from-file <f>] -- <path...>
|
|
77
|
+
worktree 内 per-task 串行化提交(文件锁排队 + 强制显式 pathspec,
|
|
78
|
+
防 add -A 卷入并行兄弟 WIP;worktree 内可省 --change,从 cwd 推断)
|
|
73
79
|
sillyspec worktree <cmd> git worktree 隔离管理(execute 阶段相关)
|
|
74
80
|
create <change> [--base <branch>] 创建隔离 worktree
|
|
75
81
|
apply <change> [--check-only] 校验并应用变更到主工作区
|
|
@@ -198,7 +204,7 @@ async function main() {
|
|
|
198
204
|
|
|
199
205
|
// E22:重路径统一加载(轻路径 --version/help 已早退,未付此税)。
|
|
200
206
|
const { ProgressManager, resolvePlatformSpecDir, resolvePlatformOpts } = await import('./progress.js');
|
|
201
|
-
const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift } = await import('./run/shared.js');
|
|
207
|
+
const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift, detectCwdInsideWorktree } = await import('./run/shared.js');
|
|
202
208
|
|
|
203
209
|
// 解析全局选项
|
|
204
210
|
let json = false;
|
|
@@ -221,13 +227,13 @@ async function main() {
|
|
|
221
227
|
json = true;
|
|
222
228
|
} else if (args[i] === '--save') {
|
|
223
229
|
saveWorkflowRunFlag = true;
|
|
224
|
-
} else if (args[i] === '--dir' && args[i + 1]) {
|
|
230
|
+
} else if (args[i] === '--dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
225
231
|
targetDir = resolve(args[i + 1]);
|
|
226
232
|
i++;
|
|
227
|
-
} else if (args[i] === '--spec-dir' && args[i + 1]) {
|
|
233
|
+
} else if (args[i] === '--spec-dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
228
234
|
specDir = resolve(args[i + 1]);
|
|
229
235
|
i++;
|
|
230
|
-
} else if (args[i] === '--tool' && args[i + 1]) {
|
|
236
|
+
} else if (args[i] === '--tool' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
231
237
|
// 多值支持:逗号分隔(--tool claude,codex)与重复 flag(--tool a --tool b)都收集进数组
|
|
232
238
|
for (const v of args[i + 1].split(',')) {
|
|
233
239
|
const t = v.trim();
|
|
@@ -237,18 +243,21 @@ async function main() {
|
|
|
237
243
|
} else if (args[i] === '--no-skills') {
|
|
238
244
|
// init 专属:跳过 skills 复制段(吞进变量,不透传 filteredArgs)
|
|
239
245
|
noSkills = true;
|
|
240
|
-
} else if (args[i] === '--workspace-id' && args[i + 1]) {
|
|
246
|
+
} else if (args[i] === '--workspace-id' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
241
247
|
// 平台模式专属 flag(init 落平台指针用;--runtime-root/--scan-run-id 保持在
|
|
242
248
|
// filteredArgs 由 runCommand 解析,init 侧只需要 workspaceId 作为平台信号 + runtimeRoot)
|
|
243
249
|
platformWorkspaceId = args[i + 1];
|
|
244
250
|
filteredArgs.push(args[i], args[i + 1]);
|
|
245
251
|
i++;
|
|
246
|
-
} else if (args[i] === '--runtime-root' && args[i + 1]) {
|
|
252
|
+
} else if (args[i] === '--runtime-root' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
247
253
|
platformRuntimeRoot = resolve(args[i + 1]);
|
|
248
254
|
filteredArgs.push(args[i], args[i + 1]);
|
|
249
255
|
i++;
|
|
250
256
|
} else if (args[i] === '--interactive' || args[i] === '-i') {
|
|
251
257
|
interactive = true;
|
|
258
|
+
} else if (args[i] === '--allow-worktree-cwd') {
|
|
259
|
+
// worktree cwd 硬拦逃生门:默认阻止在 .sillyspec/.runtime/worktrees/* 内跑 CLI,
|
|
260
|
+
// 此 flag 显式放行(坑 worktree-cwd-silent-split)
|
|
252
261
|
} else if (args[i] === '--list' || args[i] === '-l') {
|
|
253
262
|
filteredArgs.push('--list');
|
|
254
263
|
} else {
|
|
@@ -311,6 +320,22 @@ async function main() {
|
|
|
311
320
|
process.exit(1);
|
|
312
321
|
}
|
|
313
322
|
|
|
323
|
+
// ── worktree cwd 硬拦(坑 worktree-cwd-silent-split,2026-08-29 用户实证)──
|
|
324
|
+
// Bash cwd 残留让 agent 两次误在 worktree 内跑 sillyspec:status 类完全静默、进度写进
|
|
325
|
+
// worktree 副本 .sillyspec 与主仓进度库分裂。守卫在命令分发前拦截,--dir 显式指向主仓
|
|
326
|
+
// 或 --allow-worktree-cwd 逃生门放行;wt-commit 命令豁免(子代理 workdir=worktree 内合法)。
|
|
327
|
+
if (!args.includes('--allow-worktree-cwd') && command !== 'wt-commit') {
|
|
328
|
+
const guardDir = args.includes('--dir') ? dir : process.cwd();
|
|
329
|
+
const wtInfo = detectCwdInsideWorktree(guardDir);
|
|
330
|
+
if (wtInfo) {
|
|
331
|
+
console.error(`\n❌ 当前在隔离 worktree 内(${wtInfo.changeName}):${guardDir}`);
|
|
332
|
+
console.error(` 主仓根:${wtInfo.mainRepoRoot}`);
|
|
333
|
+
console.error(' SillySpec 进度库只存在于主仓,worktree 内跑会产生分裂进度(坑 worktree-cwd-silent-split)。');
|
|
334
|
+
console.error(' 修复:cd 到主仓根后重跑,或用 --dir <主仓根>,或用 --allow-worktree-cwd 强制放行。');
|
|
335
|
+
process.exit(2);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
314
339
|
switch (command) {
|
|
315
340
|
case 'init':
|
|
316
341
|
await (await import('./init.js')).cmdInit(dir, { tool, tools: toolValues.length > 0 ? toolValues : null, interactive, specDir, noSkills, platformOpts: (platformWorkspaceId || platformRuntimeRoot) ? { workspaceId: platformWorkspaceId, runtimeRoot: platformRuntimeRoot } : null });
|
|
@@ -324,10 +349,10 @@ async function main() {
|
|
|
324
349
|
const progDir = specDir ? dir : resolveEffectiveDir(dir);
|
|
325
350
|
const subCommand = filteredArgs[1];
|
|
326
351
|
const stageIdx = filteredArgs.indexOf('--stage');
|
|
327
|
-
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
|
|
352
|
+
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] && !String(filteredArgs[stageIdx + 1]).startsWith("--") ? filteredArgs[stageIdx + 1] : null;
|
|
328
353
|
// 解析 --change 参数
|
|
329
354
|
const progChangeIdx = args.indexOf('--change');
|
|
330
|
-
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] ? args[progChangeIdx + 1] : null;
|
|
355
|
+
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] && !String(args[progChangeIdx + 1]).startsWith("--") ? args[progChangeIdx + 1] : null;
|
|
331
356
|
// 与 run 入口同源消毒(防路径穿越;progress 下游 completeStage 写 history 文件名拼 change 名)
|
|
332
357
|
if (progChangeName) assertSafeChangeName(progChangeName, '--change 变更名');
|
|
333
358
|
|
|
@@ -469,7 +494,7 @@ async function main() {
|
|
|
469
494
|
// 只读查询,不依赖 worktree、不写状态(D-002@v1)。
|
|
470
495
|
const gateStage = filteredArgs[1];
|
|
471
496
|
const gateChangeIdx = args.indexOf('--change');
|
|
472
|
-
const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] ? args[gateChangeIdx + 1] : null;
|
|
497
|
+
const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] && !String(args[gateChangeIdx + 1]).startsWith("--") ? args[gateChangeIdx + 1] : null;
|
|
473
498
|
if (!gateStage || gateStage.startsWith('-') || !gateChange) {
|
|
474
499
|
console.error('用法: sillyspec gate <stage> --change <name> [--json]\n stage: brainstorm | plan | execute | verify | archive | ...');
|
|
475
500
|
process.exit(2);
|
|
@@ -529,7 +554,7 @@ async function main() {
|
|
|
529
554
|
// 单项事实核验(machine-interface v1):查询变更某一 facet 的真实状态,返回结构化 data。
|
|
530
555
|
const facet = filteredArgs[1];
|
|
531
556
|
const deriveChangeIdx = args.indexOf('--change');
|
|
532
|
-
const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] ? args[deriveChangeIdx + 1] : null;
|
|
557
|
+
const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] && !String(args[deriveChangeIdx + 1]).startsWith("--") ? args[deriveChangeIdx + 1] : null;
|
|
533
558
|
if (!facet || facet.startsWith('-') || !deriveChange) {
|
|
534
559
|
console.error('用法: sillyspec derive <facet> --change <name> [--json]\n facet: execute-evidence | verify-test | task-reviews | artifacts');
|
|
535
560
|
process.exit(2);
|
|
@@ -587,7 +612,7 @@ async function main() {
|
|
|
587
612
|
}
|
|
588
613
|
const rwVal = (flag) => {
|
|
589
614
|
const i = filteredArgs.indexOf(flag);
|
|
590
|
-
return i >= 0 && filteredArgs[i + 1] ? filteredArgs[i + 1] : null;
|
|
615
|
+
return i >= 0 && filteredArgs[i + 1] && !String(filteredArgs[i + 1]).startsWith("--") ? filteredArgs[i + 1] : null;
|
|
591
616
|
};
|
|
592
617
|
const rwChange = rwVal('--change');
|
|
593
618
|
const rwTask = rwVal('--task');
|
|
@@ -645,6 +670,63 @@ async function main() {
|
|
|
645
670
|
}
|
|
646
671
|
break;
|
|
647
672
|
}
|
|
673
|
+
case 'task': {
|
|
674
|
+
// task 级进行中状态标记(2026-09-12 驾驭第十四批②,用户实证「中断续跑时半成品无主,
|
|
675
|
+
// 主代理接管审查负担重」):开工/完工各一条命令落 .runtime/task-progress/<change>/<task-NN>.json
|
|
676
|
+
// (startedAt + note),list 带陈旧标红(>2h 视为中断遗留——续跑时主代理据此点名接管审查,
|
|
677
|
+
// 不再靠翻 git 工作区猜半成品)。轻量 marker:不进 DB、不设门禁(状态可见性工具,非流程卡点)。
|
|
678
|
+
const taskSub = filteredArgs[1];
|
|
679
|
+
if (!taskSub || ['help', '--help', '-h'].includes(taskSub)) {
|
|
680
|
+
console.log(`用法:
|
|
681
|
+
sillyspec task start --change <名> --task task-NN [--note "一句话在做什么"]
|
|
682
|
+
sillyspec task finish --change <名> --task task-NN
|
|
683
|
+
sillyspec task list --change <名>
|
|
684
|
+
task 进行中状态标记:开工/完工落 .runtime/task-progress/,list 标红 >2h 中断遗留(接管审查点名用)`);
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
const tChangeIdx = filteredArgs.indexOf('--change');
|
|
688
|
+
const tChange = tChangeIdx >= 0 && filteredArgs[tChangeIdx + 1] && !String(filteredArgs[tChangeIdx + 1]).startsWith("--") ? filteredArgs[tChangeIdx + 1] : null;
|
|
689
|
+
const tTaskIdx = filteredArgs.indexOf('--task');
|
|
690
|
+
const tTask = tTaskIdx >= 0 && filteredArgs[tTaskIdx + 1] && !String(filteredArgs[tTaskIdx + 1]).startsWith("--") ? filteredArgs[tTaskIdx + 1] : null;
|
|
691
|
+
const tNoteIdx = filteredArgs.indexOf('--note');
|
|
692
|
+
const tNote = tNoteIdx >= 0 && filteredArgs[tNoteIdx + 1] && !String(filteredArgs[tNoteIdx + 1]).startsWith("--") ? filteredArgs[tNoteIdx + 1] : null;
|
|
693
|
+
if (!tChange) { console.error('❌ 缺 --change <变更名>'); process.exit(2); }
|
|
694
|
+
if (taskSub !== 'list') {
|
|
695
|
+
if (!tTask || !/^task-\d+$/.test(tTask)) { console.error('❌ --task 格式应为 task-NN'); process.exit(2); }
|
|
696
|
+
}
|
|
697
|
+
const tSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
698
|
+
const tRuntime = join(tSpecBase, '.runtime');
|
|
699
|
+
const tDir = join(tRuntime, 'task-progress', tChange);
|
|
700
|
+
if (taskSub === 'start') {
|
|
701
|
+
mkdirSync(tDir, { recursive: true });
|
|
702
|
+
writeFileSync(join(tDir, `${tTask}.json`), JSON.stringify({
|
|
703
|
+
task: tTask, change: tChange, note: tNote || '', startedAt: new Date().toISOString(),
|
|
704
|
+
}, null, 2) + '\n');
|
|
705
|
+
console.log(`📌 ${tTask} 已标记进行中${tNote ? '(' + tNote + ')' : ''}——完工时 sillyspec task finish --change ${tChange} --task ${tTask}`);
|
|
706
|
+
} else if (taskSub === 'finish') {
|
|
707
|
+
try { unlinkSync(join(tDir, `${tTask}.json`)); console.log(`✅ ${tTask} 进行中标记已清除(完工)`); }
|
|
708
|
+
catch { console.log(`ℹ️ ${tTask} 无进行中标记(已完工或从未 start)`); }
|
|
709
|
+
} else if (taskSub === 'list') {
|
|
710
|
+
if (!existsSync(tDir)) { console.log(`ℹ️ 无 ${tChange} 的进行中 task 标记`); break; }
|
|
711
|
+
const entries = readdirSync(tDir).filter(f => f.endsWith('.json'));
|
|
712
|
+
if (entries.length === 0) { console.log(`ℹ️ 无进行中标记(全部完工)`); break; }
|
|
713
|
+
console.log(`📋 ${tChange} 进行中 task(${entries.length} 个)——中断遗留接管审查点名用:`);
|
|
714
|
+
let stale = 0;
|
|
715
|
+
for (const f of entries.sort()) {
|
|
716
|
+
try {
|
|
717
|
+
const m = JSON.parse(readFileSync(join(tDir, f), 'utf8'));
|
|
718
|
+
const ageMin = Math.round((Date.now() - new Date(m.startedAt).getTime()) / 60000);
|
|
719
|
+
const isStale = ageMin > 120;
|
|
720
|
+
if (isStale) stale++;
|
|
721
|
+
console.log(` ${isStale ? '🔴' : '🟢'} ${m.task}: 开始于 ${m.startedAt}(${ageMin >= 60 ? Math.floor(ageMin / 60) + 'h' + ageMin % 60 + 'm' : ageMin + 'm'} 前)${m.note ? '——' + m.note : ''}${isStale ? ' ⚠️ 超过 2h,疑似中断遗留(接管者:对照 task 卡 + git 工作区半成品审查)' : ''}`);
|
|
722
|
+
} catch { console.log(` ⚠️ ${f} 标记损坏(忽略)`); }
|
|
723
|
+
}
|
|
724
|
+
if (stale > 0) console.log(` 共 ${stale} 个疑似中断遗留——主代理接管时逐一审查(半成品无主的痛点出口)`);
|
|
725
|
+
} else {
|
|
726
|
+
console.error(`❌ 未知子命令「${taskSub}」(start | finish | list)`); process.exit(2);
|
|
727
|
+
}
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
648
730
|
case 'backfill-reviews': {
|
|
649
731
|
// 手动补 task(reopen execute / 直接实现)缺 review.json 的官方入口(坑 verify-archive-flow-pitfalls 坑2)。
|
|
650
732
|
// 复用 generateTaskReviewDrafts(execute --done 同源兜底,幂等、fail-open):据 git diff base..head + working-tree
|
|
@@ -657,7 +739,7 @@ async function main() {
|
|
|
657
739
|
// verdict——手算 hash、手抄 diff 清单是纯错误面,gate 校验的恰是这些字段。与草稿互补:
|
|
658
740
|
// 草稿补「缺失」,adopt 修「已存在但 mechanics 错/缺」,verdict 原样保留。
|
|
659
741
|
const brChangeIdx = args.indexOf('--change');
|
|
660
|
-
const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] ? args[brChangeIdx + 1] : null;
|
|
742
|
+
const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] && !String(args[brChangeIdx + 1]).startsWith("--") ? args[brChangeIdx + 1] : null;
|
|
661
743
|
const brAdopt = args.includes('--adopt');
|
|
662
744
|
if (!brChange) {
|
|
663
745
|
console.error('用法: sillyspec backfill-reviews --change <name> [--adopt] [--json] [--spec-dir <path>]\n 缺失 review.json → 生成 cannot_verify 草稿(解 archive 客观完成度阻断)\n --adopt → 已存在 review.json 的 base/head/changedFiles/diffPaths 等 mechanics 字段一键重算代填(verdict 保留;统一 commit 模式 diffPaths=task 卡 allowed_paths)');
|
|
@@ -739,7 +821,7 @@ async function main() {
|
|
|
739
821
|
const wsArgs = filteredArgs.slice(2);
|
|
740
822
|
const wsFlag = (name) => {
|
|
741
823
|
const i = wsArgs.indexOf(name);
|
|
742
|
-
return i >= 0 && wsArgs[i + 1] ? wsArgs[i + 1] : null;
|
|
824
|
+
return i >= 0 && wsArgs[i + 1] && !String(wsArgs[i + 1]).startsWith("--") ? wsArgs[i + 1] : null;
|
|
743
825
|
};
|
|
744
826
|
const { workspaceAdd, workspaceRemove, workspaceStatus } = await import('./workspace.js');
|
|
745
827
|
if (wsSub === 'add') {
|
|
@@ -838,7 +920,9 @@ async function main() {
|
|
|
838
920
|
const childArgs = m[1].trim().split(/\s+/);
|
|
839
921
|
console.log(`🤖 next --apply 代跑:sillyspec ${childArgs.join(' ')}\n`);
|
|
840
922
|
const { spawnSync } = await import('node:child_process');
|
|
841
|
-
const
|
|
923
|
+
const { fileURLToPath } = await import('node:url');
|
|
924
|
+
// ESM 无 __dirname(ql-20260911-029:此处曾直接引用,--apply 常态路径必 ReferenceError)
|
|
925
|
+
const r = spawnSync(process.execPath, [fileURLToPath(new URL('../bin/sillyspec.js', import.meta.url)), '--dir', dir, ...childArgs], { stdio: 'inherit' });
|
|
842
926
|
process.exit(r.status === null ? 1 : r.status);
|
|
843
927
|
}
|
|
844
928
|
if (json) {
|
|
@@ -868,7 +952,7 @@ async function main() {
|
|
|
868
952
|
// 通用自动 git 在多会话仓是历史重灾区,故收窄到这一处。
|
|
869
953
|
if (args.includes('--apply')) {
|
|
870
954
|
const apChangeIdx = args.indexOf('--change');
|
|
871
|
-
const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] ? args[apChangeIdx + 1] : null;
|
|
955
|
+
const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] && !String(args[apChangeIdx + 1]).startsWith("--") ? args[apChangeIdx + 1] : null;
|
|
872
956
|
if (!apChange) {
|
|
873
957
|
console.error('用法: sillyspec commit --apply --change <名>\n 只限归档语境(changes.status=archived 或 current_stage=archive)执行归档产物提交');
|
|
874
958
|
process.exit(2);
|
|
@@ -947,7 +1031,7 @@ async function main() {
|
|
|
947
1031
|
// --init 顺带生成 verify-result.md 七章节骨架(探针结果预填;结论留「待填」——gate 找
|
|
948
1032
|
// 不到 PASS/FAIL 即判不过,骨架不能直接过门)。已存在不覆盖。
|
|
949
1033
|
const vpChangeIdx = args.indexOf('--change');
|
|
950
|
-
const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] ? args[vpChangeIdx + 1] : null;
|
|
1034
|
+
const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] && !String(args[vpChangeIdx + 1]).startsWith("--") ? args[vpChangeIdx + 1] : null;
|
|
951
1035
|
const vpInit = args.includes('--init');
|
|
952
1036
|
if (!vpChange) {
|
|
953
1037
|
console.error('用法: sillyspec verify-probes --change <name> [--init] [--json] [--spec-dir <path>]\n 跑机械探针(TODO 标记/测试覆盖/API 对账/删除对账)输出 markdown;--init 生成 verify-result.md 骨架(探针预填,已存在不覆盖)');
|
|
@@ -1031,6 +1115,18 @@ async function main() {
|
|
|
1031
1115
|
console.log(`📄 已补齐缺失槽段(不触碰既有正文): ${vpAdded.added.join('、')}`)
|
|
1032
1116
|
}
|
|
1033
1117
|
}
|
|
1118
|
+
// 2026-09-14-acceptance-test-matrix:探针 7 验收×测试覆盖矩阵骨架幂等补段——
|
|
1119
|
+
// verify-result.md 已存在但缺「#### 探针 7」段且 applicable(有 TaskCard)时补矩阵
|
|
1120
|
+
// 骨架(学 backfillMissingEvidenceSlots 形态,不触既有正文;判定/证据槽 fail-closed
|
|
1121
|
+
// 由 verify 门禁负责)。本变更自举:verify-result.md 先于特性存在的存量走此补段。
|
|
1122
|
+
try {
|
|
1123
|
+
const { ensureAcceptanceMatrixSection } = await import('./verify-probes.js')
|
|
1124
|
+
const vpMatrix = ensureAcceptanceMatrixSection(vpReportPath, vpResult.probe7)
|
|
1125
|
+
if (vpMatrix.added) {
|
|
1126
|
+
try { await mirrorInitArtifact(vpReportPath, 'verify-result.md', readFileSync(vpReportPath, 'utf8')) } catch { /* 镜像失败不阻断 */ }
|
|
1127
|
+
console.log(`🧩 探针 7 验收×测试覆盖矩阵骨架段已补齐(判定=四选一/证据两槽待填,命中≠判定;不触碰既有正文): ${vpReportPath}${vpPlatformNote}`)
|
|
1128
|
+
}
|
|
1129
|
+
} catch { /* 矩阵补段失败不阻断 --init */ }
|
|
1034
1130
|
// P0-1(noai-ir-roadmap §3)两件机械预填,均幂等 fail-soft:
|
|
1035
1131
|
// ①决策追踪矩阵机械半边——D→FR→task 链自 decisions.md × tasks/*.md frontmatter
|
|
1036
1132
|
// 结构化字段构建,Evidence/状态两列留 agent 逐格复核;
|
|
@@ -1071,9 +1167,9 @@ async function main() {
|
|
|
1071
1167
|
// stage-reviews 路径)、--kill=清在途记录+平台处置指引(处置权在人)。薄壳:全部逻辑在
|
|
1072
1168
|
// src/review-dispatch.js(可测核心),此处只做 flag 解析与退出码。
|
|
1073
1169
|
const rdChangeIdx = args.indexOf('--change');
|
|
1074
|
-
const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] ? args[rdChangeIdx + 1] : null;
|
|
1170
|
+
const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] && !String(args[rdChangeIdx + 1]).startsWith("--") ? args[rdChangeIdx + 1] : null;
|
|
1075
1171
|
const rdStageIdx = args.indexOf('--stage');
|
|
1076
|
-
const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] ? args[rdStageIdx + 1] : null;
|
|
1172
|
+
const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] && !String(args[rdStageIdx + 1]).startsWith("--") ? args[rdStageIdx + 1] : null;
|
|
1077
1173
|
const rdStatus = args.includes('--status');
|
|
1078
1174
|
const rdKill = args.includes('--kill');
|
|
1079
1175
|
const rdMode = rdKill ? 'kill' : rdStatus ? 'status' : 'create';
|
|
@@ -1124,9 +1220,9 @@ async function main() {
|
|
|
1124
1220
|
// 模块文档 sidecar 同步命令化(2026-09-09 刀批):diff 归属模块 → sidecar 追加变更索引行 +
|
|
1125
1221
|
// 卡 updated_at 戳——机械部分 CLI 代算,卡片正文语义更新仍归 agent/归档流程。幂等。
|
|
1126
1222
|
const mdsChangeIdx = args.indexOf('--change');
|
|
1127
|
-
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] ? args[mdsChangeIdx + 1] : null;
|
|
1223
|
+
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] && !String(args[mdsChangeIdx + 1]).startsWith("--") ? args[mdsChangeIdx + 1] : null;
|
|
1128
1224
|
const mdsNoteIdx = args.indexOf('--note');
|
|
1129
|
-
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] ? args[mdsNoteIdx + 1] : null;
|
|
1225
|
+
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] && !String(args[mdsNoteIdx + 1]).startsWith("--") ? args[mdsNoteIdx + 1] : null;
|
|
1130
1226
|
if (!mdsChange) {
|
|
1131
1227
|
console.error('用法: sillyspec module-docs-sync --change <name> [--note "一句话"] [--json]\n diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等;卡片正文语义更新仍归 agent)');
|
|
1132
1228
|
process.exit(2);
|
|
@@ -1151,7 +1247,7 @@ async function main() {
|
|
|
1151
1247
|
// 手排 Wave 与拓扑比对不一致需手改;依赖方向硬拦(plan-postcheck)落地后给一条零手写出路。
|
|
1152
1248
|
// depMap 与 postcheck 同源(collectTaskDepMap)→ topoSortWaves → 重写 Wave 段 + 同步任务总表 W 列。
|
|
1153
1249
|
const pawChangeIdx = args.indexOf('--change');
|
|
1154
|
-
const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] ? args[pawChangeIdx + 1] : null;
|
|
1250
|
+
const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] && !String(args[pawChangeIdx + 1]).startsWith("--") ? args[pawChangeIdx + 1] : null;
|
|
1155
1251
|
const pawDryRun = args.includes('--dry-run');
|
|
1156
1252
|
if (!pawChange) {
|
|
1157
1253
|
console.error('用法: sillyspec plan-adopt-waves --change <name> [--dry-run] [--json] [--spec-dir <path>]\n 把 plan.md 的 Wave 段一键重排为 depends_on 拓扑分组(同步任务总表 W 列;--dry-run 只看不写)');
|
|
@@ -1196,7 +1292,7 @@ async function main() {
|
|
|
1196
1292
|
// archive 模块影响矩阵骨架(2026-08-21 审计第三批 D3):文件×模块归属按 _module-map.yaml
|
|
1197
1293
|
// paths 前缀匹配预填(机械),影响类型/review 标记留 <!--TODO-->(语义)。已存在不覆盖。
|
|
1198
1294
|
const miChangeIdx = args.indexOf('--change');
|
|
1199
|
-
const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] ? args[miChangeIdx + 1] : null;
|
|
1295
|
+
const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] && !String(args[miChangeIdx + 1]).startsWith("--") ? args[miChangeIdx + 1] : null;
|
|
1200
1296
|
if (!miChange) {
|
|
1201
1297
|
console.error('用法: sillyspec module-impact --change <name> [--json] [--spec-dir <path>]\n 生成 module-impact.md 骨架(模块影响矩阵按 module-map 预填 + 未匹配文件清单;已存在不覆盖)');
|
|
1202
1298
|
process.exit(2);
|
|
@@ -1231,10 +1327,14 @@ async function main() {
|
|
|
1231
1327
|
// 路由形态对齐 verify-probes(:897)/ module-impact(:1041)先例——--change 必填缺参
|
|
1232
1328
|
// 用法错 exit 2、assertSafeChangeName 消毒、动态 import、--json 结构化输出、运行错
|
|
1233
1329
|
// fail-soft 出错误摘要 exit 1 不抛栈。
|
|
1330
|
+
// 画像双出口(2026-09-14-quick-exit-tiered-gates task-03,FR-04 / D-008):quick 会话的
|
|
1331
|
+
// gateProfile 由 computeChangeScopeAudit 结果对象携带——--json 出口经 ...saResult 展开
|
|
1332
|
+
// 自动透出(gateProfile-json)、表格出口经 renderScopeAuditTable 按存在性追加 [gate]
|
|
1333
|
+
// 画像段(gate-table-section),本层零分支零新解析;--file 子命令行为不变。
|
|
1234
1334
|
const saChangeIdx = args.indexOf('--change');
|
|
1235
|
-
const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] ? args[saChangeIdx + 1] : null;
|
|
1335
|
+
const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] && !String(args[saChangeIdx + 1]).startsWith("--") ? args[saChangeIdx + 1] : null;
|
|
1236
1336
|
if (!saChange) {
|
|
1237
|
-
console.error('用法: sillyspec scope-audit --change <name> [--file <path>] [--json] [--spec-dir <path>]\n 变更范围对账:计划×实际三态全表 + 行数(quick 会话传 quick-<8hex>
|
|
1337
|
+
console.error('用法: sillyspec scope-audit --change <name> [--file <path>] [--json] [--spec-dir <path>]\n 变更范围对账:计划×实际三态全表 + 行数(quick 会话传 quick-<8hex> 出归属表,附 [gate] 分级门禁画像——表格画像段与 --json gateProfile 字段;已归档变更可查——快照冻结记录态);--file <path> 看单文件变化内容(对账同源锚点的 git diff);advisory 只读展示,不构成门禁');
|
|
1238
1338
|
process.exit(2);
|
|
1239
1339
|
}
|
|
1240
1340
|
assertSafeChangeName(saChange, '--change 变更名');
|
|
@@ -1262,10 +1362,12 @@ async function main() {
|
|
|
1262
1362
|
}
|
|
1263
1363
|
// full-flow 变更目录存在性预检(endpoints baseline 先例同款):post-apply 形态下实际侧三源
|
|
1264
1364
|
// 对「变更不存在」不敏感(status 成功即 ok=true,会把主仓脏区误当实际侧),靠预检给明确
|
|
1265
|
-
// 错误摘要 exit 1;quick 会话 id 无 changes/ 目录(guard
|
|
1365
|
+
// 错误摘要 exit 1;quick 会话 id 无 changes/ 目录(guard 祖先链定位),不预检;ql-xxx
|
|
1366
|
+
// 条目号同 quick 会话 id(ql-xxx 反查走 quicklog/patches,亦无 changes/ 目录)不预检。
|
|
1266
1367
|
// 归档形态兼容(quick-8aa52289):changes/<名> 不在 → 接受 changes/archive/<名>(scope-audit
|
|
1267
1368
|
// 内部走快照记录态/HEAD 未提交窗口兜底)。
|
|
1268
1369
|
if (!/^quick-[0-9a-f]{8}$/.test(saChange)
|
|
1370
|
+
&& !/^ql-\d{8}-\d{3}-[0-9a-f]{4}$/.test(saChange)
|
|
1269
1371
|
&& !existsSync(join(saSpecBase, 'changes', saChange))
|
|
1270
1372
|
&& !existsSync(join(saSpecBase, 'changes', 'archive', saChange))) {
|
|
1271
1373
|
console.error(`❌ 变更目录不存在(活跃与归档区均未找到): ${join(saSpecBase, 'changes', saChange)}(确认 --change 名,或 --spec-dir 指向主仓 .sillyspec)`);
|
|
@@ -1329,7 +1431,7 @@ async function main() {
|
|
|
1329
1431
|
// 端点增删的 before 侧;扫描/落盘在 endpoint-baseline.js 纯函数,此处只做参数解析 +
|
|
1330
1432
|
// worktree 主仓锚定 + 输出。
|
|
1331
1433
|
const ebChangeIdx = args.indexOf('--change');
|
|
1332
|
-
const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] ? args[ebChangeIdx + 1] : null;
|
|
1434
|
+
const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] && !String(args[ebChangeIdx + 1]).startsWith("--") ? args[ebChangeIdx + 1] : null;
|
|
1333
1435
|
if (!ebChange) {
|
|
1334
1436
|
console.error('用法: sillyspec endpoints baseline --change <name> [--spec-dir <path>] [--json]\n 拍变更前端点基线(全仓现扫,幂等已拍过不覆盖)→ .runtime/endpoint-baselines/<change>.json;归档 delta 端点增删的 before 侧。\n execute worktree 内跑:扫描根与落点自动锚定主仓(变更前代码在主仓;worktree 内是交付态且其 .runtime 随清理丢失)');
|
|
1335
1437
|
process.exit(2);
|
|
@@ -1397,14 +1499,14 @@ async function main() {
|
|
|
1397
1499
|
process.exit(2);
|
|
1398
1500
|
}
|
|
1399
1501
|
const epChangeIdx = args.indexOf('--change');
|
|
1400
|
-
const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] ? args[epChangeIdx + 1] : null;
|
|
1502
|
+
const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] && !String(args[epChangeIdx + 1]).startsWith("--") ? args[epChangeIdx + 1] : null;
|
|
1401
1503
|
const epTaskIdx = args.indexOf('--task');
|
|
1402
|
-
const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] ? args[epTaskIdx + 1] : null;
|
|
1504
|
+
const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] && !String(args[epTaskIdx + 1]).startsWith("--") ? args[epTaskIdx + 1] : null;
|
|
1403
1505
|
const epAllTasks = args.includes('--all-tasks');
|
|
1404
1506
|
const epDirIdx = args.indexOf('--dir');
|
|
1405
|
-
const epDir = epDirIdx >= 0 && args[epDirIdx + 1] ? args[epDirIdx + 1] : null;
|
|
1507
|
+
const epDir = epDirIdx >= 0 && args[epDirIdx + 1] && !String(args[epDirIdx + 1]).startsWith("--") ? args[epDirIdx + 1] : null;
|
|
1406
1508
|
const epFilesIdx = args.indexOf('--files');
|
|
1407
|
-
const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] ? args[epFilesIdx + 1] : null;
|
|
1509
|
+
const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] && !String(args[epFilesIdx + 1]).startsWith("--") ? args[epFilesIdx + 1] : null;
|
|
1408
1510
|
if (!epChange) {
|
|
1409
1511
|
console.error('❌ 缺 --change <变更名>');
|
|
1410
1512
|
process.exit(2);
|
|
@@ -1552,7 +1654,7 @@ async function main() {
|
|
|
1552
1654
|
// author/created_at 只 warning,agent 手改还常忘。本命令补齐(git user + 当前时间),
|
|
1553
1655
|
// 已有两键的文件不动;有 frontmatter 就地插入,无则补一段。
|
|
1554
1656
|
const sfhProjectIdx = args.indexOf('--project');
|
|
1555
|
-
const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] ? args[sfhProjectIdx + 1] : null;
|
|
1657
|
+
const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] && !String(args[sfhProjectIdx + 1]).startsWith("--") ? args[sfhProjectIdx + 1] : null;
|
|
1556
1658
|
const { fixScanDocHeaders } = await import('./scan-postcheck.js');
|
|
1557
1659
|
const sfhResult = fixScanDocHeaders({ cwd: dir, specDir, project: sfhProject });
|
|
1558
1660
|
if (json) {
|
|
@@ -1572,7 +1674,7 @@ async function main() {
|
|
|
1572
1674
|
// plan.md)注册表生成逐 task <!--TODO--> 骨架;gate 拒绝未替换的占位(防骨架直接过门),
|
|
1573
1675
|
// agent 只需逐行填结论。已存在不覆盖(幂等,agent 产出优先)。
|
|
1574
1676
|
const siChangeIdx = args.indexOf('--change');
|
|
1575
|
-
const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] ? args[siChangeIdx + 1] : null;
|
|
1677
|
+
const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] && !String(args[siChangeIdx + 1]).startsWith("--") ? args[siChangeIdx + 1] : null;
|
|
1576
1678
|
if (!siChange) {
|
|
1577
1679
|
console.error('用法: sillyspec symbol-impact --change <name> [--spec-dir <path>]\n 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(已存在不覆盖);gate 拒绝未替换的占位行');
|
|
1578
1680
|
process.exit(2);
|
|
@@ -1608,7 +1710,7 @@ async function main() {
|
|
|
1608
1710
|
// 手写 proposal/requirements/decisions 的 frontmatter/章节骨架是格式返工根源——本命令
|
|
1609
1711
|
// 逐文件生成骨架(已存在不覆盖),agent 只填语义。与 design-init 同族(幂等)。
|
|
1610
1712
|
const fpChangeIdx = args.indexOf('--change');
|
|
1611
|
-
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] ? args[fpChangeIdx + 1] : null;
|
|
1713
|
+
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] && !String(args[fpChangeIdx + 1]).startsWith("--") ? args[fpChangeIdx + 1] : null;
|
|
1612
1714
|
if (!fpChange) {
|
|
1613
1715
|
console.error('用法: sillyspec fourpiece-init --change <name> [--json] [--spec-dir <path>]\n 生成 proposal.md/requirements.md/decisions.md 骨架(frontmatter+章节标题+模板占位;已存在不覆盖;design.md 归 design-init)');
|
|
1614
1716
|
process.exit(2);
|
|
@@ -1701,7 +1803,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1701
1803
|
// design-facts.js generateDesignSkeleton——本 case 只负责读盘/幂等保护/落盘)。已存在不覆盖
|
|
1702
1804
|
// (幂等,agent 产出优先),--force 覆盖重生成。
|
|
1703
1805
|
const diChangeIdx = args.indexOf('--change');
|
|
1704
|
-
const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] ? args[diChangeIdx + 1] : null;
|
|
1806
|
+
const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] && !String(args[diChangeIdx + 1]).startsWith("--") ? args[diChangeIdx + 1] : null;
|
|
1705
1807
|
const diForce = args.includes('--force');
|
|
1706
1808
|
if (!diChange) {
|
|
1707
1809
|
console.error('用法: sillyspec design-init --change <name> [--force] [--json] [--spec-dir <path>]\n 从变更 decisions.md 生成 design.md 十三章节骨架(决策追踪表按当前版本 D 条目预填;已存在不覆盖,--force 覆盖)');
|
|
@@ -1765,7 +1867,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1765
1867
|
// (重跑即刷新):与 design-init「agent 产出优先不覆盖」语义相反——delta 是源数据
|
|
1766
1868
|
// (verify-runs/module-map)时点快照,旧报告天然过期,重新生成即对齐。
|
|
1767
1869
|
const dlChangeIdx = args.indexOf('--change');
|
|
1768
|
-
const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] ? args[dlChangeIdx + 1] : null;
|
|
1870
|
+
const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] && !String(args[dlChangeIdx + 1]).startsWith("--") ? args[dlChangeIdx + 1] : null;
|
|
1769
1871
|
if (!dlChange) {
|
|
1770
1872
|
console.error('用法: sillyspec delta --change <name> [--spec-dir <path>] [--json]\n 生成变更 delta.md(Before/Delta/After 三段式;四源 fail-soft,缺源逐段降级注记;幂等覆盖重跑即刷新)');
|
|
1771
1873
|
process.exit(2);
|
|
@@ -1827,11 +1929,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1827
1929
|
// 调度者手动派独立子代理不写 marker 的死锁。docHash 由 CLI computeDocHash 算(部分实现 P6.1b defer)。
|
|
1828
1930
|
// 纯新增,不改 gate 语义。与 task 级 backfill-reviews 对称(stage 级等价物)。
|
|
1829
1931
|
const rsrChangeIdx = args.indexOf('--change');
|
|
1830
|
-
const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] ? args[rsrChangeIdx + 1] : null;
|
|
1932
|
+
const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] && !String(args[rsrChangeIdx + 1]).startsWith("--") ? args[rsrChangeIdx + 1] : null;
|
|
1831
1933
|
const rsrStageIdx = args.indexOf('--stage');
|
|
1832
|
-
const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] ? args[rsrStageIdx + 1] : null;
|
|
1934
|
+
const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] && !String(args[rsrStageIdx + 1]).startsWith("--") ? args[rsrStageIdx + 1] : null;
|
|
1833
1935
|
const rsrFromIdx = args.indexOf('--from');
|
|
1834
|
-
const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] ? args[rsrFromIdx + 1] : null;
|
|
1936
|
+
const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] && !String(args[rsrFromIdx + 1]).startsWith("--") ? args[rsrFromIdx + 1] : null;
|
|
1835
1937
|
// docHash 联动(坑 stage-review-dochash-manual-resync,2026-08-21 实证):改一版 design 要
|
|
1836
1938
|
// 重算 2-3 个 stage review 的 docHash,手工易漏、gate 报错才补。--refresh-hash 就地刷新单个
|
|
1837
1939
|
// 既有 review 的 hash(保留结论);--all 一次处理三个 stage(有 review 刷 hash,无 review
|
|
@@ -1940,13 +2042,14 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1940
2042
|
// --dry-run 列独立存在(「报告修复预览 + exit 1」),即 dryRun 置位 = fix 语义自动生效
|
|
1941
2043
|
// 但零写盘(本口径记录于此,防歧义)。
|
|
1942
2044
|
const BARE_FLAGS = ['--fix', '--dry-run', '--no-exempt'];
|
|
1943
|
-
const PAIRED_FLAGS = ['--paths'];
|
|
2045
|
+
const PAIRED_FLAGS = ['--paths', '--against'];
|
|
1944
2046
|
const rawDocsArgs = filteredArgs.slice(2);
|
|
1945
2047
|
const docsCheckFlags = [];
|
|
1946
2048
|
let cliPaths = null;
|
|
1947
2049
|
let fix = false;
|
|
1948
2050
|
let dryRun = false;
|
|
1949
2051
|
let noExempt = false;
|
|
2052
|
+
let againstRef = null;
|
|
1950
2053
|
for (let i = 0; i < rawDocsArgs.length; i++) {
|
|
1951
2054
|
const a = rawDocsArgs[i];
|
|
1952
2055
|
if (a === '--paths' && rawDocsArgs[i + 1] !== undefined) {
|
|
@@ -1955,6 +2058,13 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1955
2058
|
} else if (a === '--paths') {
|
|
1956
2059
|
console.error('❌ docs check: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
1957
2060
|
process.exit(2);
|
|
2061
|
+
} else if (a === '--against' && rawDocsArgs[i + 1] !== undefined && !rawDocsArgs[i + 1].startsWith('--')) {
|
|
2062
|
+
// 坑 docs-gate-shared-worktree-parallel-block:按提交树校验,隔离并行在途编辑(移动靶)
|
|
2063
|
+
againstRef = rawDocsArgs[i + 1];
|
|
2064
|
+
i++;
|
|
2065
|
+
} else if (a === '--against') {
|
|
2066
|
+
console.error('❌ docs check: --against 缺值(如 --against HEAD)');
|
|
2067
|
+
process.exit(2);
|
|
1958
2068
|
} else if (BARE_FLAGS.includes(a)) {
|
|
1959
2069
|
if (a === '--fix') fix = true;
|
|
1960
2070
|
else if (a === '--dry-run') dryRun = true;
|
|
@@ -1966,6 +2076,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1966
2076
|
docsCheckFlags.push(a);
|
|
1967
2077
|
}
|
|
1968
2078
|
}
|
|
2079
|
+
if ((fix || dryRun) && againstRef) {
|
|
2080
|
+
// --fix 操作工作区磁盘内容,against 语义与其矛盾(修的是未提交文本、校验的是提交树)
|
|
2081
|
+
console.error('❌ docs check: --fix/--dry-run 与 --against 互斥(fix 恒操作工作区,against 恒校验提交树)');
|
|
2082
|
+
process.exit(2);
|
|
2083
|
+
}
|
|
1969
2084
|
const { runDocsCheck, readDocsCheckConfig, DocsCheckConfigError, applyFixes, runChangeNameAdvisory } = await import('./docs-check.js');
|
|
1970
2085
|
try {
|
|
1971
2086
|
// 配置优先级:--paths > local.yaml docs-check.paths > 缺省 docs/**/*.md
|
|
@@ -1978,6 +2093,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1978
2093
|
keywordAssert: cfg.keywordAssert,
|
|
1979
2094
|
crossRepoRoots: cfg.crossRepoRoots,
|
|
1980
2095
|
exempt: !noExempt,
|
|
2096
|
+
against: againstRef,
|
|
1981
2097
|
});
|
|
1982
2098
|
// task-03 修复链路(FR-04,platform-map-auto-anchors):fixActive 才构造 fixes——
|
|
1983
2099
|
// 仅 fix.fixable===true 且 newLine 为整数的条目可重锚;newRef = ref 的行号部分替换为
|
|
@@ -2152,11 +2268,14 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2152
2268
|
// usage 输出且 exit 0——gate 形同虚设(docs-signals 范围内文件+hook 已就位,仅此分支缺)。
|
|
2153
2269
|
// B9(2026-08-16):flag 白名单化(对齐 docs check 分支)——未知 --xxx 显式 exit 2
|
|
2154
2270
|
// (interface-contract §1.3b 宣称),并接线 --paths 透传 runDocsGate.checkOpts(原被忽略)。
|
|
2271
|
+
// 坑 docs-gate-stale-baseline(ql-20260915-004):超基线分支实测 origin/main 兜底——
|
|
2272
|
+
// 未劣于远端放行 + 提示重锚;--json 输出含 originCount 字段(null=未实测/快路径)。
|
|
2155
2273
|
const GATE_BARE_FLAGS = ['--init-baseline'];
|
|
2156
|
-
const GATE_PAIRED_FLAGS = ['--paths'];
|
|
2274
|
+
const GATE_PAIRED_FLAGS = ['--paths', '--against'];
|
|
2157
2275
|
const rawGateArgs = filteredArgs.slice(2);
|
|
2158
2276
|
let initBaseline = false;
|
|
2159
2277
|
let cliGatePaths = null;
|
|
2278
|
+
let gateAgainst = null;
|
|
2160
2279
|
for (let i = 0; i < rawGateArgs.length; i++) {
|
|
2161
2280
|
const a = rawGateArgs[i];
|
|
2162
2281
|
if (a === '--paths' && rawGateArgs[i + 1] !== undefined) {
|
|
@@ -2165,13 +2284,21 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2165
2284
|
} else if (a === '--paths') {
|
|
2166
2285
|
console.error('❌ docs gate: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
2167
2286
|
process.exit(2);
|
|
2287
|
+
} else if (a === '--against' && rawGateArgs[i + 1] !== undefined && !rawGateArgs[i + 1].startsWith('--')) {
|
|
2288
|
+
// 坑 docs-gate-shared-worktree-parallel-block:按提交树校验(pre-push 语义——推的
|
|
2289
|
+
// 是提交不是工作区),隔离并行会话在途编辑造成的锚点瞬时漂移(移动靶)
|
|
2290
|
+
gateAgainst = rawGateArgs[i + 1];
|
|
2291
|
+
i++;
|
|
2292
|
+
} else if (a === '--against') {
|
|
2293
|
+
console.error('❌ docs gate: --against 缺值(如 --against HEAD)');
|
|
2294
|
+
process.exit(2);
|
|
2168
2295
|
} else if (GATE_BARE_FLAGS.includes(a)) {
|
|
2169
2296
|
if (a === '--init-baseline') initBaseline = true;
|
|
2170
2297
|
} else if (a.startsWith('--')) {
|
|
2171
2298
|
console.error(`❌ docs gate: 未知 flag「${a}」。已知 flag:${[...GATE_BARE_FLAGS, ...GATE_PAIRED_FLAGS].join(' ')}(--json 为全局 flag)`);
|
|
2172
2299
|
process.exit(2);
|
|
2173
2300
|
} else {
|
|
2174
|
-
console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--json]`);
|
|
2301
|
+
console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--against HEAD] [--json]`);
|
|
2175
2302
|
process.exit(2);
|
|
2176
2303
|
}
|
|
2177
2304
|
}
|
|
@@ -2181,7 +2308,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2181
2308
|
const gateSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
2182
2309
|
const g = await runDocsGate(
|
|
2183
2310
|
{ projectRoot: dir, specBase: gateSpecBase, initBaseline },
|
|
2184
|
-
cliGatePaths ? { paths: cliGatePaths } :
|
|
2311
|
+
{ ...(cliGatePaths ? { paths: cliGatePaths } : {}), ...(gateAgainst ? { against: gateAgainst } : {}) }
|
|
2185
2312
|
);
|
|
2186
2313
|
if (json) {
|
|
2187
2314
|
console.log(JSON.stringify(g, null, 2));
|
|
@@ -2237,7 +2364,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2237
2364
|
const dumpDbFlag = filteredArgs.includes('--dump-db');
|
|
2238
2365
|
const doctorConfirm = filteredArgs.includes('--confirm');
|
|
2239
2366
|
const pathIdx = filteredArgs.indexOf('--path');
|
|
2240
|
-
const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] ? filteredArgs[pathIdx + 1] : null;
|
|
2367
|
+
const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null;
|
|
2241
2368
|
|
|
2242
2369
|
// --align-execute-progress:基于 plan.md 声明对齐 execute 派生戳(仿 --cleanup-remnant 范式)
|
|
2243
2370
|
// 默认 dry-run(只报告将补哪些 step),加 --confirm 才写。命中即 break,绝不 fall-through。
|
|
@@ -2245,7 +2372,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2245
2372
|
if (alignFlag) {
|
|
2246
2373
|
// --change 解析:显式优先,缺省用单活跃变更自动兜底(与 run.js resolveChangeNameAuto 同逻辑)
|
|
2247
2374
|
const alignChangeIdx = filteredArgs.indexOf('--change');
|
|
2248
|
-
let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] ? filteredArgs[alignChangeIdx + 1] : null;
|
|
2375
|
+
let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] && !String(filteredArgs[alignChangeIdx + 1]).startsWith("--") ? filteredArgs[alignChangeIdx + 1] : null;
|
|
2249
2376
|
if (!alignChange) {
|
|
2250
2377
|
const changesDir = join(resolvePlatformSpecDir(doctorEffectiveDir, specDir) || join(doctorEffectiveDir, '.sillyspec'), 'changes');
|
|
2251
2378
|
if (existsSync(changesDir)) {
|
|
@@ -2417,8 +2544,8 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2417
2544
|
const r = buildAndWriteScanFacts({
|
|
2418
2545
|
cwd: factsEffectiveDir,
|
|
2419
2546
|
specDir: resolvePlatformSpecDir(dir, specDir) || join(factsEffectiveDir, '.sillyspec'),
|
|
2420
|
-
projectName: projIdx >= 0 && filteredArgs[projIdx + 1] ? filteredArgs[projIdx + 1] : basename(factsEffectiveDir),
|
|
2421
|
-
projectPath: pathIdx >= 0 && filteredArgs[pathIdx + 1] ? filteredArgs[pathIdx + 1] : null,
|
|
2547
|
+
projectName: projIdx >= 0 && filteredArgs[projIdx + 1] && !String(filteredArgs[projIdx + 1]).startsWith("--") ? filteredArgs[projIdx + 1] : basename(factsEffectiveDir),
|
|
2548
|
+
projectPath: pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null,
|
|
2422
2549
|
})
|
|
2423
2550
|
const f = r.facts
|
|
2424
2551
|
console.log(`scan facts 已写入: ${r.outPath}`)
|
|
@@ -2431,7 +2558,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2431
2558
|
if (filteredArgs[1] === 'diff') {
|
|
2432
2559
|
const { runScanDiff } = await import('./scan-diff.js')
|
|
2433
2560
|
const diffBaseIdx = filteredArgs.indexOf('--base')
|
|
2434
|
-
const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] ? filteredArgs[diffBaseIdx + 1] : null
|
|
2561
|
+
const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] && !String(filteredArgs[diffBaseIdx + 1]).startsWith("--") ? filteredArgs[diffBaseIdx + 1] : null
|
|
2435
2562
|
// specBase/projectName 与 scan 主流程同口径(scan-profile.js:specBase=进度库根,projectName=仓库根 basename)。
|
|
2436
2563
|
// A4 同族:平台指针 / 显式 --spec-dir / 本地 fallback 三合一,平台模式下 diff 不落本地孤儿库。
|
|
2437
2564
|
const diffEffectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
@@ -2445,6 +2572,40 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2445
2572
|
})
|
|
2446
2573
|
break
|
|
2447
2574
|
}
|
|
2575
|
+
// scan refresh 子命令(2026-09-14-scan-incremental-refresh D-002@v1:增量刷新两拍交互)。
|
|
2576
|
+
// ①拍纯读出工单(含 guard 握手写);②拍 --done 盖章收尾。与 diff 同为旁路:跳过
|
|
2577
|
+
// triggerPullActiveChange、不走 scan 主流程。--project 支持子项目(缺省仓库根 basename,
|
|
2578
|
+
// 与 scan facts 同口径)。
|
|
2579
|
+
if (filteredArgs[1] === 'refresh') {
|
|
2580
|
+
const { runRefresh, finalizeRefresh } = await import('./scan-refresh.js')
|
|
2581
|
+
const refreshEffectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
2582
|
+
const refreshSpecBase = resolvePlatformSpecDir(dir, specDir) || join(refreshEffectiveDir, '.sillyspec')
|
|
2583
|
+
const projIdxR = filteredArgs.indexOf('--project')
|
|
2584
|
+
const refreshProjectName = projIdxR >= 0 && filteredArgs[projIdxR + 1] && !String(filteredArgs[projIdxR + 1]).startsWith("--") ? filteredArgs[projIdxR + 1] : basename(refreshEffectiveDir)
|
|
2585
|
+
if (filteredArgs.includes('--done')) {
|
|
2586
|
+
const docsIdx = filteredArgs.indexOf('--docs')
|
|
2587
|
+
const docsList = docsIdx >= 0 && filteredArgs[docsIdx + 1] && !String(filteredArgs[docsIdx + 1]).startsWith("--")
|
|
2588
|
+
? filteredArgs[docsIdx + 1].split(',').map(s => s.trim()).filter(Boolean) : null
|
|
2589
|
+
const r = await finalizeRefresh({
|
|
2590
|
+
projectRoot: refreshEffectiveDir,
|
|
2591
|
+
specBase: refreshSpecBase,
|
|
2592
|
+
projectName: refreshProjectName,
|
|
2593
|
+
docs: docsList,
|
|
2594
|
+
force: filteredArgs.includes('--force'),
|
|
2595
|
+
platformOpts: platformWorkspaceId || platformRuntimeRoot ? { workspaceId: platformWorkspaceId, runtimeRoot: platformRuntimeRoot, specRoot: resolvePlatformSpecDir(dir, specDir) } : null,
|
|
2596
|
+
})
|
|
2597
|
+
process.exitCode = r.code
|
|
2598
|
+
} else {
|
|
2599
|
+
process.exitCode = await runRefresh({
|
|
2600
|
+
projectRoot: refreshEffectiveDir,
|
|
2601
|
+
specBase: refreshSpecBase,
|
|
2602
|
+
projectName: refreshProjectName,
|
|
2603
|
+
force: filteredArgs.includes('--force'),
|
|
2604
|
+
json: filteredArgs.includes('--json'),
|
|
2605
|
+
})
|
|
2606
|
+
}
|
|
2607
|
+
break
|
|
2608
|
+
}
|
|
2448
2609
|
}
|
|
2449
2610
|
case 'status':
|
|
2450
2611
|
case 'quick':
|
|
@@ -2496,14 +2657,82 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2496
2657
|
console.log('按 Ctrl+C 停止服务器');
|
|
2497
2658
|
break;
|
|
2498
2659
|
}
|
|
2660
|
+
case 'wt-commit': {
|
|
2661
|
+
// 坑 wt-commit-ghost-dispatch:runWtCommit 模块自 bd1cb91 存在但 dispatch 从未接线——
|
|
2662
|
+
// execute Wave prompt 指示的 `sillyspec wt-commit` 一直报「未知命令」(2026-09-14
|
|
2663
|
+
// knowledge-loop-close execute W1 实证,ql-20260914-016-8786 根治)。参数面见模块头:
|
|
2664
|
+
// wt-commit [--change <名>] -m <信息> [--pathspec-from-file <f>] -- <path...>;
|
|
2665
|
+
// worktree 内可省 --change(从 cwd 的 worktrees/<名> 段推断)。
|
|
2666
|
+
const wtCommitRest = filteredArgs.slice(1);
|
|
2667
|
+
const wtCommitDd = wtCommitRest.indexOf('--');
|
|
2668
|
+
const wtCommitFlags = wtCommitDd === -1 ? wtCommitRest : wtCommitRest.slice(0, wtCommitDd);
|
|
2669
|
+
const wtCommitPaths = wtCommitDd === -1 ? [] : wtCommitRest.slice(wtCommitDd + 1);
|
|
2670
|
+
let wtCommitChange = null;
|
|
2671
|
+
let wtCommitMessage = null;
|
|
2672
|
+
let wtCommitPathspecFile = null;
|
|
2673
|
+
for (let i = 0; i < wtCommitFlags.length; i++) {
|
|
2674
|
+
if (wtCommitFlags[i] === '--change' && wtCommitFlags[i + 1]) wtCommitChange = wtCommitFlags[++i];
|
|
2675
|
+
else if ((wtCommitFlags[i] === '-m' || wtCommitFlags[i] === '--message') && wtCommitFlags[i + 1]) wtCommitMessage = wtCommitFlags[++i];
|
|
2676
|
+
else if (wtCommitFlags[i] === '--pathspec-from-file' && wtCommitFlags[i + 1]) wtCommitPathspecFile = wtCommitFlags[++i];
|
|
2677
|
+
}
|
|
2678
|
+
if (!wtCommitChange) {
|
|
2679
|
+
const inferred = String(dir).match(/[\\/]worktrees[\\/]([^\\/]+)/);
|
|
2680
|
+
wtCommitChange = inferred ? inferred[1] : null;
|
|
2681
|
+
}
|
|
2682
|
+
try {
|
|
2683
|
+
const { runWtCommit } = await import('./wt-commit.js');
|
|
2684
|
+
const r = await runWtCommit({
|
|
2685
|
+
changeName: wtCommitChange,
|
|
2686
|
+
message: wtCommitMessage,
|
|
2687
|
+
pathspecs: wtCommitPaths,
|
|
2688
|
+
pathspecFile: wtCommitPathspecFile,
|
|
2689
|
+
cwd: dir,
|
|
2690
|
+
});
|
|
2691
|
+
if (json) console.log(JSON.stringify({ command: 'wt-commit', ...r }, null, 2));
|
|
2692
|
+
else if (r.skipped) console.log(`⏭️ wt-commit 无变更跳过(HEAD 不动):${r.shortHead}`);
|
|
2693
|
+
else console.log(`✅ wt-commit 完成:${r.shortHead}(${r.files.length} 文件,worktree=${r.worktreePath})`);
|
|
2694
|
+
} catch (e) {
|
|
2695
|
+
console.error(`❌ wt-commit 失败:${e.message}`);
|
|
2696
|
+
process.exit(1);
|
|
2697
|
+
}
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2499
2700
|
case 'worktree': {
|
|
2500
2701
|
const { WorktreeManager } = await import('./worktree.js');
|
|
2501
|
-
const { ProgressManager } = await import('./progress.js');
|
|
2702
|
+
const { ProgressManager, resolveSessionIdentity } = await import('./progress.js');
|
|
2502
2703
|
const wtSubCmd = filteredArgs[1];
|
|
2503
2704
|
const wtName = filteredArgs.slice(2).find(a => !a.startsWith('-'));
|
|
2504
2705
|
const wm = new WorktreeManager({ cwd: dir });
|
|
2505
2706
|
const pm = new ProgressManager({ specDir: resolvePlatformSpecDir(dir, specDir) });
|
|
2506
2707
|
|
|
2708
|
+
// ── 所有权护栏公共件(2026-09-14-change-ownership-guards task-02 / FR-01 / D-001@v1)──
|
|
2709
|
+
// 接管类操作(apply / cleanup / assess 自动 apply)改主仓前校验 change 归属:
|
|
2710
|
+
// 他人且活跃(活跃窗内,heartbeat_minutes 缺省 15min)→ 拒绝;无主 / 窗口外 / 本会话
|
|
2711
|
+
// 自有 / --takeover → 放行,接管分支重写 owner=本会话(setChangeOwner 含 last_active
|
|
2712
|
+
// 心跳起算)。调用点必须在 withMainRepoLock 锁内(判定与执行无 TOCTOU);只读路径
|
|
2713
|
+
// (diff/list/meta/doctor/--check-only)不接。会话标识三级解析:--session flag >
|
|
2714
|
+
// env SILLYSPEC_SESSION_ID > anon@host 机器级降级(同机并行不设防是 R-01 明示局限)。
|
|
2715
|
+
const sessionFlagIdx = args.indexOf('--session');
|
|
2716
|
+
const sessionFlagValue = (sessionFlagIdx !== -1 && args[sessionFlagIdx + 1]
|
|
2717
|
+
&& !String(args[sessionFlagIdx + 1]).startsWith('--')) ? String(args[sessionFlagIdx + 1]).trim() : null;
|
|
2718
|
+
// soft=true:assess 自动路径拒绝语义(跳过自动落盘 + warning 不抛错,同 overlapSkipped 软跳);
|
|
2719
|
+
// soft=false:显式 apply/cleanup 拒绝(结构化错误,调用方 exit 非零)。
|
|
2720
|
+
function _guardChangeOwnership(changeName, { forced = false, soft = false } = {}) {
|
|
2721
|
+
const { session } = resolveSessionIdentity({ flagSession: sessionFlagValue, cwd: dir });
|
|
2722
|
+
const check = pm.assertChangeOwnership(dir, changeName, { selfSession: session, nowMs: Date.now(), forced });
|
|
2723
|
+
if (check.allowed) {
|
|
2724
|
+
if (check.action !== 'self') pm.setChangeOwner(dir, changeName, session);
|
|
2725
|
+
return { allowed: true, action: check.action };
|
|
2726
|
+
}
|
|
2727
|
+
const lines = [
|
|
2728
|
+
`变更 ${changeName} 正被其他会话持有(owner: ${check.owner},最后活跃: ${check.lastActive || '未知'}),活跃窗口 ${Math.round(check.heartbeatMs / 60000)} 分钟内拒绝接管类操作`,
|
|
2729
|
+
`确认对方已放弃且本会话应接管:加 --takeover 显式强制接管(重写 owner 并留痕);或等对方会话收尾后重试`,
|
|
2730
|
+
];
|
|
2731
|
+
if (soft) { for (const l of lines) console.warn(`⚠️ ${l}`); }
|
|
2732
|
+
else { for (const l of lines) console.error(`🚫 ${l}`); }
|
|
2733
|
+
return { allowed: false, action: check.action };
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2507
2736
|
// isolation 写入 DB 的辅助函数
|
|
2508
2737
|
function _writeIsolationToDB(cwd, changeName, info) {
|
|
2509
2738
|
if (info.blocked) {
|
|
@@ -2528,17 +2757,19 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2528
2757
|
|
|
2529
2758
|
用法:
|
|
2530
2759
|
sillyspec worktree create <change-name> [--base <branch>] [--adopt-branch] 创建隔离 worktree(--adopt-branch:收编既有同名分支为工作分支,分支现状作 baseline)
|
|
2531
|
-
sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty] 校验并应用变更到主工作区(--stash-dirty:主仓在途改动自动 stash→apply→恢复,SHA
|
|
2532
|
-
sillyspec worktree assess <change-name> 风险审计 + 自动 apply
|
|
2760
|
+
sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty] [--force] [--takeover] 校验并应用变更到主工作区(--stash-dirty:主仓在途改动自动 stash→apply→恢复,SHA 兜底可审计;--force:越过活跃 quick 会话 guard 相交预检,overlapForced 留痕;--takeover:越过所有权护栏显式强制接管,owner 重写留痕)
|
|
2761
|
+
sillyspec worktree assess <change-name> 风险审计 + 自动 apply(自动路径受所有权护栏约束:他人活跃 change 跳过自动落盘并 warning)
|
|
2533
2762
|
sillyspec worktree diff <change-name> [--base <commit>] 查看 worktree 相对 base 的变更
|
|
2534
2763
|
sillyspec worktree list 列出所有活跃 worktree
|
|
2535
2764
|
sillyspec worktree meta <change-name> 读取 worktree meta.json
|
|
2536
|
-
sillyspec worktree cleanup <change-name> [--force]
|
|
2765
|
+
sillyspec worktree cleanup <change-name> [--force] [--takeover] 强制清理 worktree(受所有权护栏约束:他人活跃 change 拒绝,--takeover 显式接管)
|
|
2537
2766
|
sillyspec worktree doctor [--fix] [--stale-hours N] [--change <name>] 健康检查 + 修复(--change 仅扫指定 change)
|
|
2538
2767
|
|
|
2539
2768
|
选项:
|
|
2540
2769
|
--base <branch> create: 指定基础分支(默认当前 HEAD)
|
|
2541
2770
|
--check-only apply: 只输出检查结果,不实际 apply
|
|
2771
|
+
--takeover apply/cleanup: 所有权护栏拒绝时的显式强制接管口(owner 他人且活跃时解锁,重写 owner 留痕)
|
|
2772
|
+
--session <id> 显式会话标识(所有权三级解析最高优先级;缺省 env SILLYSPEC_SESSION_ID,再缺省 anon@<host> 机器级降级——同机并行不设防)
|
|
2542
2773
|
`);
|
|
2543
2774
|
break;
|
|
2544
2775
|
}
|
|
@@ -2553,7 +2784,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2553
2784
|
try { assertSafeChangeName(wtName, 'worktree 变更名'); }
|
|
2554
2785
|
catch (e) { console.error(`❌ ${e.message}`); process.exit(2); }
|
|
2555
2786
|
const baseIdx = args.indexOf('--base');
|
|
2556
|
-
const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
2787
|
+
const base = baseIdx >= 0 && args[baseIdx + 1] && !String(args[baseIdx + 1]).startsWith("--") ? args[baseIdx + 1] : undefined;
|
|
2557
2788
|
const adoptBranch = args.includes('--adopt-branch'); // 收编既有同名分支为工作分支(坑 worktree-user-branch-conflict)
|
|
2558
2789
|
try {
|
|
2559
2790
|
const info = wm.create(wtName, { base, adoptBranch });
|
|
@@ -2576,13 +2807,15 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2576
2807
|
}
|
|
2577
2808
|
case 'apply': {
|
|
2578
2809
|
if (!wtName) {
|
|
2579
|
-
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty]');
|
|
2810
|
+
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only] [--base merge-base|baseline] [--merge] [--skip-overlap] [--stash-dirty] [--force] [--takeover]');
|
|
2580
2811
|
process.exit(1);
|
|
2581
2812
|
}
|
|
2582
2813
|
const checkOnly = args.includes('--check-only');
|
|
2583
2814
|
const merge = args.includes('--merge');
|
|
2584
2815
|
const skipOverlap = args.includes('--skip-overlap');
|
|
2585
2816
|
const stashDirty = args.includes('--stash-dirty');
|
|
2817
|
+
const force = args.includes('--force'); // guard 相交预检显式解锁(task-02 / FR-03;人工 flag,自动路径永不带)
|
|
2818
|
+
const takeover = args.includes('--takeover'); // 所有权护栏显式强制接管(2026-09-14-change-ownership-guards task-02 / FR-01;人工 flag,自动路径永不带)
|
|
2586
2819
|
|
|
2587
2820
|
// 解析 --base 参数(默认 merge-base)
|
|
2588
2821
|
let base = 'merge-base';
|
|
@@ -2610,15 +2843,22 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2610
2843
|
}
|
|
2611
2844
|
// 主仓 apply 互斥锁(坑 main-apply-no-mutex):真 apply(非 checkOnly)在锁内跑——
|
|
2612
2845
|
// apply 链(rollback/merge/cleanup)直接改主仓工作区,两会话并发互踩会互相清文件。
|
|
2846
|
+
// 所有权护栏(2026-09-14-change-ownership-guards task-02 / FR-01):锁内、实际 apply 前
|
|
2847
|
+
// 判归属——他人且活跃(活跃窗内)拒绝;--check-only 是只读路径不校验(铁律:只读命令不接)。
|
|
2848
|
+
// 拒绝时结构化错误已在护栏内打印,经哨兵返回值带出锁外统一 exit 1(不进结果展示分支)。
|
|
2613
2849
|
let result;
|
|
2614
2850
|
try {
|
|
2615
2851
|
result = checkOnly
|
|
2616
|
-
? applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap, stashDirty })
|
|
2617
|
-
: await withMainRepoLock(dir, wtName, 'apply', () =>
|
|
2852
|
+
? applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap, stashDirty, force })
|
|
2853
|
+
: await withMainRepoLock(dir, wtName, 'apply', () => {
|
|
2854
|
+
if (!_guardChangeOwnership(wtName, { forced: takeover }).allowed) return { __ownershipDenied: true };
|
|
2855
|
+
return applyWorktree(wtName, { cwd: dir, checkOnly, merge, base, ctx: _applyCtx, skipOverlap, stashDirty, force });
|
|
2856
|
+
});
|
|
2618
2857
|
} catch (lockErr) {
|
|
2619
2858
|
console.error(`❌ ${lockErr.message}`);
|
|
2620
2859
|
process.exit(1);
|
|
2621
2860
|
}
|
|
2861
|
+
if (result && result.__ownershipDenied) process.exit(1);
|
|
2622
2862
|
|
|
2623
2863
|
if (result.errors.length > 0) {
|
|
2624
2864
|
console.error(`❌ 校验失败:`);
|
|
@@ -2754,15 +2994,29 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2754
2994
|
if (assessment.decision === 'SAFE' || assessment.decision === 'WARNING') {
|
|
2755
2995
|
console.log('Action: auto-applying...');
|
|
2756
2996
|
const { applyWorktree, withMainRepoLock } = await import('./worktree-apply.js');
|
|
2757
|
-
// 主仓互斥锁(与手动 apply 同款):自动 apply
|
|
2997
|
+
// 主仓互斥锁(与手动 apply 同款):自动 apply 同样改主仓工作区,必须互斥。
|
|
2998
|
+
// 所有权护栏(2026-09-14-change-ownership-guards task-02 / FR-01):assess 自动 apply
|
|
2999
|
+
// 是护栏①最可能的旁路(SAFE/WARNING 即自动改主仓+链内 cleanup)——锁内、实际动作前
|
|
3000
|
+
// 判归属。自动路径永不带 --takeover;他人活跃 change 拒绝 = 跳过自动落盘 + warning
|
|
3001
|
+
// 不抛错(同 overlapSkipped 软跳语义:无人值守不越权也不阻断审计流)。
|
|
2758
3002
|
let applyResult;
|
|
2759
3003
|
try {
|
|
2760
|
-
applyResult = await withMainRepoLock(dir, wtName, 'assess-auto-apply', () =>
|
|
3004
|
+
applyResult = await withMainRepoLock(dir, wtName, 'assess-auto-apply', () => {
|
|
3005
|
+
if (!_guardChangeOwnership(wtName, { soft: true }).allowed) return { ownershipSkipped: true };
|
|
3006
|
+
return applyWorktree(wtName, { cwd: dir, ctx: _assessCtx, autoApply: true });
|
|
3007
|
+
});
|
|
2761
3008
|
} catch (lockErr) {
|
|
2762
3009
|
console.error(`❌ 自动 apply 未执行:${lockErr.message}`);
|
|
2763
3010
|
break;
|
|
2764
3011
|
}
|
|
2765
|
-
if (applyResult.
|
|
3012
|
+
if (applyResult.ownershipSkipped) {
|
|
3013
|
+
// 所有权拒绝(结构化 warning 已在护栏内打印):跳过自动落盘,指引人工显式接管
|
|
3014
|
+
console.log(' → 确认对方会话已放弃:人工评估后显式接管 sillyspec worktree apply ' + wtName + ' --takeover(或等对方收尾后重跑 assess)');
|
|
3015
|
+
} else if (applyResult.overlapSkipped) {
|
|
3016
|
+
// guard 相交软跳过(task-02 / FR-03):无人值守不越权也不阻断审计流——不落盘,亮 warning 指引人工评估
|
|
3017
|
+
for (const w of applyResult.warnings || []) console.log(`⚠️ ${w}`);
|
|
3018
|
+
console.log(' → 人工评估后显式 apply: sillyspec worktree apply ' + wtName + '(确认与活跃 quick 会话无冲突;或等对方 --done 后重跑 assess)');
|
|
3019
|
+
} else if (applyResult.errors.length > 0) {
|
|
2766
3020
|
console.error('❌ apply 失败:', applyResult.errors.join('; '));
|
|
2767
3021
|
} else {
|
|
2768
3022
|
console.log(`✅ 已自动应用 ${applyResult.changedFiles.length} 个文件变更:`);
|
|
@@ -2855,15 +3109,23 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2855
3109
|
}
|
|
2856
3110
|
case 'cleanup': {
|
|
2857
3111
|
if (!wtName) {
|
|
2858
|
-
console.error('❌ 用法: sillyspec worktree cleanup <change-name>');
|
|
3112
|
+
console.error('❌ 用法: sillyspec worktree cleanup <change-name> [--force] [--takeover]');
|
|
2859
3113
|
process.exit(1);
|
|
2860
3114
|
}
|
|
2861
3115
|
const forceFlag = args.includes('--force');
|
|
3116
|
+
const takeover = args.includes('--takeover'); // 所有权护栏显式强制接管(task-02 / FR-01)
|
|
2862
3117
|
try {
|
|
2863
3118
|
// 主仓互斥锁(坑 main-repo-no-mutex 二批):cleanup 删 worktree 注册表/分支/目录,
|
|
2864
|
-
// 与并行会话的 apply/cleanup 互踩会互相清——与其他写主仓操作共用一把 main-repo.lock
|
|
3119
|
+
// 与并行会话的 apply/cleanup 互踩会互相清——与其他写主仓操作共用一把 main-repo.lock。
|
|
3120
|
+
// 所有权护栏(2026-09-14-change-ownership-guards task-02 / FR-01):锁内、实际清理前
|
|
3121
|
+
// 判归属——他人且活跃拒绝(结构化错误在护栏内打印,哨兵带出统一 exit 1,跨仓清理
|
|
3122
|
+
// 同步跳过);--takeover 显式强制接管放行。
|
|
2865
3123
|
const { withMainRepoLock } = await import('./worktree-apply.js');
|
|
2866
|
-
const result = await withMainRepoLock(dir, wtName, 'worktree-cleanup', () =>
|
|
3124
|
+
const result = await withMainRepoLock(dir, wtName, 'worktree-cleanup', () => {
|
|
3125
|
+
if (!_guardChangeOwnership(wtName, { forced: takeover }).allowed) return { __ownershipDenied: true };
|
|
3126
|
+
return wm.cleanup(wtName, { force: forceFlag });
|
|
3127
|
+
});
|
|
3128
|
+
if (result && result.__ownershipDenied) process.exit(1);
|
|
2867
3129
|
// 跨仓 worktree(坑 cross-repo-no-worktree-isolation):与主仓同锁清理——跨仓 worktree
|
|
2868
3130
|
// 注册在各跨仓仓 .git 内,但目录/meta 在主仓 .sillyspec 运行时区,同样怕并发互踩
|
|
2869
3131
|
const { cleanupCrossWorktrees } = await import('./worktree-cross.js');
|
|
@@ -2885,7 +3147,16 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2885
3147
|
}
|
|
2886
3148
|
} else if (result.result === 'skipped') {
|
|
2887
3149
|
console.log(`⏭️ worktree 跳过清理: ${wtName} (mode: ${result.mode})`);
|
|
2888
|
-
|
|
3150
|
+
// ql-20260915-003 修复②(坑 cleanup-meta-fallback,2026-09-15 实证「mode: null
|
|
3151
|
+
// 跳过清理」但 worktree 实际创建过):早退跳过(meta/目录/git 注册三路探针全未
|
|
3152
|
+
// 命中)时,误导性的「in-place 模式」文案升级为诊断式——列三路探针路径 + meta
|
|
3153
|
+
// 丢失可能原因 + doctor 指引。probePaths 由 cleanup 兜底探测带回。
|
|
3154
|
+
if (Array.isArray(result.probePaths) && result.probePaths.length > 0) {
|
|
3155
|
+
for (const p of result.probePaths) console.log(` 探针: ${p}`);
|
|
3156
|
+
console.log(` meta 丢失可能原因:apply/cleanup 后随目录删除、并行会话已清理、cwd/specDir 漂移。可用 sillyspec worktree doctor 检查 git 注册与分支残留。`);
|
|
3157
|
+
} else {
|
|
3158
|
+
console.log(` 原因: in-place 模式没有隔离目录需要清理`);
|
|
3159
|
+
}
|
|
2889
3160
|
} else if (result.result === 'blocked') {
|
|
2890
3161
|
console.error(`🚫 拒绝清理:有未落主仓交付变更,请先 sillyspec worktree apply ${wtName} 或 --force`);
|
|
2891
3162
|
} else if (result.result === 'partial') {
|
|
@@ -2907,7 +3178,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2907
3178
|
const staleIdx = args.indexOf('--stale-hours');
|
|
2908
3179
|
const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
|
|
2909
3180
|
const changeIdx = args.indexOf('--change');
|
|
2910
|
-
const changeName = changeIdx !== -1 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
|
|
3181
|
+
const changeName = changeIdx !== -1 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
|
|
2911
3182
|
const diag = await wm.doctor({ fix: fixFlag, staleHours, changeName });
|
|
2912
3183
|
if (diag.issues.length === 0) {
|
|
2913
3184
|
console.log('✅ worktree 健康检查通过,无异常');
|
|
@@ -2990,7 +3261,7 @@ SillySpec dispatch — SillyHub 派发能力探测与策略生成(agent 调用
|
|
|
2990
3261
|
if (dispatchSubCmd === 'hint') {
|
|
2991
3262
|
// 解析 --contract <json>(仿 worktree 用 args.indexOf 取 flag 后一个值)
|
|
2992
3263
|
const contractIdx = args.indexOf('--contract');
|
|
2993
|
-
const contractRaw = contractIdx >= 0 && args[contractIdx + 1] ? args[contractIdx + 1] : null;
|
|
3264
|
+
const contractRaw = contractIdx >= 0 && args[contractIdx + 1] && !String(args[contractIdx + 1]).startsWith("--") ? args[contractIdx + 1] : null;
|
|
2994
3265
|
if (!contractRaw) {
|
|
2995
3266
|
console.error('用法: sillyspec dispatch hint --contract <json> [--json]\n 生成派发指令(DispatchContract:brief/worktreePath/branch/allowedPaths/readOnly/...)');
|
|
2996
3267
|
process.exit(2);
|
|
@@ -3062,7 +3333,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3062
3333
|
// join(runtimeDir, `sync-conflict-${changeName}.json`) 可被 ../ 穿越出 .runtime
|
|
3063
3334
|
{
|
|
3064
3335
|
const pChangeIdx = args.indexOf('--change');
|
|
3065
|
-
const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] ? args[pChangeIdx + 1] : null;
|
|
3336
|
+
const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] && !String(args[pChangeIdx + 1]).startsWith("--") ? args[pChangeIdx + 1] : null;
|
|
3066
3337
|
if (pChangeVal) assertSafeChangeName(pChangeVal, 'platform --change 变更名');
|
|
3067
3338
|
}
|
|
3068
3339
|
|
|
@@ -3130,7 +3401,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3130
3401
|
process.exit(1);
|
|
3131
3402
|
}
|
|
3132
3403
|
const tokenIdx = args.indexOf('--token');
|
|
3133
|
-
const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
|
|
3404
|
+
const token = tokenIdx >= 0 && args[tokenIdx + 1] && !String(args[tokenIdx + 1]).startsWith("--") ? args[tokenIdx + 1] : undefined;
|
|
3134
3405
|
if (!token) {
|
|
3135
3406
|
// 缺 token 直接终止(体检 HUB-02):交互式输入尚未实现(task-11),此前
|
|
3136
3407
|
// 继续执行会把 undefined 序列化成字符串 "undefined" 写入 local.yaml,
|
|
@@ -3142,17 +3413,20 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3142
3413
|
break;
|
|
3143
3414
|
}
|
|
3144
3415
|
case 'disconnect':
|
|
3145
|
-
|
|
3416
|
+
// --keep-pointer(坑 platform-sync-progress-rollback-and-db-corruption 坑1③):只清
|
|
3417
|
+
// local.yaml platform 段,保留指针/声明给 daemon 内嵌 CLI——回滚环恢复序列正规出口
|
|
3418
|
+
await syncModule.disconnect(dir, { keepPointer: filteredArgs.includes('--keep-pointer') });
|
|
3146
3419
|
break;
|
|
3147
3420
|
case 'sync': {
|
|
3148
3421
|
const syncChangeIdx = args.indexOf('--change');
|
|
3149
|
-
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
|
|
3150
|
-
|
|
3422
|
+
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] && !String(args[syncChangeIdx + 1]).startsWith("--") ? args[syncChangeIdx + 1] : null;
|
|
3423
|
+
// manual:用户显式同步,change_deleted 回执噪音闸旁路(显式动作的回执必须可见)
|
|
3424
|
+
await syncModule.sync(syncChangeName, dir, { manual: true });
|
|
3151
3425
|
break;
|
|
3152
3426
|
}
|
|
3153
3427
|
case 'sync-docs': {
|
|
3154
3428
|
const syncDocsChangeIdx = args.indexOf('--change');
|
|
3155
|
-
const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
|
|
3429
|
+
const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] && !String(args[syncDocsChangeIdx + 1]).startsWith("--") ? args[syncDocsChangeIdx + 1] : null;
|
|
3156
3430
|
await syncModule.syncDocuments(syncDocsChangeName, dir);
|
|
3157
3431
|
break;
|
|
3158
3432
|
}
|
|
@@ -3187,7 +3461,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3187
3461
|
// task-11 / D-006 / D-009 / FR-03:手动下行拉取平台进度到本地。
|
|
3188
3462
|
// 与自动 triggerPull 共用 syncModule.pull(SyncManager.pull 实例方法),行为一致。
|
|
3189
3463
|
const pullChangeIdx = args.indexOf('--change');
|
|
3190
|
-
const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] ? args[pullChangeIdx + 1] : null;
|
|
3464
|
+
const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] && !String(args[pullChangeIdx + 1]).startsWith("--") ? args[pullChangeIdx + 1] : null;
|
|
3191
3465
|
// 未连接明确提示不崩(acceptance:未连接平台输出明确提示)
|
|
3192
3466
|
const smForCheck = new syncModule.SyncManager(dir);
|
|
3193
3467
|
if (!smForCheck._getPlatform()) {
|
|
@@ -3231,7 +3505,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3231
3505
|
// 第一个非 flag 位置参数 → 唯一未决冲突自动选中。修复:旧实现盲取 platformArgs[0],
|
|
3232
3506
|
// flag 放前面时(resolve --keep-local --change x)把 '--keep-local' 当变更名去读冲突文件。
|
|
3233
3507
|
const changeIdx = args.indexOf('--change');
|
|
3234
|
-
let resolveName = changeIdx >= 0 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
|
|
3508
|
+
let resolveName = changeIdx >= 0 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
|
|
3235
3509
|
if (!resolveName) {
|
|
3236
3510
|
resolveName = platformArgs.find((a) => a && !a.startsWith('--')) || null;
|
|
3237
3511
|
}
|
|
@@ -3297,7 +3571,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3297
3571
|
process.exit(1);
|
|
3298
3572
|
}
|
|
3299
3573
|
const reasonIdx = args.indexOf('--reason');
|
|
3300
|
-
const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
|
|
3574
|
+
const reason = reasonIdx >= 0 && args[reasonIdx + 1] && !String(args[reasonIdx + 1]).startsWith("--") ? args[reasonIdx + 1] : undefined;
|
|
3301
3575
|
await syncModule.reject(rejectName, reason, dir);
|
|
3302
3576
|
break;
|
|
3303
3577
|
}
|
|
@@ -3321,7 +3595,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3321
3595
|
// task review / stage review / module-map)+ 轻形状检查(required-evidence / endpoints)
|
|
3322
3596
|
// 为一把伞——「新解析器写之前先过 schema」的统一落点。skip = 未生成不算失败。
|
|
3323
3597
|
const vaChangeIdx = args.indexOf('--change');
|
|
3324
|
-
const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] ? args[vaChangeIdx + 1] : null;
|
|
3598
|
+
const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] && !String(args[vaChangeIdx + 1]).startsWith("--") ? args[vaChangeIdx + 1] : null;
|
|
3325
3599
|
if (!vaChange) {
|
|
3326
3600
|
console.error('用法: sillyspec validate --change <名> [--json]');
|
|
3327
3601
|
console.error(' 校验该变更的全部结构化产物(verify-facts/required-evidence/task reviews/stage reviews/endpoints + module-map)');
|
|
@@ -3350,7 +3624,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3350
3624
|
// decisions list --change <c>——parseDecisions 只读视图(id/状态/类型/模块域/标题)。
|
|
3351
3625
|
const decSub = filteredArgs[1];
|
|
3352
3626
|
const decChangeIdx = args.indexOf('--change');
|
|
3353
|
-
const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] ? args[decChangeIdx + 1] : null;
|
|
3627
|
+
const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] && !String(args[decChangeIdx + 1]).startsWith("--") ? args[decChangeIdx + 1] : null;
|
|
3354
3628
|
if (!decChange || !['add', 'list'].includes(decSub)) {
|
|
3355
3629
|
console.error('用法: sillyspec decisions add --change <名> --id D-001 --title "…" [--version N] [--status confirmed] [--type architecture] [--domain x(可重复)] [--question/--answer/--anchor/--reject-reason/--revisit-when/--supersedes/--impacts/--note]\n sillyspec decisions list --change <名>');
|
|
3356
3630
|
process.exit(2);
|
|
@@ -3649,12 +3923,12 @@ SillySpec workflow — 工作流管理
|
|
|
3649
3923
|
}
|
|
3650
3924
|
// 解析 --project
|
|
3651
3925
|
const projectIdx = filteredArgs.indexOf('--project');
|
|
3652
|
-
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
|
|
3926
|
+
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] && !String(filteredArgs[projectIdx + 1]).startsWith("--") ? filteredArgs[projectIdx + 1] : null;
|
|
3653
3927
|
// 解析 --json(已在顶层解析)
|
|
3654
3928
|
const isJson = json;
|
|
3655
3929
|
// 解析 --change
|
|
3656
3930
|
const changeIdx = filteredArgs.indexOf('--change');
|
|
3657
|
-
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
|
|
3931
|
+
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] && !String(filteredArgs[changeIdx + 1]).startsWith("--") ? filteredArgs[changeIdx + 1] : null;
|
|
3658
3932
|
|
|
3659
3933
|
if (!project && wfName !== 'archive-impact') {
|
|
3660
3934
|
console.error('❌ 请指定 --project,例如:--project sillyspec');
|
|
@@ -3757,7 +4031,7 @@ SillySpec modules — 模块文档管理
|
|
|
3757
4031
|
// per-task 模块卡级联解析(token 成本优化 P0a):execute「加载上下文」步的
|
|
3758
4032
|
// {MODULE_RESOLVE_TABLE} 注入同源;独立命令供 agent/人随时查(细卡优先,防整读大卡)
|
|
3759
4033
|
const mrChangeIdx = args.indexOf('--change');
|
|
3760
|
-
const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] ? args[mrChangeIdx + 1] : null;
|
|
4034
|
+
const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] && !String(args[mrChangeIdx + 1]).startsWith("--") ? args[mrChangeIdx + 1] : null;
|
|
3761
4035
|
if (!mrChange) {
|
|
3762
4036
|
console.error('用法: sillyspec modules resolve --change <名> [--spec-dir <path>] [--json]\n 输出 per-task 最优模块卡表(跨全部 _module-map.yaml 最长前缀匹配,细卡优先)');
|
|
3763
4037
|
process.exit(2);
|
|
@@ -3765,7 +4039,8 @@ SillySpec modules — 模块文档管理
|
|
|
3765
4039
|
assertSafeChangeName(mrChange, '--change 变更名');
|
|
3766
4040
|
const mrSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
3767
4041
|
const { resolveChangeModuleCards, renderModuleResolveTable } = await import('./module-resolve.js');
|
|
3768
|
-
if (
|
|
4042
|
+
if (json) {
|
|
4043
|
+
// 全局解析器把 --json 收进顶层 json 变量、不进 filteredArgs——查 filteredArgs 恒 false(ql-20260911-029)
|
|
3769
4044
|
const r = resolveChangeModuleCards({ cwd: dir, specBase: mrSpecBase, changeName: mrChange });
|
|
3770
4045
|
console.log(JSON.stringify(r, null, 2));
|
|
3771
4046
|
} else {
|
|
@@ -3818,7 +4093,7 @@ SillySpec modules — 模块文档管理
|
|
|
3818
4093
|
const { registerRepoInLocalYaml } = await import('./local-register.js');
|
|
3819
4094
|
let rrResult;
|
|
3820
4095
|
try {
|
|
3821
|
-
rrResult = registerRepoInLocalYaml(target.path, rrKey, absRepo);
|
|
4096
|
+
rrResult = await registerRepoInLocalYaml(target.path, rrKey, absRepo);
|
|
3822
4097
|
} catch (e) {
|
|
3823
4098
|
console.error(`❌ 注册失败: ${e.message}`);
|
|
3824
4099
|
process.exit(1);
|