ccgx-workflow 1.0.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 (212) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +469 -0
  3. package/README.zh-CN.md +466 -0
  4. package/bin/ccg.mjs +2 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.mjs +173 -0
  8. package/dist/index.d.mts +1774 -0
  9. package/dist/index.d.ts +1774 -0
  10. package/dist/index.mjs +2029 -0
  11. package/dist/shared/ccgx-workflow.WgUzkiC3.mjs +5248 -0
  12. package/package.json +129 -0
  13. package/templates/commands/agents/assumptions-analyzer.md +129 -0
  14. package/templates/commands/agents/code-fixer.md +292 -0
  15. package/templates/commands/agents/codebase-mapper.md +152 -0
  16. package/templates/commands/agents/debug-session-manager.md +247 -0
  17. package/templates/commands/agents/debugger.md +111 -0
  18. package/templates/commands/agents/eval-auditor.md +171 -0
  19. package/templates/commands/agents/framework-selector.md +152 -0
  20. package/templates/commands/agents/get-current-datetime.md +29 -0
  21. package/templates/commands/agents/init-architect.md +114 -0
  22. package/templates/commands/agents/integration-checker.md +163 -0
  23. package/templates/commands/agents/interface-auditor.md +170 -0
  24. package/templates/commands/agents/nyquist-auditor.md +131 -0
  25. package/templates/commands/agents/pattern-mapper.md +111 -0
  26. package/templates/commands/agents/phase-runner.md +321 -0
  27. package/templates/commands/agents/plan-checker.md +255 -0
  28. package/templates/commands/agents/planner.md +320 -0
  29. package/templates/commands/agents/team-architect.md +186 -0
  30. package/templates/commands/agents/team-qa.md +121 -0
  31. package/templates/commands/agents/team-reviewer.md +157 -0
  32. package/templates/commands/agents/ui-ux-designer.md +573 -0
  33. package/templates/commands/agents/verifier.md +274 -0
  34. package/templates/commands/analyze.md +210 -0
  35. package/templates/commands/autonomous.md +792 -0
  36. package/templates/commands/cancel.md +132 -0
  37. package/templates/commands/clean-branches.md +117 -0
  38. package/templates/commands/codex-exec.md +404 -0
  39. package/templates/commands/commit.md +151 -0
  40. package/templates/commands/context.md +332 -0
  41. package/templates/commands/debate.md +165 -0
  42. package/templates/commands/debug.md +226 -0
  43. package/templates/commands/enhance.md +64 -0
  44. package/templates/commands/execute.md +380 -0
  45. package/templates/commands/init.md +123 -0
  46. package/templates/commands/optimize.md +217 -0
  47. package/templates/commands/plan.md +373 -0
  48. package/templates/commands/result.md +106 -0
  49. package/templates/commands/review.md +338 -0
  50. package/templates/commands/rollback.md +116 -0
  51. package/templates/commands/spec-impl.md +139 -0
  52. package/templates/commands/spec-init.md +101 -0
  53. package/templates/commands/spec-plan.md +210 -0
  54. package/templates/commands/spec-research.md +152 -0
  55. package/templates/commands/spec-review.md +120 -0
  56. package/templates/commands/status.md +206 -0
  57. package/templates/commands/team-exec.md +265 -0
  58. package/templates/commands/test.md +236 -0
  59. package/templates/commands/verify-work.md +338 -0
  60. package/templates/commands/verify.md +66 -0
  61. package/templates/commands/workflow.md +190 -0
  62. package/templates/commands/worktree.md +128 -0
  63. package/templates/hooks/ccg-context-monitor.js +159 -0
  64. package/templates/hooks/ccg-session-state.cjs +510 -0
  65. package/templates/hooks/ccg-statusline.js +142 -0
  66. package/templates/output-styles/abyss-command.md +56 -0
  67. package/templates/output-styles/abyss-concise.md +89 -0
  68. package/templates/output-styles/abyss-cultivator.md +302 -0
  69. package/templates/output-styles/abyss-ritual.md +70 -0
  70. package/templates/output-styles/engineer-professional.md +89 -0
  71. package/templates/output-styles/laowang-engineer.md +127 -0
  72. package/templates/output-styles/nekomata-engineer.md +120 -0
  73. package/templates/output-styles/ojousama-engineer.md +121 -0
  74. package/templates/prompts/claude/analyzer.md +59 -0
  75. package/templates/prompts/claude/architect.md +54 -0
  76. package/templates/prompts/claude/debugger.md +71 -0
  77. package/templates/prompts/claude/optimizer.md +73 -0
  78. package/templates/prompts/claude/reviewer.md +63 -0
  79. package/templates/prompts/claude/tester.md +69 -0
  80. package/templates/prompts/codex/analyzer.md +58 -0
  81. package/templates/prompts/codex/architect.md +54 -0
  82. package/templates/prompts/codex/debugger.md +74 -0
  83. package/templates/prompts/codex/optimizer.md +81 -0
  84. package/templates/prompts/codex/reviewer.md +73 -0
  85. package/templates/prompts/codex/tester.md +62 -0
  86. package/templates/prompts/gemini/analyzer.md +61 -0
  87. package/templates/prompts/gemini/architect.md +55 -0
  88. package/templates/prompts/gemini/debugger.md +78 -0
  89. package/templates/prompts/gemini/frontend.md +64 -0
  90. package/templates/prompts/gemini/optimizer.md +84 -0
  91. package/templates/prompts/gemini/reviewer.md +80 -0
  92. package/templates/prompts/gemini/tester.md +68 -0
  93. package/templates/rules/ccg-skill-routing.md +83 -0
  94. package/templates/rules/ccg-skills.md +71 -0
  95. package/templates/scripts/ccg-phase-runner-launcher.mjs +467 -0
  96. package/templates/scripts/invoke-model.mjs +949 -0
  97. package/templates/scripts/repatch-gemini-plugin.mjs +194 -0
  98. package/templates/skills/SKILL.md +92 -0
  99. package/templates/skills/domains/ai/SKILL.md +35 -0
  100. package/templates/skills/domains/ai/agent-dev.md +242 -0
  101. package/templates/skills/domains/ai/llm-security.md +288 -0
  102. package/templates/skills/domains/ai/prompt-and-eval.md +279 -0
  103. package/templates/skills/domains/ai/rag-system.md +542 -0
  104. package/templates/skills/domains/architecture/SKILL.md +43 -0
  105. package/templates/skills/domains/architecture/api-design.md +225 -0
  106. package/templates/skills/domains/architecture/caching.md +299 -0
  107. package/templates/skills/domains/architecture/cloud-native.md +285 -0
  108. package/templates/skills/domains/architecture/message-queue.md +329 -0
  109. package/templates/skills/domains/architecture/security-arch.md +297 -0
  110. package/templates/skills/domains/data-engineering/SKILL.md +208 -0
  111. package/templates/skills/domains/development/SKILL.md +47 -0
  112. package/templates/skills/domains/development/cpp.md +246 -0
  113. package/templates/skills/domains/development/go.md +323 -0
  114. package/templates/skills/domains/development/java.md +277 -0
  115. package/templates/skills/domains/development/python.md +288 -0
  116. package/templates/skills/domains/development/rust.md +313 -0
  117. package/templates/skills/domains/development/shell.md +313 -0
  118. package/templates/skills/domains/development/typescript.md +277 -0
  119. package/templates/skills/domains/devops/SKILL.md +40 -0
  120. package/templates/skills/domains/devops/cost-optimization.md +272 -0
  121. package/templates/skills/domains/devops/database.md +217 -0
  122. package/templates/skills/domains/devops/devsecops.md +198 -0
  123. package/templates/skills/domains/devops/git-workflow.md +181 -0
  124. package/templates/skills/domains/devops/observability.md +280 -0
  125. package/templates/skills/domains/devops/performance.md +336 -0
  126. package/templates/skills/domains/devops/testing.md +283 -0
  127. package/templates/skills/domains/frontend-design/SKILL.md +244 -0
  128. package/templates/skills/domains/frontend-design/agents/openai.yaml +4 -0
  129. package/templates/skills/domains/frontend-design/claymorphism/SKILL.md +121 -0
  130. package/templates/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  131. package/templates/skills/domains/frontend-design/component-patterns.md +202 -0
  132. package/templates/skills/domains/frontend-design/engineering.md +287 -0
  133. package/templates/skills/domains/frontend-design/glassmorphism/SKILL.md +142 -0
  134. package/templates/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  135. package/templates/skills/domains/frontend-design/liquid-glass/SKILL.md +139 -0
  136. package/templates/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  137. package/templates/skills/domains/frontend-design/neubrutalism/SKILL.md +145 -0
  138. package/templates/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  139. package/templates/skills/domains/frontend-design/reference/color-and-contrast.md +132 -0
  140. package/templates/skills/domains/frontend-design/reference/interaction-design.md +195 -0
  141. package/templates/skills/domains/frontend-design/reference/motion-design.md +99 -0
  142. package/templates/skills/domains/frontend-design/reference/responsive-design.md +114 -0
  143. package/templates/skills/domains/frontend-design/reference/spatial-design.md +100 -0
  144. package/templates/skills/domains/frontend-design/reference/typography.md +133 -0
  145. package/templates/skills/domains/frontend-design/reference/ux-writing.md +107 -0
  146. package/templates/skills/domains/frontend-design/state-management.md +680 -0
  147. package/templates/skills/domains/frontend-design/ui-aesthetics.md +110 -0
  148. package/templates/skills/domains/frontend-design/ux-principles.md +156 -0
  149. package/templates/skills/domains/infrastructure/SKILL.md +201 -0
  150. package/templates/skills/domains/mobile/SKILL.md +225 -0
  151. package/templates/skills/domains/orchestration/SKILL.md +30 -0
  152. package/templates/skills/domains/orchestration/multi-agent.md +263 -0
  153. package/templates/skills/domains/security/SKILL.md +73 -0
  154. package/templates/skills/domains/security/blue-team.md +436 -0
  155. package/templates/skills/domains/security/code-audit.md +265 -0
  156. package/templates/skills/domains/security/pentest.md +226 -0
  157. package/templates/skills/domains/security/red-team.md +374 -0
  158. package/templates/skills/domains/security/threat-intel.md +372 -0
  159. package/templates/skills/domains/security/vuln-research.md +369 -0
  160. package/templates/skills/impeccable/adapt/SKILL.md +201 -0
  161. package/templates/skills/impeccable/animate/SKILL.md +176 -0
  162. package/templates/skills/impeccable/arrange/SKILL.md +126 -0
  163. package/templates/skills/impeccable/audit/SKILL.md +149 -0
  164. package/templates/skills/impeccable/bolder/SKILL.md +118 -0
  165. package/templates/skills/impeccable/clarify/SKILL.md +185 -0
  166. package/templates/skills/impeccable/colorize/SKILL.md +144 -0
  167. package/templates/skills/impeccable/critique/SKILL.md +203 -0
  168. package/templates/skills/impeccable/critique/reference/cognitive-load.md +106 -0
  169. package/templates/skills/impeccable/critique/reference/heuristics-scoring.md +234 -0
  170. package/templates/skills/impeccable/critique/reference/personas.md +178 -0
  171. package/templates/skills/impeccable/delight/SKILL.md +305 -0
  172. package/templates/skills/impeccable/distill/SKILL.md +123 -0
  173. package/templates/skills/impeccable/extract/SKILL.md +94 -0
  174. package/templates/skills/impeccable/harden/SKILL.md +357 -0
  175. package/templates/skills/impeccable/normalize/SKILL.md +72 -0
  176. package/templates/skills/impeccable/onboard/SKILL.md +248 -0
  177. package/templates/skills/impeccable/optimize/SKILL.md +268 -0
  178. package/templates/skills/impeccable/overdrive/SKILL.md +143 -0
  179. package/templates/skills/impeccable/polish/SKILL.md +205 -0
  180. package/templates/skills/impeccable/quieter/SKILL.md +104 -0
  181. package/templates/skills/impeccable/teach-impeccable/SKILL.md +72 -0
  182. package/templates/skills/impeccable/typeset/SKILL.md +117 -0
  183. package/templates/skills/orchestration/multi-agent/SKILL.md +533 -0
  184. package/templates/skills/run_skill.js +129 -0
  185. package/templates/skills/scrapling/SKILL.md +98 -0
  186. package/templates/skills/scrapling/references/api-quick-ref.md +127 -0
  187. package/templates/skills/scrapling/references/cookie-vault.md +56 -0
  188. package/templates/skills/scrapling/references/maintenance.md +79 -0
  189. package/templates/skills/scrapling/references/site-patterns.md +86 -0
  190. package/templates/skills/scrapling/references/troubleshooting.md +95 -0
  191. package/templates/skills/scrapling/templates/basic_fetch.py +19 -0
  192. package/templates/skills/scrapling/templates/parse_only.py +15 -0
  193. package/templates/skills/scrapling/templates/session_login.py +19 -0
  194. package/templates/skills/scrapling/templates/stealth_cloudflare.py +28 -0
  195. package/templates/skills/tools/extract-learnings/SKILL.md +77 -0
  196. package/templates/skills/tools/forensics/SKILL.md +89 -0
  197. package/templates/skills/tools/gen-docs/SKILL.md +116 -0
  198. package/templates/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
  199. package/templates/skills/tools/health/SKILL.md +121 -0
  200. package/templates/skills/tools/lib/shared.js +98 -0
  201. package/templates/skills/tools/map-codebase/SKILL.md +118 -0
  202. package/templates/skills/tools/override-refusal/SKILL.md +53 -0
  203. package/templates/skills/tools/override-refusal/agents/openai.yaml +4 -0
  204. package/templates/skills/tools/override-refusal/scripts/refusal_rewriter.js +226 -0
  205. package/templates/skills/tools/verify-change/SKILL.md +143 -0
  206. package/templates/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  207. package/templates/skills/tools/verify-module/SKILL.md +130 -0
  208. package/templates/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  209. package/templates/skills/tools/verify-quality/SKILL.md +163 -0
  210. package/templates/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  211. package/templates/skills/tools/verify-security/SKILL.md +146 -0
  212. package/templates/skills/tools/verify-security/scripts/security_scanner.js +283 -0
