@yeison.restrepo.r/code-conductor 1.23.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.
Files changed (43) hide show
  1. package/LICENSE +185 -0
  2. package/README.md +314 -0
  3. package/bin/code-conductor.mjs +133 -0
  4. package/global/CLAUDE.md +93 -0
  5. package/global/commands/cc-checkpoint.md +50 -0
  6. package/global/commands/cc-compact.md +49 -0
  7. package/global/commands/cc-lang.md +32 -0
  8. package/global/commands/cc-stack.md +69 -0
  9. package/global/hooks/graphify-ast-refresh.py +81 -0
  10. package/global/hooks/verbosity-remind.sh +372 -0
  11. package/global/memory/personal.md +24 -0
  12. package/global/memory/verbosity.md +7 -0
  13. package/global/settings.json +24 -0
  14. package/lib/installer/config.mjs +51 -0
  15. package/lib/installer/deploy.mjs +76 -0
  16. package/lib/installer/env.mjs +35 -0
  17. package/lib/installer/settings.mjs +77 -0
  18. package/package.json +34 -0
  19. package/project-template/.claude/commands/cc-debug.md +42 -0
  20. package/project-template/.claude/commands/cc-docs.md +49 -0
  21. package/project-template/.claude/commands/cc-implement.md +164 -0
  22. package/project-template/.claude/commands/cc-init.md +84 -0
  23. package/project-template/.claude/commands/cc-plan.md +114 -0
  24. package/project-template/.claude/commands/cc-refactor.md +42 -0
  25. package/project-template/.claude/commands/cc-resume.md +142 -0
  26. package/project-template/.claude/commands/cc-review.md +77 -0
  27. package/project-template/.claude/commands/cc-spec.md +138 -0
  28. package/project-template/.claude/commands/cc-test.md +56 -0
  29. package/project-template/.claude/hooks/context-guard.ps1 +55 -0
  30. package/project-template/.claude/hooks/context-guard.sh +43 -0
  31. package/project-template/.claude/hooks/post-compact.ps1 +41 -0
  32. package/project-template/.claude/hooks/post-compact.sh +36 -0
  33. package/project-template/.claude/hooks/pre-tool-use.sh +81 -0
  34. package/project-template/.claude/hooks/verbosity-remind.sh +168 -0
  35. package/project-template/.claude/memory/context-threshold.txt +1 -0
  36. package/project-template/.claude/memory/project.md +48 -0
  37. package/project-template/.claude/settings.json +52 -0
  38. package/project-template/CLAUDE.md +104 -0
  39. package/skills/agent-delegation.md +44 -0
  40. package/skills/code-simplifier.md +124 -0
  41. package/skills/critical-review.md +75 -0
  42. package/skills/memory-first.md +50 -0
  43. package/skills/verbosity.md +30 -0
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env bash
2
+ # Design invariant: this hook MUST always exit 0.
3
+ # Claude Code's UserPromptSubmit mechanism blocks prompt submission if a
4
+ # registered hook exits non-zero — a non-zero exit would freeze the user's session.
5
+ # `trap 'exit 0' EXIT ERR` enforces this invariant unconditionally:
6
+ # - EXIT: ensures exit code 0 even if `exit N` is called with N > 0.
7
+ # - ERR: catches any unhandled command failure (set -e is NOT active here)
8
+ # and converts it to a clean 0 exit.
9
+ # Consequence: genuine failures cannot be signalled via exit code. They are
10
+ # reported exclusively via stderr (which Claude Code surfaces as a warning) and
11
+ # via the log file at $HOME/.claude/logs/verbosity-hook.log. If a future version
12
+ # of Claude Code relaxes the exit-code constraint, remove this trap and replace
13
+ # with explicit `exit 0` calls at each return point.
14
+ #
15
+ # Trap scoping note: the trap applies to the entire script process. It is
16
+ # intentionally total — no ERR or EXIT path can bubble a non-zero code out.
17
+ # This is correct for production; it is a deliberate trade-off against debuggability.
18
+ #
19
+ # Debug bypass: set CC_VERBOSITY_DEBUG=1 to disable the trap and expose raw exit
20
+ # codes and set -e behaviour. NEVER use CC_VERBOSITY_DEBUG in production — a
21
+ # non-zero exit from the hook will block all user prompt submissions:
22
+ # CC_VERBOSITY_DEBUG=1 bash -x global/hooks/verbosity-remind.sh
23
+ # The CC_VERBOSITY_SKIP check below still fires before the trap is conditionally armed.
24
+ if [ "${CC_VERBOSITY_DEBUG:-0}" = "1" ]; then
25
+ set -euo pipefail
26
+ # trap not armed — raw exit codes propagate
27
+ else
28
+ trap 'exit 0' EXIT ERR
29
+ fi
30
+
31
+ # CI/CD bypass — exit before any I/O
32
+ # Precedence rule for CC_VERBOSITY_SKIP:
33
+ # The hook reads this variable exclusively from the process environment.
34
+ # There is no file-based config for this flag. Precedence (highest → lowest):
35
+ # 1. Variable set inline at invocation: CC_VERBOSITY_SKIP=1 bash hook.sh
36
+ # 2. Variable exported in the calling shell's environment (.bashrc, .zshrc,
37
+ # CI runner environment block, Docker --env).
38
+ # 3. Default: 0 (feature active).
39
+ # System-level environment variables (set before the shell that launched Claude
40
+ # Code) take precedence over any user-level shell profile export, because the
41
+ # shell profile is sourced AFTER the system environment is inherited.
42
+ # There is no project-local override mechanism; to disable per-project, set
43
+ # CC_VERBOSITY_SKIP=1 in the shell that launches Claude Code for that project.
44
+ #
45
+ # Windows-specific precedence (Git Bash / WSL / PowerShell):
46
+ # - Git Bash: inherits system env from Windows, then sources ~/.bashrc. A
47
+ # system-level CC_VERBOSITY_SKIP set via "setx" or group policy takes
48
+ # precedence over ~/.bashrc exports. Set with: setx CC_VERBOSITY_SKIP 1
49
+ # - WSL: inherits Windows env vars (WSLENV-mapped), then sources ~/.bashrc.
50
+ # If WSLENV includes CC_VERBOSITY_SKIP, the Windows value propagates into
51
+ # WSL and overrides any ~/.bashrc export. Check with: echo $CC_VERBOSITY_SKIP
52
+ # - PowerShell: hook invoked as bash subprocess. The PS session's environment
53
+ # ($env:CC_VERBOSITY_SKIP) propagates to child processes. Set with:
54
+ # $env:CC_VERBOSITY_SKIP = "1" (session-scope)
55
+ # [System.Environment]::SetEnvironmentVariable('CC_VERBOSITY_SKIP','1','User') (user-scope)
56
+ # On all platforms: prefer user-scope env vars over system-scope to avoid
57
+ # affecting all users on a shared machine.
58
+ #
59
+ # Quick-reference table for CC_VERBOSITY_SKIP across environments:
60
+ # ┌──────────────────┬───────────────────────────────────────────────────────┐
61
+ # │ Environment │ How to set (user-scope, session-persistent) │
62
+ # ├──────────────────┼───────────────────────────────────────────────────────┤
63
+ # │ bash/zsh │ echo 'export CC_VERBOSITY_SKIP=1' >> ~/.bashrc/.zshrc │
64
+ # │ Git Bash (Win) │ echo 'export CC_VERBOSITY_SKIP=1' >> ~/.bashrc │
65
+ # │ PowerShell │ Add to $PROFILE: $env:CC_VERBOSITY_SKIP = "1" │
66
+ # │ WSL │ echo 'export CC_VERBOSITY_SKIP=1' >> ~/.bashrc │
67
+ # │ CI (GitHub) │ Add to env: block in workflow YAML │
68
+ # │ CI (GitLab) │ Add to variables: block in .gitlab-ci.yml │
69
+ # │ Docker │ Add -e CC_VERBOSITY_SKIP=1 to docker run │
70
+ # │ Inline one-shot │ CC_VERBOSITY_SKIP=1 bash hook.sh │
71
+ # └──────────────────┴───────────────────────────────────────────────────────┘
72
+ # Accepted truthy values: 1, true, yes, on (any case). All other values: feature active.
73
+ case "${CC_VERBOSITY_SKIP:-0}" in
74
+ 1|true|yes|on|TRUE|YES|ON|True|Yes|On) exit 0 ;;
75
+ esac
76
+
77
+ # Named traversal cap — defined once here to avoid a magic number repeated in
78
+ # Stage 1 and Stage 2 loop bodies. Increase only with justification: each unit
79
+ # adds one stat() call per prompt invocation; 40 covers paths up to 40 components
80
+ # deep which is far beyond any realistic project directory structure.
81
+ readonly _VERBOSITY_TRAVERSAL_CAP=40
82
+
83
+ # $HOME null guard — no memory path or log path is resolvable without it
84
+ if [ -z "${HOME:-}" ]; then
85
+ printf '\n[VERBOSITY:MIN] One sentence. [CHANGES] file list only. No prose.\n'
86
+ exit 0
87
+ fi
88
+
89
+ # $PWD null guard
90
+ _start="${PWD:-}"
91
+ _skip_traversal=0
92
+ [ -z "$_start" ] && _skip_traversal=1
93
+
94
+ # Log setup — done once, used throughout.
95
+ # Graceful degradation for restricted/read-only environments:
96
+ # - If $_logdir exists but is not a directory: log silently disabled (_log_ok=0).
97
+ # - If mkdir -p fails (read-only FS, permission denied): _log_ok=0 — hook continues
98
+ # without logging. The hook NEVER blocks or exits non-zero due to log failures.
99
+ # - If $_logdir is writable but $_logfile is occupied by a directory: _log_ok=0.
100
+ # - If the log file itself becomes read-only after creation (e.g., chmod 444 by
101
+ # another process): the >> append in _write_log silently fails (2>/dev/null).
102
+ # In all restricted cases, the hook's functional output (the [VERBOSITY:…] line
103
+ # emitted to stdout) is unaffected — only the audit log is suppressed.
104
+ # Unified machine-readable log format for this hook and the installer:
105
+ # YYYY-MM-DD HH:MM:SS [<scope>] <LEVEL> <message>
106
+ # scope = global | project | install
107
+ # LEVEL = INFO | WARN | ERROR | PASS | FAIL
108
+ # Example: 2026-06-12 09:31:05 [global] INFO level=MIN source=~/.claude/memory/verbosity.md
109
+ # Parse with: awk '{print $1, $2, $3, $4, substr($0, index($0,$5))}'
110
+ # Master Agent: tail -F "$HOME/.claude/logs/verbosity-hook.log" | grep '\[install\]'
111
+ # The installer writes to the same log file with scope=install so all events
112
+ # are co-located for automated success tracking.
113
+ _SCOPE="global"
114
+ _logdir="$HOME/.claude/logs"
115
+ _logfile="$_logdir/verbosity-hook.log"
116
+ _log_ok=0
117
+ if [ -e "$_logdir" ] && [ ! -d "$_logdir" ]; then
118
+ echo "[verbosity-remind] log dir blocked by non-directory: $_logdir" >&2
119
+ elif mkdir -p "$_logdir" 2>/dev/null && [ -w "$_logdir" ]; then
120
+ if [ -e "$_logfile" ] && [ ! -f "$_logfile" ]; then
121
+ echo "[verbosity-remind] log file path blocked by directory: $_logfile" >&2
122
+ else
123
+ _log_ok=1
124
+ fi
125
+ fi
126
+
127
+ _write_log() {
128
+ (( _log_ok )) || return 0
129
+ _logsize=0
130
+ [ -f "$_logfile" ] && _logsize=$(wc -c < "$_logfile" 2>/dev/null || echo 0)
131
+ if [ "${_logsize:-0}" -gt 1048576 ]; then
132
+ # Log rotation: use `true > "$_logfile"` (POSIX-portable, no coreutils needed).
133
+ # Works on Linux, macOS, Alpine/musl, Busybox, and Solaris. Do NOT use
134
+ # `truncate -s 0` (GNU-only, absent on macOS/BSDs) as the primary command.
135
+ # If truncation fails (read-only FS or permission denied), continue silently.
136
+ true > "$_logfile" 2>/dev/null && \
137
+ printf '%s [%s] log rotated at 1 MB ceiling\n' \
138
+ "$(date '+%Y-%m-%d %H:%M:%S')" "$_SCOPE" >> "$_logfile" 2>/dev/null
139
+ # Fall through — write the current message to the freshly truncated file.
140
+ fi
141
+ # Concurrent writes: POSIX O_APPEND (>>) serialises each write() atomically for
142
+ # payloads under PIPE_BUF (~512 B). A single log line from printf is well under
143
+ # that limit, so byte-level interleaving between simultaneous sessions cannot occur.
144
+ # The wc -c size check above is a non-atomic TOCTOU — two sessions may both read
145
+ # size < 1 MB and both proceed, pushing the file slightly over the ceiling. This is
146
+ # a benign race; the next session will catch the overage on its own size check.
147
+ printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$_SCOPE" "$1" >> "$_logfile" 2>/dev/null
148
+ }
149
+
150
+ # ── Stage 1: upward project-hook detection (global hook only) ────────────────
151
+ if [ "$_skip_traversal" = "0" ]; then
152
+ _dir="$_start"; _prev=""; _iters=0; _cap=$_VERBOSITY_TRAVERSAL_CAP
153
+ while [ "$_dir" != "$_prev" ] && [ "$_dir" != "${HOME:-}" ] && (( _iters < _cap )); do
154
+ _h="$_dir/.claude/hooks/verbosity-remind.sh"
155
+ [ -f "$_h" ] && [ -r "$_h" ] && exit 0
156
+ _prev="$_dir"
157
+ _dir="${_dir%/*}"
158
+ [ -z "$_dir" ] && _dir="/"
159
+ (( _iters++ )) || true
160
+ done
161
+ fi
162
+
163
+ # ── Stage 2: cascading memory resolution ─────────────────────────────────────
164
+ _mem_file=""
165
+ if [ "$_skip_traversal" = "0" ]; then
166
+ # ── Traversal: symlinks and circular structures ───────────────────────────
167
+ # The upward traversal uses ONLY string operations on the path — it never
168
+ # calls stat(2), readdir(2), or follows filesystem symlinks during directory
169
+ # navigation. Specifically:
170
+ #
171
+ # _dir="${_dir%/*}" — POSIX parameter expansion; pure string truncation.
172
+ # No syscall. Cannot follow a symlink loop.
173
+ #
174
+ # This means if $PWD itself is a symlink (e.g., /project → /data/project),
175
+ # the traversal walks the LOGICAL path (the symlink chain) not the physical
176
+ # path. It will not revisit directories because each iteration strictly
177
+ # shortens the string (or hits "/" and exits via the $_dir != $_prev guard).
178
+ #
179
+ # Circular structures that CAN arise and their handling:
180
+ #
181
+ # Directory symlink loop on the PHYSICAL filesystem (e.g., a/.claude → b,
182
+ # b/.claude → a): SAFE. The traversal walks the logical string path, not
183
+ # physical inodes. It will never re-enter a directory via a symlink because
184
+ # it only shortens $_dir; it never appends components or follows links.
185
+ #
186
+ # Circular mount point (bind-mount of / inside itself): SAFE. Same reason —
187
+ # string truncation cannot follow a mount.
188
+ #
189
+ # symlink to verbosity.md (the FILE, not the directory): The file itself
190
+ # is a symlink. `[ -f "$_f" ]` returns true for a symlink to a regular file.
191
+ # `[ -L "$_f" ]` then detects it, and `readlink -f` resolves the target.
192
+ # If the resolved target is in /etc/, /proc/, /sys/, or /dev/, the file is
193
+ # skipped. Any other resolved target is treated as a normal verbosity.md.
194
+ # A symlink cycle in verbosity.md itself (a → b → a) causes `readlink -f`
195
+ # to fail (ELOOP); the fallback `readlink "$_f"` returns the immediate link
196
+ # target (one level), and the security check runs on that. If both fail,
197
+ # _resolved="" and the security case-match passes (empty string does not
198
+ # match /etc/*, /proc/*, etc.) — the file is used as-is. This is the safe
199
+ # side: a symlink cycle that cannot be resolved is unlikely to be a valid
200
+ # verbosity.md and will produce no VERBOSITY: tokens (the read loop over a
201
+ # cyclic symlink fails immediately).
202
+ #
203
+ # $_VERBOSITY_TRAVERSAL_CAP (40) is a DEPTH guard, not a cycle guard.
204
+ # It caps degenerate paths with > 40 path components (e.g., deeply nested
205
+ # monorepos, Docker overlay mounts with long chain paths). It is not needed
206
+ # for cycle prevention — that is already guaranteed by string truncation.
207
+ _dir="$_start"; _prev=""; _iters=0; _cap=$_VERBOSITY_TRAVERSAL_CAP
208
+ while [ "$_dir" != "$_prev" ] && (( _iters < _cap )); do
209
+ _f="$_dir/.claude/memory/verbosity.md"
210
+ if [ -f "$_f" ] && [ -r "$_f" ]; then
211
+ # Symlink safety: reject verbosity.md files that resolve to sensitive system paths.
212
+ # A symlink crafted to point at /etc/passwd, /proc/self/environ, etc. would
213
+ # allow arbitrary file reads via the VERBOSITY: extraction loop below.
214
+ if [ -L "$_f" ]; then
215
+ _resolved=$(readlink -f "$_f" 2>/dev/null || readlink "$_f" 2>/dev/null || echo "")
216
+ case "$_resolved" in
217
+ /etc/*|/proc/*|/sys/*|/dev/*)
218
+ _write_log "WARN: verbosity.md at $_f resolves to '$_resolved' — skipping"
219
+ _prev="$_dir"; _dir="${_dir%/*}"; [ -z "$_dir" ] && _dir="/"
220
+ (( _iters++ )) || true; continue ;;
221
+ esac
222
+ fi
223
+ _mem_file="$_f"
224
+ break
225
+ fi
226
+ _prev="$_dir"
227
+ _dir="${_dir%/*}"
228
+ [ -z "$_dir" ] && _dir="/"
229
+ (( _iters++ )) || true
230
+ done
231
+ (( _iters >= _cap )) && [ -z "$_mem_file" ] && \
232
+ _write_log "traversal cap (${_cap}) reached; no verbosity.md found"
233
+ fi
234
+
235
+ [ -z "$_mem_file" ] && _mem_file="$HOME/.claude/memory/verbosity.md"
236
+ # Precedence rule: the first verbosity.md found during upward traversal is used exclusively.
237
+ # If a project-specific file is found but contains no VERBOSITY: token, LEVEL remains ""
238
+ # after the extraction loop. Stage 3 normalisation then maps "" to MIN (the safe default).
239
+ # The global ($HOME) file is NOT consulted as a secondary fallback — it is only the
240
+ # initial value used when no project-specific file exists at all. This means a project
241
+ # can intentionally "reset" to MIN by providing a verbosity.md with no VERBOSITY: token.
242
+ #
243
+ # ── verbosity.md marker edge cases ───────────────────────────────────────────
244
+ # CASE 1 — No VERBOSITY: token at all (file exists but marker absent):
245
+ # _in_fence and LEVEL both remain at their initial values (0 and "").
246
+ # Stage 3 maps "" → MIN. The hook emits [VERBOSITY:MIN] and logs a WARN:
247
+ # "WARN: no VERBOSITY: token found in <path>; defaulting to MIN"
248
+ # Implication: an empty verbosity.md or a file consisting only of comments
249
+ # is treated as "intentional MIN reset" and is not an error.
250
+ #
251
+ # CASE 2 — Multiple VERBOSITY: tokens in the same file:
252
+ # The extraction loop hits `break` on the FIRST match outside a fence or
253
+ # front-matter block. All subsequent VERBOSITY: lines are ignored.
254
+ # Example file:
255
+ # VERBOSITY: MIN ← THIS line wins (first non-fence match)
256
+ # VERBOSITY: VERBOSE ← ignored
257
+ # Log entry: "INFO: VERBOSITY:MIN (first match at line N of <path>)"
258
+ # Future maintainers MUST NOT add a second VERBOSITY: token expecting it
259
+ # to override the first — only the first token outside a fence is authoritative.
260
+ #
261
+ # CASE 3 — VERBOSITY: token inside a fenced code block (``` or ~~~):
262
+ # The _in_fence flag is toggled by fence-open/close lines. A VERBOSITY: line
263
+ # inside an open fence is skipped via `(( _in_fence )) && continue`. It does
264
+ # NOT set LEVEL. This is intentional — code examples in verbosity.md must not
265
+ # accidentally activate a level change.
266
+ #
267
+ # CASE 4 — VERBOSITY: token inside a YAML front-matter block (--- ... ---):
268
+ # _in_fm=1 is set on encountering a leading `---` on line 1. All lines inside
269
+ # the front-matter block are skipped. The VERBOSITY: token in front-matter is
270
+ # NOT extracted. This prevents Obsidian/Jekyll metadata from leaking into level
271
+ # detection. The hook looks for VERBOSITY: in the document body only.
272
+ #
273
+ # CASE 5 — VERBOSITY: with an unrecognised value (e.g., "VERBOSITY: QUIET"):
274
+ # LEVEL is set to the raw value "QUIET". Stage 3 normalisation maps any value
275
+ # that is not MIN, INFO, or VERBOSE to MIN, and logs:
276
+ # "WARN: unknown VERBOSITY level 'QUIET'; normalised to MIN"
277
+ # The hook still emits [VERBOSITY:MIN] — it does not error or block.
278
+ #
279
+ # CASE 6 — verbosity.md is completely absent (no project file, no global file):
280
+ # _mem_file points to $HOME/.claude/memory/verbosity.md after the traversal
281
+ # finds nothing. If that path also does not exist, the `[ -f ] && [ -r ]` guard
282
+ # above skips the while loop entirely. LEVEL remains "". Stage 3 maps → MIN.
283
+ # (Do NOT rely on "no iterations" here — `done < missing_file` triggers ERR.)
284
+
285
+ # ── Extraction loop (bash 3.2 compatible) ────────────────────────────────────
286
+ # Guard: `done < "$_mem_file"` triggers the ERR trap when the file doesn't exist
287
+ # (because the `<` redirection fails), causing silent exit 0 with no output.
288
+ # The guard converts a missing/unreadable file into LEVEL="" → sanity guard → MIN,
289
+ # which is the documented CASE 6 behaviour. Do NOT rely on "no iterations" here.
290
+ _in_fence=0; _in_fm=0; LEVEL=""; _lineno=0
291
+ if [ -f "$_mem_file" ] && [ -r "$_mem_file" ]; then
292
+ while IFS= read -r _line || [ -n "$_line" ]; do
293
+ _lineno=$(( _lineno + 1 ))
294
+ [ "$_lineno" = "1" ] && _line="${_line#$'\xef\xbb\xbf'}" # BOM strip on line 1
295
+ _line="${_line%$'\r'}" # CRLF compat, all lines
296
+ if [ "$_lineno" = "1" ] && [ "$_line" = "---" ]; then
297
+ _in_fm=1; continue
298
+ fi
299
+ if [ "$_in_fm" = "1" ]; then
300
+ case "$_line" in
301
+ ---|\.\.\.) _in_fm=0 ;;
302
+ esac
303
+ continue
304
+ fi
305
+ case "$_line" in
306
+ '```'*|'~~~'*) (( _in_fence = 1 - _in_fence )) || true ;;
307
+ VERBOSITY:*)
308
+ (( _in_fence )) && continue
309
+ LEVEL="${_line#VERBOSITY:}"
310
+ LEVEL="${LEVEL#"${LEVEL%%[! ]*}"}"
311
+ LEVEL="${LEVEL%% #*}" # strip " # comment" (space+hash); bare # not treated as delimiter
312
+ LEVEL="${LEVEL%%[[:space:]]*}" # strip remaining trailing whitespace
313
+ break
314
+ ;;
315
+ esac
316
+ done < "$_mem_file"
317
+ fi
318
+
319
+ # Recovery pass — unclosed fence
320
+ if [ -z "$LEVEL" ] && [ "$_in_fence" = "1" ]; then
321
+ mkdir -p "$HOME/.claude/logs" 2>/dev/null
322
+ # .verbosity-fence-warned lifecycle: 60-minute TTL, per-machine not per-session.
323
+ # A marker from a prior session suppresses this warning if < 60 min old — intentional
324
+ # throttle to avoid noise on repeated openings of the same malformed file.
325
+ # The marker ages out automatically; tests must remove it before asserting on stderr.
326
+ find "$HOME/.claude/logs/.verbosity-fence-warned" -mmin -60 2>/dev/null | grep -q . || {
327
+ echo "[verbosity-remind] unclosed fence in $_mem_file; using recovery pass" >&2
328
+ touch "$HOME/.claude/logs/.verbosity-fence-warned" 2>/dev/null
329
+ }
330
+ while IFS= read -r _line || [ -n "$_line" ]; do
331
+ _line="${_line%$'\r'}"
332
+ case "$_line" in
333
+ VERBOSITY:*)
334
+ LEVEL="${_line#VERBOSITY:}"
335
+ LEVEL="${LEVEL#"${LEVEL%%[! ]*}"}"
336
+ LEVEL="${LEVEL%% #*}" # strip " # comment" (space+hash only)
337
+ LEVEL="${LEVEL%%[[:space:]]*}" # strip remaining trailing whitespace
338
+ break
339
+ ;;
340
+ esac
341
+ done < "$_mem_file"
342
+ fi
343
+
344
+ # ── Normalization (bash 3.2 compatible, no ${LEVEL^^}) ───────────────────────
345
+ # Empty / whitespace-only verbosity.md recovery:
346
+ # If verbosity.md is empty or contains only whitespace (no VERBOSITY: token),
347
+ # the extraction loop exits with LEVEL="". The recovery pass is skipped
348
+ # (it runs only for unclosed fences). The normalization case below passes
349
+ # LEVEL="" through unchanged. The Stage 3 sanity guard then catches
350
+ # LEVEL="" via the wildcard branch and sets LEVEL="MIN" — same result as
351
+ # an absent file. No additional guard needed; this path is already safe.
352
+ case "$LEVEL" in
353
+ [Mm][Ii][Nn]) LEVEL="MIN" ;;
354
+ [Ii][Nn][Ff][Oo]) LEVEL="INFO" ;;
355
+ [Vv][Ee][Rr][Bb][Oo][Ss][Ee]) LEVEL="VERBOSE" ;;
356
+ esac
357
+
358
+ # ── Stage 3: sanity guard + emit ─────────────────────────────────────────────
359
+ case "$LEVEL" in
360
+ MIN|INFO|VERBOSE) ;;
361
+ *) LEVEL="MIN" ;;
362
+ esac
363
+
364
+ # Output is injected into Claude's prompt context via the UserPromptSubmit hook.
365
+ # Use only printable ASCII in the format strings. Never add ANSI escape codes,
366
+ # terminal control sequences (\033[…), or non-ASCII bytes — the hook runs in a
367
+ # non-TTY subprocess; control characters appear as literal bytes in Claude's input.
368
+ case "$LEVEL" in
369
+ MIN) printf '\n[VERBOSITY:MIN] One sentence. [CHANGES] file list only. No prose.\n' ;;
370
+ INFO) printf '\n[VERBOSITY:INFO] Bullet list max 5. [CHANGES]+[REASON] tags.\n' ;;
371
+ VERBOSE) printf '\n[VERBOSITY:VERBOSE] Full explanation. All tags active.\n' ;;
372
+ esac
@@ -0,0 +1,24 @@
1
+ # Personal Preferences
2
+
3
+ > Local only. Never commit this file. Updated automatically by /checkpoint.
4
+
5
+ ## Language
6
+ response_language: en
7
+
8
+ ## Communication Style
9
+ - Strict tone compliance: no em-dashes in any output (docs, plans, specs, responses).
10
+ - Provides precise, numbered review feedback — expects each point addressed individually before proceeding.
11
+ - Reviews intermediate artifacts (plans, specs) thoroughly before approving; multiple revision rounds are normal.
12
+
13
+ ## Editor & Tools
14
+ <!-- e.g.: VSCode, prefer TypeScript, always use pnpm -->
15
+
16
+ ## Workflow Preferences
17
+ - Works through backlog items sequentially in natural order (PILLAR 1 first, then by item number).
18
+ - Prefers Subagent-Driven execution over inline execution for implementation plans.
19
+ - Expects spec → plan → implement cycle with explicit approval gates at each phase.
20
+ - Uses `/cc-checkpoint` before finishing branches.
21
+
22
+ ## Notes
23
+ - Prefers version bump + CHANGELOG + README as a separate chore commit after the feature commit, not bundled together.
24
+ - Confirms between tasks during `/cc-implement` execution (does not use "proceed without confirmation" mode).
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: Verbosity Level
3
+ description: Active verbosity level — MIN, INFO, or VERBOSE. Controls Claude response length globally.
4
+ type: user
5
+ ---
6
+
7
+ VERBOSITY: MIN
@@ -0,0 +1,24 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep:*)",
5
+ "Bash(find:*)",
6
+ "Bash(ls:*)",
7
+ "Bash(cat:*)"
8
+ ],
9
+ "deny": []
10
+ },
11
+ "hooks": {
12
+ "UserPromptSubmit": [
13
+ {
14
+ "matcher": "",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "python ~/.claude/hooks/graphify-ast-refresh.py"
19
+ }
20
+ ]
21
+ }
22
+ ]
23
+ }
24
+ }
@@ -0,0 +1,51 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+
4
+ const LEVELS = ['MIN', 'INFO', 'VERBOSE'];
5
+
6
+ export function normalizeVerbosity(value) {
7
+ if (typeof value !== 'string') return null;
8
+ const m = /(?:VERBOSITY:\s*)?\b(MIN|INFO|VERBOSE)\b/i.exec(value.trim());
9
+ return m ? m[1].toUpperCase() : null;
10
+ }
11
+
12
+ function setLevelLine(content, level) {
13
+ if (/VERBOSITY:\s*\S+/i.test(content)) return content.replace(/VERBOSITY:\s*\S+/i, `VERBOSITY: ${level}`);
14
+ return `${content.replace(/\s*$/, '')}\n\nVERBOSITY: ${level}\n`;
15
+ }
16
+
17
+ export function writeVerbosity(home, assetRoot, requested, flagGiven) {
18
+ const file = join(home, '.claude', 'memory', 'verbosity.md');
19
+ mkdirSync(dirname(file), { recursive: true });
20
+ if (!existsSync(file)) copyFileSync(join(assetRoot, 'global', 'memory', 'verbosity.md'), file);
21
+ const content = readFileSync(file, 'utf8');
22
+
23
+ if (flagGiven) {
24
+ const valid = normalizeVerbosity(typeof requested === 'string' ? requested : '');
25
+ const level = valid || 'MIN';
26
+ writeFileSync(file, setLevelLine(content, level), 'utf8');
27
+ return { level, warn: valid ? null : `code-conductor: invalid --verbosity '${requested}', using MIN` };
28
+ }
29
+ const current = normalizeVerbosity(content);
30
+ if (current) return { level: current, warn: null };
31
+ writeFileSync(file, setLevelLine(content, 'MIN'), 'utf8');
32
+ return { level: 'MIN', warn: 'code-conductor: verbosity.md unreadable, reset to MIN' };
33
+ }
34
+
35
+ export function seedMemoryFile(home, relName, assetRoot) {
36
+ const dst = join(home, '.claude', 'memory', relName);
37
+ if (existsSync(dst)) return false;
38
+ mkdirSync(dirname(dst), { recursive: true });
39
+ copyFileSync(join(assetRoot, 'global', 'memory', relName), dst);
40
+ return true;
41
+ }
42
+
43
+ // conductor-version.md is a plain data file, NOT markdown prose: exactly one bare
44
+ // semver line plus a trailing newline (e.g. "1.22.0\n"). No frontmatter, no heading.
45
+ // This matches the legacy install.sh contract, which reads it with `cat` and string-
46
+ // compares against the bare VERSION file, so any decoration would break the compare.
47
+ export function writeVersionFile(home, version) {
48
+ const file = join(home, '.claude', 'memory', 'conductor-version.md');
49
+ mkdirSync(dirname(file), { recursive: true });
50
+ writeFileSync(file, `${version}\n`, 'utf8');
51
+ }
@@ -0,0 +1,76 @@
1
+ import { cpSync, mkdirSync, chmodSync, existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, relative, sep } from 'node:path';
3
+
4
+ // Verified at plan time: `find global skills project-template -type l` returns
5
+ // nothing — the bundled asset tree contains ZERO symlinks. `dereference: false`
6
+ // therefore never has a link to copy-as-link, so there is no Windows symlink-EPERM
7
+ // path here; the option is set only to guarantee that a symlink accidentally added
8
+ // later is copied as a link (never followed into user content), not resolved.
9
+ const CP_OPTS = { recursive: true, force: true, dereference: false };
10
+
11
+ // Pure guard: throws ONLY, emits nothing. It does not write a stderr diagnostic
12
+ // before throwing — run() surfaces the error's message exactly once
13
+ // (`code-conductor: missing bundled asset dir: <path>`), so warning here too would
14
+ // double-log. Keeping it side-effect-free also keeps it trivially unit-testable.
15
+ export function assertAssets(assetRoot, dirs) {
16
+ for (const d of dirs) {
17
+ const p = join(assetRoot, d);
18
+ if (!existsSync(p)) {
19
+ const err = new Error(`missing bundled asset dir: ${p}`);
20
+ err.code = 'MISSING_ASSET';
21
+ throw err;
22
+ }
23
+ }
24
+ }
25
+
26
+ // User data under global/memory (personal.md, verbosity.md) is preserved, so it
27
+ // is excluded from the forced overwrite and seeded write-if-absent by config.mjs.
28
+ // The filter matches on the path RELATIVE to the copy source, so a parent
29
+ // directory named e.g. "/memory/checkout/..." can never exclude every file.
30
+ function skipMemory(sourceRoot) {
31
+ return (src) => {
32
+ const rel = relative(sourceRoot, src);
33
+ return rel !== 'memory' && !rel.startsWith(`memory${sep}`);
34
+ };
35
+ }
36
+
37
+ export function deployGlobal(assetRoot, home) {
38
+ const target = join(home, '.claude');
39
+ const globalDir = join(assetRoot, 'global');
40
+ mkdirSync(target, { recursive: true });
41
+ cpSync(globalDir, target, { ...CP_OPTS, filter: skipMemory(globalDir) });
42
+ cpSync(join(assetRoot, 'skills'), join(target, 'skills'), CP_OPTS);
43
+ // skipMemory excludes global/memory from the copy, so cpSync never creates
44
+ // <target>/memory. Create it explicitly here so the write-if-absent seeding in
45
+ // config.mjs (and the version/verbosity writes) always has its parent dir.
46
+ mkdirSync(join(target, 'memory'), { recursive: true });
47
+ return target;
48
+ }
49
+
50
+ export function deployProject(assetRoot, cwd) {
51
+ const target = join(cwd, '.claude');
52
+ // If something already occupies ./.claude and it is NOT a directory, this is a
53
+ // precondition conflict (not a partial write): fail fast with a tagged error so
54
+ // run() can report exit 1 with a clear message instead of a confusing cpSync abort.
55
+ if (existsSync(target) && !statSync(target).isDirectory()) {
56
+ const err = new Error(`cannot scaffold project: ${target} exists and is not a directory`);
57
+ err.code = 'PROJECT_TARGET_NOT_DIR';
58
+ throw err;
59
+ }
60
+ mkdirSync(target, { recursive: true });
61
+ cpSync(join(assetRoot, 'project-template'), target, CP_OPTS);
62
+ return target;
63
+ }
64
+
65
+ // Deliberately SILENT on a suppressed chmod error. On Windows chmod is a genuine
66
+ // no-op (POSIX perm bits do not apply) and the hook still runs via `bash <path>`,
67
+ // so a warning would be pure noise. The failure is always non-fatal; the deployed
68
+ // scripts remain functional. No diagnostic is emitted.
69
+ export function chmodHooks(claudeDir) {
70
+ const hooksDir = join(claudeDir, 'hooks');
71
+ if (!existsSync(hooksDir)) return;
72
+ for (const name of readdirSync(hooksDir)) {
73
+ if (!name.endsWith('.sh')) continue;
74
+ try { chmodSync(join(hooksDir, name), 0o755); } catch { /* intentionally quiet — see above */ }
75
+ }
76
+ }
@@ -0,0 +1,35 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ // This module lives at <root>/lib/installer/env.mjs, so the package root
5
+ // (where global/ skills/ project-template/ sit) is two levels up. Deriving it
6
+ // from import.meta.url makes it correct from both the npx cache and the -g prefix.
7
+ export function resolveAssetRoot() {
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ return resolve(here, '..', '..');
10
+ }
11
+
12
+ // Platform-independent absolute-path test so a Windows path (drive or UNC) is
13
+ // recognized as absolute even when the code runs on a POSIX test host, and vice
14
+ // versa. Covers: POSIX root `/…`, Windows drive `C:\…` / `C:/…`, UNC `\\…` / `//…`.
15
+ export function isAbsolutePath(p) {
16
+ return /^([/\\]|[A-Za-z]:[/\\])/.test(p);
17
+ }
18
+
19
+ export function resolveHome(env = process.env) {
20
+ const home = env.HOME || env.USERPROFILE;
21
+ if (!home || !home.trim()) return null;
22
+ // Reject a relative HOME/USERPROFILE: joining assets onto it would write into an
23
+ // unpredictable location (e.g. under CWD). Treat as unresolvable → exit 1 upstream.
24
+ if (!isAbsolutePath(home.trim())) return null;
25
+ return home;
26
+ }
27
+
28
+ // Runtime floor enforcement (engines.node only warns at install; npx/-g can run on
29
+ // any Node). Returns true when the major version satisfies >=20, false otherwise, so
30
+ // the entry can abort with a clear message instead of failing obscurely later.
31
+ // Accepts a version string like "18.19.1" (defaults to the running process).
32
+ export function nodeMajorAtLeast(min, version = process.versions.node) {
33
+ const major = parseInt(String(version).split('.')[0], 10);
34
+ return Number.isFinite(major) && major >= min;
35
+ }