@thincoder/core 0.10.6 → 0.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -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 +11 -6
- package/agent/helpers.mjs +6 -4
- package/agent/image-heal.mjs +65 -0
- package/agent/light-round.mjs +4 -4
- package/agent/live-beat.mjs +4 -4
- package/agent/record-results.mjs +9 -1
- package/agent/run-stages.mjs +2 -1
- package/agent/setup-reminders.mjs +14 -13
- package/agent/setup.mjs +62 -46
- 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 +1 -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 +1 -1
- package/agent-tools/batch-paths.mjs +20 -20
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +44 -5
- package/agent-tools/doc-check.mjs +37 -0
- package/agent-tools/eng.mjs +5 -3
- package/agent-tools/manifest.mjs +25 -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 +8 -6
- package/agent-tools/ssh-session.mjs +439 -0
- package/agent-tools/ssh-transport.mjs +311 -0
- package/agent-tools/subagent-scheduler.mjs +6 -5
- package/agent-tools/subagent.mjs +7 -5
- package/agent-tools/task.mjs +1 -1
- package/agent-tools/timer.mjs +6 -0
- 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-io.mjs +7 -2
- package/config-watch.mjs +2 -2
- package/config.mjs +7 -0
- package/conventions.mjs +1 -1
- package/declaration.mjs +157 -13
- package/doc-check/anchors.mjs +100 -0
- package/doc-check/index.mjs +61 -0
- package/doc-check/width.mjs +131 -0
- package/file-refs.mjs +6 -6
- package/generate-title.mjs +1 -1
- package/hooks.mjs +1 -1
- package/i18n.mjs +11 -1
- package/ledger-cmd.mjs +4 -5
- package/ledger-db.mjs +100 -21
- package/ledger-migrate.mjs +3 -3
- package/ledger-read.mjs +6 -7
- package/ledger-tools.mjs +24 -9
- package/ledger.mjs +23 -54
- package/log.mjs +17 -3
- package/manifest-discovery.mjs +21 -18
- package/manifest-schema.mjs +2 -13
- package/manifest.mjs +37 -22
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +2 -2
- package/memory/code-search.mjs +21 -4
- package/memory/code-sync.mjs +7 -4
- package/memory/core.mjs +1 -1
- package/memory/docs.mjs +23 -6
- package/memory/file-list.mjs +13 -7
- package/memory/file-walk.mjs +9 -4
- package/memory/scan.mjs +1 -1
- package/memory/sweep.mjs +1 -1
- package/memory/sync-tail.mjs +11 -7
- package/model-specs.mjs +7 -7
- package/notify-policy.mjs +6 -6
- package/package.json +2 -1
- package/permission.mjs +14 -0
- package/prompts/advisor-design.md +43 -13
- package/prompts/advisor-round1.md +25 -5
- package/prompts/advisor-round2.md +17 -4
- package/prompts/advisor-round3.md +17 -4
- package/prompts/common.md +60 -6
- package/prompts/consult-base.md +8 -2
- package/prompts/discipline-engineering.md +11 -10
- package/prompts/discipline-normal.md +22 -14
- package/prompts/persona-coder.md +1 -1
- package/prompts/persona-eng-designer.md +8 -3
- package/prompts/persona-engineering.md +21 -5
- package/prompts/persona-normal.md +4 -1
- package/provider/errors.mjs +8 -0
- package/provider/google.mjs +3 -0
- package/provider/normalize.mjs +42 -12
- package/provider/responses-request.mjs +1 -1
- 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 +1 -1
- 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 +61 -4
- package/think-off.mjs +3 -3
- package/token-ttl.mjs +1 -1
- package/tool-docs/bash.md +1 -1
- package/tool-docs/browser.md +1 -1
- package/tool-docs/doc_check.md +31 -0
- package/tool-docs/manifest.md +14 -2
- package/tool-docs/ssh.md +34 -0
- package/tool-docs/subagent.md +2 -0
- package/tool-docs/timer.md +2 -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 +92 -0
- package/vision-reader.mjs +4 -4
package/ledger-cmd.mjs
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* 消费侧静态 import 会破 W8 契约②)。
|
|
12
12
|
*/
|
|
13
13
|
import { resolve } from "node:path"
|
|
14
|
-
import { ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES,
|
|
15
|
-
import { resolveProjectRoot } from "./manifest.mjs"
|
|
14
|
+
import { AGING_DAYS, ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
16
15
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
17
16
|
|
|
18
17
|
/** 逐行老化判真(LEDGER.md §13.4 · 2026-10-07 批 ledger-tool):未决四态(`PENDING_STATUSES`)∧
|
|
@@ -60,8 +59,8 @@ export function ledgerCount({ cwd } = {}) {
|
|
|
60
59
|
* (首个 `§` 前子串,trim)须经基准 `resolve(base, …)` 指向存在的档;缺文件部分(`§2` / 空串 /
|
|
61
60
|
* 全空白)⇒ 拒;不在册 / 非文件 ⇒ 拒(throw,行不变)。
|
|
62
61
|
* 判位与迁移表判同层(落盘前);文案前缀 = 调用函数名(与同函数既有两条文案同款)。
|
|
63
|
-
* 基准 `base` =
|
|
64
|
-
* resolve(cwd ?? ".")
|
|
62
|
+
* 基准 `base` = **与台账库关联键同源**(设计档 §6.1 口径 2——根推导单源):`resolveLedgerRoot(cwd)?.root
|
|
63
|
+
* ?? resolve(cwd ?? ".")`——同一子表达式见 `ledger-db.mjs` `ledgerDbPath`;容器根会话(锚 cwd 下唯一注册
|
|
65
64
|
* 子仓 P)与子仓会话 ⇒ 库键与指针基准同取 P(同库同基准;原实现 `resolve(cwd, …)` = 原始 cwd 会误拒)。
|
|
66
65
|
* #832 ∥ #834:声明源前缀形同判——判定单源 = `declaration.mjs` `resolveDeclaredRef`(① 仓根解析 →
|
|
67
66
|
* ② 声明源前缀解析序;同名多仓 ⇒ 声明序首者)——**与迁移旗(`ledger-migrate.mjs` `writeGateFlag`)
|
|
@@ -71,7 +70,7 @@ function assertTaskBookGate(cwd, fnName, status, taskBook) {
|
|
|
71
70
|
if (taskBook == null) throw new Error(`${fnName}:task_book 必填(在途 / 待核销 须携任务书指针)`)
|
|
72
71
|
const part = String(taskBook).split("§")[0].trim()
|
|
73
72
|
if (!part) throw new Error(`${fnName}:task_book 不可解析(缺文件部分):${taskBook}`)
|
|
74
|
-
const base =
|
|
73
|
+
const base = resolveLedgerRoot(cwd)?.root ?? resolve(cwd ?? ".")
|
|
75
74
|
const { ok, abs } = resolveDeclaredRef(base, part)
|
|
76
75
|
if (!ok) throw new Error(`${fnName}:task_book 指向的档不存在:${taskBook}(解析 = ${abs})`)
|
|
77
76
|
}
|
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 集;已核销 / 已废弃 = 归档态)。 */
|
|
@@ -135,25 +213,26 @@ function markSchemaVersion(db) {
|
|
|
135
213
|
if (v < LEDGER_SCHEMA_VERSION) db.exec(`PRAGMA user_version = ${LEDGER_SCHEMA_VERSION}`)
|
|
136
214
|
}
|
|
137
215
|
|
|
138
|
-
/** 开库(cwd =
|
|
139
|
-
*
|
|
140
|
-
*
|
|
216
|
+
/** 开库(cwd = 「动作目标路径」)→ DatabaseSync 句柄 + 幂等建表 + 老库幂等迁移(executor 列)。
|
|
217
|
+
* **根推导**(设计档 §2.1 / §3.8):项目根 = `resolveLedgerRoot(cwd)`——读写同一判据;嵌套取近者、
|
|
218
|
+
* 容器锚向下收敛。**无根(`null`)**:写面(`create:true`)抛(fail-loud、零落盘零建库);读面空账
|
|
219
|
+
* (`null`——与「库不在 = 空账」同形)。
|
|
220
|
+
* 读面(create=false):库文件不存在 → 返回 null(读面不建库、无副作用)。
|
|
221
|
+
* 写面(create=true):库文件不存在自动建(台账目录随之创建)。
|
|
141
222
|
* **只读态**(readOnly=true——读面专用):只读句柄 + 零 DDL / ALTER + items 探针(见 openReadOnly);
|
|
142
223
|
* 如实副作用:旧库(缺 executor 列)**读**不再自动补列——补列归写面开库;读出行缺列按 null 归一。
|
|
143
|
-
*
|
|
144
|
-
* 零写——不静默回退锚;族锚「项目不可解析」,AC-M2-17);兜底 `resolve(cwd ?? ".")` 只属 none 态。 */
|
|
224
|
+
* 「目录不存在」的真错照抛(fail-loud——先于推导,文案逐字零改)。 */
|
|
145
225
|
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}`)
|
|
226
|
+
const start = resolve(cwd ?? ".")
|
|
227
|
+
if (!existsSync(start)) {
|
|
228
|
+
throw new Error(`项目目录不存在:${start}——cwd 请给存在的项目根(相对路径按当前工作目录解析;缺省 = 会话项目根)`)
|
|
152
229
|
}
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
throw new Error(
|
|
230
|
+
const derived = resolveLedgerRoot(start)
|
|
231
|
+
if (!derived) {
|
|
232
|
+
if (create) throw new Error(noRootMessage(start)) // 写面抛——零落盘零建库
|
|
233
|
+
return null // 读面空账(未注册目录下旧行为亦为空 ⇒ 零回归)
|
|
156
234
|
}
|
|
235
|
+
const file = keyedDbFile(derived.root)
|
|
157
236
|
if (!existsSync(file) && !create) return null
|
|
158
237
|
if (readOnly) return openReadOnly(file)
|
|
159
238
|
try { mkdirSync(ledgerDir, { recursive: true }) } catch { /* 已存在 / 并发建目录竞争——忽略 */ }
|
package/ledger-migrate.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { basename, dirname, join, resolve } from "node:path"
|
|
|
12
12
|
import { DatabaseSync } from "node:sqlite"
|
|
13
13
|
|
|
14
14
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
15
|
-
import { ledgerDirPath, ledgerKey, openLedger } from "./ledger-db.mjs"
|
|
15
|
+
import { ledgerDirPath, ledgerKey, openLedger, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
16
16
|
import { resolveProjectRoot } from "./manifest.mjs"
|
|
17
17
|
|
|
18
18
|
/** 12 数据列(**等值比对列集**——不含 id:id 重发,§2.2 语义保全 / AC-M2-12)。 */
|
|
@@ -92,9 +92,9 @@ function fromKeyRefusal(fromKeys, dir) {
|
|
|
92
92
|
return null
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2
|
|
95
|
+
/** 迁移计划(dry-run / confirm 共用)。cwd 口径 = 写门口径 2 同一子表达式(根推导单源 `resolveLedgerRoot(cwd)?.root` ∨ 兜底)——与实开库(`openLedger`)同源。 */
|
|
96
96
|
export function planLedgerMigration(cwd, { fromKeys = [] } = {}) {
|
|
97
|
-
const projectRoot =
|
|
97
|
+
const projectRoot = resolveLedgerRoot(cwd)?.root ?? resolve(cwd ?? ".")
|
|
98
98
|
const dir = ledgerDirPath()
|
|
99
99
|
const targetKey = ledgerKey(projectRoot)
|
|
100
100
|
const targetPath = join(dir, `${targetKey}.db`)
|
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 = []
|
package/ledger-tools.mjs
CHANGED
|
@@ -11,11 +11,20 @@
|
|
|
11
11
|
* 消费侧一律动态 import(`ledger-db.mjs` 静态 import node:sqlite ⇒ W8 契约②)。
|
|
12
12
|
*/
|
|
13
13
|
import { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
|
|
14
|
+
import { resolveLedgerRoot } from "./ledger-db.mjs"
|
|
14
15
|
import { DESC } from "./tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/ledger.md(DESC 单解析面)
|
|
15
16
|
|
|
16
|
-
/** 工具 cwd 供值面(缺省 →
|
|
17
|
+
/** 工具 cwd 供值面(缺省 → 当前会话锚——「动作目标路径」;项目根由核 `resolveLedgerRoot` 推导)。 */
|
|
17
18
|
const cwdOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd ?? process.cwd()
|
|
18
19
|
|
|
20
|
+
/** 根推导**报明面**(设计档 §3.8「报明形态」):向下发现(`basis === 'descendant'`——容器锚收敛序)⇒
|
|
21
|
+
* 对象形回执增 `root` 键;祖先支(自明路径)⇒ `null`(展开零键——回执逐字零改)。供值面 = 核同一
|
|
22
|
+
* 单源 `resolveLedgerRoot`(工具层零自算根;单实现、多调用点)。 */
|
|
23
|
+
const reportedRoot = (cwd) => {
|
|
24
|
+
const derived = resolveLedgerRoot(cwd)
|
|
25
|
+
return derived && derived.basis === "descendant" ? { root: derived.root } : null
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
// ── 工具层参数守卫(设计档 §3.2 · 2026-09-27 快车道修复 · Gitee #IKIQGK / 台账 #472) ──
|
|
20
29
|
|
|
21
30
|
/** 值预览(P1–P5 文案的 `<预览>`——设计档 §3.2):`JSON.stringify`,超 80 字符截断加 `…`
|
|
@@ -80,7 +89,7 @@ const FULL_ACTIONS = ["add", "update", "close", "query", "count"]
|
|
|
80
89
|
const READ_ACTIONS = ["query", "count"]
|
|
81
90
|
|
|
82
91
|
/** cwd 参数描述(五前身声明同文——文本单点)。 */
|
|
83
|
-
const CWD_DESC = "
|
|
92
|
+
const CWD_DESC = "本次动作的目标路径——项目根由它推导:沿祖先链最近的项目目录(带 PROJECT-MANIFEST.json 或已有台账库);容器目录下收敛到子项目。台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话锚"
|
|
84
93
|
|
|
85
94
|
/** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。 */
|
|
86
95
|
const ACTION_SPECS = {
|
|
@@ -207,25 +216,31 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
|
|
|
207
216
|
switch (action) {
|
|
208
217
|
case "add": {
|
|
209
218
|
const { cwd, ...row } = rest
|
|
210
|
-
const
|
|
211
|
-
|
|
219
|
+
const target = cwd ?? cwdOf(ctx)
|
|
220
|
+
const id = ledgerAdd({ cwd: target, row })
|
|
221
|
+
return JSON.stringify({ id, status: "待讨论", ...reportedRoot(target) })
|
|
212
222
|
}
|
|
213
223
|
case "update": {
|
|
214
224
|
const { cwd, id, ...patch } = rest
|
|
225
|
+
const target = cwd ?? cwdOf(ctx)
|
|
215
226
|
let executorSessionId
|
|
216
227
|
if (patch.executor == null) {
|
|
217
228
|
// 显式 `null` ≡ 略去(§3.1 `null` 口径 · #474——与缺省同分支);动态 import——零新静态边(K-LX1)
|
|
218
229
|
const { getSessionId } = await import("./session-slots.mjs")
|
|
219
230
|
executorSessionId = getSessionId()
|
|
220
231
|
}
|
|
221
|
-
return JSON.stringify(ledgerUpdate({ cwd:
|
|
232
|
+
return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId }), ...reportedRoot(target) })
|
|
233
|
+
}
|
|
234
|
+
case "close": {
|
|
235
|
+
const target = rest.cwd ?? cwdOf(ctx)
|
|
236
|
+
return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
|
|
222
237
|
}
|
|
223
|
-
case "close":
|
|
224
|
-
return JSON.stringify(ledgerClose({ cwd: rest.cwd ?? cwdOf(ctx), id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }))
|
|
225
238
|
case "query":
|
|
226
239
|
return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger }), null, 2)
|
|
227
|
-
case "count":
|
|
228
|
-
|
|
240
|
+
case "count": {
|
|
241
|
+
const target = rest.cwd ?? cwdOf(ctx)
|
|
242
|
+
return JSON.stringify({ count: ledgerCount({ cwd: target }), ...reportedRoot(target) })
|
|
243
|
+
}
|
|
229
244
|
default:
|
|
230
245
|
// 不可达(② 枚举守门已先拒)——fail-loud 兜底(防未来变体增量 drift)
|
|
231
246
|
throw new Error(`ledger:action 非法:${argPreview(action)}(取值 ∈ ${enumText(actions)})`)
|
package/ledger.mjs
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* W8 契约②:ledger-db.mjs 静态 import node:sqlite ⇒ 消费侧一律**动态 import** 本档(禁止静态 import)。
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { projectRootView } from "./manifest.mjs"
|
|
21
|
-
import { ledgerDbPath, PENDING_STATUSES, AGING_DAYS } from "./ledger-db.mjs"
|
|
18
|
+
import { basename, dirname, resolve } from "node:path"
|
|
19
|
+
import { AGING_DAYS, PENDING_STATUSES, ledgerChildren, ledgerDbPath, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
22
20
|
import { ledgerQuery } from "./ledger-cmd.mjs"
|
|
23
21
|
import { executorTail } from "./ledger-executors.mjs"
|
|
24
22
|
|
|
@@ -45,55 +43,20 @@ export function entryTitle(text) {
|
|
|
45
43
|
return norm.length > 20 ? norm.slice(0, 20) + "…" : norm
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* 标记 = 用户数据目录里以该项目路径为键的台账库——项目目录内不落任何标记文件)。 */
|
|
46
|
+
/** 向上(含自身)最近的**已注册**项目根 → {root, ledger} / null(F4——CLI 锚 = cwd)。
|
|
47
|
+
* 薄委托 `resolveLedgerRoot(anchor, { descend: false })`(设计档 §3.8——**取祖先支**):`root` = 推导所
|
|
48
|
+
* 得项目根(非锚);容器 ∥ 工作区锚 ⇒ `null` ⇒ 族发现按容器形(族求和——§7.2 / AC-M2-18 / AC-M2-19)。 */
|
|
52
49
|
export function findProject(anchor) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const ledger = ledgerDbPath(dir)
|
|
56
|
-
if (isFile(ledger)) return { root: dir, ledger }
|
|
57
|
-
const parent = dirname(dir)
|
|
58
|
-
if (parent === dir) return null
|
|
59
|
-
dir = parent
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** 同级枚举上限(N2 成本有界:父目录超此规模 = 缓存 / 临时等非仓族形态 → 退化空集,只显当前项目)。 */
|
|
64
|
-
export const MAX_SIBLING_SCAN = 100
|
|
65
|
-
|
|
66
|
-
/** 目录下已注册台账的直接子目录(目录名升序;不可读 → [];**惰性枚举 + 上限**——大目录零全量扫描)。
|
|
67
|
-
* 导出面 = 族发现(本档)∥ 轻通道闸候选枚举(`agent/light-round.mjs`——同级判据单源)。 */
|
|
68
|
-
export function ledgerChildren(dir) {
|
|
69
|
-
const out = []
|
|
70
|
-
let handle
|
|
71
|
-
try { handle = opendirSync(dir) } catch { return out }
|
|
72
|
-
try {
|
|
73
|
-
let n = 0
|
|
74
|
-
for (;;) {
|
|
75
|
-
const ent = handle.readSync()
|
|
76
|
-
if (!ent) break
|
|
77
|
-
if (++n > MAX_SIBLING_SCAN) return []
|
|
78
|
-
if (!ent.isDirectory()) continue
|
|
79
|
-
const root = join(dir, ent.name)
|
|
80
|
-
const ledger = ledgerDbPath(root)
|
|
81
|
-
if (isFile(ledger)) out.push({ root, ledger })
|
|
82
|
-
}
|
|
83
|
-
} catch { /* 读中断 → 已得子集(尽力而为) */ }
|
|
84
|
-
finally { try { handle.closeSync() } catch { /* 已关 */ } }
|
|
85
|
-
return out.sort((a, b) => (a.root < b.root ? -1 : a.root > b.root ? 1 : 0))
|
|
50
|
+
const derived = resolveLedgerRoot(anchor, { descend: false })
|
|
51
|
+
return derived ? { root: derived.root, ledger: ledgerDbPath(derived.root) } : null
|
|
86
52
|
}
|
|
87
53
|
|
|
88
|
-
/** 项目族发现:{current, projects}——
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* 按容器形落子目录族(需求:打开容器根 ⇒ 族内合计——标记范围批 §1 原话)。 */
|
|
54
|
+
/** 项目族发现:{current, projects}——current = `findProject`(**祖先支**:容器 ∥ 工作区锚 ⇒ `null` ⇒ 按
|
|
55
|
+
* 容器形落子目录族);projects = current + 其**同级已注册**目录(**发现序:current 在前**,其余按目录
|
|
56
|
+
* 名升序);current 缺(容器目录,K6)→ **锚本地**:取锚自身**已注册**的直接子目录(不沿祖先链上找)。
|
|
57
|
+
* 「工作区根不充当项目」的判据单源 = `resolveLedgerRoot` 的库臂排除(§2.1)——本函数零自持判据。 */
|
|
93
58
|
export function discoverFamily(anchor) {
|
|
94
|
-
const
|
|
95
|
-
// 歧义根跳过(键控库存在 ≠ 可作项目——扫描面 `buildScan` 对歧义锚必拒;遮蔽则标记静默缺席)。
|
|
96
|
-
const current = hit && projectRootView(hit.root).state === "ambiguous" ? null : hit
|
|
59
|
+
const current = findProject(anchor)
|
|
97
60
|
if (current) {
|
|
98
61
|
const siblings = ledgerChildren(dirname(current.root)).filter((p) => p.root !== current.root)
|
|
99
62
|
return { current, projects: [current, ...siblings] }
|
|
@@ -105,10 +68,16 @@ export function discoverFamily(anchor) {
|
|
|
105
68
|
* agedKeys/agedTitles/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
|
|
106
69
|
* (updated_at ?? created_at 距今 > days;未知不计——零假阳降级,同 v1 行龄未知口径)。
|
|
107
70
|
* `now`/`days` = 注入面(确定性用例——替代 v1 ageOf git 注入面)。
|
|
71
|
+
* **根推导**(设计档 §3.8):`root` / `name` / `ledger` = `resolveLedgerRoot(anchor)` 推导所得项目根(非锚
|
|
72
|
+
* ——容器 ∥ 工作区锚取收敛序之根)——与行集读取同一单源;无根 ⇒ `ledger` = `null`(不落兜底根——
|
|
73
|
+
* 该态下 `root` / `name` 回落锚、行集为空,且该 scan 不进族枚举(`discoverFamily` 容器支只列已注册子目录))。
|
|
108
74
|
* `inflightExecutors`(F-LX1 · sync 零变——K-LX2):在途且 executor 非空行集原样携带
|
|
109
75
|
* {executor, updated_at, created_at}——判活解析由 `resolveExecutorStates` async 单源负责,本函数零探测。 */
|
|
110
76
|
export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
111
|
-
const
|
|
77
|
+
const anchor = resolve(cwd)
|
|
78
|
+
const derived = resolveLedgerRoot(anchor)
|
|
79
|
+
const root = derived ? derived.root : anchor
|
|
80
|
+
const rows = ledgerQuery({ cwd: anchor })
|
|
112
81
|
const pending = rows.filter((r) => PENDING_STATUSES.includes(r.status))
|
|
113
82
|
const poolEntries = pending.filter((r) => r.kind === "requirement")
|
|
114
83
|
const techEntries = pending.filter((r) => r.kind === "tech_todo")
|
|
@@ -123,12 +92,11 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
|
123
92
|
if (key) boards.set(key, (boards.get(key) ?? 0) + 1)
|
|
124
93
|
}
|
|
125
94
|
const thresholdReached = poolEntries.length >= THRESHOLD_POOL || [...boards.values()].some((n) => n >= THRESHOLD_BOARD)
|
|
126
|
-
const root = resolve(cwd)
|
|
127
95
|
const inflightExecutors = pending
|
|
128
96
|
.filter((r) => r.status === "在途" && r.executor)
|
|
129
97
|
.map((r) => ({ executor: r.executor, updated_at: r.updated_at ?? null, created_at: r.created_at ?? null }))
|
|
130
98
|
return {
|
|
131
|
-
name: basename(root), root, ledger: ledgerDbPath(root),
|
|
99
|
+
name: basename(root), root, ledger: derived ? ledgerDbPath(root) : null,
|
|
132
100
|
pool: poolEntries.length, tech: techEntries.length, aged: aged.length,
|
|
133
101
|
agedKeys: aged.map((r) => normalizeEntry(r.title)), agedTitles: aged.map((r) => entryTitle(r.title)),
|
|
134
102
|
thresholdReached, boards, actionable: aged.length > 0 || thresholdReached,
|
|
@@ -178,8 +146,9 @@ export function detailScans(scans, current) {
|
|
|
178
146
|
}
|
|
179
147
|
|
|
180
148
|
// ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
|
|
181
|
-
// ——2026-10-07 批 ledger-tool
|
|
182
|
-
|
|
149
|
+
// ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
|
|
150
|
+
// ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
|
|
151
|
+
export { AGING_DAYS, ALLOWED_MIGRATIONS, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
183
152
|
export { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
|
|
184
153
|
export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
|
|
185
154
|
export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
|
package/log.mjs
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* ① 字段名精确匹配黑名单(apiKey/designToken/password/secret/token,大小写不敏感;
|
|
17
17
|
* 另加防御性名称 authorization/proxy/proxyUri——防凭据型 URL/头字段泄漏——§2.5)
|
|
18
18
|
* → 整个字段丢弃;
|
|
19
|
-
* ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=…
|
|
19
|
+
* ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=… / token 值形等)→ 截断到形态之前
|
|
20
20
|
* ——宁可丢信息不漏密钥。
|
|
21
|
+
* token 值形(`uuid:epoch`——2026-10-14 design-token-recovery 批并入):design-slots
|
|
22
|
+
* reveal 结果经 `tool:done` head 落盘时不落值(与 batch.mjs 凭据第三形同判据)。
|
|
21
23
|
* 工具事件不记 args;URL 不入事件(llm/tool 事件从不携带 URL;err 文本经形态扫描)。
|
|
22
24
|
* - 摘要截断(B 方案,2026-09-03 用户裁定):截断处带 "…" 标记(截后仍 ≤上限)。
|
|
23
25
|
* - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
|
|
@@ -37,8 +39,8 @@ import { join } from "node:path"
|
|
|
37
39
|
export const MAX_LINE = 512
|
|
38
40
|
/** 字段名精确黑名单(NF-L3/§2.4——2026-09-03 评审 refinement #7:与 §2.4 词表一致含 token) */
|
|
39
41
|
const BLACKLIST_FIELDS = new Set(["apikey", "designtoken", "password", "secret", "token", "authorization", "proxyuri", "proxy"])
|
|
40
|
-
/**
|
|
41
|
-
const SECRET_FORM = /(\bsk-[A-Za-z0-9_-]{6,}|\bBearer\s+[A-Za-z0-9._~+/=-]{6,}|\b(?:api[_-]?key|key|token|secret|password|pwd|passwd)\s*[=:]\s*['"]?[A-Za-z0-9._~+/=-]{6,})/i
|
|
42
|
+
/** 内容密钥形态扫描(大小写不敏感)——命中即截断到形态前(含 token 值形 `uuid:epoch`——同 batch.mjs 凭据第三形判据) */
|
|
43
|
+
const SECRET_FORM = /(\bsk-[A-Za-z0-9_-]{6,}|\bBearer\s+[A-Za-z0-9._~+/=-]{6,}|\b(?:api[_-]?key|key|token|secret|password|pwd|passwd)\s*[=:]\s*['"]?[A-Za-z0-9._~+/=-]{6,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:\d{10,16})/i
|
|
42
44
|
/** 字符串字段长度兜底:head ≤300 / err ≤200 / 其余 ≤120 */
|
|
43
45
|
const FIELD_CAPS = { head: 300, err: 200 }
|
|
44
46
|
|
|
@@ -154,6 +156,18 @@ export function redactSecret(fieldKey, value) {
|
|
|
154
156
|
return hit ? s.slice(0, hit.index) + "[redacted]" : s
|
|
155
157
|
}
|
|
156
158
|
|
|
159
|
+
/** 结构面遮蔽(#1432——tool-errors args 落痕前过同一链):键级 ∥ 内容级复用 `redactSecret`;嵌套对象 ∥ 数组递归;
|
|
160
|
+
* 非串值仅在键名命中黑名单时整键遮蔽(否则原样 ∥ 递归)——「宁可丢信息不漏密钥」同判据。 */
|
|
161
|
+
export function redactArgs(value) {
|
|
162
|
+
if (Array.isArray(value)) return value.map(redactArgs)
|
|
163
|
+
if (value && typeof value === "object") {
|
|
164
|
+
const out = {}
|
|
165
|
+
for (const [k, v] of Object.entries(value)) out[k] = typeof v === "string" || BLACKLIST_FIELDS.has(String(k).toLowerCase()) ? redactSecret(k, v) : redactArgs(v)
|
|
166
|
+
return out
|
|
167
|
+
}
|
|
168
|
+
return typeof value === "string" ? redactSecret("", value) : value
|
|
169
|
+
}
|
|
170
|
+
|
|
157
171
|
/** 内容级净化:密钥形态截断到形态前(②);超长截断 + "…" 标记。 */
|
|
158
172
|
export function sanitizeString(key, value) {
|
|
159
173
|
let s = String(value)
|
package/manifest-discovery.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* manifest-discovery.mjs — **发现 ∕ 归属族**出档(2026-09-29 structure-split-2 · 台账 #620):自 `manifest.mjs`
|
|
3
|
-
*
|
|
3
|
+
* 全量迁出——`MANIFEST_REL` · 测试注入位(`_setProjectRootForTest` / `_resetProjectRootForTest`)·
|
|
4
4
|
* `scanChildren`(两条梯表共用 walk 内核)· `discoverProjects`(项目梯)· `discoverRepos`(仓梯)·
|
|
5
5
|
* `owningProject`(归属形)· `resolveProjectRoot`(归属 ∨ 发现);**结构拆分零语义**(面不变 ∕ 判据不变,
|
|
6
6
|
* 只换宿主档;切点 ∕ 缝单源 = 批档 `docs/batches/2026-09-29-structure-split-2.md` §2.2-C)。
|
|
@@ -41,13 +41,14 @@ function scanChildren(anchor, isHit) {
|
|
|
41
41
|
/**
|
|
42
42
|
* **项目发现**(项目梯五级——KD-M1-23 / 设计 `docs/core/design/MANIFEST.md` §2.2;**git 非前提**,
|
|
43
43
|
* 2026-09-21 用户裁定):① 锚自身带 `MANIFEST_REL` ⇒ 锚;② 锚含 `.git` ⇒ 锚(缺档 = 建档机会);
|
|
44
|
-
* ③ 直接子目录中**带档**者优先(非空即只看此级:恰一 ⇒ `unique` / ≥2 ⇒ `
|
|
45
|
-
*
|
|
44
|
+
* ③ 直接子目录中**带档**者优先(非空即只看此级:恰一 ⇒ `unique` / ≥2 ⇒ `workspace`——下辖多项目
|
|
45
|
+
* = 工作区常态,非错误态);④ 零带档才看含 `.git` 的**裸仓**(同判);⑤ 均无 ⇒ `none`(⇒ 建档流,
|
|
46
|
+
* 落点 = 会话锚)。
|
|
46
47
|
* `candidates` 一律**按名排序**(`self` / `none` ⇒ `[]`);`matched` ∈ `manifest` / `git` / `null`
|
|
47
|
-
*
|
|
48
|
+
* 记命中(或工作区)出自哪一级。**纯 fs**(只判存在性——不解析档内容)· **不抛** · **不向上** · **不递归**。
|
|
48
49
|
* **覆盖位**(`_setProjectRootForTest` 在场)⇒ **头部短路**(测试注入面——先于真判据,同 `discoverRepos`)。
|
|
49
50
|
* @param {string} [cwd] 会话锚(缺省 → 进程 cwd)
|
|
50
|
-
* @returns {{kind:'self'|'unique'|'none'|'
|
|
51
|
+
* @returns {{kind:'self'|'unique'|'none'|'workspace', root:string|null, candidates:string[], matched:'manifest'|'git'|null}}
|
|
51
52
|
*/
|
|
52
53
|
export function discoverProjects(cwd) {
|
|
53
54
|
if (_projectRootOverride) return { kind: "self", root: resolve(_projectRootOverride), candidates: [], matched: null }
|
|
@@ -55,10 +56,10 @@ export function discoverProjects(cwd) {
|
|
|
55
56
|
if (existsSync(join(anchor, MANIFEST_REL))) return { kind: "self", root: anchor, candidates: [], matched: "manifest" }
|
|
56
57
|
if (existsSync(join(anchor, ".git"))) return { kind: "self", root: anchor, candidates: [], matched: "git" }
|
|
57
58
|
const withManifest = scanChildren(anchor, (d) => existsSync(join(d, MANIFEST_REL)))
|
|
58
|
-
if (withManifest.length > 1) return { kind: "
|
|
59
|
+
if (withManifest.length > 1) return { kind: "workspace", root: null, candidates: withManifest, matched: "manifest" }
|
|
59
60
|
if (withManifest.length === 1) return { kind: "unique", root: withManifest[0], candidates: [...withManifest], matched: "manifest" }
|
|
60
61
|
const bareRepos = scanChildren(anchor, (d) => existsSync(join(d, ".git")))
|
|
61
|
-
if (bareRepos.length > 1) return { kind: "
|
|
62
|
+
if (bareRepos.length > 1) return { kind: "workspace", root: null, candidates: bareRepos, matched: "git" }
|
|
62
63
|
if (bareRepos.length === 1) return { kind: "unique", root: bareRepos[0], candidates: [...bareRepos], matched: "git" }
|
|
63
64
|
return { kind: "none", root: null, candidates: [], matched: null }
|
|
64
65
|
}
|
|
@@ -66,21 +67,22 @@ export function discoverProjects(cwd) {
|
|
|
66
67
|
/**
|
|
67
68
|
* **仓发现**(仓梯——KD-M1-23 / `docs/core/design/TOOLS.md` §6.13;**单源** KD-M1-22——`git` 工具
|
|
68
69
|
* 经此符号接线,禁第二份实现):① 锚含 `.git` ⇒ 锚;② 直接子目录中 `.git` **∧** 带档者恰一 ⇒ 命中
|
|
69
|
-
* (≥2 ⇒
|
|
70
|
+
* (≥2 ⇒ `workspace`——下辖多仓 = 常态);③ **零个此类时才看**含 `.git` 的裸仓(恰一 ⇒ 命中 /
|
|
71
|
+
* ≥2 ⇒ `workspace`);④ 均无 ⇒ `none`。
|
|
70
72
|
* 与 `discoverProjects` = **同一 walk 内核**(`scanChildren`)的两种梯表——差异只在谓词与级序。
|
|
71
73
|
* `candidates` 按名排序;`matched` 记档位;纯 fs / 不抛 / 不递归 / 不向上;覆盖位 ⇒ 头部短路。
|
|
72
74
|
* @param {string} [cwd] 会话锚(缺省 → 进程 cwd)
|
|
73
|
-
* @returns {{kind:'self'|'unique'|'none'|'
|
|
75
|
+
* @returns {{kind:'self'|'unique'|'none'|'workspace', root:string|null, candidates:string[], matched:'manifest'|'git'|null}}
|
|
74
76
|
*/
|
|
75
77
|
export function discoverRepos(cwd) {
|
|
76
78
|
if (_projectRootOverride) return { kind: "self", root: resolve(_projectRootOverride), candidates: [], matched: null }
|
|
77
79
|
const anchor = resolve(cwd ?? ".")
|
|
78
80
|
if (existsSync(join(anchor, ".git"))) return { kind: "self", root: anchor, candidates: [], matched: "git" }
|
|
79
81
|
const scoped = scanChildren(anchor, (d) => existsSync(join(d, ".git")) && existsSync(join(d, MANIFEST_REL)))
|
|
80
|
-
if (scoped.length > 1) return { kind: "
|
|
82
|
+
if (scoped.length > 1) return { kind: "workspace", root: null, candidates: scoped, matched: "manifest" }
|
|
81
83
|
if (scoped.length === 1) return { kind: "unique", root: scoped[0], candidates: [...scoped], matched: "manifest" }
|
|
82
84
|
const bare = scanChildren(anchor, (d) => existsSync(join(d, ".git")))
|
|
83
|
-
if (bare.length > 1) return { kind: "
|
|
85
|
+
if (bare.length > 1) return { kind: "workspace", root: null, candidates: bare, matched: "git" }
|
|
84
86
|
if (bare.length === 1) return { kind: "unique", root: bare[0], candidates: [...bare], matched: "git" }
|
|
85
87
|
return { kind: "none", root: null, candidates: [], matched: null }
|
|
86
88
|
}
|
|
@@ -107,23 +109,24 @@ export function owningProject(target) {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
112
|
+
/** **项目根解析的直读形**(判定单点——设计 `MANIFEST.md` §2.2 `projectRootView` 契约行 / §2.5
|
|
113
|
+
* 「单项目解析律与四处会话级缺省收口」条):`ok`(唯一解析:归属命中 ∥ 发现 `self` / `unique`)/
|
|
114
|
+
* `workspace`(锚自身无档 ∧ 下辖 **≥2** 项目 ⇒ `root` null + 下辖项目全列**按名排序**——工作区
|
|
115
|
+
* 常态,非错误态)/ `none`(无项目 ⇒ 双空);覆盖位在场 ⇒ 头部短路(`ok` + 覆盖值)。
|
|
116
|
+
* **非抛错 / 零写**;`resolveProjectRoot` 薄委托本函数(行为逐字零变)。
|
|
117
|
+
* @returns {{state:'ok'|'workspace'|'none', root:string|null, candidates:string[]}} */
|
|
115
118
|
export function projectRootView(cwd) {
|
|
116
119
|
const owning = owningProject(cwd)
|
|
117
120
|
if (owning) return { state: "ok", root: owning, candidates: [] }
|
|
118
121
|
const d = discoverProjects(cwd)
|
|
119
122
|
if (d.root) return { state: "ok", root: d.root, candidates: [] }
|
|
120
|
-
if (d.kind === "
|
|
123
|
+
if (d.kind === "workspace") return { state: "workspace", root: null, candidates: d.candidates }
|
|
121
124
|
return { state: "none", root: null, candidates: [] }
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/** 项目根解析(**归属 ∨ 发现**——KD-M1-30 / M1-24):**薄委托** `projectRootView(cwd).root`(判定
|
|
125
128
|
* 单点见上——#828)。带档路径与批前**逐字同**;变更面两条(设计 §2.2 同条):① 项目树内路径
|
|
126
|
-
* (祖先带档)⇒ 该项目根;② 裸仓恰一 ⇒ 该仓根(零档降级 = 建档机会)。`none` / `
|
|
129
|
+
* (祖先带档)⇒ 该项目根;② 裸仓恰一 ⇒ 该仓根(零档降级 = 建档机会)。`none` / `workspace` ⇒
|
|
127
130
|
* `null`。调用方零改(行为随语义变更——设计 §2.5 键面条 / 错层条)。
|
|
128
131
|
* @returns {string|null} 项目根绝对路径 / null */
|
|
129
132
|
export function resolveProjectRoot(cwd) {
|