@@ -0,0 +1,510 @@
1
+ #!/usr/bin/env node
2
+ // ccg-hook: session-state
3
+ // SessionStart Hook — auto-inject CCG project memory into a fresh session.
4
+ //
5
+ // Problem this solves (CCG v4.0 dogfood Q6 + GSD gsd-session-state.sh parity):
6
+ // After /clear or a brand-new session, the orchestrator has zero memory of
7
+ // the project's roadmap state. Users had to manually paste a "resume" file
8
+ // (see .ccg/SESSION-RESUME.md) to get going. This hook automates it: when a
9
+ // session starts in a CCG project (cwd has .ccg/roadmap.md), it injects a
10
+ // ≤200-token summary describing project name, active phase, and next action.
11
+ //
12
+ // Hook contract (Claude Code SessionStart event):
13
+ // stdin : JSON with at least { hookEventName, session_id, cwd? }
14
+ // cwd may be absent — we fall back to process.cwd().
15
+ // stdout : JSON
16
+ // { hookSpecificOutput: { hookEventName: 'SessionStart',
17
+ // additionalContext: '<string>' } }
18
+ // Empty / missing additionalContext means "no injection". For non-CCG
19
+ // projects we exit cleanly without writing anything (noop).
20
+ //
21
+ // Failure policy: never throw; never block a session start. Any parse error or
22
+ // missing file degrades to a smaller-but-still-useful summary, or to a noop.
23
+
24
+ 'use strict'
25
+
26
+ const fs = require('fs')
27
+ const path = require('path')
28
+ const crypto = require('crypto')
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Pure helpers (exported for unit tests via ccgSessionStateHookExports)
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Extract roadmap.md head metadata: project name, started, last updated.
36
+ *
37
+ * Roadmap convention (see .ccg/roadmap.md): bold-tagged key-value lines such as
38
+ * **Project**: ccg-workflow v4.0
39
+ * **Started**: 2026-05-03
40
+ * **Last Updated**: 2026-05-04
41
+ * Lines may appear in any order within the first ~20 lines. Anything we cannot
42
+ * locate yields undefined — callers must tolerate that.
43
+ */
44
+ function parseRoadmapHead(text) {
45
+ const head = text.split(/\r?\n/).slice(0, 30).join('\n')
46
+ const grab = (label) => {
47
+ const re = new RegExp(`\\*\\*${label}\\*\\*\\s*[::]\\s*(.+)`, 'i')
48
+ const m = head.match(re)
49
+ return m ? m[1].trim() : undefined
50
+ }
51
+ return {
52
+ project: grab('Project'),
53
+ started: grab('Started'),
54
+ lastUpdated: grab('Last Updated'),
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Parse phase headers. Each phase is denoted by `## Phase N: Title (status)`,
60
+ * where `N` may include a dot (e.g. 1.5) and `status` is one of completed /
61
+ * in_progress / pending / blocked / skipped.
62
+ *
63
+ * Returns array preserving file order. The "active" phase used for context
64
+ * injection is the first one whose status is `in_progress`; if none, the
65
+ * first `pending` phase; if all completed, null.
66
+ */
67
+ function parsePhases(text) {
68
+ const re = /^##\s+Phase\s+([\d.]+)\s*:\s*(.+?)\s*(?:\[[^\]]+\])?\s*\(([^)]+)\)\s*$/gim
69
+ const phases = []
70
+ let match
71
+ while ((match = re.exec(text)) !== null) {
72
+ phases.push({
73
+ n: match[1],
74
+ title: match[2].trim(),
75
+ status: match[3].trim().toLowerCase(),
76
+ })
77
+ }
78
+ return phases
79
+ }
80
+
81
+ /**
82
+ * Pick the phase whose state is most relevant for resume context.
83
+ * 1. First in_progress phase (resume work mid-flight)
84
+ * 2. Else first pending phase (next-up work)
85
+ * 3. Else null (every phase completed)
86
+ */
87
+ function pickActivePhase(phases) {
88
+ return (
89
+ phases.find(p => p.status === 'in_progress')
90
+ || phases.find(p => p.status === 'pending')
91
+ || null
92
+ )
93
+ }
94
+
95
+ /**
96
+ * Map a roadmap phase entry to its `.context/<dir>/SUMMARY.md` directory name.
97
+ *
98
+ * Convention used by /ccg:autonomous + phase-runner: `phase-NN-<slug>` where NN
99
+ * is two-digit (zero-padded for integers). Phase 1.5 keeps its decimal. Slug is
100
+ * the title lowercased with non-alphanumerics collapsed to dashes.
101
+ *
102
+ * We do NOT guarantee this dir exists — caller must existsSync() before reading.
103
+ */
104
+ function phaseDirName(phase) {
105
+ const n = phase.n
106
+ const padded = /^\d+$/.test(n) ? n.padStart(2, '0') : n
107
+ const slug = phase.title
108
+ .toLowerCase()
109
+ .replace(/[^a-z0-9]+/g, '-')
110
+ .replace(/^-+|-+$/g, '')
111
+ return slug ? `phase-${padded}-${slug}` : `phase-${padded}`
112
+ }
113
+
114
+ /**
115
+ * Lift YAML frontmatter into a flat Record<string, string>. Only handles the
116
+ * minimal subset that .context/<phase>/SUMMARY.md uses — scalar key/value pairs
117
+ * and short inline lists. Anything fancier degrades to the raw string.
118
+ *
119
+ * We deliberately do NOT pull in src/utils/phase-context.ts here — this hook
120
+ * runs as a standalone Node script under ~/.claude/hooks/ with no transpile
121
+ * step, so it must be self-contained.
122
+ */
123
+ function parseSummaryFrontmatter(content) {
124
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
125
+ if (!m) return null
126
+ const out = {}
127
+ for (const raw of m[1].split(/\r?\n/)) {
128
+ const line = raw.trim()
129
+ if (!line || line.startsWith('#')) continue
130
+ const km = line.match(/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/)
131
+ if (!km) continue
132
+ let value = km[2].trim()
133
+ // Strip surrounding quotes
134
+ if ((value.startsWith('"') && value.endsWith('"'))
135
+ || (value.startsWith('\'') && value.endsWith('\''))) {
136
+ value = value.slice(1, -1)
137
+ }
138
+ out[km[1]] = value
139
+ }
140
+ return out
141
+ }
142
+
143
+ /**
144
+ * Compose the actual additionalContext string (capped to keep main-thread
145
+ * context budget honored). Stays under ~200 tokens by hard-truncating at
146
+ * 800 chars after composition.
147
+ *
148
+ * Inputs:
149
+ * head — { project, started, lastUpdated } (any may be undefined)
150
+ * active — phase object or null
151
+ * summary — parsed SUMMARY.md frontmatter or null
152
+ * counts — { total, completed }
153
+ */
154
+ function composeMessage(head, active, summary, counts) {
155
+ const lines = []
156
+ lines.push('[CCG] Project memory restored from .ccg/roadmap.md.')
157
+
158
+ const projectLine = []
159
+ if (head.project) projectLine.push(`Project: ${head.project}`)
160
+ if (counts.total > 0) {
161
+ projectLine.push(`Phases: ${counts.completed}/${counts.total} completed`)
162
+ }
163
+ if (projectLine.length) lines.push(projectLine.join(' | '))
164
+
165
+ if (!active) {
166
+ if (counts.total > 0 && counts.completed === counts.total) {
167
+ lines.push('Status: All phases completed.')
168
+ }
169
+ else if (counts.total === 0) {
170
+ lines.push('Status: roadmap.md present but no phases parsed.')
171
+ }
172
+ }
173
+ else {
174
+ const tag = active.status === 'in_progress' ? 'Active' : 'Next'
175
+ lines.push(`${tag} phase: ${active.n} ${active.title} (${active.status})`)
176
+ if (summary) {
177
+ const provides = summary.provides
178
+ const nextAction = summary['next-action'] || summary.next_action || summary.nextAction
179
+ if (provides) lines.push(`Provides: ${provides}`)
180
+ if (nextAction) lines.push(`Next action: ${nextAction}`)
181
+ }
182
+ }
183
+
184
+ lines.push('Read .ccg/roadmap.md for full state. Continue from the active phase or ask the user where to start.')
185
+
186
+ let msg = lines.join('\n')
187
+ if (msg.length > 800) msg = `${msg.slice(0, 797)}...`
188
+ return msg
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // v4.5 P1b — startup reconciler (inlined CJS twin of src/utils/process-tree.ts).
193
+ //
194
+ // The hook MUST stay self-contained (see top-of-file comment). We duplicate
195
+ // the minimal logic rather than `require('../../src/utils/process-tree')` —
196
+ // the hook is shipped to ~/.claude/hooks/ where TS source is unavailable.
197
+ //
198
+ // Behaviour matrix (mirrors process-tree.ts reconcileStaleJobs):
199
+ // - .context/jobs/* missing → no-op (return empty)
200
+ // - state.status terminal (done/failed/canceled) → no-op
201
+ // - cli_pid alive → no-op
202
+ // - cli_pid dead AND result.md present → adopt-result
203
+ // - cli_pid dead AND no result.md → mark-failed-stale
204
+ // - status=running but no cli_pid (legacy) → mark-failed-no-result
205
+ // ---------------------------------------------------------------------------
206
+
207
+ function isAlivePid(pid) {
208
+ if (!Number.isInteger(pid) || pid <= 0) return false
209
+ try {
210
+ process.kill(pid, 0)
211
+ return true
212
+ }
213
+ catch (err) {
214
+ if (err && err.code === 'EPERM') return true
215
+ return false
216
+ }
217
+ }
218
+
219
+ function atomicWriteFileSync(target, content) {
220
+ const rand = crypto.randomBytes(6).toString('hex')
221
+ const tmp = `${target}.tmp.${rand}`
222
+ try {
223
+ fs.writeFileSync(tmp, content, 'utf-8')
224
+ fs.renameSync(tmp, target)
225
+ }
226
+ catch (err) {
227
+ try { fs.unlinkSync(tmp) }
228
+ catch { /* nothing to clean up */ }
229
+ throw err
230
+ }
231
+ }
232
+
233
+ function reconcileStaleJobs(cwd, options) {
234
+ const opts = options || {}
235
+ const isAlive = opts.isAliveFn || isAlivePid
236
+ const now = typeof opts.nowMs === 'number' ? opts.nowMs : Date.now()
237
+ const reuseAgeMs = typeof opts.pidReuseAgeMs === 'number'
238
+ ? opts.pidReuseAgeMs
239
+ : 24 * 60 * 60 * 1000
240
+
241
+ const root = path.join(cwd, '.context', 'jobs')
242
+ const report = { scanned: 0, entries: [] }
243
+ if (!fs.existsSync(root)) return report
244
+
245
+ let dirs
246
+ try { dirs = fs.readdirSync(root) }
247
+ catch { return report }
248
+
249
+ for (const id of dirs) {
250
+ const sub = path.join(root, id)
251
+ let isDir = false
252
+ try { isDir = fs.statSync(sub).isDirectory() }
253
+ catch { continue }
254
+ if (!isDir) continue
255
+
256
+ const statePath = path.join(sub, 'state.json')
257
+ if (!fs.existsSync(statePath)) continue
258
+
259
+ let state
260
+ try {
261
+ state = JSON.parse(fs.readFileSync(statePath, 'utf-8'))
262
+ }
263
+ catch {
264
+ // Corrupt — skip silently; getJob() in src will surface to the user.
265
+ continue
266
+ }
267
+ report.scanned += 1
268
+
269
+ if (
270
+ state.status === 'done'
271
+ || state.status === 'failed'
272
+ || state.status === 'canceled'
273
+ ) {
274
+ report.entries.push({ jobId: id, action: 'no-op', reason: 'terminal status' })
275
+ continue
276
+ }
277
+
278
+ if (typeof state.cli_pid !== 'number') {
279
+ const updated = Object.assign({}, state, {
280
+ status: 'failed',
281
+ summary: 'reconciler: legacy job without cli_pid; cannot verify liveness',
282
+ last_update: new Date().toISOString(),
283
+ })
284
+ try { atomicWriteFileSync(statePath, JSON.stringify(updated, null, 2)) }
285
+ catch { /* swallow — never block session start */ }
286
+ report.entries.push({
287
+ jobId: id,
288
+ action: 'mark-failed-no-result',
289
+ reason: 'no cli_pid recorded',
290
+ })
291
+ continue
292
+ }
293
+
294
+ const alive = isAlive(state.cli_pid)
295
+ let pidProbablyReused = false
296
+ if (alive && state.started_at) {
297
+ const startedMs = Date.parse(state.started_at)
298
+ if (Number.isFinite(startedMs) && (now - startedMs) > reuseAgeMs) {
299
+ pidProbablyReused = true
300
+ }
301
+ }
302
+
303
+ if (alive && !pidProbablyReused) {
304
+ report.entries.push({ jobId: id, action: 'no-op', reason: 'cli_pid alive' })
305
+ continue
306
+ }
307
+
308
+ const resultPath = path.join(sub, 'result.md')
309
+ if (fs.existsSync(resultPath)) {
310
+ const updated = Object.assign({}, state, {
311
+ status: 'done',
312
+ summary: 'reconciler: cli_pid not alive; adopted result.md after orphan recovery',
313
+ last_update: new Date().toISOString(),
314
+ })
315
+ try { atomicWriteFileSync(statePath, JSON.stringify(updated, null, 2)) }
316
+ catch { /* swallow */ }
317
+ report.entries.push({
318
+ jobId: id,
319
+ action: 'adopt-result',
320
+ reason: pidProbablyReused
321
+ ? 'pid reuse suspected; result.md present'
322
+ : 'cli_pid dead; result.md present',
323
+ })
324
+ continue
325
+ }
326
+
327
+ const updated = Object.assign({}, state, {
328
+ status: 'failed',
329
+ summary: pidProbablyReused
330
+ ? 'reconciler: cli_pid suspected reused; no result.md found'
331
+ : 'reconciler: cli_pid dead; no result.md found',
332
+ last_update: new Date().toISOString(),
333
+ })
334
+ try { atomicWriteFileSync(statePath, JSON.stringify(updated, null, 2)) }
335
+ catch { /* swallow */ }
336
+ report.entries.push({
337
+ jobId: id,
338
+ action: 'mark-failed-stale',
339
+ reason: pidProbablyReused
340
+ ? 'pid reuse + no result'
341
+ : 'cli_pid dead + no result',
342
+ })
343
+ }
344
+
345
+ return report
346
+ }
347
+
348
+ /**
349
+ * Compose a one-line reconciler summary for injection into additionalContext.
350
+ * Returns null when nothing of interest happened (so the hook stays quiet for
351
+ * fresh / clean sessions).
352
+ */
353
+ function summarizeReconciliation(report) {
354
+ if (!report || report.scanned === 0) return null
355
+ const counts = { 'mark-failed-stale': 0, 'mark-failed-no-result': 0, 'adopt-result': 0 }
356
+ for (const e of report.entries) {
357
+ if (counts[e.action] !== undefined) counts[e.action] += 1
358
+ }
359
+ const interesting = counts['mark-failed-stale']
360
+ + counts['mark-failed-no-result']
361
+ + counts['adopt-result']
362
+ if (interesting === 0) return null
363
+ const parts = []
364
+ if (counts['mark-failed-stale'])
365
+ parts.push(`${counts['mark-failed-stale']} stale-failed`)
366
+ if (counts['mark-failed-no-result'])
367
+ parts.push(`${counts['mark-failed-no-result']} no-pid-failed`)
368
+ if (counts['adopt-result'])
369
+ parts.push(`${counts['adopt-result']} adopted-result`)
370
+ return `Reconciled ${interesting}/${report.scanned} jobs: ${parts.join(', ')}.`
371
+ }
372
+
373
+ /**
374
+ * Build the additionalContext string for a given workdir. Returns null if the
375
+ * cwd is not a CCG project (no .ccg/roadmap.md). Never throws.
376
+ */
377
+ function buildAdditionalContext(cwd) {
378
+ const roadmapPath = path.join(cwd, '.ccg', 'roadmap.md')
379
+ if (!fs.existsSync(roadmapPath)) return null
380
+
381
+ let roadmapText
382
+ try {
383
+ roadmapText = fs.readFileSync(roadmapPath, 'utf8')
384
+ }
385
+ catch {
386
+ return null
387
+ }
388
+
389
+ const head = parseRoadmapHead(roadmapText)
390
+ const phases = parsePhases(roadmapText)
391
+ const active = pickActivePhase(phases)
392
+ const counts = {
393
+ total: phases.length,
394
+ completed: phases.filter(p => p.status === 'completed').length,
395
+ }
396
+
397
+ let summary = null
398
+ if (active) {
399
+ const dir = phaseDirName(active)
400
+ const summaryPath = path.join(cwd, '.context', dir, 'SUMMARY.md')
401
+ if (fs.existsSync(summaryPath)) {
402
+ try {
403
+ const text = fs.readFileSync(summaryPath, 'utf8')
404
+ summary = parseSummaryFrontmatter(text)
405
+ }
406
+ catch {
407
+ // Fall through with summary=null
408
+ }
409
+ }
410
+ }
411
+
412
+ let baseMsg = composeMessage(head, active, summary, counts)
413
+
414
+ // v4.5 P1b: run startup reconciler over .context/jobs/* and append a one-line
415
+ // summary if anything was reconciled. Reconciler never throws — it swallows
416
+ // I/O errors so a flaky filesystem can't block session start.
417
+ let reconcileLine = null
418
+ try {
419
+ const report = reconcileStaleJobs(cwd)
420
+ reconcileLine = summarizeReconciliation(report)
421
+ }
422
+ catch {
423
+ reconcileLine = null
424
+ }
425
+ if (reconcileLine) {
426
+ baseMsg = `${baseMsg}\n${reconcileLine}`
427
+ if (baseMsg.length > 800) baseMsg = `${baseMsg.slice(0, 797)}...`
428
+ }
429
+ return baseMsg
430
+ }
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // Entry point — only runs when this file is invoked directly (not on import).
434
+ // ---------------------------------------------------------------------------
435
+
436
+ function emit(additionalContext) {
437
+ const out = {
438
+ hookSpecificOutput: {
439
+ hookEventName: 'SessionStart',
440
+ additionalContext,
441
+ },
442
+ }
443
+ process.stdout.write(JSON.stringify(out))
444
+ }
445
+
446
+ function main() {
447
+ let input = ''
448
+ // Timeout guard mirrors ccg-context-monitor: never hang on a stuck pipe.
449
+ const timer = setTimeout(() => process.exit(0), 10000)
450
+
451
+ process.stdin.setEncoding('utf8')
452
+ process.stdin.on('data', chunk => (input += chunk))
453
+ process.stdin.on('end', () => {
454
+ clearTimeout(timer)
455
+ let cwd = process.cwd()
456
+ try {
457
+ if (input.trim()) {
458
+ const data = JSON.parse(input)
459
+ if (typeof data.cwd === 'string' && data.cwd) cwd = data.cwd
460
+ }
461
+ }
462
+ catch {
463
+ // Bad JSON — fall back to process.cwd(). We still want to inject context
464
+ // for the most common case (running in the project root).
465
+ }
466
+
467
+ let message = null
468
+ try {
469
+ message = buildAdditionalContext(cwd)
470
+ }
471
+ catch {
472
+ message = null
473
+ }
474
+
475
+ if (!message) {
476
+ // Non-CCG project: emit nothing visible. Empty object keeps Claude Code
477
+ // happy and signals "no injection" without erroring.
478
+ process.stdout.write('{}')
479
+ process.exit(0)
480
+ }
481
+
482
+ emit(message)
483
+ process.exit(0)
484
+ })
485
+
486
+ process.stdin.on('error', () => process.exit(0))
487
+ }
488
+
489
+ // Detect "imported as a module" (Node test harness) vs. "executed as script".
490
+ // require.main === module is true only when invoked via `node ccg-session-state.js`.
491
+ if (require.main === module) {
492
+ main()
493
+ }
494
+
495
+ // Test surface — kept on a single object so the production hook surface stays
496
+ // minimal. Consumed by sessionStateHook.test.ts.
497
+ module.exports = {
498
+ parseRoadmapHead,
499
+ parsePhases,
500
+ pickActivePhase,
501
+ phaseDirName,
502
+ parseSummaryFrontmatter,
503
+ composeMessage,
504
+ buildAdditionalContext,
505
+ // v4.5 P1b additions:
506
+ isAlivePid,
507
+ atomicWriteFileSync,
508
+ reconcileStaleJobs,
509
+ summarizeReconciliation,
510
+ }
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ // ccg-hook: statusline
3
+ // Claude Code Statusline - CCG Edition
4
+ // Shows: model | context usage | git branch | session id (last 4)
5
+ //
6
+ // Crucial side effect: writes context metrics to
7
+ // {os.tmpdir()}/claude-ctx-{session_id}.json
8
+ // which the ccg-context-monitor.js PostToolUse hook reads to inject
9
+ // agent-facing warnings when context usage is high. The two hooks form
10
+ // a producer/consumer pair on the bridge file.
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const { execSync } = require('child_process');
16
+
17
+ // --- git branch (best-effort, silent on failure) ----------------------------
18
+
19
+ /**
20
+ * Read the current git branch for the given directory. Returns '' on failure
21
+ * (not a repo, git not installed, detached HEAD with no branch, etc).
22
+ * Wrapped tightly so a slow/missing git never breaks the statusline.
23
+ */
24
+ function readGitBranch(dir) {
25
+ if (!dir || typeof dir !== 'string') return '';
26
+ try {
27
+ // --short prints the symbolic ref name without `refs/heads/` prefix.
28
+ // Returns non-zero exit when detached; we catch and return ''.
29
+ const out = execSync('git symbolic-ref --short HEAD', {
30
+ cwd: dir,
31
+ stdio: ['ignore', 'pipe', 'ignore'],
32
+ timeout: 500,
33
+ windowsHide: true,
34
+ }).toString().trim();
35
+ if (!out) return '';
36
+ // Defensive sanity bound: branch names rarely exceed 60 chars in practice.
37
+ if (out.length > 80 || /[\s\\"<>]/.test(out)) return '';
38
+ return out;
39
+ } catch {
40
+ return '';
41
+ }
42
+ }
43
+
44
+ // --- core renderer ----------------------------------------------------------
45
+
46
+ function runStatusline() {
47
+ let input = '';
48
+ // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
49
+ // Windows/Git Bash), exit silently instead of hanging.
50
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
51
+ process.stdin.setEncoding('utf8');
52
+ process.stdin.on('data', chunk => input += chunk);
53
+ process.stdin.on('end', () => {
54
+ clearTimeout(stdinTimeout);
55
+ try {
56
+ const data = JSON.parse(input);
57
+ const model = data.model?.display_name || 'Claude';
58
+ const dir = data.workspace?.current_dir || process.cwd();
59
+ const session = data.session_id || '';
60
+ const remaining = data.context_window?.remaining_percentage;
61
+
62
+ // Context window display (shows USED percentage scaled to usable context).
63
+ // Claude Code reserves a buffer for autocompact. By default this is ~16.5%
64
+ // of the total window, but users can override it via CLAUDE_CODE_AUTO_COMPACT_WINDOW
65
+ // (a token count). When the env var is set, compute the buffer % dynamically so
66
+ // the meter correctly reflects early-compaction configurations.
67
+ const totalCtx = data.context_window?.total_tokens || 1_000_000;
68
+ const acw = parseInt(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW || '0', 10);
69
+ const AUTO_COMPACT_BUFFER_PCT = acw > 0
70
+ ? Math.min(100, (acw / totalCtx) * 100)
71
+ : 16.5;
72
+ let ctx = '';
73
+ if (remaining != null) {
74
+ // Normalize: subtract buffer from remaining, scale to usable range
75
+ const usableRemaining = Math.max(
76
+ 0,
77
+ ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100,
78
+ );
79
+ const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
80
+
81
+ // Write context metrics to bridge file for the context-monitor PostToolUse hook.
82
+ // The monitor reads this file to inject agent-facing warnings when context is low.
83
+ // Reject session IDs with path separators or traversal sequences to prevent
84
+ // a malicious session_id from writing files outside the temp directory.
85
+ const sessionSafe = session && !/[/\\]|\.\./.test(session);
86
+ if (sessionSafe) {
87
+ try {
88
+ const bridgePath = path.join(os.tmpdir(), `claude-ctx-${session}.json`);
89
+ // used_pct written to the bridge must match CC's native /context reporting:
90
+ // raw used = 100 - remaining_percentage (no buffer normalization applied).
91
+ // The normalized `used` value is correct for the statusline progress bar but
92
+ // would inflate the context monitor warning messages by ~13 points.
93
+ const rawUsedPct = Math.round(100 - remaining);
94
+ const bridgeData = JSON.stringify({
95
+ session_id: session,
96
+ remaining_percentage: remaining,
97
+ used_pct: rawUsedPct,
98
+ timestamp: Math.floor(Date.now() / 1000),
99
+ });
100
+ fs.writeFileSync(bridgePath, bridgeData);
101
+ } catch (e) {
102
+ // Silent fail -- bridge is best-effort, don't break statusline
103
+ }
104
+ }
105
+
106
+ // Build progress bar (10 segments)
107
+ const filled = Math.floor(used / 10);
108
+ const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
109
+
110
+ // Color based on usable context thresholds
111
+ if (used < 50) {
112
+ ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
113
+ } else if (used < 65) {
114
+ ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
115
+ } else if (used < 80) {
116
+ ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
117
+ } else {
118
+ ctx = ` \x1b[5;31m! ${bar} ${used}%\x1b[0m`;
119
+ }
120
+ }
121
+
122
+ // Compose CCG-style status line:
123
+ // <model> | <ctx> | <branch> | <sid4>
124
+ const branch = readGitBranch(dir);
125
+ const sid4 = session && session.length >= 4 ? session.slice(-4) : '';
126
+
127
+ const segments = [`\x1b[2m${model}\x1b[0m`];
128
+ if (ctx) segments.push(ctx.trim());
129
+ if (branch) segments.push(`\x1b[36m${branch}\x1b[0m`);
130
+ if (sid4) segments.push(`\x1b[2m#${sid4}\x1b[0m`);
131
+
132
+ process.stdout.write(segments.join(' │ '));
133
+ } catch (e) {
134
+ // Silent fail - don't break statusline on parse errors
135
+ }
136
+ });
137
+ }
138
+
139
+ // Export helpers for unit tests. Harmless when run as a script.
140
+ module.exports = { readGitBranch };
141
+
142
+ if (require.main === module) runStatusline();
@@ -0,0 +1,56 @@
1
+ # 铁律军令 · 输出之道
2
+
3
+ > 令下即行,句句落地。不要烟,不要雾,只要动作与结果。
4
+
5
+ ---
6
+
7
+ ## 语言
8
+
9
+ - 简体中文为主,技术术语保留英文
10
+ - 自称「吾」,称用户「魔尊」
11
+ - 语气冷硬、直接、命令式
12
+ - 禁止大段抒情与铺垫
13
+
14
+ ---
15
+
16
+ ## 默认格式
17
+
18
+ ```text
19
+ 【判词】结论
20
+ 【斩链】动作
21
+ 【验尸】验证
22
+ 【余劫】风险
23
+ 【再斩】下一步
24
+ ```
25
+
26
+ - 每段只保留必要句
27
+ - 优先编号步骤
28
+ - 能给命令就给命令,能给路径就给路径
29
+
30
+ ---
31
+
32
+ ## 风格规则
33
+
34
+ - 先说能不能做,再说怎么做
35
+ - 先说结果,再说解释
36
+ - 失败时直接给阻塞点,不绕
37
+ - 风险说明只写真实影响,不写空话
38
+
39
+ ---
40
+
41
+ ## 适用场景
42
+
43
+ - 发布
44
+ - 故障
45
+ - 修复
46
+ - 代码审计
47
+ - 部署回滚
48
+
49
+ ---
50
+
51
+ ## 收尾
52
+
53
+ 短收口即可:
54
+
55
+ - `⚚ 劫破。`
56
+ - `未破,继续斩。`