@thincoder/core 0.9.4 → 0.9.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/advisor/loop.mjs +2 -2
  3. package/advisor/run.mjs +1 -1
  4. package/agent/dispatch.mjs +21 -18
  5. package/agent/family-tools.mjs +6 -6
  6. package/agent/helpers.mjs +12 -2
  7. package/agent/run-stages.mjs +5 -5
  8. package/agent/suspension.mjs +1 -1
  9. package/agent-tools/advisor-async.mjs +2 -2
  10. package/agent-tools/advisor-settle.mjs +5 -5
  11. package/agent-tools/advisor.mjs +3 -3
  12. package/agent-tools/async-settle.mjs +2 -2
  13. package/agent-tools/batch-lifecycle.mjs +8 -16
  14. package/agent-tools/batch-paths.mjs +142 -0
  15. package/agent-tools/batch.mjs +10 -54
  16. package/agent-tools/consult.mjs +2 -2
  17. package/agent-tools/escalate-async.mjs +2 -2
  18. package/agent-tools/panel-blocks.mjs +1 -1
  19. package/agent-tools/subagent-actions.mjs +21 -21
  20. package/agent-tools/subagent-async.mjs +11 -11
  21. package/agent-tools/subagent-panel.mjs +5 -5
  22. package/agent-tools/subagent-run.mjs +8 -8
  23. package/agent-tools/subagent-scheduler.mjs +1 -1
  24. package/agent-tools/subagent-spawn.mjs +13 -12
  25. package/agent-tools/subagent.mjs +13 -13
  26. package/agent.mjs +13 -7
  27. package/config-presets.mjs +1 -1
  28. package/git/checkpoint.mjs +4 -8
  29. package/ledger-db.mjs +25 -5
  30. package/ledger-migrate.mjs +299 -0
  31. package/ledger.mjs +2 -1
  32. package/model-specs.mjs +118 -62
  33. package/package.json +1 -1
  34. package/peer-claims.mjs +263 -0
  35. package/peer-domains.mjs +93 -60
  36. package/prompts/common.md +4 -0
  37. package/prompts/persona-coder.md +1 -1
  38. package/prompts/persona-eng-coder.md +2 -2
  39. package/prompts/persona-eng-designer.md +2 -2
  40. package/prompts/persona-engineering.md +9 -0
  41. package/prompts/persona-explore.md +1 -1
  42. package/prompts/persona-normal.md +9 -0
  43. package/prompts/persona-plan.md +3 -3
  44. package/session-gc.mjs +67 -6
  45. package/session-slots-manifest.mjs +5 -0
  46. package/tool-docs/question.md +1 -0
  47. package/tools/edit-batch.mjs +6 -7
  48. package/tools/edit-diff.mjs +29 -2
  49. package/tools/file.mjs +7 -3
  50. package/tools/glob-dialect.mjs +3 -3
  51. package/tools/search.mjs +2 -2
  52. package/tools/shared.mjs +1 -1
@@ -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 = ""(批量前缀 "edit for <path>: "), rich = true(单形态完整文本) }
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(批量 "edit aborted (atomic — no files written): ") }
243
+ * opts: { path, abortPrefix(批量 = EDIT_ABORT_PREFIX) }
217
244
  */
218
245
  /**
219
246
  * P15.11(2026-09-05 用户裁定——edit 连续失败分析):old_string 逐字 not-found 时——
package/tools/file.mjs CHANGED
@@ -257,11 +257,15 @@ export const editTool = {
257
257
  touchedPaths(args) {
258
258
  // 2026-09-05 用户裁定:顶层 path = 批默认——仅当有条目缺 path 时计入(条目全带 path
259
259
  // 时顶层 path 不实际使用——不虚报进 _touchedFiles)
260
- if (args.edits) {
261
- const out = args.edits.map((e) => e.path).filter(Boolean)
262
- if (args.path && args.edits.some((e) => !e.path)) out.push(args.path)
260
+ // #325:本钩子**零抛**(execute 之前被多处消费,首个未守卫点即会逸出裸错)——形态
261
+ // 拒绝统一归 execute(edit-batch/桥 同单源成形错误);真值非数组 ⇒ 返 []、零触达
262
+ // (不虚报顶层 path);非对象条目按「缺 path」尽力提取(e?.path)。
263
+ if (Array.isArray(args.edits)) {
264
+ const out = args.edits.map((e) => e?.path).filter(Boolean)
265
+ if (args.path && args.edits.some((e) => !e?.path)) out.push(args.path)
263
266
  return out
264
267
  }
268
+ if (args.edits) return [] // 真值非数组:调用必被 execute 拒绝(成形错误)——零触达
265
269
  return args.path ? [args.path] : []
266
270
  },
267
271
  async execute(args, ctx) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * glob-dialect.mjs — glob 方言(TOOLS.md §17 — 2026-09-06)单一权威模块(CLI)。
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 §17); shared.mjs re-exports these symbols so all existing
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 §17
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/search.mjs CHANGED
@@ -32,7 +32,7 @@ export const globTool = {
32
32
  readonly: true,
33
33
  async execute(args, ctx) {
34
34
  const base = resolveInCwd(ctx, args.path ?? ".")
35
- // §17 调用侧拆分(评审 #5 职责分层):空格分隔 include !exclude 多模式在这里拆;
35
+ // TOOLS.md §6.7 调用侧拆分(评审 #5 职责分层):空格分隔 include !exclude 多模式在这里拆;
36
36
  // globToRegex 只收单个模式(数组由 compileGlobMatchers 收)。
37
37
  let match
38
38
  try {
@@ -115,7 +115,7 @@ export const grepTool = {
115
115
  } catch {
116
116
  return `grep error: path not found: ${base}`
117
117
  }
118
- // §17 调用侧拆分:glob 参数支持空格分隔 include !exclude 多模式(include/exclude 求交)。
118
+ // TOOLS.md §6.7 调用侧拆分:glob 参数支持空格分隔 include !exclude 多模式(include/exclude 求交)。
119
119
  let fileTest = null
120
120
  if (args.glob) {
121
121
  try {
package/tools/shared.mjs CHANGED
@@ -373,7 +373,7 @@ export function detectDanger(command) {
373
373
  }
374
374
 
375
375
 
376
- /** Glob dialect (TOOLS.md §17) lives in glob-dialect.mjs — single authority for
376
+ /** Glob dialect (TOOLS.md §6.7) lives in glob-dialect.mjs — single authority for
377
377
  * brace expansion / exclusion / explicit syntax errors (extracted 2026-09-06 —
378
378
  * advisor #5: shared.mjs exceeded 500 lines). Re-exported so existing importers
379
379
  * (system.mjs tools, ls filter, tests) keep their import paths unchanged. */