@thincoder/core 0.10.7 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/advisor/compaction.mjs +1 -1
- package/advisor/run.mjs +1 -1
- package/agent/chat-call.mjs +40 -27
- package/agent/child-marks.mjs +1 -1
- package/agent/dispatch-gates.mjs +7 -4
- package/agent/dispatch-run.mjs +1 -0
- package/agent/family-tools.mjs +13 -8
- package/agent/helpers.mjs +12 -10
- package/agent/image-heal.mjs +65 -0
- package/agent/light-round.mjs +4 -4
- package/agent/live-beat.mjs +4 -4
- package/agent/post-turn.mjs +34 -0
- package/agent/record-results.mjs +9 -1
- package/agent/run-stages.mjs +45 -10
- package/agent/setup-reminders.mjs +14 -13
- package/agent/setup.mjs +72 -48
- package/agent/spawn-child.mjs +1 -1
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +2 -1
- package/agent/timers.mjs +8 -8
- package/agent/turn-loop.mjs +7 -1
- package/agent-tools/advisor.mjs +1 -1
- package/agent-tools/async-discard.mjs +40 -5
- package/agent-tools/async-settle.mjs +3 -1
- package/agent-tools/audit-block.mjs +5 -3
- package/agent-tools/batch-gate.mjs +198 -0
- package/agent-tools/batch-lifecycle.mjs +216 -36
- package/agent-tools/batch-paths.mjs +20 -20
- package/agent-tools/batch-skeleton.mjs +87 -0
- package/agent-tools/batch.mjs +11 -3
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/context.mjs +28 -5
- package/agent-tools/design-slots.mjs +20 -7
- package/agent-tools/doc-check.mjs +75 -0
- package/agent-tools/eng.mjs +5 -3
- package/agent-tools/manifest.mjs +18 -18
- package/agent-tools/parent-channel.mjs +3 -3
- package/agent-tools/review-facts.mjs +2 -2
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/spawn-gates.mjs +108 -47
- package/agent-tools/ssh-session.mjs +486 -0
- package/agent-tools/ssh-transport.mjs +324 -0
- package/agent-tools/ssh-vault.mjs +291 -0
- package/agent-tools/subagent-scheduler.mjs +6 -5
- package/agent-tools/subagent-spawn.mjs +18 -11
- package/agent-tools/subagent.mjs +17 -9
- package/agent-tools/task.mjs +1 -1
- package/agent-tools/timer.mjs +8 -2
- package/agent-tools/zero-write-watch.mjs +2 -2
- package/agent-tools.mjs +3 -2
- package/attachments.mjs +4 -4
- package/browser/cdp.mjs +2 -2
- package/browser/launch.mjs +50 -4
- package/browser/snapshot.mjs +1 -1
- package/config-defaults.mjs +92 -0
- package/config-io.mjs +7 -2
- package/config-normalize.mjs +109 -0
- package/config-watch.mjs +2 -2
- package/config.mjs +88 -190
- package/declaration.mjs +5 -1
- package/doc-check/anchors.mjs +142 -0
- package/doc-check/index.mjs +210 -0
- package/doc-check/version.mjs +84 -0
- package/doc-check/width.mjs +132 -0
- package/file-refs.mjs +6 -6
- package/generate-title.mjs +1 -1
- package/hooks.mjs +1 -1
- package/i18n.mjs +13 -1
- package/ledger-cmd.mjs +314 -62
- package/ledger-db.mjs +122 -31
- package/ledger-migrate.mjs +18 -12
- package/ledger-read.mjs +6 -7
- package/ledger-tools.mjs +138 -77
- package/ledger.mjs +34 -62
- package/log.mjs +17 -3
- package/manifest-discovery.mjs +21 -18
- package/manifest-schema.mjs +17 -14
- package/manifest.mjs +37 -22
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +2 -2
- package/memory/core.mjs +1 -1
- package/memory/scan.mjs +1 -1
- package/memory/sweep.mjs +1 -1
- package/model-specs.mjs +7 -7
- package/notify-policy.mjs +6 -6
- package/package.json +2 -1
- package/permission.mjs +21 -1
- package/prompts/advisor-design.md +53 -23
- package/prompts/advisor-round1.md +34 -14
- package/prompts/advisor-round2.md +32 -19
- package/prompts/advisor-round3.md +34 -21
- package/prompts/common.md +148 -79
- package/prompts/consult-base.md +14 -8
- package/prompts/discipline-engineering.md +70 -66
- package/prompts/discipline-normal.md +86 -77
- package/prompts/persona-coder.md +6 -6
- package/prompts/persona-eng-coder.md +19 -18
- package/prompts/persona-eng-designer.md +40 -31
- package/prompts/persona-engineering.md +124 -92
- package/prompts/persona-explore.md +2 -2
- package/prompts/persona-normal.md +20 -17
- package/prompts/persona-plan.md +7 -7
- package/prompts/subagent-base.md +4 -4
- package/provider/anthropic.mjs +4 -2
- package/provider/core.mjs +4 -2
- package/provider/errors.mjs +8 -0
- package/provider/google.mjs +8 -1
- package/provider/normalize.mjs +42 -12
- package/provider/responses-request.mjs +4 -3
- package/provider/responses.mjs +2 -2
- package/provider/wait-status.mjs +1 -1
- package/provider-flows.mjs +1 -1
- package/queued.mjs +2 -2
- package/rules.mjs +4 -4
- package/session-gc.mjs +1 -1
- package/session-lifecycle.mjs +7 -7
- package/session-slot-write.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session.mjs +1 -1
- package/shell-candidates.mjs +2 -2
- package/shell-identity.mjs +1 -1
- package/team.mjs +63 -5
- package/think-off.mjs +3 -3
- package/token-ttl.mjs +1 -1
- package/token-window.mjs +1 -1
- package/tool-docs/bash.md +1 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +1 -1
- package/tool-docs/doc_check.md +54 -0
- package/tool-docs/fetch.md +3 -2
- package/tool-docs/ledger.md +7 -5
- package/tool-docs/manifest.md +14 -2
- package/tool-docs/ssh.md +51 -0
- package/tool-docs/subagent.md +6 -9
- package/tool-docs/timer.md +2 -1
- package/tool-docs/websearch.md +3 -1
- package/tools/browser.mjs +1 -1
- package/tools/execute.mjs +1 -1
- package/tools/git-run.mjs +2 -2
- package/tools/git.mjs +8 -8
- package/tools/index.mjs +3 -0
- package/tools/ops.mjs +1 -1
- package/tools/process-tree.mjs +2 -2
- package/tools/ssh-askpass.mjs +35 -0
- package/tools/ssh.mjs +105 -0
- package/tools/web-deepseek.mjs +172 -0
- package/tools/web.mjs +29 -4
- package/vision-reader.mjs +4 -4
package/ledger-db.mjs
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* 靠 DDL(AC-M2-2),不靠应用层 if。归档 = 软删除(状态值,无物理 DELETE——KD-M2-4)。
|
|
7
7
|
* 本档静态 import node:sqlite ⇒ 消费侧一律动态 import 本模块链(W8 契约②——KD-M2-3)。
|
|
8
8
|
*
|
|
9
|
+
* 项目根推导单源(`resolveLedgerRoot`)+ 同级枚举(`ledgerChildren`)居本档——键 / 库位 / 根同源
|
|
10
|
+
* (设计档 `LEDGER.md` §2.1「根解析面」/ §3.8):读写同一判据(档在盘 ∨ 以目录自身路径为键的库在盘)。
|
|
11
|
+
*
|
|
9
12
|
* 落点(2026-09-17 用户裁定收正):台账库**不在项目目录**——住用户数据目录
|
|
10
13
|
* `~/.thincoder/ledger/<sha1(normalizeCwd(项目根绝对路径))[:16]>.db`(工作树外、天然不进 git;
|
|
11
14
|
* 同区先例 = configDir 的 memory.db / tool-results)。项目根仅作**关联键**,不在项目内留任何文件。
|
|
@@ -13,12 +16,12 @@
|
|
|
13
16
|
* `normalizeCwd`——与 session / checkpoint / traces / peers 同契约,跨端同 cwd 同键同库。
|
|
14
17
|
*/
|
|
15
18
|
import { createHash } from "node:crypto"
|
|
16
|
-
import { existsSync, mkdirSync } from "node:fs"
|
|
17
|
-
import { join, resolve } from "node:path"
|
|
19
|
+
import { existsSync, mkdirSync, opendirSync } from "node:fs"
|
|
20
|
+
import { dirname, join, resolve } from "node:path"
|
|
18
21
|
import { DatabaseSync } from "node:sqlite"
|
|
19
22
|
|
|
20
23
|
import { configDir } from "./config-io.mjs"
|
|
21
|
-
import {
|
|
24
|
+
import { MANIFEST_REL, projectRootView } from "./manifest.mjs"
|
|
22
25
|
// 归一步单源(KD-LN1——直引,不抽新 util:先例 = peer-domains.mjs / traces/trace-store.mjs)。
|
|
23
26
|
import { normalizeCwd } from "./session-slots.mjs"
|
|
24
27
|
|
|
@@ -42,14 +45,89 @@ export function ledgerKey(root) {
|
|
|
42
45
|
return createHash("sha1").update(normalizeCwd(root)).digest("hex").slice(0, 16)
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/** 键控库文件路径(根 → 库位)——**内部形**:根推导内部判「库在盘」时用它,**不得经 `ledgerDbPath`**
|
|
49
|
+
* (互指即环,设计档 §3.8「无自指」)。库目录 = `ledgerDir`(`_setLedgerDirForTest` 覆盖 ⇒ 夹具根外零写)。 */
|
|
50
|
+
const keyedDbFile = (root) => join(ledgerDir, `${ledgerKey(root)}.db`)
|
|
51
|
+
|
|
52
|
+
/** 「已注册」判据(设计档 §2.1——**唯一**事实,两臂取一即成立):
|
|
53
|
+
* 档臂 = 该目录带项目声明档 `PROJECT-MANIFEST.json`(档在盘);
|
|
54
|
+
* 库臂 = 以该目录自身路径为键的键控库在盘 ∧ 该目录**非工作区态**(键控库存在 ≠ 可作项目);
|
|
55
|
+
* 工作区判据只在库在盘时才查(`projectRootView` 含 fs 扫描——档臂已中即短路)。 */
|
|
56
|
+
function isRegistered(dir) {
|
|
57
|
+
if (existsSync(join(dir, MANIFEST_REL))) return true
|
|
58
|
+
if (!existsSync(keyedDbFile(dir))) return false
|
|
59
|
+
return projectRootView(dir).state !== "workspace"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 同级枚举上限(成本有界:父目录超此规模 = 缓存 / 临时等非仓族形态 → 退化空集)。
|
|
63
|
+
* 2026-10-16 批 ledger-root-derivation 自 `ledger.mjs` 随迁(根推导收敛序 ∥ 族发现同级枚举同源)。 */
|
|
64
|
+
export const MAX_SIBLING_SCAN = 100
|
|
65
|
+
|
|
66
|
+
/** 目录下**已注册**(档 ∨ 库——§2.1 单谓词)的直接子目录(目录名升序;不可读 ∥ 超限 ⇒ 空集;
|
|
67
|
+
* **惰性枚举 + 上限**——大目录零全量扫描)。消费面 = 根推导收敛序 ∥ 族发现 ∥ 轻通道闸候选枚举。
|
|
68
|
+
* `ledger` = 该子目录根的键控库路径(谓词含档臂 ⇒ 可能尚未在盘)。2026-10-16 批自 `ledger.mjs` 随迁。 */
|
|
69
|
+
export function ledgerChildren(dir) {
|
|
70
|
+
const out = []
|
|
71
|
+
let handle
|
|
72
|
+
try { handle = opendirSync(dir) } catch { return out }
|
|
73
|
+
try {
|
|
74
|
+
let n = 0
|
|
75
|
+
for (;;) {
|
|
76
|
+
const ent = handle.readSync()
|
|
77
|
+
if (!ent) break
|
|
78
|
+
if (++n > MAX_SIBLING_SCAN) return []
|
|
79
|
+
if (!ent.isDirectory()) continue
|
|
80
|
+
const root = join(dir, ent.name)
|
|
81
|
+
if (!isRegistered(root)) continue
|
|
82
|
+
out.push({ root, ledger: keyedDbFile(root) })
|
|
83
|
+
}
|
|
84
|
+
} catch { /* 读中断 → 已得子集(尽力而为) */ }
|
|
85
|
+
finally { try { handle.closeSync() } catch { /* 已关 */ } }
|
|
86
|
+
return out.sort((a, b) => (a.root < b.root ? -1 : a.root > b.root ? 1 : 0))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 项目根推导**单源**(设计档 §2.1「根解析面」/ §3.8——读写同一判据、同一实现):
|
|
90
|
+
* 自 `target`(「动作目标路径」;缺省 = `.`)起沿**祖先链**向上(含自身)取**最近一个「已注册」目录**
|
|
91
|
+
* ⇒ `{ root, basis: 'ancestor' }`(自明——回执逐字零改)。
|
|
92
|
+
* 祖先链无命中 ∧ `descend: true` ⇒ **向下收敛**锚的直接子目录(`ledgerChildren` 同款惰性枚举 +
|
|
93
|
+
* 上限、名序升序):恰一 ⇒ 取之;≥2 ⇒ ① 先取**有键控库**者(账已在盘 = 事实)② 该集名序首者 ⇒
|
|
94
|
+
* `{ root, basis: 'descendant' }`(向下发现——**须报明**);零已注册子目录 ⇒ `null`(无根)。
|
|
95
|
+
* `descend: false` ⇒ 只问祖先支(族发现 ∥ 标记面——容器 ∥ 工作区锚 ⇒ `null` ⇒ 族求和)。
|
|
96
|
+
* **无自指**:内部「库在盘」判据只用 `ledgerKey` + `keyedDbFile`(不经 `ledgerDbPath`)。
|
|
97
|
+
* @param {string} [target] 动作目标路径(目录)
|
|
98
|
+
* @param {{descend?: boolean}} [opts] `descend` 缺省 `true`(动作面——`openLedger` / `ledgerDbPath` / 单范围读)
|
|
99
|
+
* @returns {{root: string, basis: 'ancestor'|'descendant'}|null} 项目根 + 判据来源 / 无根(`null`) */
|
|
100
|
+
export function resolveLedgerRoot(target, { descend = true } = {}) {
|
|
101
|
+
const start = resolve(target ?? ".")
|
|
102
|
+
let dir = start
|
|
103
|
+
for (;;) {
|
|
104
|
+
if (isRegistered(dir)) return { root: dir, basis: "ancestor" }
|
|
105
|
+
const parent = dirname(dir)
|
|
106
|
+
if (parent === dir) break // 盘根 → 祖先链无已注册者
|
|
107
|
+
dir = parent
|
|
108
|
+
}
|
|
109
|
+
if (!descend) return null
|
|
110
|
+
const children = ledgerChildren(start)
|
|
111
|
+
if (children.length === 0) return null
|
|
112
|
+
if (children.length === 1) return { root: children[0].root, basis: "descendant" }
|
|
113
|
+
const withDb = children.filter((c) => existsSync(c.ledger))
|
|
114
|
+
return { root: (withDb.length > 0 ? withDb : children)[0].root, basis: "descendant" }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 无根文案(单源——设计档 §3.8「无根契约」逐字;`openLedger` 写面 ∥ `ledgerDbPath` 共用同一句)。 */
|
|
118
|
+
const noRootMessage = (start) =>
|
|
119
|
+
`台账:自 ${start} 未能推导项目根——祖先链与本级子目录均无已注册项目(带 PROJECT-MANIFEST.json 或已有键控台账库)`
|
|
120
|
+
|
|
45
121
|
/** 项目根 → 库路径(稳定键 = `ledgerKey` 前 16 位——路径唯一标识,不含分隔符)。
|
|
46
|
-
*
|
|
47
|
-
*
|
|
122
|
+
* **归单源**(2026-10-16 批 ledger-root-derivation):根 = `resolveLedgerRoot(cwd)`——与读面 / 写面
|
|
123
|
+
* 同一推导;无根 ⇒ 抛**同一**无根文案(键需根,不产第二答案)。
|
|
48
124
|
* v2 键归一(2026-09-25 批):盘符本大写(CLI)⇒ 键恒等零变;小写盘符(VSC `uri.fsPath`)
|
|
49
125
|
* 并入同键 ⇒ 同项目两端同库(收正前 = 同项目两键两库)。 */
|
|
50
126
|
export function ledgerDbPath(cwd) {
|
|
51
|
-
const
|
|
52
|
-
|
|
127
|
+
const start = resolve(cwd ?? ".")
|
|
128
|
+
const derived = resolveLedgerRoot(start)
|
|
129
|
+
if (!derived) throw new Error(noRootMessage(start))
|
|
130
|
+
return keyedDbFile(derived.root)
|
|
53
131
|
}
|
|
54
132
|
|
|
55
133
|
/** 未决四态(「活条目」口径 = 计数单源 WHERE 集;已核销 / 已废弃 = 归档态)。 */
|
|
@@ -61,9 +139,15 @@ export const PENDING_STATUSES = ["待讨论", "待设计", "在途", "待核销"
|
|
|
61
139
|
* 保公共面零变——LEDGER.md §13.4)。 */
|
|
62
140
|
export const AGING_DAYS = 30
|
|
63
141
|
|
|
142
|
+
/** `evidence` 软上限(字——设计档 `LEDGER.md` §5 P4 读数面单源):超限**只出读数,不拒写**
|
|
143
|
+
* (软上限非闸门——用户裁定「无限机检反感」;读数面 = `query` 逐行 `evidenceOversize` ∥
|
|
144
|
+
* `count` 回报)。 */
|
|
145
|
+
export const EVIDENCE_SOFT_LIMIT = 300
|
|
146
|
+
|
|
64
147
|
/** 台账库结构版本(N2 标记**单源**——`PRAGMA user_version` 落标:写面开库 `v < 本值 ⇒ 置`;
|
|
65
|
-
* 读面如实回读(旧库未标 = 0);结构变更(新列 / 新表)同步升号——2026-10-03 read-data-interface
|
|
66
|
-
|
|
148
|
+
* 读面如实回读(旧库未标 = 0);结构变更(新列 / 新表)同步升号——2026-10-03 read-data-interface 批。
|
|
149
|
+
* 2 = `resume_when` 可空列(设计档 §3.4 P5)。 */
|
|
150
|
+
export const LEDGER_SCHEMA_VERSION = 2
|
|
67
151
|
|
|
68
152
|
/** 六态允许迁移表(ledgerUpdate 迁移前判,不在表内 → 拒——规格 ②.2;设计档 §2.1)。 */
|
|
69
153
|
export const ALLOWED_MIGRATIONS = {
|
|
@@ -91,25 +175,30 @@ CREATE TABLE IF NOT EXISTS items (
|
|
|
91
175
|
created_at TEXT,
|
|
92
176
|
updated_at TEXT,
|
|
93
177
|
closed_at TEXT,
|
|
178
|
+
resume_when TEXT,
|
|
94
179
|
CHECK (status NOT IN ('在途','待核销') OR (task_book IS NOT NULL AND task_book <> ''))
|
|
95
180
|
)`
|
|
96
181
|
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
182
|
+
/** 老库幂等迁移共件:items 表缺该可空列 ⇒ ALTER 补列(`executor` ∥ `resume_when` 两列共用一份
|
|
183
|
+
* 「复判列在否」逻辑——设计档 §3.4)。判据 = PRAGMA table_info 实查(不猜版本号);ALTER
|
|
184
|
+
* duplicate column 竞争 → 复核列在吞 / 不在抛真错。列存在性谓词抽成单元(param exists),
|
|
185
|
+
* 便于并发竞争拍直测。K-LX5:老库零删改、零重建。 */
|
|
186
|
+
export function ensureColumn(db, name, { exists } = {}) {
|
|
101
187
|
const columnExists = exists
|
|
102
|
-
?? (() => db.prepare("SELECT 1 FROM pragma_table_info('items') WHERE name =
|
|
188
|
+
?? (() => db.prepare("SELECT 1 FROM pragma_table_info('items') WHERE name = ?").get(name) !== undefined)
|
|
103
189
|
if (columnExists()) return false
|
|
104
190
|
try {
|
|
105
|
-
db.exec(
|
|
191
|
+
db.exec(`ALTER TABLE items ADD COLUMN ${name} TEXT`)
|
|
106
192
|
return true
|
|
107
193
|
} catch (e) {
|
|
108
194
|
if (columnExists()) return false // 并发竞争——另一连接已加列:吞
|
|
109
|
-
throw new Error(`台账库
|
|
195
|
+
throw new Error(`台账库 ${name} 列迁移失败:${e.message}`)
|
|
110
196
|
}
|
|
111
197
|
}
|
|
112
198
|
|
|
199
|
+
/** `executor` 列补列(F-LX1——共件别名,公共面形零改)。 */
|
|
200
|
+
export const ensureExecutorColumn = (db, opts) => ensureColumn(db, "executor", opts)
|
|
201
|
+
|
|
113
202
|
/** 只读态开库(read-data-interface 批 FR2③——readOnly 句柄 + **零 DDL / 零 ALTER**;先例 = 本包
|
|
114
203
|
* `ledger-migrate.mjs` 的 `openRead`):文件必已在盘;items 表缺失探针 ⇒ 视同空账返回 null
|
|
115
204
|
* (非台账库的 SQLite 档 / 空档——观测面保持「空集」,文件零触)。不可开(坏档 / 非 SQLite)⇒
|
|
@@ -135,32 +224,34 @@ function markSchemaVersion(db) {
|
|
|
135
224
|
if (v < LEDGER_SCHEMA_VERSION) db.exec(`PRAGMA user_version = ${LEDGER_SCHEMA_VERSION}`)
|
|
136
225
|
}
|
|
137
226
|
|
|
138
|
-
/** 开库(cwd =
|
|
139
|
-
*
|
|
140
|
-
*
|
|
227
|
+
/** 开库(cwd = 「动作目标路径」)→ DatabaseSync 句柄 + 幂等建表 + 老库幂等迁移(executor 列)。
|
|
228
|
+
* **根推导**(设计档 §2.1 / §3.8):项目根 = `resolveLedgerRoot(cwd)`——读写同一判据;嵌套取近者、
|
|
229
|
+
* 容器锚向下收敛。**无根(`null`)**:写面(`create:true`)抛(fail-loud、零落盘零建库);读面空账
|
|
230
|
+
* (`null`——与「库不在 = 空账」同形)。
|
|
231
|
+
* 读面(create=false):库文件不存在 → 返回 null(读面不建库、无副作用)。
|
|
232
|
+
* 写面(create=true):库文件不存在自动建(台账目录随之创建)。
|
|
141
233
|
* **只读态**(readOnly=true——读面专用):只读句柄 + 零 DDL / ALTER + items 探针(见 openReadOnly);
|
|
142
234
|
* 如实副作用:旧库(缺 executor 列)**读**不再自动补列——补列归写面开库;读出行缺列按 null 归一。
|
|
143
|
-
*
|
|
144
|
-
* 零写——不静默回退锚;族锚「项目不可解析」,AC-M2-17);兜底 `resolve(cwd ?? ".")` 只属 none 态。 */
|
|
235
|
+
* 「目录不存在」的真错照抛(fail-loud——先于推导,文案逐字零改)。 */
|
|
145
236
|
export function openLedger(cwd, { create = false, readOnly = false } = {}) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (view.state === "ambiguous") {
|
|
150
|
-
const list = view.candidates.map((c) => `- ${c}`).join("\n")
|
|
151
|
-
throw new Error(`项目不可解析:cwd ${dir} 下候选项目不是恰好一个(下列 ${view.candidates.length} 个)——台账按项目根键控,请显式给出项目根 cwd:\n${list}`)
|
|
237
|
+
const start = resolve(cwd ?? ".")
|
|
238
|
+
if (!existsSync(start)) {
|
|
239
|
+
throw new Error(`项目目录不存在:${start}——cwd 请给存在的项目根(相对路径按当前工作目录解析;缺省 = 会话项目根)`)
|
|
152
240
|
}
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
throw new Error(
|
|
241
|
+
const derived = resolveLedgerRoot(start)
|
|
242
|
+
if (!derived) {
|
|
243
|
+
if (create) throw new Error(noRootMessage(start)) // 写面抛——零落盘零建库
|
|
244
|
+
return null // 读面空账(未注册目录下旧行为亦为空 ⇒ 零回归)
|
|
156
245
|
}
|
|
246
|
+
const file = keyedDbFile(derived.root)
|
|
157
247
|
if (!existsSync(file) && !create) return null
|
|
158
248
|
if (readOnly) return openReadOnly(file)
|
|
159
249
|
try { mkdirSync(ledgerDir, { recursive: true }) } catch { /* 已存在 / 并发建目录竞争——忽略 */ }
|
|
160
250
|
const db = new DatabaseSync(file)
|
|
161
251
|
try {
|
|
162
252
|
db.exec(DDL)
|
|
163
|
-
|
|
253
|
+
ensureColumn(db, "executor")
|
|
254
|
+
ensureColumn(db, "resume_when")
|
|
164
255
|
markSchemaVersion(db)
|
|
165
256
|
} catch (e) {
|
|
166
257
|
db.close()
|
package/ledger-migrate.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* ② `ledger migrate --dry-run | --confirm`:变体键库一次性合并——三件套 = dry-run 零写 / 先备份(拷贝 + 读回同计数)/ 幂等(全字段等值);
|
|
4
4
|
* 六步 = 备份 → 源就绪与列集判 → 单事务迁入(事务内读回核验)→ 回收源(rename 进 `ledger-trash`,不 unlink)→ 报告 → 汇总。执行主体 = 父侧 ops(KD-LN6)。
|
|
5
5
|
* ③ `ledger audit`:逐 `*.db` 定性(目标库 / 变体源 / 空库 / 不可归因(有行)/ 不可读(坏档);stat 失败 ⇒ 读取失败态)+ 候选根集合归因;只报告零动作。
|
|
6
|
-
* 铁律:**
|
|
6
|
+
* 铁律:**13 数据列逐字复制(不含 id)+ id 重发**(两库 id 空间重叠;列集单源 = `DATA_COLUMNS`——`resume_when`
|
|
7
|
+
* 落地后 12 ⇒ 13,设计档 §3.4);备份 / 回收目录 = `ledgerDirPath()` 的**兄弟位**(§2.11#6 ⇒ 夹具根外零写);
|
|
7
8
|
* 迁移走直接 SQL(§6.1 写门不拦迁移本身 ⇒ 风险行出旗不拦截);本档静态 import node:sqlite ⇒ 消费侧一律动态 import(W8 契约②——同 ledger-db.mjs)。
|
|
8
9
|
*/
|
|
9
10
|
import { createHash } from "node:crypto"
|
|
@@ -12,14 +13,17 @@ import { basename, dirname, join, resolve } from "node:path"
|
|
|
12
13
|
import { DatabaseSync } from "node:sqlite"
|
|
13
14
|
|
|
14
15
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
15
|
-
import { ledgerDirPath, ledgerKey, openLedger } from "./ledger-db.mjs"
|
|
16
|
+
import { ledgerDirPath, ledgerKey, openLedger, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
16
17
|
import { resolveProjectRoot } from "./manifest.mjs"
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
+
/** 13 数据列(**等值比对列集**——不含 id:id 重发,§2.2 语义保全 / AC-M2-12;`resume_when` 落地后\n * 12 ⇒ 13——设计档 §3.4)。 */
|
|
19
20
|
export const DATA_COLUMNS = [
|
|
20
21
|
"kind", "status", "title", "board", "req_doc", "task_book",
|
|
21
|
-
"evidence", "trigger", "executor", "created_at", "updated_at", "closed_at",
|
|
22
|
+
"evidence", "trigger", "executor", "created_at", "updated_at", "closed_at", "resume_when",
|
|
22
23
|
]
|
|
24
|
+
/** 旧库可缺列容忍集(缺则按 NULL 映射照迁):`executor`(F-LX1 前的旧 DDL)∥ `resume_when`
|
|
25
|
+
* (P5 落地前的旧 DDL——设计档 §3.4)。其余任一数据列缺 ⇒ 列集不可归因,拒(fail-closed)。 */
|
|
26
|
+
const TOLERATED_MISSING = ["executor", "resume_when"]
|
|
23
27
|
/** 备份面根 / 回收面根(`ledgerDirPath()` 的**兄弟位**——同派生于可覆盖基,§2.11#6)。 */
|
|
24
28
|
export function ledgerBackupRoot() { return join(dirname(ledgerDirPath()), "ledger-backup") }
|
|
25
29
|
export function ledgerTrashRoot() { return join(dirname(ledgerDirPath()), "ledger-trash") }
|
|
@@ -48,7 +52,7 @@ function batchStamp(date) {
|
|
|
48
52
|
const p = (n) => String(n).padStart(2, "0")
|
|
49
53
|
return `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`
|
|
50
54
|
}
|
|
51
|
-
/** 行集读取(id +
|
|
55
|
+
/** 行集读取(id + `DATA_COLUMNS` 全列;缺失列按 NULL 映射——旧 DDL 源的 `executor` / `resume_when` 正常态,KD-LN10)。 */
|
|
52
56
|
function readRows(db, missing = []) {
|
|
53
57
|
const cols = DATA_COLUMNS.filter((c) => !missing.includes(c))
|
|
54
58
|
return db.prepare(`SELECT id, ${cols.join(", ")} FROM items ORDER BY id`).all()
|
|
@@ -62,7 +66,7 @@ function describeSource(file) {
|
|
|
62
66
|
const cols = itemColumns(db)
|
|
63
67
|
if (!cols.length) return { rows: 0, missing: [], refusal: "不可开为台账库(无 items 表)" }
|
|
64
68
|
const missing = DATA_COLUMNS.filter((c) => !cols.includes(c))
|
|
65
|
-
const bad = missing.filter((c) => c
|
|
69
|
+
const bad = missing.filter((c) => !TOLERATED_MISSING.includes(c))
|
|
66
70
|
const rows = readRows(db, missing)
|
|
67
71
|
return { rows: rows.length, missing, dist: byStatus(rows), refusal: bad.length ? `缺数据列(列集不可归因):${bad.join(" / ")}` : null }
|
|
68
72
|
} catch (e) { return { error: e.message, refusal: `不可读(${e.message})` } } finally { db.close() }
|
|
@@ -72,7 +76,9 @@ function readRowsOf(file, missing = []) {
|
|
|
72
76
|
const db = openRead(file)
|
|
73
77
|
try { return readRows(db, missing) } finally { db.close() }
|
|
74
78
|
}
|
|
75
|
-
/**
|
|
79
|
+
/** 指针读数旗(§2.2 表 · §6.1 口径 5):迁入行属 `在途 / 待核销` 且 `task_book` 不可解析 / 指向档不存在 ⇒ 出旗;
|
|
80
|
+
* **读数非门**——不拦任何写入(该行迁入照旧、后续更新按 §6.1 本笔写入面判)。判面与写门同源·#834
|
|
81
|
+
* (共享解析单点 `resolveDeclaredRef`:① 仓根解析 → ② 声明源前缀解析序)。 */
|
|
76
82
|
function writeGateFlag(values, projectRoot) {
|
|
77
83
|
const tb = values[5]
|
|
78
84
|
if ((values[1] !== "在途" && values[1] !== "待核销") || tb == null || tb === "") return null
|
|
@@ -92,9 +98,9 @@ function fromKeyRefusal(fromKeys, dir) {
|
|
|
92
98
|
return null
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
/** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2
|
|
101
|
+
/** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2 同一子表达式(根推导单源 `resolveLedgerRoot(cwd)?.root` ∨ 兜底)——与实开库(`openLedger`)同源。 */
|
|
96
102
|
export function planLedgerMigration(cwd, { fromKeys = [] } = {}) {
|
|
97
|
-
const projectRoot =
|
|
103
|
+
const projectRoot = resolveLedgerRoot(cwd)?.root ?? resolve(cwd ?? ".")
|
|
98
104
|
const dir = ledgerDirPath()
|
|
99
105
|
const targetKey = ledgerKey(projectRoot)
|
|
100
106
|
const targetPath = join(dir, `${targetKey}.db`)
|
|
@@ -116,7 +122,7 @@ export function planLedgerMigration(cwd, { fromKeys = [] } = {}) {
|
|
|
116
122
|
return { projectRoot, targetKey, targetPath, targetDesc, sources, flags, migrate }
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
/** dry-run 计划行(零写报告:目标 / 变体源候选 / 计划动作 /
|
|
125
|
+
/** dry-run 计划行(零写报告:目标 / 变体源候选 / 计划动作 / 指针读数旗(非门))。 */
|
|
120
126
|
function printPlan(plan, out) {
|
|
121
127
|
const t = plan.targetDesc
|
|
122
128
|
const dist = (d) => (d.dist ? Object.entries(d.dist).map(([k, v]) => `${k} ${v}`).join(" · ") : `unreadable: ${d.error}`)
|
|
@@ -127,13 +133,13 @@ function printPlan(plan, out) {
|
|
|
127
133
|
`Variant sources (${plan.sources.length}):${plan.sources.length ? "" : " none(无待迁源)"}`,
|
|
128
134
|
...plan.sources.map((s) => ` ${s.key} ${s.path} rows ${s.desc.rows ?? "?"} mtime ${s.mtime}\n ${dist(s.desc)}${s.refusal ? ` ⚠ ${s.refusal}(confirm 面将拒跑)` : ""}`),
|
|
129
135
|
`Plan: backup → ${join(ledgerBackupRoot(), "<批次>")}/ ; migrate ${plan.migrate} rows → target (post-migration count = ${t.error ? "?" : (t.rows ?? 0) + plan.migrate})`,
|
|
130
|
-
`
|
|
136
|
+
`Pointer reading flags: ${plan.flags.length}${plan.flags.length ? "(读数非门——不拦任何写入:行照迁,后续更新按本笔写入面判)" : ""}`,
|
|
131
137
|
...plan.flags.map((f) => ` ⚠ ${f.source} #${f.id}: ${f.flag}`),
|
|
132
138
|
'Run "thincoder ledger migrate --confirm" to execute (backup first, then single-transaction import, then sources are recycled into ledger-trash).',
|
|
133
139
|
].join("\n"))
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
/** 事务内读回核验(§2.2 步 3):逐插入行按新 id 回读
|
|
142
|
+
/** 事务内读回核验(§2.2 步 3):逐插入行按新 id 回读 `DATA_COLUMNS` 全列与源等值 ∧ 目标既有行零动;不等 ⇒ throw(ROLLBACK ⇒ 目标零行变、源零动——新建档可能残留)。 */
|
|
137
143
|
function verifyImported(db, inserted, existingById) {
|
|
138
144
|
const get = db.prepare(`SELECT ${DATA_COLUMNS.join(", ")} FROM items WHERE id = ?`)
|
|
139
145
|
for (const { key, id, values } of inserted) {
|
package/ledger-read.mjs
CHANGED
|
@@ -10,19 +10,18 @@
|
|
|
10
10
|
* (列集单源 = `DATA_COLUMNS`——D-10 直引,不另立副本;`evidence` 仅 full 入行);缺列(旧库)按
|
|
11
11
|
* null 归一;`schemaVersion` = 库 `PRAGMA user_version` 如实回读(旧库未标 = 0);库不在 / 非台账库
|
|
12
12
|
* ⇒ 项目不出项(空集)。
|
|
13
|
-
* 范围(§2.4):单范围 =
|
|
14
|
-
*
|
|
13
|
+
* 范围(§2.4):单范围 = 推导所得项目根一项(`resolveLedgerRoot(cwd)`——与库键同源;无根 ⇒ 空集);
|
|
14
|
+
* `--family` = `discoverFamily` 发现序逐项(不可读跳过——既有语义)。
|
|
15
15
|
*
|
|
16
16
|
* W8 契约②:本档静态入 `ledger-db.mjs`(node:sqlite)⇒ 消费侧一律**动态 import** 本档。
|
|
17
17
|
* D-11:`ledger.mjs` 零编辑——族发现只 import `discoverFamily`。
|
|
18
18
|
*/
|
|
19
|
-
import { basename
|
|
19
|
+
import { basename } from "node:path"
|
|
20
20
|
|
|
21
21
|
import { ledgerQuery } from "./ledger-cmd.mjs"
|
|
22
22
|
import { discoverFamily } from "./ledger.mjs"
|
|
23
23
|
import { DATA_COLUMNS } from "./ledger-migrate.mjs"
|
|
24
|
-
import {
|
|
25
|
-
import { openLedger } from "./ledger-db.mjs"
|
|
24
|
+
import { openLedger, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
26
25
|
|
|
27
26
|
/** 行出形(§3.2):`id` + 数据列序(`evidence` 仅 full);缺列按 null 归一。 */
|
|
28
27
|
function exportRow(r, full) {
|
|
@@ -50,8 +49,8 @@ function readProject(root, full) {
|
|
|
50
49
|
export function ledgerExport({ cwd, family = false, full = false } = {}) {
|
|
51
50
|
const anchor = cwd ?? process.cwd()
|
|
52
51
|
if (!family) {
|
|
53
|
-
const
|
|
54
|
-
const item = readProject(root, full)
|
|
52
|
+
const derived = resolveLedgerRoot(anchor)
|
|
53
|
+
const item = derived ? readProject(derived.root, full) : null
|
|
55
54
|
return { projects: item ? [item] : [] }
|
|
56
55
|
}
|
|
57
56
|
const projects = []
|