sillyspec 3.28.6 → 3.28.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-execute/SKILL.md +224 -222
- package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
- package/.claude/skills/sillyspec-quick/SKILL.md +117 -116
- package/.husky/pre-push +3 -1
- package/.patch16-tmp.mjs +169 -0
- package/attachments/2ab2fd87cfa568ca6acadbb934a651d293a0ad8f3227c66a70b56dcf53a0ea3d.pptx +0 -0
- package/attachments/dc4c2b314ee0c295e1fe5243073eb5c7df7e429074ad7c14424523fbe86e6193.pptx +0 -0
- package/meta.json +11 -0
- package/package.json +1 -1
- package/src/agent-session-log.js +446 -35
- package/src/config-cat.js +6 -3
- package/src/config-schema.js +13 -0
- package/src/constants.js +3 -0
- package/src/db.js +12 -3
- package/src/decision-distill.js +6 -2
- package/src/dispatch/probe.js +8 -1
- package/src/docs-check.js +137 -12
- package/src/docs-gate.js +3 -0
- package/src/doctor-diagnostics.js +19 -7
- package/src/friction-tally.js +7 -2
- package/src/git-helper.js +6 -4
- package/src/hooks/worktree-guard.js +59 -29
- package/src/index.js +168 -64
- package/src/knowledge-match.js +90 -6
- 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 +58 -26
- package/src/progress/consistency-doctor.js +11 -4
- package/src/progress/stage-machine.js +3 -1
- package/src/progress/step-store.js +30 -17
- package/src/progress.js +26 -0
- package/src/quicklog.js +44 -18
- package/src/review-dispatch.js +7 -1
- package/src/run/command.js +17 -3
- package/src/run/complete-handlers.js +40 -10
- package/src/run/complete.js +48 -17
- package/src/run/concurrent-detect.js +1 -1
- package/src/run/gate-snapshot.js +115 -5
- package/src/run/gates.js +35 -5
- package/src/run/prompt.js +61 -1
- package/src/run/quick-audit.js +80 -4
- package/src/run/shared.js +98 -10
- package/src/run/verify-quality-scan.js +14 -3
- package/src/scope-audit.js +89 -14
- package/src/semantic-guard.js +207 -0
- package/src/sillyhub-mcp/client.js +14 -0
- package/src/spec-sync.js +19 -14
- package/src/stage-contract-spec.js +4 -3
- package/src/stage-review.js +6 -5
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/execute.js +6 -1
- package/src/stages/plan-postcheck.js +57 -12
- package/src/sync.js +66 -4
- package/src/task-review.js +41 -6
- package/src/verify-facts-schema.js +10 -3
- package/src/verify-postcheck.js +21 -6
- package/src/verify-probes.js +8 -3
- package/src/worktree-apply.js +56 -18
- package/src/worktree-cross.js +12 -18
- package/src/worktree.js +138 -25
- package/src/wt-commit.js +128 -0
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';
|
|
@@ -198,7 +198,7 @@ async function main() {
|
|
|
198
198
|
|
|
199
199
|
// E22:重路径统一加载(轻路径 --version/help 已早退,未付此税)。
|
|
200
200
|
const { ProgressManager, resolvePlatformSpecDir, resolvePlatformOpts } = await import('./progress.js');
|
|
201
|
-
const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift } = await import('./run/shared.js');
|
|
201
|
+
const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift, detectCwdInsideWorktree } = await import('./run/shared.js');
|
|
202
202
|
|
|
203
203
|
// 解析全局选项
|
|
204
204
|
let json = false;
|
|
@@ -221,13 +221,13 @@ async function main() {
|
|
|
221
221
|
json = true;
|
|
222
222
|
} else if (args[i] === '--save') {
|
|
223
223
|
saveWorkflowRunFlag = true;
|
|
224
|
-
} else if (args[i] === '--dir' && args[i + 1]) {
|
|
224
|
+
} else if (args[i] === '--dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
225
225
|
targetDir = resolve(args[i + 1]);
|
|
226
226
|
i++;
|
|
227
|
-
} else if (args[i] === '--spec-dir' && args[i + 1]) {
|
|
227
|
+
} else if (args[i] === '--spec-dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
228
228
|
specDir = resolve(args[i + 1]);
|
|
229
229
|
i++;
|
|
230
|
-
} else if (args[i] === '--tool' && args[i + 1]) {
|
|
230
|
+
} else if (args[i] === '--tool' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
231
231
|
// 多值支持:逗号分隔(--tool claude,codex)与重复 flag(--tool a --tool b)都收集进数组
|
|
232
232
|
for (const v of args[i + 1].split(',')) {
|
|
233
233
|
const t = v.trim();
|
|
@@ -237,18 +237,21 @@ async function main() {
|
|
|
237
237
|
} else if (args[i] === '--no-skills') {
|
|
238
238
|
// init 专属:跳过 skills 复制段(吞进变量,不透传 filteredArgs)
|
|
239
239
|
noSkills = true;
|
|
240
|
-
} else if (args[i] === '--workspace-id' && args[i + 1]) {
|
|
240
|
+
} else if (args[i] === '--workspace-id' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
241
241
|
// 平台模式专属 flag(init 落平台指针用;--runtime-root/--scan-run-id 保持在
|
|
242
242
|
// filteredArgs 由 runCommand 解析,init 侧只需要 workspaceId 作为平台信号 + runtimeRoot)
|
|
243
243
|
platformWorkspaceId = args[i + 1];
|
|
244
244
|
filteredArgs.push(args[i], args[i + 1]);
|
|
245
245
|
i++;
|
|
246
|
-
} else if (args[i] === '--runtime-root' && args[i + 1]) {
|
|
246
|
+
} else if (args[i] === '--runtime-root' && args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
247
247
|
platformRuntimeRoot = resolve(args[i + 1]);
|
|
248
248
|
filteredArgs.push(args[i], args[i + 1]);
|
|
249
249
|
i++;
|
|
250
250
|
} else if (args[i] === '--interactive' || args[i] === '-i') {
|
|
251
251
|
interactive = true;
|
|
252
|
+
} else if (args[i] === '--allow-worktree-cwd') {
|
|
253
|
+
// worktree cwd 硬拦逃生门:默认阻止在 .sillyspec/.runtime/worktrees/* 内跑 CLI,
|
|
254
|
+
// 此 flag 显式放行(坑 worktree-cwd-silent-split)
|
|
252
255
|
} else if (args[i] === '--list' || args[i] === '-l') {
|
|
253
256
|
filteredArgs.push('--list');
|
|
254
257
|
} else {
|
|
@@ -311,6 +314,22 @@ async function main() {
|
|
|
311
314
|
process.exit(1);
|
|
312
315
|
}
|
|
313
316
|
|
|
317
|
+
// ── worktree cwd 硬拦(坑 worktree-cwd-silent-split,2026-08-29 用户实证)──
|
|
318
|
+
// Bash cwd 残留让 agent 两次误在 worktree 内跑 sillyspec:status 类完全静默、进度写进
|
|
319
|
+
// worktree 副本 .sillyspec 与主仓进度库分裂。守卫在命令分发前拦截,--dir 显式指向主仓
|
|
320
|
+
// 或 --allow-worktree-cwd 逃生门放行;wt-commit 命令豁免(子代理 workdir=worktree 内合法)。
|
|
321
|
+
if (!args.includes('--allow-worktree-cwd') && command !== 'wt-commit') {
|
|
322
|
+
const guardDir = args.includes('--dir') ? dir : process.cwd();
|
|
323
|
+
const wtInfo = detectCwdInsideWorktree(guardDir);
|
|
324
|
+
if (wtInfo) {
|
|
325
|
+
console.error(`\n❌ 当前在隔离 worktree 内(${wtInfo.changeName}):${guardDir}`);
|
|
326
|
+
console.error(` 主仓根:${wtInfo.mainRepoRoot}`);
|
|
327
|
+
console.error(' SillySpec 进度库只存在于主仓,worktree 内跑会产生分裂进度(坑 worktree-cwd-silent-split)。');
|
|
328
|
+
console.error(' 修复:cd 到主仓根后重跑,或用 --dir <主仓根>,或用 --allow-worktree-cwd 强制放行。');
|
|
329
|
+
process.exit(2);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
314
333
|
switch (command) {
|
|
315
334
|
case 'init':
|
|
316
335
|
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 +343,10 @@ async function main() {
|
|
|
324
343
|
const progDir = specDir ? dir : resolveEffectiveDir(dir);
|
|
325
344
|
const subCommand = filteredArgs[1];
|
|
326
345
|
const stageIdx = filteredArgs.indexOf('--stage');
|
|
327
|
-
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
|
|
346
|
+
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] && !String(filteredArgs[stageIdx + 1]).startsWith("--") ? filteredArgs[stageIdx + 1] : null;
|
|
328
347
|
// 解析 --change 参数
|
|
329
348
|
const progChangeIdx = args.indexOf('--change');
|
|
330
|
-
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] ? args[progChangeIdx + 1] : null;
|
|
349
|
+
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] && !String(args[progChangeIdx + 1]).startsWith("--") ? args[progChangeIdx + 1] : null;
|
|
331
350
|
// 与 run 入口同源消毒(防路径穿越;progress 下游 completeStage 写 history 文件名拼 change 名)
|
|
332
351
|
if (progChangeName) assertSafeChangeName(progChangeName, '--change 变更名');
|
|
333
352
|
|
|
@@ -469,7 +488,7 @@ async function main() {
|
|
|
469
488
|
// 只读查询,不依赖 worktree、不写状态(D-002@v1)。
|
|
470
489
|
const gateStage = filteredArgs[1];
|
|
471
490
|
const gateChangeIdx = args.indexOf('--change');
|
|
472
|
-
const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] ? args[gateChangeIdx + 1] : null;
|
|
491
|
+
const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] && !String(args[gateChangeIdx + 1]).startsWith("--") ? args[gateChangeIdx + 1] : null;
|
|
473
492
|
if (!gateStage || gateStage.startsWith('-') || !gateChange) {
|
|
474
493
|
console.error('用法: sillyspec gate <stage> --change <name> [--json]\n stage: brainstorm | plan | execute | verify | archive | ...');
|
|
475
494
|
process.exit(2);
|
|
@@ -529,7 +548,7 @@ async function main() {
|
|
|
529
548
|
// 单项事实核验(machine-interface v1):查询变更某一 facet 的真实状态,返回结构化 data。
|
|
530
549
|
const facet = filteredArgs[1];
|
|
531
550
|
const deriveChangeIdx = args.indexOf('--change');
|
|
532
|
-
const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] ? args[deriveChangeIdx + 1] : null;
|
|
551
|
+
const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] && !String(args[deriveChangeIdx + 1]).startsWith("--") ? args[deriveChangeIdx + 1] : null;
|
|
533
552
|
if (!facet || facet.startsWith('-') || !deriveChange) {
|
|
534
553
|
console.error('用法: sillyspec derive <facet> --change <name> [--json]\n facet: execute-evidence | verify-test | task-reviews | artifacts');
|
|
535
554
|
process.exit(2);
|
|
@@ -587,7 +606,7 @@ async function main() {
|
|
|
587
606
|
}
|
|
588
607
|
const rwVal = (flag) => {
|
|
589
608
|
const i = filteredArgs.indexOf(flag);
|
|
590
|
-
return i >= 0 && filteredArgs[i + 1] ? filteredArgs[i + 1] : null;
|
|
609
|
+
return i >= 0 && filteredArgs[i + 1] && !String(filteredArgs[i + 1]).startsWith("--") ? filteredArgs[i + 1] : null;
|
|
591
610
|
};
|
|
592
611
|
const rwChange = rwVal('--change');
|
|
593
612
|
const rwTask = rwVal('--task');
|
|
@@ -645,6 +664,63 @@ async function main() {
|
|
|
645
664
|
}
|
|
646
665
|
break;
|
|
647
666
|
}
|
|
667
|
+
case 'task': {
|
|
668
|
+
// task 级进行中状态标记(2026-09-12 驾驭第十四批②,用户实证「中断续跑时半成品无主,
|
|
669
|
+
// 主代理接管审查负担重」):开工/完工各一条命令落 .runtime/task-progress/<change>/<task-NN>.json
|
|
670
|
+
// (startedAt + note),list 带陈旧标红(>2h 视为中断遗留——续跑时主代理据此点名接管审查,
|
|
671
|
+
// 不再靠翻 git 工作区猜半成品)。轻量 marker:不进 DB、不设门禁(状态可见性工具,非流程卡点)。
|
|
672
|
+
const taskSub = filteredArgs[1];
|
|
673
|
+
if (!taskSub || ['help', '--help', '-h'].includes(taskSub)) {
|
|
674
|
+
console.log(`用法:
|
|
675
|
+
sillyspec task start --change <名> --task task-NN [--note "一句话在做什么"]
|
|
676
|
+
sillyspec task finish --change <名> --task task-NN
|
|
677
|
+
sillyspec task list --change <名>
|
|
678
|
+
task 进行中状态标记:开工/完工落 .runtime/task-progress/,list 标红 >2h 中断遗留(接管审查点名用)`);
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
const tChangeIdx = filteredArgs.indexOf('--change');
|
|
682
|
+
const tChange = tChangeIdx >= 0 && filteredArgs[tChangeIdx + 1] && !String(filteredArgs[tChangeIdx + 1]).startsWith("--") ? filteredArgs[tChangeIdx + 1] : null;
|
|
683
|
+
const tTaskIdx = filteredArgs.indexOf('--task');
|
|
684
|
+
const tTask = tTaskIdx >= 0 && filteredArgs[tTaskIdx + 1] && !String(filteredArgs[tTaskIdx + 1]).startsWith("--") ? filteredArgs[tTaskIdx + 1] : null;
|
|
685
|
+
const tNoteIdx = filteredArgs.indexOf('--note');
|
|
686
|
+
const tNote = tNoteIdx >= 0 && filteredArgs[tNoteIdx + 1] && !String(filteredArgs[tNoteIdx + 1]).startsWith("--") ? filteredArgs[tNoteIdx + 1] : null;
|
|
687
|
+
if (!tChange) { console.error('❌ 缺 --change <变更名>'); process.exit(2); }
|
|
688
|
+
if (taskSub !== 'list') {
|
|
689
|
+
if (!tTask || !/^task-\d+$/.test(tTask)) { console.error('❌ --task 格式应为 task-NN'); process.exit(2); }
|
|
690
|
+
}
|
|
691
|
+
const tSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
692
|
+
const tRuntime = join(tSpecBase, '.runtime');
|
|
693
|
+
const tDir = join(tRuntime, 'task-progress', tChange);
|
|
694
|
+
if (taskSub === 'start') {
|
|
695
|
+
mkdirSync(tDir, { recursive: true });
|
|
696
|
+
writeFileSync(join(tDir, `${tTask}.json`), JSON.stringify({
|
|
697
|
+
task: tTask, change: tChange, note: tNote || '', startedAt: new Date().toISOString(),
|
|
698
|
+
}, null, 2) + '\n');
|
|
699
|
+
console.log(`📌 ${tTask} 已标记进行中${tNote ? '(' + tNote + ')' : ''}——完工时 sillyspec task finish --change ${tChange} --task ${tTask}`);
|
|
700
|
+
} else if (taskSub === 'finish') {
|
|
701
|
+
try { unlinkSync(join(tDir, `${tTask}.json`)); console.log(`✅ ${tTask} 进行中标记已清除(完工)`); }
|
|
702
|
+
catch { console.log(`ℹ️ ${tTask} 无进行中标记(已完工或从未 start)`); }
|
|
703
|
+
} else if (taskSub === 'list') {
|
|
704
|
+
if (!existsSync(tDir)) { console.log(`ℹ️ 无 ${tChange} 的进行中 task 标记`); break; }
|
|
705
|
+
const entries = readdirSync(tDir).filter(f => f.endsWith('.json'));
|
|
706
|
+
if (entries.length === 0) { console.log(`ℹ️ 无进行中标记(全部完工)`); break; }
|
|
707
|
+
console.log(`📋 ${tChange} 进行中 task(${entries.length} 个)——中断遗留接管审查点名用:`);
|
|
708
|
+
let stale = 0;
|
|
709
|
+
for (const f of entries.sort()) {
|
|
710
|
+
try {
|
|
711
|
+
const m = JSON.parse(readFileSync(join(tDir, f), 'utf8'));
|
|
712
|
+
const ageMin = Math.round((Date.now() - new Date(m.startedAt).getTime()) / 60000);
|
|
713
|
+
const isStale = ageMin > 120;
|
|
714
|
+
if (isStale) stale++;
|
|
715
|
+
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 工作区半成品审查)' : ''}`);
|
|
716
|
+
} catch { console.log(` ⚠️ ${f} 标记损坏(忽略)`); }
|
|
717
|
+
}
|
|
718
|
+
if (stale > 0) console.log(` 共 ${stale} 个疑似中断遗留——主代理接管时逐一审查(半成品无主的痛点出口)`);
|
|
719
|
+
} else {
|
|
720
|
+
console.error(`❌ 未知子命令「${taskSub}」(start | finish | list)`); process.exit(2);
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
648
724
|
case 'backfill-reviews': {
|
|
649
725
|
// 手动补 task(reopen execute / 直接实现)缺 review.json 的官方入口(坑 verify-archive-flow-pitfalls 坑2)。
|
|
650
726
|
// 复用 generateTaskReviewDrafts(execute --done 同源兜底,幂等、fail-open):据 git diff base..head + working-tree
|
|
@@ -657,7 +733,7 @@ async function main() {
|
|
|
657
733
|
// verdict——手算 hash、手抄 diff 清单是纯错误面,gate 校验的恰是这些字段。与草稿互补:
|
|
658
734
|
// 草稿补「缺失」,adopt 修「已存在但 mechanics 错/缺」,verdict 原样保留。
|
|
659
735
|
const brChangeIdx = args.indexOf('--change');
|
|
660
|
-
const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] ? args[brChangeIdx + 1] : null;
|
|
736
|
+
const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] && !String(args[brChangeIdx + 1]).startsWith("--") ? args[brChangeIdx + 1] : null;
|
|
661
737
|
const brAdopt = args.includes('--adopt');
|
|
662
738
|
if (!brChange) {
|
|
663
739
|
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 +815,7 @@ async function main() {
|
|
|
739
815
|
const wsArgs = filteredArgs.slice(2);
|
|
740
816
|
const wsFlag = (name) => {
|
|
741
817
|
const i = wsArgs.indexOf(name);
|
|
742
|
-
return i >= 0 && wsArgs[i + 1] ? wsArgs[i + 1] : null;
|
|
818
|
+
return i >= 0 && wsArgs[i + 1] && !String(wsArgs[i + 1]).startsWith("--") ? wsArgs[i + 1] : null;
|
|
743
819
|
};
|
|
744
820
|
const { workspaceAdd, workspaceRemove, workspaceStatus } = await import('./workspace.js');
|
|
745
821
|
if (wsSub === 'add') {
|
|
@@ -838,7 +914,9 @@ async function main() {
|
|
|
838
914
|
const childArgs = m[1].trim().split(/\s+/);
|
|
839
915
|
console.log(`🤖 next --apply 代跑:sillyspec ${childArgs.join(' ')}\n`);
|
|
840
916
|
const { spawnSync } = await import('node:child_process');
|
|
841
|
-
const
|
|
917
|
+
const { fileURLToPath } = await import('node:url');
|
|
918
|
+
// ESM 无 __dirname(ql-20260911-029:此处曾直接引用,--apply 常态路径必 ReferenceError)
|
|
919
|
+
const r = spawnSync(process.execPath, [fileURLToPath(new URL('../bin/sillyspec.js', import.meta.url)), '--dir', dir, ...childArgs], { stdio: 'inherit' });
|
|
842
920
|
process.exit(r.status === null ? 1 : r.status);
|
|
843
921
|
}
|
|
844
922
|
if (json) {
|
|
@@ -868,7 +946,7 @@ async function main() {
|
|
|
868
946
|
// 通用自动 git 在多会话仓是历史重灾区,故收窄到这一处。
|
|
869
947
|
if (args.includes('--apply')) {
|
|
870
948
|
const apChangeIdx = args.indexOf('--change');
|
|
871
|
-
const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] ? args[apChangeIdx + 1] : null;
|
|
949
|
+
const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] && !String(args[apChangeIdx + 1]).startsWith("--") ? args[apChangeIdx + 1] : null;
|
|
872
950
|
if (!apChange) {
|
|
873
951
|
console.error('用法: sillyspec commit --apply --change <名>\n 只限归档语境(changes.status=archived 或 current_stage=archive)执行归档产物提交');
|
|
874
952
|
process.exit(2);
|
|
@@ -947,7 +1025,7 @@ async function main() {
|
|
|
947
1025
|
// --init 顺带生成 verify-result.md 七章节骨架(探针结果预填;结论留「待填」——gate 找
|
|
948
1026
|
// 不到 PASS/FAIL 即判不过,骨架不能直接过门)。已存在不覆盖。
|
|
949
1027
|
const vpChangeIdx = args.indexOf('--change');
|
|
950
|
-
const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] ? args[vpChangeIdx + 1] : null;
|
|
1028
|
+
const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] && !String(args[vpChangeIdx + 1]).startsWith("--") ? args[vpChangeIdx + 1] : null;
|
|
951
1029
|
const vpInit = args.includes('--init');
|
|
952
1030
|
if (!vpChange) {
|
|
953
1031
|
console.error('用法: sillyspec verify-probes --change <name> [--init] [--json] [--spec-dir <path>]\n 跑机械探针(TODO 标记/测试覆盖/API 对账/删除对账)输出 markdown;--init 生成 verify-result.md 骨架(探针预填,已存在不覆盖)');
|
|
@@ -1071,9 +1149,9 @@ async function main() {
|
|
|
1071
1149
|
// stage-reviews 路径)、--kill=清在途记录+平台处置指引(处置权在人)。薄壳:全部逻辑在
|
|
1072
1150
|
// src/review-dispatch.js(可测核心),此处只做 flag 解析与退出码。
|
|
1073
1151
|
const rdChangeIdx = args.indexOf('--change');
|
|
1074
|
-
const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] ? args[rdChangeIdx + 1] : null;
|
|
1152
|
+
const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] && !String(args[rdChangeIdx + 1]).startsWith("--") ? args[rdChangeIdx + 1] : null;
|
|
1075
1153
|
const rdStageIdx = args.indexOf('--stage');
|
|
1076
|
-
const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] ? args[rdStageIdx + 1] : null;
|
|
1154
|
+
const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] && !String(args[rdStageIdx + 1]).startsWith("--") ? args[rdStageIdx + 1] : null;
|
|
1077
1155
|
const rdStatus = args.includes('--status');
|
|
1078
1156
|
const rdKill = args.includes('--kill');
|
|
1079
1157
|
const rdMode = rdKill ? 'kill' : rdStatus ? 'status' : 'create';
|
|
@@ -1124,9 +1202,9 @@ async function main() {
|
|
|
1124
1202
|
// 模块文档 sidecar 同步命令化(2026-09-09 刀批):diff 归属模块 → sidecar 追加变更索引行 +
|
|
1125
1203
|
// 卡 updated_at 戳——机械部分 CLI 代算,卡片正文语义更新仍归 agent/归档流程。幂等。
|
|
1126
1204
|
const mdsChangeIdx = args.indexOf('--change');
|
|
1127
|
-
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] ? args[mdsChangeIdx + 1] : null;
|
|
1205
|
+
const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] && !String(args[mdsChangeIdx + 1]).startsWith("--") ? args[mdsChangeIdx + 1] : null;
|
|
1128
1206
|
const mdsNoteIdx = args.indexOf('--note');
|
|
1129
|
-
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] ? args[mdsNoteIdx + 1] : null;
|
|
1207
|
+
const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] && !String(args[mdsNoteIdx + 1]).startsWith("--") ? args[mdsNoteIdx + 1] : null;
|
|
1130
1208
|
if (!mdsChange) {
|
|
1131
1209
|
console.error('用法: sillyspec module-docs-sync --change <name> [--note "一句话"] [--json]\n diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等;卡片正文语义更新仍归 agent)');
|
|
1132
1210
|
process.exit(2);
|
|
@@ -1151,7 +1229,7 @@ async function main() {
|
|
|
1151
1229
|
// 手排 Wave 与拓扑比对不一致需手改;依赖方向硬拦(plan-postcheck)落地后给一条零手写出路。
|
|
1152
1230
|
// depMap 与 postcheck 同源(collectTaskDepMap)→ topoSortWaves → 重写 Wave 段 + 同步任务总表 W 列。
|
|
1153
1231
|
const pawChangeIdx = args.indexOf('--change');
|
|
1154
|
-
const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] ? args[pawChangeIdx + 1] : null;
|
|
1232
|
+
const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] && !String(args[pawChangeIdx + 1]).startsWith("--") ? args[pawChangeIdx + 1] : null;
|
|
1155
1233
|
const pawDryRun = args.includes('--dry-run');
|
|
1156
1234
|
if (!pawChange) {
|
|
1157
1235
|
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 +1274,7 @@ async function main() {
|
|
|
1196
1274
|
// archive 模块影响矩阵骨架(2026-08-21 审计第三批 D3):文件×模块归属按 _module-map.yaml
|
|
1197
1275
|
// paths 前缀匹配预填(机械),影响类型/review 标记留 <!--TODO-->(语义)。已存在不覆盖。
|
|
1198
1276
|
const miChangeIdx = args.indexOf('--change');
|
|
1199
|
-
const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] ? args[miChangeIdx + 1] : null;
|
|
1277
|
+
const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] && !String(args[miChangeIdx + 1]).startsWith("--") ? args[miChangeIdx + 1] : null;
|
|
1200
1278
|
if (!miChange) {
|
|
1201
1279
|
console.error('用法: sillyspec module-impact --change <name> [--json] [--spec-dir <path>]\n 生成 module-impact.md 骨架(模块影响矩阵按 module-map 预填 + 未匹配文件清单;已存在不覆盖)');
|
|
1202
1280
|
process.exit(2);
|
|
@@ -1232,7 +1310,7 @@ async function main() {
|
|
|
1232
1310
|
// 用法错 exit 2、assertSafeChangeName 消毒、动态 import、--json 结构化输出、运行错
|
|
1233
1311
|
// fail-soft 出错误摘要 exit 1 不抛栈。
|
|
1234
1312
|
const saChangeIdx = args.indexOf('--change');
|
|
1235
|
-
const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] ? args[saChangeIdx + 1] : null;
|
|
1313
|
+
const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] && !String(args[saChangeIdx + 1]).startsWith("--") ? args[saChangeIdx + 1] : null;
|
|
1236
1314
|
if (!saChange) {
|
|
1237
1315
|
console.error('用法: sillyspec scope-audit --change <name> [--file <path>] [--json] [--spec-dir <path>]\n 变更范围对账:计划×实际三态全表 + 行数(quick 会话传 quick-<8hex> 出归属表;已归档变更可查——快照冻结记录态);--file <path> 看单文件变化内容(对账同源锚点的 git diff);advisory 只读展示,不构成门禁');
|
|
1238
1316
|
process.exit(2);
|
|
@@ -1329,7 +1407,7 @@ async function main() {
|
|
|
1329
1407
|
// 端点增删的 before 侧;扫描/落盘在 endpoint-baseline.js 纯函数,此处只做参数解析 +
|
|
1330
1408
|
// worktree 主仓锚定 + 输出。
|
|
1331
1409
|
const ebChangeIdx = args.indexOf('--change');
|
|
1332
|
-
const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] ? args[ebChangeIdx + 1] : null;
|
|
1410
|
+
const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] && !String(args[ebChangeIdx + 1]).startsWith("--") ? args[ebChangeIdx + 1] : null;
|
|
1333
1411
|
if (!ebChange) {
|
|
1334
1412
|
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
1413
|
process.exit(2);
|
|
@@ -1397,14 +1475,14 @@ async function main() {
|
|
|
1397
1475
|
process.exit(2);
|
|
1398
1476
|
}
|
|
1399
1477
|
const epChangeIdx = args.indexOf('--change');
|
|
1400
|
-
const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] ? args[epChangeIdx + 1] : null;
|
|
1478
|
+
const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] && !String(args[epChangeIdx + 1]).startsWith("--") ? args[epChangeIdx + 1] : null;
|
|
1401
1479
|
const epTaskIdx = args.indexOf('--task');
|
|
1402
|
-
const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] ? args[epTaskIdx + 1] : null;
|
|
1480
|
+
const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] && !String(args[epTaskIdx + 1]).startsWith("--") ? args[epTaskIdx + 1] : null;
|
|
1403
1481
|
const epAllTasks = args.includes('--all-tasks');
|
|
1404
1482
|
const epDirIdx = args.indexOf('--dir');
|
|
1405
|
-
const epDir = epDirIdx >= 0 && args[epDirIdx + 1] ? args[epDirIdx + 1] : null;
|
|
1483
|
+
const epDir = epDirIdx >= 0 && args[epDirIdx + 1] && !String(args[epDirIdx + 1]).startsWith("--") ? args[epDirIdx + 1] : null;
|
|
1406
1484
|
const epFilesIdx = args.indexOf('--files');
|
|
1407
|
-
const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] ? args[epFilesIdx + 1] : null;
|
|
1485
|
+
const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] && !String(args[epFilesIdx + 1]).startsWith("--") ? args[epFilesIdx + 1] : null;
|
|
1408
1486
|
if (!epChange) {
|
|
1409
1487
|
console.error('❌ 缺 --change <变更名>');
|
|
1410
1488
|
process.exit(2);
|
|
@@ -1552,7 +1630,7 @@ async function main() {
|
|
|
1552
1630
|
// author/created_at 只 warning,agent 手改还常忘。本命令补齐(git user + 当前时间),
|
|
1553
1631
|
// 已有两键的文件不动;有 frontmatter 就地插入,无则补一段。
|
|
1554
1632
|
const sfhProjectIdx = args.indexOf('--project');
|
|
1555
|
-
const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] ? args[sfhProjectIdx + 1] : null;
|
|
1633
|
+
const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] && !String(args[sfhProjectIdx + 1]).startsWith("--") ? args[sfhProjectIdx + 1] : null;
|
|
1556
1634
|
const { fixScanDocHeaders } = await import('./scan-postcheck.js');
|
|
1557
1635
|
const sfhResult = fixScanDocHeaders({ cwd: dir, specDir, project: sfhProject });
|
|
1558
1636
|
if (json) {
|
|
@@ -1572,7 +1650,7 @@ async function main() {
|
|
|
1572
1650
|
// plan.md)注册表生成逐 task <!--TODO--> 骨架;gate 拒绝未替换的占位(防骨架直接过门),
|
|
1573
1651
|
// agent 只需逐行填结论。已存在不覆盖(幂等,agent 产出优先)。
|
|
1574
1652
|
const siChangeIdx = args.indexOf('--change');
|
|
1575
|
-
const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] ? args[siChangeIdx + 1] : null;
|
|
1653
|
+
const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] && !String(args[siChangeIdx + 1]).startsWith("--") ? args[siChangeIdx + 1] : null;
|
|
1576
1654
|
if (!siChange) {
|
|
1577
1655
|
console.error('用法: sillyspec symbol-impact --change <name> [--spec-dir <path>]\n 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(已存在不覆盖);gate 拒绝未替换的占位行');
|
|
1578
1656
|
process.exit(2);
|
|
@@ -1608,7 +1686,7 @@ async function main() {
|
|
|
1608
1686
|
// 手写 proposal/requirements/decisions 的 frontmatter/章节骨架是格式返工根源——本命令
|
|
1609
1687
|
// 逐文件生成骨架(已存在不覆盖),agent 只填语义。与 design-init 同族(幂等)。
|
|
1610
1688
|
const fpChangeIdx = args.indexOf('--change');
|
|
1611
|
-
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] ? args[fpChangeIdx + 1] : null;
|
|
1689
|
+
const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] && !String(args[fpChangeIdx + 1]).startsWith("--") ? args[fpChangeIdx + 1] : null;
|
|
1612
1690
|
if (!fpChange) {
|
|
1613
1691
|
console.error('用法: sillyspec fourpiece-init --change <name> [--json] [--spec-dir <path>]\n 生成 proposal.md/requirements.md/decisions.md 骨架(frontmatter+章节标题+模板占位;已存在不覆盖;design.md 归 design-init)');
|
|
1614
1692
|
process.exit(2);
|
|
@@ -1701,7 +1779,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1701
1779
|
// design-facts.js generateDesignSkeleton——本 case 只负责读盘/幂等保护/落盘)。已存在不覆盖
|
|
1702
1780
|
// (幂等,agent 产出优先),--force 覆盖重生成。
|
|
1703
1781
|
const diChangeIdx = args.indexOf('--change');
|
|
1704
|
-
const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] ? args[diChangeIdx + 1] : null;
|
|
1782
|
+
const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] && !String(args[diChangeIdx + 1]).startsWith("--") ? args[diChangeIdx + 1] : null;
|
|
1705
1783
|
const diForce = args.includes('--force');
|
|
1706
1784
|
if (!diChange) {
|
|
1707
1785
|
console.error('用法: sillyspec design-init --change <name> [--force] [--json] [--spec-dir <path>]\n 从变更 decisions.md 生成 design.md 十三章节骨架(决策追踪表按当前版本 D 条目预填;已存在不覆盖,--force 覆盖)');
|
|
@@ -1765,7 +1843,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1765
1843
|
// (重跑即刷新):与 design-init「agent 产出优先不覆盖」语义相反——delta 是源数据
|
|
1766
1844
|
// (verify-runs/module-map)时点快照,旧报告天然过期,重新生成即对齐。
|
|
1767
1845
|
const dlChangeIdx = args.indexOf('--change');
|
|
1768
|
-
const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] ? args[dlChangeIdx + 1] : null;
|
|
1846
|
+
const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] && !String(args[dlChangeIdx + 1]).startsWith("--") ? args[dlChangeIdx + 1] : null;
|
|
1769
1847
|
if (!dlChange) {
|
|
1770
1848
|
console.error('用法: sillyspec delta --change <name> [--spec-dir <path>] [--json]\n 生成变更 delta.md(Before/Delta/After 三段式;四源 fail-soft,缺源逐段降级注记;幂等覆盖重跑即刷新)');
|
|
1771
1849
|
process.exit(2);
|
|
@@ -1827,11 +1905,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1827
1905
|
// 调度者手动派独立子代理不写 marker 的死锁。docHash 由 CLI computeDocHash 算(部分实现 P6.1b defer)。
|
|
1828
1906
|
// 纯新增,不改 gate 语义。与 task 级 backfill-reviews 对称(stage 级等价物)。
|
|
1829
1907
|
const rsrChangeIdx = args.indexOf('--change');
|
|
1830
|
-
const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] ? args[rsrChangeIdx + 1] : null;
|
|
1908
|
+
const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] && !String(args[rsrChangeIdx + 1]).startsWith("--") ? args[rsrChangeIdx + 1] : null;
|
|
1831
1909
|
const rsrStageIdx = args.indexOf('--stage');
|
|
1832
|
-
const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] ? args[rsrStageIdx + 1] : null;
|
|
1910
|
+
const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] && !String(args[rsrStageIdx + 1]).startsWith("--") ? args[rsrStageIdx + 1] : null;
|
|
1833
1911
|
const rsrFromIdx = args.indexOf('--from');
|
|
1834
|
-
const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] ? args[rsrFromIdx + 1] : null;
|
|
1912
|
+
const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] && !String(args[rsrFromIdx + 1]).startsWith("--") ? args[rsrFromIdx + 1] : null;
|
|
1835
1913
|
// docHash 联动(坑 stage-review-dochash-manual-resync,2026-08-21 实证):改一版 design 要
|
|
1836
1914
|
// 重算 2-3 个 stage review 的 docHash,手工易漏、gate 报错才补。--refresh-hash 就地刷新单个
|
|
1837
1915
|
// 既有 review 的 hash(保留结论);--all 一次处理三个 stage(有 review 刷 hash,无 review
|
|
@@ -1940,13 +2018,14 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1940
2018
|
// --dry-run 列独立存在(「报告修复预览 + exit 1」),即 dryRun 置位 = fix 语义自动生效
|
|
1941
2019
|
// 但零写盘(本口径记录于此,防歧义)。
|
|
1942
2020
|
const BARE_FLAGS = ['--fix', '--dry-run', '--no-exempt'];
|
|
1943
|
-
const PAIRED_FLAGS = ['--paths'];
|
|
2021
|
+
const PAIRED_FLAGS = ['--paths', '--against'];
|
|
1944
2022
|
const rawDocsArgs = filteredArgs.slice(2);
|
|
1945
2023
|
const docsCheckFlags = [];
|
|
1946
2024
|
let cliPaths = null;
|
|
1947
2025
|
let fix = false;
|
|
1948
2026
|
let dryRun = false;
|
|
1949
2027
|
let noExempt = false;
|
|
2028
|
+
let againstRef = null;
|
|
1950
2029
|
for (let i = 0; i < rawDocsArgs.length; i++) {
|
|
1951
2030
|
const a = rawDocsArgs[i];
|
|
1952
2031
|
if (a === '--paths' && rawDocsArgs[i + 1] !== undefined) {
|
|
@@ -1955,6 +2034,13 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1955
2034
|
} else if (a === '--paths') {
|
|
1956
2035
|
console.error('❌ docs check: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
1957
2036
|
process.exit(2);
|
|
2037
|
+
} else if (a === '--against' && rawDocsArgs[i + 1] !== undefined && !rawDocsArgs[i + 1].startsWith('--')) {
|
|
2038
|
+
// 坑 docs-gate-shared-worktree-parallel-block:按提交树校验,隔离并行在途编辑(移动靶)
|
|
2039
|
+
againstRef = rawDocsArgs[i + 1];
|
|
2040
|
+
i++;
|
|
2041
|
+
} else if (a === '--against') {
|
|
2042
|
+
console.error('❌ docs check: --against 缺值(如 --against HEAD)');
|
|
2043
|
+
process.exit(2);
|
|
1958
2044
|
} else if (BARE_FLAGS.includes(a)) {
|
|
1959
2045
|
if (a === '--fix') fix = true;
|
|
1960
2046
|
else if (a === '--dry-run') dryRun = true;
|
|
@@ -1966,6 +2052,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1966
2052
|
docsCheckFlags.push(a);
|
|
1967
2053
|
}
|
|
1968
2054
|
}
|
|
2055
|
+
if ((fix || dryRun) && againstRef) {
|
|
2056
|
+
// --fix 操作工作区磁盘内容,against 语义与其矛盾(修的是未提交文本、校验的是提交树)
|
|
2057
|
+
console.error('❌ docs check: --fix/--dry-run 与 --against 互斥(fix 恒操作工作区,against 恒校验提交树)');
|
|
2058
|
+
process.exit(2);
|
|
2059
|
+
}
|
|
1969
2060
|
const { runDocsCheck, readDocsCheckConfig, DocsCheckConfigError, applyFixes, runChangeNameAdvisory } = await import('./docs-check.js');
|
|
1970
2061
|
try {
|
|
1971
2062
|
// 配置优先级:--paths > local.yaml docs-check.paths > 缺省 docs/**/*.md
|
|
@@ -1978,6 +2069,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
1978
2069
|
keywordAssert: cfg.keywordAssert,
|
|
1979
2070
|
crossRepoRoots: cfg.crossRepoRoots,
|
|
1980
2071
|
exempt: !noExempt,
|
|
2072
|
+
against: againstRef,
|
|
1981
2073
|
});
|
|
1982
2074
|
// task-03 修复链路(FR-04,platform-map-auto-anchors):fixActive 才构造 fixes——
|
|
1983
2075
|
// 仅 fix.fixable===true 且 newLine 为整数的条目可重锚;newRef = ref 的行号部分替换为
|
|
@@ -2153,10 +2245,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2153
2245
|
// B9(2026-08-16):flag 白名单化(对齐 docs check 分支)——未知 --xxx 显式 exit 2
|
|
2154
2246
|
// (interface-contract §1.3b 宣称),并接线 --paths 透传 runDocsGate.checkOpts(原被忽略)。
|
|
2155
2247
|
const GATE_BARE_FLAGS = ['--init-baseline'];
|
|
2156
|
-
const GATE_PAIRED_FLAGS = ['--paths'];
|
|
2248
|
+
const GATE_PAIRED_FLAGS = ['--paths', '--against'];
|
|
2157
2249
|
const rawGateArgs = filteredArgs.slice(2);
|
|
2158
2250
|
let initBaseline = false;
|
|
2159
2251
|
let cliGatePaths = null;
|
|
2252
|
+
let gateAgainst = null;
|
|
2160
2253
|
for (let i = 0; i < rawGateArgs.length; i++) {
|
|
2161
2254
|
const a = rawGateArgs[i];
|
|
2162
2255
|
if (a === '--paths' && rawGateArgs[i + 1] !== undefined) {
|
|
@@ -2165,13 +2258,21 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2165
2258
|
} else if (a === '--paths') {
|
|
2166
2259
|
console.error('❌ docs gate: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
|
|
2167
2260
|
process.exit(2);
|
|
2261
|
+
} else if (a === '--against' && rawGateArgs[i + 1] !== undefined && !rawGateArgs[i + 1].startsWith('--')) {
|
|
2262
|
+
// 坑 docs-gate-shared-worktree-parallel-block:按提交树校验(pre-push 语义——推的
|
|
2263
|
+
// 是提交不是工作区),隔离并行会话在途编辑造成的锚点瞬时漂移(移动靶)
|
|
2264
|
+
gateAgainst = rawGateArgs[i + 1];
|
|
2265
|
+
i++;
|
|
2266
|
+
} else if (a === '--against') {
|
|
2267
|
+
console.error('❌ docs gate: --against 缺值(如 --against HEAD)');
|
|
2268
|
+
process.exit(2);
|
|
2168
2269
|
} else if (GATE_BARE_FLAGS.includes(a)) {
|
|
2169
2270
|
if (a === '--init-baseline') initBaseline = true;
|
|
2170
2271
|
} else if (a.startsWith('--')) {
|
|
2171
2272
|
console.error(`❌ docs gate: 未知 flag「${a}」。已知 flag:${[...GATE_BARE_FLAGS, ...GATE_PAIRED_FLAGS].join(' ')}(--json 为全局 flag)`);
|
|
2172
2273
|
process.exit(2);
|
|
2173
2274
|
} else {
|
|
2174
|
-
console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--json]`);
|
|
2275
|
+
console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--against HEAD] [--json]`);
|
|
2175
2276
|
process.exit(2);
|
|
2176
2277
|
}
|
|
2177
2278
|
}
|
|
@@ -2181,7 +2282,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2181
2282
|
const gateSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
2182
2283
|
const g = await runDocsGate(
|
|
2183
2284
|
{ projectRoot: dir, specBase: gateSpecBase, initBaseline },
|
|
2184
|
-
cliGatePaths ? { paths: cliGatePaths } :
|
|
2285
|
+
{ ...(cliGatePaths ? { paths: cliGatePaths } : {}), ...(gateAgainst ? { against: gateAgainst } : {}) }
|
|
2185
2286
|
);
|
|
2186
2287
|
if (json) {
|
|
2187
2288
|
console.log(JSON.stringify(g, null, 2));
|
|
@@ -2237,7 +2338,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2237
2338
|
const dumpDbFlag = filteredArgs.includes('--dump-db');
|
|
2238
2339
|
const doctorConfirm = filteredArgs.includes('--confirm');
|
|
2239
2340
|
const pathIdx = filteredArgs.indexOf('--path');
|
|
2240
|
-
const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] ? filteredArgs[pathIdx + 1] : null;
|
|
2341
|
+
const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null;
|
|
2241
2342
|
|
|
2242
2343
|
// --align-execute-progress:基于 plan.md 声明对齐 execute 派生戳(仿 --cleanup-remnant 范式)
|
|
2243
2344
|
// 默认 dry-run(只报告将补哪些 step),加 --confirm 才写。命中即 break,绝不 fall-through。
|
|
@@ -2245,7 +2346,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2245
2346
|
if (alignFlag) {
|
|
2246
2347
|
// --change 解析:显式优先,缺省用单活跃变更自动兜底(与 run.js resolveChangeNameAuto 同逻辑)
|
|
2247
2348
|
const alignChangeIdx = filteredArgs.indexOf('--change');
|
|
2248
|
-
let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] ? filteredArgs[alignChangeIdx + 1] : null;
|
|
2349
|
+
let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] && !String(filteredArgs[alignChangeIdx + 1]).startsWith("--") ? filteredArgs[alignChangeIdx + 1] : null;
|
|
2249
2350
|
if (!alignChange) {
|
|
2250
2351
|
const changesDir = join(resolvePlatformSpecDir(doctorEffectiveDir, specDir) || join(doctorEffectiveDir, '.sillyspec'), 'changes');
|
|
2251
2352
|
if (existsSync(changesDir)) {
|
|
@@ -2417,8 +2518,8 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2417
2518
|
const r = buildAndWriteScanFacts({
|
|
2418
2519
|
cwd: factsEffectiveDir,
|
|
2419
2520
|
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,
|
|
2521
|
+
projectName: projIdx >= 0 && filteredArgs[projIdx + 1] && !String(filteredArgs[projIdx + 1]).startsWith("--") ? filteredArgs[projIdx + 1] : basename(factsEffectiveDir),
|
|
2522
|
+
projectPath: pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null,
|
|
2422
2523
|
})
|
|
2423
2524
|
const f = r.facts
|
|
2424
2525
|
console.log(`scan facts 已写入: ${r.outPath}`)
|
|
@@ -2431,7 +2532,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
|
|
|
2431
2532
|
if (filteredArgs[1] === 'diff') {
|
|
2432
2533
|
const { runScanDiff } = await import('./scan-diff.js')
|
|
2433
2534
|
const diffBaseIdx = filteredArgs.indexOf('--base')
|
|
2434
|
-
const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] ? filteredArgs[diffBaseIdx + 1] : null
|
|
2535
|
+
const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] && !String(filteredArgs[diffBaseIdx + 1]).startsWith("--") ? filteredArgs[diffBaseIdx + 1] : null
|
|
2435
2536
|
// specBase/projectName 与 scan 主流程同口径(scan-profile.js:specBase=进度库根,projectName=仓库根 basename)。
|
|
2436
2537
|
// A4 同族:平台指针 / 显式 --spec-dir / 本地 fallback 三合一,平台模式下 diff 不落本地孤儿库。
|
|
2437
2538
|
const diffEffectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
@@ -2553,7 +2654,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2553
2654
|
try { assertSafeChangeName(wtName, 'worktree 变更名'); }
|
|
2554
2655
|
catch (e) { console.error(`❌ ${e.message}`); process.exit(2); }
|
|
2555
2656
|
const baseIdx = args.indexOf('--base');
|
|
2556
|
-
const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
2657
|
+
const base = baseIdx >= 0 && args[baseIdx + 1] && !String(args[baseIdx + 1]).startsWith("--") ? args[baseIdx + 1] : undefined;
|
|
2557
2658
|
const adoptBranch = args.includes('--adopt-branch'); // 收编既有同名分支为工作分支(坑 worktree-user-branch-conflict)
|
|
2558
2659
|
try {
|
|
2559
2660
|
const info = wm.create(wtName, { base, adoptBranch });
|
|
@@ -2907,7 +3008,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
2907
3008
|
const staleIdx = args.indexOf('--stale-hours');
|
|
2908
3009
|
const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
|
|
2909
3010
|
const changeIdx = args.indexOf('--change');
|
|
2910
|
-
const changeName = changeIdx !== -1 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
|
|
3011
|
+
const changeName = changeIdx !== -1 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
|
|
2911
3012
|
const diag = await wm.doctor({ fix: fixFlag, staleHours, changeName });
|
|
2912
3013
|
if (diag.issues.length === 0) {
|
|
2913
3014
|
console.log('✅ worktree 健康检查通过,无异常');
|
|
@@ -2990,7 +3091,7 @@ SillySpec dispatch — SillyHub 派发能力探测与策略生成(agent 调用
|
|
|
2990
3091
|
if (dispatchSubCmd === 'hint') {
|
|
2991
3092
|
// 解析 --contract <json>(仿 worktree 用 args.indexOf 取 flag 后一个值)
|
|
2992
3093
|
const contractIdx = args.indexOf('--contract');
|
|
2993
|
-
const contractRaw = contractIdx >= 0 && args[contractIdx + 1] ? args[contractIdx + 1] : null;
|
|
3094
|
+
const contractRaw = contractIdx >= 0 && args[contractIdx + 1] && !String(args[contractIdx + 1]).startsWith("--") ? args[contractIdx + 1] : null;
|
|
2994
3095
|
if (!contractRaw) {
|
|
2995
3096
|
console.error('用法: sillyspec dispatch hint --contract <json> [--json]\n 生成派发指令(DispatchContract:brief/worktreePath/branch/allowedPaths/readOnly/...)');
|
|
2996
3097
|
process.exit(2);
|
|
@@ -3062,7 +3163,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3062
3163
|
// join(runtimeDir, `sync-conflict-${changeName}.json`) 可被 ../ 穿越出 .runtime
|
|
3063
3164
|
{
|
|
3064
3165
|
const pChangeIdx = args.indexOf('--change');
|
|
3065
|
-
const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] ? args[pChangeIdx + 1] : null;
|
|
3166
|
+
const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] && !String(args[pChangeIdx + 1]).startsWith("--") ? args[pChangeIdx + 1] : null;
|
|
3066
3167
|
if (pChangeVal) assertSafeChangeName(pChangeVal, 'platform --change 变更名');
|
|
3067
3168
|
}
|
|
3068
3169
|
|
|
@@ -3130,7 +3231,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3130
3231
|
process.exit(1);
|
|
3131
3232
|
}
|
|
3132
3233
|
const tokenIdx = args.indexOf('--token');
|
|
3133
|
-
const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
|
|
3234
|
+
const token = tokenIdx >= 0 && args[tokenIdx + 1] && !String(args[tokenIdx + 1]).startsWith("--") ? args[tokenIdx + 1] : undefined;
|
|
3134
3235
|
if (!token) {
|
|
3135
3236
|
// 缺 token 直接终止(体检 HUB-02):交互式输入尚未实现(task-11),此前
|
|
3136
3237
|
// 继续执行会把 undefined 序列化成字符串 "undefined" 写入 local.yaml,
|
|
@@ -3142,17 +3243,19 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3142
3243
|
break;
|
|
3143
3244
|
}
|
|
3144
3245
|
case 'disconnect':
|
|
3145
|
-
|
|
3246
|
+
// --keep-pointer(坑 platform-sync-progress-rollback-and-db-corruption 坑1③):只清
|
|
3247
|
+
// local.yaml platform 段,保留指针/声明给 daemon 内嵌 CLI——回滚环恢复序列正规出口
|
|
3248
|
+
await syncModule.disconnect(dir, { keepPointer: filteredArgs.includes('--keep-pointer') });
|
|
3146
3249
|
break;
|
|
3147
3250
|
case 'sync': {
|
|
3148
3251
|
const syncChangeIdx = args.indexOf('--change');
|
|
3149
|
-
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
|
|
3252
|
+
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] && !String(args[syncChangeIdx + 1]).startsWith("--") ? args[syncChangeIdx + 1] : null;
|
|
3150
3253
|
await syncModule.sync(syncChangeName, dir);
|
|
3151
3254
|
break;
|
|
3152
3255
|
}
|
|
3153
3256
|
case 'sync-docs': {
|
|
3154
3257
|
const syncDocsChangeIdx = args.indexOf('--change');
|
|
3155
|
-
const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
|
|
3258
|
+
const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] && !String(args[syncDocsChangeIdx + 1]).startsWith("--") ? args[syncDocsChangeIdx + 1] : null;
|
|
3156
3259
|
await syncModule.syncDocuments(syncDocsChangeName, dir);
|
|
3157
3260
|
break;
|
|
3158
3261
|
}
|
|
@@ -3187,7 +3290,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3187
3290
|
// task-11 / D-006 / D-009 / FR-03:手动下行拉取平台进度到本地。
|
|
3188
3291
|
// 与自动 triggerPull 共用 syncModule.pull(SyncManager.pull 实例方法),行为一致。
|
|
3189
3292
|
const pullChangeIdx = args.indexOf('--change');
|
|
3190
|
-
const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] ? args[pullChangeIdx + 1] : null;
|
|
3293
|
+
const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] && !String(args[pullChangeIdx + 1]).startsWith("--") ? args[pullChangeIdx + 1] : null;
|
|
3191
3294
|
// 未连接明确提示不崩(acceptance:未连接平台输出明确提示)
|
|
3192
3295
|
const smForCheck = new syncModule.SyncManager(dir);
|
|
3193
3296
|
if (!smForCheck._getPlatform()) {
|
|
@@ -3231,7 +3334,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3231
3334
|
// 第一个非 flag 位置参数 → 唯一未决冲突自动选中。修复:旧实现盲取 platformArgs[0],
|
|
3232
3335
|
// flag 放前面时(resolve --keep-local --change x)把 '--keep-local' 当变更名去读冲突文件。
|
|
3233
3336
|
const changeIdx = args.indexOf('--change');
|
|
3234
|
-
let resolveName = changeIdx >= 0 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
|
|
3337
|
+
let resolveName = changeIdx >= 0 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
|
|
3235
3338
|
if (!resolveName) {
|
|
3236
3339
|
resolveName = platformArgs.find((a) => a && !a.startsWith('--')) || null;
|
|
3237
3340
|
}
|
|
@@ -3297,7 +3400,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3297
3400
|
process.exit(1);
|
|
3298
3401
|
}
|
|
3299
3402
|
const reasonIdx = args.indexOf('--reason');
|
|
3300
|
-
const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
|
|
3403
|
+
const reason = reasonIdx >= 0 && args[reasonIdx + 1] && !String(args[reasonIdx + 1]).startsWith("--") ? args[reasonIdx + 1] : undefined;
|
|
3301
3404
|
await syncModule.reject(rejectName, reason, dir);
|
|
3302
3405
|
break;
|
|
3303
3406
|
}
|
|
@@ -3321,7 +3424,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3321
3424
|
// task review / stage review / module-map)+ 轻形状检查(required-evidence / endpoints)
|
|
3322
3425
|
// 为一把伞——「新解析器写之前先过 schema」的统一落点。skip = 未生成不算失败。
|
|
3323
3426
|
const vaChangeIdx = args.indexOf('--change');
|
|
3324
|
-
const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] ? args[vaChangeIdx + 1] : null;
|
|
3427
|
+
const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] && !String(args[vaChangeIdx + 1]).startsWith("--") ? args[vaChangeIdx + 1] : null;
|
|
3325
3428
|
if (!vaChange) {
|
|
3326
3429
|
console.error('用法: sillyspec validate --change <名> [--json]');
|
|
3327
3430
|
console.error(' 校验该变更的全部结构化产物(verify-facts/required-evidence/task reviews/stage reviews/endpoints + module-map)');
|
|
@@ -3350,7 +3453,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
3350
3453
|
// decisions list --change <c>——parseDecisions 只读视图(id/状态/类型/模块域/标题)。
|
|
3351
3454
|
const decSub = filteredArgs[1];
|
|
3352
3455
|
const decChangeIdx = args.indexOf('--change');
|
|
3353
|
-
const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] ? args[decChangeIdx + 1] : null;
|
|
3456
|
+
const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] && !String(args[decChangeIdx + 1]).startsWith("--") ? args[decChangeIdx + 1] : null;
|
|
3354
3457
|
if (!decChange || !['add', 'list'].includes(decSub)) {
|
|
3355
3458
|
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
3459
|
process.exit(2);
|
|
@@ -3649,12 +3752,12 @@ SillySpec workflow — 工作流管理
|
|
|
3649
3752
|
}
|
|
3650
3753
|
// 解析 --project
|
|
3651
3754
|
const projectIdx = filteredArgs.indexOf('--project');
|
|
3652
|
-
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
|
|
3755
|
+
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] && !String(filteredArgs[projectIdx + 1]).startsWith("--") ? filteredArgs[projectIdx + 1] : null;
|
|
3653
3756
|
// 解析 --json(已在顶层解析)
|
|
3654
3757
|
const isJson = json;
|
|
3655
3758
|
// 解析 --change
|
|
3656
3759
|
const changeIdx = filteredArgs.indexOf('--change');
|
|
3657
|
-
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
|
|
3760
|
+
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] && !String(filteredArgs[changeIdx + 1]).startsWith("--") ? filteredArgs[changeIdx + 1] : null;
|
|
3658
3761
|
|
|
3659
3762
|
if (!project && wfName !== 'archive-impact') {
|
|
3660
3763
|
console.error('❌ 请指定 --project,例如:--project sillyspec');
|
|
@@ -3757,7 +3860,7 @@ SillySpec modules — 模块文档管理
|
|
|
3757
3860
|
// per-task 模块卡级联解析(token 成本优化 P0a):execute「加载上下文」步的
|
|
3758
3861
|
// {MODULE_RESOLVE_TABLE} 注入同源;独立命令供 agent/人随时查(细卡优先,防整读大卡)
|
|
3759
3862
|
const mrChangeIdx = args.indexOf('--change');
|
|
3760
|
-
const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] ? args[mrChangeIdx + 1] : null;
|
|
3863
|
+
const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] && !String(args[mrChangeIdx + 1]).startsWith("--") ? args[mrChangeIdx + 1] : null;
|
|
3761
3864
|
if (!mrChange) {
|
|
3762
3865
|
console.error('用法: sillyspec modules resolve --change <名> [--spec-dir <path>] [--json]\n 输出 per-task 最优模块卡表(跨全部 _module-map.yaml 最长前缀匹配,细卡优先)');
|
|
3763
3866
|
process.exit(2);
|
|
@@ -3765,7 +3868,8 @@ SillySpec modules — 模块文档管理
|
|
|
3765
3868
|
assertSafeChangeName(mrChange, '--change 变更名');
|
|
3766
3869
|
const mrSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
3767
3870
|
const { resolveChangeModuleCards, renderModuleResolveTable } = await import('./module-resolve.js');
|
|
3768
|
-
if (
|
|
3871
|
+
if (json) {
|
|
3872
|
+
// 全局解析器把 --json 收进顶层 json 变量、不进 filteredArgs——查 filteredArgs 恒 false(ql-20260911-029)
|
|
3769
3873
|
const r = resolveChangeModuleCards({ cwd: dir, specBase: mrSpecBase, changeName: mrChange });
|
|
3770
3874
|
console.log(JSON.stringify(r, null, 2));
|
|
3771
3875
|
} else {
|
|
@@ -3818,7 +3922,7 @@ SillySpec modules — 模块文档管理
|
|
|
3818
3922
|
const { registerRepoInLocalYaml } = await import('./local-register.js');
|
|
3819
3923
|
let rrResult;
|
|
3820
3924
|
try {
|
|
3821
|
-
rrResult = registerRepoInLocalYaml(target.path, rrKey, absRepo);
|
|
3925
|
+
rrResult = await registerRepoInLocalYaml(target.path, rrKey, absRepo);
|
|
3822
3926
|
} catch (e) {
|
|
3823
3927
|
console.error(`❌ 注册失败: ${e.message}`);
|
|
3824
3928
|
process.exit(1);
|