@thincoder/core 0.10.13 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/advisor/messages.mjs +4 -2
- package/advisor/notice.mjs +1 -1
- package/advisor/project-context.mjs +121 -11
- package/agent/completion.mjs +13 -1
- package/agent/digest-account.mjs +73 -46
- package/agent/dispatch-run.mjs +1 -1
- package/agent/dispatch.mjs +29 -21
- package/agent/family-tools.mjs +9 -5
- package/agent/helpers.mjs +1 -94
- package/agent/offload.mjs +88 -0
- package/agent/record-results.mjs +1 -6
- package/agent/run-stages.mjs +69 -48
- package/agent/run-start.mjs +8 -8
- package/agent/setup.mjs +3 -11
- package/agent/suspension.mjs +1 -1
- package/agent/turn-loop.mjs +32 -3
- package/agent/write-face.mjs +99 -0
- package/agent/write-gate.mjs +354 -5
- package/agent-tools/advisor.mjs +127 -11
- package/agent-tools/async-settle.mjs +2 -2
- package/agent-tools/bash-async.mjs +8 -2
- package/agent-tools/batch-closeout.mjs +206 -0
- package/agent-tools/batch-gate.mjs +287 -73
- package/agent-tools/batch-ledger-bind.mjs +183 -0
- package/agent-tools/batch-ledger-link.mjs +73 -0
- package/agent-tools/batch-lifecycle.mjs +113 -367
- package/agent-tools/batch-paths.mjs +45 -40
- package/agent-tools/batch-read.mjs +1 -1
- package/agent-tools/batch-reopen.mjs +142 -0
- package/agent-tools/batch-skeleton.mjs +275 -80
- package/agent-tools/batch.mjs +243 -50
- package/agent-tools/context.mjs +6 -3
- package/agent-tools/design-slots.mjs +24 -24
- package/agent-tools/design-token.mjs +96 -15
- package/agent-tools/doc-check.mjs +18 -2
- package/agent-tools/eng.mjs +1 -1
- package/agent-tools/escalate-async.mjs +3 -3
- package/agent-tools/manifest.mjs +8 -5
- package/agent-tools/read-history.mjs +5 -5
- package/agent-tools/review-facts.mjs +31 -13
- package/agent-tools/spawn-gates.mjs +10 -14
- package/agent-tools/ssh-session.mjs +120 -111
- package/agent-tools/ssh-transfer.mjs +210 -0
- package/agent-tools/ssh-transport.mjs +277 -46
- package/agent-tools/subagent-actions-query.mjs +1 -1
- package/agent-tools/subagent-actions.mjs +1 -1
- package/agent-tools/subagent-async.mjs +3 -2
- package/agent-tools/subagent-scheduler.mjs +43 -9
- package/agent-tools/subagent-spawn.mjs +68 -9
- package/agent-tools/subagent.mjs +16 -14
- package/client-identity.mjs +22 -6
- package/config-presets.mjs +5 -3
- package/context.mjs +34 -3
- package/declaration.mjs +2 -2
- package/doc-check/anchors.mjs +128 -28
- package/doc-check/index.mjs +68 -15
- package/doc-check/line-refs.mjs +112 -0
- package/doc-check/paragraph.mjs +18 -5
- package/explore-distill.mjs +1 -1
- package/i18n.mjs +1 -1
- package/index-discover.mjs +4 -1
- package/ledger-close-proof.mjs +19 -37
- package/ledger-cmd.mjs +71 -195
- package/ledger-db.mjs +29 -28
- package/ledger-migrate.mjs +4 -4
- package/ledger-read.mjs +10 -8
- package/ledger-surface.mjs +1 -1
- package/ledger-tools.mjs +6 -6
- package/ledger-variant-notice.mjs +1 -1
- package/ledger-write-ops.mjs +373 -0
- package/ledger.mjs +2 -2
- package/manifest-discovery.mjs +2 -2
- package/manifest-schema.mjs +4 -0
- package/memory/memory-tool.mjs +5 -2
- package/memory/schema.mjs +5 -26
- package/package.json +1 -1
- package/path-resolve.mjs +217 -0
- package/permission.mjs +4 -0
- package/prompt-overlays.mjs +5 -0
- package/prompts/advisor-design.md +9 -6
- package/prompts/advisor-round1.md +4 -3
- package/prompts/advisor-round2.md +6 -5
- package/prompts/advisor-round3.md +6 -5
- package/prompts/common.md +23 -10
- package/prompts/consult-base.md +2 -1
- package/prompts/discipline-engineering.md +29 -48
- package/prompts/discipline-normal.md +10 -5
- package/prompts/persona-eng-coder.md +8 -0
- package/prompts/persona-eng-designer.md +22 -12
- package/prompts/persona-engineering.md +81 -68
- package/prompts/persona-plan.md +2 -1
- package/prompts/persona-tester.md +55 -0
- package/provider/core.mjs +2 -2
- package/provider/google.mjs +15 -5
- package/provider/rate.mjs +11 -3
- package/provider/sse.mjs +15 -4
- package/session-identity.mjs +21 -0
- package/session-lifecycle.mjs +9 -4
- package/session-slots.mjs +9 -14
- package/session.mjs +5 -0
- package/skip-dirs.mjs +48 -0
- package/team-ledger.mjs +4 -4
- package/text-budget.mjs +4 -0
- package/token-window.mjs +90 -14
- package/tool-docs/advisor.md +2 -2
- package/tool-docs/apply_patch.md +2 -0
- package/tool-docs/bash.md +4 -0
- package/tool-docs/batch.md +85 -1
- package/tool-docs/browser.md +3 -1
- package/tool-docs/code_search.md +3 -1
- package/tool-docs/consult_start.md +6 -2
- package/tool-docs/consult_stop.md +3 -1
- package/tool-docs/delete.md +3 -0
- package/tool-docs/doc_check.md +43 -8
- package/tool-docs/doc_search.md +5 -1
- package/tool-docs/edit.md +22 -1
- package/tool-docs/execute.md +7 -1
- package/tool-docs/fetch.md +3 -1
- package/tool-docs/file_ops.md +3 -1
- package/tool-docs/git.md +9 -1
- package/tool-docs/goal.md +2 -0
- package/tool-docs/grep.md +1 -1
- package/tool-docs/hashline_edit.md +2 -0
- package/tool-docs/insert_after.md +3 -1
- package/tool-docs/ledger.md +5 -5
- package/tool-docs/lint.md +1 -0
- package/tool-docs/manifest-read.md +2 -1
- package/tool-docs/manifest.md +9 -2
- package/tool-docs/memory.md +11 -1
- package/tool-docs/notify_parent.md +1 -1
- package/tool-docs/plan.md +2 -0
- package/tool-docs/question.md +1 -1
- package/tool-docs/read_history.md +15 -3
- package/tool-docs/read_image.md +3 -1
- package/tool-docs/recent_changes.md +3 -1
- package/tool-docs/repo_outline.md +3 -1
- package/tool-docs/settings.md +10 -2
- package/tool-docs/skill.md +2 -0
- package/tool-docs/ssh.md +30 -17
- package/tool-docs/subagent.md +50 -13
- package/tool-docs/task.md +2 -0
- package/tool-docs/team_ledger.md +1 -1
- package/tool-docs/timer.md +7 -0
- package/tool-docs/verify.md +13 -1
- package/tool-docs/websearch.md +3 -1
- package/tool-docs/write.md +2 -0
- package/tools/bash.mjs +12 -2
- package/tools/edit-batch.mjs +40 -33
- package/tools/edit-diff.mjs +69 -1
- package/tools/execute.mjs +10 -4
- package/tools/file.mjs +17 -27
- package/tools/git.mjs +27 -1
- package/tools/ops.mjs +2 -1
- package/tools/patch.mjs +12 -3
- package/tools/process-doc-mark.mjs +139 -0
- package/tools/search.mjs +15 -6
- package/tools/shared.mjs +24 -1
- package/tools/ssh.mjs +48 -7
- package/tools/tree.mjs +4 -1
- package/tools/write-path.mjs +64 -3
- package/traces/trace-store.mjs +4 -3
- package/agent/light-round.mjs +0 -63
- package/agent/unbooked-pen.mjs +0 -62
|
@@ -12,11 +12,18 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { randomUUID } from "node:crypto"
|
|
15
|
+
import { readFileSync } from "node:fs"
|
|
15
16
|
import { tokenExpiryMs } from "../token-ttl.mjs"
|
|
16
|
-
//
|
|
17
|
+
// 段面读判据单源(骨架档):轮次块计数 ∥ 段级状态行 ∥ §3 完成词——§3 落地门与写入面读同一形态。
|
|
18
|
+
// 裁定行锚(`_V_PRE` / `_V_SUF` / `VERDICT_CHANGES_LINE`)同住骨架档锚块,组合序也只在那一档(`verdictGapOf`):
|
|
19
|
+
// 本档的裁定闸判据、写入面(`append §3` 的打回重置 `statusResetPlan`)与 `§5` 写入门读同一个函数——
|
|
20
|
+
// 锚与序都只此一份,三处不各写一遍。
|
|
21
|
+
import { countRoundBlocks, readSectionStatusWord, STATUS_WORDS, VERDICT_PASS_LINE, verdictGapOf } from "./batch-skeleton.mjs"
|
|
22
|
+
// 批归属派生(DESIGN-TOKEN-SETTLEMENT §10)——两源定序 `batchAbsPathOfRun`:`run.batchDoc` 优先、
|
|
23
|
+
// `docSetKey` 的批档兜底(判据与清点面同源)。
|
|
17
24
|
// 本档 ⇄ `design-slots.mjs` 为**静态环**(对方取 `effectiveTokenTtlMs`):两侧绑定只在函数体内
|
|
18
25
|
// 运行时使用,求值期零顶层调用 ⇒ 环安全(与 token-ttl ⇄ session 同构)。
|
|
19
|
-
import {
|
|
26
|
+
import { batchAbsPathOfRun } from "./design-slots.mjs"
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* F2c/F2e (§29.1 2026-09-07): the engine-generated Approved suffix — ONE builder
|
|
@@ -104,29 +111,46 @@ const TRUNCATED_ECHO_MARK = "注:回显被截断(uuid 之后缺失或不符
|
|
|
104
111
|
// 裁定行接受本地化等价形(GitHub #32 / #14):评审席用中文回执很常见,
|
|
105
112
|
// 只认英文会让 pass 落成 no-pass-line、凭证永不签发;判据仍只认整行逐字面,
|
|
106
113
|
// 不做「句中含『通过』」这类模糊匹配——那样会把「未通过」也放过。
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
// 四条锚与两条裁定正则全住骨架档锚块(`batch-skeleton.mjs`),裁定读数单源 = 同档 `verdictGapOf`:
|
|
115
|
+
// 本档、`§5` 写入门与 `append §3` 的通过置位读同一个函数(判定单源,零复制)。
|
|
116
|
+
// pass 锚经本档**原样再导出**——`batch-closeout.mjs` 的 import 面零改。
|
|
117
|
+
export { VERDICT_PASS_LINE }
|
|
118
|
+
/** 裁定闸的拒因(§5.2 #940):对外只两个词,`changes-required` ∥ `no-pass-line`——本批改的是判据来源,
|
|
119
|
+
* 不是这两个词。四格映射(`verdictGapOf` 值域 → 拒因):
|
|
120
|
+
* - `null`(说了 pass)⇒ `null`(签发);
|
|
121
|
+
* - `「VERDICT: changes-required」` ∥ `「两者同携」` ⇒ `changes-required`——同携今天就走这一格
|
|
122
|
+
* (改前打回锚先命中即判打回),组合序挪进函数后这张表照旧;
|
|
123
|
+
* - `「无裁定行」` ⇒ `no-pass-line`。 */
|
|
113
124
|
export function verdictGateFailure(rawResult) {
|
|
114
125
|
if (typeof rawResult !== "string") return "no-pass-line"
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
const gap = verdictGapOf(rawResult)
|
|
127
|
+
if (gap === null) return null
|
|
128
|
+
return gap === "无裁定行" ? "no-pass-line" : "changes-required"
|
|
117
129
|
}
|
|
118
130
|
|
|
119
131
|
/** §5.2 rejection mark (verbatim — machine-greppable; reason = no-pass-line | changes-required). */
|
|
120
132
|
const VERDICT_GATE_MARK = (reason) => `裁定非通过——token 未签发 (review verdict is not pass — no design token was issued; reason: ${reason}; the echoed token was stripped)`
|
|
121
133
|
|
|
134
|
+
/** §3 落地门拒因(ADVISOR-GUARDS.md §1.1——三值:判据 1 无条件的两个拒形落裁定闸之前 ⇒
|
|
135
|
+
* changes-required 轮也会拿到;判据 2 的拒形只在签发路径上)。 */
|
|
136
|
+
const RECORD_NO_BLOCK = "no-section3-block"
|
|
137
|
+
const RECORD_STATUS_NOT_PASSED = "status-not-passed"
|
|
138
|
+
const RECORD_BATCH_UNREADABLE = "batch-unreadable"
|
|
139
|
+
|
|
140
|
+
/** §3 落地门 rejection mark (verbatim — machine-greppable; reason = 上三值之一)。与裁定闸同形:
|
|
141
|
+
* 槽零写、实例不翻 open——标记即回执,不进 `settlementCriterion`(§7 不新增判据值)。
|
|
142
|
+
* **恒定位 = 上方两行逐字不动**;`evidence`(可执行证据 = 当前状态行 ∥ 轮次块数 ∥ 写面通道)
|
|
143
|
+
* 另起一行落在其后——指名当前值,但不把证据塞进那个机器可 grep 的括号里。 */
|
|
144
|
+
const REVIEW_RECORD_MARK = (reason, evidence) => `评审记录不合格——token 未签发 (review record incomplete — no design token was issued; reason: ${reason})\n补写该批档 §3(发现 + VERDICT —— 状态行由工具随块落)后重跑设计评审;未补写不得按已核处理。${evidence ? `\n证据:${evidence}` : ""}`
|
|
145
|
+
|
|
122
146
|
/**
|
|
123
147
|
* Shared design-review settlement (sync wrapper + async settle): the token echo
|
|
124
148
|
* IS the verdict — the advisor echoes it only on approval. On echo (full string
|
|
125
149
|
* OR truncated — uuid present; §5.1①): slot the token under designId (always the
|
|
126
150
|
* FULL token; + eng-coder gate flag; single-value mirror retired per
|
|
127
151
|
* DESIGN-TOKEN-SETTLEMENT D3), record its batch attribution (`_engDesignTokenBatches`
|
|
128
|
-
* — the
|
|
129
|
-
* attribution) and return the clean output with the
|
|
152
|
+
* — `batchAbsPathOfRun(run)`: the instance's bound `batchDoc`, else the reviewed doc-set's
|
|
153
|
+
* `batches/` file, §10; neither ⇒ no attribution) and return the clean output with the
|
|
130
154
|
* Approved suffix (truncated echo: the M2 marker sits before the suffix); the review
|
|
131
155
|
* instance CLOSES (a later review of the same doc-set starts a fresh full
|
|
132
156
|
* review). On non-echo: strip every echo form (single source §5.1③) and return
|
|
@@ -136,6 +160,11 @@ const VERDICT_GATE_MARK = (reason) => `裁定非通过——token 未签发 (rev
|
|
|
136
160
|
* (单谓词 `advisorIncompleteMarker`——结算方透传)。非空 ⇒ **一律不签发**(无论评审文本
|
|
137
161
|
* 是否回显 token):剥除全部 token 回显 + 追加未签发提示(原因 + 恢复指引),不写槽、
|
|
138
162
|
* 不关实例(可重评)。
|
|
163
|
+
* @remarks §3 落地门(ADVISOR-GUARDS.md §1.1——同族:未完成即不签发):**前置 = 绑了批档**(`run.batchDoc`
|
|
164
|
+
* 非空)——设计评审必携 `batchDoc`(准入门,判据 = 同档 §2 第 6 条)⇒ 到结算面仍为空只是接线故障,
|
|
165
|
+
* 在 `boundBatch` 判定处断言并 throw(程序不变量,早于写槽;不设对外拒因值,不返回判决);已过回显闸 ⇒ 两次判:① 轮次块数 > 点火基线
|
|
166
|
+
* `run.section3BlocksAtStart`(**无条件**——changes-required 轮同判;嵌在裁定闸之前)② 段级状态行 = `评审通过`
|
|
167
|
+
* (只在签发路径上判)。不满足 ⇒ `{passed:false}` + 恒定标记,槽零写、实例不翻 open(补写后同实例可重评)。
|
|
139
168
|
* @returns {{passed: boolean, output: string}}
|
|
140
169
|
*/
|
|
141
170
|
export function settleDesignReview(agent, run, designToken, rawResult, opts = {}) {
|
|
@@ -160,6 +189,47 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
160
189
|
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
161
190
|
return { passed: false, output: [stripped, NO_VALID_ECHO_MARK].filter(Boolean).join("\n\n") }
|
|
162
191
|
}
|
|
192
|
+
// §3 落地门判据 1(**无条件**——pass 与 changes-required 都判)—— 嵌在回显闸之后、裁定闸之前。
|
|
193
|
+
// 设计单源 = `docs/core/design/ADVISOR-GUARDS.md` §1.1;**一次读档、两次判**(判据 2 复用同一 src)。
|
|
194
|
+
// 未回显 ⇒ 已走上面的 M1,记录面零判(连凭据都没有,报「记录不合格」会把真因盖掉)。
|
|
195
|
+
//
|
|
196
|
+
// 前置 = 绑了批档,**程序不变量、不是拒发分支**:设计评审必携 `batchDoc`(准入门,判据 = 同档 §2 第 6 条)
|
|
197
|
+
// ⇒ 到结算面仍为空只能是「绑定没传到实例」的接线故障 ⇒ 断言并 throw(串点名设计评审实例 + designId),
|
|
198
|
+
// 零槽写、不返回判决、不设对外拒因值。给没发生过的情形配文案就是没有场景的分支;接线 bug 该炸得响,
|
|
199
|
+
// 不该伪装成一条给用户看的拒因(本批 🔴1 裁定)。
|
|
200
|
+
// 位置 = 写槽(`agent._engDesignTokens.set(...)`)之前:落在其后 = 没拦,token 已在槽里、授权照发。
|
|
201
|
+
// 调用点不得补 catch 后继续——控制流会落回 `set(...)` ⇒ token 照签、零报错、只留一条日志(同档 §1.1)。
|
|
202
|
+
const boundBatch = typeof run?.batchDoc === "string" && run.batchDoc.trim() ? run.batchDoc : null
|
|
203
|
+
if (!boundBatch) {
|
|
204
|
+
throw new Error(`design review settled with no batch-record binding (designId: ${run?.designId ?? "(none)"}) — a design review is launched with \`batchDoc\` required, so settling without it is a wiring fault, not an input to report.`)
|
|
205
|
+
}
|
|
206
|
+
let batchSrc = null
|
|
207
|
+
let reason = null
|
|
208
|
+
let detail = ""
|
|
209
|
+
try {
|
|
210
|
+
batchSrc = readFileSync(boundBatch, "utf8")
|
|
211
|
+
} catch (e) {
|
|
212
|
+
reason = RECORD_BATCH_UNREADABLE
|
|
213
|
+
detail = `批档读不到: ${e?.code ?? e?.message ?? "unknown"}`
|
|
214
|
+
}
|
|
215
|
+
if (reason === null && !Number.isInteger(run.section3BlocksAtStart)) {
|
|
216
|
+
// 点火基线 = 评审实例绑定批档时读该档记下的块数(`advisor.mjs`);null / 缺失 = 点火时读不到。
|
|
217
|
+
// 判据方向 =「绑了批档却验不了记录」不得当「没有记录要验」。
|
|
218
|
+
reason = RECORD_BATCH_UNREADABLE
|
|
219
|
+
detail = "点火基线缺失(批档在点火时不可读)"
|
|
220
|
+
}
|
|
221
|
+
if (reason === null) {
|
|
222
|
+
const blocks = countRoundBlocks(batchSrc, 3)
|
|
223
|
+
if (blocks <= run.section3BlocksAtStart) {
|
|
224
|
+
reason = RECORD_NO_BLOCK
|
|
225
|
+
detail = `§3 轮次块 ${blocks} 块、点火基线 ${run.section3BlocksAtStart} 块——本轮未写进 §3`
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (reason !== null) {
|
|
229
|
+
// 槽零写、`run.open` 不翻 ⇒ 补写 §3 后同一实例可重评(与裁定闸同形)。
|
|
230
|
+
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
231
|
+
return { passed: false, output: [stripped, REVIEW_RECORD_MARK(reason, detail)].filter(Boolean).join("\n\n") }
|
|
232
|
+
}
|
|
163
233
|
// §5.2 裁定闸(#940):签发 = 回显 ∧ 裁定行——缺 `VERDICT: pass` 行或含 `VERDICT: changes-required` 行
|
|
164
234
|
// ⇒ 拒签(与 M1 径同形:剥净回显 + 恒定标记;槽零写 ∥ 实例保持——修后重评)。
|
|
165
235
|
const gateFailure = verdictGateFailure(rawResult)
|
|
@@ -167,13 +237,24 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
167
237
|
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
168
238
|
return { passed: false, output: [stripped, VERDICT_GATE_MARK(gateFailure)].filter(Boolean).join("\n\n") }
|
|
169
239
|
}
|
|
240
|
+
// 判据 2(**只在签发路径上判**)——裁定行已判 pass、批档已读到(读不到已在上述判据 1 退场)。
|
|
241
|
+
// 段级 = 唯一状态行写面(`batch-skeleton.mjs` `updateSectionStatusLine`)的同一处——写它的是工具:
|
|
242
|
+
// `append §3` 那一笔随裁定行置位 ∥ 重置(`statusPassPlan` ∥ `statusResetPlan`),`status` 是同面上的
|
|
243
|
+
// 可选手写口——也是下游 `§5` 写入门读的那一处;轮次块只由工具戳标题、块内无状态行落点 ⇒
|
|
244
|
+
// 读块内状态行是读一行没人写的字。旧字面 `评审完成` 由 `readSectionStatusWord` 读侧等义。
|
|
245
|
+
const word = readSectionStatusWord(batchSrc, 3)
|
|
246
|
+
if (word !== STATUS_WORDS[3].done) {
|
|
247
|
+
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
248
|
+
const detail = `§3 状态行 = ${word ?? "(缺)"};须 = ${STATUS_WORDS[3].done}(块落盘时由工具置位)`
|
|
249
|
+
return { passed: false, output: [stripped, REVIEW_RECORD_MARK(RECORD_STATUS_NOT_PASSED, detail)].filter(Boolean).join("\n\n") }
|
|
250
|
+
}
|
|
170
251
|
// Echoed the token → review passed. Issue it to the parent for eng-coder.
|
|
171
252
|
agent._engDesignTokens ??= new Map()
|
|
172
253
|
agent._engDesignTokens.set(run.designId, designToken)
|
|
173
|
-
// §10 E1(DESIGN-TOKEN-SETTLEMENT):批归属旁列同拍写——派生 =
|
|
174
|
-
// `
|
|
254
|
+
// §10 E1(DESIGN-TOKEN-SETTLEMENT):批归属旁列同拍写——派生 = `batchAbsPathOfRun(run)`(两源定序:
|
|
255
|
+
// `run.batchDoc` 优先、文档集里的批档兜底;零新输入)。派生为无 ⇒ 归属 = 无(不猜):**删**该 designId
|
|
175
256
|
// 的旧行——否则一次改了范围的复评会让新槽继承上一轮的归属,收口时消费掉本不该消费的授权。
|
|
176
|
-
const batchAbs =
|
|
257
|
+
const batchAbs = batchAbsPathOfRun(run)
|
|
177
258
|
if (batchAbs) {
|
|
178
259
|
agent._engDesignTokenBatches ??= new Map()
|
|
179
260
|
agent._engDesignTokenBatches.set(run.designId, batchAbs)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { resolve } from "node:path"
|
|
17
17
|
import { projectRootView } from "../manifest.mjs"
|
|
18
|
+
import { resolvePathTarget } from "../path-resolve.mjs"
|
|
18
19
|
import { runDocCheck } from "../doc-check/index.mjs"
|
|
19
20
|
import { DESC } from "../tools/shared.mjs"
|
|
20
21
|
|
|
@@ -31,10 +32,25 @@ export const docCheckTool = {
|
|
|
31
32
|
},
|
|
32
33
|
readonly: true,
|
|
33
34
|
execute(args, ctx) {
|
|
34
|
-
const anchor = ctx?.agent?.cwd ?? ctx?.cwd
|
|
35
|
-
const target = args?.target ? resolve(anchor, String(args.target)) : resolve(anchor)
|
|
35
|
+
const anchor = ctx?.agent?.cwd ?? ctx?.cwd
|
|
36
36
|
const mode = args?.mode === undefined ? "report" : String(args.mode)
|
|
37
37
|
if (mode !== "report" && mode !== "gate") return `doc_check: unknown mode "${mode}" — use "report" (a report over the documents you list) or "gate" (this round's documents).`
|
|
38
|
+
// `target` 的路径判定经统一解析层(`kind:"dir"` / `order:"root-first"`):判不出唯一根 ⇒ 报明候选(KD-5
|
|
39
|
+
// 的形),解析出 ⇒ 用。`dangling` 不阻断本面——本面判据 = 内置常量、不读声明面,任可判目录都能跑
|
|
40
|
+
// (受判档由 `docs` 给定),故无项目根时按显式目标 ∥ 会话锚照跑(受理判据归本面)。
|
|
41
|
+
const picked = resolvePathTarget(anchor, args?.target, { kind: "dir", order: "root-first" })
|
|
42
|
+
if (picked.state === "ambiguous") {
|
|
43
|
+
if (mode === "gate") {
|
|
44
|
+
return [
|
|
45
|
+
"DOC-CHECK GATE: ERROR(多项目工作区:请指名 target)",
|
|
46
|
+
`候选:${picked.candidates.join(" ∥ ")}`,
|
|
47
|
+
"处置:指名项目根(`target`)后复跑",
|
|
48
|
+
].join("\n")
|
|
49
|
+
}
|
|
50
|
+
return `doc_check: ${anchor} is a workspace holding ${picked.candidates.length} projects — pass the project root explicitly via target:\n` +
|
|
51
|
+
picked.candidates.map((c) => `- ${c}`).join("\n")
|
|
52
|
+
}
|
|
53
|
+
const target = picked.state === "resolved" ? picked.abs : resolve(anchor, args?.target ? String(args.target) : "")
|
|
38
54
|
const explicit = Array.isArray(args?.docs) ? args.docs : null
|
|
39
55
|
if (args?.docs !== undefined && !Array.isArray(args.docs)) {
|
|
40
56
|
return `doc_check: \`docs\` must be an array of paths (got ${typeof args.docs}) — a bare string is refused rather than read as a whole-domain request; pass ["<path>"].`
|
package/agent-tools/eng.mjs
CHANGED
|
@@ -80,7 +80,7 @@ export const engTool = {
|
|
|
80
80
|
// **零副作用**(不清 token / 不重置 _advisorRuns / 不入列 ENG_ON_REMINDER /
|
|
81
81
|
// 不动 _lastEngState / 不触镜像缝);准 ⇒ 缺档格就地建档(writer:'main')+
|
|
82
82
|
// agent.manifest ← 结果(翻转面唯一赋值点——相位行当回合起活,§2.8 F2 放行行)。
|
|
83
|
-
const r = resolveEngineeringManifest(ctx.agent.cwd ?? ctx.cwd
|
|
83
|
+
const r = resolveEngineeringManifest(ctx.agent.cwd ?? ctx.cwd, { writer: "main" })
|
|
84
84
|
if (!r.ok && r.code !== "workspace") {
|
|
85
85
|
return `Error: cannot enter engineering mode — ${r.message} (mode unchanged)`
|
|
86
86
|
}
|
|
@@ -62,7 +62,7 @@ function overlapPaths(parent, launchSeq, childTouched) {
|
|
|
62
62
|
const sinceKeys = new Set(since.map(key))
|
|
63
63
|
const hits = childTouched.filter((p) => sinceKeys.has(key(p)))
|
|
64
64
|
// relative display form (touchedFilesNote precedent)
|
|
65
|
-
const cwd = parent?.cwd
|
|
65
|
+
const cwd = parent?.cwd
|
|
66
66
|
return hits.map((p) => {
|
|
67
67
|
const r = relative(cwd, p)
|
|
68
68
|
return r && !r.startsWith("..") && !isAbsolute(r) ? r : p
|
|
@@ -74,7 +74,7 @@ function childTouchedDisplay(child, cwd) {
|
|
|
74
74
|
const touched = child?._touchedFiles ?? []
|
|
75
75
|
if (touched.length === 0) return "(none recorded)"
|
|
76
76
|
const shown = touched.map((f) => {
|
|
77
|
-
const r = relative(cwd
|
|
77
|
+
const r = relative(cwd, f)
|
|
78
78
|
return r && !r.startsWith("..") && !isAbsolute(r) ? r : f
|
|
79
79
|
})
|
|
80
80
|
return shown.join(", ")
|
|
@@ -85,7 +85,7 @@ function touchedFilesNote(child, cwd) {
|
|
|
85
85
|
const touched = child?._touchedFiles ?? []
|
|
86
86
|
if (touched.length === 0) return ""
|
|
87
87
|
const shown = touched.map((f) => {
|
|
88
|
-
const r = relative(cwd
|
|
88
|
+
const r = relative(cwd, f)
|
|
89
89
|
return r && !r.startsWith("..") && !isAbsolute(r) ? r : f
|
|
90
90
|
})
|
|
91
91
|
return `\nTouched files: ${shown.join(", ")}`
|
package/agent-tools/manifest.mjs
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* - `write` ⇒ **点改**:读现档 → 设节点 → 落盘(`writeManifest` 内先 `validateManifest`——
|
|
12
12
|
* 编辑结果非法 ⇒ 拒 + 盘零变);**缺档 ⇒ 拒** + 引导 `init`(写不建档)。
|
|
13
13
|
*
|
|
14
|
-
* 写面白名单(判据单源 = `MANIFEST_SCHEMA`——钉死谓词):**
|
|
15
|
-
* 父键者(`version` / `phase` / `codePaths` 整键)+ `nestedKeys` 子键(`docRoot.*`
|
|
14
|
+
* 写面白名单(判据单源 = `MANIFEST_SCHEMA`——钉死谓词):**13 个可写节点** = `keys` 中**非** `nestedKeys`
|
|
15
|
+
* 父键者(`version` / `phase` / `codePaths` 整键)+ `nestedKeys` 子键(`docRoot.*` 六子键 / `index.*` 四子键);
|
|
16
16
|
* **两族整键写(传整个对象)⇒ 拒**;白名单外 ⇒ 拒(拼写保护——未知键写盘 = 静默死键,KD-M1-36 问题本体)。
|
|
17
17
|
* `value` 解析 = 同款口径 `settings.mjs` 的 `parseValue`(先 `JSON.parse`,失败取字面串;`"null"` ⇒ `null`
|
|
18
18
|
* ——`docRoot.*` 的「本面无」,KD-M1-37)。
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
import { existsSync } from "node:fs"
|
|
40
40
|
import { join, resolve } from "node:path"
|
|
41
41
|
import { MANIFEST_REL, MANIFEST_SCHEMA, initManifest, projectRootView, projectView, readManifest, writeManifest } from "../manifest.mjs"
|
|
42
|
-
import { DESC } from "../tools/shared.mjs"
|
|
42
|
+
import { DESC, LEDGER_PARAM_DESC_MANIFEST, PROJECT_PARAM_DESC } from "../tools/shared.mjs"
|
|
43
43
|
import { clearDeclarationCache } from "../conventions.mjs" // #1367 面⑦:写档成功 ⇒ 缓存失效(fix 轮接线)
|
|
44
44
|
|
|
45
45
|
/** 拒面统一前缀(设计 §2.10:`manifest <action>: refused — …`)——盘零变类拒绝一律经此抛错。 */
|
|
@@ -48,9 +48,9 @@ function refuse(action, reason) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** `target` 解析(先例 `eng.mjs` 的 `resolveEngineeringManifest` 缺省会话锚 ∥ `git.mjs` 的 `resolveBaseDir` 相对形归一):缺省 = 会话锚
|
|
51
|
-
* (`ctx.agent.cwd ?? ctx.cwd
|
|
51
|
+
* (`ctx.agent.cwd ?? ctx.cwd`);显式相对形按会话锚归一(绝对形原样)。 */
|
|
52
52
|
function resolveTarget(args, ctx) {
|
|
53
|
-
const base = ctx?.agent?.cwd ?? ctx?.cwd
|
|
53
|
+
const base = ctx?.agent?.cwd ?? ctx?.cwd
|
|
54
54
|
return args?.target ? resolve(base, String(args.target)) : resolve(base)
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -134,6 +134,9 @@ export const manifestTool = {
|
|
|
134
134
|
target: { type: "string", description: "Explicit target directory — defaults to the session anchor; relative paths resolve against it. A workspace target (several projects below it) is refused with the full project list — pass the project root you mean." },
|
|
135
135
|
key: { type: "string", description: "(write) Dot path of the node to set — version / phase / codePaths, or a sub-key of docRoot / index (e.g. docRoot.design / index.publicRepos); whole-object writes of those two families are refused", },
|
|
136
136
|
value: { type: "string", description: "(write) New value — parsed as JSON first (null / true / 5 / [\"a\"]), otherwise kept as a literal string; \"null\" is the explicit no-such-face for docRoot.<key>" },
|
|
137
|
+
ledger: { type: "string", description: LEDGER_PARAM_DESC_MANIFEST },
|
|
138
|
+
// 项目声明面(决策 40):`target` 缺位时才被读(定位参数优先);纯声明,不改查/建的目标目录。
|
|
139
|
+
project: { type: "string", description: PROJECT_PARAM_DESC },
|
|
137
140
|
},
|
|
138
141
|
required: ["action"],
|
|
139
142
|
},
|
|
@@ -80,8 +80,8 @@ function messageText(m) {
|
|
|
80
80
|
|
|
81
81
|
/** Truncate long content (~500 chars) with an explicit marker. The cut never splits a UTF-16
|
|
82
82
|
* surrogate pair (emoji etc.) — a lone high surrogate in tool output would be an eyesore at
|
|
83
|
-
* minimum; the send layer sanitizes it anyway, but clean output costs nothing
|
|
84
|
-
* safeSliceUTF16 same rule). */
|
|
83
|
+
* minimum; the send layer sanitizes it anyway, but clean output costs nothing
|
|
84
|
+
* (thincoder-core/text-budget.mjs `safeSliceUTF16` same rule). */
|
|
85
85
|
function truncateContent(text) {
|
|
86
86
|
const t = String(text ?? "")
|
|
87
87
|
if (t.length <= CONTENT_CAP) return t
|
|
@@ -190,7 +190,7 @@ function exceedsScanMax(file) {
|
|
|
190
190
|
|
|
191
191
|
/** 跨会话深查(回落面):单个槽文件,同 filter 面(§6.13 D-R19a——两道护栏逐字保留)。 */
|
|
192
192
|
function querySessionFile(pathArg, { role, kwRe, tool, since, until, direction, limit }, baseCwd) {
|
|
193
|
-
const file = isAbsolute(pathArg) ? pathArg : resolve(baseCwd
|
|
193
|
+
const file = isAbsolute(pathArg) ? pathArg : resolve(baseCwd, pathArg)
|
|
194
194
|
if (!existsSync(file)) return `Error: session file not found: ${file}`
|
|
195
195
|
if (exceedsScanMax(file)) return TOO_LARGE_ERROR
|
|
196
196
|
let text
|
|
@@ -221,7 +221,7 @@ function querySessionFile(pathArg, { role, kwRe, tool, since, until, direction,
|
|
|
221
221
|
* ——评审 #2:摘要必须含寻址字段——模型第二步深查 = 复制行内文件路径重调 path=)。
|
|
222
222
|
* 计数不可得 = `null` ⇒ `messages: —`(§6.25 判据句 4——禁显示数值 0:真 0 与未知可分)。 */
|
|
223
223
|
function discoverCwd(raw, baseCwd) {
|
|
224
|
-
const dir = resolve(baseCwd
|
|
224
|
+
const dir = resolve(baseCwd, raw)
|
|
225
225
|
let st = null
|
|
226
226
|
try {
|
|
227
227
|
st = statSync(dir)
|
|
@@ -358,7 +358,7 @@ export const readHistoryTool = {
|
|
|
358
358
|
// keyword stays a literal substring).
|
|
359
359
|
const kwRe = keyword ? new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : null
|
|
360
360
|
const tool = typeof a.tool === "string" && a.tool.length > 0 ? a.tool : null
|
|
361
|
-
const baseCwd = ctx.agent?.cwd
|
|
361
|
+
const baseCwd = ctx.agent?.cwd
|
|
362
362
|
|
|
363
363
|
const pathArg = typeof a.path === "string" && a.path.trim().length > 0 ? a.path.trim() : null
|
|
364
364
|
if (pathArg !== null) {
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
* 既有 import 面);`resolveReviewDocPaths` = 评审文档路径解析单源;
|
|
10
10
|
* - 2026-10-25(评审文档路径收正批):该解析器收为**单规则**——相对形按会话项目根 / 绝对形
|
|
11
11
|
* 照用 / 受理 = 项目内现存 `.md`;候选试探与歧义归一退场(判据正文 = `ADVISOR-GUARDS.md` §12)。
|
|
12
|
+
* - 2026-11-02(path-resolution-unify 批):路径判定改调统一解析层(`../path-resolve.mjs`)——
|
|
13
|
+
* 本面只留受理判据;拒因词 `no-project-root` 退役,拆成 `ambiguous-path`(判不出唯一基根)
|
|
14
|
+
* ∥ `dangling-path`(无项目根)。
|
|
12
15
|
*/
|
|
13
16
|
import { existsSync, statSync } from "node:fs"
|
|
14
|
-
import { isAbsolute, join, relative,
|
|
17
|
+
import { isAbsolute, join, relative, sep } from "node:path"
|
|
15
18
|
import { DEFAULT_MANIFEST, docRootPaths, readManifest } from "../manifest.mjs"
|
|
16
19
|
import { owningProject, projectRootView } from "../manifest-discovery.mjs"
|
|
20
|
+
import { resolvePathTarget } from "../path-resolve.mjs"
|
|
17
21
|
|
|
18
22
|
/** ABS 归一(cwd 相对 → cwd 拼接)——陈旧判定 / 冻结拦截 / 评审范围键同源(ADVISOR-GUARDS.md §5 E-4)。 */
|
|
19
23
|
export function normAbs(p, cwd) {
|
|
@@ -66,26 +70,40 @@ function isInside(p, root) {
|
|
|
66
70
|
|
|
67
71
|
/**
|
|
68
72
|
* **评审文档路径解析单规则**(判据正文 = `docs/core/design/ADVISOR-GUARDS.md` §12)——逐文档:
|
|
69
|
-
* ①
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* ① 路径判定全部经统一解析层(`kind:"file"` / `order:"root-first"` / `bases` = 无 /
|
|
74
|
+
* `scanBases` = `docRoot` 各键——只喂同名候选,不作受理基底;本面受理面零放宽);
|
|
75
|
+
* ② 判不出唯一基根(层判 `ambiguous`——工作区锚)⇒ `ambiguous-path` + 候选全列;
|
|
76
|
+
* 无项目根(层判 `dangling.level:"base"`)⇒ `dangling-path`;
|
|
77
|
+
* ③ 相对形 ∧ 锚无项目根 ⇒ `dangling-path`(受理判据「必须在项目内」——档层无根时相对形无基可挂);
|
|
78
|
+
* 相对形越根 ⇒ `outside-project`;
|
|
79
|
+
* ④ 绝对形照用(零根试探、零拼接);不住在任何项目里(`owningProject` 空)⇒ `outside-project`;
|
|
80
|
+
* ⑤ 受理 = 现存普通文件 ∧ `.md`(大小写不敏感);否则 `not-doc-file`。
|
|
81
|
+
* **零试探**:层判不出唯一即停(用户 2026-10-25 11:41「为啥要试探?不能要求写对吗?」)。
|
|
75
82
|
* 非字符串 / 空白串项**静默跳过**(同 docSetKey 既有过滤语义——不进 resolved / invalid)。
|
|
76
|
-
* @returns {{ resolved: Map<string,string>, invalid: Array<{doc: string, resolved: string|null, reason: string
|
|
77
|
-
*
|
|
78
|
-
*
|
|
83
|
+
* @returns {{ resolved: Map<string,string>, invalid: Array<{doc: string, resolved: string|null, reason: string,
|
|
84
|
+
* candidates?: string[], sameName?: string[]}> }}
|
|
85
|
+
* `reason` ∈ `ambiguous-path` / `dangling-path` / `outside-project` / `not-doc-file`(标识行恒
|
|
86
|
+
* `criterion=scope-not-doc`,四值只作 `reason=`);`resolved` 项 = 解析后绝对路径(无基面 ⇒ `null`)。
|
|
79
87
|
*/
|
|
80
88
|
export function resolveReviewDocPaths(documents, cwd) {
|
|
81
|
-
const view = projectRootView(cwd
|
|
89
|
+
const view = projectRootView(cwd)
|
|
90
|
+
const scanBases = resolveReviewRootsFor(cwd)
|
|
82
91
|
const resolved = new Map()
|
|
83
92
|
const invalid = []
|
|
84
93
|
for (const doc of (documents ?? [])) {
|
|
85
94
|
if (typeof doc !== "string" || !doc.trim()) continue
|
|
86
95
|
const abs = /^[a-zA-Z]:[\\/]/.test(doc) || doc.startsWith("/") || doc.startsWith("\\\\")
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
const r = resolvePathTarget(cwd, doc, { kind: "file", order: "root-first", crossRepo: "dangling", scanBases })
|
|
97
|
+
if (r.state === "ambiguous") { invalid.push({ doc, resolved: null, reason: "ambiguous-path", candidates: r.candidates }); continue }
|
|
98
|
+
// 相对形 ∧ 锚无项目根 ⇒ 无基可挂(受理判据「必须在项目内」)——**与盘上有没有那档无关**:
|
|
99
|
+
// 同一锚态同一个词(KD-8),否则拒因词成了磁盘状态的函数。
|
|
100
|
+
if (!abs && view.state !== "ok") { invalid.push({ doc, resolved: null, reason: "dangling-path" }); continue }
|
|
101
|
+
if (r.state === "dangling") {
|
|
102
|
+
if (r.level === "base") { invalid.push({ doc, resolved: null, reason: "dangling-path" }); continue }
|
|
103
|
+
invalid.push({ doc, resolved: r.tried?.[0] ?? null, reason: "not-doc-file", sameName: r.sameName })
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
const p = r.abs
|
|
89
107
|
if (!abs && !isInside(p, view.root)) { invalid.push({ doc, resolved: p, reason: "outside-project" }); continue }
|
|
90
108
|
if (abs && !owningProject(p, cwd)) { invalid.push({ doc, resolved: p, reason: "outside-project" }); continue }
|
|
91
109
|
if (!isDocFile(p)) { invalid.push({ doc, resolved: p, reason: "not-doc-file" }); continue }
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 已实现:F2(round 枚举 + 任务书参数门/拼装)· F6(files 声明面拦截谓词)。
|
|
5
5
|
* 2026-10-24(AGENT-LOOP-SUBAGENT.md §6.22):任务书单通道 = 批次档 §2——工程角色
|
|
6
6
|
* (eng-coder / eng-designer)零任务书文本参数,八个自由文本键(键在场即拒)命中即点名拒收;
|
|
7
|
-
* 非工程角色(explore / plan / coder)保持四项核心 + `scope`。
|
|
7
|
+
* 非工程角色(explore / plan / coder / tester)保持四项核心 + `scope`。
|
|
8
8
|
* 裁撤(2026-09-17 主 agent 裁定——不落代码):
|
|
9
9
|
* - F4 修复轮窄带:全量勘察散文 marker 不可机判(误拒风险不可接受)→ 由 F2 的 round 参数承载轮次语义;
|
|
10
10
|
* - F5 advisor 角色:advisor 不入 spawn 通道(评审走 advisor 工具)——enum 收正为
|
|
@@ -26,7 +26,7 @@ const TASK_BOOK_CORE_MIN = {
|
|
|
26
26
|
reportFormat: "交付表 + 文件路径 + 验证命令与结果。",
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/** 非工程角色样例(核心四项 + `scope`)——explore / plan / coder。 */
|
|
29
|
+
/** 非工程角色样例(核心四项 + `scope`)——explore / plan / coder / tester。 */
|
|
30
30
|
export const PLAIN_TASK_BOOK_MIN = { ...TASK_BOOK_CORE_MIN, scope: "只读:相关源 / 设计档;不写任何文件、不碰 bench/。" }
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -80,7 +80,7 @@ export function rejectEngFreeTextParams(args, role) {
|
|
|
80
80
|
if (offending.length === 0) return
|
|
81
81
|
const names = offending
|
|
82
82
|
.map((name) => (name === "designPoints"
|
|
83
|
-
? `${name} (designPoints is retired for every role — write §2, or scope for explore/plan/coder)`
|
|
83
|
+
? `${name} (designPoints is retired for every role — write §2, or scope for explore/plan/coder/tester)`
|
|
84
84
|
: name))
|
|
85
85
|
.join(" / ")
|
|
86
86
|
const required = ENG_REQUIRED_NAMES[role] ?? "role · batchDoc (readable) · round"
|
|
@@ -182,22 +182,18 @@ const PROCESS_BASENAMES = new Set(["changelog.md"])
|
|
|
182
182
|
const MANIFEST_BASENAME = "project-manifest.json"
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
|
-
* F6 files 声明面拦截谓词(ENGINEERING-MODE-V2-MODULE-DELEGATION §2.2
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* 序不变)。调用点 = normalizeFileList(subagent-scheduler.mjs——本谓词在循环后跑——
|
|
191
|
-
* 目录错误先于过程档错误)。
|
|
185
|
+
* F6 files 声明面拦截谓词(ENGINEERING-MODE-V2-MODULE-DELEGATION §2.2):过程档(父侧职责)与项目状态档
|
|
186
|
+
* → 收集全部违规后抛合并错误(fail-closed——错误即工具结果 JSON——无排队残留)。目录声明不在本谓词
|
|
187
|
+
* (normalizeFileList 循环内先行拒——错误序不变)。调用点 = normalizeFileList(subagent-scheduler.mjs——
|
|
188
|
+
* 本谓词在循环后跑——目录错误先于过程档错误)。
|
|
189
|
+
* 产品面路径(含仓根 `scripts/**` · `test/**`)本就该进 `files` 声明面——工程工具面退役后不再有这一臂。
|
|
192
190
|
*/
|
|
193
|
-
export function
|
|
191
|
+
export function rejectProcessDocPaths(files) {
|
|
194
192
|
const violations = []
|
|
195
193
|
for (const f of Array.isArray(files) ? files : []) {
|
|
196
194
|
const segs = String(f).replace(/\\/g, "/").split("/").filter((s) => s)
|
|
197
195
|
const base = (segs[segs.length - 1] ?? "").toLowerCase()
|
|
198
|
-
if (
|
|
199
|
-
violations.push(`engineering-tools face path ${f} must not be listed in files — scripts/* at the repo root or a direct child package is the engineering-tools face, not a content product (applies to every session, not engineering-mode-scoped; files carries source/test/design-doc paths only)`)
|
|
200
|
-
} else if (PROCESS_BASENAMES.has(base)) {
|
|
196
|
+
if (PROCESS_BASENAMES.has(base)) {
|
|
201
197
|
violations.push(`Parent-side maintained file ${f} must not be listed in files — reconciliation is the parent's duty; use the design-doc path if you need to edit a design doc`)
|
|
202
198
|
} else if (base === MANIFEST_BASENAME) {
|
|
203
199
|
violations.push(`Manifest file ${f} must not be listed in files — PROJECT-MANIFEST.json is the project state manifest and the main agent is its only writer (M1 write gate); this files-domain check is the second line of defense`)
|