@searls/turbocommit 0.10.2 → 0.12.0

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/README.md CHANGED
@@ -136,6 +136,12 @@ Here's every property with its default:
136
136
  // "Name <email>" → custom trailer value
137
137
  "coauthor": true,
138
138
 
139
+ // Auto-push after each turbocommit.
140
+ // Only pushes when fast-forward is possible (never forces).
141
+ // true → push after each commit
142
+ // false → don't push (default)
143
+ "push": false,
144
+
139
145
  "title": {
140
146
  // "agent" (default) → run a title agent to generate the headline
141
147
  // "transcript" → extract the first prompt as the headline
package/lib/git.js CHANGED
@@ -56,6 +56,15 @@ function currentBranch (cwd) {
56
56
  }
57
57
  }
58
58
 
59
+ function pushClean (cwd) {
60
+ try {
61
+ git('push', { cwd })
62
+ return true
63
+ } catch {
64
+ return false
65
+ }
66
+ }
67
+
59
68
  function esc (s) {
60
69
  return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`')
61
70
  }
@@ -66,5 +75,6 @@ module.exports = {
66
75
  hasChanges,
67
76
  addAndCommit,
68
77
  hasCommits,
69
- currentBranch
78
+ currentBranch,
79
+ pushClean
70
80
  }
package/lib/redact.js ADDED
@@ -0,0 +1,156 @@
1
+ const SAFE_NAMES = new Set([
2
+ // Shell / OS
3
+ 'HOME', 'USER', 'SHELL', 'PATH', 'PWD', 'OLDPWD', 'LANG', 'TERM',
4
+ 'EDITOR', 'VISUAL', 'PAGER', 'HOSTNAME', 'LOGNAME', 'DISPLAY', 'TZ',
5
+ 'TMPDIR', 'SHLVL', '_', 'BROWSER', 'COMMAND_MODE', 'COLUMNS', 'LINES',
6
+ 'MANPATH', 'INFOPATH',
7
+
8
+ // Terminal
9
+ 'COLORTERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'TERM_SESSION_ID',
10
+ 'ITERM_SESSION_ID', 'ITERM_PROFILE', 'APPLE_TERMINAL_ID',
11
+
12
+ // Locale
13
+ 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
14
+ 'LC_NUMERIC', 'LC_TIME',
15
+
16
+ // XDG
17
+ 'XDG_DATA_HOME', 'XDG_CONFIG_HOME', 'XDG_STATE_HOME', 'XDG_CACHE_HOME',
18
+ 'XDG_RUNTIME_DIR', 'XDG_DATA_DIRS', 'XDG_CONFIG_DIRS', 'XDG_SESSION_TYPE',
19
+ 'XDG_CURRENT_DESKTOP', 'XDG_SESSION_CLASS', 'XDG_SESSION_ID', 'XDG_SEAT',
20
+ 'XDG_VTNR', 'XDG_MENU_PREFIX',
21
+
22
+ // Dev environment
23
+ 'NODE_ENV', 'RAILS_ENV', 'RACK_ENV', 'GO_ENV', 'MIX_ENV', 'FLASK_ENV',
24
+ 'DEBUG', 'VERBOSE', 'LOG_LEVEL', 'PORT', 'HOST', 'CI',
25
+ 'NVM_DIR', 'NVM_BIN', 'NVM_INC', 'NVM_CD_FLAGS',
26
+ 'VOLTA_HOME', 'HOMEBREW_PREFIX', 'HOMEBREW_CELLAR', 'HOMEBREW_REPOSITORY',
27
+ 'HOMEBREW_SHELLENV_PREFIX',
28
+
29
+ // SSH / GPG (socket paths, not keys)
30
+ 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'GPG_AGENT_INFO', 'GPG_TTY',
31
+
32
+ // Git
33
+ 'GIT_EDITOR', 'GIT_PAGER', 'GIT_TERMINAL_PROMPT'
34
+ ])
35
+
36
+ // Values that are common English words, config toggles, or well-known
37
+ // non-secret defaults. Checked case-insensitively.
38
+ const SAFE_VALUES = new Set([
39
+ // Boolean-like
40
+ 'true', 'false', 'yes', 'no', 'on', 'off',
41
+
42
+ // Environment modes
43
+ 'development', 'production', 'staging', 'test', 'testing',
44
+ 'dev', 'prod', 'local', 'ci', 'qa', 'sandbox', 'preview',
45
+ 'canary', 'demo', 'benchmark', 'profile', 'uat', 'integration',
46
+ 'preprod', 'pre-production', 'stage', 'deployment',
47
+
48
+ // Log levels
49
+ 'trace', 'debug', 'info', 'warn', 'warning', 'error', 'fatal',
50
+ 'critical', 'notice', 'verbose', 'silent', 'quiet', 'off',
51
+
52
+ // Common toggles/modes
53
+ 'enabled', 'disabled', 'always', 'never', 'auto', 'default',
54
+ 'manual', 'none', 'null', 'undefined', 'inherit', 'unset',
55
+ 'strict', 'lax', 'permissive', 'enforcing', 'noninteractive',
56
+
57
+ // Editors, pagers, browsers (bare names)
58
+ 'vim', 'vi', 'nano', 'emacs', 'nvim', 'code', 'subl', 'mate',
59
+ 'micro', 'ed', 'pico', 'gedit', 'kate', 'hx', 'kak', 'kakoune',
60
+ 'joe', 'emacsclient', 'less', 'more', 'most', 'cat',
61
+ 'open', 'firefox', 'chromium', 'links', 'lynx', 'w3m', 'safari', 'opera',
62
+
63
+ // Common English words that appear as env values
64
+ 'allow', 'deny', 'block', 'drop', 'accept', 'reject',
65
+ 'public', 'private', 'master', 'main', 'release',
66
+ 'read', 'write', 'root', 'latest', 'stable', 'edge',
67
+ 'screen', 'random', 'node', 'ruby', 'python', 'go', 'java',
68
+ 'rust', 'php', 'slim', 'alpine',
69
+ 'localhost',
70
+
71
+ // Platform/architecture identifiers
72
+ 'linux', 'darwin', 'win32', 'windows', 'freebsd', 'openbsd',
73
+ 'netbsd', 'sunos', 'aix', 'android', 'cygwin', 'msys',
74
+ 'x86_64', 'amd64', 'x64', 'arm64', 'aarch64', 'i386', 'i686',
75
+ 'ia32', 'armv7l', 'arm', 'ppc64le', 's390x', 'riscv64',
76
+
77
+ // TERM values
78
+ 'xterm', 'xterm-256color', 'xterm-color', 'xterm-16color',
79
+ 'screen', 'screen-256color', 'tmux', 'tmux-256color',
80
+ 'linux', 'vt100', 'vt220', 'dumb', 'ansi', 'rxvt',
81
+ 'rxvt-unicode', 'rxvt-unicode-256color', 'alacritty', 'kitty',
82
+ 'wezterm', 'eterm-color', 'foot', 'foot-extra',
83
+ 'gnome-256color', 'konsole-256color', 'st-256color',
84
+ 'putty', 'putty-256color',
85
+
86
+ // COLORTERM
87
+ 'truecolor', '24bit',
88
+
89
+ // TERM_PROGRAM
90
+ 'apple_terminal', 'iterm.app', 'vscode', 'hyper',
91
+
92
+ // Docker/CI identifiers
93
+ 'bullseye', 'bookworm', 'jammy', 'lts',
94
+ 'travis-ci', 'circleci', 'codeship', 'github-actions',
95
+ 'gitlab-ci', 'jenkins', 'buildkite',
96
+
97
+ // System users
98
+ 'nobody', 'www-data', 'daemon', 'nonroot', 'appuser',
99
+ 'nginx', 'postgres', 'mysql', 'redis',
100
+
101
+ // XDG session types
102
+ 'x11', 'wayland', 'tty',
103
+
104
+ // Metasyntactic / genuinely-never-a-credential values
105
+ 'todo', 'fixme', 'tbd', 'n/a',
106
+ 'xxx', 'yyy', 'zzz', 'foo', 'bar', 'baz',
107
+ 'lorem', 'temp', 'tmp', 'blah'
108
+ ])
109
+
110
+ // Pattern-based rules for values that match known non-secret formats
111
+ const SAFE_VALUE_PATTERNS = [
112
+ /^\d+$/, // pure numeric
113
+ /^(.)\1+$/, // repeated single char (xxxx, ****, ....)
114
+ /^\d+\.\d+\.\d+(-[\w.]+)?$/, // semver (18.17.1, 1.0.0-beta.1)
115
+ /^[a-z]{2}_[A-Z]{2}([.\w-]*)?$/, // locale (en_US.UTF-8)
116
+ /^(C|POSIX)(\.UTF-8)?$/, // C/POSIX locale
117
+ /^[A-Z][a-z]+\/[A-Za-z_]+$/, // timezone (America/New_York)
118
+ /^(UTC|Etc\/UTC|US\/\w+)$/, // timezone shorthand
119
+ /^\/[a-z][\w/.-]*$/, // unix path (/usr/local/bin)
120
+ /^(https?|redis|mongodb|postgres(ql)?|mysql|amqp|smtp|memcached|elasticsearch):\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|::1)/, // localhost URLs
121
+ /^(xterm|screen|tmux|linux|vt\d+|dumb|ansi|rxvt|alacritty|kitty|wezterm|foot)(-[\w]+)*$/, // TERM identifiers
122
+ /^(localhost)?:\d+(\.\d+)?$/, // X11 DISPLAY (:0, :0.0)
123
+ /^wayland-\d+$/, // Wayland display
124
+ /^sqlite:(\/\/\/)?(:\w+:|[\w./]+)$/ // sqlite URLs
125
+ ]
126
+
127
+ function isSafeValue (value) {
128
+ if (!value || value.length <= 3) return true
129
+ if (SAFE_VALUES.has(value.toLowerCase())) return true
130
+ for (const pattern of SAFE_VALUE_PATTERNS) {
131
+ if (pattern.test(value)) return true
132
+ }
133
+ return false
134
+ }
135
+
136
+ function buildRedactions (env) {
137
+ env = env || process.env
138
+ const entries = []
139
+ for (const [name, value] of Object.entries(env)) {
140
+ if (SAFE_NAMES.has(name)) continue
141
+ if (isSafeValue(value)) continue
142
+ entries.push({ name, value })
143
+ }
144
+ entries.sort((a, b) => b.value.length - a.value.length)
145
+ return entries
146
+ }
147
+
148
+ function redact (text, redactions) {
149
+ if (!text) return text
150
+ for (const { name, value } of redactions) {
151
+ text = text.replaceAll(value, '[REDACTED:' + name + ']')
152
+ }
153
+ return text
154
+ }
155
+
156
+ module.exports = { buildRedactions, redact }
package/lib/run.js CHANGED
@@ -4,10 +4,11 @@ const path = require('path')
4
4
  const { loadJson, mergeConfig } = require('./io')
5
5
  const { parseTranscript, formatBody, formatTitleTranscript, extractHeadline, extractModel } = require('./transcript')
6
6
  const { runTitleAgent, runBodyAgent } = require('./agent')
7
- const { gitRoot, hasChanges, addAndCommit, hasCommits, currentBranch } = require('./git')
7
+ const { gitRoot, hasChanges, addAndCommit, hasCommits, currentBranch, pushClean } = require('./git')
8
8
  const { logEvent } = require('./log')
9
9
  const { wrapText } = require('./wrap')
10
10
  const { hasTrackedModifications, cleanupTracking } = require('./track')
11
+ const { redact, buildRedactions } = require('./redact')
11
12
  const { getAncestors, savePending, collectPending, cleanupConsumed, cleanupStale, readWatermark, saveWatermark, resolveParentCommit } = require('./session')
12
13
 
13
14
  /**
@@ -187,9 +188,20 @@ function run (input) {
187
188
  const coauthor = resolveCoauthor(config, hookInput.transcript_path, root)
188
189
  const tag = coauthor ? '\n\n' + coauthor : ''
189
190
 
190
- const sha = addAndCommit(root, headline, wrappedBody + tag)
191
+ const redactions = buildRedactions()
192
+ const safeHeadline = redact(headline, redactions)
193
+ const safeBody = redact(wrappedBody + tag, redactions)
194
+ const sha = addAndCommit(root, safeHeadline, safeBody)
191
195
 
192
- logEvent('success', { project, branch, context, title: headline })
196
+ logEvent('success', { project, branch, context, title: safeHeadline })
197
+
198
+ if (config.push === true) {
199
+ if (pushClean(root)) {
200
+ logEvent('push', { project, branch })
201
+ } else {
202
+ logEvent('push-fail', { project, branch })
203
+ }
204
+ }
193
205
 
194
206
  // Post-commit: save watermark and cleanup
195
207
  if (sessionId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@searls/turbocommit",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "description": "Auto-commit after every Claude Code turn",
5
5
  "bin": {
6
6
  "turbocommit": "./cli.js"