thincoder 0.12.59 → 0.12.61

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 (192) hide show
  1. package/CHANGELOG.md +62 -4
  2. package/README.md +10 -8
  3. package/bin/thincoder.mjs +99 -133
  4. package/package.json +6 -4
  5. package/src/abort-provenance.mjs +116 -0
  6. package/src/acp/bridge.mjs +45 -21
  7. package/src/acp.mjs +6 -1
  8. package/src/advisor/citations.mjs +83 -21
  9. package/src/advisor/compaction.mjs +174 -0
  10. package/src/advisor/loop.mjs +293 -0
  11. package/src/advisor/messages.mjs +59 -137
  12. package/src/advisor/project-context.mjs +194 -0
  13. package/src/advisor/repos.mjs +17 -40
  14. package/src/advisor/run.mjs +156 -359
  15. package/src/advisor/truncate.mjs +57 -0
  16. package/src/advisor.mjs +27 -7
  17. package/src/agent/completion.mjs +17 -11
  18. package/src/agent/dispatch.mjs +145 -27
  19. package/src/agent/helpers.mjs +107 -13
  20. package/src/agent/record-results.mjs +55 -11
  21. package/src/agent/relay-prefix.mjs +39 -0
  22. package/src/agent/run-stages.mjs +242 -0
  23. package/src/agent/setup-reminders.mjs +69 -0
  24. package/src/agent/setup.mjs +107 -127
  25. package/src/agent/spawn-child.mjs +55 -13
  26. package/src/agent-tools/advisor-async.mjs +346 -0
  27. package/src/agent-tools/advisor-settle.mjs +231 -0
  28. package/src/agent-tools/advisor.mjs +167 -116
  29. package/src/agent-tools/async-settle.mjs +191 -0
  30. package/src/agent-tools/batch-segment.mjs +195 -0
  31. package/src/agent-tools/consult.mjs +139 -107
  32. package/src/agent-tools/design-token.mjs +117 -0
  33. package/src/agent-tools/digest-budget.mjs +76 -0
  34. package/src/agent-tools/eng.mjs +24 -29
  35. package/src/agent-tools/escalate-async.mjs +289 -0
  36. package/src/agent-tools/read-history.mjs +166 -32
  37. package/src/agent-tools/recent-changes.mjs +2 -1
  38. package/src/agent-tools/review-streak.mjs +93 -0
  39. package/src/agent-tools/settings.mjs +137 -34
  40. package/src/agent-tools/subagent-actions.mjs +180 -133
  41. package/src/agent-tools/subagent-async.mjs +184 -177
  42. package/src/agent-tools/subagent-panel.mjs +160 -0
  43. package/src/agent-tools/subagent-run.mjs +205 -0
  44. package/src/agent-tools/subagent-scheduler.mjs +100 -27
  45. package/src/agent-tools/subagent-spawn.mjs +453 -0
  46. package/src/agent-tools/subagent.mjs +256 -578
  47. package/src/agent-tools/verify.mjs +119 -292
  48. package/src/agent-tools.mjs +1 -0
  49. package/src/agent.mjs +89 -205
  50. package/src/cli/distill-command.mjs +12 -6
  51. package/src/cli/make-agent.mjs +26 -8
  52. package/src/cli/memory-command.mjs +4 -3
  53. package/src/cli/permission.mjs +2 -2
  54. package/src/cli/setup-wizard.mjs +42 -17
  55. package/src/completions.mjs +114 -0
  56. package/src/config-migrate.mjs +70 -0
  57. package/src/config.mjs +180 -63
  58. package/src/context.mjs +5 -147
  59. package/src/conventions.mjs +223 -0
  60. package/src/crash-reports.mjs +128 -0
  61. package/src/distill.mjs +11 -11
  62. package/src/expand-home.mjs +16 -0
  63. package/src/explore-distill.mjs +155 -0
  64. package/src/generate-title.mjs +1 -1
  65. package/src/hooks.mjs +7 -3
  66. package/src/memory/code-index.mjs +9 -3
  67. package/src/memory/code-sync.mjs +72 -32
  68. package/src/memory/core.mjs +6 -193
  69. package/src/memory/delete.mjs +236 -0
  70. package/src/memory/docs.mjs +68 -54
  71. package/src/memory/file-walk.mjs +109 -0
  72. package/src/memory/schema.mjs +15 -3
  73. package/src/memory.mjs +3 -1
  74. package/src/model-ref.mjs +66 -0
  75. package/src/model-specs.mjs +42 -8
  76. package/src/peer-domains.mjs +265 -0
  77. package/src/peer-instances.mjs +231 -0
  78. package/src/prompt-overlays.mjs +82 -0
  79. package/src/prompts/advisor-design.md +18 -75
  80. package/src/prompts/advisor-round1.md +14 -67
  81. package/src/prompts/advisor-round2.md +15 -51
  82. package/src/prompts/advisor-round3.md +15 -51
  83. package/src/prompts/common.md +115 -0
  84. package/src/prompts/consult-base.md +5 -23
  85. package/src/prompts/discipline-engineering.md +217 -0
  86. package/src/prompts/discipline-normal.md +179 -0
  87. package/src/prompts/persona-coder.md +21 -0
  88. package/src/prompts/persona-eng-coder.md +37 -0
  89. package/src/prompts/persona-eng-designer.md +55 -0
  90. package/src/prompts/persona-engineering.md +54 -0
  91. package/src/prompts/persona-explore.md +15 -0
  92. package/src/prompts/persona-normal.md +27 -0
  93. package/src/prompts/persona-plan.md +26 -0
  94. package/src/provider/anthropic.mjs +4 -4
  95. package/src/provider/core.mjs +18 -98
  96. package/src/provider/errors.mjs +101 -0
  97. package/src/provider/google.mjs +5 -6
  98. package/src/provider/index.mjs +2 -1
  99. package/src/provider/list-models.mjs +93 -0
  100. package/src/provider/rate.mjs +2 -1
  101. package/src/provider/responses.mjs +5 -3
  102. package/src/provider/retry.mjs +8 -45
  103. package/src/provider/sse.mjs +3 -4
  104. package/src/proxy.mjs +9 -14
  105. package/src/session-gc.mjs +214 -0
  106. package/src/session-guard.mjs +47 -0
  107. package/src/session-rename.mjs +38 -0
  108. package/src/session-slots.mjs +188 -60
  109. package/src/session.mjs +104 -124
  110. package/src/token-ttl.mjs +274 -0
  111. package/src/tools/{system.mjs → bash.mjs} +19 -221
  112. package/src/tools/checklist-sync.mjs +181 -0
  113. package/src/tools/checklist.mjs +52 -39
  114. package/src/tools/edit-batch.mjs +109 -10
  115. package/src/tools/edit-diff.mjs +110 -27
  116. package/src/tools/edit.md +17 -12
  117. package/src/tools/execute.mjs +31 -4
  118. package/src/tools/file.mjs +41 -16
  119. package/src/tools/git.md +1 -1
  120. package/src/tools/git.mjs +23 -34
  121. package/src/tools/glob-dialect.mjs +130 -0
  122. package/src/tools/glob.md +3 -3
  123. package/src/tools/grep.md +1 -1
  124. package/src/tools/index.mjs +9 -8
  125. package/src/tools/ops.mjs +188 -3
  126. package/src/tools/patch.mjs +3 -3
  127. package/src/tools/question.md +4 -0
  128. package/src/tools/question.mjs +26 -0
  129. package/src/tools/read.md +1 -2
  130. package/src/tools/read_image.md +1 -1
  131. package/src/tools/search.mjs +236 -0
  132. package/src/tools/shared.mjs +14 -13
  133. package/src/tools/wait_for.md +22 -0
  134. package/src/tui/agent-turn.mjs +36 -228
  135. package/src/tui/ansi.mjs +2 -0
  136. package/src/tui/clipboard.mjs +7 -1
  137. package/src/tui/cmd-advisor.mjs +3 -2
  138. package/src/tui/cmd-config.mjs +142 -30
  139. package/src/tui/cmd-eng.mjs +28 -40
  140. package/src/tui/cmd-exit.mjs +6 -8
  141. package/src/tui/cmd-mcp.mjs +8 -2
  142. package/src/tui/cmd-model.mjs +14 -12
  143. package/src/tui/cmd-new.mjs +3 -2
  144. package/src/tui/cmd-reindex.mjs +7 -0
  145. package/src/tui/cmd-session.mjs +19 -4
  146. package/src/tui/cmd-submodel.mjs +8 -5
  147. package/src/tui/cmd-think.mjs +10 -10
  148. package/src/tui/cmd-undo.mjs +4 -3
  149. package/src/tui/cmd-upgrade.mjs +19 -4
  150. package/src/tui/config-helpers.mjs +28 -16
  151. package/src/tui/distill-cmd.mjs +1 -1
  152. package/src/tui/index.mjs +40 -38
  153. package/src/tui/interaction.mjs +3 -3
  154. package/src/tui/key-handler.mjs +61 -17
  155. package/src/tui/key-modes.mjs +86 -8
  156. package/src/tui/layout.mjs +18 -10
  157. package/src/tui/model-catalog.mjs +89 -0
  158. package/src/tui/model-picker.mjs +498 -0
  159. package/src/tui/mouse.mjs +52 -9
  160. package/src/tui/pickers.mjs +28 -392
  161. package/src/tui/render-frame.mjs +32 -16
  162. package/src/tui/render-loop.mjs +2 -0
  163. package/src/tui/render-segments.mjs +12 -9
  164. package/src/tui/render.mjs +37 -5
  165. package/src/tui/slash-commands.mjs +2 -2
  166. package/src/tui/startup.mjs +4 -0
  167. package/src/tui/subagent-blocks.mjs +106 -295
  168. package/src/tui/subagent-children.mjs +162 -0
  169. package/src/tui/subagent-freeze.mjs +169 -0
  170. package/src/tui/subagent-panel.mjs +24 -31
  171. package/src/tui/suspension-drive.mjs +297 -0
  172. package/src/tui/tool-args.mjs +7 -5
  173. package/src/tui/tool-display.mjs +143 -0
  174. package/src/tui/tool-events.mjs +56 -185
  175. package/src/tui/tui-lifecycle.mjs +46 -4
  176. package/src/tui/update-notice.mjs +4 -0
  177. package/src/tui/wizard.mjs +61 -21
  178. package/src/tui/wrapped-spawn.mjs +38 -0
  179. package/src/prompts/coder.md +0 -56
  180. package/src/prompts/discipline.md +0 -102
  181. package/src/prompts/eng-coder.md +0 -44
  182. package/src/prompts/engineering-sub.md +0 -34
  183. package/src/prompts/engineering.md +0 -340
  184. package/src/prompts/explore.md +0 -21
  185. package/src/prompts/main.md +0 -56
  186. package/src/prompts/methodology-template.md +0 -58
  187. package/src/prompts/plan.md +0 -16
  188. package/src/prompts/system.md +0 -63
  189. package/src/tools/pdf-parse-text.mjs +0 -497
  190. package/src/tools/pdf-parse-xref.mjs +0 -499
  191. package/src/tools/pdf.mjs +0 -155
  192. package/src/tools/read_pdf.md +0 -21
