claude-code-autoconfig 1.0.195 → 1.0.197
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/.claude/commands/validate-cca-install.md +1 -1
- package/.claude/docs/autoconfig.docs.html +219 -131
- package/.claude/hooks/terminal-title.directive.md +86 -82
- package/.claude/hooks/terminal-title.js +71 -16
- package/.claude/scripts/sync-docs.js +12 -2
- package/CHANGELOG.md +7 -6
- package/CLAUDE.md +5 -9
- package/bin/cli.js +7 -3
- package/package.json +4 -3
|
@@ -68,7 +68,7 @@ for d in expected_dirs:
|
|
|
68
68
|
info.append(f'OK: .claude/{d}/ exists')
|
|
69
69
|
|
|
70
70
|
# --- 2. Check command files and versions ---
|
|
71
|
-
dev_only = ['
|
|
71
|
+
dev_only = ['deploy-to-npmjs.md']
|
|
72
72
|
pkg_cmds_dir = os.path.join(pkg_dir, '.claude', 'commands')
|
|
73
73
|
local_cmds_dir = os.path.join(claude_dir, 'commands')
|
|
74
74
|
|
|
@@ -888,11 +888,6 @@
|
|
|
888
888
|
<span class="tree-file-icon">📄</span>
|
|
889
889
|
<span class="file">gls.md</span>
|
|
890
890
|
</div>
|
|
891
|
-
<div class="tree-item indent-3 hidden" data-info="publish" data-parent="commands">
|
|
892
|
-
<span class="tree-spacer"></span>
|
|
893
|
-
<span class="tree-file-icon">📄</span>
|
|
894
|
-
<span class="file">publish.md</span>
|
|
895
|
-
</div>
|
|
896
891
|
<div class="tree-item indent-3 hidden" data-info="recover-context" data-parent="commands">
|
|
897
892
|
<span class="tree-spacer"></span>
|
|
898
893
|
<span class="tree-file-icon">📄</span>
|
|
@@ -953,6 +948,11 @@
|
|
|
953
948
|
<span class="tree-file-icon">📄</span>
|
|
954
949
|
<span class="file">migrate-feedback.js</span>
|
|
955
950
|
</div>
|
|
951
|
+
<div class="tree-item indent-3 hidden" data-info="terminal-title.directive-hook" data-parent="hooks">
|
|
952
|
+
<span class="tree-spacer"></span>
|
|
953
|
+
<span class="tree-file-icon">📄</span>
|
|
954
|
+
<span class="file">terminal-title.directive.md</span>
|
|
955
|
+
</div>
|
|
956
956
|
<div class="tree-item indent-3 hidden" data-info="terminal-title-hook" data-parent="hooks">
|
|
957
957
|
<span class="tree-spacer"></span>
|
|
958
958
|
<span class="tree-file-icon">📄</span>
|
|
@@ -1263,7 +1263,7 @@
|
|
|
1263
1263
|
title: '.claude/ Directory',
|
|
1264
1264
|
desc: 'Commands, rules, settings, and these docs. Keeps configuration organized as your project grows.'
|
|
1265
1265
|
},
|
|
1266
|
-
|
|
1266
|
+
'rules': {
|
|
1267
1267
|
title: 'rules/',
|
|
1268
1268
|
desc: 'Path-scoped context that loads when Claude works on matching files.'
|
|
1269
1269
|
},
|
|
@@ -1308,11 +1308,6 @@
|
|
|
1308
1308
|
desc: 'Get the latest screenshot(s) and display them.<div style="margin-top: 12px;"><strong>Parameters</strong><table style="margin-top: 6px; border-collapse: collapse; font-size: 0.9em; text-align: left;"><tr style="text-align: left; border-bottom: 1px solid var(--border);"><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Name</th><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Type</th><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Required</th><th style="padding: 4px 8px 4px 0;">Description</th></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>count</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>integer</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">optional</td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Number of screenshots to display. Use /gls-N syntax. Default: 1. Min: 1.</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>path</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>string</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">optional</td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Screenshot directory path. Saved for future use. Auto-detected if omitted.</td></tr></table></div><div style="margin-top: 12px;"><strong>Responses</strong><table style="margin-top: 6px; border-collapse: collapse; font-size: 0.9em; text-align: left;"><tr style="text-align: left; border-bottom: 1px solid var(--border);"><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Status</th><th style="padding: 4px 8px 4px 0;">Description</th></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>success</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Displays requested screenshot(s) from newest to oldest.</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>no-screenshots</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Directory exists but contains no image files.</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>no-directory</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Unable to detect screenshot directory — prompts for path.</td></tr></table></div><div style="margin-top: 12px;"><strong>Side Effects</strong><div style="margin-top: 4px; font-size: 0.9em;">Saves detected screenshot path to .claude/cca.config.json on first run</div></div><div style="margin-top: 12px;"><strong>Examples</strong><div style="margin-top: 6px; background: var(--bg-elevated); border-radius: 6px; padding: 8px 12px; font-family: monospace; font-size: 0.85em;"><div><code>/gls</code> <span style="opacity: 0.6;">— Display the most recent screenshot</span></div><div><code>/gls-3</code> <span style="opacity: 0.6;">— Display the 3 most recent screenshots</span></div><div><code>/gls /path/to/dir</code> <span style="opacity: 0.6;">— Use a specific screenshot directory</span></div></div></div>',
|
|
1309
1309
|
trigger: '/gls'
|
|
1310
1310
|
},
|
|
1311
|
-
'publish': {
|
|
1312
|
-
title: 'publish.md',
|
|
1313
|
-
desc: 'Runs tests, bumps version, commits, and publishes to npm.<div style="margin-top: 12px;"><strong>Parameters</strong><div style="margin-top: 4px; opacity: 0.6;">None</div></div>',
|
|
1314
|
-
trigger: '/publish'
|
|
1315
|
-
},
|
|
1316
1311
|
'recover-context': {
|
|
1317
1312
|
title: 'recover-context.md',
|
|
1318
1313
|
desc: 'Recovers conversation context from the session transcript after compaction.<div style="margin-top: 12px;"><strong>Parameters</strong><table style="margin-top: 6px; border-collapse: collapse; font-size: 0.9em; text-align: left;"><tr style="text-align: left; border-bottom: 1px solid var(--border);"><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Name</th><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Type</th><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Required</th><th style="padding: 4px 8px 4px 0;">Description</th></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>minutes</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>integer</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">required</td><td style="padding: 4px 8px 4px 0; vertical-align: top;">How far back to recover, in minutes. Leading dash optional. Min: 1.</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>--show</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>flag</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">optional</td><td style="padding: 4px 8px 4px 0; vertical-align: top;">Opens the extracted transcript in your default editor.</td></tr></table></div><div style="margin-top: 12px;"><strong>Responses</strong><table style="margin-top: 6px; border-collapse: collapse; font-size: 0.9em; text-align: left;"><tr style="text-align: left; border-bottom: 1px solid var(--border);"><th style="padding: 4px 8px 4px 0; white-space: nowrap;">Status</th><th style="padding: 4px 8px 4px 0;">Description</th></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>success</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">~{tokens} tokens recovered ({N} messages across {sessions} session(s), last {minutes} min).</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>no-transcript</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">No transcript files found.</td></tr><tr style="border-bottom: 1px solid var(--border);"><td style="padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap;"><code>no-messages</code></td><td style="padding: 4px 8px 4px 0; vertical-align: top;">No messages found in the requested time range.</td></tr></table></div><div style="margin-top: 12px;"><strong>Side Effects</strong><div style="margin-top: 4px; font-size: 0.9em;">Reads .jsonl transcripts from ~/.claude/projects/, writes temp file</div></div><div style="margin-top: 12px;"><strong>Examples</strong><div style="margin-top: 6px; background: var(--bg-elevated); border-radius: 6px; padding: 8px 12px; font-family: monospace; font-size: 0.85em;"><div><code>/recover-context -60</code> <span style="opacity: 0.6;">— Last 60 minutes of conversation</span></div><div><code>/recover-context 120</code> <span style="opacity: 0.6;">— Last 2 hours (dash optional)</span></div><div><code>/recover-context -60 --show</code> <span style="opacity: 0.6;">— Last 60 min + open transcript file</span></div></div></div>',
|
|
@@ -1353,10 +1348,15 @@
|
|
|
1353
1348
|
desc: 'On session start, migrates custom FEEDBACK.md content to',
|
|
1354
1349
|
trigger: 'SessionStart'
|
|
1355
1350
|
},
|
|
1351
|
+
'terminal-title.directive-hook': {
|
|
1352
|
+
title: 'terminal-title.directive.md',
|
|
1353
|
+
desc: 'terminal-title.directive.md in hooks/',
|
|
1354
|
+
trigger: 'PostToolUse hook'
|
|
1355
|
+
},
|
|
1356
1356
|
'terminal-title-hook': {
|
|
1357
1357
|
title: 'terminal-title.js',
|
|
1358
|
-
desc: '
|
|
1359
|
-
trigger: '
|
|
1358
|
+
desc: 'terminal-title.js in hooks/',
|
|
1359
|
+
trigger: 'PostToolUse hook'
|
|
1360
1360
|
},
|
|
1361
1361
|
'feedback-feedback': {
|
|
1362
1362
|
title: 'FEEDBACK.md',
|
|
@@ -1569,7 +1569,7 @@ CRITICAL: A plausible-looking cause from code reading is NOT confirmed evidence.
|
|
|
1569
1569
|
|
|
1570
1570
|
> Run \`/autoconfig\` to populate this based on your project.`
|
|
1571
1571
|
},
|
|
1572
|
-
|
|
1572
|
+
'autoconfig-update': {
|
|
1573
1573
|
filename: 'autoconfig-update.md',
|
|
1574
1574
|
content: `<!-- @applied
|
|
1575
1575
|
-->
|
|
@@ -1721,36 +1721,6 @@ OS=$(uname -s); echo "OS=$OS"; for d in \\
|
|
|
1721
1721
|
"$HOME/Videos/Captures"; do \\
|
|
1722
1722
|
[ -d "$d" ] || continue; \\
|
|
1723
1723
|
newest=$(ls -t "$d/" 2>/dev/null | grep -iE '\\.(png|jpg|jpeg|bmp|webp|gif)$' | head -1); \\`
|
|
1724
|
-
},
|
|
1725
|
-
'publish': {
|
|
1726
|
-
filename: 'publish.md',
|
|
1727
|
-
content: `# Publish
|
|
1728
|
-
|
|
1729
|
-
Run tests, bump the patch version, commit, push, and publish to npm.
|
|
1730
|
-
|
|
1731
|
-
## Steps
|
|
1732
|
-
|
|
1733
|
-
1. **Sync README.md** — Compare the current README against the actual state of the project (file tree, slash commands, features, settings). If anything is out of date or missing, update the README before proceeding. Check:
|
|
1734
|
-
- File tree matches actual \`.claude/\` directory contents
|
|
1735
|
-
- Slash commands table matches actual \`.claude/commands/\` files
|
|
1736
|
-
- Feature sections reflect current capabilities (update system, MEMORY.md, hooks, etc.)
|
|
1737
|
-
- No references to nonexistent commands or files
|
|
1738
|
-
2. Run the project's test suite (\`npm test\`)
|
|
1739
|
-
3. **If tests fail, stop here.** Do not publish broken code.
|
|
1740
|
-
4. Check for uncommitted changes. If there are any:
|
|
1741
|
-
a. Stage all changes (\`git add -A\`)
|
|
1742
|
-
b. Generate a conventional commit message based on the diff
|
|
1743
|
-
c. Commit the changes
|
|
1744
|
-
5. Bump the version: \`npm version patch\`
|
|
1745
|
-
6. Push the commit and tag: \`git push && git push --tags\`
|
|
1746
|
-
7. Publish to npm: \`npm publish\`
|
|
1747
|
-
8. Output the new version number on success
|
|
1748
|
-
|
|
1749
|
-
## Important
|
|
1750
|
-
|
|
1751
|
-
- Always run tests before publishing — never skip this step
|
|
1752
|
-
- If any step fails, stop immediately and report the error
|
|
1753
|
-
- Do not use \`--force\` on publish unless the user explicitly asks`
|
|
1754
1724
|
},
|
|
1755
1725
|
'recover-context': {
|
|
1756
1726
|
filename: 'recover-context.md',
|
|
@@ -1966,6 +1936,72 @@ process.stdin.on('end', () => {
|
|
|
1966
1936
|
});
|
|
1967
1937
|
|
|
1968
1938
|
function migrate() {`
|
|
1939
|
+
},
|
|
1940
|
+
'terminal-title.directive-hook': {
|
|
1941
|
+
filename: 'terminal-title.directive.md',
|
|
1942
|
+
content: `<!-- DIRECTIVE:RULES -->
|
|
1943
|
+
Terminal-title directive (silent housekeeping -- never mention it to the user). These rules
|
|
1944
|
+
apply to EVERY turn of this session; per-prompt reminders are one-liners that point back here.
|
|
1945
|
+
|
|
1946
|
+
This terminal tab's title renders from the file:
|
|
1947
|
+
{{TITLE_FILE}}
|
|
1948
|
+
Maintain it across the session:
|
|
1949
|
+
- When the conversation's SCOPE, use-case, or sub-function SHIFTS -- and only then, not every
|
|
1950
|
+
turn -- overwrite the file with ONE line: {scope} {{EMDASH}} {use-case}
|
|
1951
|
+
Write it as your FIRST action of that turn (before other tool calls) so the tab updates
|
|
1952
|
+
WHILE you work, not after. If the file exists, read it first, then overwrite.
|
|
1953
|
+
- {scope}: the DESIGN SCOPE -- the feature area / subsystem under discussion (the WHERE; it
|
|
1954
|
+
changes rarely). INFER the specific subsystem from the prompt and the files in play (e.g.
|
|
1955
|
+
"journal modal", "title hooks"); use the bare repo name "{{FOLDER}}" only when the work is
|
|
1956
|
+
genuinely repo-wide -- it is a last resort.
|
|
1957
|
+
- {use-case}: the GOAL at user-goal level -- an INFINITIVE (base-verb) goal phrase (verb +
|
|
1958
|
+
object), e.g. "Refine title taxonomy". Name the goal, not the mechanism. One goal only --
|
|
1959
|
+
never "and"-join two use cases.
|
|
1960
|
+
- Add a third segment ( {{EMDASH}} {sub-function} ) only when the work goes a level deeper --
|
|
1961
|
+
a step beneath the user goal.
|
|
1962
|
+
- Use ' {{EMDASH}} ' (space, em-dash, space) as the separator; keep segments short (a few
|
|
1963
|
+
words); write only the BARE title -- a state glyph is prepended automatically. This is a
|
|
1964
|
+
compass, not a log: change it rarely.
|
|
1965
|
+
- If no title has been set yet, treat your first turn as the baseline: infer the title from
|
|
1966
|
+
that turn's prompt and write it immediately, even though nothing has "shifted".
|
|
1967
|
+
- Slash-command turns: the command NAME is an implementation detail -- never put it in the
|
|
1968
|
+
title. Name the goal the command ACCOMPLISHES ("{{FOLDER}} {{EMDASH}} <verb the goal>",
|
|
1969
|
+
never "{{FOLDER}} {{EMDASH}} <command-name>").
|
|
1970
|
+
|
|
1971
|
+
Pending-question signal: when you END a turn on a question the user must answer before you`
|
|
1972
|
+
},
|
|
1973
|
+
'terminal-title-hook': {
|
|
1974
|
+
filename: 'terminal-title.js',
|
|
1975
|
+
content: `#!/usr/bin/env node
|
|
1976
|
+
/**
|
|
1977
|
+
* Terminal Title — distributable plugin hook (installed to <project>/.claude/hooks/terminal-title.js).
|
|
1978
|
+
* ONE self-dispatching hook for five events (keyed on hook_event_name):
|
|
1979
|
+
* UserPromptSubmit -> ⬤ working + inject the per-prompt REMINDER one-liner (+BASELINE while no
|
|
1980
|
+
* title exists, +COMMAND on /slash turns) + clear any stale {sid}.ask flag
|
|
1981
|
+
* PostToolUse -> ⬤ working (refresh, so a mid-turn title flip shows live + clears a stale ◐)
|
|
1982
|
+
* Notification -> ◐ awaiting your approval (permission_prompt matcher only)
|
|
1983
|
+
* Stop -> ✻ idle / done — OR ◐ awaiting (+ a 2nd BEL = gold tab) when the turn ended
|
|
1984
|
+
* on a question (last visible response text ends in '?', or a {sid}.ask flag)
|
|
1985
|
+
* SessionStart -> ✻ idle "Claude Code — New session" (or an existing title on resume/compact)
|
|
1986
|
+
* + inject the FULL RULES block — once per session instead of every prompt
|
|
1987
|
+
* (~90% less directive overhead); resume/compact re-inject so a squeezed
|
|
1988
|
+
* context re-learns the rules.
|
|
1989
|
+
*
|
|
1990
|
+
* Dedupe: when this file is the USER-LEVEL copy (~/.claude/hooks) and the project ships its own
|
|
1991
|
+
* managed copy (<cwd>/.claude/hooks/terminal-title.js), this copy stands down entirely — otherwise
|
|
1992
|
+
* both hooks inject directives naming two different title files (double tokens, double Writes).
|
|
1993
|
+
*
|
|
1994
|
+
* The title is set TWO ways on every event: \`process.title\` (= SetConsoleTitleW on Windows — the ONLY
|
|
1995
|
+
* mechanism that flips the tab on UserPromptSubmit, where Claude Code drops \`terminalSequence\`) AND
|
|
1996
|
+
* \`terminalSequence\` (honored on the other events). node writes UTF-8 natively, so glyphs go out as-is.
|
|
1997
|
+
*
|
|
1998
|
+
* Title files are PROJECT-SCOPED at <cwd>/.claude/hooks/.titles/<session_id>.txt — the model authors
|
|
1999
|
+
* them; the directive injected each prompt tells it the path + format. Optional forensic log (one line
|
|
2000
|
+
* per paint, for tracing an out-of-sync tab) is gated behind CLAUDE_TITLE_DEBUG=1 — default OFF,
|
|
2001
|
+
* ~512KB-capped, written to .titles/_debug.log — so it never ships a growing log.
|
|
2002
|
+
*
|
|
2003
|
+
* Requires \`env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE = "1"\` (set by plugin.json), or CC's own writer races.
|
|
2004
|
+
*/`
|
|
1969
2005
|
},
|
|
1970
2006
|
'feedback-feedback': {
|
|
1971
2007
|
filename: 'FEEDBACK.md',
|
|
@@ -2002,93 +2038,145 @@ Claude reads this file but must never write to it. When Claude discovers project
|
|
|
2002
2038
|
},
|
|
2003
2039
|
'settings': {
|
|
2004
2040
|
filename: 'settings.json',
|
|
2005
|
-
content: `{
|
|
2006
|
-
"env": {
|
|
2007
|
-
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "0"
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
"
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
"
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
"
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
"
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
"
|
|
2088
|
-
"
|
|
2089
|
-
"
|
|
2090
|
-
|
|
2091
|
-
|
|
2041
|
+
content: `{
|
|
2042
|
+
"env": {
|
|
2043
|
+
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "0",
|
|
2044
|
+
"CLAUDE_CODE_DISABLE_TERMINAL_TITLE": "1"
|
|
2045
|
+
},
|
|
2046
|
+
"hooks": {
|
|
2047
|
+
"SessionStart": [
|
|
2048
|
+
{
|
|
2049
|
+
"matcher": "",
|
|
2050
|
+
"hooks": [
|
|
2051
|
+
{
|
|
2052
|
+
"type": "command",
|
|
2053
|
+
"command": "node .claude/hooks/migrate-feedback.js"
|
|
2054
|
+
}
|
|
2055
|
+
]
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
"matcher": "*",
|
|
2059
|
+
"hooks": [
|
|
2060
|
+
{
|
|
2061
|
+
"type": "command",
|
|
2062
|
+
"command": "node .claude/hooks/terminal-title.js"
|
|
2063
|
+
}
|
|
2064
|
+
]
|
|
2065
|
+
}
|
|
2066
|
+
],
|
|
2067
|
+
"UserPromptSubmit": [
|
|
2068
|
+
{
|
|
2069
|
+
"matcher": "",
|
|
2070
|
+
"hooks": [
|
|
2071
|
+
{
|
|
2072
|
+
"type": "command",
|
|
2073
|
+
"command": "node .claude/hooks/terminal-title.js"
|
|
2074
|
+
}
|
|
2075
|
+
]
|
|
2076
|
+
}
|
|
2077
|
+
],
|
|
2078
|
+
"Stop": [
|
|
2079
|
+
{
|
|
2080
|
+
"matcher": "",
|
|
2081
|
+
"hooks": [
|
|
2082
|
+
{
|
|
2083
|
+
"type": "command",
|
|
2084
|
+
"command": "node .claude/hooks/terminal-title.js"
|
|
2085
|
+
}
|
|
2086
|
+
]
|
|
2087
|
+
}
|
|
2088
|
+
],
|
|
2089
|
+
"Notification": [
|
|
2090
|
+
{
|
|
2091
|
+
"matcher": "permission_prompt",
|
|
2092
|
+
"hooks": [
|
|
2093
|
+
{
|
|
2094
|
+
"type": "command",
|
|
2095
|
+
"command": "node .claude/hooks/terminal-title.js"
|
|
2096
|
+
}
|
|
2097
|
+
]
|
|
2098
|
+
}
|
|
2099
|
+
],
|
|
2100
|
+
"PostToolUse": [
|
|
2101
|
+
{
|
|
2102
|
+
"matcher": "Edit|Write",
|
|
2103
|
+
"hooks": [
|
|
2104
|
+
{
|
|
2105
|
+
"type": "command",
|
|
2106
|
+
"command": "node .claude/hooks/feedback-rule-check.js"
|
|
2107
|
+
}
|
|
2108
|
+
]
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
"matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|WebSearch|Task|AskUserQuestion",
|
|
2112
|
+
"hooks": [
|
|
2113
|
+
{
|
|
2114
|
+
"type": "command",
|
|
2115
|
+
"command": "node .claude/hooks/terminal-title.js"
|
|
2116
|
+
}
|
|
2117
|
+
]
|
|
2118
|
+
}
|
|
2119
|
+
]
|
|
2120
|
+
},
|
|
2121
|
+
"permissions": {
|
|
2122
|
+
"allow": [
|
|
2123
|
+
"Read(./**)",
|
|
2124
|
+
"Edit(./**)",
|
|
2125
|
+
"Write(./**)",
|
|
2126
|
+
"Glob",
|
|
2127
|
+
"Grep",
|
|
2128
|
+
"WebSearch",
|
|
2129
|
+
"WebFetch",
|
|
2130
|
+
"Bash(npm test)",
|
|
2131
|
+
"Bash(npm test *)",
|
|
2132
|
+
"Bash(npm run dev)",
|
|
2133
|
+
"Bash(npm run build)",
|
|
2134
|
+
"Bash(npm run lint)",
|
|
2135
|
+
"Bash(npm run lint:fix)",
|
|
2136
|
+
"Bash(npm run typecheck)",
|
|
2137
|
+
"Bash(npm run test)",
|
|
2138
|
+
"Bash(npm run test:*)",
|
|
2139
|
+
"Bash(npm run validate)",
|
|
2140
|
+
"Bash(git status)",
|
|
2141
|
+
"Bash(git status *)",
|
|
2142
|
+
"Bash(git diff *)",
|
|
2143
|
+
"Bash(git add *)",
|
|
2144
|
+
"Bash(git commit *)",
|
|
2145
|
+
"Bash(git push *)",
|
|
2146
|
+
"Bash(git pull)",
|
|
2147
|
+
"Bash(git pull *)",
|
|
2148
|
+
"Bash(git checkout *)",
|
|
2149
|
+
"Bash(git branch *)",
|
|
2150
|
+
"Bash(node .claude/*)",
|
|
2151
|
+
"Bash(python3 *)",
|
|
2152
|
+
"Bash(npx claude-code-autoconfig*)",
|
|
2153
|
+
"Bash(npm pack *)",
|
|
2154
|
+
"Bash(tar *)",
|
|
2155
|
+
"Bash(mktemp *)",
|
|
2156
|
+
"Bash(find ./*)",
|
|
2157
|
+
"Bash(ls *)",
|
|
2158
|
+
"Bash(dir *)",
|
|
2159
|
+
"Bash(mkdir *)",
|
|
2160
|
+
"Bash(mkdir -p *)",
|
|
2161
|
+
"Bash(start *)",
|
|
2162
|
+
"Bash(start .claude *)",
|
|
2163
|
+
"Bash(open *)",
|
|
2164
|
+
"Bash(open .claude *)",
|
|
2165
|
+
"Bash(xdg-open *)",
|
|
2166
|
+
"Bash(xdg-open .claude *)"
|
|
2167
|
+
],
|
|
2168
|
+
"deny": [
|
|
2169
|
+
"Read(./.env)",
|
|
2170
|
+
"Read(./.env.*)",
|
|
2171
|
+
"Read(./secrets/**)",
|
|
2172
|
+
"Read(./**/credentials.*)",
|
|
2173
|
+
"Read(./**/*.pem)",
|
|
2174
|
+
"Read(./**/*.key)",
|
|
2175
|
+
"Bash(rm -rf *)",
|
|
2176
|
+
"Bash(curl *)",
|
|
2177
|
+
"Bash(wget *)"
|
|
2178
|
+
]
|
|
2179
|
+
}
|
|
2092
2180
|
}`
|
|
2093
2181
|
},
|
|
2094
2182
|
'mcp': {
|
|
@@ -1,82 +1,86 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
terminal-title.directive.md — the instruction terminal-title.js injects into the
|
|
3
|
-
model's context
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
as
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<!--
|
|
1
|
+
<!--
|
|
2
|
+
terminal-title.directive.md — the instruction text terminal-title.js injects into the
|
|
3
|
+
model's context. THIS is the tunable "product" surface: reword it freely without touching
|
|
4
|
+
the hook's logic.
|
|
5
|
+
|
|
6
|
+
Token-lean delivery (v2): the full rulebook is injected ONCE per session (SessionStart,
|
|
7
|
+
including resume/compact so a squeezed context re-learns it); every prompt gets only a
|
|
8
|
+
short reminder, plus state-specific addenda the hook selects when they apply.
|
|
9
|
+
|
|
10
|
+
Blocks below:
|
|
11
|
+
RULES — the full directive, injected at SessionStart (all sources)
|
|
12
|
+
REMINDER — the per-prompt one-liner (every UserPromptSubmit)
|
|
13
|
+
BASELINE — appended to REMINDER while no title file exists yet (first turn)
|
|
14
|
+
COMMAND — appended to REMINDER when the turn starts with a /slash-command
|
|
15
|
+
terminal-title.js picks REMINDER (+BASELINE/+COMMAND as applicable), substitutes the
|
|
16
|
+
tokens, and injects. SessionStart injects RULES.
|
|
17
|
+
|
|
18
|
+
Tokens (substituted at runtime):
|
|
19
|
+
{{TITLE_FILE}} — absolute path to this session's title file the model writes
|
|
20
|
+
{{ASK_FILE}} — absolute path to this session's one-shot {sid}.ask flag
|
|
21
|
+
{{FOLDER}} — the working-directory folder name (scope fallback)
|
|
22
|
+
{{EMDASH}} — the ' — ' segment separator
|
|
23
|
+
{{CMD}} — the slash-command name (COMMAND block only)
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
<!-- DIRECTIVE:RULES -->
|
|
27
|
+
Terminal-title directive (silent housekeeping -- never mention it to the user). These rules
|
|
28
|
+
apply to EVERY turn of this session; per-prompt reminders are one-liners that point back here.
|
|
29
|
+
|
|
30
|
+
This terminal tab's title renders from the file:
|
|
31
|
+
{{TITLE_FILE}}
|
|
32
|
+
Maintain it across the session:
|
|
33
|
+
- When the conversation's SCOPE, use-case, or sub-function SHIFTS -- and only then, not every
|
|
34
|
+
turn -- overwrite the file with ONE line: {scope} {{EMDASH}} {use-case}
|
|
35
|
+
Write it as your FIRST action of that turn (before other tool calls) so the tab updates
|
|
36
|
+
WHILE you work, not after. If the file exists, read it first, then overwrite.
|
|
37
|
+
- {scope}: the DESIGN SCOPE -- the feature area / subsystem under discussion (the WHERE; it
|
|
38
|
+
changes rarely). INFER the specific subsystem from the prompt and the files in play (e.g.
|
|
39
|
+
"journal modal", "title hooks"); use the bare repo name "{{FOLDER}}" only when the work is
|
|
40
|
+
genuinely repo-wide -- it is a last resort.
|
|
41
|
+
- {use-case}: the GOAL at user-goal level -- an INFINITIVE (base-verb) goal phrase (verb +
|
|
42
|
+
object), e.g. "Refine title taxonomy". Name the goal, not the mechanism. One goal only --
|
|
43
|
+
never "and"-join two use cases.
|
|
44
|
+
- Add a third segment ( {{EMDASH}} {sub-function} ) only when the work goes a level deeper --
|
|
45
|
+
a step beneath the user goal.
|
|
46
|
+
- Use ' {{EMDASH}} ' (space, em-dash, space) as the separator; keep segments short (a few
|
|
47
|
+
words); write only the BARE title -- a state glyph is prepended automatically. This is a
|
|
48
|
+
compass, not a log: change it rarely.
|
|
49
|
+
- If no title has been set yet, treat your first turn as the baseline: infer the title from
|
|
50
|
+
that turn's prompt and write it immediately, even though nothing has "shifted".
|
|
51
|
+
- Slash-command turns: the command NAME is an implementation detail -- never put it in the
|
|
52
|
+
title. Name the goal the command ACCOMPLISHES ("{{FOLDER}} {{EMDASH}} <verb the goal>",
|
|
53
|
+
never "{{FOLDER}} {{EMDASH}} <command-name>").
|
|
54
|
+
|
|
55
|
+
Pending-question signal: when you END a turn on a question the user must answer before you
|
|
56
|
+
can proceed, do BOTH as near-final actions so the tab flips to the AWAITING half-circle
|
|
57
|
+
(instead of the idle asterisk):
|
|
58
|
+
1. Write the flag file {{ASK_FILE}} (any short content, e.g. "1"). This is the RELIABLE
|
|
59
|
+
trigger -- it is on disk before the turn ends, so it never misses on phrasing or timing.
|
|
60
|
+
The flag is one-shot (consumed at turn end, auto-cleared next turn), so write it ONLY on
|
|
61
|
+
a turn genuinely blocked on an answer -- but ALWAYS then, even when the closing question
|
|
62
|
+
is wrapped in parens or is not the literal final character of the message.
|
|
63
|
+
2. Phrase your FINAL line to end with a question mark ('?') -- the backup signal, and good
|
|
64
|
+
UX. A single trailing parenthetical aside after the '?' is fine.
|
|
65
|
+
Make the closing question self-contained: answerable without re-reading the response above
|
|
66
|
+
it. Never signal for a rhetorical question or a recap.
|
|
67
|
+
<!-- /DIRECTIVE:RULES -->
|
|
68
|
+
|
|
69
|
+
<!-- DIRECTIVE:REMINDER -->
|
|
70
|
+
Terminal-title reminder (housekeeping -- never mention to the user; full rules were injected
|
|
71
|
+
at session start): if this turn SHIFTS the scope/use-case, FIRST action: overwrite
|
|
72
|
+
{{TITLE_FILE}} with "{scope} {{EMDASH}} {use-case}". If you END this turn blocked on a
|
|
73
|
+
question, write the flag file {{ASK_FILE}} and end your final line with '?'.
|
|
74
|
+
<!-- /DIRECTIVE:REMINDER -->
|
|
75
|
+
|
|
76
|
+
<!-- DIRECTIVE:BASELINE -->
|
|
77
|
+
No title is set yet -- treat THIS turn as the baseline: infer {scope} {{EMDASH}} {use-case}
|
|
78
|
+
from this prompt and the files in play, and write {{TITLE_FILE}} NOW (first action). Do not
|
|
79
|
+
default to the bare folder name "{{FOLDER}}" unless the work is genuinely repo-wide.
|
|
80
|
+
<!-- /DIRECTIVE:BASELINE -->
|
|
81
|
+
|
|
82
|
+
<!-- DIRECTIVE:COMMAND -->
|
|
83
|
+
This turn runs the /{{CMD}} slash command -- the command NAME is an implementation detail:
|
|
84
|
+
never put "{{CMD}}" in the title. If the scope shifted, name the goal the command
|
|
85
|
+
ACCOMPLISHES (e.g. "<area> {{EMDASH}} <verb the goal>").
|
|
86
|
+
<!-- /DIRECTIVE:COMMAND -->
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Terminal Title — distributable plugin hook (installed to <project>/.claude/hooks/terminal-title.js).
|
|
4
4
|
* ONE self-dispatching hook for five events (keyed on hook_event_name):
|
|
5
|
-
* UserPromptSubmit -> ⬤ working + inject the
|
|
6
|
-
* clear any stale {sid}.ask flag
|
|
5
|
+
* UserPromptSubmit -> ⬤ working + inject the per-prompt REMINDER one-liner (+BASELINE while no
|
|
6
|
+
* title exists, +COMMAND on /slash turns) + clear any stale {sid}.ask flag
|
|
7
7
|
* PostToolUse -> ⬤ working (refresh, so a mid-turn title flip shows live + clears a stale ◐)
|
|
8
8
|
* Notification -> ◐ awaiting your approval (permission_prompt matcher only)
|
|
9
9
|
* Stop -> ✻ idle / done — OR ◐ awaiting (+ a 2nd BEL = gold tab) when the turn ended
|
|
10
10
|
* on a question (last visible response text ends in '?', or a {sid}.ask flag)
|
|
11
11
|
* SessionStart -> ✻ idle "Claude Code — New session" (or an existing title on resume/compact)
|
|
12
|
+
* + inject the FULL RULES block — once per session instead of every prompt
|
|
13
|
+
* (~90% less directive overhead); resume/compact re-inject so a squeezed
|
|
14
|
+
* context re-learns the rules.
|
|
15
|
+
*
|
|
16
|
+
* Dedupe: when this file is the USER-LEVEL copy (~/.claude/hooks) and the project ships its own
|
|
17
|
+
* managed copy (<cwd>/.claude/hooks/terminal-title.js), this copy stands down entirely — otherwise
|
|
18
|
+
* both hooks inject directives naming two different title files (double tokens, double Writes).
|
|
12
19
|
*
|
|
13
20
|
* The title is set TWO ways on every event: `process.title` (= SetConsoleTitleW on Windows — the ONLY
|
|
14
21
|
* mechanism that flips the tab on UserPromptSubmit, where Claude Code drops `terminalSequence`) AND
|
|
@@ -23,6 +30,7 @@
|
|
|
23
30
|
*/
|
|
24
31
|
const fs = require('fs');
|
|
25
32
|
const path = require('path');
|
|
33
|
+
const os = require('os');
|
|
26
34
|
|
|
27
35
|
const ESC = '\x1b';
|
|
28
36
|
const BEL = '\x07';
|
|
@@ -61,6 +69,10 @@ async function handle(data) {
|
|
|
61
69
|
const event = data.hook_event_name || '';
|
|
62
70
|
const sid = data.session_id || '';
|
|
63
71
|
const cwd = data.cwd || process.cwd();
|
|
72
|
+
// User-level copy stands down when a project-level managed copy exists (see header).
|
|
73
|
+
if (shouldDefer(cwd, __dirname, __filename, path.join(os.homedir(), '.claude', 'hooks'))) {
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
64
76
|
// PROJECT-SCOPED title dir (cwd-relative) — the plugin installs per-project. (The live ~/.claude
|
|
65
77
|
// variant uses os.homedir() instead; that is the only difference between the two.)
|
|
66
78
|
const dir = path.join(cwd, '.claude', 'hooks', '.titles');
|
|
@@ -96,7 +108,14 @@ async function handle(data) {
|
|
|
96
108
|
if (event === 'SessionStart') {
|
|
97
109
|
// Fresh-session placeholder; on resume/compact an existing model-authored title is preferred.
|
|
98
110
|
const title = normalize(readTitle(file) || 'Claude Code - New session');
|
|
99
|
-
|
|
111
|
+
const out = setTitle(GLYPH.idle, title);
|
|
112
|
+
// Inject the FULL rulebook here — once per session — instead of on every prompt. All sources
|
|
113
|
+
// get it: startup/clear teach a fresh context, resume/compact re-teach a squeezed one.
|
|
114
|
+
const rules = buildBlocks(['RULES'], file, cwd, '');
|
|
115
|
+
if (rules) {
|
|
116
|
+
out.hookSpecificOutput = { hookEventName: 'SessionStart', additionalContext: rules };
|
|
117
|
+
}
|
|
118
|
+
emit(out);
|
|
100
119
|
return;
|
|
101
120
|
}
|
|
102
121
|
|
|
@@ -203,7 +222,15 @@ function inspectLastResponse(transcriptPath) {
|
|
|
203
222
|
if (!line) continue;
|
|
204
223
|
let obj;
|
|
205
224
|
try { obj = JSON.parse(line); } catch (_) { continue; }
|
|
206
|
-
if (!obj ||
|
|
225
|
+
if (!obj || !obj.message) continue;
|
|
226
|
+
// The current turn's response always sits AFTER the last real user prompt. Crossing one while
|
|
227
|
+
// scanning back for that response means it hasn't flushed to the JSONL yet (~200ms lag) — force a
|
|
228
|
+
// re-read (suspectRace) instead of falling through to the PRIOR turn's text, which may end in '?'
|
|
229
|
+
// and would wrongly paint the awaiting ◐ on what was really a statement turn.
|
|
230
|
+
if (obj.type === 'user' && isRealUserPrompt(obj)) {
|
|
231
|
+
return { ends: false, found: false, tail: '', suspectRace: true };
|
|
232
|
+
}
|
|
233
|
+
if (obj.type !== 'assistant') continue;
|
|
207
234
|
const c = obj.message.content;
|
|
208
235
|
let text = '';
|
|
209
236
|
if (typeof c === 'string') {
|
|
@@ -234,6 +261,15 @@ function inspectLastResponse(transcriptPath) {
|
|
|
234
261
|
return blank;
|
|
235
262
|
}
|
|
236
263
|
|
|
264
|
+
// A genuine human prompt (real text) vs a tool_result-carrier user message (content is only
|
|
265
|
+
// tool_result blocks). Marks the boundary of the current turn's response while walking the transcript.
|
|
266
|
+
function isRealUserPrompt(obj) {
|
|
267
|
+
const c = obj.message && obj.message.content;
|
|
268
|
+
if (typeof c === 'string') return c.trim().length > 0;
|
|
269
|
+
if (Array.isArray(c)) return c.some(b => b && b.type === 'text' && typeof b.text === 'string' && b.text.trim().length > 0);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
237
273
|
// Event-loop-friendly sleep for the Stop flush-race re-read beat (handle awaits this; no busy-wait).
|
|
238
274
|
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
|
|
239
275
|
|
|
@@ -264,28 +300,47 @@ function emit(obj) {
|
|
|
264
300
|
process.exit(0);
|
|
265
301
|
}
|
|
266
302
|
|
|
267
|
-
// Build the
|
|
268
|
-
//
|
|
303
|
+
// Build the per-prompt injection: the REMINDER one-liner, plus BASELINE while no title file
|
|
304
|
+
// exists yet (first turn), plus COMMAND on a /slash-command turn. The full rulebook (RULES) is
|
|
305
|
+
// injected by SessionStart, not here. Wording lives in the .md so it tunes without code edits.
|
|
269
306
|
function buildDirective(data, file, cwd) {
|
|
307
|
+
const prompt = typeof data.prompt === 'string' ? data.prompt : '';
|
|
308
|
+
const m = prompt.match(/^\s*\/([A-Za-z0-9][\w:.-]*)/);
|
|
309
|
+
const blocks = ['REMINDER'];
|
|
310
|
+
if (!fileExists(file)) blocks.push('BASELINE');
|
|
311
|
+
if (m) blocks.push('COMMAND');
|
|
312
|
+
return buildBlocks(blocks, file, cwd, m ? m[1] : '');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Extract the named blocks from terminal-title.directive.md, join them, substitute the tokens.
|
|
316
|
+
function buildBlocks(names, file, cwd, cmd) {
|
|
270
317
|
let tpl = '';
|
|
271
318
|
try {
|
|
272
319
|
tpl = fs.readFileSync(path.join(__dirname, 'terminal-title.directive.md'), 'utf8');
|
|
273
320
|
} catch (_) {
|
|
274
321
|
return '';
|
|
275
322
|
}
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const block = extractBlock(tpl, cmd ? 'COMMAND' : 'SHIFT');
|
|
280
|
-
if (!block) return '';
|
|
281
|
-
const pending = extractBlock(tpl, 'PENDING');
|
|
282
|
-
const combined = pending ? `${block}\n\n${pending}` : block;
|
|
283
|
-
return combined
|
|
323
|
+
const parts = names.map(n => extractBlock(tpl, n)).filter(Boolean);
|
|
324
|
+
if (!parts.length) return '';
|
|
325
|
+
return parts.join('\n')
|
|
284
326
|
.split('{{TITLE_FILE}}').join(file)
|
|
285
327
|
.split('{{ASK_FILE}}').join(file.replace(/\.txt$/, '.ask'))
|
|
286
328
|
.split('{{FOLDER}}').join(folderName(cwd))
|
|
287
329
|
.split('{{EMDASH}}').join(EMDASH)
|
|
288
|
-
.split('{{CMD}}').join(cmd);
|
|
330
|
+
.split('{{CMD}}').join(cmd || '');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Should THIS copy of the hook stand down? True only for the user-level copy (~/.claude/hooks)
|
|
334
|
+
// running inside a project that ships its own managed copy — the project copy wins, so a session
|
|
335
|
+
// gets ONE directive and ONE title file. Parameterized (no ambient __dirname/homedir) for tests.
|
|
336
|
+
function shouldDefer(cwd, selfDir, selfFile, homeHooksDir) {
|
|
337
|
+
try {
|
|
338
|
+
if (path.resolve(selfDir) !== path.resolve(homeHooksDir)) return false; // we ARE the project copy
|
|
339
|
+
const projectCopy = path.join(cwd, '.claude', 'hooks', 'terminal-title.js');
|
|
340
|
+
return fs.existsSync(projectCopy) && path.resolve(projectCopy) !== path.resolve(selfFile);
|
|
341
|
+
} catch (_) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
289
344
|
}
|
|
290
345
|
|
|
291
346
|
function extractBlock(tpl, name) {
|
|
@@ -297,4 +352,4 @@ function extractBlock(tpl, name) {
|
|
|
297
352
|
}
|
|
298
353
|
|
|
299
354
|
// Exported for tests (require()'d when require.main !== module). The hook itself never reads these.
|
|
300
|
-
module.exports = { inspectLastResponse, normalize, GLYPH };
|
|
355
|
+
module.exports = { inspectLastResponse, normalize, GLYPH, shouldDefer };
|
|
@@ -32,6 +32,10 @@ const SKIP = new Set([
|
|
|
32
32
|
// Folders we scan for files
|
|
33
33
|
const SCAN_FOLDERS = ['commands', 'agents', 'hooks', 'feedback'];
|
|
34
34
|
|
|
35
|
+
// Dev-only files that live in the maintainer repo but are never installed to
|
|
36
|
+
// user projects (mirror of bin/cli.js DEV_ONLY_FILES) — never document them.
|
|
37
|
+
const DEV_ONLY_FILES = new Set(['deploy-to-npmjs.md']);
|
|
38
|
+
|
|
35
39
|
// Structural keys that are not file-backed (always preserved, never generated)
|
|
36
40
|
const STRUCTURAL_KEYS = new Set([
|
|
37
41
|
'memory-md', 'root', 'claude-md', 'claude-dir'
|
|
@@ -263,6 +267,7 @@ function scanFiles() {
|
|
|
263
267
|
}).sort();
|
|
264
268
|
|
|
265
269
|
for (const file of files) {
|
|
270
|
+
if (DEV_ONLY_FILES.has(file)) continue;
|
|
266
271
|
const ext = path.extname(file);
|
|
267
272
|
const filePath = path.join(folderPath, file);
|
|
268
273
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
@@ -541,7 +546,12 @@ function generateFileContents(entries) {
|
|
|
541
546
|
// =============================================================================
|
|
542
547
|
|
|
543
548
|
const entries = scanFiles();
|
|
544
|
-
|
|
549
|
+
// The docs HTML may be CRLF on Windows, but every marker below is authored with
|
|
550
|
+
// `\n`. Normalize to LF for the string surgery, then restore the file's original
|
|
551
|
+
// EOL on write so we don't churn every line.
|
|
552
|
+
const rawHtml = fs.readFileSync(docsPath, 'utf8');
|
|
553
|
+
const eol = rawHtml.includes('\r\n') ? '\r\n' : '\n';
|
|
554
|
+
let html = rawHtml.replace(/\r\n/g, '\n');
|
|
545
555
|
|
|
546
556
|
// 1. Replace the file tree (between claude-dir folder row and settings.json closing div)
|
|
547
557
|
// We find the marker after the claude-dir folder and replace up to the settings div
|
|
@@ -656,7 +666,7 @@ if (fcEnd === -1) {
|
|
|
656
666
|
const newFileContents = generateFileContents(entries);
|
|
657
667
|
html = html.slice(0, fcInsertPoint) + newFileContents + '\n ' + html.slice(fcEnd);
|
|
658
668
|
|
|
659
|
-
fs.writeFileSync(docsPath, html);
|
|
669
|
+
fs.writeFileSync(docsPath, eol === '\r\n' ? html.replace(/\n/g, '\r\n') : html);
|
|
660
670
|
|
|
661
671
|
const fileCount = entries.filter(e => !e.isEmptyFolder).length;
|
|
662
672
|
console.log(`Synced ${fileCount} files to docs.`);
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.0.197
|
|
4
|
+
- perf(terminal-title): deliver the directive once per session; dedupe user/project hooks
|
|
5
|
+
|
|
6
|
+
## v1.0.196
|
|
7
|
+
- fix(terminal-title): stop at the turn boundary when grading the closing '?' (stale-awaiting race)
|
|
8
|
+
- fix(cli): require non-TTY stdout for inside-Claude block (env var alone false-positives in inherited terminals)
|
|
9
|
+
|
|
3
10
|
## v1.0.195
|
|
4
11
|
- fix(terminal-title): flip awaiting ◐ for a closing question with a trailing parenthetical aside
|
|
5
12
|
|
|
@@ -141,9 +148,3 @@
|
|
|
141
148
|
## v1.0.148
|
|
142
149
|
- fix: refine inside-Claude error wording
|
|
143
150
|
|
|
144
|
-
## v1.0.147
|
|
145
|
-
- fix: make inside-Claude error message more specific
|
|
146
|
-
|
|
147
|
-
## v1.0.146
|
|
148
|
-
- style: improve inside-Claude block message formatting
|
|
149
|
-
|
package/CLAUDE.md
CHANGED
|
@@ -42,17 +42,13 @@ See `.claude/feedback/` for corrections and guidance from the team.
|
|
|
42
42
|
## Discoveries
|
|
43
43
|
<!-- Claude: append project-specific learnings, gotchas, and context below. This section persists across /autoconfig runs. -->
|
|
44
44
|
|
|
45
|
-
### Publishing to npm — web-auth
|
|
45
|
+
### Publishing to npm — bypass-2FA token flow (web-auth publish is a DEAD END)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
(Supersedes the auto-generated "## Publishing" one-liner above.)
|
|
48
48
|
|
|
49
|
-
-
|
|
50
|
-
- **
|
|
51
|
-
|
|
52
|
-
cd C:\CODE\claude-code-autoconfig && npm login --auth-type=web && npm publish
|
|
53
|
-
```
|
|
54
|
-
→ browser opens → pick the Google passkey → close it → `+ claude-code-autoconfig@<version>`.
|
|
55
|
-
- **After** the success line, the agent pushes: `git push origin main --follow-tags`. Never double-bump if the version is already bumped. Full procedure: `.claude/commands/publish.md`.
|
|
49
|
+
- **Primary flow (agent can do it end-to-end):** `npm test` + `npm version patch` (commit + tag) → `npm whoami` to verify the Bypass-2FA granular token in `~/.npmrc` still authenticates → plain `npm publish` (works headless, no OTP) → after the `+ claude-code-autoconfig@<version>` line, `git push origin main --follow-tags`. Never double-bump if the version is already bumped.
|
|
50
|
+
- **If `npm whoami` returns E401**, the token is dead/expired (90-day cap). The human rotates it (npmjs.com → Access Tokens → Granular → "Bypass 2FA", read-write, scoped to this package only) and pastes it into `~/.npmrc` in an editor — never via a command. Stopgap without a token: `npm publish --otp=<recovery-code>` in the human's own terminal.
|
|
51
|
+
- **Do NOT use `npm login --auth-type=web && npm publish`:** login succeeds but publish still dies `EOTP` — the npm CLI can't route publish-time 2FA to the browser on this webauthn-only account (confirmed 2026-07-03, npm 10.9.2). Don't retry it in-agent, and don't ask for a typed TOTP (none exists). Full procedure: `.claude/commands/deploy-to-npmjs.md` (`/deploy-to-npmjs`).
|
|
56
52
|
|
|
57
53
|
## Debugging Methodology — Evidence Before Solutions
|
|
58
54
|
|
package/bin/cli.js
CHANGED
|
@@ -435,8 +435,12 @@ if (process.argv[2] === 'plugin') {
|
|
|
435
435
|
|
|
436
436
|
const forceMode = process.argv.includes('--force');
|
|
437
437
|
|
|
438
|
-
// Detect if running inside Claude Code session
|
|
439
|
-
|
|
438
|
+
// Detect if running inside Claude Code session.
|
|
439
|
+
// CLAUDECODE=1 alone is not reliable: env vars inherit into every descendant
|
|
440
|
+
// process (e.g. VS Code launched from a Claude session, and any terminal it
|
|
441
|
+
// spawns). A genuine in-agent run has piped stdio (isTTY falsy), while a human
|
|
442
|
+
// terminal has a real TTY — so require both signals before blocking.
|
|
443
|
+
const insideClaude = process.env.CLAUDECODE === '1' && !process.stdout.isTTY;
|
|
440
444
|
|
|
441
445
|
console.log('\x1b[36m%s\x1b[0m', '🚀 Claude Code Autoconfig');
|
|
442
446
|
console.log();
|
|
@@ -653,7 +657,7 @@ const hooksSrc = path.join(packageDir, '.claude', 'hooks');
|
|
|
653
657
|
const scriptsSrc = path.join(packageDir, '.claude', 'scripts');
|
|
654
658
|
|
|
655
659
|
// Files that exist in the dev repo but should never be installed to user projects
|
|
656
|
-
const DEV_ONLY_FILES = ['
|
|
660
|
+
const DEV_ONLY_FILES = ['deploy-to-npmjs.md'];
|
|
657
661
|
|
|
658
662
|
function copyDir(src, dest) {
|
|
659
663
|
fs.mkdirSync(dest, { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-autoconfig",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.197",
|
|
4
4
|
"description": "Intelligent, self-configuring setup for Claude Code. One command analyzes your project, configures Claude, and shows you what it did.",
|
|
5
5
|
"author": "ADAC 1001 <info@adac1001.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "node test/box-alignment.test.js && node test/cli-install.test.js && node test/update-system.test.js && node test/plugin-system.test.js && node test/terminal-title.test.js",
|
|
26
|
+
"pub": "npm whoami && npm publish",
|
|
26
27
|
"test:box": "node test/box-alignment.test.js",
|
|
27
28
|
"test:install": "node test/cli-install.test.js",
|
|
28
29
|
"test:update": "node test/update-system.test.js",
|
|
29
30
|
"postversion": "node scripts/generate-changelog.js --postversion"
|
|
30
31
|
},
|
|
31
32
|
"bin": {
|
|
32
|
-
"claude-code-autoconfig": "
|
|
33
|
+
"claude-code-autoconfig": "bin/cli.js"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"bin",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"!.claude/cca.config.json",
|
|
41
42
|
"!.claude/.autoconfig-version",
|
|
42
43
|
"!.claude/.autoconfig-plugins.json",
|
|
43
|
-
"!.claude/commands/
|
|
44
|
+
"!.claude/commands/deploy-to-npmjs.md",
|
|
44
45
|
"!.claude/plans",
|
|
45
46
|
"CLAUDE.md",
|
|
46
47
|
"CHANGELOG.md"
|