@thincoder/core 0.10.3 → 0.10.5
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 +62 -0
- package/README.md +1 -1
- package/advisor/citations.mjs +29 -3
- package/advisor/compaction.mjs +31 -7
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +2 -1
- package/advisor/run.mjs +9 -5
- package/advisor/timeline.mjs +26 -0
- package/advisor.mjs +4 -2
- package/agent/assemble.mjs +1 -1
- package/agent/chat-call.mjs +1 -1
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +3 -1
- package/agent/digest-account.mjs +189 -0
- package/agent/dispatch-gates.mjs +8 -0
- package/agent/dispatch.mjs +39 -18
- package/agent/family-tools.mjs +27 -16
- package/agent/helpers.mjs +12 -3
- package/agent/light-round.mjs +66 -0
- package/agent/post-turn.mjs +8 -4
- package/agent/record-results.mjs +4 -0
- package/agent/run-stages.mjs +32 -7
- package/agent/run-start.mjs +50 -3
- package/agent/setup-reminders.mjs +1 -1
- package/agent/setup.mjs +18 -3
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +37 -12
- package/agent/timers.mjs +56 -5
- package/agent/turn-loop.mjs +30 -5
- package/agent/write-gate.mjs +20 -32
- package/agent-tools/advisor-async.mjs +15 -7
- package/agent-tools/advisor-settle.mjs +2 -2
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-discard.mjs +34 -0
- package/agent-tools/async-settle.mjs +32 -7
- package/agent-tools/audit-block.mjs +1 -1
- package/agent-tools/bash-async.mjs +1 -1
- package/agent-tools/batch-lifecycle.mjs +59 -17
- package/agent-tools/batch-paths.mjs +24 -4
- package/agent-tools/batch-skeleton.mjs +59 -43
- package/agent-tools/batch.mjs +39 -18
- package/agent-tools/browser-async.mjs +198 -0
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +232 -0
- package/agent-tools/design-token.mjs +20 -0
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/escalate-async.mjs +4 -2
- package/agent-tools/manifest.mjs +193 -0
- package/agent-tools/parent-channel.mjs +7 -4
- package/agent-tools/review-facts.mjs +143 -16
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/skill.mjs +1 -1
- package/agent-tools/subagent-actions-query.mjs +13 -6
- package/agent-tools/subagent-async.mjs +31 -15
- package/agent-tools/subagent-panel.mjs +2 -1
- package/agent-tools/subagent-scheduler.mjs +8 -5
- package/agent-tools/subagent-spawn.mjs +30 -56
- package/agent-tools/subagent.mjs +21 -8
- package/agent-tools/timer.mjs +186 -12
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent-tools.mjs +2 -1
- package/agent.mjs +2 -2
- package/attachments.mjs +1 -1
- package/config-io.mjs +18 -13
- package/config-migrate.mjs +13 -29
- package/config-presets.mjs +33 -29
- package/config.mjs +68 -25
- package/context-degrade.mjs +1 -1
- package/context-echo.mjs +1 -1
- package/context-push.mjs +2 -2
- package/context.mjs +1 -1
- package/conventions.mjs +28 -8
- package/declaration.mjs +18 -2
- package/escape.mjs +1 -1
- package/file-links.mjs +1 -1
- package/file-refs.mjs +1 -1
- package/history-window.mjs +1 -1
- package/i18n.mjs +7 -0
- package/ledger-cmd.mjs +62 -16
- package/ledger-db.mjs +6 -0
- package/ledger-surface.mjs +49 -5
- package/ledger-tools.mjs +158 -87
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +11 -18
- package/log.mjs +1 -1
- package/manifest-discovery.mjs +1 -1
- package/manifest-schema.mjs +7 -5
- package/manifest.mjs +5 -3
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +1 -1
- package/memory/code-index.mjs +1 -1
- package/memory/code-search.mjs +1 -1
- package/memory/code-sync.mjs +1 -1
- package/memory/docs.mjs +6 -2
- package/memory/file-list.mjs +1 -1
- package/memory/file-walk.mjs +17 -5
- package/memory/maintain.mjs +410 -0
- package/memory/memory-tool.mjs +21 -6
- package/memory/schema.mjs +19 -7
- package/memory/sweep.mjs +8 -5
- package/memory/sync-tail.mjs +16 -10
- package/model-ref.mjs +16 -13
- package/model-specs.mjs +21 -9
- package/package.json +1 -1
- package/peer-instances.mjs +1 -1
- package/permission.mjs +33 -0
- package/process-probe.mjs +2 -2
- package/prompts/advisor-design.md +4 -2
- package/prompts/advisor-round1.md +1 -1
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +38 -4
- package/prompts/consult-base.md +1 -1
- package/prompts/discipline-engineering.md +32 -15
- package/prompts/discipline-normal.md +46 -2
- package/prompts/persona-eng-designer.md +6 -6
- package/prompts/persona-engineering.md +18 -9
- package/prompts/persona-normal.md +2 -0
- package/prompts/subagent-base.md +3 -0
- package/provider/anthropic.mjs +14 -2
- package/provider/core.mjs +6 -0
- package/provider/errors.mjs +28 -3
- package/provider/google.mjs +22 -10
- package/provider/list-models.mjs +3 -2
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/provider/sse.mjs +3 -0
- package/provider-flows.mjs +14 -15
- package/proxy-chunked.mjs +102 -0
- package/proxy-target.mjs +54 -0
- package/proxy-transport.mjs +298 -0
- package/proxy.mjs +17 -248
- package/session-gc.mjs +2 -2
- package/session-lifecycle.mjs +18 -14
- package/session-slot-claims.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session-slots.mjs +3 -3
- package/shell-identity.mjs +107 -0
- package/team-ledger.mjs +282 -0
- package/team.mjs +223 -0
- package/tool-docs/advisor.md +1 -1
- package/tool-docs/bash.md +2 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +48 -0
- package/tool-docs/delete.md +7 -5
- package/tool-docs/ledger.md +6 -0
- package/tool-docs/manifest.md +16 -0
- package/tool-docs/memory.md +1 -0
- package/tool-docs/notify_parent.md +5 -0
- package/tool-docs/process.md +4 -4
- package/tool-docs/question.md +1 -0
- package/tool-docs/subagent.md +14 -3
- package/tool-docs/team_ledger.md +9 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/wait_for.md +1 -0
- package/tools/bash.mjs +9 -4
- package/tools/browser.mjs +161 -0
- package/tools/git.mjs +1 -1
- package/tools/index.mjs +4 -4
- package/tools/ops.mjs +26 -10
- package/tools/patch.mjs +8 -2
- package/tools/repomap.mjs +1 -1
- package/tools/write-path.mjs +8 -6
- package/traces/trace-cleanup.mjs +1 -1
- package/traces/trace-store.mjs +1 -1
- package/vision-reader.mjs +10 -16
- package/tool-docs/ledger_add.md +0 -1
- package/tool-docs/ledger_close.md +0 -1
- package/tool-docs/ledger_count.md +0 -1
- package/tool-docs/ledger_query.md +0 -1
- package/tool-docs/ledger_update.md +0 -1
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* non-stale code review marks _calledAdvisorThisRun; a passing design review
|
|
32
32
|
* issues its token under the designId slot at settle time.
|
|
33
33
|
* 2026-09-11 第 11 批拆分:settle 记账 + 变更日志 + 陈旧/冻结判定迁 `advisor-settle.mjs`
|
|
34
|
-
* (本文件 500
|
|
34
|
+
* (本文件 500 行——2026-09-11 拆分时);既有 import 面经本文件 re-export 保持不变。
|
|
35
35
|
*
|
|
36
36
|
* The sync path (depth>0 eng-coder self-review / explicit async:false) reuses
|
|
37
37
|
* the same instance resolution (rounds/prior/cap) — its accounting still lands
|
|
@@ -47,13 +47,13 @@ export {
|
|
|
47
47
|
// 第 11 批拆分(2026-09-11):settle 记账 + 变更日志(noteMutations)+ 陈旧判定
|
|
48
48
|
// (reviewIsStale)+ 冻结冲突(inflightDesignReviewConflict)迁 advisor-settle.mjs——
|
|
49
49
|
// 既有 import 面(dispatch / subagent-async / escalate-async / 测试)经此 re-export 不变。
|
|
50
|
-
import {
|
|
50
|
+
import { mutationSeqOf, settleAdvisorRun } from "./advisor-settle.mjs"
|
|
51
51
|
export {
|
|
52
52
|
mutationSeqOf, noteMutations, reviewIsStale, settleAdvisorRun, inflightDesignReviewConflict,
|
|
53
53
|
} from "./advisor-settle.mjs"
|
|
54
54
|
// 第 33 批(评审失败护栏):doc-set 键迁事实面档(实例续跑与陈旧判定同锚单源;原为私有——零 import 面)。
|
|
55
55
|
// 本文件继续在实例解析 / 池 entries 上消费它。
|
|
56
|
-
import { docSetKey } from "./review-facts.mjs"
|
|
56
|
+
import { docSetKey, resolveReviewDocPaths, scopeAdvisorMirror } from "./review-facts.mjs"
|
|
57
57
|
// F31(2026-09-18 顾问面治理批):拒回文案对象标识行单源。
|
|
58
58
|
import { withIdentityLine, scopeSummary } from "../advisor/notice.mjs"
|
|
59
59
|
import { runAdvisorReview, resolveAdvisorProvider, ADVISOR_THINKING_PLACEHOLDER } from "../advisor/run.mjs"
|
|
@@ -65,6 +65,8 @@ import { deathLine } from "../abort-provenance.mjs"
|
|
|
65
65
|
// ASYNC-RESULT-CONTAINER.md D3/D6:settle 公共收尾单点 + child signal 构建单点
|
|
66
66
|
import { bindChildController, buildChildSignal, carrierField, getAsyncPool, settleAsyncEntry, tombstoneOf, writeTombstone } from "./async-settle.mjs"
|
|
67
67
|
import { nextSubagentId, consumeSubagentToken, assertPoolKeyFree, entryTerminal } from "./subagent-scheduler.mjs"
|
|
68
|
+
// #934(零落笔看门狗——AGENT-LOOP-UPSTREAM.md §6.32.6 触发接线②):评审族推送单源(叶档——恒零抛)。
|
|
69
|
+
import { pushAdvisorAlert } from "./zero-write-watch.mjs"
|
|
68
70
|
|
|
69
71
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
72
|
// Review-instance registry (_advisorRuns — per-review rounds/prior/cap)
|
|
@@ -161,9 +163,9 @@ export function resolveAdvisorLaunch(agent, reviewType, { documents = null } = {
|
|
|
161
163
|
runs.set(reviewId, run)
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
|
-
// Scope the
|
|
165
|
-
|
|
166
|
-
agent
|
|
166
|
+
// Scope the mirror fields to THIS instance (message builder + run.mjs cap +
|
|
167
|
+
// displays; #949 ① — the queued-launch window is re-secured at entry.start).
|
|
168
|
+
scopeAdvisorMirror(agent, run)
|
|
167
169
|
return { run, isNew: run.round === 0 && !run.priorOutput, reviewId: run.reviewId, designId: run.designId }
|
|
168
170
|
}
|
|
169
171
|
|
|
@@ -390,8 +392,11 @@ export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
|
390
392
|
id, role: "advisor", reviewType, run,
|
|
391
393
|
reviewId: run.reviewId, designId, designToken, documents, paths, object,
|
|
392
394
|
launchSeq: mutationSeqOf(parent),
|
|
395
|
+
// docAbs 产出面同源归一(2026-10-04 · #921 · A 随件):四分支单源 resolved(裸形/前缀形
|
|
396
|
+
// 命中候选项目根即归一为绝对路径)——冻结窗/陈旧判定对**裸形受理档**照护成立(否则 A
|
|
397
|
+
// 开冻结窗保护洞);绝对形与旧 normAbs 面逐字等价。
|
|
393
398
|
docAbs: reviewType === "design" && Array.isArray(documents)
|
|
394
|
-
? documents
|
|
399
|
+
? [...resolveReviewDocPaths(documents, parent.cwd).resolved.values()]
|
|
395
400
|
: [],
|
|
396
401
|
relayPrefix: `advisor#${id}/`,
|
|
397
402
|
status: queued ? "queued" : "running", position: undefined,
|
|
@@ -420,9 +425,12 @@ export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
|
420
425
|
// Async mark + [model] — the TUI block opens at ACTUAL start (⏹ gating reads it).
|
|
421
426
|
ctx?.callbacks?.onToken?.(entry.relayPrefix + "⟦ev⟧async\x1e")
|
|
422
427
|
ctx?.callbacks?.onToken?.(entry.relayPrefix + "[model]" + (entry.model ?? ""))
|
|
428
|
+
// #949 ①:排队 / 延迟窗口封死——构建前按本评审实例复核镜像(消费点定域;幂等)。
|
|
429
|
+
scopeAdvisorMirror(parent, run)
|
|
423
430
|
runAdvisorReview(parent, reviewType, {
|
|
424
431
|
onOutput: (chunk) => relayAdvisorOutput(ctx?.callbacks, entry.relayPrefix, chunk),
|
|
425
432
|
signal: entry.controller.signal,
|
|
433
|
+
onStall: (rounds) => pushAdvisorAlert(parent, `advisor#${id}`, rounds), // #934:连续无产出越阈 ⇒ 自动上行提醒(只推——§6.32)
|
|
426
434
|
}, designToken, documents, paths, object, designId)
|
|
427
435
|
.then((report) => { entry.report = report })
|
|
428
436
|
// §6.12 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* advisor-settle.mjs — advisor settle accounting + mutation log + stale / freeze
|
|
3
|
-
* determination(2026-09-11 第 11 批自 advisor-async.mjs
|
|
4
|
-
*
|
|
3
|
+
* determination(2026-09-11 第 11 批自 advisor-async.mjs 拆出;
|
|
4
|
+
* 既有 import 面经 advisor-async.mjs **re-export 保持不变**
|
|
5
5
|
* (noteMutations / mutationSeqOf / reviewIsStale / settleAdvisorRun)。
|
|
6
6
|
*
|
|
7
7
|
* 内容 = 迁入(逐字搬移含注释)+ 本批两处接线:
|
package/agent-tools/advisor.mjs
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { runAdvisorReview, advisorIncompleteMarker, ADVISOR_LAUNCH_REFUSAL_PREFIX } from "../advisor/run.mjs"
|
|
11
11
|
import { resolveBatchDocPath } from "./batch.mjs"
|
|
12
|
-
// M6(模块设计 §2.1 F3):评审对象来源读 manifest docRoot
|
|
13
|
-
//
|
|
14
|
-
// normAbs
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { sep } from "node:path"
|
|
12
|
+
// M6(模块设计 §2.1 F3):评审对象来源读 manifest docRoot(声明面)——单一权威源 =
|
|
13
|
+
// review-facts.mjs 的 resolveReviewDocPaths(2026-10-04 · #921 · KD-2——分类四分支 + B 文案
|
|
14
|
+
// 诊断数据单源;write-gate 同名再出口保 normAbs 面)。不 import dispatch.mjs(簇间回边,环风险)。
|
|
15
|
+
import { normAbs } from "../agent/write-gate.mjs"
|
|
16
|
+
import { resolveReviewDocPaths } from "./review-facts.mjs"
|
|
18
17
|
import {
|
|
19
18
|
generateDesignToken,
|
|
20
19
|
settleDesignReview,
|
|
@@ -63,11 +62,11 @@ export const advisorTool = {
|
|
|
63
62
|
documents: {
|
|
64
63
|
type: "array",
|
|
65
64
|
items: { type: "string" },
|
|
66
|
-
description: "Doc paths to review (design / requirements / referenced docs). The advisor reviews ONLY these — no git diff scan. Use for both review types to pass the task's docs list.",
|
|
65
|
+
description: "Doc paths to review (design / requirements / referenced docs). Relative paths resolve against the session cwd first, then candidate project roots (project-root-relative e.g. <repo>/docs/…, or absolute). The advisor reviews ONLY these — no git diff scan. Use for both review types to pass the task's docs list.",
|
|
67
66
|
},
|
|
68
67
|
batchDoc: {
|
|
69
68
|
type: "string",
|
|
70
|
-
description: "Design review only: the in-flight batch record path — validated whenever passed (unreadable ⇒ refused, not ignored); the reviewer then also gets the `batch` write channel to record its findings + verdict. Omit when no batch record is in flight (no write channel — zero regression).",
|
|
69
|
+
description: "Design review only: the in-flight batch record path — validated whenever passed (unreadable ⇒ refused, not ignored); the reviewer then also gets the `batch` write channel to record its findings + verdict. Relative paths resolve against the session cwd first, then candidate project roots (project-root-relative e.g. <repo>/docs/batches/…, or absolute). Omit when no batch record is in flight (no write channel — zero regression).",
|
|
71
70
|
},
|
|
72
71
|
},
|
|
73
72
|
required: ["type"],
|
|
@@ -112,34 +111,36 @@ export const advisorTool = {
|
|
|
112
111
|
)
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
// Design review: the review scope must be documentation files. Classification
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
114
|
+
// Design review: the review scope must be documentation files. Classification =
|
|
115
|
+
// resolveReviewDocPaths (2026-10-04 · #921 · the four-leg single authority in
|
|
116
|
+
// review-facts.mjs): legs ① session-root set / ② owning-project root set keep the
|
|
117
|
+
// legacy behavior verbatim; leg ③ probes candidate project roots for bare
|
|
118
|
+
// project-root-relative forms; leg ④ disambiguates by readable-file uniqueness
|
|
119
|
+
// (fail-closed — criterion=scope-doc-ambiguous when ≥2 candidates are readable).
|
|
119
120
|
if (reviewType === "design" && documents) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
// 的 docRoot 判定;无主档文档回退会话根集判定(原行为零变)。
|
|
123
|
-
const ownerRootsCache = new Map()
|
|
124
|
-
const ownRoots = (abs) => {
|
|
125
|
-
const owner = owningProject(abs)
|
|
126
|
-
if (!owner) return []
|
|
127
|
-
let r = ownerRootsCache.get(owner)
|
|
128
|
-
if (!r) {
|
|
129
|
-
r = resolveReviewRootsFor(owner).map((x) => x.replace(/[\\/]/g, sep))
|
|
130
|
-
ownerRootsCache.set(owner, r)
|
|
131
|
-
}
|
|
132
|
-
return r
|
|
133
|
-
}
|
|
134
|
-
const invalidDocs = documents.filter((doc) => {
|
|
135
|
-
const n = normAbs(doc, agent.cwd).replace(/[\\/]/g, sep)
|
|
136
|
-
const under = (r) => n === r || n.startsWith(r + sep)
|
|
137
|
-
return !roots.some(under) && !ownRoots(n).some(under)
|
|
138
|
-
})
|
|
139
|
-
if (invalidDocs.length > 0) {
|
|
121
|
+
const { resolved, invalid, ambiguous, candidates } = resolveReviewDocPaths(documents, agent.cwd)
|
|
122
|
+
if (invalid.length > 0 || ambiguous) {
|
|
140
123
|
if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
|
|
124
|
+
if (ambiguous) {
|
|
125
|
+
// 歧义拒(KD-4 独立判据——不混入 not-doc;列全部可读命中——fail-closed 不静默挑一)。
|
|
126
|
+
return withIdentityLine(
|
|
127
|
+
`Advisor: design review document is ambiguous — "${ambiguous.doc}" matches several candidate projects: ${ambiguous.hits.join(", ")}. Pass a project-root-relative or absolute path.`,
|
|
128
|
+
{ type: reviewType, scope: scopeSummary(documents), round: "—", criterion: "scope-doc-ambiguous" },
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
// not-doc 拒:既有稳定前缀逐字(行首)+ 解析诊断尾(B 面根治——逐文档「解析后绝对
|
|
132
|
+
// 路径 ← 基面 = 会话 cwd」+ 候选项目根提示 cap 5 + 重试指引——纠正「误报因」缺陷)。
|
|
133
|
+
const invalidList = invalid.map((v) => v.doc).join(", ")
|
|
134
|
+
const attemptedList = invalid
|
|
135
|
+
.map((v) => `${v.doc} → ${v.attempted.length > 0 ? v.attempted.join(" | ") : normAbs(v.doc, agent.cwd)}`)
|
|
136
|
+
.join("; ")
|
|
137
|
+
const shown = candidates.slice(0, 5)
|
|
138
|
+
const more = candidates.length - shown.length
|
|
139
|
+
const candidateNote = shown.length > 0
|
|
140
|
+
? `(candidates: ${shown.join(", ")}${more > 0 ? ` +${more} more` : ""}) — pass project-root-relative (e.g. ${shown[0]}/docs/…) or absolute paths`
|
|
141
|
+
: "— pass project-root-relative or absolute paths"
|
|
141
142
|
return withIdentityLine(
|
|
142
|
-
`Advisor: design review documents must be documentation files (per the project's conventions). Invalid: ${
|
|
143
|
+
`Advisor: design review documents must be documentation files (per the project's conventions). Invalid: ${invalidList}\nDiagnosis: resolved against session cwd ${agent.cwd} → ${attemptedList}; no candidate project docRoot contains it ${candidateNote}.`,
|
|
143
144
|
{ type: reviewType, scope: scopeSummary(documents), round: "—", criterion: "scope-not-doc" },
|
|
144
145
|
)
|
|
145
146
|
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* ——其控制器链只作**判据**(杀 ⟺ 控制器已中止),杀树动作经本收尾面 `spec.dispose` 钩子落
|
|
18
18
|
* `bash-async.mjs` 杀单点(`killBgTree`);回合中断(Ctrl+I)经 `bindChildController`
|
|
19
19
|
* interrupt 豁免不中止控制器 ⇒ 本面零动作(F2 同款豁免——池保留)。
|
|
20
|
+
* 批 browser-async-fix(2026-10-07):browser 后台动作族同面并入(`BROWSER_SPEC`——dispose = 条目
|
|
21
|
+
* 控制器 abort ⇒ 动作展开 / 排队丢队;**不杀浏览器**)。
|
|
20
22
|
*
|
|
21
23
|
* 判据口径(D-AD6):两接线点**均不传 `ctx`** ⇒ 实走 controller 支(`parentAborted(null, entry)`)
|
|
22
24
|
* ——与对侧有效判据同判(对侧传入的 `ctx` 为死参);`ctx` 保留为签名备用面(直调用例 / 对侧收敛)。
|
|
@@ -59,6 +61,11 @@ const bgReminderText = (n, list) =>
|
|
|
59
61
|
`[System reminder: ${n} background bash task(s) were killed by the user's Stop — their processes are gone and no digest will arrive: ${list}. ` +
|
|
60
62
|
"Full output logs remain on disk (paths were given in the start ack).]"
|
|
61
63
|
|
|
64
|
+
/** browser 后台动作族提醒模板(同族形态——取消无摘要:终止凭据 = 墓碑 + 杀点确认;浏览器本体不杀)。 */
|
|
65
|
+
const browserReminderText = (n, list) =>
|
|
66
|
+
`[System reminder: ${n} background browser task(s) were cancelled by the user's Stop — no digest will arrive: ${list}. ` +
|
|
67
|
+
"The browser session itself was not killed (run `close` to reset it).]"
|
|
68
|
+
|
|
62
69
|
/** 列表词(wasStatus 数据源):queued → "(was queued — never started)";其余 "(was running)"。 */
|
|
63
70
|
const wasPhrase = (wasStatus) => (wasStatus === "queued" ? " (was queued — never started)" : " (was running)")
|
|
64
71
|
|
|
@@ -151,6 +158,21 @@ const BG_SPEC = {
|
|
|
151
158
|
},
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
/** browser 后台动作族 spec(批 browser-async-fix——收尾档同面):dispose = 中止在飞(条目控制器
|
|
162
|
+
* abort ⇒ running 动作展开 / queued 丢队从未运行)+ 标记 discarded(settle 面据此不注入——摘要不可达);
|
|
163
|
+
* 无队列面(帽 = 起跑显式拒)。**不杀浏览器**(会话重置是独立动作 `close`)。 */
|
|
164
|
+
const BROWSER_SPEC = {
|
|
165
|
+
pool: "browser",
|
|
166
|
+
describe: (entry) => ({ id: entry.id, role: "browser", action: entry.action }),
|
|
167
|
+
roleOf: () => "browser",
|
|
168
|
+
listPhrase: (d) => `browser#${d.id} (${d.action})`,
|
|
169
|
+
reminder: browserReminderText,
|
|
170
|
+
dispose: (entry) => {
|
|
171
|
+
entry.discarded = true
|
|
172
|
+
entry.controller?.abort?.({ abortTrigger: "discard" })
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
154
176
|
/**
|
|
155
177
|
* 中止收尾(F1–F3):只清已死子代理条目——写 `discarded` 墓碑 + 出池 + 队列剔除 + 整批一次提醒。
|
|
156
178
|
* @param parent agent 形态(载体双形经 `getAsyncPool` / `writeTombstone` 吸收)
|
|
@@ -184,3 +206,15 @@ export function discardAbortedAdvisors(parent, ctx = null) {
|
|
|
184
206
|
export function discardAbortedBgTasks(parent, ctx = null) {
|
|
185
207
|
return discardRole(parent, BG_SPEC, ctx)
|
|
186
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 中止收尾(browser 后台动作族——同面并入):只清已死条目——逐条中止在飞(running ⇒ 动作展开;
|
|
212
|
+
* queued ⇒ 丢队从未运行)+ `discarded` 墓碑 + 出池 + 整批一次提醒;回合中断(Ctrl+I)不中止控制器
|
|
213
|
+
* ⇒ 本面零动作(池保留——与 bg 同豁免)。
|
|
214
|
+
* @param parent agent 形态
|
|
215
|
+
* @param ctx 判据 ctx(接线点不传——controller 支,同上)
|
|
216
|
+
* @returns {{discarded: Array<{id, role: "browser", action}>, kept: number}}
|
|
217
|
+
*/
|
|
218
|
+
export function discardAbortedBrowserTasks(parent, ctx = null) {
|
|
219
|
+
return discardRole(parent, BROWSER_SPEC, ctx)
|
|
220
|
+
}
|
|
@@ -113,11 +113,12 @@ export function bindChildController(ctrl, baseSignal) {
|
|
|
113
113
|
|
|
114
114
|
/** 池 accessor(D1):role "advisor" → `_asyncAdvisors`(独立评审池——无队列独立调度)·
|
|
115
115
|
* role "consult" → `_consultSessions`(会话池)· role "bg" → `_bgTasks`(#9 后台 bash
|
|
116
|
-
* 任务池)·
|
|
116
|
+
* 任务池)· role "browser" → `_browserTasks`(批 browser-async-fix 后台浏览器动作池)·
|
|
117
|
+
* 其余 → `_asyncSubagents`(子代理/飞刀共享槽位队列池)。载体经 `carrierField`
|
|
117
118
|
* 吸收(#94);未初始化返 null——调用方以 `?? new Map()` / 可选链处置。 */
|
|
118
119
|
export function getAsyncPool(parent, role) {
|
|
119
120
|
const field = role === "advisor" ? "_asyncAdvisors" : role === "consult" ? "_consultSessions"
|
|
120
|
-
: role === "bg" ? "_bgTasks" : "_asyncSubagents"
|
|
121
|
+
: role === "bg" ? "_bgTasks" : role === "browser" ? "_browserTasks" : "_asyncSubagents"
|
|
121
122
|
return carrierField(parent, field) ?? null
|
|
122
123
|
}
|
|
123
124
|
|
|
@@ -134,6 +135,17 @@ export function parkAsyncPending(parent, entry) {
|
|
|
134
135
|
pend.push(entry)
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
/**
|
|
139
|
+
* §6.31.5 升级账本写助手(消化账务批):`_unsettledDigests` 追加一行
|
|
140
|
+
* (`{ id, role, failures, report, at }`)。载体吸收(父字段优先 / history 借用——形态同
|
|
141
|
+
* `parkAsyncPending`);行序 = 升级序(读面 = `digest-account.mjs` `unsettledRows`)。
|
|
142
|
+
*/
|
|
143
|
+
export function appendUnsettledDigest(parent, row) {
|
|
144
|
+
const existing = carrierField(parent, "_unsettledDigests")
|
|
145
|
+
const list = (parent._unsettledDigests ??= Array.isArray(existing) ? existing : [])
|
|
146
|
+
list.push(row)
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
// ─── D4 守卫统一(严格版)+ D6 信号兜底单点 ──────────────────────────────────
|
|
138
150
|
|
|
139
151
|
/** D4 settle 守卫统一(escalate 严格版):父侧中止 = 回合 signal aborted(#98 VSC 侧
|
|
@@ -161,7 +173,9 @@ export function buildChildSignal(parent, ctx) {
|
|
|
161
173
|
* 对子代理对象的引用(`childAgent`/`report`——条目前此从不显式释放,挂起期 = 分钟级驻留)。
|
|
162
174
|
* 幂等(重入不抛);池内/挂起未消化窗口语义零变(settle 时刻不释放——表 2 候选 2 否决:
|
|
163
175
|
* status/observe 在窗口内仍读 child 摘要)。三消费点(回合尾收集 / run 起始 pending 注入 /
|
|
164
|
-
*
|
|
176
|
+
* 挂起残差注入)——fallback 面(无 `_daSession`)注入完成后调本 helper(逐字沿用);会话态
|
|
177
|
+
* (`_daSession`)注入后改调 `releaseChildHold`(仅释放 `childAgent`——`report` 保留至销账 /
|
|
178
|
+
* 升级;§6.31.4 ∥ KD-DA7)。
|
|
165
179
|
*/
|
|
166
180
|
export function releaseSettledEntry(entry) {
|
|
167
181
|
if (!entry || typeof entry !== "object") return
|
|
@@ -169,6 +183,16 @@ export function releaseSettledEntry(entry) {
|
|
|
169
183
|
entry.report = null
|
|
170
184
|
}
|
|
171
185
|
|
|
186
|
+
/**
|
|
187
|
+
* §6.31.4 投递面(消化账务批 · 2026-10-05 · 台账 #930):会话态注入后**仅**释放 `childAgent`——
|
|
188
|
+
* `report` 保留至销账 / 升级(重投需原文;驻留增量以报告量级 ∥ 重投上限封顶)。fallback 面零调用
|
|
189
|
+
* (既有 `releaseSettledEntry` 逐字沿用——注入即释放)。
|
|
190
|
+
*/
|
|
191
|
+
export function releaseChildHold(entry) {
|
|
192
|
+
if (!entry || typeof entry !== "object") return
|
|
193
|
+
entry.childAgent = null
|
|
194
|
+
}
|
|
195
|
+
|
|
172
196
|
/**
|
|
173
197
|
* D3 settle 共享 helper——四族 settle 回调公共收尾单点(ASYNC-RESULT-CONTAINER.md
|
|
174
198
|
* AC1:settle 记账单点,无逐字重复)。流程:
|
|
@@ -273,10 +297,11 @@ export function settleAsyncEntry(parent, entry, opts = {}) {
|
|
|
273
297
|
parkAsyncPending(parent, entry)
|
|
274
298
|
pool?.delete(String(entry.id))
|
|
275
299
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
300
|
+
// 发射单点(块到达时点归位批 · #746|§6.8「settle 延迟冻结(两态统一)」):settle 一律 ⟦ev⟧settled
|
|
301
|
+
// (区块头恒中间态「done · awaiting digestion」驻留面板);⟦ev⟧done = 消费面补发(消费窗清单单源
|
|
302
|
+
// = §6.8)⇒ 冻结 ∥ 归档恒落投递时点(消化账务批 · 2026-10-05——归档 ≠ 销账;销账后移见账 = §6.31);
|
|
303
|
+
// 回合运行中零归档触发器(「S 不夹运行中回合」结构不变式)。
|
|
304
|
+
ctx?.callbacks?.onToken?.(`${entry.relayPrefix}⟦ev⟧settled\x1e0\x1e0\x1esettled\x1e`)
|
|
280
305
|
}
|
|
281
306
|
// ④ 公共尾部:settleSeq 递增 + _settle 唤醒(never rejects)+ 唤醒挂起驱动 waiter
|
|
282
307
|
entry._settleSeq = (parent._asyncSettleSeq = (parent._asyncSettleSeq ?? 0) + 1)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* audit-block.mjs — 审计块构造器(AGENT-LOOP-SUBAGENT.md §6.26 · 台账 #23)。
|
|
3
3
|
*
|
|
4
4
|
* 自 `subagent-spawn.mjs` **逐字搬移**(D23-3 外提:spawn 档越 300 软线 + 块构造成纯函数
|
|
5
|
-
* ⇒
|
|
5
|
+
* ⇒ 判据可直测;D23-4:块内文本零改——含块内自指词,整块搬移不改真值)。本档承载
|
|
6
6
|
* spawn 级固定机制性指令的第二块(审计模板 + A2 任务书摘要 + touched 快照):
|
|
7
7
|
* 装配侧只收集(`thincoder-core/agent-tools/subagent-spawn.mjs`),下发面 = system 固块
|
|
8
8
|
* (`child._spawnSystemBlock` → 核 `prepareRun` 拼接——`thincoder-core/agent/setup.mjs`)。
|
|
@@ -56,7 +56,7 @@ const TAIL_KEEP_CHARS = 32 * 1024
|
|
|
56
56
|
/** exit→close 宽限(同同步径后台注记面:孙进程握管道时 close 不来——`bash.mjs` 1500ms 同值)。 */
|
|
57
57
|
const EXIT_GRACE_MS = 1500
|
|
58
58
|
|
|
59
|
-
/** 测试缝:log
|
|
59
|
+
/** 测试缝:log 目录重定向(单元测试沙箱——生产从不调用;`_setDigestOffloadDirForTest` 同式)。 */
|
|
60
60
|
let _bgTaskLogDir = null
|
|
61
61
|
export function _setBgTaskLogDirForTest(dir) { _bgTaskLogDir = dir }
|
|
62
62
|
function bgTaskLogDir() { return _bgTaskLogDir ?? join(configDir, "tool-results") }
|
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
* 身份判据(D-BR17/D-BR18/D-BR21):主 agent 的工具调用 ctx 带 `depth === 0`(dispatch 装配);
|
|
13
13
|
* eng 子代理 `ctx.depth > 0`;评审实例 ctx 无 depth 但工具以 `review: true` 绑定。create/close
|
|
14
14
|
* 仅 depth-0 放行(BR-19/BR-24「… is main-agent-only」);status 写域 = 调用者自己段
|
|
15
|
-
* (eng-designer → §2 · 评审 → §3 · eng-coder → §5 ·
|
|
16
|
-
* 状态面走普通文档写);depth-0 的 status/path
|
|
15
|
+
* (eng-designer → §2 · 评审 → §3 · eng-coder → §5 · coder → §5【普通模式分支增表】· 主 agent →
|
|
16
|
+
* 工程 §1 ∥ 普通 §1/§2/§5【取段——§4.16】——§4/§6 状态面走普通文档写);depth-0 的 status/path 面
|
|
17
|
+
* 按 D-BR21(可选 path,缺省 = 在飞批唯一时取)。
|
|
17
18
|
*/
|
|
18
19
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
19
20
|
import { dirname, resolve } from "node:path"
|
|
20
21
|
|
|
21
22
|
import {
|
|
22
|
-
SEGMENT_BY_ROLE, STATUS_WORDS, STATUS_LINE_RE, readBatchStatusLine, sectionHasStatusLine, sectionHeaderRe, batchSkeleton,
|
|
23
|
+
SEGMENT_BY_ROLE, SEGMENT_BY_ROLE_NORMAL, DEPTH0_SEGMENTS, STATUS_WORDS, STATUS_LINE_RE, readBatchStatusLine, sectionHasStatusLine, sectionHeaderRe, batchSkeleton,
|
|
23
24
|
TEMPLATE_PLACEHOLDERS, findPlaceholderResidue, placeholderResidueError,
|
|
24
25
|
} from "./batch-skeleton.mjs"
|
|
25
26
|
import { resolveBatchCreatePath } from "./batch-paths.mjs"
|
|
@@ -49,6 +50,18 @@ function segmentNumber(raw) {
|
|
|
49
50
|
return m ? Number(m[1]) : null
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
/** 档头两可选参归一(`BATCH-RECORD.md` §4.11 契约——逐字文本面 = `TOOLS.md` §6.20):trim ∥
|
|
54
|
+
* 单行(拒句逐字)∥ 空串 ≡ 未给(null);`ledger` 前导 `#` 补齐(board 无补齐)。给参 ⇒ 建即填
|
|
55
|
+
* 台账行(batchSkeleton 实参面);未给(任一)⇒ 占位留存 + 回执填法句。 */
|
|
56
|
+
function normalizeHeaderParam(name, raw) {
|
|
57
|
+
const v = typeof raw === "string" ? raw.trim() : ""
|
|
58
|
+
if (!v) return null
|
|
59
|
+
if (/\r?\n/.test(v)) {
|
|
60
|
+
throw new Error("batch: create ledger / board must be single lines — a multi-line value would break the header's 台账 line. Nothing was written.")
|
|
61
|
+
}
|
|
62
|
+
return name === "ledger" && !v.startsWith("#") ? `#${v}` : v
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
/** main-agent-only 门(BR-19/BR-24):create/close 仅 depth-0 放行(eng 子代理 / 评审皆拒)。 */
|
|
53
66
|
function assertMainAgentOnly(ctx, action, review) {
|
|
54
67
|
if (review || !isDepthZero(ctx)) {
|
|
@@ -108,6 +121,10 @@ export function findInFlightBatch(cwd, bases) {
|
|
|
108
121
|
* 路径解析 = `resolveBatchCreatePath`(§4.15 单源:候选序首个落基底内者 / 锚定串只解析项目根形)。
|
|
109
122
|
* F11-B:`source` **必填**(topic 同款空拒——骨架编制行实参化;不传 = 死占位残留必被 F11-C 拒
|
|
110
123
|
* ⇒ create 即拒,不留死锁);`prev` 传入值**幂等剥**「前情 = 」前缀(值规范化;默认值零变)。
|
|
124
|
+
* 2026-10-05(A-2):`ledger` / `board` 可选参**建即填**台账行(归一契约 = §4.11;回执两态 =
|
|
125
|
+
* 两参皆给 ⇒ 标注已填 ∥ 未给(任一)⇒ 填法句)。
|
|
126
|
+
* 2026-10-09(§4.16):骨架**按模式位取形**(`batchSkeleton({…, mode})`——执行期实读;普通面
|
|
127
|
+
* 作者标签四处替换,工程面骨架产出逐字不变)。
|
|
111
128
|
* @returns {string} 成功消息(含落盘绝对路径)
|
|
112
129
|
*/
|
|
113
130
|
export function createBatchRecord({ args, ctx, review, cwd, bases, onWritten }) {
|
|
@@ -138,15 +155,24 @@ export function createBatchRecord({ args, ctx, review, cwd, bases, onWritten })
|
|
|
138
155
|
if (/\r?\n/.test(source)) {
|
|
139
156
|
throw new Error("batch: create source must be a single line — a multi-line source would break the header's 编制 line. Nothing was written.")
|
|
140
157
|
}
|
|
158
|
+
// 档头两可选参(§4.11 契约——给则建即填台账行;未给任一 ⇒ 占位留存 + 回执填法句)
|
|
159
|
+
const ledger = normalizeHeaderParam("ledger", args?.ledger)
|
|
160
|
+
const board = normalizeHeaderParam("board", args?.board)
|
|
141
161
|
const date = typeof args?.date === "string" && args.date.trim() ? args.date.trim() : todayLocal()
|
|
142
162
|
// F11-B:prev 幂等 strip——调用方自带「前情 = 」前缀(单或重复)一并剥净(`+` 量词;归一化目的
|
|
143
163
|
// = 值规范化,无前缀值原样通过)。默认值形态「无(独立批)」零变。
|
|
144
164
|
const prevRaw = typeof args?.prev === "string" ? args.prev.trim() : ""
|
|
145
165
|
const prev = prevRaw ? (prevRaw.replace(/^(?:前情\s*[=::]\s*)+/g, "").trim() || "无(独立批)") : "无(独立批)"
|
|
146
166
|
mkdirSync(dirname(abs), { recursive: true })
|
|
147
|
-
|
|
167
|
+
// 模式位执行期实读(§4.16——骨架取形时点 = 建档即固;翻转不回写既有骨架)
|
|
168
|
+
const mode = ctx?.agent?.config?.agent?.engineering ? "engineering" : "normal"
|
|
169
|
+
writeFileSync(abs, batchSkeleton({ date, topic, source, prev, ledger, board, mode }))
|
|
148
170
|
onWritten?.(ctx?.agent ?? {}, abs)
|
|
149
|
-
|
|
171
|
+
// 回执两态(`TOOLS.md` §6.20 逐字):两参皆给 ⇒ 标注已填;未给(任一)⇒ 填法句(填法 = 文件编辑)
|
|
172
|
+
const headerNote = ledger !== null && board !== null
|
|
173
|
+
? `header 台账 line filled (${ledger} · ${board})`
|
|
174
|
+
: "header 台账 placeholder(s) remain — fill them by file edit before the record's first append/status opens"
|
|
175
|
+
return `batch: created ${abs} — six-section skeleton written (§1 status line carries the gate-legal 「进行中」 placeholder; register the ledger entry yourself — create does not). ${headerNote}`
|
|
150
176
|
}
|
|
151
177
|
|
|
152
178
|
/**
|
|
@@ -260,24 +286,40 @@ function assertStatusNote(note) {
|
|
|
260
286
|
}
|
|
261
287
|
|
|
262
288
|
/**
|
|
263
|
-
* status——状态行流转(§4.12
|
|
264
|
-
* §
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
289
|
+
* status——状态行流转(§4.12,段属主;§4.16 模式感知)。写域 = 调用者自己段内 `**状态行**:`
|
|
290
|
+
* 行(评审 → §3 · eng-designer → §2 · eng-coder / coder → §5 · 主 agent:工程 §1 ∥ 普通
|
|
291
|
+
* §1/§2/§5 取段——缺省 §1);值域 = STATUS_WORDS 该段项恰一词(**余核 = 关键词**——F11-A
|
|
292
|
+
* 谓词收紧),散文说明走独立 `note` 字段(落状态行括注);冻结真值不变(gate 只读 §1)。
|
|
293
|
+
* path 参数 = 仅 depth-0(D-BR21)——子代理/评审传 path ⇒ 拒(目标 = spawn/实例注入,语法上
|
|
294
|
+
* 写不到别处)。F11-C 挂点 = 写盘前(assertStatusValue 后):档头结构行含骨架死占位 ⇒ 拒
|
|
295
|
+
* (段体引用豁免;close 不拦——收口是主 agent 终态动作)。
|
|
269
296
|
* @returns {string} 成功消息
|
|
270
297
|
*/
|
|
271
298
|
export function statusBatchRecord({ args, ctx, review, pickTarget, onWritten }) {
|
|
272
299
|
const depth0 = isDepthZero(ctx)
|
|
273
|
-
|
|
300
|
+
// 模式位执行期实读(§4.16——同款先例 = task.mjs execute 门;翻转即时生效——D-BR26)。
|
|
301
|
+
// 写域:评审 → §3 · 子代理 → 身份段(普通模式分支合并 coder 增表——工程模式分支不查)·
|
|
302
|
+
// depth-0 工程 → §1 ∥ 普通 → §1/§2/§5(取段——缺省 §1)。
|
|
303
|
+
const engineering = !!ctx?.agent?.config?.agent?.engineering
|
|
304
|
+
let seg = review ? 3 : depth0 ? 1 : SEGMENT_BY_ROLE[ctx?.agent?._role] ?? (engineering ? null : SEGMENT_BY_ROLE_NORMAL[ctx?.agent?._role] ?? null)
|
|
274
305
|
if (seg === null) {
|
|
275
306
|
throw new Error("batch: no segment is writable by this caller — status writes the caller's OWN section (eng-designer → §2, design review → §3, eng-coder → §5, main agent → §1).")
|
|
276
307
|
}
|
|
277
|
-
//
|
|
278
|
-
//
|
|
308
|
+
// 段域判据(BR-22「eng-designer 对 §1 调 status ⇒ 段白名单拒」——镜像 append 面):
|
|
309
|
+
// depth-0 普通面(§4.16)= **取段**——写域集 §1/§2/§5、缺省 §1、越出集合 ⇒ 拒(模式化新串);
|
|
310
|
+
// depth-0 工程面 ∥ 子代理 ∥ 评审 = **声明段核对**(声明段 ≠ 身份写域段 ⇒ 拒;身份写域段可省略)。
|
|
279
311
|
const declared = args?.segment === undefined || args?.segment === null ? null : segmentNumber(args.segment)
|
|
280
|
-
|
|
312
|
+
// 传值而解析不可得 ⇒ 显式拒(#1116——镜像 append 面「unknown segment」串形;未传 = 缺省语义零变)。
|
|
313
|
+
if (declared === null && args?.segment !== undefined && args?.segment !== null) {
|
|
314
|
+
throw new Error(`batch: unknown segment ${JSON.stringify(args?.segment ?? null)} — pass the section number you write (e.g. "§${seg}"), or omit it. Nothing was written.`)
|
|
315
|
+
}
|
|
316
|
+
if (depth0 && !review && !engineering) {
|
|
317
|
+
const target = declared ?? 1
|
|
318
|
+
if (!DEPTH0_SEGMENTS.status.normal.includes(target)) {
|
|
319
|
+
throw new Error(`batch: §${declared} is not yours to write — in normal mode status writes §1/§2/§5 only (一段一作者: §3 = design review; §4/§6 carry no status word list). Nothing was written.`)
|
|
320
|
+
}
|
|
321
|
+
seg = target
|
|
322
|
+
} else if (declared !== null && declared !== seg) {
|
|
281
323
|
throw new Error(`batch: §${declared} is not yours to write — status writes YOUR OWN section §${seg} only (一段一作者: eng-designer → §2, design review → §3, eng-coder → §5, main agent → §1). Nothing was written.`)
|
|
282
324
|
}
|
|
283
325
|
if (args?.path !== undefined && args?.path !== null && !depth0) {
|
|
@@ -298,8 +340,8 @@ export function statusBatchRecord({ args, ctx, review, pickTarget, onWritten })
|
|
|
298
340
|
const abs = pickTarget(args?.path)
|
|
299
341
|
const src = readFileSync(abs, "utf8")
|
|
300
342
|
assertGateOpen(src)
|
|
301
|
-
// F11-C
|
|
302
|
-
const residues = findPlaceholderResidue(src
|
|
343
|
+
// F11-C(写盘前):档头结构行死占位残留 ⇒ 拒(段体引用豁免——判据可辨性收正,`TOOLS.md` §6.20)
|
|
344
|
+
const residues = findPlaceholderResidue(src)
|
|
303
345
|
if (residues.length) throw new Error(placeholderResidueError(residues))
|
|
304
346
|
const written = updateSectionStatusLine(src, seg, lineValue)
|
|
305
347
|
writeFileSync(abs, written)
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 契约(§4.15 逐字):**候选序** = cwd → 项目根 → 逐基底(基底 = `batchDocBases` 声明面单源);
|
|
8
8
|
* **判据分野** = 读面取首个**可读**文件 / create 取首个**落基底根内**者;**锚定防嵌套** = 串以任一基底的
|
|
9
9
|
* 「项目根相对前缀」打头且前缀后为**路径段边界** ⇒ 只解析项目根形(不二次拼接;不落基底内 ⇒ fail-closed);
|
|
10
|
-
* **歧义锚**(条 5 · 2026-10-02 · #828)= create 按目标所属项目落基底 ∥
|
|
10
|
+
* **歧义锚**(条 5 · 2026-10-02 · #828)= create 按目标所属项目落基底 ∥ 无所属显式拒;读面候选分支追加;
|
|
11
11
|
* 在飞 ∥ 写门基底集 = 候选并集(`batchDocBases`);**零变面** = 绝对路径照旧、三个错误文案逐字、
|
|
12
12
|
* 「参数在 + 路径可读」判据句、`ok` / `none` 两态。
|
|
13
|
+
* **基底自有项目根分支 + 基底相对单段收窄**(条 6 · 2026-10-05 · #942)= 逐基底 `owningProject` 恰一落点收 ∥ 多落点显式拒(`ambiguousLandingError`);基底相对分支限单段(多段串不二次拼接)。
|
|
13
14
|
*
|
|
14
15
|
* 依赖(KD-4 单向):本档为**叶档**——只 import `node:*` 与 `../manifest.mjs`;主档 re-export 保既有面。
|
|
15
16
|
*/
|
|
@@ -98,8 +99,9 @@ const outsideBasesError = (raw) => new Error(`batch: create path resolves outsid
|
|
|
98
99
|
const anchoredEscapeError = (raw) => new Error(`batch: create path is anchored at a batch base root but does not resolve under the declared docRoot.batches root(s) — refusing to nest it (fail-closed). Path: ${raw}`)
|
|
99
100
|
/** 歧义锚 create 拒面(**文案逐字** = `BATCH-RECORD.md` §4.15 条 5——机检锚 `ambiguous session anchor`)。 */
|
|
100
101
|
const ambiguousCreateError = (raw, candidates) => new Error(`batch: ambiguous session anchor — create target "${raw}" belongs to no candidate project; pass an explicit project path (e.g. "${candidates[0]}/docs/batches/<file>.md"). Candidates: ${candidates.join(", ")}`)
|
|
102
|
+
const ambiguousLandingError = (raw, landings) => new Error(`batch: create path resolves under more than one declared batch-record base — refusing to pick one (fail-closed); pass an explicit path (absolute preferred). Path: ${raw}. Candidates: ${landings.join(", ")}`)
|
|
101
103
|
|
|
102
|
-
/**
|
|
104
|
+
/** 歧义锚读面候选分支(条 5):逐候选项目——项目根形(`resolve(candidate, p)`)∥ 声明基底形
|
|
103
105
|
* (候选按名排序、并集保序——「首个可读」在此即为确定判读)。 */
|
|
104
106
|
function ambiguousReadLegs(cwd, p) {
|
|
105
107
|
const amb = ambiguousCandidates(cwd)
|
|
@@ -149,7 +151,7 @@ export function resolveBatchDocPath(cwd, given) {
|
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
152
|
-
* **create 面**解析(§4.15 条 2/3):绝对路径照旧取用(仍过越基底判据);相对路径 = 候选序中首个**落基底
|
|
154
|
+
* **create 面**解析(§4.15 条 2/3/6):绝对路径照旧取用(仍过越基底判据);相对路径 = 候选序中首个**落基底
|
|
153
155
|
* 根内**者(新档不存在——可读性无判别力);锚定串只解析项目根形,不落基底内 ⇒ fail-closed 新文案。
|
|
154
156
|
* **歧义锚**(条 5):目标所属项目(`owningProject`)∈ 锚候选集 ⇒ 基底 = 该项目声明面(落其基底内;
|
|
155
157
|
* 越出 ⇒ 既有 fail-closed);无所属 ⇒ **显式拒**(列候选 + 显式路径指引——不静默落锚)。
|
|
@@ -179,8 +181,26 @@ export function resolveBatchCreatePath(cwd, given, bases) {
|
|
|
179
181
|
if (!insideBases(abs, roots)) throw anchoredEscapeError(raw)
|
|
180
182
|
return abs
|
|
181
183
|
}
|
|
182
|
-
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p)
|
|
184
|
+
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p)]) {
|
|
183
185
|
if (insideBases(abs, roots)) return abs
|
|
184
186
|
}
|
|
187
|
+
// ③ 基底自有项目根形(#942 条 6):owningProject 可判者逐基底取值;distinct 落点 ≥2 ⇒ 拒
|
|
188
|
+
const owned = []
|
|
189
|
+
for (const b of roots) {
|
|
190
|
+
const ownRoot = owningProject(b)
|
|
191
|
+
if (!ownRoot) continue
|
|
192
|
+
const abs = resolve(ownRoot, p)
|
|
193
|
+
if (insideBases(abs, roots) && !owned.some((x) => samePath(x, abs))) owned.push(abs)
|
|
194
|
+
}
|
|
195
|
+
if (owned.length > 1) throw ambiguousLandingError(raw, owned)
|
|
196
|
+
if (owned.length === 1) return owned[0]
|
|
197
|
+
// ④ 基底相对形收窄单段(归一后无 `/` 才走原基底分支——多段串不二次拼接)
|
|
198
|
+
const leafRel = roots.length ? relative(roots[0], resolve(roots[0], p)) : ""
|
|
199
|
+
if (leafRel && !leafRel.startsWith("..") && !leafRel.includes(sep)) {
|
|
200
|
+
for (const b of roots) {
|
|
201
|
+
const abs = resolve(b, p)
|
|
202
|
+
if (insideBases(abs, roots)) return abs
|
|
203
|
+
}
|
|
204
|
+
}
|
|
185
205
|
throw outsideBasesError(raw)
|
|
186
206
|
}
|