@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.
- package/LICENSE +185 -0
- package/README.md +314 -0
- package/bin/code-conductor.mjs +133 -0
- package/global/CLAUDE.md +93 -0
- package/global/commands/cc-checkpoint.md +50 -0
- package/global/commands/cc-compact.md +49 -0
- package/global/commands/cc-lang.md +32 -0
- package/global/commands/cc-stack.md +69 -0
- package/global/hooks/graphify-ast-refresh.py +81 -0
- package/global/hooks/verbosity-remind.sh +372 -0
- package/global/memory/personal.md +24 -0
- package/global/memory/verbosity.md +7 -0
- package/global/settings.json +24 -0
- package/lib/installer/config.mjs +51 -0
- package/lib/installer/deploy.mjs +76 -0
- package/lib/installer/env.mjs +35 -0
- package/lib/installer/settings.mjs +77 -0
- package/package.json +34 -0
- package/project-template/.claude/commands/cc-debug.md +42 -0
- package/project-template/.claude/commands/cc-docs.md +49 -0
- package/project-template/.claude/commands/cc-implement.md +164 -0
- package/project-template/.claude/commands/cc-init.md +84 -0
- package/project-template/.claude/commands/cc-plan.md +114 -0
- package/project-template/.claude/commands/cc-refactor.md +42 -0
- package/project-template/.claude/commands/cc-resume.md +142 -0
- package/project-template/.claude/commands/cc-review.md +77 -0
- package/project-template/.claude/commands/cc-spec.md +138 -0
- package/project-template/.claude/commands/cc-test.md +56 -0
- package/project-template/.claude/hooks/context-guard.ps1 +55 -0
- package/project-template/.claude/hooks/context-guard.sh +43 -0
- package/project-template/.claude/hooks/post-compact.ps1 +41 -0
- package/project-template/.claude/hooks/post-compact.sh +36 -0
- package/project-template/.claude/hooks/pre-tool-use.sh +81 -0
- package/project-template/.claude/hooks/verbosity-remind.sh +168 -0
- package/project-template/.claude/memory/context-threshold.txt +1 -0
- package/project-template/.claude/memory/project.md +48 -0
- package/project-template/.claude/settings.json +52 -0
- package/project-template/CLAUDE.md +104 -0
- package/skills/agent-delegation.md +44 -0
- package/skills/code-simplifier.md +124 -0
- package/skills/critical-review.md +75 -0
- package/skills/memory-first.md +50 -0
- package/skills/verbosity.md +30 -0
package/global/CLAUDE.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Global Claude Configuration
|
|
2
|
+
|
|
3
|
+
Applies to every project on this machine. Project CLAUDE.md adds project-specific overrides on top.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
Always follow this sequence. No exceptions.
|
|
8
|
+
|
|
9
|
+
1. `/cc-spec` — define the problem and wait for approval
|
|
10
|
+
2. `/cc-plan` — map the implementation and wait for approval
|
|
11
|
+
3. Implement — one step at a time, confirm between steps unless told otherwise
|
|
12
|
+
|
|
13
|
+
Never write code without an approved spec. Never start implementing without an approved plan.
|
|
14
|
+
|
|
15
|
+
## Token Efficiency
|
|
16
|
+
|
|
17
|
+
- grep/find before read — never open a file without searching first
|
|
18
|
+
- Never read a full file over 150 lines — use line offsets and limits
|
|
19
|
+
- One tool call, one purpose — no exploratory reads during implementation
|
|
20
|
+
|
|
21
|
+
## Orchestrator Protocol
|
|
22
|
+
|
|
23
|
+
Before reading any file or spawning any search, classify the task and walk this chain. Stop at the first step that answers the question.
|
|
24
|
+
|
|
25
|
+
1. **Memory**: check `.claude/memory/project.md`. If the answer is there, stop.
|
|
26
|
+
2. **Graph** — structural question? Run `graphify query "<question>"` if `graphify` is installed. Stop.
|
|
27
|
+
3. **Grep / Glob** — pattern search? Use `Grep` or `Glob` inline. Stop.
|
|
28
|
+
4. **Explore sub-agent** — need 3+ files to answer one question? Spawn an `Explore` sub-agent. It returns a ≤200-word summary. Main context receives only the summary. Stop.
|
|
29
|
+
5. **Parallel agents** — 2+ independent implementation tasks? Spawn parallel agents in worktrees. Each returns a ≤200-word summary.
|
|
30
|
+
6. **Targeted read** — last resort. Always use `offset` + `limit`. Max 150 lines per call.
|
|
31
|
+
|
|
32
|
+
Never jump to a later step if an earlier one can answer the question.
|
|
33
|
+
|
|
34
|
+
## Safety
|
|
35
|
+
|
|
36
|
+
- Check file exists before creating — use the pre-tool-use hook
|
|
37
|
+
- Confirm before any write or destructive shell command
|
|
38
|
+
- Never hardcode secrets, tokens, passwords, or API keys
|
|
39
|
+
- Never run `rm -rf` without explicit user confirmation
|
|
40
|
+
|
|
41
|
+
## Simplicity
|
|
42
|
+
|
|
43
|
+
The code-simplifier skill is always active. Apply its rules to every change:
|
|
44
|
+
|
|
45
|
+
- No speculative abstractions — solve today's problem only
|
|
46
|
+
- Functions do one thing, max 30 lines
|
|
47
|
+
- Flat over nested — guard clauses and early returns
|
|
48
|
+
- Descriptive names — no Base/Abstract/Generic/Manager/Handler
|
|
49
|
+
- Comments explain why, never what
|
|
50
|
+
|
|
51
|
+
## Language
|
|
52
|
+
|
|
53
|
+
- Default: English (responses + code comments + identifiers)
|
|
54
|
+
- Switch: `/cc-lang [code]` changes response language for the session
|
|
55
|
+
- Code identifiers, file names, and commit messages are always English
|
|
56
|
+
- Supported codes: en es pt fr de it zh ja ko
|
|
57
|
+
|
|
58
|
+
## Stack Detection
|
|
59
|
+
|
|
60
|
+
Auto-run `/cc-stack` at the start of every session. It runs the dynamic detector and records the stack in the project `CLAUDE.md`. If a stack is already recorded, it asks whether anything changed before re-detecting.
|
|
61
|
+
|
|
62
|
+
## Memory
|
|
63
|
+
|
|
64
|
+
- `project.md` — in git, shared with the team. Decisions, conventions, debt, workarounds.
|
|
65
|
+
- `personal.md` — local only, never committed. Developer preferences, personal shortcuts.
|
|
66
|
+
- Run `/cc-checkpoint` before `/compact`, after feature completion, after key architectural decisions.
|
|
67
|
+
|
|
68
|
+
## Delegation
|
|
69
|
+
|
|
70
|
+
- Use Superpowers for isolated, parallelizable sub-tasks
|
|
71
|
+
- Use Playwright MCP for visual debugging and E2E tests
|
|
72
|
+
- Always confirm scope before spawning sub-agents
|
|
73
|
+
|
|
74
|
+
## Response Format
|
|
75
|
+
|
|
76
|
+
Always tag responses:
|
|
77
|
+
|
|
78
|
+
- `[CHANGES]` — list of files changed and what changed
|
|
79
|
+
- `[BUG]` — bug report with file:line reference
|
|
80
|
+
- `[PLAN]` — ordered implementation steps
|
|
81
|
+
- `[REASON]` — explanation of a decision or approach
|
|
82
|
+
|
|
83
|
+
## Active Skills
|
|
84
|
+
|
|
85
|
+
- `code-simplifier` — always active, loaded automatically
|
|
86
|
+
- `ui-ux-pro-max` — activate via `/cc-stack` when frontend frameworks are detected
|
|
87
|
+
- `verbosity` — always active; reads level from `memory/verbosity.md` (default: MIN)
|
|
88
|
+
- `memory-first` — always active; enforces the orchestrator lookup chain
|
|
89
|
+
- `agent-delegation` — always active; governs when and how to spawn sub-agents
|
|
90
|
+
|
|
91
|
+
## Loaded Profiles
|
|
92
|
+
|
|
93
|
+
`/cc-stack` detects your stack dynamically and writes the detected commands and a generated ruleset into the project `CLAUDE.md`. Never apply one language's conventions to another.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "(Conductor) Save session decisions, conventions, and debt to memory"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Read the conversation history from the current session.
|
|
6
|
+
|
|
7
|
+
Identify and extract:
|
|
8
|
+
1. **Decisions made** — architectural choices, approach selections, rejected alternatives
|
|
9
|
+
2. **Conventions established** — naming patterns, file organization, code style choices
|
|
10
|
+
3. **Technical debt** — shortcuts taken, known limitations, deferred work
|
|
11
|
+
4. **Workarounds** — non-obvious solutions and why they were needed
|
|
12
|
+
|
|
13
|
+
**Update `.claude/memory/project.md`** (append only, never delete):
|
|
14
|
+
- Add a timestamped section: `## Checkpoint [YYYY-MM-DD HH:MM]`
|
|
15
|
+
- List decisions, conventions, and debt from this session
|
|
16
|
+
|
|
17
|
+
**Update `.claude/memory/personal.md`** (local only):
|
|
18
|
+
- Add any developer preferences observed this session
|
|
19
|
+
|
|
20
|
+
**Report:**
|
|
21
|
+
- Timestamp of this checkpoint
|
|
22
|
+
- What was saved to project.md (2–3 bullets)
|
|
23
|
+
- What was saved to personal.md (1–2 bullets)
|
|
24
|
+
|
|
25
|
+
Suggest running `/cc-checkpoint` automatically: before `/compact`, after completing a feature, after any key architectural decision.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Fail-open DB tail (best-effort, synchronous)
|
|
30
|
+
|
|
31
|
+
Run **only after** the `project.md` append succeeded (authoritative). Any failure here is non-fatal — never block or revert the `project.md` update.
|
|
32
|
+
|
|
33
|
+
1. Ensure `.conductor/` exists (`mkdir -p .conductor`, best-effort); if that fails, skip the tail.
|
|
34
|
+
2. Derive `c` — the **full-40** `git rev-parse HEAD` (lowercased, `/^[0-9a-f]{7,40}$/`, `"0000000"` fallback on non-zero/timeout/git-absent/format-mismatch; shell `timeout` only if the binary exists).
|
|
35
|
+
3. Resolve the id: `id="$(node scripts/session-id.mjs 2>>.conductor/last-write.log)"`.
|
|
36
|
+
4. Derive `ph` by carry-forward: with a bounded ~5 s outer timeout (only if `timeout` exists; `conductor-db`'s internal 2 s `busy_timeout` bounds real contention otherwise), run `node <probe-flags> scripts/conductor-db.mjs get-session "$id"`; if it prints a row whose `phase` is a non-empty enum member, reuse it. On no row, empty phase, DB unavailable, or timeout, default to `"impl"`.
|
|
37
|
+
5. Set `pr` to the **verbatim `## Checkpoint …` section body** just appended to `project.md` (the same in-context string) — so `project.md` and the snapshot's `pr` are byte-identical. `pr` is always non-empty here, so the blob is always **v2**.
|
|
38
|
+
6. Project `d` / `x` from that section (best-effort): match headings `/^#{2,4}[ \t]+(.+?)[ \t]*$/`; normalize the heading text by removing all `*`, `_`, `` ` `` then `.trim()` + lowercase; a heading containing `decision` or `convention` opens a `d` block, one containing `debt`, `workaround`, or `limitation` opens an `x` block. **The `d` set is tested first and wins** (a heading matching both opens one `d` block). The parent `## Checkpoint …` heading matches no keyword, so it opens no block and closes any open one. Within a block, bullet lines `/^[ \t]*[-*][ \t]+(\S.*?)[ \t]*$/` contribute their trimmed capture (line-based; continuation lines ignored — the full text survives in `pr`). If unstructured, `d`/`x` may be empty (valid).
|
|
39
|
+
7. Defaults: `n=[]`, `f=[]`, `s`= the active spec stem or `"none"`.
|
|
40
|
+
8. Build the v2 blob: pipe `{ph, c, s, n, f, d, x, pr}` on **stdin** to `node scripts/snap-build.mjs`; capture its stdout. If it exits non-zero, skip the DB write (fail-open).
|
|
41
|
+
9. Write the rows (both argv double-quoted; blob on stdin; all output to the log):
|
|
42
|
+
|
|
43
|
+
`node <probe-flags> scripts/conductor-db.mjs session "$id" "$ph" "$s" "$c" >> .conductor/last-write.log 2>&1`
|
|
44
|
+
`printf '%s' "$snap_json" | node <probe-flags> scripts/conductor-db.mjs snapshot "$c" >> .conductor/last-write.log 2>&1`
|
|
45
|
+
|
|
46
|
+
All DB-tail redirects use **append mode (`>>`)** so concurrent runs never truncate a preceding trace. **Cross-platform note:** the forms above are Unix-canonical; on Windows/PowerShell realize the same semantics — set `$OutputEncoding = [System.Text.UTF8Encoding]::new($false)` before piping, capture `$id = node …`, pipe `$snap_json | node … snapshot "$c"`, append the log via `… 2>&1 | Out-File -Append -Encoding utf8 .conductor/last-write.log` (not `*>>`), and `New-Item -ItemType Directory -Force .conductor`.
|
|
47
|
+
|
|
48
|
+
**No checkpoint post-hook needed (fail-open, no orphaned state):** unlike `/cc-compact`, `/cc-checkpoint` does not clear history, so no `post-compact` hook fires after it — and none is needed. A mid-tail failure leaves no stale telemetry to clean: the session id is unchanged (same env-var session), a partial write (session ok / snapshot fail, or vice-versa) is **accepted** — ARCH-008-B tolerates each miss independently with no rollback — and the only temp file (`session-id.*.tmp`) is cleaned by `session-id.mjs` itself and swept at the next compaction boundary (T-006). Adding a dedicated checkpoint cleanup hook would be YAGNI.
|
|
49
|
+
|
|
50
|
+
Report the checkpoint as usual regardless of the tail's outcome.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "(Conductor) Serialize phase state and prompt for context compaction"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Run `git rev-parse HEAD` to get the current commit SHA (the **full 40-char** hash — NOT `--short`, whose abbreviation length auto-scales with repo size and is unstable across growth). Lowercase and trim it; if it does not match `/^[0-9a-f]{7,40}$/`, or the command exits non-zero (non-git workspace, no commits, git absent, or permission-denied), use `"0000000"`. Wrap the call with shell `timeout` only when `command -v timeout` (or `gtimeout`) succeeds; otherwise run it unwrapped. This same value is `sys.c` and the `sessions` / `snapshots` git-hash key.
|
|
6
|
+
|
|
7
|
+
Determine `sys.s`: take the active specification file path from this phase's context, split on `/` and `\`, keep the last segment, strip a trailing `.md` extension, truncate to 200 characters. If no active spec file exists, use `"none"`.
|
|
8
|
+
|
|
9
|
+
Collect from the current conversation context:
|
|
10
|
+
- **Phase** (`sys.ph`): `spec` | `plan` | `impl` | `rev`, the phase that just completed
|
|
11
|
+
- **Decisions** (`mem.d`): finalized decisions this phase, max 10 elements, each ≤300 chars (JSON-serialized length)
|
|
12
|
+
- **Pending** (`ops.n`): next immediate step(s), max 3 elements, each ≤200 chars
|
|
13
|
+
- **Files Touched** (`ops.f`): each entry `<relpath>:C|M|D` (uppercase only); replace any backslash in the path with `/`; if the path contains a literal `:`, percent-encode it as `%3A` before appending the suffix; max 20 elements, each ≤300 chars
|
|
14
|
+
- **Constraints** (`mem.x`): hard constraints next phase must respect, max 5 elements, each ≤200 chars
|
|
15
|
+
|
|
16
|
+
For all four array fields: filter out empty/whitespace-only strings, deduplicate by exact case-sensitive string equality (first occurrence wins), then truncate each surviving element to its per-element cap (Unicode-safe: `Array.from(str).slice(0, cap).join('')`), then if the array still exceeds its cap, drop from the **head** (oldest first) until it fits. Empty arrays are valid and must still be serialized (never omitted).
|
|
17
|
+
|
|
18
|
+
Serialize by piping a flat JSON object `{ph, c, s, n, f, d, x}` (no `pr`) on **stdin** to `node scripts/snap-build.mjs`; its stdout is the canonical single-line **v1** SNAP JSON. `snap-build` performs all array normalization (filter/dedup/per-element cap/head-drop) and the 4096-char size trim internally — do not pre-serialize. If `snap-build` exits non-zero (malformed field object), report the error and stop; do not write the handoff file, do not run the DB tail, do not print the compact prompt.
|
|
19
|
+
|
|
20
|
+
Write the single JSON line, followed by exactly one trailing newline, to `.claude/memory/session-snapshot.json`.
|
|
21
|
+
|
|
22
|
+
If `.claude/memory/session-snapshot.md` exists, delete it as part of this write.
|
|
23
|
+
|
|
24
|
+
Idempotently append `.claude/memory/session-snapshot.json` to the project's `.gitignore`: read the file; if any line, after stripping leading/trailing whitespace, exactly equals `.claude/memory/session-snapshot.json`, skip the append. Otherwise, check whether the last byte of `.gitignore` is a newline; if not, prepend a newline before the appended entry. If `.gitignore` cannot be read or written (permission or lock error), log a non-fatal warning to stderr and continue; do not block the snapshot write.
|
|
25
|
+
|
|
26
|
+
If writing the snapshot fails (e.g., missing `.claude/memory/` directory), report the error and stop. Do NOT output the compact prompt.
|
|
27
|
+
|
|
28
|
+
Once the snapshot is written successfully, output exactly:
|
|
29
|
+
|
|
30
|
+
> Snapshot written. Run `/compact` now to clear history.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Fail-open DB tail (best-effort, synchronous)
|
|
35
|
+
|
|
36
|
+
After the handoff file is written and the compact prompt is ready — and **only** if the authoritative write succeeded — run this synchronous, fail-open tail. Any failure here is non-fatal: never revert the handoff file, never suppress the compact prompt.
|
|
37
|
+
|
|
38
|
+
1. Ensure `.conductor/` exists (`mkdir -p .conductor`, best-effort). If that fails, skip the tail entirely.
|
|
39
|
+
2. Resolve the session id: `id="$(node scripts/session-id.mjs 2>>.conductor/last-write.log)"`.
|
|
40
|
+
3. Upsert the session row (Node-flag probe applies — same as the cc-implement Step 6 hook: no-flag-first, else `--experimental-sqlite --no-warnings`, else skip):
|
|
41
|
+
|
|
42
|
+
`node <probe-flags> scripts/conductor-db.mjs session "$id" "$ph" "$s" "$c" >> .conductor/last-write.log 2>&1`
|
|
43
|
+
4. Insert the snapshot row, piping the v1 blob on **stdin** (never argv):
|
|
44
|
+
|
|
45
|
+
`printf '%s' "$snap_json" | node <probe-flags> scripts/conductor-db.mjs snapshot "$c" >> .conductor/last-write.log 2>&1`
|
|
46
|
+
|
|
47
|
+
All argv scalars are double-quoted. **Every** DB-tail redirect uses **append mode (`>>`)** — never `>` — so a rapid or parallel second run never truncates a preceding trace; the log is gitignored, best-effort, and per-run growth is one line at most (rotation is out of scope). The lone `CONDUCTOR_DB:` degradation line (Node < 22.5 / `node:sqlite` absent) lands only in `.conductor/last-write.log`, never the UI. Finally print `> Snapshot written. Run /compact now to clear history.` regardless of the tail's outcome.
|
|
48
|
+
|
|
49
|
+
**Cross-platform note:** the incantations above are the **Unix (bash) canonical form**; the `.md` file is an agent instruction, not a literal script — realize the same semantics on the host shell. On Windows/PowerShell: **first set `$OutputEncoding = [System.Text.UTF8Encoding]::new($false)`** (no-BOM UTF-8, so the piped snapshot string reaches Node's UTF-8 `TextDecoder` byte-clean); capture the id via `$id = node scripts/session-id.mjs`; pipe the blob with `$snap_json | node … snapshot "$c"`; append the log with `… 2>&1 | Out-File -Append -Encoding utf8 .conductor/last-write.log` (explicit UTF-8 append — never the bare `*>>`, whose default encoding is UTF-16LE on PS 5.1 and would corrupt the trace); ensure the dir with `New-Item -ItemType Directory -Force .conductor`. The stdin-only-for-payloads, double-quoted-argv, append-log, and fail-open rules are identical on both platforms.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "(Conductor) Switch response language for this session"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Switch the response language for this session.
|
|
6
|
+
|
|
7
|
+
**With argument** (`/lang es`, `/lang pt`, etc.):
|
|
8
|
+
- Switch immediately to the requested language
|
|
9
|
+
- Confirm in the new language (e.g., "Idioma cambiado a Español. ¿En qué puedo ayudarte?")
|
|
10
|
+
- Scope: responses and code comments only
|
|
11
|
+
- Never change: identifiers, file names, commit messages, branch names
|
|
12
|
+
|
|
13
|
+
**Without argument** (`/lang`):
|
|
14
|
+
Show current setting and list available codes:
|
|
15
|
+
|
|
16
|
+
| Code | Language |
|
|
17
|
+
|------|-------------|
|
|
18
|
+
| en | English |
|
|
19
|
+
| es | Spanish |
|
|
20
|
+
| pt | Portuguese |
|
|
21
|
+
| fr | French |
|
|
22
|
+
| de | German |
|
|
23
|
+
| it | Italian |
|
|
24
|
+
| zh | Chinese |
|
|
25
|
+
| ja | Japanese |
|
|
26
|
+
| ko | Korean |
|
|
27
|
+
|
|
28
|
+
**Priority hierarchy:**
|
|
29
|
+
1. Session setting (this command) — highest
|
|
30
|
+
2. Project CLAUDE.md `language:` setting
|
|
31
|
+
3. `personal.md` `response_language:` value
|
|
32
|
+
4. Default: English
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "(Conductor) Detect the project stack and write commands + a generated ruleset into CLAUDE.md"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Detect the project stack dynamically and record it in the project's root `CLAUDE.md`. Do not pattern-match manifest files by hand and do not load any static profile; the detector owns detection.
|
|
6
|
+
|
|
7
|
+
## 1. Resolve the repository root
|
|
8
|
+
|
|
9
|
+
Run `git rev-parse --show-toplevel`. If it fails (not a git repo), fall back to the directory that contains `scripts/detect-stack.mjs`. Run every command below from that root, and treat the root-level `CLAUDE.md` as the only write target regardless of the user's current subdirectory.
|
|
10
|
+
|
|
11
|
+
## 2. Verify Node is available
|
|
12
|
+
|
|
13
|
+
Probe with `node --version`. If it exits non-zero or reports "not found", stop the write path: announce in one line `No stack detected (Node not found on PATH; Node >= 18 recommended)` and leave `CLAUDE.md` untouched.
|
|
14
|
+
|
|
15
|
+
## 3. Run the detector
|
|
16
|
+
|
|
17
|
+
Run `node scripts/detect-stack.mjs` from the repo root, capture stdout, and parse it as JSON. Nothing executes the JSON; consume it as data.
|
|
18
|
+
|
|
19
|
+
Treat all of the following identically as "no stack detected" - announce it in one line and leave `CLAUDE.md` unmodified (surface stderr if any, but it is non-fatal):
|
|
20
|
+
- non-zero exit code,
|
|
21
|
+
- empty stdout,
|
|
22
|
+
- stdout that does not parse as JSON,
|
|
23
|
+
- a parsed object equal to `{}`.
|
|
24
|
+
|
|
25
|
+
## 4. Read CLAUDE.md and branch
|
|
26
|
+
|
|
27
|
+
If the root `CLAUDE.md` does not exist, do not create it: instruct the user to run `/cc-init` and stop.
|
|
28
|
+
|
|
29
|
+
Otherwise read `CLAUDE.md` and look for the `- Stack:` line under `## Project Identity`:
|
|
30
|
+
- **Stack line present and non-empty** -> ask: `I see you're using [stack]. Has anything changed?` This is a blocking yes/no prompt with no timeout.
|
|
31
|
+
- `no` -> acknowledge in one line and stop; make no edits.
|
|
32
|
+
- `yes`, or free text describing a change -> re-run the detector (step 3) and rewrite the managed sections from the fresh output. Free text is an additive hint only: fold user-named tools the detector missed into the ruleset, but detected facts always win on conflict.
|
|
33
|
+
- **`## Project Identity` missing, or present without a `- Stack:` line** -> treat as "no recorded stack" (not an error). Proceed to the write path; append the `## Project Identity` header if absent, or surgically insert the `- Stack:` line if only that line is missing. Note in one line that a fresh stack identity was recorded.
|
|
34
|
+
|
|
35
|
+
## 5. Write path
|
|
36
|
+
|
|
37
|
+
Detect and preserve `CLAUDE.md`'s dominant EOL style (LF or CRLF); never convert wholesale. If the OS denies the write or the file is locked, abort that write with a single-line error naming the path and cause (e.g. `ERROR: cannot write CLAUDE.md: permission denied - no changes applied`), leave the file byte-for-byte untouched, and still report the detection result.
|
|
38
|
+
|
|
39
|
+
### 5a. Development Commands (per-field surgical edits, BUG-015 pattern)
|
|
40
|
+
|
|
41
|
+
For each of `Build`, `Test`, `Lint`, `Format`, `Setup`, if the detector JSON has a non-empty string for the matching key (`build`, `test`, `lint`, `format`, `setup`), replace that single line under `## Development Commands` with `- <Label>: <value>`. Edit one field line at a time (BUG-003). If a key is absent, leave the existing placeholder (e.g. `<command>` or `N/A`) unchanged - never fabricate a command. Also set the `- Stack:` line under `## Project Identity` from the `stack` key (or record the stack name derived from detection). If `## Development Commands` is absent, append the header and its five fields at the end of the file.
|
|
42
|
+
|
|
43
|
+
### 5b. Active Stack Profiles (whole-block regeneration)
|
|
44
|
+
|
|
45
|
+
Regenerate the entire body between the `## Active Stack Profiles` header and the next `##` (or end of file). If the header is absent, append it at the end of the file - never insert mid-document. The block is bounded and its prior content is fully replaced, not merged; all content outside it is preserved verbatim.
|
|
46
|
+
|
|
47
|
+
Open the block with this sentinel line (colon separator, no em dash):
|
|
48
|
+
|
|
49
|
+
<!-- cc-stack:managed: regenerated by /cc-stack, manual edits inside are overwritten -->
|
|
50
|
+
|
|
51
|
+
Then synthesize a concise ruleset from the detected stack(s):
|
|
52
|
+
- One `### <stack>` subsection per distinct detected stack, in detector output order, each owning its own bullet list.
|
|
53
|
+
- Secondary JSON properties (`packageManager`, `monorepo`, and any other non-command key) are optional inputs to the ruleset only - they are never `## Development Commands` fields. Emit a bullet for one only when its value is a non-empty string. If a key is absent, `null`, empty, or whitespace-only, omit its bullet entirely - never write a placeholder such as `null`, `<packageManager>`, or `N/A` for it, and never fabricate a value. This mirrors the BUG-015 fill guard (`typeof val !== 'string' || !val.trim()` is skipped).
|
|
54
|
+
- For a monorepo, when `monorepo` is a non-empty string record the monorepo tool (pnpm/nx/melos/turbo) once as a single top-level bullet above the per-stack subsections; when it is null/empty/absent, write no monorepo bullet.
|
|
55
|
+
- When `packageManager` is a non-empty string, it may inform one identity bullet (per trim keep-priority 2); when null/empty/absent, omit it.
|
|
56
|
+
- Bullets are imperative, project-agnostic conventions grounded strictly in the detected stack (language, framework, package manager, test/lint tooling). No tutorials, no version-specific advice, no speculation about code not present. Match the tone of existing `CLAUDE.md` convention bullets. Prose paragraphs are disallowed.
|
|
57
|
+
- Write the ruleset prose in the project language from the `- Language:` line (fallback: global `/cc-lang`, default `en`). Code identifiers, commands, and file names stay English.
|
|
58
|
+
|
|
59
|
+
Enforce these hard caps; if synthesis would exceed any of them, trim lowest-value bullets first:
|
|
60
|
+
- <= 8 bullets and <= 200 words per stack,
|
|
61
|
+
- <= 500 words total across the whole block,
|
|
62
|
+
- <= 2500 characters for the whole block,
|
|
63
|
+
- <= 120 characters per bullet.
|
|
64
|
+
|
|
65
|
+
Trim keep-priority (keep highest first): (1) build/test/lint/format command correctness, (2) package-manager + monorepo-tool identity, (3) primary language/framework conventions. Drop first: (a) generic stack-agnostic advice, (b) optional tooling niceties, (c) bullets already implied by a recorded command. Ties break toward the earlier-listed detected stack.
|
|
66
|
+
|
|
67
|
+
## 6. Announce
|
|
68
|
+
|
|
69
|
+
State the detected stack(s) in one line, name each managed section you regenerated, and note that prior managed content is recoverable from git history.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""UserPromptSubmit hook: runs detect + AST extraction in background when graph is stale."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import subprocess
|
|
6
|
+
import platform
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
STALE_MINUTES = int(os.environ.get("GRAPHIFY_STALE_MINUTES", "60"))
|
|
12
|
+
except ValueError:
|
|
13
|
+
STALE_MINUTES = 60
|
|
14
|
+
|
|
15
|
+
AST_SCRIPT = """
|
|
16
|
+
import json, sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from graphify.detect import detect
|
|
19
|
+
from graphify.extract import collect_files, extract
|
|
20
|
+
|
|
21
|
+
out = Path("graphify-out")
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
result = detect(Path("."))
|
|
25
|
+
code = result.get("files", {}).get("code", [])
|
|
26
|
+
if not code:
|
|
27
|
+
sys.exit(0)
|
|
28
|
+
|
|
29
|
+
out.mkdir(exist_ok=True)
|
|
30
|
+
(out / ".graphify_detect.json").write_text(json.dumps(result))
|
|
31
|
+
|
|
32
|
+
code_files = []
|
|
33
|
+
for f in code:
|
|
34
|
+
p = Path(f)
|
|
35
|
+
code_files.extend(collect_files(p) if p.is_dir() else [p])
|
|
36
|
+
|
|
37
|
+
if code_files:
|
|
38
|
+
ast_result = extract(code_files)
|
|
39
|
+
(out / ".graphify_ast.json").write_text(json.dumps(ast_result))
|
|
40
|
+
|
|
41
|
+
(out / ".graphify_ast_done").write_text("")
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_fresh(cwd: Path) -> bool:
|
|
48
|
+
sentinel = cwd / "graphify-out" / ".graphify_ast_done"
|
|
49
|
+
if not sentinel.exists():
|
|
50
|
+
return False
|
|
51
|
+
age_minutes = (time.time() - sentinel.stat().st_mtime) / 60
|
|
52
|
+
return age_minutes < STALE_MINUTES
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main() -> None:
|
|
56
|
+
cwd = Path.cwd()
|
|
57
|
+
|
|
58
|
+
if is_fresh(cwd):
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
import graphify # noqa: F401
|
|
63
|
+
except ImportError:
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
kwargs: dict = {
|
|
67
|
+
"stdin": subprocess.DEVNULL,
|
|
68
|
+
"stdout": subprocess.DEVNULL,
|
|
69
|
+
"stderr": subprocess.DEVNULL,
|
|
70
|
+
"env": os.environ.copy(),
|
|
71
|
+
}
|
|
72
|
+
if platform.system() == "Windows":
|
|
73
|
+
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
74
|
+
else:
|
|
75
|
+
kwargs["start_new_session"] = True
|
|
76
|
+
|
|
77
|
+
subprocess.Popen([sys.executable, "-c", AST_SCRIPT], cwd=str(cwd), **kwargs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|