thinkpool-pair 0.7.330 → 0.7.331

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 (2) hide show
  1. package/package.json +1 -1
  2. package/terminal-name.mjs +17 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.330",
3
+ "version": "0.7.331",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {
package/terminal-name.mjs CHANGED
@@ -17,6 +17,7 @@ const ANY_HEADING = /^(?:#{1,6}\s+|(?:context|background|constraints?|inputs?|ou
17
17
  const NOISE = /^(?:context|background|for reference|here(?:'s| is)|note|current(?:ly)?|example|environment|room now|constraints?|acceptance|success criteria)\b/i
18
18
  const EXPLANATION = /^(?:because|cause|since|so that|this is because)\b/i
19
19
  const CONSTRAINT = /^(?:users? can still|keep|must|never|should|without)\b/i
20
+ const DEPENDENT_FRAGMENT = /^(?:after|before|by|during|for|from|in|instead(?: of)?|on|through|until|with|without)\b/i
20
21
  const INFORMATION_REQUEST = /^(?:which|what|who|where|when|why|how)\b/i
21
22
  const NEGATIVE_PREFERENCE = /^(?:(?:i|we)\s+)?(?:do not|don't|dont|would not|wouldn't|won't|wont)\s+(?:(?:want|wanna|need)(?:\s+to)?|use|include|choose)\b|^(?:(?:i|we)\s+)?(?:want|wanna|need)(?:\s+to)?\s+avoid\b/i
22
23
  const ISSUE = /\b(?:broken|buggy|crash(?:es|ed|ing)?|duplicate|error|fail(?:s|ed|ing|ure)?|flash(?:es|ed|ing)?|missing|no animation|not working|out of (?:scrollable )?view|stuck|wrong)\b/i
@@ -29,6 +30,7 @@ const ACTIONS = Object.freeze([
29
30
  { title: 'Harden', score: 98, re: /\b(?:harden|hardens|hardened|hardening)\b/i },
30
31
  { title: 'Snap', score: 96, re: /\b(?:snap|snaps|snapped|snapping)\b/i },
31
32
  { title: 'Improve', score: 94, re: /\b(?:improve|improves|improved|improving|optimi[sz](?:e|es|ed|ing)|\bbetter\b)\b/i },
33
+ { title: 'Rework', score: 93, re: /\b(?:rework|reworks|reworked|reworking)\b/i },
32
34
  { title: 'Implement', score: 92, re: /\b(?:implement|implements|implemented|implementing)\b/i },
33
35
  { title: 'Build', score: 90, re: /\b(?:build|builds|built|building|create|creates|created|creating)\b/i },
34
36
  { title: 'Add', score: 88, re: /\b(?:add|adds|added|adding|wire|wires|wired|wiring)\b/i },
@@ -136,7 +138,11 @@ const bestIntentClause = (text) => {
136
138
  if (EXPLANATION.test(clause)) score -= 34
137
139
  if (CONSTRAINT.test(clause)) score -= 45
138
140
  if (NEGATIVE_PREFERENCE.test(clause)) score -= 70
139
- score += Math.round((i / Math.max(1, clauses.length - 1)) * 4)
141
+ // A short dependent tail such as "From ground up" or "On mobile" adds
142
+ // scope to the preceding request; it is not a standalone task. Recency is
143
+ // deliberately not a signal here: the title should represent the main
144
+ // theme, not whichever sentence happened to come last.
145
+ if (!action && !REQUEST.test(clause) && !INFORMATION_REQUEST.test(clause) && DEPENDENT_FRAGMENT.test(clause)) score -= 24
140
146
  if (!best || score > best.score) best = { clause, score }
141
147
  }
142
148
  return best?.clause || clauses[0] || null
@@ -175,13 +181,18 @@ const titleWord = (word) => {
175
181
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
176
182
  }
177
183
 
178
- const contentWords = (value) => {
184
+ const contentWords = (value, excludedFamilies = []) => {
179
185
  const words = String(value || '').match(/[\p{L}\p{N}][\p{L}\p{N}+#.'-]*/gu) || []
180
186
  const seen = new Set()
181
187
  const result = []
182
188
  for (let word of words) {
183
189
  const lower = word.toLowerCase()
184
190
  if (SKIP.has(lower) || /^https?$/i.test(word)) continue
191
+ // ACTIONS intentionally folds synonyms into one title verb (review/test/verify
192
+ // all become Audit). Once that verb is chosen, words from the same family are
193
+ // not objects. Keeping them spent the title budget on "Audit Reviews Audits"
194
+ // before the extractor reached the actual scope.
195
+ if (excludedFamilies.some((family) => family.test(word))) continue
185
196
  if (lower === 'flashes' || lower === 'flashing') word = 'flash'
186
197
  if (lower === 'failing' || lower === 'failed' || lower === 'fails') word = 'failure'
187
198
  const key = word.toLowerCase().replace(/(?:s|ed|ing)$/i, '')
@@ -227,12 +238,13 @@ const taskTitle = (value) => {
227
238
  const before = body.slice(0, action.index)
228
239
  objectText = body.slice(action.index + action.match.length)
229
240
  const weakLead = bestAction(before)
230
- if (weakLead?.score <= 76) domain = contentWords(before.slice(weakLead.index + weakLead.match.length)).slice(0, 3)
241
+ if (weakLead?.score <= 76) domain = contentWords(before.slice(weakLead.index + weakLead.match.length), [weakLead.re]).slice(0, 3)
231
242
  objectText = objectText.split(/\s*,?\s+(?:and|then)\s+(?:(?:also)\s+)?(?=(?:add|build|create|implement|remove|replace|update|wire)\b)/i, 1)[0]
232
243
  objectText = objectText.replace(/\b(?:and|then)\s+(?:audit|check|debug|investigate|review|test|verify)\b/gi, ' ')
233
244
  }
234
- let object = contentWords(objectText)
235
- if (!object.length) object = contentWords(body)
245
+ const excludedObjectFamilies = action ? [action.re] : []
246
+ let object = contentWords(objectText, excludedObjectFamilies)
247
+ if (!object.length) object = contentWords(body, excludedObjectFamilies)
236
248
  if (informationRequest) {
237
249
  const available = object.findIndex((word) => /^available$/i.test(word))
238
250
  if (available > 0) object = [object[available], ...object.slice(0, available), ...object.slice(available + 1)]