thinkpool-pair 0.7.338 → 0.7.339
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/bridge.mjs +2 -2
- package/command-catalog.mjs +1 -1
- package/git-diff-report.mjs +118 -0
- package/package.json +2 -1
package/bridge.mjs
CHANGED
|
@@ -58,6 +58,7 @@ import { startStructuredSession } from './runtime-session.mjs'
|
|
|
58
58
|
import { fallbackTerminalName } from './terminal-name.mjs'
|
|
59
59
|
import { defaultStructuredMode, normalizeStructuredEffort, shouldDeferStructuredRuntime, structuredModeForSlice, structuredModeLocked, structuredModesForLane, structuredRuntimeForCommand, structuredRuntimeMetadata, structuredRuntimeSupportsMode } from './runtime-registry.mjs'
|
|
60
60
|
import { commandCatalogForRuntime, commandHelpLine, reconcileCommandCatalog } from './command-catalog.mjs'
|
|
61
|
+
import { gitDiffReport } from './git-diff-report.mjs'
|
|
61
62
|
import { probeHermesRuntime } from './hermes-probe.mjs'
|
|
62
63
|
import { hermesRequiredMcpTools, hermesRoleFor } from './hermes-policy.mjs'
|
|
63
64
|
import { canonicalRoomFilePath, waitForNativeImages } from './codex-images.mjs'
|
|
@@ -4365,8 +4366,7 @@ channel
|
|
|
4365
4366
|
if (/^\/diff\s*$/.test(text)) {
|
|
4366
4367
|
try {
|
|
4367
4368
|
const cwd = s.cwd || process.cwd()
|
|
4368
|
-
|
|
4369
|
-
ctlLine(summary ? `Working tree changes\n${summary.slice(0, 1600)}` : 'Working tree clean')
|
|
4369
|
+
ctlLine(gitDiffReport({ cwd }))
|
|
4370
4370
|
} catch { ctlLine('Working-tree diff unavailable outside a readable Git checkout') }
|
|
4371
4371
|
return
|
|
4372
4372
|
}
|
package/command-catalog.mjs
CHANGED
|
@@ -19,7 +19,7 @@ export const CODE_ROOM_COMMANDS = Object.freeze([
|
|
|
19
19
|
command('/status', 'runtime, model, permissions, and busy state', 'control'),
|
|
20
20
|
command('/usage', 'session usage and provider limits', 'control'),
|
|
21
21
|
command('/context', 'current context-window usage', 'control'),
|
|
22
|
-
command('/diff', '
|
|
22
|
+
command('/diff', 'Git changes and branch status', 'control'),
|
|
23
23
|
command('/compact', 'compact context', 'runtime'),
|
|
24
24
|
command('/clear', 'clear context · confirms', 'clear'),
|
|
25
25
|
command('/model', 'pick model — opens selector', 'model'),
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MAX_FILES = 24
|
|
4
|
+
const DEFAULT_MAX_COMMITS = 5
|
|
5
|
+
const DEFAULT_MAX_CHARS = 1800
|
|
6
|
+
|
|
7
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? '' : 's'}`
|
|
8
|
+
|
|
9
|
+
function defaultRunGit(args, cwd) {
|
|
10
|
+
return execFileSync('git', args, {
|
|
11
|
+
cwd,
|
|
12
|
+
encoding: 'utf8',
|
|
13
|
+
timeout: 3000,
|
|
14
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cleanLine(value, max = 240) {
|
|
19
|
+
return String(value || '')
|
|
20
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '')
|
|
21
|
+
.replace(/\s+/g, ' ')
|
|
22
|
+
.trim()
|
|
23
|
+
.slice(0, max)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function gitText(runGit, cwd, args) {
|
|
27
|
+
return String(runGit(args, cwd) || '').trim()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function tryGit(runGit, cwd, args) {
|
|
31
|
+
try { return gitText(runGit, cwd, args) } catch { return '' }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveComparisonRef(runGit, cwd) {
|
|
35
|
+
const remoteHead = tryGit(runGit, cwd, ['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'])
|
|
36
|
+
if (remoteHead && tryGit(runGit, cwd, ['rev-parse', '--verify', '--quiet', remoteHead])) return remoteHead
|
|
37
|
+
for (const candidate of ['origin/main', 'origin/master', 'main', 'master']) {
|
|
38
|
+
if (tryGit(runGit, cwd, ['rev-parse', '--verify', '--quiet', candidate])) return candidate
|
|
39
|
+
}
|
|
40
|
+
return ''
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function boundedReport(lines, maxChars) {
|
|
44
|
+
const kept = []
|
|
45
|
+
let length = 0
|
|
46
|
+
for (const raw of lines) {
|
|
47
|
+
const line = cleanLine(raw)
|
|
48
|
+
if (!line) continue
|
|
49
|
+
const nextLength = length + (kept.length ? 1 : 0) + line.length
|
|
50
|
+
if (nextLength > maxChars) {
|
|
51
|
+
const suffix = '… output shortened'
|
|
52
|
+
if (length + (kept.length ? 1 : 0) + suffix.length <= maxChars) kept.push(suffix)
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
kept.push(line)
|
|
56
|
+
length = nextLength
|
|
57
|
+
}
|
|
58
|
+
return kept.join('\n')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function gitDiffReport({
|
|
62
|
+
cwd = process.cwd(),
|
|
63
|
+
runGit = defaultRunGit,
|
|
64
|
+
maxFiles = DEFAULT_MAX_FILES,
|
|
65
|
+
maxCommits = DEFAULT_MAX_COMMITS,
|
|
66
|
+
maxChars = DEFAULT_MAX_CHARS,
|
|
67
|
+
} = {}) {
|
|
68
|
+
gitText(runGit, cwd, ['rev-parse', '--show-toplevel'])
|
|
69
|
+
|
|
70
|
+
const status = tryGit(runGit, cwd, ['status', '--porcelain=v1', '--untracked-files=normal'])
|
|
71
|
+
const statusLines = status ? status.split(/\r?\n/).filter(Boolean) : []
|
|
72
|
+
const branch = tryGit(runGit, cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD']) || 'detached HEAD'
|
|
73
|
+
const head = tryGit(runGit, cwd, ['rev-parse', '--short=8', 'HEAD']) || 'unknown'
|
|
74
|
+
const subject = cleanLine(tryGit(runGit, cwd, ['log', '-1', '--pretty=%s']), 160)
|
|
75
|
+
const comparisonRef = resolveComparisonRef(runGit, cwd)
|
|
76
|
+
const lines = []
|
|
77
|
+
|
|
78
|
+
if (statusLines.length) {
|
|
79
|
+
lines.push(`${plural(statusLines.length, 'uncommitted file')}`)
|
|
80
|
+
for (const line of statusLines.slice(0, Math.max(0, maxFiles))) lines.push(line)
|
|
81
|
+
if (statusLines.length > maxFiles) lines.push(`… ${plural(statusLines.length - maxFiles, 'more file')}`)
|
|
82
|
+
} else {
|
|
83
|
+
lines.push('No uncommitted changes')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lines.push(`Branch · ${branch} · ${head}`)
|
|
87
|
+
|
|
88
|
+
if (!comparisonRef) {
|
|
89
|
+
if (subject) lines.push(`Latest commit · ${head} — ${subject}`)
|
|
90
|
+
return boundedReport(lines, maxChars)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const counts = tryGit(runGit, cwd, ['rev-list', '--left-right', '--count', `${comparisonRef}...HEAD`])
|
|
94
|
+
.split(/\s+/)
|
|
95
|
+
.map((value) => Number(value))
|
|
96
|
+
const behind = Number.isFinite(counts[0]) ? counts[0] : 0
|
|
97
|
+
const ahead = Number.isFinite(counts[1]) ? counts[1] : 0
|
|
98
|
+
|
|
99
|
+
if (ahead > 0) {
|
|
100
|
+
lines.push(`${plural(ahead, 'commit')} ahead of ${comparisonRef}${behind ? ` · ${plural(behind, 'commit')} behind` : ''}`)
|
|
101
|
+
const commitLines = tryGit(runGit, cwd, ['log', `--max-count=${Math.max(0, maxCommits)}`, '--pretty=%h %s', `${comparisonRef}..HEAD`])
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
if (commitLines.length) {
|
|
105
|
+
lines.push(`Commits not in ${comparisonRef}`)
|
|
106
|
+
lines.push(...commitLines)
|
|
107
|
+
if (ahead > commitLines.length) lines.push(`… ${plural(ahead - commitLines.length, 'more commit')}`)
|
|
108
|
+
}
|
|
109
|
+
} else if (behind > 0) {
|
|
110
|
+
if (subject) lines.push(`Latest commit · ${head} — ${subject} · already in ${comparisonRef}`)
|
|
111
|
+
lines.push(`${plural(behind, 'commit')} behind ${comparisonRef}`)
|
|
112
|
+
} else {
|
|
113
|
+
if (subject) lines.push(`Latest commit · ${head} — ${subject}`)
|
|
114
|
+
lines.push(`Up to date with ${comparisonRef}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return boundedReport(lines, maxChars)
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.339",
|
|
4
4
|
"description": "Connect Claude Code, Codex, or Hermes on your computer to a Thinkpool Code room.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"hermes-delegation-guard.mjs",
|
|
48
48
|
"runtime-registry.mjs",
|
|
49
49
|
"command-catalog.mjs",
|
|
50
|
+
"git-diff-report.mjs",
|
|
50
51
|
"runtime-session.mjs",
|
|
51
52
|
"turn-stall.mjs",
|
|
52
53
|
"update-gate.mjs",
|