@thincoder/core 0.9.4 → 0.10.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 +63 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +14 -14
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +23 -13
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +55 -294
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +63 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +19 -15
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +90 -30
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +11 -11
- package/agent-tools/advisor-settle.mjs +17 -17
- package/agent-tools/advisor.mjs +38 -43
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +23 -20
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +65 -34
- package/agent-tools/batch-paths.mjs +186 -0
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +22 -119
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +58 -58
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +18 -13
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +3 -3
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +29 -273
- package/agent-tools/subagent-async.mjs +36 -31
- package/agent-tools/subagent-panel.mjs +38 -9
- package/agent-tools/subagent-run.mjs +44 -29
- package/agent-tools/subagent-scheduler.mjs +29 -28
- package/agent-tools/subagent-spawn.mjs +57 -25
- package/agent-tools/subagent.mjs +46 -66
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +20 -351
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +12 -7
- package/config-watch.mjs +83 -0
- package/config.mjs +17 -4
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/git/checkpoint.mjs +4 -8
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +35 -7
- package/ledger-migrate.mjs +299 -0
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +11 -5
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-specs.mjs +131 -66
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +263 -0
- package/peer-domains.mjs +100 -71
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +19 -7
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +1 -2
- package/prompts/persona-engineering.md +41 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +10 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-gc.mjs +67 -6
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +90 -18
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -236
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/question.md +1 -0
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +10 -11
- package/tools/edit-diff.mjs +33 -25
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +11 -12
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/glob-dialect.mjs +3 -3
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/search.mjs +2 -2
- package/tools/shared.mjs +9 -7
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/tools/edit-batch.mjs
CHANGED
|
@@ -12,24 +12,23 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { readFile } from "node:fs/promises"
|
|
14
14
|
import { resolveInCwd, normalizeEOL, joinWithEol, gitDiffOne, autoSyntaxCheck } from "./shared.mjs"
|
|
15
|
-
// edit-batch ↔ edit-diff
|
|
15
|
+
// edit-batch ↔ edit-diff 循环引用:两侧导入的都是函数声明(提升初始化),
|
|
16
16
|
// 仅在调用期使用——ESM 循环下安全(无模块求值期取值)。
|
|
17
|
-
// 写盘经单一写路径点(§2.13.5)——本档不再直调 fs
|
|
17
|
+
// 写盘经单一写路径点(§2.13.5)——本档不再直调 fs;回执组装共用 edit-diff 的 composeEditReceipt。
|
|
18
18
|
import { writeThroughPath } from "./write-path.mjs"
|
|
19
19
|
// EDIT.md §6:批量条目判定+应用共用 edit-diff(EDIT.md §4 分支 0 单行替换 + 行级 LCS——零重叠→替换即删);
|
|
20
20
|
// EDIT.md §5:edits 互斥错误文本随前置校验分支迁出至 edit-diff.mjs。
|
|
21
|
-
//
|
|
22
|
-
import { assertEditArgsExclusive, validateEditEntry, computeEditEntry, splitLines, EMPTY_NEW_STRING_LINE, deleteTarget, composeEditReceipt } from "./edit-diff.mjs"
|
|
21
|
+
// 回执组装共用 edit-diff 的 composeEditReceipt(单点——缺省回执 = base + 写入点上下文)。
|
|
22
|
+
import { assertEditArgsExclusive, assertEditsContainer, assertEditEntries, validateEditEntry, computeEditEntry, splitLines, EMPTY_NEW_STRING_LINE, EDIT_ENTRY_NO_PATH, EDIT_ABORT_PREFIX, editEntryLabel, deleteTarget, composeEditReceipt } from "./edit-diff.mjs"
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Apply the `edits` array form: multi-file atomic replacement. Throws on any
|
|
26
26
|
* failure (atomic — nothing written). Returns the per-entry result text (joined).
|
|
27
27
|
*/
|
|
28
28
|
export async function applyEditBatch(args, ctx) {
|
|
29
|
-
|
|
30
|
-
throw new Error("edits must be a non-empty array of {path, old_string | line/startLine+endLine, new_string}")
|
|
31
|
-
}
|
|
29
|
+
assertEditsContainer(args.edits)
|
|
32
30
|
assertEditArgsExclusive(args)
|
|
31
|
+
assertEditEntries(args.edits) // #325 条目预扫(非对象条目 ⇒ 含下标成形错误;不读盘)
|
|
33
32
|
// 原子:先全量检查(所有文件的替换都可执行)——任一失败全不写。
|
|
34
33
|
// 2026-09-01 缺陷修复(TOOLS.md §9 ②"同文件多条规则"):同一 path 的多条编辑
|
|
35
34
|
// 按序**串行累积应用**——第 n 条基于前 n-1 条已应用后的累积内容做匹配与替换
|
|
@@ -40,9 +39,9 @@ export async function applyEditBatch(args, ctx) {
|
|
|
40
39
|
// 2026-09-05 用户裁定:顶层 path + edits 并存合法化——`e.path || args.path`(条目优先;
|
|
41
40
|
// 缺省入口仅在此兜底);两者皆无 → 原错误文本(措辞补顶层选项)。
|
|
42
41
|
const p = e.path || args.path
|
|
43
|
-
if (!p) throw new Error(
|
|
42
|
+
if (!p) throw new Error(EDIT_ENTRY_NO_PATH)
|
|
44
43
|
// 前置校验(空 old / 非字符串 new——文本同 edit-diff 批量形态;读盘前校验)
|
|
45
|
-
validateEditEntry(e, { label:
|
|
44
|
+
validateEditEntry(e, { label: editEntryLabel(p), rich: false })
|
|
46
45
|
const abs = resolveInCwd(ctx, p)
|
|
47
46
|
let g = groups.get(abs)
|
|
48
47
|
if (!g) {
|
|
@@ -61,7 +60,7 @@ export async function applyEditBatch(args, ctx) {
|
|
|
61
60
|
const out = computeEditEntry(g.content, e, {
|
|
62
61
|
path: g.path,
|
|
63
62
|
absPath: g.abs,
|
|
64
|
-
abortPrefix:
|
|
63
|
+
abortPrefix: EDIT_ABORT_PREFIX,
|
|
65
64
|
})
|
|
66
65
|
prepared.push({
|
|
67
66
|
g,
|
|
@@ -92,7 +91,7 @@ export async function applyEditBatch(args, ctx) {
|
|
|
92
91
|
for (const p of prepared) {
|
|
93
92
|
// #4(2026-09-01 交付评审尾巴):与单文件路径对齐——每条结果附 git diff +
|
|
94
93
|
// autoSyntaxCheck(同文件多条会重复 diff/检查,换取格式一致、实现零分支)
|
|
95
|
-
const diff = gitDiffOne(ctx.cwd, p.g.abs)
|
|
94
|
+
const diff = await gitDiffOne(ctx.cwd, p.g.abs)
|
|
96
95
|
const base = p.deleted
|
|
97
96
|
? `Deleted ${deleteTarget(p)} of ${p.g.path}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(p.g.abs)}`
|
|
98
97
|
: `Edited ${p.g.path}: replaced ${p.occurrences} occurrence(s)${p.note ? ` — ${p.note}` : ""}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(p.g.abs)}`
|
package/tools/edit-diff.mjs
CHANGED
|
@@ -45,6 +45,18 @@ export const EMPTY_NEW_STRING = "empty new_string — for deletion, keep the con
|
|
|
45
45
|
export const EMPTY_NEW_STRING_LINE = "empty new_string with line-based targeting is an explicit error — OMIT new_string to delete the line/range (deleting by number is explicit intent; an empty replacement is a mistake, not a delete)"
|
|
46
46
|
export const EDIT_ARGS_MUTEX = "edits array is mutually exclusive with top-level old_string/new_string/line/startLine/endLine — a top-level path is allowed (default for entries without their own path); provide each change's targeting (old_string or line range) and new_string inside its edits entry"
|
|
47
47
|
|
|
48
|
+
// ─── #325 入参守卫文案(单源——EDIT.md §5 / §8 D-7:核 edit-batch 与 ACP 桥 bridge.mjs 同调用)───
|
|
49
|
+
/** 容器级成形错误(真值非数组 / 空数组——同一错误面)。 */
|
|
50
|
+
export const EDITS_CONTAINER_ERROR = "edits must be a non-empty array of {path, old_string | line/startLine+endLine, new_string}"
|
|
51
|
+
/** 条目级成形错误(i = 数组下标;非对象条目 = null / 字符串 / 数字 / 数组)。 */
|
|
52
|
+
export const editsEntryError = (i) => `edits[${i}] must be an object of {path, old_string | line/startLine+endLine, new_string}`
|
|
53
|
+
/** 条目缺 path 且无顶层默认路径。 */
|
|
54
|
+
export const EDIT_ENTRY_NO_PATH = "each edit must have a path — give each entry its own path or pass a top-level path"
|
|
55
|
+
/** 批量原子失败前缀(条目判定失败时拼在消息前的 abortPrefix)。 */
|
|
56
|
+
export const EDIT_ABORT_PREFIX = "edit aborted (atomic — no files written): "
|
|
57
|
+
/** 批量条目校验前缀(validateEditEntry 的 opts.label)。 */
|
|
58
|
+
export const editEntryLabel = (path) => `edit for ${path}: `
|
|
59
|
+
|
|
48
60
|
/** D1(2026-09-08):条目带按行号改参数(line / startLine / endLine 任一)。 */
|
|
49
61
|
export function hasLineParams(entry) {
|
|
50
62
|
return entry.line !== undefined || entry.startLine !== undefined || entry.endLine !== undefined
|
|
@@ -132,13 +144,28 @@ export function assertEditArgsExclusive(args) {
|
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
/** 入参容器守卫(#325——EDIT.md §5):edits 真值非数组 / 空数组 ⇒ 成形错误(同句单源——
|
|
148
|
+
* 核 edit-batch 与 ACP 桥 bridge.mjs 同调用)。批量判定序:容器 → 互斥 → 条目预扫 → 条目循环。 */
|
|
149
|
+
export function assertEditsContainer(edits) {
|
|
150
|
+
if (!Array.isArray(edits) || edits.length === 0) throw new Error(EDITS_CONTAINER_ERROR)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 入参条目守卫(#325——EDIT.md §5):条目非对象(null / 字符串 / 数字 / 数组)⇒ 含下标成形
|
|
154
|
+
* 错误——预扫于条目循环之前(不读盘、不触盘、零部分应用)。 */
|
|
155
|
+
export function assertEditEntries(edits) {
|
|
156
|
+
for (let i = 0; i < edits.length; i++) {
|
|
157
|
+
const e = edits[i]
|
|
158
|
+
if (e === null || typeof e !== "object" || Array.isArray(e)) throw new Error(editsEntryError(i))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
135
162
|
/**
|
|
136
163
|
* 前置校验——内容形态:空 old / 非字符串 new;按行号改形态(D1,2026-09-08;阶段 2
|
|
137
164
|
* 删行——EDIT.md §5):与 old_string 互斥 / line 与 startLine|endLine 互斥 /
|
|
138
165
|
* startLine+endLine 须成对 / 正整数 / endLine ≥ startLine / replace_all 不适用 /
|
|
139
166
|
* new_string:省略(undefined)= 删行 / 显式空串 = 显式错(防误删)/ 其他类型报错。
|
|
140
167
|
* error 文本按调用形态(单形态 rich / 批量 label)。
|
|
141
|
-
* opts: { label = ""(批量前缀
|
|
168
|
+
* opts: { label = ""(批量前缀 = editEntryLabel(path)), rich = true(单形态完整文本) }
|
|
142
169
|
*/
|
|
143
170
|
export function validateEditEntry(entry, opts = {}) {
|
|
144
171
|
const label = opts.label ?? ""
|
|
@@ -213,7 +240,7 @@ function isSingleLineReplace(entry) {
|
|
|
213
240
|
* LCS 替换 / replace_all 字面)→
|
|
214
241
|
* 元数据(受影响区首行/行数差/次数——recordWrite 与结果回显用)。
|
|
215
242
|
* 返回 { updated, editStartLine, lineShift, occurrences, note?, deleted? };失败抛错(含路径/引导)。
|
|
216
|
-
* opts: { path, abortPrefix(批量
|
|
243
|
+
* opts: { path, abortPrefix(批量 = EDIT_ABORT_PREFIX) }
|
|
217
244
|
*/
|
|
218
245
|
/**
|
|
219
246
|
* P15.11(2026-09-05 用户裁定——edit 连续失败分析):old_string 逐字 not-found 时——
|
|
@@ -345,7 +372,7 @@ export async function runSingleEdit(args, ctx) {
|
|
|
345
372
|
op: "write",
|
|
346
373
|
record: { type: "edit", startLine: out.editStartLine, shift: out.lineShift },
|
|
347
374
|
})
|
|
348
|
-
const diff = gitDiffOne(ctx.cwd, abs)
|
|
375
|
+
const diff = await gitDiffOne(ctx.cwd, abs)
|
|
349
376
|
const baseResult = out.deleted
|
|
350
377
|
? `Deleted ${deleteTarget(out)} of ${args.path}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(abs)}`
|
|
351
378
|
: `Edited ${args.path}: replaced ${out.occurrences} occurrence(s)${out.note ? ` — ${out.note}` : ""}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(abs)}`
|
|
@@ -360,29 +387,10 @@ export async function runSingleEdit(args, ctx) {
|
|
|
360
387
|
})
|
|
361
388
|
}
|
|
362
389
|
|
|
363
|
-
// ─── edit 回执形态注入缝(#69——「回执形态按端注入」,形态参 §2.13.5 注入缝)──────────
|
|
364
390
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* 契约:`format(fields) → string | null | undefined | Promise<同>`——null / undefined =
|
|
368
|
-
* 未处理 ⇒ 回默认回执;其余返回值即该条回执。
|
|
369
|
-
*/
|
|
370
|
-
let injectedReceipt = null
|
|
371
|
-
export function configureEditReceipt(impl) {
|
|
372
|
-
injectedReceipt = impl && typeof impl.format === "function" ? impl : null
|
|
373
|
-
}
|
|
374
|
-
/** 撤销注入(测试与端装配生命周期用——缺省态 = CLI 回执)。 */
|
|
375
|
-
export function resetEditReceipt() { injectedReceipt = null }
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* edit 回执组装单点(单形态 / 数组形态共用——#69):先组默认回执(base + 写入点上下文),
|
|
379
|
-
* 注入面命中 ⇒ 以其 format(fields) 返回值为准(`fields.default` = 默认回执原文,供端侧改写)。
|
|
391
|
+
* edit 回执组装单点(单形态 / 数组形态共用):`base` + 写入点上下文——唯一形态
|
|
392
|
+
* (#69 注入面已退场;余字段为调用面统一形,不参与组装)。
|
|
380
393
|
*/
|
|
381
394
|
export async function composeEditReceipt(fields) {
|
|
382
|
-
|
|
383
|
-
if (injectedReceipt) {
|
|
384
|
-
const out = await injectedReceipt.format({ ...fields, default: defaultText })
|
|
385
|
-
if (out !== null && out !== undefined) return String(out)
|
|
386
|
-
}
|
|
387
|
-
return defaultText
|
|
395
|
+
return await appendWriteContext(fields.abs, fields.writeLine, fields.base)
|
|
388
396
|
}
|
package/tools/exec-run.mjs
CHANGED
|
@@ -14,10 +14,17 @@
|
|
|
14
14
|
* · `run(cmd, args, opts) → Promise<string> | string`——成功给 stdout;
|
|
15
15
|
* · 失败以 Error reject——`.stdout` / `.stderr` / `.code` 与 `execFileSync` 错误同形
|
|
16
16
|
* (端侧现形 = 宿主 `runInterruptible`);
|
|
17
|
-
* · `opts = { cwd?, timeout?, signal? }`——**默认径忽略 signal**(execFileSync 不可中断,
|
|
18
|
-
* 与现行 CLI
|
|
17
|
+
* · `opts = { cwd?, timeout?, signal?, env? }`——**默认径忽略 signal**(execFileSync 不可中断,
|
|
18
|
+
* 与现行 CLI 行为逐字同);`env` 缺省(undefined)不设 = 继承(逐字零变),给定则透传
|
|
19
|
+
* (Electron 宿主旗标径 = TOOLS.md §6.18);注入径自决(如 abort ⇒ 树杀)。
|
|
20
|
+
*
|
|
21
|
+
* **可中断执行器上提(R3 · 桌面功能对位批 · #523②)**:`runInterruptible` 自端侧现形
|
|
22
|
+
* (`thincoder-vscode/src/tools/shared.mjs`)**纯搬**落核(零语义改 —— KD-T2);两端改指核件
|
|
23
|
+
* (VSC `configureExecRun({ run: runInterruptible })` 同轮改指;桌面 `src/main/exec-run.mjs` 消费)。
|
|
24
|
+
* 树杀 = 本仓 `tools/process-tree.mjs` 单源(`killProcessTree` —— 转口,零第二实现)。
|
|
19
25
|
*/
|
|
20
|
-
import { execFileSync } from "node:child_process"
|
|
26
|
+
import { execFileSync, spawn } from "node:child_process"
|
|
27
|
+
import { killProcessTree } from "./process-tree.mjs"
|
|
21
28
|
|
|
22
29
|
let injected = null
|
|
23
30
|
|
|
@@ -29,10 +36,11 @@ export function configureExecRun(impl) {
|
|
|
29
36
|
/** 撤销注入(测试与端装配生命周期用——缺省态 = execFileSync 默认径)。 */
|
|
30
37
|
export function resetExecRun() { injected = null }
|
|
31
38
|
|
|
32
|
-
/** 默认径(CLI 语义——utf8 + timeout + stdio ignore/pipe/pipe;cwd 未给则不设)。 */
|
|
39
|
+
/** 默认径(CLI 语义——utf8 + timeout + stdio ignore/pipe/pipe;cwd ∕ env 未给则不设)。 */
|
|
33
40
|
function defaultRun(cmd, args, opts) {
|
|
34
41
|
const options = { encoding: "utf8", timeout: opts.timeout, stdio: ["ignore", "pipe", "pipe"] }
|
|
35
42
|
if (opts.cwd !== undefined) options.cwd = opts.cwd
|
|
43
|
+
if (opts.env !== undefined) options.env = opts.env
|
|
36
44
|
return execFileSync(cmd, args, options)
|
|
37
45
|
}
|
|
38
46
|
|
|
@@ -41,3 +49,93 @@ export async function runCommand(cmd, args, opts = {}) {
|
|
|
41
49
|
if (injected) return await injected.run(cmd, args, opts)
|
|
42
50
|
return defaultRun(cmd, args, opts)
|
|
43
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 可中断执行器(**上提件** —— 端侧 `configureExecRun` 供值;原住 `thincoder-vscode/src/tools/shared.mjs`,
|
|
55
|
+
* 逐字搬运 —— R3 · #523②):`spawn`(非 `execSync` —— 同步执行阻塞事件循环:长 lint ∕ verify 期间
|
|
56
|
+
* Stop 点击连“送达”都做不到)跑命令,abort ∕ timeout ⇒ 整树杀(本档 `killProcessTree` 单源)。
|
|
57
|
+
*
|
|
58
|
+
* 成功 → resolve stdout 串(与 `execFileSync` 调用点兼容)。
|
|
59
|
+
* 非零退出 ∕ spawn 失败 ∕ 超时 ∕ 中止 → reject 携 `.stdout` / `.stderr` / `.code`(同 `execFileSync` 错误形)。
|
|
60
|
+
*/
|
|
61
|
+
export function runInterruptible(cmd, args, opts = {}) {
|
|
62
|
+
const { cwd, timeout, signal, env } = opts
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
let child
|
|
65
|
+
try {
|
|
66
|
+
child = spawn(cmd, args, {
|
|
67
|
+
cwd, env: env ?? process.env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true,
|
|
68
|
+
// First-line abort guard: Node kills the direct child on signal abort.
|
|
69
|
+
...(signal ? { signal } : {}),
|
|
70
|
+
})
|
|
71
|
+
} catch (e) {
|
|
72
|
+
reject(e)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
let stdout = "", stderr = "", settled = false, timer = null, kickTimer = null, mode = null
|
|
76
|
+
|
|
77
|
+
const KICK_MS = 3000
|
|
78
|
+
const finish = (err, out) => {
|
|
79
|
+
if (settled) return
|
|
80
|
+
settled = true
|
|
81
|
+
if (timer) clearTimeout(timer)
|
|
82
|
+
if (kickTimer) clearTimeout(kickTimer)
|
|
83
|
+
signal?.removeEventListener("abort", onAbort)
|
|
84
|
+
if (err) {
|
|
85
|
+
err.stdout = stdout
|
|
86
|
+
err.stderr = stderr
|
|
87
|
+
reject(err)
|
|
88
|
+
} else {
|
|
89
|
+
resolve(out)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Kill the whole tree on abort/timeout — grandchildren (npm test's children)
|
|
93
|
+
// must die too, or they keep the stdout/stderr pipes and "close" never fires.
|
|
94
|
+
const killTree = () => { try { killProcessTree(child) } catch { /* already gone */ } }
|
|
95
|
+
// Kick-clockback: settle even if "close" never arrives (a grandchild that
|
|
96
|
+
// mocks kill signal, or a wedged pipe). Prevents a hang + leaked pipes.
|
|
97
|
+
const armKick = (err) => {
|
|
98
|
+
if (kickTimer) return
|
|
99
|
+
kickTimer = setTimeout(() => finish(err), KICK_MS)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const onAbort = () => {
|
|
103
|
+
if (mode) return
|
|
104
|
+
mode = "abort"
|
|
105
|
+
const e = new Error("aborted by user (Stop)")
|
|
106
|
+
e.name = "AbortError"
|
|
107
|
+
killTree()
|
|
108
|
+
armKick(e)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (timeout) {
|
|
112
|
+
timer = setTimeout(() => {
|
|
113
|
+
if (mode) return
|
|
114
|
+
mode = "timeout"
|
|
115
|
+
const e = new Error(`timed out after ${timeout}ms`)
|
|
116
|
+
e.name = "TimeoutError"
|
|
117
|
+
killTree()
|
|
118
|
+
armKick(e)
|
|
119
|
+
}, timeout)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
child.stdout?.on("data", (d) => { stdout += d })
|
|
123
|
+
child.stderr?.on("data", (d) => { stderr += d })
|
|
124
|
+
child.on("error", (e) => { if (e.name === "AbortError") return; finish(e) })
|
|
125
|
+
child.on("close", (code) => {
|
|
126
|
+
if (mode === "abort") { const e = new Error("aborted by user (Stop)"); e.name = "AbortError"; return finish(e) }
|
|
127
|
+
if (mode === "timeout") { const e = new Error(`timed out after ${timeout}ms`); e.name = "TimeoutError"; return finish(e) }
|
|
128
|
+
if (code === 0) finish(null, stdout)
|
|
129
|
+
else {
|
|
130
|
+
const e = new Error(`command failed with exit code ${code}`)
|
|
131
|
+
e.code = code
|
|
132
|
+
finish(e)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (signal) {
|
|
137
|
+
if (signal.aborted) { onAbort(); return }
|
|
138
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
package/tools/execute.mjs
CHANGED
|
@@ -28,13 +28,14 @@ import { spawn } from "node:child_process"
|
|
|
28
28
|
import { resolve } from "node:path"
|
|
29
29
|
import { DESC } from "./shared.mjs"
|
|
30
30
|
import { killProcessTree } from "./process-tree.mjs"
|
|
31
|
+
import { nodeChildEnv } from "./node-child.mjs"
|
|
31
32
|
|
|
32
33
|
const MAX_SCRIPT = 50_000
|
|
33
34
|
const MAX_OUTPUT = 50_000
|
|
34
35
|
const DEFAULT_TIMEOUT = 30_000
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* 超时错误文本——带重试引导(
|
|
38
|
+
* 超时错误文本——带重试引导("下一跳"):数字 = 实际生效的
|
|
38
39
|
* timeoutMs(Math.min(t, 600_000) 或默认 30s)——上限 600000 与 schema/头注/execute.md 一致。
|
|
39
40
|
*/
|
|
40
41
|
const timeoutErrorText = (timeoutMs) =>
|
|
@@ -83,6 +84,8 @@ function runNode(childArgs, baseDir, timeoutMs, signal) {
|
|
|
83
84
|
return new Promise((resolvePromise) => {
|
|
84
85
|
const child = spawn(process.execPath, childArgs, {
|
|
85
86
|
cwd: baseDir,
|
|
87
|
+
// TOOLS.md §6.18:Electron 宿主 execPath 属 Electron 族二进制——补旗标按 node 语义启动
|
|
88
|
+
env: nodeChildEnv(),
|
|
86
89
|
stdio: ["ignore", "pipe", "pipe"],
|
|
87
90
|
windowsHide: true,
|
|
88
91
|
// POSIX detached → 子进程为组首——killProcessTree 的 -pid 组杀可达孙进程(win 用
|
|
@@ -223,7 +226,7 @@ export const executeTool = {
|
|
|
223
226
|
if (code.length > MAX_SCRIPT) {
|
|
224
227
|
return `Error: script too large (${code.length} > ${MAX_SCRIPT} bytes). Split into smaller scripts or use individual tools.`
|
|
225
228
|
}
|
|
226
|
-
// inline mode: pure node ESM — no prelude, nothing injected
|
|
229
|
+
// inline mode: pure node ESM — no prelude, nothing injected.
|
|
227
230
|
childArgs = ["--input-type=module", "--eval", code]
|
|
228
231
|
}
|
|
229
232
|
|
package/tools/file.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { runSingleEdit } from "./edit-diff.mjs";
|
|
|
19
19
|
import { writeThroughPath, clearDirty, clearLastWrite, lastWriteOf, isDirty } from "./write-path.mjs";
|
|
20
20
|
import { specForModel } from "../config.mjs";
|
|
21
21
|
import { createHash } from "node:crypto";
|
|
22
|
-
import { mkdir, readFile, stat
|
|
22
|
+
import { mkdir, readFile, stat } from "node:fs/promises";
|
|
23
23
|
import { join, relative, dirname } from "node:path";
|
|
24
24
|
|
|
25
25
|
const MAX_FILE_READ_BYTES = 10_000_000
|
|
@@ -178,11 +178,6 @@ export const readImageTool = {
|
|
|
178
178
|
text: `[read_image: ${args.path} (${mime}, ${bytes} bytes)]`,
|
|
179
179
|
images: [{ type: "image_url", image_url: { url: `data:${mime};base64,${b64}` } }],
|
|
180
180
|
})
|
|
181
|
-
// Paste-created temp files: delete after use, no litter
|
|
182
|
-
const basename = abs.includes("/") ? abs.slice(abs.lastIndexOf("/") + 1) : abs.slice(abs.lastIndexOf("\\") + 1)
|
|
183
|
-
if (basename.startsWith(".thincoder-paste-")) {
|
|
184
|
-
try { await unlink(abs) } catch { /* can't delete, so be it */ }
|
|
185
|
-
}
|
|
186
181
|
return result
|
|
187
182
|
},
|
|
188
183
|
}
|
|
@@ -214,7 +209,7 @@ export const writeTool = {
|
|
|
214
209
|
const content = eol === "\r\n" ? normalizeEOL(args.content).replace(/\n/g, "\r\n") : args.content
|
|
215
210
|
// 单一写路径点:默认 = writeFile + 记账;端侧注入 ⇒ 编辑器径(§2.13.5)。
|
|
216
211
|
await writeThroughPath(abs, content, { op: "write", record: { type: "write", startLine: 1, shift: 0 } }) // 全文重写——全文件受影响
|
|
217
|
-
const diff = gitDiffOne(ctx.cwd, abs)
|
|
212
|
+
const diff = await gitDiffOne(ctx.cwd, abs)
|
|
218
213
|
return `Wrote ${args.content.length} chars to ${args.path}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(abs)}`
|
|
219
214
|
},
|
|
220
215
|
}
|
|
@@ -257,11 +252,15 @@ export const editTool = {
|
|
|
257
252
|
touchedPaths(args) {
|
|
258
253
|
// 2026-09-05 用户裁定:顶层 path = 批默认——仅当有条目缺 path 时计入(条目全带 path
|
|
259
254
|
// 时顶层 path 不实际使用——不虚报进 _touchedFiles)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
255
|
+
// #325:本钩子**零抛**(execute 之前被多处消费,首个未守卫点即会逸出裸错)——形态
|
|
256
|
+
// 拒绝统一归 execute(edit-batch/桥 同单源成形错误);真值非数组 ⇒ 返 []、零触达
|
|
257
|
+
// (不虚报顶层 path);非对象条目按「缺 path」尽力提取(e?.path)。
|
|
258
|
+
if (Array.isArray(args.edits)) {
|
|
259
|
+
const out = args.edits.map((e) => e?.path).filter(Boolean)
|
|
260
|
+
if (args.path && args.edits.some((e) => !e?.path)) out.push(args.path)
|
|
263
261
|
return out
|
|
264
262
|
}
|
|
263
|
+
if (args.edits) return [] // 真值非数组:调用必被 execute 拒绝(成形错误)——零触达
|
|
265
264
|
return args.path ? [args.path] : []
|
|
266
265
|
},
|
|
267
266
|
async execute(args, ctx) {
|
|
@@ -361,7 +360,7 @@ export const insertAfterTool = {
|
|
|
361
360
|
op: "write",
|
|
362
361
|
record: { type: "insert", startLine: targetLine, shift: normalizeEOL(args.content).split("\n").length },
|
|
363
362
|
})
|
|
364
|
-
const diff = gitDiffOne(ctx.cwd, abs)
|
|
363
|
+
const diff = await gitDiffOne(ctx.cwd, abs)
|
|
365
364
|
const baseResult = `Inserted after line ${targetLine} in ${args.path}${diff ? "\n" + diff : ""}${await autoSyntaxCheck(abs)}`
|
|
366
365
|
return await appendWriteContext(abs, targetLine + 1, baseResult)
|
|
367
366
|
},
|
|
@@ -456,7 +455,7 @@ export const hashlineEditTool = {
|
|
|
456
455
|
op: "write",
|
|
457
456
|
record: { type: "edit", startLine: pos + 1, shift: newLines.length - target.length },
|
|
458
457
|
})
|
|
459
|
-
const diff = gitDiffOne(ctx.cwd, abs)
|
|
458
|
+
const diff = await gitDiffOne(ctx.cwd, abs)
|
|
460
459
|
const baseResult = `Edited ${args.path}: replaced ${target.length} line(s) at L${pos + 1} with ${newLines.length} line(s)${diff ? "\n" + diff : ""}${await autoSyntaxCheck(abs)}${corrupted ? `\n${FFFD_WARNING}` : ""}`
|
|
461
460
|
return await appendWriteContext(abs, pos + 1, baseResult)
|
|
462
461
|
},
|
package/tools/git-run.mjs
CHANGED
|
@@ -33,7 +33,7 @@ export const GIT_NET_TIMEOUT_MS = 300_000
|
|
|
33
33
|
/** 网路面五动作(300s 档——合法耗时可远超本地;依据 = 需求档 §4.7 TTY-DRIVE N3 候选参照值)。 */
|
|
34
34
|
const GIT_NET_ACTIONS = new Set(["push", "fetch", "pull", "clone", "ls-remote"])
|
|
35
35
|
|
|
36
|
-
/** 测试态缝(模块级 · 缺省 null = 生产零行为变——先例 `manifest.mjs:
|
|
36
|
+
/** 测试态缝(模块级 · 缺省 null = 生产零行为变——先例 `manifest-discovery.mjs:15-20` · `session-gc.mjs:186`)。
|
|
37
37
|
* 用例 `finally` 复位;不经用户参数面(边界:不新增参数 / 用户选项)。 */
|
|
38
38
|
let gitTimeoutOverride = null
|
|
39
39
|
export function _setGitTimeoutForTest(ms) { gitTimeoutOverride = ms }
|
package/tools/git.mjs
CHANGED
|
@@ -26,9 +26,6 @@ async function runGitRaw(cwd, cmdArgs, config = []) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
29
|
/** Resolve workdir relative to cwd — no boundary assertion
|
|
33
30
|
* (§10.1 2026-09-02: workspace confinement removed; git itself is not
|
|
34
31
|
* directory-limited — same boundary as bash). */
|
|
@@ -53,22 +50,6 @@ function ambiguousRepoMessage(candidates, cwd, matched) {
|
|
|
53
50
|
return `${head} — pass workdir to run git in the intended one (no silent pick):\n` + candidates.map((c) => `- ${c}`).join("\n")
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
// ─── 审批门注入缝(#59——「只读判定 / 审批门按端注入」,形态参 §2.13.5 注入缝)────────
|
|
57
|
-
/**
|
|
58
|
-
* 审批门注入位(**缺省不覆盖** = 无审批面:全部动作照常执行——CLI 语义,零行为变;端装配层
|
|
59
|
-
* 可覆盖为本端审批 / 只读判定。核内零端名分支(契约 5——本档只认 `gate` 函数名)。
|
|
60
|
-
* 契约:`gate(args, ctx) → string | null | Promise<同>`——非空字符串 = 拒执行(该串原样作为
|
|
61
|
-
* 工具结果返回);null / undefined / 其他值 = 放行。每个 execute 恰问一次(workdir 归一后)。
|
|
62
|
-
*/
|
|
63
|
-
let injectedApproval = null
|
|
64
|
-
export function configureGitApproval(impl) {
|
|
65
|
-
injectedApproval = impl && typeof impl.gate === "function" ? impl.gate : null
|
|
66
|
-
}
|
|
67
|
-
/** 撤销注入(测试与端装配生命周期用——缺省态 = 无审批门)。 */
|
|
68
|
-
export function resetGitApproval() { injectedApproval = null }
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
53
|
export const gitTool = {
|
|
73
54
|
name: "git",
|
|
74
55
|
description: DESC("git"),
|
|
@@ -127,11 +108,6 @@ export const gitTool = {
|
|
|
127
108
|
repoNote = `(repo: ${discovered.root})`
|
|
128
109
|
}
|
|
129
110
|
}
|
|
130
|
-
// #59:审批门(端注入面)——缺省无门(CLI 语义);非空字符串返回 ⇒ 拒执行(拒串原样、不加注记)。
|
|
131
|
-
if (injectedApproval) {
|
|
132
|
-
const refusal = await injectedApproval(args, ctx)
|
|
133
|
-
if (typeof refusal === "string" && refusal.length > 0) return refusal
|
|
134
|
-
}
|
|
135
111
|
const out = await gitActionCore.dispatch(args, ctx)
|
|
136
112
|
return repoNote ? `${repoNote}\n${out}` : out
|
|
137
113
|
},
|
package/tools/glob-dialect.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* glob-dialect.mjs — glob 方言(TOOLS.md §
|
|
2
|
+
* glob-dialect.mjs — glob 方言(TOOLS.md §6.7 — 2026-09-06)单一权威模块(CLI)。
|
|
3
3
|
*
|
|
4
4
|
* `{a,b}` brace expansion is handled BEFORE the sentinel/escape flow (braces must
|
|
5
5
|
* not be literal-escaped — a bare "star-star slash star .{js,txt}" pattern
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* intersects includes and excludes.
|
|
13
13
|
*
|
|
14
14
|
* Extracted from shared.mjs (2026-09-06 advisor #5 — shared.mjs exceeded the
|
|
15
|
-
* 500-line cap after §
|
|
15
|
+
* 500-line cap after the TOOLS.md §6.7 move); shared.mjs re-exports these symbols so all existing
|
|
16
16
|
* importers (system.mjs / ls filter / tests) keep their import paths.
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -77,7 +77,7 @@ export function globToRegex(pattern) {
|
|
|
77
77
|
/** Whitespace-split a glob expression into parts WITHOUT splitting inside {a,b}
|
|
78
78
|
* brace groups and WITHOUT splitting literal spaces inside a single pattern
|
|
79
79
|
* (2026-09-06 audit F1 / advisor #4): a whitespace run is a pattern separator
|
|
80
|
-
* ONLY when the next non-space token starts an exclusion ("!" — the §
|
|
80
|
+
* ONLY when the next non-space token starts an exclusion ("!" — the TOOLS.md §6.7
|
|
81
81
|
* "include !exclude" form). So a BARE pattern like "docs/my file/*.md" and an
|
|
82
82
|
* exclude like "!docs/my file/**" each stay ONE part; the standard
|
|
83
83
|
* "js-pattern 空格 !test-pattern" form still splits into its two parts. */
|
package/tools/index.mjs
CHANGED
|
@@ -68,7 +68,8 @@ export async function assembleBuiltinTools({ memory, cwd, projectDir = null, aut
|
|
|
68
68
|
...memoryTools(memory, { cwd, projectDir, author, team }),
|
|
69
69
|
codeSearchTool(memory),
|
|
70
70
|
docSearchTool(memory),
|
|
71
|
-
|
|
71
|
+
// memory 缺位容错:组装期零解引用(memory?.db);缺位不抛(懒失败留执行期——与 memory ∕ code_search ∕ doc_search 同族,dispatch 收成 Error 回模型);有 memory 逐字零变
|
|
72
|
+
repoOutlineTool(memory?.db, cwd),
|
|
72
73
|
settingsTool(),
|
|
73
74
|
peerInstancesTool,
|
|
74
75
|
// 台账查询命令族(M2 设计 §2.2 命令面接线——查询命令全角色面;写命令住 family-tools depthOnly 分支)
|
package/tools/linter.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { DESC, resolveInCwd } from "./shared.mjs"
|
|
|
4
4
|
import { runCommand } from "./exec-run.mjs"
|
|
5
5
|
import { existsSync } from "node:fs"
|
|
6
6
|
import { join } from "node:path"
|
|
7
|
+
import { nodeChildEnv } from "./node-child.mjs"
|
|
7
8
|
|
|
8
9
|
export const lintTool = {
|
|
9
10
|
name: "lint",
|
|
@@ -47,7 +48,7 @@ async function nodeCheckResult(abs, signal) {
|
|
|
47
48
|
return `lint (check): only JS/TS-family files supported for fast syntax check; use full=true for other languages. Path: ${abs}`
|
|
48
49
|
}
|
|
49
50
|
try {
|
|
50
|
-
await runCommand(process.execPath, ["--check", abs], { timeout: 10000, signal })
|
|
51
|
+
await runCommand(process.execPath, ["--check", abs], { timeout: 10000, signal, env: nodeChildEnv() })
|
|
51
52
|
return `Syntax OK: ${abs}`
|
|
52
53
|
} catch (e) {
|
|
53
54
|
const msg = (e.stderr || e.stdout || e.message || "").trim()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/node-child.mjs — node 语义子进程启动单源(TOOLS.md §6.18 · D-TO14 · 台账 #602)。
|
|
3
|
+
*
|
|
4
|
+
* 缺口:核内 spawn `process.execPath` 的隐含前提 = 「execPath 为 node」。桌面宿主主进程 =
|
|
5
|
+
* `electron.exe` ⇒ execPath 属 Electron 族二进制——子进程按 Electron 应用启动、不执行代码
|
|
6
|
+
* (execute 悬挂至超时)。本档 = 那一跳:Electron 宿主补 `ELECTRON_RUN_AS_NODE: "1"`,
|
|
7
|
+
* 同一二进制以 node 语义启动。
|
|
8
|
+
*
|
|
9
|
+
* 判据 = `process.versions.electron`(运行时能力探测——非端名分支):三端同判据、同式执行;
|
|
10
|
+
* 已处 node 模式的 Electron 宿主(VS Code 扩展宿主——env 携旗标)为同值幂等。
|
|
11
|
+
* 消费 = `execute.runNode` spawn 选项 ∕ `lint` 快路径(经 `exec-run` `opts.env` 透传)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* node 语义子进程 env:Electron 宿主 ⇒ `{ ...base, ELECTRON_RUN_AS_NODE: "1" }` 副本
|
|
16
|
+
* (不改父进程 env);否则原样返回 `base`(同引用——与 spawn 缺省继承逐字等价,零行为变)。
|
|
17
|
+
* 第二参 = 判据注入(机检用——先例 `_setGitTimeoutForTest` 族)。
|
|
18
|
+
*/
|
|
19
|
+
export function nodeChildEnv(base = process.env, isElectron = !!process.versions.electron) {
|
|
20
|
+
return isElectron ? { ...base, ELECTRON_RUN_AS_NODE: "1" } : base
|
|
21
|
+
}
|