@thincoder/core 0.10.13 → 0.11.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/CHANGELOG.md +29 -0
- package/advisor/messages.mjs +4 -2
- package/advisor/notice.mjs +1 -1
- package/advisor/project-context.mjs +121 -11
- package/agent/completion.mjs +13 -1
- package/agent/digest-account.mjs +73 -46
- package/agent/dispatch-run.mjs +1 -1
- package/agent/dispatch.mjs +29 -21
- package/agent/family-tools.mjs +9 -5
- package/agent/helpers.mjs +1 -94
- package/agent/offload.mjs +88 -0
- package/agent/record-results.mjs +1 -6
- package/agent/run-stages.mjs +69 -48
- package/agent/run-start.mjs +8 -8
- package/agent/setup.mjs +3 -11
- package/agent/suspension.mjs +1 -1
- package/agent/turn-loop.mjs +32 -3
- package/agent/write-face.mjs +99 -0
- package/agent/write-gate.mjs +354 -5
- package/agent-tools/advisor.mjs +127 -11
- package/agent-tools/async-settle.mjs +2 -2
- package/agent-tools/bash-async.mjs +8 -2
- package/agent-tools/batch-closeout.mjs +206 -0
- package/agent-tools/batch-gate.mjs +287 -73
- package/agent-tools/batch-ledger-bind.mjs +183 -0
- package/agent-tools/batch-ledger-link.mjs +73 -0
- package/agent-tools/batch-lifecycle.mjs +113 -367
- package/agent-tools/batch-paths.mjs +45 -40
- package/agent-tools/batch-read.mjs +1 -1
- package/agent-tools/batch-reopen.mjs +142 -0
- package/agent-tools/batch-skeleton.mjs +275 -80
- package/agent-tools/batch.mjs +243 -50
- package/agent-tools/context.mjs +6 -3
- package/agent-tools/design-slots.mjs +24 -24
- package/agent-tools/design-token.mjs +96 -15
- package/agent-tools/doc-check.mjs +18 -2
- package/agent-tools/eng.mjs +1 -1
- package/agent-tools/escalate-async.mjs +3 -3
- package/agent-tools/manifest.mjs +8 -5
- package/agent-tools/read-history.mjs +5 -5
- package/agent-tools/review-facts.mjs +31 -13
- package/agent-tools/spawn-gates.mjs +10 -14
- package/agent-tools/ssh-session.mjs +120 -111
- package/agent-tools/ssh-transfer.mjs +210 -0
- package/agent-tools/ssh-transport.mjs +277 -46
- package/agent-tools/subagent-actions-query.mjs +1 -1
- package/agent-tools/subagent-actions.mjs +1 -1
- package/agent-tools/subagent-async.mjs +3 -2
- package/agent-tools/subagent-scheduler.mjs +43 -9
- package/agent-tools/subagent-spawn.mjs +68 -9
- package/agent-tools/subagent.mjs +16 -14
- package/client-identity.mjs +22 -6
- package/config-presets.mjs +5 -3
- package/context.mjs +34 -3
- package/declaration.mjs +2 -2
- package/doc-check/anchors.mjs +128 -28
- package/doc-check/index.mjs +68 -15
- package/doc-check/line-refs.mjs +112 -0
- package/doc-check/paragraph.mjs +18 -5
- package/explore-distill.mjs +1 -1
- package/i18n.mjs +1 -1
- package/index-discover.mjs +4 -1
- package/ledger-close-proof.mjs +19 -37
- package/ledger-cmd.mjs +71 -195
- package/ledger-db.mjs +29 -28
- package/ledger-migrate.mjs +4 -4
- package/ledger-read.mjs +10 -8
- package/ledger-surface.mjs +1 -1
- package/ledger-tools.mjs +6 -6
- package/ledger-variant-notice.mjs +1 -1
- package/ledger-write-ops.mjs +373 -0
- package/ledger.mjs +2 -2
- package/manifest-discovery.mjs +2 -2
- package/manifest-schema.mjs +4 -0
- package/memory/memory-tool.mjs +5 -2
- package/memory/schema.mjs +5 -26
- package/package.json +1 -1
- package/path-resolve.mjs +217 -0
- package/permission.mjs +4 -0
- package/prompt-overlays.mjs +5 -0
- package/prompts/advisor-design.md +9 -6
- package/prompts/advisor-round1.md +4 -3
- package/prompts/advisor-round2.md +6 -5
- package/prompts/advisor-round3.md +6 -5
- package/prompts/common.md +23 -10
- package/prompts/consult-base.md +2 -1
- package/prompts/discipline-engineering.md +29 -48
- package/prompts/discipline-normal.md +10 -5
- package/prompts/persona-eng-coder.md +8 -0
- package/prompts/persona-eng-designer.md +22 -12
- package/prompts/persona-engineering.md +81 -68
- package/prompts/persona-plan.md +2 -1
- package/prompts/persona-tester.md +55 -0
- package/provider/core.mjs +2 -2
- package/provider/google.mjs +15 -5
- package/provider/rate.mjs +11 -3
- package/provider/sse.mjs +15 -4
- package/session-identity.mjs +21 -0
- package/session-lifecycle.mjs +9 -4
- package/session-slots.mjs +9 -14
- package/session.mjs +5 -0
- package/skip-dirs.mjs +48 -0
- package/team-ledger.mjs +4 -4
- package/text-budget.mjs +4 -0
- package/token-window.mjs +90 -14
- package/tool-docs/advisor.md +2 -2
- package/tool-docs/apply_patch.md +2 -0
- package/tool-docs/bash.md +4 -0
- package/tool-docs/batch.md +85 -1
- package/tool-docs/browser.md +3 -1
- package/tool-docs/code_search.md +3 -1
- package/tool-docs/consult_start.md +6 -2
- package/tool-docs/consult_stop.md +3 -1
- package/tool-docs/delete.md +3 -0
- package/tool-docs/doc_check.md +43 -8
- package/tool-docs/doc_search.md +5 -1
- package/tool-docs/edit.md +22 -1
- package/tool-docs/execute.md +7 -1
- package/tool-docs/fetch.md +3 -1
- package/tool-docs/file_ops.md +3 -1
- package/tool-docs/git.md +9 -1
- package/tool-docs/goal.md +2 -0
- package/tool-docs/grep.md +1 -1
- package/tool-docs/hashline_edit.md +2 -0
- package/tool-docs/insert_after.md +3 -1
- package/tool-docs/ledger.md +5 -5
- package/tool-docs/lint.md +1 -0
- package/tool-docs/manifest-read.md +2 -1
- package/tool-docs/manifest.md +9 -2
- package/tool-docs/memory.md +11 -1
- package/tool-docs/notify_parent.md +1 -1
- package/tool-docs/plan.md +2 -0
- package/tool-docs/question.md +1 -1
- package/tool-docs/read_history.md +15 -3
- package/tool-docs/read_image.md +3 -1
- package/tool-docs/recent_changes.md +3 -1
- package/tool-docs/repo_outline.md +3 -1
- package/tool-docs/settings.md +10 -2
- package/tool-docs/skill.md +2 -0
- package/tool-docs/ssh.md +30 -17
- package/tool-docs/subagent.md +50 -13
- package/tool-docs/task.md +2 -0
- package/tool-docs/team_ledger.md +1 -1
- package/tool-docs/timer.md +7 -0
- package/tool-docs/verify.md +13 -1
- package/tool-docs/websearch.md +3 -1
- package/tool-docs/write.md +2 -0
- package/tools/bash.mjs +12 -2
- package/tools/edit-batch.mjs +40 -33
- package/tools/edit-diff.mjs +69 -1
- package/tools/execute.mjs +10 -4
- package/tools/file.mjs +17 -27
- package/tools/git.mjs +27 -1
- package/tools/ops.mjs +2 -1
- package/tools/patch.mjs +12 -3
- package/tools/process-doc-mark.mjs +139 -0
- package/tools/search.mjs +15 -6
- package/tools/shared.mjs +24 -1
- package/tools/ssh.mjs +48 -7
- package/tools/tree.mjs +4 -1
- package/tools/write-path.mjs +64 -3
- package/traces/trace-store.mjs +4 -3
- package/agent/light-round.mjs +0 -63
- package/agent/unbooked-pen.mjs +0 -62
package/ledger-close-proof.mjs
CHANGED
|
@@ -2,24 +2,27 @@
|
|
|
2
2
|
* ledger-close-proof.mjs — 台账核销机检单源(F-LX4 · 2026-10-26 批 §2;台账 #1807)。
|
|
3
3
|
*
|
|
4
4
|
* 判据(需求档 §② 功能点 11 / AC-M2-34):凡写 `已核销` 的语句,写库前判三件——① 行的 `task_book`
|
|
5
|
-
* 所指批档在盘 ② 该档 §1 状态行 = `已收口` ③
|
|
5
|
+
* 所指批档在盘 ② 该档 §1 状态行 = `已收口` ③ 该批档的 `§3` 状态行 = `评审通过`;缺一即拒、
|
|
6
6
|
* **一次报全缺项**、行零改。**判据只有目标值一条**——闸挂在写这个值的两处逐行共函数
|
|
7
7
|
* (`ledger-cmd.mjs` 的 `applyRowUpdate` / `applyRowClose`)之前,不判来源态。
|
|
8
8
|
*
|
|
9
|
+
* ③ 为什么能去运行态(2026-10-27 三链统一批 · R6):`§3` 只能由**绑在该档上的评审实例**写
|
|
10
|
+
* (`batch` 工具的 review 绑定),所以「§3 有 pass」本身就证明了「有一次绑本档的评审跑过并给了 pass」,
|
|
11
|
+
* 与 token 槽归属同义,而且换会话、重启进程、隔天补账都成立。旧判据(槽内有归属)在需要「换会话核销」
|
|
12
|
+
* 时给出假拒——它判的是**谁在跑**,不是**评审过没过**。
|
|
13
|
+
* 代价:评审自报 pass 的信任面不变(今天也是评审自己写 §3)。
|
|
14
|
+
*
|
|
9
15
|
* 住本档的理由(拆分件——先例 = `ledger-tools.mjs` 自 `ledger-cmd.mjs` 拆出):`ledger-cmd.mjs` 收尾
|
|
10
16
|
* 446 行,机检本体 + 三形拒因入册即越单档 500 顾问线(设计 §五「实落超 500 ⇒ 主动拆分」)。两枚指针
|
|
11
17
|
* 值谓词(`filePartOf`——设计档 §6.1 口径 1 单源 ∥ `namesPointer`)随迁本档,`ledger-cmd` 反向 import
|
|
12
18
|
* 取用——方向单向、无静态环。
|
|
13
19
|
*
|
|
14
|
-
*
|
|
15
|
-
* `readEngTokensFromSlot` 不入本档)——拒因文案里只有行 id ∥ `task_book` 值 ∥ 批档路径。
|
|
20
|
+
* 只读:三件都只读档面与行值,不读任何 token 槽状态(`_engDesignTokens` / `unionBatches` 不入本档)。
|
|
16
21
|
*/
|
|
17
22
|
import { readFileSync } from "node:fs"
|
|
18
23
|
|
|
19
24
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
20
|
-
import {
|
|
21
|
-
import { isBatchRecord, readBatchStatusLine } from "./agent-tools/batch-skeleton.mjs"
|
|
22
|
-
import { loadManifest } from "./session-slots.mjs"
|
|
25
|
+
import { isBatchRecord, readBatchStatusLine, readSectionStatusWord, STATUS_WORDS } from "./agent-tools/batch-skeleton.mjs"
|
|
23
26
|
|
|
24
27
|
/** 指针 ∥ 批档值 → 文件部分(首个 `§` 前子串,trim——设计档 §6.1 口径 1 单源;`task_book` 与
|
|
25
28
|
* `batch` 同形同口径)。 */
|
|
@@ -28,32 +31,14 @@ export const filePartOf = (v) => (v == null ? "" : String(v).split("§")[0].trim
|
|
|
28
31
|
/** 本笔指名谓词(设计档 §6.1 口径 3):显式空值(`null` / 空串 / 全空白) ≡ **未指名**——臂② 不触。 */
|
|
29
32
|
export const namesPointer = (v) => v != null && String(v).trim() !== ""
|
|
30
33
|
|
|
31
|
-
/** 会话槽号(与 `token-ttl.mjs` `currentSlotNoReadOnly` 同一取值式:`_slot` 优先、未钉槽读 manifest
|
|
32
|
-
* `active`)——只用于 ③ 拒因分辨两种成因,不读任何 token 值面。 */
|
|
33
|
-
function slotNoOf(agent) {
|
|
34
|
-
if (Number.isInteger(agent?._slot)) return agent._slot
|
|
35
|
-
const active = loadManifest(agent?.cwd)?.active
|
|
36
|
-
return Number.isInteger(active) ? active : null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** ③ 归属并集一次读入(严格形):`fnName` 在场 ⇒ 读失败即抛(必写 `已核销` 的批量径 ⇒ BEGIN 之前整批拒、
|
|
40
|
-
* 不进 skipped);缺省 ⇒ 返回错误对象(单行径 ∥ 纯字段补丁逐行报 ③)。 */
|
|
41
|
-
export function readCloseOutBatches(agent, fnName) {
|
|
42
|
-
try { return unionBatches(agent, { strict: true }) } catch (e) {
|
|
43
|
-
if (!fnName) return e
|
|
44
|
-
throw new Error(`${fnName}:核销机检未过(整批零行改——会话级读失败,与行无关)\n ③ 读不到 token 槽归属(${e?.message ?? e}):核销被拒、行零改;修好槽文件或权限后重试`)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
34
|
/** 核销机检(F-LX4 · 需求档 §② 功能点 11 / AC-M2-34):写 `已核销` 前判三件——① `task_book` 所指档在盘
|
|
49
|
-
* ② 该档 §1 = `已收口` ③
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
export function assertCloseOutProof(fnName, { agent, base, row, batches }) {
|
|
35
|
+
* ② 该档 §1 = `已收口` ③ 该档 §3 = `评审通过`;**判据只有目标值一条**(两处写这个值的 SQL 之前同挂),
|
|
36
|
+
* 三件各判、**一次报全缺项**、行零改(只判不写;2026-10-27 起 ③ = 档面事实,零运行态)。 */
|
|
37
|
+
export function assertCloseOutProof(fnName, { base, row }) {
|
|
53
38
|
const missing = [] // 每项 = 一条拒因行(首字 = 缺项号)
|
|
54
39
|
let abs = null
|
|
55
40
|
if (!namesPointer(row?.task_book)) {
|
|
56
|
-
missing.push(" ① 行无 task_book
|
|
41
|
+
missing.push(" ① 行无 task_book 指针:核销须先挂批档(update 写 task_book),再跑全链")
|
|
57
42
|
} else {
|
|
58
43
|
const hit = resolveDeclaredRef(base, filePartOf(row.task_book))
|
|
59
44
|
if (!hit.ok) missing.push(` ① 批档不在盘(task_book = ${row.task_book}):先补指针,或恢复该档`)
|
|
@@ -64,17 +49,14 @@ export function assertCloseOutProof(fnName, { agent, base, row, batches }) {
|
|
|
64
49
|
else {
|
|
65
50
|
const state = readBatchStatusLine(src)
|
|
66
51
|
if (state !== "closed") missing.push(` ② 批档未收口(${abs} §1 = ${state}):该批须先跑全链收口`)
|
|
52
|
+
else {
|
|
53
|
+
const s3 = readSectionStatusWord(src, 3)
|
|
54
|
+
if (s3 !== STATUS_WORDS[3].done) {
|
|
55
|
+
missing.push(` ③ 批档 §3 未通过评审(§3 状态行 = ${s3 ?? "(无状态行)"},须 = 评审通过):重开该档补评审结论,或另起批次`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
60
|
}
|
|
70
|
-
if (abs !== null) {
|
|
71
|
-
let map = batches instanceof Map || batches instanceof Error ? batches : readCloseOutBatches(agent)
|
|
72
|
-
if (map instanceof Error) missing.push(` ③ 读不到 token 槽归属(${map?.message ?? map}):核销被拒、行零改;修好槽文件或权限后重试`)
|
|
73
|
-
else if (![...map].some(([, b]) => typeof b === "string" && batchPathKey(b) === batchPathKey(abs))) {
|
|
74
|
-
missing.push(slotNoOf(agent) == null
|
|
75
|
-
? " ③ 本会话无槽号(manifest 无 active):核销须在持有该批归属的会话里跑"
|
|
76
|
-
: " ③ 本会话槽内无该批档的归属:先确认是否换了会话——在跑该批设计评审的会话里核销;\n 确无归属 ⇒ 那次评审的 documents 未带该批档,须重跑")
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
61
|
if (missing.length > 0) throw new Error(`${fnName}:核销机检未过(行 ${row?.id} 零改)——缺:${missing.map((t) => t.trim()[0]).join(" ")}\n${missing.join("\n")}`)
|
|
80
62
|
}
|
package/ledger-cmd.mjs
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ledger-cmd.mjs — 台账核心函数档(查询 = 只读全角色 / 写 = 仅主 agent 装配)。
|
|
3
3
|
* 工具定义 + 工具层参数守卫 = 拆分件 `thincoder-core/ledger-tools.mjs`(2026-09-28 守卫族微修批 ·
|
|
4
|
-
* 台账 #473
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 台账 #473——纯搬移、语义零变);逐行写核 + 两个批级写点 = 拆分件 `thincoder-core/ledger-write-ops.mjs`
|
|
5
|
+
* (2026-10-27 三链统一批——本档收尾 455 行,新增两枚批级写点会越 500 顾问线,故外提;本档反向 import
|
|
6
|
+
* 取用并 re-export,公共面逐字不变)。核函数(`ledgerQuery` / `ledgerCount` / `ledgerAdd` /
|
|
7
|
+
* `ledgerUpdate` / `ledgerClose` + 批级批量 `ledgerUpdateBatch` / `ledgerCloseBatch` + 重锚单源
|
|
8
|
+
* `ledgerReanchorBatch`)留本档,工具档消费之;`ledgerBindBatch` / `ledgerReclaimBatch` 由本档
|
|
9
|
+
* re-export(设计档 §2.4 / §2.13 的公共面落点不变)。
|
|
7
10
|
*
|
|
8
|
-
* 读数面(逐行携、不入库、不入 `DATA_COLUMNS`——先例 = `aged`):`
|
|
11
|
+
* 读数面(逐行携、不入库、不入 `DATA_COLUMNS`——先例 = `aged`):`evidenceTooLong`(§5 P4 文字太长)∥
|
|
9
12
|
* `overdue`(§3.7 出口态——机判单源 `overdueOf`)∥ `taskBookOnDisk`(§7.1 B——指针在盘读数)。
|
|
10
13
|
* 三枚皆**读数非闸门**(不拒任何写入)。
|
|
11
14
|
* 指针判据重锚面(§6.1 两臂写门 ∥ §6.2 收口重锚 · 台账 #1550):写门判**本笔写入面**、只判本笔新指名;
|
|
12
15
|
* 「档此刻在不在盘」降为读数——读面导出 `ledgerBatchRows`、收口重锚单源 `ledgerReanchorBatch`。
|
|
13
16
|
* 批量面(§3.3 P1):选行单源 `resolveBatchRows` + 逐行落笔 `applyRowUpdate` / `applyRowClose`
|
|
14
17
|
* (与单行形共函数——迁移表判 / 写门 / evidence 门逐行照判)。
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 核销机检(F-LX4 · 2026-10-26 批 §2):同一两逐行共函数在写 `已核销` 前判三件(批档在盘 ∥ §1 =
|
|
19
|
-
* 已收口 ∥ 该批档在 design token 槽的批次归属有记录),缺一即拒、一次报全缺项、行零改;判据只有
|
|
20
|
-
* 目标值一条。本体住拆分件 `ledger-close-proof.mjs`(同 `ledger-tools.mjs` 先例);标记门在前
|
|
21
|
-
* (零成本字符串判先报),两门互不替代。
|
|
18
|
+
* 核销机检(F-LX4 · 2026-10-26 批 §2;2026-10-27 改判 ③ = 档面):同一两逐行共函数在写 `已核销` 前判
|
|
19
|
+
* 三件(批档在盘 ∥ §1 = 已收口 ∥ §3 = 评审通过),缺一即拒、一次报全缺项、行零改;判据只有目标值一条。
|
|
20
|
+
* 本体住拆分件 `ledger-close-proof.mjs`(同 `ledger-tools.mjs` 先例)。
|
|
22
21
|
*
|
|
23
22
|
* 接线(设计档 §2.2 命令面接线):族出口 = `ledger.mjs`(re-export 两源——KD-M2-3);查询命令 ←
|
|
24
23
|
* tools/index.mjs(assembleBuiltinTools 全角色面);写命令 ← agent/family-tools.mjs(assembleFamilyTools
|
|
25
24
|
* 的 depthOnly 分支)——一律**动态 import** 本模块链(ledger-db.mjs 静态 import node:sqlite ⇒
|
|
26
25
|
* 消费侧静态 import 会破 W8 契约②)。
|
|
27
26
|
*/
|
|
28
|
-
import {
|
|
29
|
-
import { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, nowIso, openLedger, PENDING_STATUSES, resolveLedgerRoot, ROUND_MARKER } from "./ledger-db.mjs"
|
|
27
|
+
import { AGING_DAYS, EVIDENCE_TOO_LONG_MAX, PENDING_STATUSES, nowIso, openLedger, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
30
28
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
31
|
-
import {
|
|
29
|
+
import { filePartOf, namesPointer } from "./ledger-close-proof.mjs"
|
|
30
|
+
import {
|
|
31
|
+
applyRowClose, applyRowUpdate, assertBatchArg, assertTaskBookGate, baseOf, evidenceValue, resolveBatchRows,
|
|
32
|
+
} from "./ledger-write-ops.mjs"
|
|
33
|
+
|
|
34
|
+
export { ledgerBindBatch, ledgerReclaimBatch } from "./ledger-write-ops.mjs"
|
|
32
35
|
|
|
33
36
|
/** 逐行老化判真(LEDGER.md §13.4 · 2026-10-07 批 ledger-tool):未决四态(`PENDING_STATUSES`)∧
|
|
34
37
|
* 行龄 > `AGING_DAYS` 天;行龄源 = `updated_at ?? created_at`(§5 同源);时间戳缺 / 不可解析
|
|
@@ -41,13 +44,9 @@ function isRowAged(row, now) {
|
|
|
41
44
|
return Number.isFinite(ms) && (now - ms) / 86400000 > AGING_DAYS
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
/** `evidence` 超限读数(设计档 §5 P4——**读数非闸门**):缺 / 空 ⇒ false;`query` 逐行携(含归档行——
|
|
49
|
-
* 行值事实);`count` 的统计集 = 未决四态(§7.1)。 */
|
|
50
|
-
const isEvidenceOversize = (row) => row?.evidence != null && String(row.evidence).length > EVIDENCE_SOFT_LIMIT
|
|
47
|
+
/** `evidence` 文字太长读数(设计档 §5 P4——**读数非闸门**):缺 / 空 ⇒ false;`query` 逐行携(含归档
|
|
48
|
+
* 行——行值事实);`count` 的统计集 = 未决四态(§7.1)。 */
|
|
49
|
+
const isEvidenceTooLong = (row) => row?.evidence != null && String(row.evidence).length > EVIDENCE_TOO_LONG_MAX
|
|
51
50
|
|
|
52
51
|
/** 本地日界(`YYYY-MM-DD`)——`resume_due` 比较粒度(设计档 §3.7:本地日界,当日锚当日即到期)。 */
|
|
53
52
|
const localDay = (ms) => { const d = new Date(ms); const p = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` }
|
|
@@ -74,7 +73,7 @@ const pointerOnDisk = (row, base) => { const part = filePartOf(row?.task_book);
|
|
|
74
73
|
* 四类出口态**首中优先**(序 = `resume_due` → `dangling` → `unlabeled` → `aged`);返回出口态字串
|
|
75
74
|
* ∥ `null`。`unlabeled`(`trigger IS NULL` ∧ 超龄)⊆ `aged` ⇒ 必先于 `aged` 判(否则永不返回)。
|
|
76
75
|
* `resume_due` 比较粒度 = 本地日界(锚日 ≤ 当地日 ⇒ 到期;不按时刻比)。 */
|
|
77
|
-
export function overdueOf(row, { now = Date.now(), base
|
|
76
|
+
export function overdueOf(row, { now = Date.now(), base } = {}) {
|
|
78
77
|
if (!PENDING_STATUSES.includes(row?.status)) return null
|
|
79
78
|
if (row.trigger === "条件") {
|
|
80
79
|
const anchor = resumeAnchor(row.resume_when)
|
|
@@ -89,7 +88,7 @@ export function overdueOf(row, { now = Date.now(), base = process.cwd() } = {})
|
|
|
89
88
|
/** 查询(只读,全角色):SELECT 行集(status / kind / board / trigger / task_book 等值过滤并取,缺省 =
|
|
90
89
|
* 全部行;id 升序)。库不在 = 空账。只读开库(readOnly 句柄 + 零 DDL/ALTER——read-data-interface 批
|
|
91
90
|
* FR2③);库档非台账库 ⇒ 空账。逐行携计算字段(均不入库、不入导出白名单 `DATA_COLUMNS`):
|
|
92
|
-
* `aged`(§13.4)∥ `
|
|
91
|
+
* `aged`(§13.4)∥ `evidenceTooLong`(§5 P4 文字太长读数)∥ `taskBookOnDisk`(§7.1 B 在盘读数)∥ `overdue`
|
|
93
92
|
* (§3.7 出口态字串 ∥ null——机判单源 `overdueOf`);`task_book` = 第 5 枚等值过滤(**原值等值**——列串直比,与 §3.3 选行的
|
|
94
93
|
* 「文件部分解析同址」两径口径不同、各自单源);`trigger` 过滤仅三枚举等值(`NULL` 不过滤);
|
|
95
94
|
* `now` = 注入缝(确定性用例)。 */
|
|
@@ -105,59 +104,36 @@ export function ledgerQuery({ cwd, boundary, status = null, kind = null, board =
|
|
|
105
104
|
if (task_book) { where.push("task_book = ?"); args.push(task_book) }
|
|
106
105
|
const rows = db.prepare(`SELECT * FROM items${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY id`).all(...args)
|
|
107
106
|
const base = baseOf(cwd, boundary)
|
|
108
|
-
return rows.map((r) => ({ ...r, aged: isRowAged(r, now),
|
|
107
|
+
return rows.map((r) => ({ ...r, aged: isRowAged(r, now), evidenceTooLong: isEvidenceTooLong(r), taskBookOnDisk: pointerOnDisk(r, base), overdue: overdueOf(r, { now, base }) }))
|
|
109
108
|
} finally { db.close() }
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
/** 计数单源(只读,全角色):回报 `{ count, overdue,
|
|
111
|
+
/** 计数单源(只读,全角色):回报 `{ count, overdue, evidenceTooLong }`(设计档 §7.1——原裸数出参
|
|
113
112
|
* 改对象形,消费面同批改)。`count` = 未决四态行数;`overdue` = 其中命中出口态的行数(`overdueOf`
|
|
114
|
-
* 机判单源);`
|
|
115
|
-
* `
|
|
113
|
+
* 机判单源);`evidenceTooLong` = 其中 `evidence` 文字太长的行数(§5)。**一次 SELECT + 逐行判**——
|
|
114
|
+
* `evidenceTooLong` 与 `overdue` 同一次遍历(零额外 IO;成本模型 = §7.2);不设缓存(读数须反映
|
|
116
115
|
* 即时盘面)。库不在 = 三零。 */
|
|
117
116
|
export function ledgerCount({ cwd, boundary, task_book = null } = {}) {
|
|
118
117
|
const db = openLedger(cwd, { readOnly: true, boundary })
|
|
119
|
-
if (!db) return { count: 0, overdue: 0,
|
|
118
|
+
if (!db) return { count: 0, overdue: 0, evidenceTooLong: 0 }
|
|
120
119
|
try {
|
|
121
120
|
const where = [`status IN (${PENDING_STATUSES.map(() => "?").join(",")})`], args = [...PENDING_STATUSES]
|
|
122
121
|
if (task_book) { where.push("task_book = ?"); args.push(task_book) }
|
|
123
122
|
const rows = db.prepare(`SELECT * FROM items WHERE ${where.join(" AND ")}`).all(...args)
|
|
124
123
|
const now = Date.now(), base = baseOf(cwd, boundary)
|
|
125
|
-
let overdue = 0,
|
|
124
|
+
let overdue = 0, tooLong = 0
|
|
126
125
|
for (const r of rows) {
|
|
127
126
|
if (overdueOf(r, { now, base }) !== null) overdue++
|
|
128
|
-
if (
|
|
127
|
+
if (isEvidenceTooLong(r)) tooLong++
|
|
129
128
|
}
|
|
130
|
-
return { count: rows.length, overdue,
|
|
129
|
+
return { count: rows.length, overdue, evidenceTooLong: tooLong }
|
|
131
130
|
} finally { db.close() }
|
|
132
131
|
}
|
|
133
132
|
|
|
134
|
-
/** `evidence`
|
|
133
|
+
/** `evidence` 文字太长的行数(设计档 §7.1 导出面——`count` 回报读数**同一单源**:本函数为其薄委托,
|
|
135
134
|
* 不另立第二判定面)。 */
|
|
136
|
-
export function
|
|
137
|
-
return ledgerCount({ cwd, boundary, task_book }).
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** 写门·指针点名(设计档 §6.1 两臂 · AC-M2-10 ∥ AC-M2-29):判**本笔写入面**——两臂之外零判。
|
|
141
|
-
* 臂①(点名非空):结果行 `status ∈ {在途, 待核销}` ∨ 结果行 `trigger = 归批` ⇒ `task_book` 非空
|
|
142
|
-
* (缺 / 空串 / 全空白 ⇒ 拒「必填」);**不判在盘**(列值事实,不随盘面翻转——DDL 咬合 CHECK 为该半幅防线)。
|
|
143
|
-
* 臂②(本笔新指名):本笔新写 `task_book`(非空值)∥ 本笔新写 `trigger = 归批` ⇒ 绑定指针非空 ∧ 文件部分
|
|
144
|
-
* 可解析 ∧ 解析出的档**在盘**(判定单源 = `resolveDeclaredRef`,零二写)。
|
|
145
|
-
* **「档此刻在不在盘」不入臂①**:批档可随时整体清理 ⇒ 当结果行判据则合法账务被拒、而出路正是被拒字段
|
|
146
|
-
* (死锁);存量指针失效只体现为读数 `taskBookOnDisk = false`(§7.1)。
|
|
147
|
-
* 判位 = 与迁移表判同层(落盘前);文案前缀 = 调用函数名;基准 = §6.1 口径 2(根推导单源同子表达式)。
|
|
148
|
-
* @param {{status:string, trigger:string|null, taskBook:string|null, wroteTaskBook:boolean, wroteBatchTrigger:boolean}} face 本笔写入面
|
|
149
|
-
*/
|
|
150
|
-
function assertTaskBookGate(cwd, fnName, face, boundary) {
|
|
151
|
-
const { status, trigger, taskBook, wroteTaskBook, wroteBatchTrigger } = face
|
|
152
|
-
if ((status === "在途" || status === "待核销" || trigger === "归批") && !namesPointer(taskBook)) {
|
|
153
|
-
throw new Error(`${fnName}:task_book 必填(在途 / 待核销 / 归批 须携任务书指针)`)
|
|
154
|
-
}
|
|
155
|
-
if (!wroteTaskBook && !wroteBatchTrigger) return // 臂② 之外零判:旧指针所指档已清不阻任何此类写入
|
|
156
|
-
const part = filePartOf(taskBook)
|
|
157
|
-
if (!part) throw new Error(`${fnName}:task_book 不可解析(缺文件部分):${taskBook}`)
|
|
158
|
-
const base = baseOf(cwd, boundary) // §6.1 口径 2 同一子表达式(单源 = 本档 `baseOf`)
|
|
159
|
-
const { ok, abs } = resolveDeclaredRef(base, part)
|
|
160
|
-
if (!ok) throw new Error(`${fnName}:task_book 指向的档不存在:${taskBook}(解析 = ${abs})`)
|
|
135
|
+
export function ledgerEvidenceTooLong({ cwd, boundary, task_book = null } = {}) {
|
|
136
|
+
return ledgerCount({ cwd, boundary, task_book }).evidenceTooLong
|
|
161
137
|
}
|
|
162
138
|
|
|
163
139
|
/** 新增(写命令,仅主 agent):INSERT——入待讨论(六态状态机入口)。`row.resume_when` 可空(§3.4)。 */
|
|
@@ -179,76 +155,12 @@ export function ledgerAdd({ cwd, row, boundary }) {
|
|
|
179
155
|
} finally { db.close() }
|
|
180
156
|
}
|
|
181
157
|
|
|
182
|
-
/** executor 目标值计算(设计档 docs/core/design/LEDGER.md §3.1——判位在 ledgerUpdate 体内:迁移表判 → 本语义 → 写门 → UPDATE)。
|
|
183
|
-
* 优先级 = patch 显式 > 自动语义(进边 = executorSessionId / 出边 = NULL)> 行现值兜底 > NULL。
|
|
184
|
-
* 进边集(2026-10-07 批 ledger-tool 扩面——§13.3 / KD-LT1):`待讨论 → 待设计`(批点火边)∥
|
|
185
|
-
* `待设计 → 在途`(实施入边)——两枚同式。 */
|
|
186
|
-
function resolveExecutorTarget(row, to, patch, executorSessionId) {
|
|
187
|
-
if ((row.status === "待讨论" && to === "待设计") || (row.status === "待设计" && to === "在途")) {
|
|
188
|
-
return patch.executor ?? executorSessionId ?? row.executor ?? null
|
|
189
|
-
}
|
|
190
|
-
if (row.status === "在途" && (to === "待核销" || to === "已废弃")) {
|
|
191
|
-
return null // 出边自动语义压 patch——显式传 executor 也不复活
|
|
192
|
-
}
|
|
193
|
-
return patch.executor ?? row.executor ?? null
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** `evidence` 值计算(设计档 LEDGER.md §13.9——update / close 两函数同源一处落):追加缺省(旧值非空 ⇒
|
|
197
|
-
* `旧 + "|" + 新`——「|」两侧零空格直接相接;旧空(`null` ∥ 空串 ∥ 全空白)⇒ 直写,零分隔符残留);
|
|
198
|
-
* `evidenceReplace === true` 与值同传 ⇒ 整段替换(旧文零保留);`evidence` 缺 / `null` ≡ 略去(零变)。
|
|
199
|
-
* 拒面①(旗独传——无新文可算)判于本阶段(close 侧先于追认门);核内旗判只认 `=== true`(类型严判在
|
|
200
|
-
* 工具层)。两值逐字落行(不裁 / 不判内容)。 */
|
|
201
|
-
function evidenceValue(fnName, oldValue, evidence, evidenceReplace) {
|
|
202
|
-
if (evidenceReplace === true && evidence == null) throw new Error(`${fnName}:evidenceReplace 须与 evidence 同传(缺新文)`)
|
|
203
|
-
if (evidence == null) return oldValue
|
|
204
|
-
const old = oldValue == null || String(oldValue).trim() === "" ? "" : String(oldValue)
|
|
205
|
-
return evidenceReplace === true || old === "" ? evidence : `${old}|${evidence}`
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** 拒面②(新值全空白——追加 ∥ 覆盖两径同判)判点(§13.9 判序):update 随值计算同拍 ∥ close 落
|
|
209
|
-
* 追认门之后;`evidence` 未传(`null` / 略去)⇒ 不判。 */
|
|
210
|
-
function assertEvidenceValue(fnName, evidence) {
|
|
211
|
-
if (evidence != null && String(evidence).trim() === "") throw new Error(`${fnName}:evidence 为空(非空字符串)`)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** 单行落笔·update(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):迁移表判 → 核销标记门
|
|
215
|
-
* (目标 = `已核销` ∧ 结果标题携标记 ⇒ 拒——判序 = LIGHT-CHANNEL.md §2.5)→ **核销机检**(目标 = `已核销`
|
|
216
|
-
* ⇒ `assertCloseOutProof`)→ executor / evidence 值计算(含两拒面)→ 写门 → UPDATE(判序 = §13.9 逐字未变)。`to` = `patch.status ?? 行现态`
|
|
217
|
-
* ——批量形纯字段补丁(不携 `status`)时 `to === row.status` ⇒ 迁移表不判、`status` 零写(§3.3)。
|
|
218
|
-
* `resume_when` = 普通可空字段补丁(`patch.resume_when ?? 行值`——§3.4)。 */
|
|
219
|
-
function applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, proof = {}) {
|
|
220
|
-
const to = patch.status ?? row.status
|
|
221
|
-
if (to !== row.status && !(ALLOWED_MIGRATIONS[row.status] ?? []).includes(to)) {
|
|
222
|
-
throw new Error(`ledgerUpdate:迁移 ${row.status} → ${to} 不在允许迁移表`)
|
|
223
|
-
}
|
|
224
|
-
// 核销标记门(LIGHT-CHANNEL.md §2.5):判**结果标题**——一笔同携清标记 title + status=已核销 ⇒ 放行
|
|
225
|
-
if (to === "已核销" && String(patch.title ?? row.title ?? "").includes(ROUND_MARKER)) {
|
|
226
|
-
throw new Error(`ledgerUpdate:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
227
|
-
}
|
|
228
|
-
// 核销机检(F-LX4):目标值 = `已核销` ⇒ 写库前三件校验(批档在盘 ∥ §1 已收口 ∥ 槽内有归属)
|
|
229
|
-
if (to === "已核销") assertCloseOutProof("ledgerUpdate", { agent: proof.agent, base: proof.base ?? baseOf(cwd, boundary), row, batches: proof.batches })
|
|
230
|
-
const nextExecutor = resolveExecutorTarget(row, to, patch, executorSessionId)
|
|
231
|
-
const nextEvidence = evidenceValue("ledgerUpdate", row.evidence, patch.evidence, patch.evidenceReplace)
|
|
232
|
-
assertEvidenceValue("ledgerUpdate", patch.evidence)
|
|
233
|
-
const nextTaskBook = patch.task_book ?? row.task_book
|
|
234
|
-
assertTaskBookGate(cwd, "ledgerUpdate", {
|
|
235
|
-
status: to,
|
|
236
|
-
trigger: patch.trigger ?? row.trigger,
|
|
237
|
-
taskBook: nextTaskBook,
|
|
238
|
-
wroteTaskBook: namesPointer(patch.task_book),
|
|
239
|
-
wroteBatchTrigger: patch.trigger === "归批",
|
|
240
|
-
}, boundary) // → 写门 → UPDATE
|
|
241
|
-
const now = nowIso()
|
|
242
|
-
db.prepare(`UPDATE items SET status = ?, title = ?, board = ?, req_doc = ?, task_book = ?, evidence = ?, trigger = ?, resume_when = ?, executor = ?, updated_at = ? WHERE id = ?`)
|
|
243
|
-
.run(to, patch.title ?? row.title, patch.board ?? row.board, patch.req_doc ?? row.req_doc, nextTaskBook, nextEvidence, patch.trigger ?? row.trigger, patch.resume_when ?? row.resume_when, nextExecutor, now, row.id)
|
|
244
|
-
return { id: Number(row.id), status: to }
|
|
245
|
-
}
|
|
246
|
-
|
|
247
158
|
/** 更新(写命令,仅主 agent):UPDATE——状态迁移前判允许迁移表(不在表内 → 拒,行不变)。
|
|
248
159
|
* executor 目标值随同一 UPDATE 落列(LEDGER.md §3.1);executorSessionId = 调用会话(函数参数注入——K-LX1
|
|
249
160
|
* 纯函数可测;工具层动态 import getSessionId() 供值,零新静态边)。
|
|
250
161
|
* `evidence` 传值 = 追加缺省、`patch.evidenceReplace` = 布尔覆盖旗(§13.9——值计算 / 两拒面 = `evidenceValue`
|
|
251
|
-
* / `assertEvidenceValue`;判位 = 本语义段,随 executor 目标值同拍)。**单行形**(`id`)——逐行落笔 =
|
|
162
|
+
* / `assertEvidenceValue`;判位 = 本语义段,随 executor 目标值同拍)。**单行形**(`id`)——逐行落笔 =
|
|
163
|
+
* `applyRowUpdate`(自开事务:半途抛 ⇒ 行零改)。 */
|
|
252
164
|
export function ledgerUpdate({ cwd, id, patch, executorSessionId, boundary, agent }) {
|
|
253
165
|
const db = openLedger(cwd, { create: true, boundary })
|
|
254
166
|
try {
|
|
@@ -259,36 +171,13 @@ export function ledgerUpdate({ cwd, id, patch, executorSessionId, boundary, agen
|
|
|
259
171
|
} finally { db.close() }
|
|
260
172
|
}
|
|
261
173
|
|
|
262
|
-
/**
|
|
263
|
-
*
|
|
264
|
-
* **核销机检**(`已核销` ⇒ `assertCloseOutProof`)→ 值计算(拒面① 旗独传判于此)→ 追认口 `evidence`
|
|
265
|
-
* 门 → 拒面② → UPDATE。
|
|
266
|
-
* 撤回(任意态 → 已废弃——含在途直撤)同步 `executor = NULL`(LEDGER.md §3.1 ④);核销两源(勾销 /
|
|
267
|
-
* 追认)`executor` 零触碰(非在途出边)。 */
|
|
268
|
-
function applyRowClose(db, row, status, evidence, evidenceReplace, proof = {}) {
|
|
269
|
-
if (status === "已核销" && !["待讨论", "待设计", "待核销"].includes(row.status)) throw new Error(`ledgerClose:核销仅限 待讨论 / 待设计 / 待核销(现态 ${row.status})`)
|
|
270
|
-
// 核销标记门(LIGHT-CHANNEL.md §2.5):勾销 ∥ 追认同判;撤回(→ 已废弃)不入本门
|
|
271
|
-
if (status === "已核销" && String(row.title ?? "").includes(ROUND_MARKER)) {
|
|
272
|
-
throw new Error(`ledgerClose:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
273
|
-
}
|
|
274
|
-
// 核销机检(F-LX4):判序 = 标记门之后(零成本字符串判先报)、值计算之前
|
|
275
|
-
if (status === "已核销") assertCloseOutProof("ledgerClose", { agent: proof.agent, base: proof.base, row, batches: proof.batches })
|
|
276
|
-
const nextEvidence = evidenceValue("ledgerClose", row.evidence, evidence, evidenceReplace)
|
|
277
|
-
if (status === "已核销" && ["待讨论", "待设计"].includes(row.status) && (nextEvidence == null || String(nextEvidence).trim() === "")) throw new Error(`ledgerClose:追认核销须带 evidence(现态 ${row.status})`)
|
|
278
|
-
assertEvidenceValue("ledgerClose", evidence) // 判序:拒面②(新值空白)落追认门之后 → UPDATE
|
|
279
|
-
const now = nowIso()
|
|
280
|
-
db.prepare("UPDATE items SET status = ?, closed_at = ?, updated_at = ?, executor = ?, evidence = ? WHERE id = ?").run(status, now, now, status === "已废弃" ? null : row.executor, nextEvidence, row.id)
|
|
281
|
-
return { id: Number(row.id), status }
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/** 收口(写命令,仅主 agent):核销两源(勾销:待核销 → 已核销;追认核销:待讨论 / 待设计 → 已核销,
|
|
285
|
-
* 行 `evidence` 非空,缺 / 全空白 ⇒ 拒)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
174
|
+
/** 收口(写命令,仅主 agent):核销(**只有一条边**——`待核销 → 已核销`:其余现态一律拒,拒文点名现态 + 该现态
|
|
175
|
+
* 的出路句)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
286
176
|
* 可选 `evidence` 参:传值 = 追加缺省(§13.9——旧空直写 / 旧非空拼 `旧|新`)、`evidenceReplace: true`
|
|
287
|
-
* 同传 =
|
|
288
|
-
* 新值空白
|
|
177
|
+
* 同传 = 整段替换;核销 / 撤回两径同携(写值落行,内容不判);两拒面(旗独传 @值计算 ∥
|
|
178
|
+
* 新值空白 @值计算后)见 `evidenceValue` / `assertEvidenceValue`。
|
|
289
179
|
* 判序(同函数体同层,§13.9):目标集判 → 行取 → 源态判 → **核销机检**(`已核销` 才触)→ 值计算 →
|
|
290
|
-
*
|
|
291
|
-
* 收口两源)。**单行形**(`id`)——逐行落笔 = `applyRowClose`。 */
|
|
180
|
+
* 拒面②(新值空白)→ UPDATE(LEDGER.md §3 核销只有一条边)。**单行形**(`id`)——逐行落笔 = `applyRowClose`。 */
|
|
292
181
|
export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, boundary, agent }) {
|
|
293
182
|
if (status !== "已核销" && status !== "已废弃") throw new Error(`ledgerClose:目标态 ${status} ∉ {已核销, 已废弃}`)
|
|
294
183
|
const db = openLedger(cwd, { create: true, boundary })
|
|
@@ -306,47 +195,25 @@ export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, bounda
|
|
|
306
195
|
|
|
307
196
|
// ── 批级批量迁移与结算(设计档 §3.3 · P1 · AC-M2-26 ∥ AC-M2-27)────────────────
|
|
308
197
|
|
|
309
|
-
/** `batch` 入参自身门(设计档 §3.3 · fail-closed):值自身须**可解析**(含非空文件部分)∧ 解析后批档
|
|
310
|
-
* **在盘** ⇒ 否则拒(零写)。判的是**本笔指名**的那一笔——与 §6.1 臂② 同一律、不同字段(§6.2 归批
|
|
311
|
-
* 门判结果行存在性 + 本笔新指名,本门判 `batch` 入参自身)。无匹配行 ⇒ 不拒(`applied = []`)。
|
|
312
|
-
* @returns {string} 批档绝对路径(解析基准 = `base`)
|
|
313
|
-
*/
|
|
314
|
-
function assertBatchArg(fnName, base, batch) {
|
|
315
|
-
const part = filePartOf(batch)
|
|
316
|
-
const hit = part === "" ? { ok: false, abs: "" } : resolveDeclaredRef(base, part)
|
|
317
|
-
if (!hit.ok) throw new Error(`${fnName}:batch 不可解析或不在盘(${batch})`)
|
|
318
|
-
return hit.abs
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** 批 → 行集(选行**单源** · 设计档 §3.3):`task_book` 文件部分经 `resolveDeclaredRef` 解析为与批档
|
|
322
|
-
* **同一绝对路径**的行(任意状态、含归档态)。判据 = 解析同址而非串等值(同批档三种指针写法并存——
|
|
323
|
-
* 无后缀 / `§2` / `§1.5(括注)`,串等值会漏行)。 */
|
|
324
|
-
function resolveBatchRows(db, base, abs) {
|
|
325
|
-
return db.prepare("SELECT * FROM items ORDER BY id").all()
|
|
326
|
-
.filter((r) => { const part = filePartOf(r.task_book); return part !== "" && resolveDeclaredRef(base, part).abs === abs })
|
|
327
|
-
}
|
|
328
|
-
|
|
329
198
|
/** 批级批量迁移(设计档 §3.3):`patch.status` 在场 ⇒ 逐行按既有迁移表重判;不在场 ⇒ **纯字段补丁**
|
|
330
199
|
* (`status` 零写、迁移表不判、`executor` 普通补丁语义——回执 `status` 键 = `null`)。
|
|
331
200
|
* **门拒 ≠ 事务失败**:逐行门(迁移表 / §6.1 写门 / evidence 门)抛错 ⇒ 本行捕获计入 `skipped`
|
|
332
201
|
* (`why` = 该门拒因文案原文),其余行照落;**事务级失败**(取写锁 / `COMMIT` 层)⇒ 整批 `ROLLBACK`
|
|
333
202
|
* 并抛(零行改)——故「部分落地」只由逐行门拒产生,「中途失败」不留半批。
|
|
334
203
|
* `BEGIN IMMEDIATE`:写锁一次先把(逐行不升级锁 ⇒ 事务级失败在首行之前即失败)。
|
|
204
|
+
* 核销机检 ③ 自 2026-10-27 起读档面(不看 token 槽),本径不再预读归属并集——逐行照判、不缺不省。
|
|
335
205
|
* @returns {{batch: string, status: string|null, applied: number[], skipped: {id: number, from: string, why: string}[]}} */
|
|
336
206
|
export function ledgerUpdateBatch({ cwd, batch, patch = {}, executorSessionId, boundary, agent }) {
|
|
337
207
|
const db = openLedger(cwd, { create: true, boundary })
|
|
338
208
|
try {
|
|
339
209
|
const base = baseOf(cwd, boundary)
|
|
340
210
|
const abs = assertBatchArg("ledgerUpdate", base, batch)
|
|
341
|
-
|
|
342
|
-
// 纯字段补丁径也读一次(不抛):闸只对归档行触(存量已知接受),读失败向每行报 ③ 形——不改业务结果
|
|
343
|
-
// (那些行本就被拦),但会以逐行拒因呈现环境问题(父侧已知项,待裁)。
|
|
344
|
-
const proof = { agent, base, batches: patch.status === "已核销" ? readCloseOutBatches(agent, "ledgerUpdate") : patch.status == null ? readCloseOutBatches(agent) : undefined }
|
|
211
|
+
const proof = { agent, base }
|
|
345
212
|
db.exec("BEGIN IMMEDIATE")
|
|
346
213
|
try {
|
|
347
214
|
const applied = [], skipped = []
|
|
348
215
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
349
|
-
try { applied.push(applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, proof).id) }
|
|
216
|
+
try { applied.push(applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, proof, true).id) }
|
|
350
217
|
catch (e) { skipped.push({ id: Number(row.id), from: row.status, why: String(e?.message ?? e) }) }
|
|
351
218
|
}
|
|
352
219
|
db.exec("COMMIT")
|
|
@@ -356,7 +223,7 @@ export function ledgerUpdateBatch({ cwd, batch, patch = {}, executorSessionId, b
|
|
|
356
223
|
}
|
|
357
224
|
|
|
358
225
|
/** 批级批量结算(设计档 §3.3):**只销已到 `待核销` 的行**(勾销径);`待讨论` / `待设计` ⇒ `skipped`
|
|
359
|
-
*
|
|
226
|
+
* 逐行点名(**须先 `update` 到 `在途` 认领**——无核销边可走,统一文本会造假账),`在途` ⇒
|
|
360
227
|
* `skipped` 逐行点名(不可跳)。**归档态(`已核销` / `已废弃`)行 ⇒ 静默不计**(不入 `applied`、
|
|
361
228
|
* 不入 `skipped`——归档行本非结算对象,点名会让每批重跑刷屏)。`evidence` 逐行追加同一文本
|
|
362
229
|
* (`evidenceValue` 同源)。回执 `status` 恒 `已核销`(批量形缺省 = 勾销单值;携则守卫已收窄为
|
|
@@ -368,15 +235,14 @@ export function ledgerCloseBatch({ cwd, batch, evidence, evidenceReplace, bounda
|
|
|
368
235
|
try {
|
|
369
236
|
const base = baseOf(cwd, boundary)
|
|
370
237
|
const abs = assertBatchArg("ledgerClose", base, batch)
|
|
371
|
-
|
|
372
|
-
const proof = { agent, base, batches: readCloseOutBatches(agent, "ledgerClose") }
|
|
238
|
+
const proof = { agent, base } // ③ 读档面(2026-10-27 起不再预读 token 槽归属)
|
|
373
239
|
db.exec("BEGIN IMMEDIATE")
|
|
374
240
|
try {
|
|
375
241
|
const applied = [], skipped = []
|
|
376
242
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
377
243
|
if (row.status === "已核销" || row.status === "已废弃") continue // 归档态静默不计
|
|
378
244
|
if (row.status !== "待核销") {
|
|
379
|
-
const why = row.status === "在途" ? "ledgerClose:在途不可跳(须先 待核销)" : "ledgerClose
|
|
245
|
+
const why = row.status === "在途" ? "ledgerClose:在途不可跳(须先 待核销)" : "ledgerClose:待认领不可核销(须先 update 到 在途)"
|
|
380
246
|
skipped.push({ id: Number(row.id), from: row.status, why })
|
|
381
247
|
continue
|
|
382
248
|
}
|
|
@@ -391,9 +257,6 @@ export function ledgerCloseBatch({ cwd, batch, evidence, evidenceReplace, bounda
|
|
|
391
257
|
|
|
392
258
|
// ── 指针判据重锚面(设计档 §7.1 · B 读数 ∥ C 收口重锚 · 台账 #1550)────────────────
|
|
393
259
|
|
|
394
|
-
/** 重锚取行集(设计档 §6.2——`待核销` 属已落只待结账,不取:对它写「须另起批次接手」失实,且核销零触指针)。 */
|
|
395
|
-
const REANCHOR_STATUSES = ["待讨论", "待设计", "在途"]
|
|
396
|
-
|
|
397
260
|
/** 本批行集(只读,任意状态——设计档 §7.1):批身份单源 = `resolveBatchRows` 解析同址(零二写);
|
|
398
261
|
* 逐行携读数 `taskBookOnDisk`(不入库、不入 `DATA_COLUMNS`)。`batch` 不可解析 ∥ 无根 ⇒ 抛(同族
|
|
399
262
|
* `assertBatchArg` 形);库不在 ⇒ 空集。`cwd` 口径 = 批档所属项目根(§6.2 `cwd` 基准——不用会话锚);
|
|
@@ -410,14 +273,21 @@ export function ledgerBatchRows({ cwd, batch, boundary } = {}) {
|
|
|
410
273
|
} finally { db.close() }
|
|
411
274
|
}
|
|
412
275
|
|
|
413
|
-
/**
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
276
|
+
/**
|
|
277
|
+
* 收口重锚单源(写命令,主 agent 面——设计档 §6.2 · C · AC-M2-30;2026-10-27 按行状态分流 · §2.7):
|
|
278
|
+
* 取本批行集 → **先行排除** `trigger = 认账不排期`(零触、不点名——政策判断归行主人)→ 按**行状态**分流:
|
|
279
|
+
* · `在途`(本批的工作行,父侧没在收口前处置)⇒ `待核销`(做完待结账)+ `executor = NULL`(出边的自动
|
|
280
|
+
* 语义——同 §3.1),回执点名;
|
|
281
|
+
* · `待讨论` / `待设计`(本批没动的行)⇒ `条件` + 复启句(`原批 <批名> 已收口,须另起批次接手`)落
|
|
282
|
+
* `resume_when`;该列现值非空(行主条件句在册)⇒ **不覆盖**、复句追加 `evidence`;回执点名;
|
|
283
|
+
* · `条件`(已在锚位)⇒ 零写跳过(幂等);`待核销` / 归档态 ⇒ 不动、不点名(`alreadyAnchored` 承载
|
|
284
|
+
* 幂等跳过面,回执不噪)。
|
|
285
|
+
* 单事务:任一步失败 ⇒ 整集 `ROLLBACK`(零行改)。**不抛**——失败经返回形 `failed` 报出(`null` 常态);
|
|
286
|
+
* `cwd` 无根 ⇒ 不取行、不重锚(**不落兜底基准**——落了就是静默零命中)。
|
|
287
|
+
* `updated_at` 零触(机器重锚不是人工推进的痕迹——行龄读数照旧)。
|
|
288
|
+
* `boundary` = 上溯上界(会话锚——与工具面 / `ledgerBatchRows` 同一枚界;缺省 = 进程 cwd)。
|
|
289
|
+
* @returns {{batch:string, reanchored:{id:number,status:string,title:string,to:"待核销"|"条件",sentenceTo?:"resume_when"|"evidence"}[], alreadyAnchored:{id:number,status:string,title:string}[], failed:string|null}}
|
|
290
|
+
*/
|
|
421
291
|
export function ledgerReanchorBatch({ cwd, batch, boundary } = {}) {
|
|
422
292
|
const failedResult = (why) => ({ batch, reanchored: [], alreadyAnchored: [], failed: why })
|
|
423
293
|
const derived = cwd == null ? null : resolveLedgerRoot(cwd, boundary)
|
|
@@ -434,15 +304,21 @@ export function ledgerReanchorBatch({ cwd, batch, boundary } = {}) {
|
|
|
434
304
|
db.exec("BEGIN IMMEDIATE")
|
|
435
305
|
const reanchored = [], alreadyAnchored = []
|
|
436
306
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
437
|
-
if (
|
|
438
|
-
if (row.trigger === "认账不排期") continue
|
|
307
|
+
if (row.trigger === "认账不排期") continue // 先行排除:零触、不点名(§2.7)
|
|
439
308
|
const face = { id: Number(row.id), status: row.status, title: row.title }
|
|
440
|
-
if (row.
|
|
309
|
+
if (row.status === "在途") {
|
|
310
|
+
// 本批的工作行:做完待结账(出边自动语义——executor 清零,行离工作位)
|
|
311
|
+
db.prepare("UPDATE items SET status = ?, executor = NULL WHERE id = ?").run("待核销", row.id)
|
|
312
|
+
reanchored.push({ ...face, to: "待核销" })
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
if (row.status !== "待讨论" && row.status !== "待设计") continue // 待核销 / 归档态:不动、不点名
|
|
316
|
+
if (row.trigger === "条件") { alreadyAnchored.push(face); continue } // 幂等零写
|
|
441
317
|
const kept = row.resume_when != null && String(row.resume_when).trim() !== ""
|
|
442
318
|
const nextEvidence = kept ? evidenceValue("ledgerReanchorBatch", row.evidence, sentence, false) : row.evidence
|
|
443
319
|
db.prepare("UPDATE items SET trigger = ?, resume_when = ?, evidence = ? WHERE id = ?")
|
|
444
320
|
.run("条件", kept ? row.resume_when : sentence, nextEvidence, row.id)
|
|
445
|
-
reanchored.push({ ...face, sentenceTo: kept ? "evidence" : "resume_when" })
|
|
321
|
+
reanchored.push({ ...face, to: "条件", sentenceTo: kept ? "evidence" : "resume_when" })
|
|
446
322
|
}
|
|
447
323
|
db.exec("COMMIT")
|
|
448
324
|
return { batch, reanchored, alreadyAnchored, failed: null }
|