@@ -0,0 +1,194 @@
1
+ /**
2
+ * advisor/project-context.mjs — project-context discovery and injection for review
3
+ * messages (Project Guide, document map, project standards, change-set context).
4
+ *
5
+ * Split out of messages.mjs (the split registered as D-1 in ENGINEERING-MODE.md
6
+ * §2.26.3, trigger "thicken again" — met by this batch). messages.mjs keeps its
7
+ * pre-split surface via re-export; this module is consumed by it only.
8
+ *
9
+ * Portability contract (PORTABILITY FR10/FR11 · PO-1/PO-2 · P8): every discovery
10
+ * step here is either satisfied or DEGRADED VISIBLY. The old code probed a fixed
11
+ * `docs/README.md` and a fixed `METHODOLOGY.md` and skipped injection silently when
12
+ * absent — in a project with a different layout the reviewer still scored the
13
+ * dimension it could no longer check (silent failure, the worst class: invisible
14
+ * and unfixable by the user). Missing pieces now say so, and the reviewer is told
15
+ * to state the limitation in its findings. Projects can point at their own files
16
+ * through `.thincoder/conventions.json` (advisor.docMap / advisor.standardsDoc).
17
+ */
18
+ import { readFileSync, existsSync } from "node:fs"
19
+ import { resolve, join, relative, dirname, sep } from "node:path"
20
+ import { providerSpec } from "../config.mjs"
21
+ import { loadConventions } from "../conventions.mjs"
22
+
23
+ /** Project guide (AGENTS.md) injection budget — decision 2026-08-08:
24
+ * NO fixed truncation; long-context models (1M+) get up to 5% of their context
25
+ * window for the doc map, small windows still get a floor so the map is always
26
+ * visible. The map is what tells the reviewer WHERE the requirements docs live
27
+ * (requirement-fit is judged against those docs, not the conversation only). */
28
+ const PROJECT_GUIDE_MIN = 8192 // chars — floor for small-window models
29
+ const PROJECT_GUIDE_FRACTION = 0.05 // 5% of the reviewer model's context window
30
+
31
+ /** Absent-AGENTS.md notice (guide is optional: the conversation carries intent). */
32
+ export const NO_GUIDE_NOTICE = "(No AGENTS.md found — neither at the working directory root nor in any review-scope subdirectory. Judge the user's requirements from the conversation background, and say so explicitly if the requirements are unclear.)"
33
+
34
+ /** Explicit degradation sentences (PORTABILITY design §4.4 — verbatim). */
35
+ export const NO_DOC_MAP_NOTICE = "(No document map found under the project root, and none is declared — the Document ownership criterion is degraded: check placement against the Project Guide where present, and state the limitation in your findings.)"
36
+ export const NO_STANDARDS_NOTICE = "(No project standards document was declared — judge methodology compliance from the Project Guide (when present) and the review criteria above; state the limitation in your findings.)"
37
+ export const NO_GIT_NOTICE = "(No git repository detected — change-set context is unavailable; read the review-scope files directly.)"
38
+
39
+ /**
40
+ * Discover the project root for the review — user decision 2026-08-08:
41
+ * the project root is a SUBDIRECTORY of the working directory, never an
42
+ * ancestor above it. Priority:
43
+ * 1. Walk UP from each review-scope file's directory, bounded by cwd —
44
+ * the NEAREST AGENTS.md inside the workspace wins. In a monorepo this is
45
+ * the subproject's own doc map even when cwd itself has an AGENTS.md
46
+ * (a workspace-level meta map must not shadow the subproject guide).
47
+ * 2. No scope files / nothing found → cwd (single project; the walk's
48
+ * last step naturally lands on cwd's own AGENTS.md when it exists).
49
+ * @param {string} cwd — the agent's working directory (workspace root)
50
+ * @param {string[]} scopeFiles — cwd-relative review-scope paths (may be empty)
51
+ * @returns {string|null} absolute project root with an AGENTS.md, or null
52
+ */
53
+ export function findProjectRoot(cwd, scopeFiles) {
54
+ // Normalize separators before comparing: input paths may use either
55
+ // convention (join() → "\\" on Windows; tool args / tests → "/"). Mixed
56
+ // styles made isInside(cwd + sep) miss legitimately nested paths.
57
+ const norm = (p) => p.replaceAll("\\", "/")
58
+ const isInside = (dir) => {
59
+ const d = norm(dir)
60
+ const c = norm(cwd)
61
+ return d === c || d.startsWith(c + "/")
62
+ }
63
+ for (const f of scopeFiles) {
64
+ let dir = dirname(resolve(cwd, f))
65
+ while (isInside(dir) && dir !== dirname(dir)) {
66
+ if (existsSync(join(dir, "AGENTS.md"))) return dir
67
+ dir = dirname(dir)
68
+ }
69
+ }
70
+ // No scope files, or none found in the walk — cwd itself (its AGENTS.md is
71
+ // checked as the walk's final step for scope files; for empty scopes, check
72
+ // it explicitly so a bare cwd project still gets its guide).
73
+ if (existsSync(join(cwd, "AGENTS.md"))) return cwd
74
+ return null
75
+ }
76
+
77
+ /**
78
+ * Inject the project guide (AGENTS.md) into the review message. AGENTS.md is the
79
+ * project's doc map — it defines the structure and where requirements/design
80
+ * documents live. The reviewer must see it FIRST: requirement-fit is judged
81
+ * against the documents it points to, with the conversation background as a
82
+ * supplement. Absent AGENTS.md degrades honestly (no pretending there is a map).
83
+ * @param {Object} agent — the parent agent
84
+ * @param {string[]} parts — message parts (mutated)
85
+ * @param {string[]} [scopeFiles] — cwd-relative review-scope paths for project-root discovery
86
+ * @returns {string|null} the discovered project root (abs), or null when no guide
87
+ */
88
+ export function injectProjectGuide(agent, parts, scopeFiles = []) {
89
+ parts.push("## Project Guide (AGENTS.md)")
90
+ const root = findProjectRoot(agent.cwd, scopeFiles)
91
+ const path = root ? join(root, "AGENTS.md") : null
92
+ let text
93
+ if (!path) {
94
+ parts.push(NO_GUIDE_NOTICE)
95
+ parts.push("")
96
+ return null // no guide — requirement-fit falls back to the conversation
97
+ }
98
+ try {
99
+ text = readFileSync(path, "utf8")
100
+ } catch (e) {
101
+ if (e.code !== "ENOENT") {
102
+ // File exists but is unreadable (EACCES etc.) — log, don't masquerade as "not found".
103
+ console.warn(`[advisor] AGENTS.md unreadable at ${path}: ${e.message}`)
104
+ }
105
+ parts.push(NO_GUIDE_NOTICE)
106
+ parts.push("")
107
+ return null // no guide — requirement-fit falls back to the conversation
108
+ }
109
+ // readFileSync succeeded — compute the budget OUTSIDE the try so a spec
110
+ // lookup failure can never masquerade as "no AGENTS.md".
111
+ // providerSpec: the project-guide budget follows the provider-level context
112
+ // override (PROVIDER.md §15 — advisor messages budget is context-based).
113
+ const ctx = providerSpec(agent.provider).context
114
+ const cap = Math.max(PROJECT_GUIDE_MIN, Math.floor(ctx * PROJECT_GUIDE_FRACTION))
115
+ const shown = text.length <= cap
116
+ ? text
117
+ : [...text].slice(0, cap).join("") + `\n\n…(truncated at ${cap} chars — read the full file if you need more)` // codepoint-safe slice: no broken surrogate pairs at the boundary
118
+ parts.push(`<!-- Project root: ${relative(agent.cwd, path).split(sep).join("/")} (inferred from the review scope under ${agent.cwd}) -->`)
119
+ parts.push("This file defines the project's structure and where its requirements/design documents live. Read the documents it points to — the user's requirements live THERE, not only in the conversation background.")
120
+ parts.push("")
121
+ parts.push(shown)
122
+ parts.push("")
123
+ return root // guide injected — requirement-fit criteria apply (truthy root)
124
+ }
125
+
126
+ /**
127
+ * Inject the project document map (PO-1). Resolution: the DECLARED path
128
+ * (`advisor.docMap`, project-root-relative) wins; the built-in probe
129
+ * (`docs/README.md` → `docs/design/README.md`) stays as a fallback for projects
130
+ * laid out that way. Neither usable → an explicit degradation sentence — never a
131
+ * silent skip: the reviewer must know the Document-ownership criterion is unchecked.
132
+ * @returns {string|null} the injected map path, or null when degraded
133
+ */
134
+ export function injectDocumentMap(agent, parts, root) {
135
+ const base = root ?? agent.cwd
136
+ const conv = loadConventions(agent.cwd)
137
+ let mapPath = null
138
+ if (conv.advisor.docMap) {
139
+ const declared = resolve(base, conv.advisor.docMap)
140
+ if (existsSync(declared)) mapPath = declared
141
+ } else {
142
+ mapPath = [resolve(base, "docs", "README.md"), resolve(base, "docs", "design", "README.md")]
143
+ .find((p) => existsSync(p)) ?? null
144
+ }
145
+ parts.push("## Document Map")
146
+ let text = null
147
+ if (mapPath) {
148
+ try {
149
+ text = readFileSync(mapPath, "utf8")
150
+ } catch (e) {
151
+ console.warn(`[advisor] document map unreadable at ${mapPath}: ${e.message}`)
152
+ }
153
+ }
154
+ if (text === null) {
155
+ parts.push(NO_DOC_MAP_NOTICE) // degraded — visible by contract
156
+ parts.push("")
157
+ return null
158
+ }
159
+ parts.push("The document map below registers which document files exist per section. Use it for the Document ownership criterion: a change for an existing section must amend that section's document, not create a new file.")
160
+ parts.push(text)
161
+ parts.push("")
162
+ return mapPath
163
+ }
164
+
165
+ /**
166
+ * Inject the project standards document (PO-2). DECLARATION ONLY
167
+ * (`advisor.standardsDoc`, project-root-relative): the old hardcoded
168
+ * `METHODOLOGY.md` probe injected a file most projects do not have — and its
169
+ * absence was silent. Undeclared (or unreadable) → explicit degradation sentence.
170
+ * @returns {string|null} the injected standards path, or null when degraded
171
+ */
172
+ export function injectProjectStandards(agent, parts, root) {
173
+ const base = root ?? agent.cwd
174
+ const conv = loadConventions(agent.cwd)
175
+ const declared = conv.advisor.standardsDoc ? resolve(base, conv.advisor.standardsDoc) : null
176
+ parts.push("## Project Standards")
177
+ let text = null
178
+ if (declared && existsSync(declared)) {
179
+ try {
180
+ text = readFileSync(declared, "utf8")
181
+ } catch (e) {
182
+ console.warn(`[advisor] project standards unreadable at ${declared}: ${e.message}`)
183
+ }
184
+ }
185
+ if (text === null) {
186
+ parts.push(NO_STANDARDS_NOTICE) // degraded — visible by contract
187
+ parts.push("")
188
+ return null
189
+ }
190
+ parts.push("The project declares these standards for this review. Judge the design/changes against them:")
191
+ parts.push(text)
192
+ parts.push("")
193
+ return declared
194
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { execFileSync } from "node:child_process"
6
6
  import { dirname, basename, resolve } from "node:path"
7
+ import { loadConventions, isCodePath } from "../conventions.mjs"
7
8
 
8
9
  const GIT_TIMEOUT = 5_000
9
10
  const MAX_EMBEDDED_DIFF = 50_000
@@ -97,32 +98,10 @@ export function collectChangedFiles(repos, cwd) {
97
98
  return files
98
99
  }
99
100
 
100
- const DOC_FILE = /(?:^|[/\\])(?:LICENSE|NOTICE|CHANGELOG|AUTHORS)(?:\.\w+)?$|\.(?:md|markdown|mdx|txt|rst|adoc)$/i
101
-
102
- /** Temporary/scratch files that must NOT count as code mutations: tmp-* named
103
- * scratch scripts (tmp-c1.mjs, tmp-check.mjs…) and .tmp/.temp extensions.
104
- * The advisor/verify guards skip these — a throwaway diagnostic script is not
105
- * a code change, and writing one must not push the agent into a review loop.
106
- * NOTE: matches the tmp-* basename at ANY directory depth, not just root —
107
- * intentional: _touchedFiles stores absolute paths, so the pattern must work
108
- * for "D:/proj/tmp-check.mjs" as well as a bare "tmp-check.mjs". */
109
- const TEMP_FILE = /(?:^|[/\\])tmp-[^/\\]+$|\.(?:tmp|temp)$/i
110
-
111
- /** True when a path matches the doc/license pattern by extension or name.
112
- * NOTE: this is extension-based only — it does NOT exclude src/ paths.
113
- * Callers must separately check the src/ prefix for product-code semantics
114
- * (e.g. src/prompts/*.md IS product code despite matching DOC_FILE).
115
- * See isDocOnlyChange for the combined check. */
116
- export function isDocFile(p) {
117
- return DOC_FILE.test(p ?? "")
118
- }
119
-
120
- /** True when a path is a throwaway temp file (tmp-* name or .tmp/.temp ext).
121
- * Excluded from code-mutation detection so scratch scripts don't trigger
122
- * advisor/verify guards. */
123
- export function isTempFile(p) {
124
- return TEMP_FILE.test(p ?? "")
125
- }
101
+ // Path classification (code / doc / temp) lives in ../conventions.mjs — the single
102
+ // authority shared by every gate and guard (PORTABILITY FR12 / PO-10). This module
103
+ // consumes it: `isDocFile` / `isTempFile` were retired here (no re-export a second
104
+ // import path would recreate the drift the authority exists to remove).
126
105
 
127
106
  /**
128
107
  * True when this run mutated at least one CODE file. Shared single source of
@@ -132,24 +111,22 @@ export function isTempFile(p) {
132
111
  * files (tmp-*, .tmp, .temp) are excluded too — a throwaway diagnostic script
133
112
  * is not a code change. Mutations without a known path (tools outside
134
113
  * FILE_MUTATORS) are treated as code — cannot tell, so guard conservatively.
135
- * Product-code semantics: anything under src/ (incl. src/prompts/*.md) is code;
136
- * anything else that isn't a doc or temp file is code.
137
- * NOTE: _touchedFiles stores ABSOLUTE paths (join(cwd, p)), so the src/ check
138
- * matches a path component (works for "src/..." and "D:\...\src\..." alike),
139
- * not a bare ^src prefix — the literal ^src[\\/] form would be dead code here.
114
+ * Classification semantics (incl. "a declared code segment wins over temp/doc")
115
+ * live in conventions.mjs; _touchedFiles stores ABSOLUTE paths, which the
116
+ * segment matcher handles at any depth.
140
117
  */
141
- export function hasCodeMutations({ _touchedFiles, _mutatedThisRun }) {
118
+ export function hasCodeMutations({ _touchedFiles, _mutatedThisRun, cwd }) {
142
119
  const files = _touchedFiles ?? []
143
120
  if (files.length === 0) return _mutatedThisRun
144
- // src/ is unconditional — anything under src/ is code regardless of its name
145
- // (incl. src/tmp-*.mjs). Temp/doc exclusions apply only outside src/.
146
- return files.some((p) => /(?:^|[\\/])src[\\/]/.test(p) || (!isTempFile(p) && !isDocFile(p)))
121
+ const conv = loadConventions(cwd)
122
+ return files.some((p) => isCodePath(p, conv))
147
123
  }
148
124
 
149
125
  /** True when all changed files across repos are documentation (md/txt/LICENSE etc.).
150
- * Anything under src/ (incl. src/prompts/*.md) counts as product code —
151
- * isProductCode semantics, consistent with the design gate. */
126
+ * Anything inside a code segment (incl. src/prompts/*.md) counts as product code —
127
+ * the same authority the design gate uses. Paths here are repo-relative. */
152
128
  export function isDocOnlyChange(repos, cwd) {
129
+ const conv = loadConventions(cwd)
153
130
  const targets = repos.length > 0 ? repos : [cwd]
154
131
  let sawChanges = false
155
132
  for (const repo of targets) {
@@ -164,9 +141,9 @@ export function isDocOnlyChange(repos, cwd) {
164
141
  for (const line of status.split("\n")) {
165
142
  // porcelain: "XY path" or "XY old -> new" (rename)
166
143
  const filePath = line.slice(3).split(" -> ").pop().replace(/^"|"$/g, "")
167
- // src/ is unconditional product code (even src/tmp-*.mjs) — check before the temp skip
168
- if (/^src[\\/]/.test(filePath) || !DOC_FILE.test(filePath)) return false
169
- if (isTempFile(filePath)) continue
144
+ // Code (a declared code segment is unconditional, even for tmp-*.mjs)
145
+ // not a doc-only change; docs and temp scratch files fall through.
146
+ if (isCodePath(filePath, conv)) return false
170
147
  }
171
148
  }
172
149
  return sawChanges