sillyspec 3.33.0 → 3.33.1
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-flow/SKILL.md +1 -1
- package/.github/workflows/ci.yml +11 -3
- package/AGENTS.md +1 -1
- package/assets/command-cards/flow.md +1 -1
- package/package.json +1 -1
- package/src/archive-delta.js +4 -3
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/db.js +8 -2
- package/src/deleted-ledger.js +2 -1
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +2 -1
- package/src/flow.js +111 -62
- package/src/friction-ledger.js +2 -1
- package/src/friction-tally.js +8 -4
- package/src/handoff.js +2 -1
- package/src/hooks/worktree-guard.js +5 -5
- package/src/index.js +41 -7
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- package/src/progress/consistency-doctor.js +5 -2
- package/src/progress/stage-machine.js +6 -2
- package/src/progress.js +19 -8
- package/src/quicklog.js +194 -9
- package/src/review-dispatch.js +4 -3
- package/src/review-tier.js +2 -1
- package/src/route-hindsight.js +4 -3
- package/src/run/bg-sync.js +10 -3
- package/src/run/command.js +13 -11
- package/src/run/complete-handlers.js +40 -37
- package/src/run/complete.js +12 -8
- package/src/run/gate-snapshot-ledger.js +2 -1
- package/src/run/gate-snapshot.js +57 -11
- package/src/run/gates.js +31 -7
- package/src/run/prompt.js +6 -5
- package/src/run/scan-profile.js +2 -1
- package/src/run/shared.js +109 -5
- package/src/run/stage.js +5 -4
- package/src/run/test-ledger.js +2 -1
- package/src/run/verify-quality-scan.js +11 -5
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +7 -6
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +38 -22
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +3 -2
- package/src/stages/brainstorm.js +1 -1
- package/src/sync-noise.js +3 -2
- package/src/sync.js +71 -15
- package/src/task-done.js +2 -1
- package/src/task-review.js +7 -5
- package/src/task-tick.js +23 -1
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +11 -10
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +2 -1
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +1 -1
|
@@ -16,7 +16,7 @@ description: 轻量变更(默认快道)——需求明确的小/中改动两
|
|
|
16
16
|
### ① flow start(立项 + 锁基线)
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
sillyspec flow start --change <变更名> --input "
|
|
19
|
+
sillyspec flow start --change <变更名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景
|
|
20
20
|
|
|
21
21
|
成功标准:
|
|
22
22
|
- <可验证标准>"
|
package/.github/workflows/ci.yml
CHANGED
|
@@ -20,7 +20,8 @@ jobs:
|
|
|
20
20
|
- uses: actions/checkout@v4
|
|
21
21
|
- uses: actions/setup-node@v4
|
|
22
22
|
with:
|
|
23
|
-
node-version: '
|
|
23
|
+
node-version: '24'
|
|
24
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
24
25
|
- name: lint(check-syntax 全文件)
|
|
25
26
|
run: npm run lint --silent
|
|
26
27
|
|
|
@@ -32,12 +33,18 @@ jobs:
|
|
|
32
33
|
- uses: actions/checkout@v4
|
|
33
34
|
- uses: actions/setup-node@v4
|
|
34
35
|
with:
|
|
35
|
-
node-version: '
|
|
36
|
+
node-version: '24'
|
|
37
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
36
38
|
# 依赖必须装:测试框架零三方(node:sqlite/test 内置)但被测 CLI 本体 import 7 个
|
|
37
39
|
# 运行时依赖(js-yaml/chalk/@inquirer/prompts 等,src 下 10+ 文件)——CI 首跑
|
|
38
40
|
# (121a33ee)无 install 步实证两 job 全灭(Cannot find package 'js-yaml')
|
|
39
41
|
- name: install(npm ci 锁定 package-lock)
|
|
40
42
|
run: npm ci --no-audit --no-fund
|
|
43
|
+
- name: 诊断:node/dash 可用性(run-tests 子进程 shell 语义面)
|
|
44
|
+
run: |
|
|
45
|
+
which node
|
|
46
|
+
node -e "0" && echo "sh 直跑 node -e OK"
|
|
47
|
+
/bin/sh -c 'node -e "0"' && echo "sh -c 内嵌引号 OK"
|
|
41
48
|
- name: test(run-tests 并发池 + TEMP/HOME 套件隔离)
|
|
42
49
|
run: npm test --silent
|
|
43
50
|
|
|
@@ -48,7 +55,8 @@ jobs:
|
|
|
48
55
|
- uses: actions/checkout@v4
|
|
49
56
|
- uses: actions/setup-node@v4
|
|
50
57
|
with:
|
|
51
|
-
node-version: '
|
|
58
|
+
node-version: '24'
|
|
59
|
+
# mock.module 内置模块 named exports 枚举在 node 22 不全(appendFileSync 等缺失实证 38859877373)——统一 24 与本地开发环一致
|
|
52
60
|
# docs-check.js 同样 import js-yaml——无 install 首跑 7s 即灭(同 test job 根因)
|
|
53
61
|
- name: install(npm ci 锁定 package-lock)
|
|
54
62
|
run: npm ci --no-audit --no-fund
|
package/AGENTS.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
`--input` 过门格式(轻量道首次调用即过,勿靠报错学;引号内换行合法,下例可照抄):
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
sillyspec flow start --change <YYYY-MM-DD-名> --input "
|
|
19
|
+
sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文概括(首行即变更标题,必须落在 10-30 字硬区间、建议 ~20 字——不合区间 flow start 拒启);随后动机与背景
|
|
20
20
|
|
|
21
21
|
成功标准:
|
|
22
22
|
- <可验证标准>"
|
|
@@ -11,7 +11,7 @@ description: 轻量变更(默认快道):需求明确的小/中改动两步
|
|
|
11
11
|
```bash
|
|
12
12
|
export SILLYSPEC_SESSION_ID=<agent名+任务名> # 每条命令都带(shell 状态不持久;多会话并行的所有权判定依赖它)
|
|
13
13
|
# ① 立项 + 锁基线(--input 引号内换行合法,可照抄;缺「成功标准」条目会被清晰度门拦下):
|
|
14
|
-
sillyspec flow start --change <变更名> --input "
|
|
14
|
+
sillyspec flow start --change <变更名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景
|
|
15
15
|
|
|
16
16
|
成功标准:
|
|
17
17
|
- <可验证标准>"
|
package/package.json
CHANGED
package/src/archive-delta.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* endpoint-extractor,被依赖者均不反向 import 本模块。
|
|
30
30
|
*/
|
|
31
31
|
import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
32
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
32
33
|
import { basename, dirname, join } from 'node:path'
|
|
33
34
|
import { parseDecisionDomains, loadModuleMap, deriveActualModules } from './design-facts.js'
|
|
34
35
|
import { parseModuleMapSimple } from './modules.js'
|
|
@@ -153,7 +154,7 @@ export function collectDeltaSources({ changeDir, specRoot, project, runtimeRoot,
|
|
|
153
154
|
// 兜底(Gap-2/D-003):全无 reconcile → apply-pathspec-<change>.txt 逐行路径作交付清单
|
|
154
155
|
let deliverables = []
|
|
155
156
|
if (!reconcile && runtimeRoot && change) {
|
|
156
|
-
const text = readTextSafe(
|
|
157
|
+
const text = readTextSafe(runtimeStatePath(runtimeRoot, `apply-pathspec-${change}.txt`))
|
|
157
158
|
if (text !== null) {
|
|
158
159
|
deliverables = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
|
|
159
160
|
}
|
|
@@ -454,8 +455,8 @@ export function writeLastDeltaSidecar(runtimeRoot, summary) {
|
|
|
454
455
|
updatedAt: new Date().toISOString(),
|
|
455
456
|
}
|
|
456
457
|
mkdirSync(runtimeRoot, { recursive: true })
|
|
457
|
-
writeFileSync(
|
|
458
|
-
return { ok: true, path:
|
|
458
|
+
writeFileSync(runtimeStatePath(runtimeRoot, 'last-delta.json'), JSON.stringify(payload, null, 2) + '\n')
|
|
459
|
+
return { ok: true, path: runtimeStatePath(runtimeRoot, 'last-delta.json') }
|
|
459
460
|
} catch (e) {
|
|
460
461
|
console.error(`[sillyspec] last-delta sidecar 写入失败(advisory 将缺席,不阻断 delta 生成):${e && e.message ? e.message : e}`)
|
|
461
462
|
return { ok: false, reason: e && e.message ? e.message : String(e) }
|
package/src/change-delete.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 不因删变更丢用户唯一副本的代码;顺带清 execute/stage-review runId marker
|
|
13
13
|
* + 该变更的 runtime 取证(pruneArchivedChangeRuntime))
|
|
14
14
|
* - git:safeGit add -A 暂存目录删除(best-effort,对齐 archive CLI 下沉 git add)
|
|
15
|
-
* - 平台:
|
|
15
|
+
* - 平台:triggerSyncTerminalStrict 推终态(sync.js 对 status='deleted' 上行墓碑,平台收敛软删;文件树推送失败显性化)
|
|
16
16
|
*
|
|
17
17
|
* 两段式安全(对齐 --cleanup-remnant / --cleanup-ghosts / --align-execute-progress 家族):
|
|
18
18
|
* 默认 dry-run 只报告将发生什么,--confirm 才执行。
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { existsSync, readdirSync, rmSync, statSync } from 'fs';
|
|
24
24
|
import { join, relative } from 'path';
|
|
25
25
|
import { safeGit } from './git-helper.js';
|
|
26
|
-
import { assertSafeChangeName,
|
|
26
|
+
import { assertSafeChangeName, triggerSyncTerminalStrict } from './run/shared.js';
|
|
27
27
|
import { archiveWorktreeCleanup } from './run/complete-handlers.js';
|
|
28
28
|
|
|
29
29
|
function countFiles(dir) {
|
|
@@ -156,10 +156,11 @@ export async function deleteChange({ cwd, specDir = null, changeName, confirm =
|
|
|
156
156
|
safeGit(cwd, ['add', '-A', '--', relDir]);
|
|
157
157
|
}
|
|
158
158
|
} catch { /* 非 git 仓等场景:删除在 git status 可见,暂存非必需 */ }
|
|
159
|
-
// 5.
|
|
160
|
-
//
|
|
159
|
+
// 5. 平台终态/墓碑上行(终态强校验:内联有界同步,文件树推送失败显性化——🚨 告警 +
|
|
160
|
+
// 自救命令 + exit 1;未连接静默。change-delete 是终态一次性命令,await 保证墓碑
|
|
161
|
+
// 发出后再退出,输出顺序确定)
|
|
161
162
|
try {
|
|
162
|
-
await
|
|
163
|
+
await triggerSyncTerminalStrict(cwd, changeName, { specRoot: specBase });
|
|
163
164
|
} catch (e) {
|
|
164
165
|
warnings.push(`平台终态同步失败(不阻断删除,下次常规同步重试):${e.message}`);
|
|
165
166
|
}
|
package/src/contract-matrix.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs'
|
|
12
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
12
13
|
import { join, resolve, basename, dirname, relative, isAbsolute } from 'path'
|
|
13
14
|
import { gitQuiet } from './git-helper.js'
|
|
14
15
|
import { splitOwnVsForeignDiffFiles } from './foreign-declared.js'
|
|
@@ -440,7 +441,7 @@ function _resolveDiffFilesForParity(specBase, scanRoot, changeName, runtimeRoot)
|
|
|
440
441
|
// 主仓 diff 空(apply 后已 commit / cleanup 完毕)→ apply-pathspec 兜底:apply 成功时落盘的
|
|
441
442
|
// 本变更精确文件清单(与 gates 归因提示②同源)。仍拿不到才回 null(调用方退全仓)。
|
|
442
443
|
try {
|
|
443
|
-
const pathspecFile =
|
|
444
|
+
const pathspecFile = runtimeStatePath(runtimeRoot || join(specBase, '.runtime'), `apply-pathspec-${changeName}.txt`)
|
|
444
445
|
if (existsSync(pathspecFile)) {
|
|
445
446
|
const fromPathspec = readFileSync(pathspecFile, 'utf8').split('\n').map(l => l.trim()).filter(Boolean)
|
|
446
447
|
if (fromPathspec.length > 0) {
|
package/src/cross-placement.js
CHANGED
|
@@ -48,9 +48,13 @@ export function crossWorktreePath(specBase, changeName, repoKey, placementRoot =
|
|
|
48
48
|
* @param {string} mainRoot 主仓根(相对路径解析基准)
|
|
49
49
|
* @returns {string} 绝对落位根
|
|
50
50
|
*/
|
|
51
|
+
// Windows 盘符形态判定(坑跨平台 CI:POSIX isAbsolute('C:/x')=false → 带引号的盘符路径
|
|
52
|
+
// 被相对主仓根拼接成 test/C:/quoted 伪路径)。跨仓落位本就面向 Windows 盘符场景设计,
|
|
53
|
+
// 语义上盘符路径恒绝对(与 win32 path.isAbsolute 同判,跨平台自洽)。
|
|
54
|
+
const WIN_DRIVE_ABS_RE = /^[A-Za-z]:[/\\]/;
|
|
51
55
|
export function resolvePlacementRoot(raw, mainRoot) {
|
|
52
56
|
const trimmed = String(raw || '').trim().replace(/^['"]|['"]$/g, '');
|
|
53
|
-
if (isAbsolute(trimmed)) return resolvePath(trimmed);
|
|
57
|
+
if (isAbsolute(trimmed) || WIN_DRIVE_ABS_RE.test(trimmed)) return resolvePath(trimmed);
|
|
54
58
|
return resolvePath(mainRoot, trimmed);
|
|
55
59
|
}
|
|
56
60
|
|
package/src/db.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openDatabase, applyPragmas, runTransaction } from './db-engine.js';
|
|
2
|
+
import { runtimeTempPath } from './runtime-paths.js'
|
|
2
3
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
3
4
|
import { dirname } from 'path';
|
|
4
5
|
|
|
@@ -102,7 +103,8 @@ export class DB {
|
|
|
102
103
|
* 全新 node:sqlite 项目不产生主 .bak)。
|
|
103
104
|
*/
|
|
104
105
|
_openWithFallback() {
|
|
105
|
-
|
|
106
|
+
// .bak 2026-10-18 布局分层后归 temp/;根路径保留兜底(未迁移旧仓/混版本窗口)
|
|
107
|
+
const bakPath = existsSync(`${this.dbPath}.bak`) ? `${this.dbPath}.bak` : runtimeTempPath(dirname(this.dbPath), 'sillyspec.db.bak');
|
|
106
108
|
// readValid: null=文件不存在;'empty'=存在但 0 字节;'ok'=有内容
|
|
107
109
|
const readValid = (p) => {
|
|
108
110
|
if (!existsSync(p)) return null;
|
|
@@ -156,7 +158,11 @@ export class DB {
|
|
|
156
158
|
// 覆盖前救援快照(2026-08-21 审查 BUG-7):主库有内容但打不开("损坏")时,其中可能
|
|
157
159
|
// 含比 .bak 更新的数据——静默覆盖即静默丢。best-effort 留一份 .corrupt-<ts> 副本供人工恢复。
|
|
158
160
|
if (primaryRaw === 'ok') {
|
|
159
|
-
try {
|
|
161
|
+
try {
|
|
162
|
+
const corruptPath = runtimeTempPath(dirname(this.dbPath), `sillyspec.db.corrupt-${Date.now()}`);
|
|
163
|
+
try { mkdirSync(dirname(corruptPath), { recursive: true }); } catch { /* fail-open */ }
|
|
164
|
+
copyFileSync(this.dbPath, corruptPath);
|
|
165
|
+
} catch { /* 救援失败不阻断恢复 */ }
|
|
160
166
|
}
|
|
161
167
|
// node:sqlite 绑定路径,无 _save:把 .bak 内容 copy 回主库,统一用 dbPath。
|
|
162
168
|
// 体检 BUG-18:主库可能正被其他进程(daemon/另一 CLI)打开——Windows 覆盖他人
|
package/src/deleted-ledger.js
CHANGED
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
* 坏行容错:单行 JSON 解析失败跳过该行(外部编辑/半写截断不拖垮读取)。
|
|
15
15
|
*/
|
|
16
16
|
import { appendFileSync, existsSync, readFileSync } from 'node:fs'
|
|
17
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
17
18
|
import { join } from 'node:path'
|
|
18
19
|
|
|
19
20
|
const LEDGER_FILENAME = 'deleted-changes.jsonl'
|
|
20
21
|
|
|
21
22
|
function ledgerPath(specDir) {
|
|
22
|
-
return join(specDir, '.runtime', LEDGER_FILENAME)
|
|
23
|
+
return runtimeStatePath(join(specDir, '.runtime'), LEDGER_FILENAME)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* writer 落盘到 <authoritySpecDir>/.runtime/。
|
|
24
24
|
*/
|
|
25
25
|
import { openDatabase, pluckGet, pluckAll } from './db-engine.js';
|
|
26
|
+
import { runtimeTempPath } from './runtime-paths.js'
|
|
26
27
|
import { safeGit } from './git-helper.js';
|
|
27
28
|
import { createHash } from 'crypto';
|
|
28
29
|
import { existsSync, statSync, readFileSync, readdirSync, mkdirSync, writeFileSync, unlinkSync, rmSync } from 'fs';
|
|
@@ -1109,12 +1110,15 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
|
|
|
1109
1110
|
const warnings = [];
|
|
1110
1111
|
|
|
1111
1112
|
// ① 活跃非零 tally 列示(events:{type:{count,lastAt}},X-10 结构)
|
|
1113
|
+
// 2026-10-18 布局分层:tally 落 state/;根目录扫描保留旧布局兜底(入口迁移收敛后恒空)
|
|
1112
1114
|
try {
|
|
1113
|
-
|
|
1115
|
+
const tallyDirs = [join(runtimeRoot, 'state'), runtimeRoot]
|
|
1116
|
+
for (const tallyDir of tallyDirs) {
|
|
1117
|
+
for (const f of (() => { try { return readdirSync(tallyDir) } catch { return [] } })()) {
|
|
1114
1118
|
const m = f.match(/^friction-tally-(.+)\.json$/);
|
|
1115
1119
|
if (!m) continue;
|
|
1116
1120
|
let tally = null;
|
|
1117
|
-
try { tally = JSON.parse(readFileSync(join(
|
|
1121
|
+
try { tally = JSON.parse(readFileSync(join(tallyDir, f), 'utf8')); } catch { continue; }
|
|
1118
1122
|
const events = tally && typeof tally === 'object' && tally.events && typeof tally.events === 'object' && !Array.isArray(tally.events) ? tally.events : {};
|
|
1119
1123
|
const segs = [];
|
|
1120
1124
|
let total = 0;
|
|
@@ -1131,6 +1135,7 @@ export function detectSelfMaintenanceTax(cwd, authoritySpecDir) {
|
|
|
1131
1135
|
findings.push(`活跃摩擦:${m[1]}(${segs.join('、')}${lastAt ? `,最近 ${lastAt}` : ''})`);
|
|
1132
1136
|
if (total >= TAX_HEAVY_TOTAL) warnings.push(`税重——评估该机制群简化/退役(对照其 decisions 退役判据):${m[1]}(活跃 total ${total})`);
|
|
1133
1137
|
}
|
|
1138
|
+
}
|
|
1134
1139
|
} catch { /* runtimeRoot 不可读 → 无活跃数据,不误报 */ }
|
|
1135
1140
|
|
|
1136
1141
|
// ② 台账聚合(近 90 天 top5 + 累计总量);④ 缺失渲染提示不告警
|
|
@@ -1748,7 +1753,7 @@ export function writeDoctorDiagnosis(json, specDir) {
|
|
|
1748
1753
|
try {
|
|
1749
1754
|
const runtimeDir = join(specDir, '.runtime');
|
|
1750
1755
|
mkdirSync(runtimeDir, { recursive: true });
|
|
1751
|
-
const outPath =
|
|
1756
|
+
const outPath = runtimeTempPath(runtimeDir, 'doctor-diagnosis.json');
|
|
1752
1757
|
writeFileSync(outPath, JSON.stringify(json, null, 2) + '\n');
|
|
1753
1758
|
return outPath;
|
|
1754
1759
|
} catch (e) {
|
package/src/flow-draft.js
CHANGED
|
@@ -23,6 +23,7 @@ import { wrapSection, verifyMarkers, reanchorText, bodyHash, parseMarkerBlocks }
|
|
|
23
23
|
// 占位句常量真同源(2026-10-03-fr-skeleton-gate 评审 P3 清偿):起草端 Then 兜底与骨架判据
|
|
24
24
|
// (fr-index.isThinSkeletonBodies)共用同一字面量——两处独立字符串会被评审实证为「镜像非同源」。
|
|
25
25
|
import { SKELETON_THEN_PLACEHOLDER } from './fr-index.js'
|
|
26
|
+
import { runtimeStatePath } from './runtime-paths.js'
|
|
26
27
|
|
|
27
28
|
/** 读文件并归一化 CRLF→LF(2026-09-25-feedback-fixes④:Python/编辑器写盘 CRLF 会破坏
|
|
28
29
|
锚定的槽位识别 regex)。 */
|
|
@@ -88,7 +89,7 @@ const QUESTION_REPAIR_SIM_THRESHOLD = 0.5
|
|
|
88
89
|
|
|
89
90
|
/** sidecar 台账路径(.runtime 下,verify-draft sidecar 同族;命名空间=文件:段键)。 */
|
|
90
91
|
export function draftLedgerPath(runtimeRoot, changeName) {
|
|
91
|
-
return
|
|
92
|
+
return runtimeStatePath(runtimeRoot, `draft-ledger-${changeName}.json`)
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const AGENT_SLOT = (n, hint) => `<!--AGENT:槽${n} ${hint}——例外裁决书写面(机器段之外合法) -->`
|
package/src/flow.js
CHANGED
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
* 幂等循 task-done 先例:子步各查自身完成标记,中断半态重入断点续,中段失败精确报告。
|
|
27
27
|
*/
|
|
28
28
|
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, renameSync, statSync } from 'node:fs'
|
|
29
|
+
import { runtimeStatePath, runtimeTempPath } from './runtime-paths.js'
|
|
29
30
|
import { join, relative, dirname } from 'node:path'
|
|
30
31
|
import { DatabaseSync } from 'node:sqlite'
|
|
31
32
|
import yaml from 'js-yaml'
|
|
32
33
|
import { git, gitQuiet } from './git-helper.js'
|
|
33
34
|
import { writeAtomicSync } from './fs-atomic.js'
|
|
34
|
-
import { resolveRuntimeRoot, triggerSync, assertDatedChangeName } from './run/shared.js'
|
|
35
|
+
import { resolveRuntimeRoot, triggerSync, triggerSyncTerminalStrict, assertDatedChangeName } from './run/shared.js'
|
|
35
36
|
import { detectUiTouch, buildUiGuidanceLines, runUiVisualProbe, readUiVisualGate, UI_EVIDENCE_FILENAME } from './ui-visual.js'
|
|
36
37
|
import { runHunkAttributionGate, readHunkGate, renderHunkAttributionLines } from './hunk-attribution.js'
|
|
37
38
|
// 2026-10-13-thin-sync-stage:FLOW_STATE_FILE/readFlowState 单一源上移叶子模块 flow-state.js
|
|
@@ -686,18 +687,24 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
686
687
|
try {
|
|
687
688
|
const { spawnWatcher } = await import('./watcher.js')
|
|
688
689
|
const w = await spawnWatcher(cwd, change, { specBase })
|
|
689
|
-
if (w.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
690
|
+
if (w.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/state/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
690
691
|
} catch { /* 观测旁路 best-effort */ }
|
|
691
|
-
// adopt
|
|
692
|
-
//
|
|
693
|
-
//
|
|
692
|
+
// adopt 标题(2026-09-29;2026-10-16-short-change-title 提炼;2026-10-16-title-range-gate
|
|
693
|
+
// 硬区间 10-30):头脑风暴 proposal H1 人写语义标题(纯模板 H1 回退动机首句)→ --title
|
|
694
|
+
// 优先(合区间校验,超界 exit 2)→ --input 首行提炼 → 兜底变更名。收编是既成事实的
|
|
695
|
+
// 延续面,非 flag 的超界不硬断(收编半态不可恢复)打 ⚠️ 强提示。
|
|
694
696
|
try {
|
|
695
|
-
const { deriveTitleFromLinkedChange,
|
|
696
|
-
|
|
697
|
+
const { deriveTitleFromLinkedChange, deriveShortChangeTitle, isValidChangeTitle, shortTitleRejectMessage, shortTitleRangeWarnLine } = await import('./quicklog.js')
|
|
698
|
+
if (titleFlag && !isValidChangeTitle(titleFlag)) {
|
|
699
|
+
console.error(shortTitleRejectMessage(titleFlag))
|
|
700
|
+
process.exit(2)
|
|
701
|
+
}
|
|
702
|
+
const adoptTitle = titleFlag || deriveTitleFromLinkedChange(specBase, change) || deriveShortChangeTitle(input) || change
|
|
697
703
|
pm.initChange(cwd, change, { title: adoptTitle })
|
|
698
704
|
const _arow = pm._ensureDB(cwd).getDb().prepare('SELECT title FROM changes WHERE name = ?').get(change)
|
|
699
705
|
if (_arow && !_arow.title) pm.updateChangeMeta(cwd, change, { title: adoptTitle })
|
|
700
706
|
console.log(`🏷️ 变更标题:${adoptTitle}(收编自头脑风暴产物;重入 --title 可改)`)
|
|
707
|
+
if (!isValidChangeTitle(adoptTitle)) console.log(shortTitleRangeWarnLine(adoptTitle))
|
|
701
708
|
} catch { /* adopt 标题 best-effort */ }
|
|
702
709
|
const materials = materialPaths(specBase, change, changeDir)
|
|
703
710
|
const artifacts = changeArtifactPaths(changeDir)
|
|
@@ -719,7 +726,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
719
726
|
`② 执行走任务循环:对 tasks.md 每个 pending 行——展示「Working on task N/M: <任务>」→ 做 → 测试绿后当场勾一格(\`- [ ]\`→\`- [x]\`:sillyspec task tick --change ${change} --task task-NN 或直接 Edit)→ 下一行;全勾后 flow done(收口硬门拒单拍多格勾选)。design/decisions 是本变更的承诺锚(flow done 豁免 design 四节槽,以其为准)。`,
|
|
720
727
|
`requirements 测试绑定槽(收编追加)每条 FR 至少一行作答;写码前后顺手填。`,
|
|
721
728
|
`✅ 任务面归你(工作分解契约):tasks.md 是你的工作队列(机器种子可增删改,保持 \`- [ ] task-NN:\` 行形态)——CLI 以 checkbox 为进度状态机(task tick/flow status 实时读它),TodoWrite 类工具不替代;`,
|
|
722
|
-
` 勾选是收口证据面(逐格核提交 token/review.json):做一件 → 勾一格(task tick 即时回显进度与下一任务)→
|
|
729
|
+
` 勾选是收口证据面(逐格核提交 token/review.json):做一件 → 勾一格(task tick 即时回显进度与下一任务)→ 继续下一条,勿攒一把勾;勾选修改与任务交付同一提交入库(pathspec 带上本变更 tasks.md——task tick 回显给现成命令);`,
|
|
723
730
|
` 任务拟法:收口/推送/发布类动作不单列 task(协议自身承担,成功标准照写 requirements);确需单列的,勾选前先有消息预埋 task-NN token 的交付提交(回执类证据写进提交消息)。`,
|
|
724
731
|
` flow status --change ${change} 为自愿查看/恢复面(非协议必需,D-007 中间零必需交互;恢复时给下一任务指针与进度)。`,
|
|
725
732
|
`⚠️ 交付纪律:收口前交付代码显式 pathspec 提交——冻结件范围=baseline..HEAD,未提交不进审计件。`,
|
|
@@ -780,17 +787,24 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
780
787
|
writeFlowState(changeDir, { autopilot: true })
|
|
781
788
|
console.log(`🤖 已声明 autopilot:spec 断点人审豁免(用户显式声明留痕)`)
|
|
782
789
|
}
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
790
|
+
// 变更标题补写(2026-09-29 title=中文概括;2026-10-16-short-change-title 提炼 ≤30;
|
|
791
|
+
// 2026-10-16-title-range-gate 硬区间 10-30)——存量在途变更 title 空/缺失时的回填通道:
|
|
792
|
+
// 显式 --title 恒生效(重入改标题)且必须合区间(超界 exit 2,参数面最易修正);提炼补写
|
|
793
|
+
// 仅当库内 title 为空才写(首写者胜),超界不阻断(resume 不可断)打 ⚠️ 强提示。
|
|
786
794
|
try {
|
|
787
|
-
const {
|
|
795
|
+
const { deriveShortChangeTitleEx, isValidChangeTitle, shortTitleRejectMessage, shortTitleRangeWarnLine, SHORT_TITLE_HARDCUT_HINT } = await import('./quicklog.js')
|
|
796
|
+
if (titleFlag && !isValidChangeTitle(titleFlag)) {
|
|
797
|
+
console.error(shortTitleRejectMessage(titleFlag))
|
|
798
|
+
process.exit(2)
|
|
799
|
+
}
|
|
788
800
|
const _row = pm._ensureDB(cwd).getDb().prepare('SELECT title FROM changes WHERE name = ?').get(change)
|
|
789
801
|
const _cur = _row ? (_row.title || '') : ''
|
|
790
|
-
const _derived = titleFlag
|
|
791
|
-
if (_derived && (titleFlag || !_cur)) {
|
|
792
|
-
pm.updateChangeMeta(cwd, change, { title: _derived })
|
|
793
|
-
console.log(`🏷️ 变更标题${titleFlag ? '已更新' : '已补写'}:${_derived}
|
|
802
|
+
const _derived = titleFlag ? { title: titleFlag, hardCut: false } : deriveShortChangeTitleEx(input)
|
|
803
|
+
if (_derived && _derived.title && (titleFlag || !_cur)) {
|
|
804
|
+
pm.updateChangeMeta(cwd, change, { title: _derived.title })
|
|
805
|
+
console.log(`🏷️ 变更标题${titleFlag ? '已更新' : '已补写'}:${_derived.title}(面板显示用;须 10-30 字、建议 ~20 字,重入 --title 可改)`)
|
|
806
|
+
if (_derived.hardCut) console.log(SHORT_TITLE_HARDCUT_HINT)
|
|
807
|
+
if (!titleFlag && !isValidChangeTitle(_derived.title)) console.log(shortTitleRangeWarnLine(_derived.title))
|
|
794
808
|
}
|
|
795
809
|
} catch { /* 标题补写 best-effort */ }
|
|
796
810
|
// 知识注入(2026-09-25-thin-fr-inject-parity):resume 路径域路由走基线以来文件面——
|
|
@@ -893,16 +907,29 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
893
907
|
// 形态信号:清晰度门管「需求说不清楚」(预段收编),升厚只留用户决策(--upgrade-thick 同意门)
|
|
894
908
|
// 与运行时证据(实测失败升档/edit_ratio/评审——风险面在收口时点按承诺词/diff 原语/盲维判定)。
|
|
895
909
|
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
|
|
910
|
+
// 变更标题硬区间门(2026-10-16-title-range-gate:10-30 字必须、建议 ~20、强制 agent 命名;
|
|
911
|
+
// 提炼口径见 2026-10-16-short-change-title):--title 显式 > --input 首行提炼 > 变更名兜底。
|
|
912
|
+
// agent 命名时刻强制:不在区间/无 --input 兜底 → 拒绝启动(exit 2,在任何状态写入前——
|
|
913
|
+
// 零半态);平台 writer 预建空目录(自动派发,无 agent 对话面)豁免硬门仅 ⚠️ 留痕。
|
|
914
|
+
let _st = null
|
|
915
|
+
let _rejectMsg = null
|
|
900
916
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
917
|
+
const _q = await import('./quicklog.js')
|
|
918
|
+
_st = _q.resolveStartTitle({ titleFlag, input, change, enforce: !proceedFreshEmptyDir })
|
|
919
|
+
if (!_st.ok) _rejectMsg = _q.shortTitleRejectMessage(_st.kind === 'fallback' ? _st.rejectedTitle : _st.title)
|
|
920
|
+
} catch { /* quicklog 不可用(实践不可达):降级保持旧行为兜底 */ _st = { title: titleFlag || change, ok: true, kind: titleFlag ? 'flag' : 'fallback', hardCut: false } }
|
|
921
|
+
if (_rejectMsg) {
|
|
922
|
+
console.error(_rejectMsg)
|
|
923
|
+
process.exit(2)
|
|
924
|
+
}
|
|
925
|
+
const changeTitle = _st.title
|
|
904
926
|
pm.initChange(cwd, change, { title: changeTitle })
|
|
905
|
-
console.log(`🏷️ 变更标题:${changeTitle}
|
|
927
|
+
console.log(`🏷️ 变更标题:${changeTitle}(面板显示用;须 10-30 字、建议 ~20 字,重入 flow start --title 可改)`)
|
|
928
|
+
try {
|
|
929
|
+
const { isValidChangeTitle, shortTitleRangeWarnLine, SHORT_TITLE_HARDCUT_HINT } = await import('./quicklog.js')
|
|
930
|
+
if (_st.hardCut) console.log(SHORT_TITLE_HARDCUT_HINT)
|
|
931
|
+
if (proceedFreshEmptyDir && !isValidChangeTitle(changeTitle)) console.log(shortTitleRangeWarnLine(changeTitle))
|
|
932
|
+
} catch { /* 提示行 best-effort */ }
|
|
906
933
|
try {
|
|
907
934
|
const { resolveSessionIdentity } = await import('./progress.js')
|
|
908
935
|
const { session } = resolveSessionIdentity({ flagSession: null, cwd })
|
|
@@ -915,7 +942,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
915
942
|
try {
|
|
916
943
|
const { spawnWatcher } = await import('./watcher.js')
|
|
917
944
|
const r = await spawnWatcher(cwd, change, { specBase })
|
|
918
|
-
if (r.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
945
|
+
if (r.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/state/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
919
946
|
} catch { /* 观测旁路 best-effort,绝不阻断协议面 */ }
|
|
920
947
|
|
|
921
948
|
const head = gitQuiet(cwd, ['rev-parse', 'HEAD'])
|
|
@@ -1112,13 +1139,13 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
1112
1139
|
` 也有 1/4 抽查采样。要强制/豁免可重启时带 --review / --no-review${reviewForce === true ? '(本变更已声明 --review)' : reviewForce === false ? '(本变更已声明 --no-review)' : ''}。`,
|
|
1113
1140
|
`⚠️ 交付纪律:收口前先把交付代码用显式 pathspec 提交(git add -- <文件> && git commit)——`,
|
|
1114
1141
|
` patch 冻结件范围=baseline..HEAD 提交面,未提交的代码不进审计件(R16 评审 P2 实证);`,
|
|
1115
|
-
` tasks.md 一并显式 pathspec 提交(勾选证据进 git 历史,勿 untracked 直至归档——R19
|
|
1142
|
+
` tasks.md 一并显式 pathspec 提交(勾选证据进 git 历史,勿 untracked 直至归档——R19 实证;task tick 回显会给现成命令)。`,
|
|
1116
1143
|
`✅ 任务面归你(工作分解契约,2026-10-07-thin-tasks-v3):tasks.md 是你的工作队列——机器种子按真实实现路径改写`,
|
|
1117
1144
|
` (每行=可独立完成并当场验证的一步:做什么+怎么验证;增删改随意,保持 \`- [ ] task-NN:\` 行形态);`,
|
|
1118
1145
|
` 执行循环:对每个未勾行 Working on task N/M → 做一件 → 测试绿后当场勾这一格 → 下一行——CLI 以 checkbox 为进度状态机`,
|
|
1119
1146
|
` (sillyspec task tick --change ${change} --task task-NN 即时回显进度 N/M 与下一任务指针,flow status 同源;或直接 Edit 翻格 \`- [ ]\`→\`- [x]\`),watcher 实时上平台。`,
|
|
1120
1147
|
` ⚠️ harness 的 TodoWrite 类工具是会话内便利面,不替代 tasks.md——平台进度/收口证据只读它(0/12 事故实证:todo 全 completed 而 tasks.md 0 勾);`,
|
|
1121
|
-
` 勾选是收口证据面:每格勾选要有对应提交(消息带 task-NN——逐个列出或 /、,连写如 task-01/02/03,「task-01..task-05
|
|
1148
|
+
` 勾选是收口证据面:每格勾选要有对应提交(消息带 task-NN——逐个列出或 /、,连写如 task-01/02/03,「task-01..task-05」区间写法不认;pathspec 带上本变更 tasks.md——勾选修改与任务交付同一提交入库,task tick 回显会给现成命令)或 review.json,全勾但零证据会被拒收;勿攒一把勾(收口硬门拒单拍多格);`,
|
|
1122
1149
|
// 任务拟法指引(2026-10-12-closeout-task-guidance,三次「哨兵抓收口型任务零证据」复盘):
|
|
1123
1150
|
// 成功标准≠工作分解——收口/推送/发布是协议自身承担的动作;单列则证据(token 提交)必须先于勾选存在。
|
|
1124
1151
|
` 任务拟法:收口/推送/发布类动作不单列 task(提交、归档、推送是 flow start→done 协议自身承担的动作——成功标准照写 requirements,勿逐字搬进任务清单);确需单列的收口/操作型任务,勾选前先做消息预埋 task-NN token 的交付提交——回执类证据(push/publish 回执、构建日志路径)写进提交消息,勿依赖勾选后补;`,
|
|
@@ -1177,7 +1204,7 @@ function printRecoveryBriefing({ cwd, specBase, change, changeDir, runtimeRoot,
|
|
|
1177
1204
|
? (gitQuiet(cwd, ['rev-list', '--count', `${st.baseline_commit}..HEAD`]) || '0').toString().trim()
|
|
1178
1205
|
: '?'
|
|
1179
1206
|
const dirty = changedFilesSinceBaseline(cwd, st.baseline_commit).length
|
|
1180
|
-
const hasLedger = existsSync(
|
|
1207
|
+
const hasLedger = existsSync(runtimeStatePath(runtimeRoot, `verify-quality-scan-${change}.json`))
|
|
1181
1208
|
console.log([
|
|
1182
1209
|
`🔁 flow start 恢复简报(重入): ${change}(tier=${st.tier}${st.legacy_fallback ? ',legacy_fallback=true 混跑回退中' : ''})`,
|
|
1183
1210
|
`══════════════════════════════════════════════════════`,
|
|
@@ -1265,7 +1292,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1265
1292
|
// 不许在失败面丢失;成功收口走函数末尾同款记录,失败面只多不少)
|
|
1266
1293
|
const appendTelemetry = (review) => {
|
|
1267
1294
|
try {
|
|
1268
|
-
appendFileSync(
|
|
1295
|
+
appendFileSync(runtimeTempPath(runtimeRoot, 'flow-telemetry.jsonl'), JSON.stringify({
|
|
1269
1296
|
ts: new Date().toISOString(), change, protocolCalls: 2,
|
|
1270
1297
|
draftAmendments: st.edit_ratio != null ? 1 : 0, editRatio: st.edit_ratio ?? null,
|
|
1271
1298
|
routeHint: st.route_hint ?? null, tier: st.tier, upgraded: st.upgrade_reason ?? null,
|
|
@@ -1369,6 +1396,22 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1369
1396
|
let _sentinelNonMirrorTasks = null // 镜像豁免面外的任务数(null=哨兵未跑;0=纯镜像任务面——节奏 advisory 静默)
|
|
1370
1397
|
let _sentinelEvidenceComplete = false // 哨兵证据完备(status=complete——每格已勾任务均有 token/review.json):单拍降级判据(2026-10-13-batchtick-evidence-domain-hint)
|
|
1371
1398
|
let _autopilotTicked = 0 // 机器代勾格数(>0 时勾选节奏门降 advisory——代勾是单拍多格机械写,非 agent 纪律面)
|
|
1399
|
+
// 勾选观测共享采集(2026-10-17-tick-timing-source):watcher 事件流 + task-ticks.jsonl 侧车
|
|
1400
|
+
// 一次读取——时点判定与勾选节奏门同源消费(此前节奏门单独读,现两门一份数据;检测逻辑
|
|
1401
|
+
// 与优先序不变:事件流先判、空结果回退侧车突发)。侧车=CLI tick 逐格毫秒时戳事实源(随
|
|
1402
|
+
// 归档进 git、不受观测盲窗影响)。任一源缺失按空(fail-open),绝不阻断收口。
|
|
1403
|
+
const _tickObs = await (async () => {
|
|
1404
|
+
try {
|
|
1405
|
+
const { readWatcherEvents } = await import('./watcher.js')
|
|
1406
|
+
const { detectBatchCheckCadence, detectTickBurst } = await import('./sentinel-assertions.js')
|
|
1407
|
+
const stream = readWatcherEvents({ runtimeRoot, change })
|
|
1408
|
+
let ticks = []
|
|
1409
|
+
try { ticks = (await import('./timeline.js')).parseTickLog(readFileSync(join(changeDir, 'task-ticks.jsonl'), 'utf8')) } catch { /* 侧车缺失/不可读按空(fail-open) */ }
|
|
1410
|
+
let batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
|
|
1411
|
+
if (!batch) batch = detectTickBurst(ticks)
|
|
1412
|
+
return { stream, ticks, batch }
|
|
1413
|
+
} catch { return { stream: { exists: false, events: [] }, ticks: [], batch: null } }
|
|
1414
|
+
})()
|
|
1372
1415
|
// 证据面=整条提交消息(2026-09-25-thin-done-gate-calibration 坑2:%s 只取标题行,正文里的
|
|
1373
1416
|
// token 被判零证据,与文案「提交带 task-NN」口径漂移)——%B%x1e 按提交切记录,advisory 的
|
|
1374
1417
|
// 提交计数不因多行正文失真。
|
|
@@ -1436,20 +1479,31 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1436
1479
|
if (sent.status === 'complete') {
|
|
1437
1480
|
_sentinelEvidenceComplete = true
|
|
1438
1481
|
console.log(`🛡️ 哨兵:全勾 ${sent.checked}/${sent.claimTotal} 证据齐(提交 token/review.json)`)
|
|
1439
|
-
// 勾选时点判定(2026-
|
|
1440
|
-
// ==
|
|
1441
|
-
//
|
|
1442
|
-
//
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1482
|
+
// 勾选时点判定(2026-10-17-tick-timing-source 换源):判据从「tasks.md git 首次提交
|
|
1483
|
+
// == 收口最后提交」整体退役——git 历史形态被官方允许的 reset --soft 压扁无声摧毁
|
|
1484
|
+
// (tick-timing-squash-blindspot 坑:并行会话压扁归档把边干边勾的渐进历史抹平,警告
|
|
1485
|
+
// 照出,结构性误报;且 tasks.md 不进交付 pathspec 的纪律下「untracked 直至归档」恒
|
|
1486
|
+
// 复现)。换源=task-ticks.jsonl 侧车墙钟时戳(detectTickTiming 纯函数:跨度超 120s
|
|
1487
|
+
// =边干边勾确认;挤 ≤120s 短窗且变更存活 ≥10×跨度=末尾集中勾选)——墙钟时戳是文件
|
|
1488
|
+
// 事实,压扁/rebase 不可改写,与「审计真相锚 change.patch sha256、不依赖 git 形态」
|
|
1489
|
+
// 口径对齐。节奏门已检出单拍跳/突发时让位(防双重警告——其判定覆盖同一批格子);
|
|
1490
|
+
// 无侧车观测输出一行 task tick 教学(不再作结构性警告);advisory 不阻断语义不变。
|
|
1445
1491
|
try {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1492
|
+
if (_tickObs.batch) {
|
|
1493
|
+
/* 节奏门将报单拍多格/突发——时点判定让位 */
|
|
1494
|
+
} else {
|
|
1495
|
+
const _baseRaw = st && st.baseline_commit ? gitQuiet(cwd, ['show', '-s', '--format=%ct', String(st.baseline_commit)]) : null
|
|
1496
|
+
const _baseTs = Number(String(_baseRaw ?? '').trim())
|
|
1497
|
+
const { detectTickTiming } = await import('./sentinel-assertions.js')
|
|
1498
|
+
const t = detectTickTiming({ ticks: _tickObs.ticks, lifespanMs: Number.isFinite(_baseTs) && _baseTs > 0 ? Date.now() - _baseTs * 1000 : NaN })
|
|
1499
|
+
if (t.status === 'concentrated') {
|
|
1500
|
+
console.warn(`⚠️ 勾选时点:${t.count} 格勾选时戳集中在 ${Math.max(1, Math.round(t.spanMs / 1000))}s 短窗内(末尾集中勾选形态)——token 证据齐放行,但边干边勾是进度锚,下次完成单元即勾(task tick 回显含现成提交命令)`)
|
|
1501
|
+
} else if (t.status === 'spread') {
|
|
1502
|
+
console.log(`✅ 勾选时点:${t.count} 格勾选时戳跨度 ${Math.max(1, Math.round(t.spanMs / 60000))} 分钟——边干边勾确认(判据=task-ticks 侧车墙钟时戳,不受 git 压扁影响)`)
|
|
1503
|
+
} else if (t.status === 'no-ticks') {
|
|
1504
|
+
console.log(`ℹ️ 勾选时点:无 tick 时戳观测(未走 task tick)——下次勾选建议 sillyspec task tick --change ${change} --task task-NN(即时回显进度与现成提交命令)`)
|
|
1505
|
+
}
|
|
1506
|
+
/* 'single'/'no-anchor' 静默:单格无节奏可言;缺存活锚 fail-open 不判 */
|
|
1453
1507
|
}
|
|
1454
1508
|
} catch { /* 时点判定 fail-soft */ }
|
|
1455
1509
|
}
|
|
@@ -1459,7 +1513,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1459
1513
|
// 任务面与实际不符可先改写 tasks.md(工作分解归 agent)再逐格勾。token 代勾(governance-
|
|
1460
1514
|
// autopilot)已在上方先跑:有证据未勾的格子已代勾,此处拒的纯属零证据未完成面。
|
|
1461
1515
|
if (sent.claimTotal > 0 && sent.checked < sent.claimTotal) {
|
|
1462
|
-
console.error(`🚫 任务未全勾(${sent.checked}/${sent.claimTotal})——tasks.md 是完成状态机:完成一件勾一格(每格勾选需对应提交带 task-NN token 或 review.json
|
|
1516
|
+
console.error(`🚫 任务未全勾(${sent.checked}/${sent.claimTotal})——tasks.md 是完成状态机:完成一件勾一格(每格勾选需对应提交带 task-NN token 或 review.json;提交 pathspec 带上本变更 tasks.md——勾选修改与任务交付同一提交入库),全勾后重跑 flow done`)
|
|
1463
1517
|
console.error(' 任务面与实际实现路径不符时,先改写 tasks.md(工作分解归你,增删改随意)再逐格勾;查下一任务:sillyspec task tick --change ' + `${change}` + ' --task task-NN(回显进度与指针)')
|
|
1464
1518
|
appendTelemetry({ sentinel: 'incomplete-tasks', checked: sent.checked, total: sent.claimTotal })
|
|
1465
1519
|
reportMidFail('ledger')
|
|
@@ -1477,19 +1531,12 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1477
1531
|
// 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):CLI 收口端快连点对事件流不可见(单格
|
|
1478
1532
|
// 精确跳),空结果时回退 task-ticks.jsonl 毫秒时戳判突发——CLI 与直接 Edit 两条路径同判。
|
|
1479
1533
|
try {
|
|
1480
|
-
const {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
// task-ticks.jsonl(CLI tick 毫秒时戳事实源,随归档进 git、不受观测盲窗影响)判快连点;
|
|
1487
|
-
// 事件流结果优先不双重报告;侧车缺失/坏文件按无证据静默(fail-open)。
|
|
1488
|
-
try {
|
|
1489
|
-
const { parseTickLog } = await import('./timeline.js')
|
|
1490
|
-
batch = detectTickBurst(parseTickLog(readFileSync(join(changeDir, 'task-ticks.jsonl'), 'utf8')))
|
|
1491
|
-
} catch { /* 侧车缺失/不可读 → 无侧车证据(fail-open) */ }
|
|
1492
|
-
}
|
|
1534
|
+
const { resolveBatchTickAction } = await import('./sentinel-assertions.js')
|
|
1535
|
+
// 观测消费共享采集(2026-10-17-tick-timing-source):事件流/侧车/突发判定已在前文哨兵段
|
|
1536
|
+
// 前置采集为 _tickObs(时点判定与节奏门同源一份数据;读取仅前移,检测逻辑与优先序不变
|
|
1537
|
+
// ——事件流先判、空结果回退侧车突发,侧车缺失按空 fail-open)。
|
|
1538
|
+
const stream = _tickObs.stream
|
|
1539
|
+
const batch = _tickObs.batch
|
|
1493
1540
|
if (batch) {
|
|
1494
1541
|
// 代勾计数取「本拍实时代勾 ∨ 持久化代勾」较大者(二轮 P2-B:重入漂移防护);决策纯函数
|
|
1495
1542
|
// 单源(resolveBatchTickAction)四态与豁免优先序见其 JSDoc——代勾须解释整跳才降级
|
|
@@ -2449,7 +2496,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2449
2496
|
// ⑥ events:事件收口(watcher 观测旁路 best-effort 读回,非真相源)
|
|
2450
2497
|
if (st.substeps?.events === 'done') { skip('events') } else {
|
|
2451
2498
|
try {
|
|
2452
|
-
const eventsPath =
|
|
2499
|
+
const eventsPath = runtimeStatePath(runtimeRoot, `watcher-events-${change}.jsonl`)
|
|
2453
2500
|
if (existsSync(eventsPath)) {
|
|
2454
2501
|
const n = readFileSync(eventsPath, 'utf8').split('\n').filter(Boolean).length
|
|
2455
2502
|
console.log(`📊 watcher 事件收口:${n} 条 provisional 事件在案(.runtime 留档,平台展示面)`)
|
|
@@ -2483,8 +2530,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2483
2530
|
console.log(` 注:knowledge「最近确认」hash 为溯源快照,压扁后成孤儿引用属预期(非活引用)。`)
|
|
2484
2531
|
}
|
|
2485
2532
|
} catch { /* 指引 best-effort */ }
|
|
2486
|
-
// 平台同步(同 flow start 尾部接线——归档后的 docs/knowledge/FR
|
|
2487
|
-
|
|
2533
|
+
// 平台同步(同 flow start 尾部接线——归档后的 docs/knowledge/FR 面随本轮回推平台)。
|
|
2534
|
+
// 终态强校验(2026-10-17-archive-half-state-location-stuck-active):flow done 到此 =
|
|
2535
|
+
// 变更已归档,文件树推送失败不再静默半态(🚨 告警 + 自救命令 + exit 1)
|
|
2536
|
+
try { await triggerSyncTerminalStrict(cwd, change) } catch { /* 同步绝不阻断协议面 */ }
|
|
2488
2537
|
return { change, substeps: doneList }
|
|
2489
2538
|
}
|
|
2490
2539
|
|
|
@@ -2663,7 +2712,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2663
2712
|
console.error(`❌ ${e.message}`)
|
|
2664
2713
|
// 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
|
|
2665
2714
|
console.error(' 重试(--input 引号内换行合法,可照抄):')
|
|
2666
|
-
console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "
|
|
2715
|
+
console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景')
|
|
2667
2716
|
console.error('')
|
|
2668
2717
|
console.error(' 成功标准:')
|
|
2669
2718
|
console.error(' - <可验证标准>"')
|
|
@@ -2892,9 +2941,9 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2892
2941
|
console.log(`✅ flow amend-draft 留痕重锚:${r.reanchored.join('、')}——ledger amendment 审计在案;editRatio=${maxRatio}(段级最大;基准=首版原文,AGENT 槽不计入)`)
|
|
2893
2942
|
return r
|
|
2894
2943
|
}
|
|
2895
|
-
console.error('用法: sillyspec flow start --change <名> --input "
|
|
2944
|
+
console.error('用法: sillyspec flow start --change <名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] [--no-prestage(hindsight 标记在场时声明需求已含决策,留痕放行)] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json] | sillyspec flow diff --change <名> [--json](只读文件统计)')
|
|
2896
2945
|
console.error(' --input 可照抄形态(引号内换行合法):')
|
|
2897
|
-
console.error(' sillyspec flow start --change <名> --input "
|
|
2946
|
+
console.error(' sillyspec flow start --change <名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景')
|
|
2898
2947
|
console.error('')
|
|
2899
2948
|
console.error(' 成功标准:')
|
|
2900
2949
|
console.error(' - <可验证标准>"')
|