@thincoder/core 0.10.12 → 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 +42 -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 +74 -49
- package/agent/run-start.mjs +8 -8
- package/agent/setup.mjs +6 -11
- package/agent/suspension.mjs +1 -1
- package/agent/turn-loop.mjs +35 -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 -354
- 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 +283 -80
- package/agent-tools/batch-unit-test-gate.mjs +107 -0
- 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/agent.mjs +1 -0
- package/client-identity.mjs +70 -0
- package/config-presets.mjs +5 -3
- package/config.mjs +19 -13
- package/context.mjs +39 -8
- package/declaration.mjs +2 -2
- package/doc-check/anchors.mjs +128 -28
- package/doc-check/index.mjs +93 -27
- package/doc-check/line-refs.mjs +112 -0
- package/doc-check/paragraph.mjs +24 -8
- package/embedding.mjs +2 -0
- package/explore-distill.mjs +1 -1
- package/generate-title.mjs +3 -2
- package/i18n.mjs +1 -1
- package/index-discover.mjs +4 -1
- package/ledger-close-proof.mjs +62 -0
- package/ledger-cmd.mjs +79 -194
- 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 +10 -10
- 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/model-specs.mjs +6 -5
- 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 -66
- package/prompts/persona-plan.md +2 -1
- package/prompts/persona-tester.md +55 -0
- package/provider/anthropic.mjs +12 -7
- package/provider/core.mjs +21 -5
- package/provider/errors.mjs +41 -0
- package/provider/google.mjs +25 -9
- package/provider/list-models.mjs +4 -3
- package/provider/rate.mjs +11 -3
- package/provider/responses.mjs +17 -7
- package/provider/sse.mjs +15 -4
- package/session-identity.mjs +21 -0
- package/session-lifecycle.mjs +12 -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 +107 -10
- 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/embedding.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { RETRYABLE_STATUS } from "./provider/index.mjs"
|
|
9
9
|
import { resolveEnvRefs } from "./env-ref.mjs"
|
|
10
|
+
import { withUserAgent } from "./client-identity.mjs"
|
|
10
11
|
// #859(MEMORY.md §6.3 ∥ §7 D-MEM31):嵌入路径发送前净化孤立 UTF-16 代理——sanitize 单源复用
|
|
11
12
|
import { sanitizeLoneSurrogates } from "./escape.mjs"
|
|
12
13
|
const MAX_RETRIES = 3
|
|
@@ -111,6 +112,7 @@ async function requestWithRetry(embedder, input, signal) {
|
|
|
111
112
|
response = await fetch(`${embedder.baseURL}/embeddings`, {
|
|
112
113
|
method: "POST",
|
|
113
114
|
headers: {
|
|
115
|
+
...withUserAgent(), // 产品标识头(embedding 无定制头字段——PROVIDER.md §6.17)
|
|
114
116
|
"Content-Type": "application/json",
|
|
115
117
|
Authorization: `Bearer ${embedder.apiKey}`,
|
|
116
118
|
},
|
package/explore-distill.mjs
CHANGED
|
@@ -103,7 +103,7 @@ async function distillExplorations(history, start, provider, signal, agent, dept
|
|
|
103
103
|
logCtx: {
|
|
104
104
|
stage: "distill", child: agent?._logId ?? null, kind: "distill",
|
|
105
105
|
role: agent?._role ?? null, depth: depth ?? null,
|
|
106
|
-
session: agent?._sessionStart ?? null, cwd: agent?.cwd
|
|
106
|
+
session: agent?._sessionStart ?? null, cwd: agent?.cwd,
|
|
107
107
|
traces: agent?.config?.traces?.enabled !== false,
|
|
108
108
|
},
|
|
109
109
|
})
|
package/generate-title.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { proxyFetch } from "./proxy.mjs"
|
|
12
|
+
import { withUserAgent } from "./client-identity.mjs"
|
|
12
13
|
import { resolveProviderSecrets } from "./env-ref.mjs"
|
|
13
14
|
// D7(2026-09-21 块标题行对齐批——标题链单源):首条真实 user 消息谓词收核单源
|
|
14
15
|
// (`isRealUserMsg`——纯函数零依赖,无环)。
|
|
@@ -97,8 +98,8 @@ export async function generateTitle(userContent, provider) {
|
|
|
97
98
|
body = JSON.stringify(bodyObj)
|
|
98
99
|
extract = (data) => data.choices?.[0]?.message?.content
|
|
99
100
|
}
|
|
100
|
-
//
|
|
101
|
-
const headers = { ...(provider.headers
|
|
101
|
+
// 定制头 + 产品标识头(UA 不可被渠道覆盖——PROVIDER.md §6.17)——定制头在前、内置头在后:内置头胜出;三格式各自内置头。
|
|
102
|
+
const headers = { ...withUserAgent(provider.headers), "Content-Type": "application/json" }
|
|
102
103
|
if (provider.format === "anthropic") {
|
|
103
104
|
headers["x-api-key"] = provider.apiKey
|
|
104
105
|
headers["anthropic-version"] = "2023-06-01"
|
package/i18n.mjs
CHANGED
|
@@ -43,7 +43,7 @@ export const CORE_MESSAGES = Object.freeze({
|
|
|
43
43
|
"digest.capStop": { en: "[auto-turn stopped at ${turns} turns — partial digest; finished reports stay in history]", zh: "自动回合在 ${turns} 轮处停止——部分消化;已完成的报告保留在历史中" },
|
|
44
44
|
// 消化账务批 · 2026-10-05 · 台账 #930(§6.31.6 端面残余行——判据 = `unsettledCount(carrier)`;
|
|
45
45
|
// 消费面 = CLI 痕行 ∥ 桌面 / VSC 行元素;三端共读本键)
|
|
46
|
-
"digest.residue": { en: "${n} background report(s) not accounted —
|
|
46
|
+
"digest.residue": { en: "${n} background report(s) not accounted — no automatic re-delivery; escalates to UNSETTLED", zh: "有 ${n} 份后台报告未销账——不再自动重投,未签收满额即升级 UNSETTLED" },
|
|
47
47
|
// ── 限流 / 供应商状态行 ──
|
|
48
48
|
"status.rateWait": { en: "TPM throttle wait ~${s}s", zh: "TPM 限流等待 ~${s}s" },
|
|
49
49
|
"status.rateLimited": { en: "Rate-limited 429, retry in ${s}s", zh: "限流 429,${s}s 后重试" },
|
package/index-discover.mjs
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
import { readdirSync } from "node:fs"
|
|
17
17
|
import { join, relative } from "node:path"
|
|
18
18
|
import { DEFAULT_DECLARATION, loadProjectDeclaration } from "./conventions.mjs"
|
|
19
|
+
// 排除名单单源(零依赖叶档 `skip-dirs.mjs`——2026-10-27 四份名单合一):本档不再持本地副本,
|
|
20
|
+
// 同名再导出保既有 import 面(`SKIP_DIRS` 的旧消费面零改)。
|
|
21
|
+
import { SKIP_DIRS } from "./skip-dirs.mjs"
|
|
22
|
+
export { SKIP_DIRS } from "./skip-dirs.mjs"
|
|
19
23
|
|
|
20
24
|
const CODE_EXTS = new Set([
|
|
21
25
|
".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".py", ".rs", ".go", ".java", ".c", ".cpp", ".h", ".hpp",
|
|
@@ -25,7 +29,6 @@ const CODE_EXTS = new Set([
|
|
|
25
29
|
".scala", ".pl", ".pm", ".r", ".jl", ".zig", ".groovy", ".ps1", ".proto", ".graphql", ".tf", ".hcl",
|
|
26
30
|
])
|
|
27
31
|
const DOC_EXTS = new Set([".md", ".markdown", ".txt", ".rst", ".adoc", ".mdx", ".org", ".wiki", ".tex"])
|
|
28
|
-
export const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".turbo", "coverage", "__pycache__", ".next"])
|
|
29
32
|
|
|
30
33
|
/** Lower-cased ".ext" of a path/name ("" when it has no extension). */
|
|
31
34
|
function extensionOf(p) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ledger-close-proof.mjs — 台账核销机检单源(F-LX4 · 2026-10-26 批 §2;台账 #1807)。
|
|
3
|
+
*
|
|
4
|
+
* 判据(需求档 §② 功能点 11 / AC-M2-34):凡写 `已核销` 的语句,写库前判三件——① 行的 `task_book`
|
|
5
|
+
* 所指批档在盘 ② 该档 §1 状态行 = `已收口` ③ 该批档的 `§3` 状态行 = `评审通过`;缺一即拒、
|
|
6
|
+
* **一次报全缺项**、行零改。**判据只有目标值一条**——闸挂在写这个值的两处逐行共函数
|
|
7
|
+
* (`ledger-cmd.mjs` 的 `applyRowUpdate` / `applyRowClose`)之前,不判来源态。
|
|
8
|
+
*
|
|
9
|
+
* ③ 为什么能去运行态(2026-10-27 三链统一批 · R6):`§3` 只能由**绑在该档上的评审实例**写
|
|
10
|
+
* (`batch` 工具的 review 绑定),所以「§3 有 pass」本身就证明了「有一次绑本档的评审跑过并给了 pass」,
|
|
11
|
+
* 与 token 槽归属同义,而且换会话、重启进程、隔天补账都成立。旧判据(槽内有归属)在需要「换会话核销」
|
|
12
|
+
* 时给出假拒——它判的是**谁在跑**,不是**评审过没过**。
|
|
13
|
+
* 代价:评审自报 pass 的信任面不变(今天也是评审自己写 §3)。
|
|
14
|
+
*
|
|
15
|
+
* 住本档的理由(拆分件——先例 = `ledger-tools.mjs` 自 `ledger-cmd.mjs` 拆出):`ledger-cmd.mjs` 收尾
|
|
16
|
+
* 446 行,机检本体 + 三形拒因入册即越单档 500 顾问线(设计 §五「实落超 500 ⇒ 主动拆分」)。两枚指针
|
|
17
|
+
* 值谓词(`filePartOf`——设计档 §6.1 口径 1 单源 ∥ `namesPointer`)随迁本档,`ledger-cmd` 反向 import
|
|
18
|
+
* 取用——方向单向、无静态环。
|
|
19
|
+
*
|
|
20
|
+
* 只读:三件都只读档面与行值,不读任何 token 槽状态(`_engDesignTokens` / `unionBatches` 不入本档)。
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from "node:fs"
|
|
23
|
+
|
|
24
|
+
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
25
|
+
import { isBatchRecord, readBatchStatusLine, readSectionStatusWord, STATUS_WORDS } from "./agent-tools/batch-skeleton.mjs"
|
|
26
|
+
|
|
27
|
+
/** 指针 ∥ 批档值 → 文件部分(首个 `§` 前子串,trim——设计档 §6.1 口径 1 单源;`task_book` 与
|
|
28
|
+
* `batch` 同形同口径)。 */
|
|
29
|
+
export const filePartOf = (v) => (v == null ? "" : String(v).split("§")[0].trim())
|
|
30
|
+
|
|
31
|
+
/** 本笔指名谓词(设计档 §6.1 口径 3):显式空值(`null` / 空串 / 全空白) ≡ **未指名**——臂② 不触。 */
|
|
32
|
+
export const namesPointer = (v) => v != null && String(v).trim() !== ""
|
|
33
|
+
|
|
34
|
+
/** 核销机检(F-LX4 · 需求档 §② 功能点 11 / AC-M2-34):写 `已核销` 前判三件——① `task_book` 所指档在盘
|
|
35
|
+
* ② 该档 §1 = `已收口` ③ 该档 §3 = `评审通过`;**判据只有目标值一条**(两处写这个值的 SQL 之前同挂),
|
|
36
|
+
* 三件各判、**一次报全缺项**、行零改(只判不写;2026-10-27 起 ③ = 档面事实,零运行态)。 */
|
|
37
|
+
export function assertCloseOutProof(fnName, { base, row }) {
|
|
38
|
+
const missing = [] // 每项 = 一条拒因行(首字 = 缺项号)
|
|
39
|
+
let abs = null
|
|
40
|
+
if (!namesPointer(row?.task_book)) {
|
|
41
|
+
missing.push(" ① 行无 task_book 指针:核销须先挂批档(update 写 task_book),再跑全链")
|
|
42
|
+
} else {
|
|
43
|
+
const hit = resolveDeclaredRef(base, filePartOf(row.task_book))
|
|
44
|
+
if (!hit.ok) missing.push(` ① 批档不在盘(task_book = ${row.task_book}):先补指针,或恢复该档`)
|
|
45
|
+
else {
|
|
46
|
+
abs = hit.abs
|
|
47
|
+
const src = readFileSync(abs, "utf8")
|
|
48
|
+
if (!isBatchRecord(src)) missing.push(` ② 指针所指档不是批档(${abs} 无 §1 段):先把 task_book 改指批档`)
|
|
49
|
+
else {
|
|
50
|
+
const state = readBatchStatusLine(src)
|
|
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
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (missing.length > 0) throw new Error(`${fnName}:核销机检未过(行 ${row?.id} 零改)——缺:${missing.map((t) => t.trim()[0]).join(" ")}\n${missing.join("\n")}`)
|
|
62
|
+
}
|
package/ledger-cmd.mjs
CHANGED
|
@@ -1,29 +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;2026-10-27 改判 ③ = 档面):同一两逐行共函数在写 `已核销` 前判
|
|
19
|
+
* 三件(批档在盘 ∥ §1 = 已收口 ∥ §3 = 评审通过),缺一即拒、一次报全缺项、行零改;判据只有目标值一条。
|
|
20
|
+
* 本体住拆分件 `ledger-close-proof.mjs`(同 `ledger-tools.mjs` 先例)。
|
|
18
21
|
*
|
|
19
22
|
* 接线(设计档 §2.2 命令面接线):族出口 = `ledger.mjs`(re-export 两源——KD-M2-3);查询命令 ←
|
|
20
23
|
* tools/index.mjs(assembleBuiltinTools 全角色面);写命令 ← agent/family-tools.mjs(assembleFamilyTools
|
|
21
24
|
* 的 depthOnly 分支)——一律**动态 import** 本模块链(ledger-db.mjs 静态 import node:sqlite ⇒
|
|
22
25
|
* 消费侧静态 import 会破 W8 契约②)。
|
|
23
26
|
*/
|
|
24
|
-
import {
|
|
25
|
-
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"
|
|
26
28
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
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"
|
|
27
35
|
|
|
28
36
|
/** 逐行老化判真(LEDGER.md §13.4 · 2026-10-07 批 ledger-tool):未决四态(`PENDING_STATUSES`)∧
|
|
29
37
|
* 行龄 > `AGING_DAYS` 天;行龄源 = `updated_at ?? created_at`(§5 同源);时间戳缺 / 不可解析
|
|
@@ -36,17 +44,9 @@ function isRowAged(row, now) {
|
|
|
36
44
|
return Number.isFinite(ms) && (now - ms) / 86400000 > AGING_DAYS
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
/**
|
|
40
|
-
* `
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
/** 解析基准(设计档 §6.1 口径 2 同一子表达式——与台账库键同源):`resolveLedgerRoot(cwd, boundary)?.root
|
|
44
|
-
* ?? resolve(cwd ?? ".")`。`boundary` = 会话锚(与 `openLedger` 同一枚上界——同一笔动作的判据不能两界)。 */
|
|
45
|
-
const baseOf = (cwd, boundary) => resolveLedgerRoot(cwd, boundary)?.root ?? resolve(cwd ?? ".")
|
|
46
|
-
|
|
47
|
-
/** `evidence` 超限读数(设计档 §5 P4——**读数非闸门**):缺 / 空 ⇒ false;`query` 逐行携(含归档行——
|
|
48
|
-
* 行值事实);`count` 的统计集 = 未决四态(§7.1)。 */
|
|
49
|
-
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
|
|
50
50
|
|
|
51
51
|
/** 本地日界(`YYYY-MM-DD`)——`resume_due` 比较粒度(设计档 §3.7:本地日界,当日锚当日即到期)。 */
|
|
52
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())}` }
|
|
@@ -73,7 +73,7 @@ const pointerOnDisk = (row, base) => { const part = filePartOf(row?.task_book);
|
|
|
73
73
|
* 四类出口态**首中优先**(序 = `resume_due` → `dangling` → `unlabeled` → `aged`);返回出口态字串
|
|
74
74
|
* ∥ `null`。`unlabeled`(`trigger IS NULL` ∧ 超龄)⊆ `aged` ⇒ 必先于 `aged` 判(否则永不返回)。
|
|
75
75
|
* `resume_due` 比较粒度 = 本地日界(锚日 ≤ 当地日 ⇒ 到期;不按时刻比)。 */
|
|
76
|
-
export function overdueOf(row, { now = Date.now(), base
|
|
76
|
+
export function overdueOf(row, { now = Date.now(), base } = {}) {
|
|
77
77
|
if (!PENDING_STATUSES.includes(row?.status)) return null
|
|
78
78
|
if (row.trigger === "条件") {
|
|
79
79
|
const anchor = resumeAnchor(row.resume_when)
|
|
@@ -88,7 +88,7 @@ export function overdueOf(row, { now = Date.now(), base = process.cwd() } = {})
|
|
|
88
88
|
/** 查询(只读,全角色):SELECT 行集(status / kind / board / trigger / task_book 等值过滤并取,缺省 =
|
|
89
89
|
* 全部行;id 升序)。库不在 = 空账。只读开库(readOnly 句柄 + 零 DDL/ALTER——read-data-interface 批
|
|
90
90
|
* FR2③);库档非台账库 ⇒ 空账。逐行携计算字段(均不入库、不入导出白名单 `DATA_COLUMNS`):
|
|
91
|
-
* `aged`(§13.4)∥ `
|
|
91
|
+
* `aged`(§13.4)∥ `evidenceTooLong`(§5 P4 文字太长读数)∥ `taskBookOnDisk`(§7.1 B 在盘读数)∥ `overdue`
|
|
92
92
|
* (§3.7 出口态字串 ∥ null——机判单源 `overdueOf`);`task_book` = 第 5 枚等值过滤(**原值等值**——列串直比,与 §3.3 选行的
|
|
93
93
|
* 「文件部分解析同址」两径口径不同、各自单源);`trigger` 过滤仅三枚举等值(`NULL` 不过滤);
|
|
94
94
|
* `now` = 注入缝(确定性用例)。 */
|
|
@@ -104,62 +104,36 @@ export function ledgerQuery({ cwd, boundary, status = null, kind = null, board =
|
|
|
104
104
|
if (task_book) { where.push("task_book = ?"); args.push(task_book) }
|
|
105
105
|
const rows = db.prepare(`SELECT * FROM items${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY id`).all(...args)
|
|
106
106
|
const base = baseOf(cwd, boundary)
|
|
107
|
-
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 }) }))
|
|
108
108
|
} finally { db.close() }
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** 计数单源(只读,全角色):回报 `{ count, overdue,
|
|
111
|
+
/** 计数单源(只读,全角色):回报 `{ count, overdue, evidenceTooLong }`(设计档 §7.1——原裸数出参
|
|
112
112
|
* 改对象形,消费面同批改)。`count` = 未决四态行数;`overdue` = 其中命中出口态的行数(`overdueOf`
|
|
113
|
-
* 机判单源);`
|
|
114
|
-
* `
|
|
113
|
+
* 机判单源);`evidenceTooLong` = 其中 `evidence` 文字太长的行数(§5)。**一次 SELECT + 逐行判**——
|
|
114
|
+
* `evidenceTooLong` 与 `overdue` 同一次遍历(零额外 IO;成本模型 = §7.2);不设缓存(读数须反映
|
|
115
115
|
* 即时盘面)。库不在 = 三零。 */
|
|
116
116
|
export function ledgerCount({ cwd, boundary, task_book = null } = {}) {
|
|
117
117
|
const db = openLedger(cwd, { readOnly: true, boundary })
|
|
118
|
-
if (!db) return { count: 0, overdue: 0,
|
|
118
|
+
if (!db) return { count: 0, overdue: 0, evidenceTooLong: 0 }
|
|
119
119
|
try {
|
|
120
120
|
const where = [`status IN (${PENDING_STATUSES.map(() => "?").join(",")})`], args = [...PENDING_STATUSES]
|
|
121
121
|
if (task_book) { where.push("task_book = ?"); args.push(task_book) }
|
|
122
122
|
const rows = db.prepare(`SELECT * FROM items WHERE ${where.join(" AND ")}`).all(...args)
|
|
123
123
|
const now = Date.now(), base = baseOf(cwd, boundary)
|
|
124
|
-
let overdue = 0,
|
|
124
|
+
let overdue = 0, tooLong = 0
|
|
125
125
|
for (const r of rows) {
|
|
126
126
|
if (overdueOf(r, { now, base }) !== null) overdue++
|
|
127
|
-
if (
|
|
127
|
+
if (isEvidenceTooLong(r)) tooLong++
|
|
128
128
|
}
|
|
129
|
-
return { count: rows.length, overdue,
|
|
129
|
+
return { count: rows.length, overdue, evidenceTooLong: tooLong }
|
|
130
130
|
} finally { db.close() }
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
/** `evidence`
|
|
133
|
+
/** `evidence` 文字太长的行数(设计档 §7.1 导出面——`count` 回报读数**同一单源**:本函数为其薄委托,
|
|
134
134
|
* 不另立第二判定面)。 */
|
|
135
|
-
export function
|
|
136
|
-
return ledgerCount({ cwd, boundary, task_book }).
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** 本笔指名谓词(设计档 §6.1 口径 3):显式空值(`null` / 空串 / 全空白) ≡ **未指名**——臂② 不触。 */
|
|
140
|
-
const namesPointer = (v) => v != null && String(v).trim() !== ""
|
|
141
|
-
|
|
142
|
-
/** 写门·指针点名(设计档 §6.1 两臂 · AC-M2-10 ∥ AC-M2-29):判**本笔写入面**——两臂之外零判。
|
|
143
|
-
* 臂①(点名非空):结果行 `status ∈ {在途, 待核销}` ∨ 结果行 `trigger = 归批` ⇒ `task_book` 非空
|
|
144
|
-
* (缺 / 空串 / 全空白 ⇒ 拒「必填」);**不判在盘**(列值事实,不随盘面翻转——DDL 咬合 CHECK 为该半幅防线)。
|
|
145
|
-
* 臂②(本笔新指名):本笔新写 `task_book`(非空值)∥ 本笔新写 `trigger = 归批` ⇒ 绑定指针非空 ∧ 文件部分
|
|
146
|
-
* 可解析 ∧ 解析出的档**在盘**(判定单源 = `resolveDeclaredRef`,零二写)。
|
|
147
|
-
* **「档此刻在不在盘」不入臂①**:批档可随时整体清理 ⇒ 当结果行判据则合法账务被拒、而出路正是被拒字段
|
|
148
|
-
* (死锁);存量指针失效只体现为读数 `taskBookOnDisk = false`(§7.1)。
|
|
149
|
-
* 判位 = 与迁移表判同层(落盘前);文案前缀 = 调用函数名;基准 = §6.1 口径 2(根推导单源同子表达式)。
|
|
150
|
-
* @param {{status:string, trigger:string|null, taskBook:string|null, wroteTaskBook:boolean, wroteBatchTrigger:boolean}} face 本笔写入面
|
|
151
|
-
*/
|
|
152
|
-
function assertTaskBookGate(cwd, fnName, face, boundary) {
|
|
153
|
-
const { status, trigger, taskBook, wroteTaskBook, wroteBatchTrigger } = face
|
|
154
|
-
if ((status === "在途" || status === "待核销" || trigger === "归批") && !namesPointer(taskBook)) {
|
|
155
|
-
throw new Error(`${fnName}:task_book 必填(在途 / 待核销 / 归批 须携任务书指针)`)
|
|
156
|
-
}
|
|
157
|
-
if (!wroteTaskBook && !wroteBatchTrigger) return // 臂② 之外零判:旧指针所指档已清不阻任何此类写入
|
|
158
|
-
const part = filePartOf(taskBook)
|
|
159
|
-
if (!part) throw new Error(`${fnName}:task_book 不可解析(缺文件部分):${taskBook}`)
|
|
160
|
-
const base = baseOf(cwd, boundary) // §6.1 口径 2 同一子表达式(单源 = 本档 `baseOf`)
|
|
161
|
-
const { ok, abs } = resolveDeclaredRef(base, part)
|
|
162
|
-
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
|
|
163
137
|
}
|
|
164
138
|
|
|
165
139
|
/** 新增(写命令,仅主 agent):INSERT——入待讨论(六态状态机入口)。`row.resume_when` 可空(§3.4)。 */
|
|
@@ -181,112 +155,30 @@ export function ledgerAdd({ cwd, row, boundary }) {
|
|
|
181
155
|
} finally { db.close() }
|
|
182
156
|
}
|
|
183
157
|
|
|
184
|
-
/** executor 目标值计算(设计档 docs/core/design/LEDGER.md §3.1——判位在 ledgerUpdate 体内:迁移表判 → 本语义 → 写门 → UPDATE)。
|
|
185
|
-
* 优先级 = patch 显式 > 自动语义(进边 = executorSessionId / 出边 = NULL)> 行现值兜底 > NULL。
|
|
186
|
-
* 进边集(2026-10-07 批 ledger-tool 扩面——§13.3 / KD-LT1):`待讨论 → 待设计`(批点火边)∥
|
|
187
|
-
* `待设计 → 在途`(实施入边)——两枚同式。 */
|
|
188
|
-
function resolveExecutorTarget(row, to, patch, executorSessionId) {
|
|
189
|
-
if ((row.status === "待讨论" && to === "待设计") || (row.status === "待设计" && to === "在途")) {
|
|
190
|
-
return patch.executor ?? executorSessionId ?? row.executor ?? null
|
|
191
|
-
}
|
|
192
|
-
if (row.status === "在途" && (to === "待核销" || to === "已废弃")) {
|
|
193
|
-
return null // 出边自动语义压 patch——显式传 executor 也不复活
|
|
194
|
-
}
|
|
195
|
-
return patch.executor ?? row.executor ?? null
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** `evidence` 值计算(设计档 LEDGER.md §13.9——update / close 两函数同源一处落):追加缺省(旧值非空 ⇒
|
|
199
|
-
* `旧 + "|" + 新`——「|」两侧零空格直接相接;旧空(`null` ∥ 空串 ∥ 全空白)⇒ 直写,零分隔符残留);
|
|
200
|
-
* `evidenceReplace === true` 与值同传 ⇒ 整段替换(旧文零保留);`evidence` 缺 / `null` ≡ 略去(零变)。
|
|
201
|
-
* 拒面①(旗独传——无新文可算)判于本阶段(close 侧先于追认门);核内旗判只认 `=== true`(类型严判在
|
|
202
|
-
* 工具层)。两值逐字落行(不裁 / 不判内容)。 */
|
|
203
|
-
function evidenceValue(fnName, oldValue, evidence, evidenceReplace) {
|
|
204
|
-
if (evidenceReplace === true && evidence == null) throw new Error(`${fnName}:evidenceReplace 须与 evidence 同传(缺新文)`)
|
|
205
|
-
if (evidence == null) return oldValue
|
|
206
|
-
const old = oldValue == null || String(oldValue).trim() === "" ? "" : String(oldValue)
|
|
207
|
-
return evidenceReplace === true || old === "" ? evidence : `${old}|${evidence}`
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** 拒面②(新值全空白——追加 ∥ 覆盖两径同判)判点(§13.9 判序):update 随值计算同拍 ∥ close 落
|
|
211
|
-
* 追认门之后;`evidence` 未传(`null` / 略去)⇒ 不判。 */
|
|
212
|
-
function assertEvidenceValue(fnName, evidence) {
|
|
213
|
-
if (evidence != null && String(evidence).trim() === "") throw new Error(`${fnName}:evidence 为空(非空字符串)`)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** 单行落笔·update(**单行形 ∥ 批量形共函数**——设计档 §3.3「逐行语义单源」):迁移表判 → 核销标记门
|
|
217
|
-
* (目标 = `已核销` ∧ 结果标题携标记 ⇒ 拒——判序 = LIGHT-CHANNEL.md §2.5)→ executor / evidence 值计算
|
|
218
|
-
* (含两拒面)→ 写门 → UPDATE(判序 = §13.9 逐字未变)。`to` = `patch.status ?? 行现态`
|
|
219
|
-
* ——批量形纯字段补丁(不携 `status`)时 `to === row.status` ⇒ 迁移表不判、`status` 零写(§3.3)。
|
|
220
|
-
* `resume_when` = 普通可空字段补丁(`patch.resume_when ?? 行值`——§3.4)。 */
|
|
221
|
-
function applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary) {
|
|
222
|
-
const to = patch.status ?? row.status
|
|
223
|
-
if (to !== row.status && !(ALLOWED_MIGRATIONS[row.status] ?? []).includes(to)) {
|
|
224
|
-
throw new Error(`ledgerUpdate:迁移 ${row.status} → ${to} 不在允许迁移表`)
|
|
225
|
-
}
|
|
226
|
-
// 核销标记门(LIGHT-CHANNEL.md §2.5):判**结果标题**——一笔同携清标记 title + status=已核销 ⇒ 放行
|
|
227
|
-
if (to === "已核销" && String(patch.title ?? row.title ?? "").includes(ROUND_MARKER)) {
|
|
228
|
-
throw new Error(`ledgerUpdate:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
229
|
-
}
|
|
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`)——逐行落笔 =
|
|
252
|
-
|
|
162
|
+
* / `assertEvidenceValue`;判位 = 本语义段,随 executor 目标值同拍)。**单行形**(`id`)——逐行落笔 =
|
|
163
|
+
* `applyRowUpdate`(自开事务:半途抛 ⇒ 行零改)。 */
|
|
164
|
+
export function ledgerUpdate({ cwd, id, patch, executorSessionId, boundary, agent }) {
|
|
253
165
|
const db = openLedger(cwd, { create: true, boundary })
|
|
254
166
|
try {
|
|
255
167
|
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
|
|
256
168
|
if (!row) throw new Error(`ledgerUpdate:行 ${id} 不存在`)
|
|
257
|
-
const { id: rowId } = applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary)
|
|
169
|
+
const { id: rowId } = applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, { agent, base: baseOf(cwd, boundary) })
|
|
258
170
|
return { id: rowId }
|
|
259
171
|
} finally { db.close() }
|
|
260
172
|
}
|
|
261
173
|
|
|
262
|
-
/**
|
|
263
|
-
*
|
|
264
|
-
* 值计算(拒面① 旗独传判于此)→ 追认口 `evidence` 门 → 拒面② → UPDATE。
|
|
265
|
-
* 撤回(任意态 → 已废弃——含在途直撤)同步 `executor = NULL`(LEDGER.md §3.1 ④);核销两源(勾销 /
|
|
266
|
-
* 追认)`executor` 零触碰(非在途出边)。 */
|
|
267
|
-
function applyRowClose(db, row, status, evidence, evidenceReplace) {
|
|
268
|
-
if (status === "已核销" && !["待讨论", "待设计", "待核销"].includes(row.status)) throw new Error(`ledgerClose:核销仅限 待讨论 / 待设计 / 待核销(现态 ${row.status})`)
|
|
269
|
-
// 核销标记门(LIGHT-CHANNEL.md §2.5):勾销 ∥ 追认同判;撤回(→ 已废弃)不入本门
|
|
270
|
-
if (status === "已核销" && String(row.title ?? "").includes(ROUND_MARKER)) {
|
|
271
|
-
throw new Error(`ledgerClose:行标题仍携「${ROUND_MARKER}」——收尾链未跑完,不许核销(先跑收尾全链,再由父侧清标记)`)
|
|
272
|
-
}
|
|
273
|
-
const nextEvidence = evidenceValue("ledgerClose", row.evidence, evidence, evidenceReplace)
|
|
274
|
-
if (status === "已核销" && ["待讨论", "待设计"].includes(row.status) && (nextEvidence == null || String(nextEvidence).trim() === "")) throw new Error(`ledgerClose:追认核销须带 evidence(现态 ${row.status})`)
|
|
275
|
-
assertEvidenceValue("ledgerClose", evidence) // 判序:拒面②(新值空白)落追认门之后 → UPDATE
|
|
276
|
-
const now = nowIso()
|
|
277
|
-
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)
|
|
278
|
-
return { id: Number(row.id), status }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** 收口(写命令,仅主 agent):核销两源(勾销:待核销 → 已核销;追认核销:待讨论 / 待设计 → 已核销,
|
|
282
|
-
* 行 `evidence` 非空,缺 / 全空白 ⇒ 拒)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
174
|
+
/** 收口(写命令,仅主 agent):核销(**只有一条边**——`待核销 → 已核销`:其余现态一律拒,拒文点名现态 + 该现态
|
|
175
|
+
* 的出路句)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
283
176
|
* 可选 `evidence` 参:传值 = 追加缺省(§13.9——旧空直写 / 旧非空拼 `旧|新`)、`evidenceReplace: true`
|
|
284
|
-
* 同传 =
|
|
285
|
-
* 新值空白
|
|
286
|
-
* 判序(同函数体同层,§13.9):目标集判 → 行取 → 源态判 → 值计算 →
|
|
287
|
-
*
|
|
288
|
-
|
|
289
|
-
export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, boundary }) {
|
|
177
|
+
* 同传 = 整段替换;核销 / 撤回两径同携(写值落行,内容不判);两拒面(旗独传 @值计算 ∥
|
|
178
|
+
* 新值空白 @值计算后)见 `evidenceValue` / `assertEvidenceValue`。
|
|
179
|
+
* 判序(同函数体同层,§13.9):目标集判 → 行取 → 源态判 → **核销机检**(`已核销` 才触)→ 值计算 →
|
|
180
|
+
* 拒面②(新值空白)→ UPDATE(LEDGER.md §3 核销只有一条边)。**单行形**(`id`)——逐行落笔 = `applyRowClose`。 */
|
|
181
|
+
export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, boundary, agent }) {
|
|
290
182
|
if (status !== "已核销" && status !== "已废弃") throw new Error(`ledgerClose:目标态 ${status} ∉ {已核销, 已废弃}`)
|
|
291
183
|
const db = openLedger(cwd, { create: true, boundary })
|
|
292
184
|
try {
|
|
@@ -294,7 +186,7 @@ export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, bounda
|
|
|
294
186
|
try {
|
|
295
187
|
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
|
|
296
188
|
if (!row) throw new Error(`ledgerClose:行 ${id} 不存在`)
|
|
297
|
-
const out = applyRowClose(db, row, status, evidence, evidenceReplace)
|
|
189
|
+
const out = applyRowClose(db, row, status, evidence, evidenceReplace, { agent, base: baseOf(cwd, boundary) })
|
|
298
190
|
db.exec("COMMIT")
|
|
299
191
|
return out
|
|
300
192
|
} catch (e) { db.exec("ROLLBACK"); throw e }
|
|
@@ -303,43 +195,25 @@ export function ledgerClose({ cwd, id, status, evidence, evidenceReplace, bounda
|
|
|
303
195
|
|
|
304
196
|
// ── 批级批量迁移与结算(设计档 §3.3 · P1 · AC-M2-26 ∥ AC-M2-27)────────────────
|
|
305
197
|
|
|
306
|
-
/** `batch` 入参自身门(设计档 §3.3 · fail-closed):值自身须**可解析**(含非空文件部分)∧ 解析后批档
|
|
307
|
-
* **在盘** ⇒ 否则拒(零写)。判的是**本笔指名**的那一笔——与 §6.1 臂② 同一律、不同字段(§6.2 归批
|
|
308
|
-
* 门判结果行存在性 + 本笔新指名,本门判 `batch` 入参自身)。无匹配行 ⇒ 不拒(`applied = []`)。
|
|
309
|
-
* @returns {string} 批档绝对路径(解析基准 = `base`)
|
|
310
|
-
*/
|
|
311
|
-
function assertBatchArg(fnName, base, batch) {
|
|
312
|
-
const part = filePartOf(batch)
|
|
313
|
-
const hit = part === "" ? { ok: false, abs: "" } : resolveDeclaredRef(base, part)
|
|
314
|
-
if (!hit.ok) throw new Error(`${fnName}:batch 不可解析或不在盘(${batch})`)
|
|
315
|
-
return hit.abs
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** 批 → 行集(选行**单源** · 设计档 §3.3):`task_book` 文件部分经 `resolveDeclaredRef` 解析为与批档
|
|
319
|
-
* **同一绝对路径**的行(任意状态、含归档态)。判据 = 解析同址而非串等值(同批档三种指针写法并存——
|
|
320
|
-
* 无后缀 / `§2` / `§1.5(括注)`,串等值会漏行)。 */
|
|
321
|
-
function resolveBatchRows(db, base, abs) {
|
|
322
|
-
return db.prepare("SELECT * FROM items ORDER BY id").all()
|
|
323
|
-
.filter((r) => { const part = filePartOf(r.task_book); return part !== "" && resolveDeclaredRef(base, part).abs === abs })
|
|
324
|
-
}
|
|
325
|
-
|
|
326
198
|
/** 批级批量迁移(设计档 §3.3):`patch.status` 在场 ⇒ 逐行按既有迁移表重判;不在场 ⇒ **纯字段补丁**
|
|
327
199
|
* (`status` 零写、迁移表不判、`executor` 普通补丁语义——回执 `status` 键 = `null`)。
|
|
328
200
|
* **门拒 ≠ 事务失败**:逐行门(迁移表 / §6.1 写门 / evidence 门)抛错 ⇒ 本行捕获计入 `skipped`
|
|
329
201
|
* (`why` = 该门拒因文案原文),其余行照落;**事务级失败**(取写锁 / `COMMIT` 层)⇒ 整批 `ROLLBACK`
|
|
330
202
|
* 并抛(零行改)——故「部分落地」只由逐行门拒产生,「中途失败」不留半批。
|
|
331
203
|
* `BEGIN IMMEDIATE`:写锁一次先把(逐行不升级锁 ⇒ 事务级失败在首行之前即失败)。
|
|
204
|
+
* 核销机检 ③ 自 2026-10-27 起读档面(不看 token 槽),本径不再预读归属并集——逐行照判、不缺不省。
|
|
332
205
|
* @returns {{batch: string, status: string|null, applied: number[], skipped: {id: number, from: string, why: string}[]}} */
|
|
333
|
-
export function ledgerUpdateBatch({ cwd, batch, patch = {}, executorSessionId, boundary }) {
|
|
206
|
+
export function ledgerUpdateBatch({ cwd, batch, patch = {}, executorSessionId, boundary, agent }) {
|
|
334
207
|
const db = openLedger(cwd, { create: true, boundary })
|
|
335
208
|
try {
|
|
336
209
|
const base = baseOf(cwd, boundary)
|
|
337
210
|
const abs = assertBatchArg("ledgerUpdate", base, batch)
|
|
211
|
+
const proof = { agent, base }
|
|
338
212
|
db.exec("BEGIN IMMEDIATE")
|
|
339
213
|
try {
|
|
340
214
|
const applied = [], skipped = []
|
|
341
215
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
342
|
-
try { applied.push(applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary).id) }
|
|
216
|
+
try { applied.push(applyRowUpdate(db, cwd, row, patch, executorSessionId, boundary, proof, true).id) }
|
|
343
217
|
catch (e) { skipped.push({ id: Number(row.id), from: row.status, why: String(e?.message ?? e) }) }
|
|
344
218
|
}
|
|
345
219
|
db.exec("COMMIT")
|
|
@@ -349,29 +223,30 @@ export function ledgerUpdateBatch({ cwd, batch, patch = {}, executorSessionId, b
|
|
|
349
223
|
}
|
|
350
224
|
|
|
351
225
|
/** 批级批量结算(设计档 §3.3):**只销已到 `待核销` 的行**(勾销径);`待讨论` / `待设计` ⇒ `skipped`
|
|
352
|
-
*
|
|
226
|
+
* 逐行点名(**须先 `update` 到 `在途` 认领**——无核销边可走,统一文本会造假账),`在途` ⇒
|
|
353
227
|
* `skipped` 逐行点名(不可跳)。**归档态(`已核销` / `已废弃`)行 ⇒ 静默不计**(不入 `applied`、
|
|
354
228
|
* 不入 `skipped`——归档行本非结算对象,点名会让每批重跑刷屏)。`evidence` 逐行追加同一文本
|
|
355
229
|
* (`evidenceValue` 同源)。回执 `status` 恒 `已核销`(批量形缺省 = 勾销单值;携则守卫已收窄为
|
|
356
230
|
* `{已核销}`——§3.2 `whenPresent`)。事务语义同 `ledgerUpdateBatch`。
|
|
357
231
|
* @returns {{batch: string, status: string, applied: number[], skipped: {id: number, from: string, why: string}[]}} */
|
|
358
|
-
export function ledgerCloseBatch({ cwd, batch, evidence, evidenceReplace, boundary }) {
|
|
232
|
+
export function ledgerCloseBatch({ cwd, batch, evidence, evidenceReplace, boundary, agent }) {
|
|
359
233
|
const status = "已核销"
|
|
360
234
|
const db = openLedger(cwd, { create: true, boundary })
|
|
361
235
|
try {
|
|
362
236
|
const base = baseOf(cwd, boundary)
|
|
363
237
|
const abs = assertBatchArg("ledgerClose", base, batch)
|
|
238
|
+
const proof = { agent, base } // ③ 读档面(2026-10-27 起不再预读 token 槽归属)
|
|
364
239
|
db.exec("BEGIN IMMEDIATE")
|
|
365
240
|
try {
|
|
366
241
|
const applied = [], skipped = []
|
|
367
242
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
368
243
|
if (row.status === "已核销" || row.status === "已废弃") continue // 归档态静默不计
|
|
369
244
|
if (row.status !== "待核销") {
|
|
370
|
-
const why = row.status === "在途" ? "ledgerClose:在途不可跳(须先 待核销)" : "ledgerClose
|
|
245
|
+
const why = row.status === "在途" ? "ledgerClose:在途不可跳(须先 待核销)" : "ledgerClose:待认领不可核销(须先 update 到 在途)"
|
|
371
246
|
skipped.push({ id: Number(row.id), from: row.status, why })
|
|
372
247
|
continue
|
|
373
248
|
}
|
|
374
|
-
try { applied.push(applyRowClose(db, row, status, evidence, evidenceReplace).id) }
|
|
249
|
+
try { applied.push(applyRowClose(db, row, status, evidence, evidenceReplace, proof).id) }
|
|
375
250
|
catch (e) { skipped.push({ id: Number(row.id), from: row.status, why: String(e?.message ?? e) }) }
|
|
376
251
|
}
|
|
377
252
|
db.exec("COMMIT")
|
|
@@ -382,9 +257,6 @@ export function ledgerCloseBatch({ cwd, batch, evidence, evidenceReplace, bounda
|
|
|
382
257
|
|
|
383
258
|
// ── 指针判据重锚面(设计档 §7.1 · B 读数 ∥ C 收口重锚 · 台账 #1550)────────────────
|
|
384
259
|
|
|
385
|
-
/** 重锚取行集(设计档 §6.2——`待核销` 属已落只待结账,不取:对它写「须另起批次接手」失实,且核销零触指针)。 */
|
|
386
|
-
const REANCHOR_STATUSES = ["待讨论", "待设计", "在途"]
|
|
387
|
-
|
|
388
260
|
/** 本批行集(只读,任意状态——设计档 §7.1):批身份单源 = `resolveBatchRows` 解析同址(零二写);
|
|
389
261
|
* 逐行携读数 `taskBookOnDisk`(不入库、不入 `DATA_COLUMNS`)。`batch` 不可解析 ∥ 无根 ⇒ 抛(同族
|
|
390
262
|
* `assertBatchArg` 形);库不在 ⇒ 空集。`cwd` 口径 = 批档所属项目根(§6.2 `cwd` 基准——不用会话锚);
|
|
@@ -401,14 +273,21 @@ export function ledgerBatchRows({ cwd, batch, boundary } = {}) {
|
|
|
401
273
|
} finally { db.close() }
|
|
402
274
|
}
|
|
403
275
|
|
|
404
|
-
/**
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
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
|
+
*/
|
|
412
291
|
export function ledgerReanchorBatch({ cwd, batch, boundary } = {}) {
|
|
413
292
|
const failedResult = (why) => ({ batch, reanchored: [], alreadyAnchored: [], failed: why })
|
|
414
293
|
const derived = cwd == null ? null : resolveLedgerRoot(cwd, boundary)
|
|
@@ -425,15 +304,21 @@ export function ledgerReanchorBatch({ cwd, batch, boundary } = {}) {
|
|
|
425
304
|
db.exec("BEGIN IMMEDIATE")
|
|
426
305
|
const reanchored = [], alreadyAnchored = []
|
|
427
306
|
for (const row of resolveBatchRows(db, base, abs)) {
|
|
428
|
-
if (
|
|
429
|
-
if (row.trigger === "认账不排期") continue
|
|
307
|
+
if (row.trigger === "认账不排期") continue // 先行排除:零触、不点名(§2.7)
|
|
430
308
|
const face = { id: Number(row.id), status: row.status, title: row.title }
|
|
431
|
-
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 } // 幂等零写
|
|
432
317
|
const kept = row.resume_when != null && String(row.resume_when).trim() !== ""
|
|
433
318
|
const nextEvidence = kept ? evidenceValue("ledgerReanchorBatch", row.evidence, sentence, false) : row.evidence
|
|
434
319
|
db.prepare("UPDATE items SET trigger = ?, resume_when = ?, evidence = ? WHERE id = ?")
|
|
435
320
|
.run("条件", kept ? row.resume_when : sentence, nextEvidence, row.id)
|
|
436
|
-
reanchored.push({ ...face, sentenceTo: kept ? "evidence" : "resume_when" })
|
|
321
|
+
reanchored.push({ ...face, to: "条件", sentenceTo: kept ? "evidence" : "resume_when" })
|
|
437
322
|
}
|
|
438
323
|
db.exec("COMMIT")
|
|
439
324
|
return { batch, reanchored, alreadyAnchored, failed: null }
|