@polymorphism-tech/morph-spec 7.7.3 → 7.8.1

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 (187) hide show
  1. package/README.md +2 -2
  2. package/bin/validate.js +379 -379
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +1 -1
  7. package/framework/MORPH.md +3 -2
  8. package/framework/agents.json +1 -1
  9. package/framework/commands/commit.md +166 -166
  10. package/framework/commands/morph-infra.md +215 -215
  11. package/framework/commands/morph-preflight.md +205 -205
  12. package/framework/commands/morph-troubleshoot.md +124 -124
  13. package/framework/evals/scenarios/dotnet-crud.yaml +1 -1
  14. package/framework/hooks/claude-code/statusline.py +679 -679
  15. package/framework/hooks/claude-code/statusline.sh +11 -11
  16. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  17. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  18. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  19. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  20. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  21. package/framework/hooks/shared/activity-logger.js +160 -160
  22. package/framework/hooks/shared/hook-response.js +45 -45
  23. package/framework/hooks/shared/payload-utils.js +78 -78
  24. package/framework/hooks/shared/stdin-reader.js +26 -26
  25. package/framework/rubrics/backend.json +41 -41
  26. package/framework/rules/ai-agents-standards.md +45 -45
  27. package/framework/rules/dotnet-templates.md +86 -87
  28. package/framework/rules/frontend-standards.md +13 -13
  29. package/framework/rules/infrastructure-standards.md +27 -27
  30. package/framework/rules/neon-standards.md +25 -25
  31. package/framework/rules/nextjs-standards.md +20 -20
  32. package/framework/rules/nextjs-templates.md +40 -40
  33. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  34. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  35. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  36. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -5
  37. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  38. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  39. package/framework/skills/core/morph-plan/SKILL.md +11 -6
  40. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  41. package/framework/skills/core/morph-review/SKILL.md +13 -7
  42. package/framework/skills/core/morph-stack-scan/SKILL.md +5 -5
  43. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  44. package/framework/standards/STANDARDS.json +1 -1
  45. package/framework/standards/ai-agents/agent-session.md +160 -160
  46. package/framework/standards/ai-agents/batching.md +193 -193
  47. package/framework/standards/ai-agents/context-providers.md +188 -188
  48. package/framework/standards/ai-agents/mcp-server.md +250 -250
  49. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  50. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  51. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  52. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  53. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  54. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  55. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  56. package/framework/standards/ai-agents/production.md +275 -275
  57. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  58. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  59. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  60. package/framework/standards/ai-agents/structured-output.md +249 -249
  61. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  62. package/framework/standards/architecture/vertical-slice/vertical-slice.md +34 -35
  63. package/framework/standards/backend/api/validation.md +2 -2
  64. package/framework/standards/backend/dotnet/scrutor.md +1 -3
  65. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +42 -37
  66. package/framework/standards/data/nosql/cache/redis.md +2 -2
  67. package/framework/templates/CLAUDE.md.core.template +1 -1
  68. package/framework/templates/IDE-SNIPPETS.md +262 -262
  69. package/framework/templates/README.md +0 -1
  70. package/framework/templates/REGISTRY.json +0 -21
  71. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  72. package/framework/templates/code/dotnet/abstractions/handler.cs +0 -3
  73. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  74. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  75. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  76. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  77. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  78. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  79. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  80. package/framework/templates/code/dotnet/constants/api-tags.cs +1 -1
  81. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -282
  82. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  83. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  84. package/framework/templates/code/dotnet/feature/handler.cs.hbs +23 -24
  85. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  86. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  87. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  88. package/framework/templates/code/dotnet/jobs/job.cs +15 -15
  89. package/framework/templates/code/dotnet/test.cs +72 -101
  90. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  91. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  92. package/framework/templates/code/typescript/contracts.ts +167 -167
  93. package/framework/templates/docs/clarifications.md +253 -253
  94. package/framework/templates/docs/schema-analysis.md +119 -119
  95. package/framework/templates/docs/ui-components.md +52 -52
  96. package/framework/templates/docs/ui-design-system.md +76 -76
  97. package/framework/templates/docs/ui-flows.md +167 -167
  98. package/framework/templates/docs/ui-mockups.md +98 -98
  99. package/framework/templates/feature/decisions.md +122 -122
  100. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  101. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  102. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  103. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  104. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  105. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  106. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  107. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  108. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  109. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  110. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  111. package/framework/templates/infrastructure/github/README.md +56 -56
  112. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  113. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  114. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  115. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  116. package/framework/templates/integrations/asaas-client.cs +387 -387
  117. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  118. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  119. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  120. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -113
  121. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  122. package/framework/templates/project/validate.js +120 -120
  123. package/framework/templates/state.template.json +17 -17
  124. package/framework/templates/ui/design-system.css +226 -226
  125. package/package.json +1 -1
  126. package/src/commands/dashboard/dashboard.js +53 -53
  127. package/src/commands/index.js +13 -13
  128. package/src/commands/mcp/mcp-setup.js +519 -519
  129. package/src/commands/project/checkpoint.js +209 -209
  130. package/src/commands/project/update.js +7 -4
  131. package/src/core/state/phase-state-machine.js +197 -197
  132. package/src/core/templates/template-renderer.js +477 -477
  133. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  134. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  135. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  136. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  137. package/src/dashboard/app/main.jsx +13 -13
  138. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  139. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  140. package/src/dashboard/app/pages/LiveState.jsx +239 -239
  141. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  142. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  143. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  144. package/src/dashboard/app/pages/Standards.jsx +332 -332
  145. package/src/dashboard/app/styles/index.css +47 -47
  146. package/src/dashboard/index.html +12 -12
  147. package/src/dashboard/package.json +24 -24
  148. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  149. package/src/dashboard/scanner/phases-scanner.js +15 -15
  150. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  151. package/src/dashboard/scanner/rules-scanner.js +37 -37
  152. package/src/dashboard/scanner/skills-scanner.js +43 -43
  153. package/src/dashboard/scanner/squads-scanner.js +13 -13
  154. package/src/dashboard/scanner/standards-scanner.js +26 -26
  155. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  156. package/src/dashboard/vite.config.js +24 -24
  157. package/src/lib/agents/metrics-aggregator.js +56 -56
  158. package/src/lib/agents/metrics-calculator.js +125 -125
  159. package/src/lib/agents/output-collector.js +73 -73
  160. package/src/lib/detectors/claude-config-detector.js +152 -152
  161. package/src/lib/detectors/design-system-detector.js +231 -231
  162. package/src/lib/detectors/index.js +217 -217
  163. package/src/lib/generators/architecture-scanner.js +639 -639
  164. package/src/lib/generators/design-system-generator.js +508 -508
  165. package/src/lib/generators/index.js +13 -13
  166. package/src/lib/generators/settings-generator.js +209 -209
  167. package/src/lib/hooks/hook-executor.js +167 -167
  168. package/src/lib/installers/mcp-installer.js +719 -719
  169. package/src/lib/tasks/task-classifier.js +30 -30
  170. package/src/lib/tasks/task-parser.js +209 -209
  171. package/src/lib/tasks/test-runner.js +81 -81
  172. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  173. package/src/lib/validators/checkpoint-persistence.js +39 -39
  174. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  175. package/src/lib/validators/css/css-validator.js +340 -340
  176. package/src/lib/validators/packages/package-validator.js +347 -347
  177. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  178. package/src/lib/validators/shared/issue-counter.js +14 -14
  179. package/src/lib/validators/validation-runner.js +349 -349
  180. package/src/scripts/setup-infra.js +383 -383
  181. package/src/utils/agent-id-mapper.js +11 -11
  182. package/src/utils/banner.js +52 -43
  183. package/src/utils/claude-settings-manager.js +300 -300
  184. package/src/utils/skills-installer.js +172 -172
  185. package/framework/templates/.idea/morph-templates.xml +0 -92
  186. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  187. package/framework/templates/code/dotnet/backend/repository.cs +0 -141
@@ -1,160 +1,160 @@
1
- /**
2
- * Activity Logger — Session-scoped hook event log
3
- *
4
- * Writes hook activity to .morph/logs/activity.json for live consumption
5
- * by `morph-spec monitor`.
6
- *
7
- * Design constraints:
8
- * - Synchronous only (hooks run in process, no async overhead)
9
- * - Fail-silent (never throws — hooks must not be blocked by logger)
10
- * - ≤5ms per call (no retries, no network)
11
- * - Windows-safe paths via path.join
12
- */
13
-
14
- import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
15
- import { join } from 'path';
16
-
17
- const LOGS_DIR = '.morph/logs';
18
- const ACTIVITY_FILE = join(LOGS_DIR, 'activity.json');
19
-
20
- function getActivityPath(projectPath) {
21
- return join(projectPath || process.cwd(), ACTIVITY_FILE);
22
- }
23
-
24
- function ensureLogsDir(projectPath) {
25
- const dir = join(projectPath || process.cwd(), LOGS_DIR);
26
- if (!existsSync(dir)) {
27
- mkdirSync(dir, { recursive: true });
28
- }
29
- }
30
-
31
- function readRaw(activityPath) {
32
- try {
33
- if (!existsSync(activityPath)) return null;
34
- return JSON.parse(readFileSync(activityPath, 'utf-8'));
35
- } catch {
36
- return null;
37
- }
38
- }
39
-
40
- /**
41
- * Log a hook firing event to the session activity log.
42
- *
43
- * @param {string} name - Hook name (e.g. 'dispatch.js', 'enrich-prompt')
44
- * @param {string} event - Claude Code event (e.g. 'PostToolUse', 'UserPromptSubmit')
45
- * @param {string} result - Outcome summary (e.g. 'ok', 'blocked', 'checkpoint_saved')
46
- * @param {string} [projectPath] - Project root (defaults to cwd)
47
- */
48
- export function logHookActivity(name, event, result, projectPath) {
49
- try {
50
- const activityPath = getActivityPath(projectPath);
51
- ensureLogsDir(projectPath);
52
-
53
- const now = new Date();
54
- const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
55
-
56
- const data = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], skills: [] };
57
-
58
- data.hooks = data.hooks || [];
59
- data.hooks.push({ name, event, result, ts });
60
-
61
- writeFileSync(activityPath, JSON.stringify(data, null, 2), 'utf-8');
62
- } catch {
63
- // Fail-silent — never block hooks
64
- }
65
- }
66
-
67
-
68
- /**
69
- * Read the current activity log.
70
- *
71
- * @param {string} [projectPath]
72
- * @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, skills: Array }|null}
73
- */
74
- export function readActivity(projectPath) {
75
- try {
76
- return readRaw(getActivityPath(projectPath));
77
- } catch {
78
- return null;
79
- }
80
- }
81
-
82
- /**
83
- * Reset the activity log for a new session.
84
- * Called from SessionStart hook to clear stale data.
85
- *
86
- * @param {string} sessionId - ISO timestamp of new session
87
- * @param {string} feature - Active feature name (empty string if none)
88
- * @param {string} phase - Current phase (empty string if none)
89
- * @param {string} [projectPath]
90
- */
91
- export function resetActivity(sessionId, feature, phase, projectPath) {
92
- try {
93
- ensureLogsDir(projectPath);
94
- const data = {
95
- sessionId: sessionId || new Date().toISOString(),
96
- feature: feature || '',
97
- phase: phase || '',
98
- hooks: [],
99
- skills: []
100
- };
101
- writeFileSync(getActivityPath(projectPath), JSON.stringify(data, null, 2), 'utf-8');
102
- } catch {
103
- // Fail-silent
104
- }
105
- }
106
-
107
- /**
108
- * Log a structured event to the session activity log.
109
- *
110
- * @param {string} type - Event type (e.g., 'task_start', 'loop_detected')
111
- * @param {Object} data - Event data payload
112
- * @param {string} [projectPath] - Project root (defaults to cwd)
113
- */
114
- export function logEvent(type, data, projectPath) {
115
- try {
116
- const activityPath = getActivityPath(projectPath);
117
- ensureLogsDir(projectPath);
118
-
119
- const now = new Date();
120
- const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
121
-
122
- const activity = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], events: [] };
123
- activity.events = activity.events || [];
124
- activity.events.push({ type, data, ts });
125
-
126
- writeFileSync(activityPath, JSON.stringify(activity, null, 2), 'utf-8');
127
- } catch {
128
- // Fail-silent
129
- }
130
- }
131
-
132
- /**
133
- * Archive current activity.json to sessions/ directory for historical logging.
134
- * Called at the start of a new session.
135
- *
136
- * @param {string} [projectPath] - Project root (defaults to cwd)
137
- */
138
- export function archiveSession(projectPath) {
139
- try {
140
- const activityPath = getActivityPath(projectPath);
141
- if (!existsSync(activityPath)) return;
142
-
143
- const data = readRaw(activityPath);
144
- if (!data) return;
145
-
146
- // Only archive if there are meaningful events
147
- if ((!data.hooks || data.hooks.length === 0) && (!data.events || data.events.length === 0)) return;
148
-
149
- const sessionsDir = join(projectPath || process.cwd(), LOGS_DIR, 'sessions');
150
- mkdirSync(sessionsDir, { recursive: true });
151
-
152
- const sessionId = (data.sessionId || new Date().toISOString()).replace(/[:.]/g, '-');
153
- const archivePath = join(sessionsDir, `${sessionId}.json`);
154
- writeFileSync(archivePath, JSON.stringify(data, null, 2), 'utf-8');
155
-
156
- unlinkSync(activityPath);
157
- } catch {
158
- // Fail-silent
159
- }
160
- }
1
+ /**
2
+ * Activity Logger — Session-scoped hook event log
3
+ *
4
+ * Writes hook activity to .morph/logs/activity.json for live consumption
5
+ * by `morph-spec monitor`.
6
+ *
7
+ * Design constraints:
8
+ * - Synchronous only (hooks run in process, no async overhead)
9
+ * - Fail-silent (never throws — hooks must not be blocked by logger)
10
+ * - ≤5ms per call (no retries, no network)
11
+ * - Windows-safe paths via path.join
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
15
+ import { join } from 'path';
16
+
17
+ const LOGS_DIR = '.morph/logs';
18
+ const ACTIVITY_FILE = join(LOGS_DIR, 'activity.json');
19
+
20
+ function getActivityPath(projectPath) {
21
+ return join(projectPath || process.cwd(), ACTIVITY_FILE);
22
+ }
23
+
24
+ function ensureLogsDir(projectPath) {
25
+ const dir = join(projectPath || process.cwd(), LOGS_DIR);
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ }
30
+
31
+ function readRaw(activityPath) {
32
+ try {
33
+ if (!existsSync(activityPath)) return null;
34
+ return JSON.parse(readFileSync(activityPath, 'utf-8'));
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Log a hook firing event to the session activity log.
42
+ *
43
+ * @param {string} name - Hook name (e.g. 'dispatch.js', 'enrich-prompt')
44
+ * @param {string} event - Claude Code event (e.g. 'PostToolUse', 'UserPromptSubmit')
45
+ * @param {string} result - Outcome summary (e.g. 'ok', 'blocked', 'checkpoint_saved')
46
+ * @param {string} [projectPath] - Project root (defaults to cwd)
47
+ */
48
+ export function logHookActivity(name, event, result, projectPath) {
49
+ try {
50
+ const activityPath = getActivityPath(projectPath);
51
+ ensureLogsDir(projectPath);
52
+
53
+ const now = new Date();
54
+ const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
55
+
56
+ const data = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], skills: [] };
57
+
58
+ data.hooks = data.hooks || [];
59
+ data.hooks.push({ name, event, result, ts });
60
+
61
+ writeFileSync(activityPath, JSON.stringify(data, null, 2), 'utf-8');
62
+ } catch {
63
+ // Fail-silent — never block hooks
64
+ }
65
+ }
66
+
67
+
68
+ /**
69
+ * Read the current activity log.
70
+ *
71
+ * @param {string} [projectPath]
72
+ * @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, skills: Array }|null}
73
+ */
74
+ export function readActivity(projectPath) {
75
+ try {
76
+ return readRaw(getActivityPath(projectPath));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Reset the activity log for a new session.
84
+ * Called from SessionStart hook to clear stale data.
85
+ *
86
+ * @param {string} sessionId - ISO timestamp of new session
87
+ * @param {string} feature - Active feature name (empty string if none)
88
+ * @param {string} phase - Current phase (empty string if none)
89
+ * @param {string} [projectPath]
90
+ */
91
+ export function resetActivity(sessionId, feature, phase, projectPath) {
92
+ try {
93
+ ensureLogsDir(projectPath);
94
+ const data = {
95
+ sessionId: sessionId || new Date().toISOString(),
96
+ feature: feature || '',
97
+ phase: phase || '',
98
+ hooks: [],
99
+ skills: []
100
+ };
101
+ writeFileSync(getActivityPath(projectPath), JSON.stringify(data, null, 2), 'utf-8');
102
+ } catch {
103
+ // Fail-silent
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Log a structured event to the session activity log.
109
+ *
110
+ * @param {string} type - Event type (e.g., 'task_start', 'loop_detected')
111
+ * @param {Object} data - Event data payload
112
+ * @param {string} [projectPath] - Project root (defaults to cwd)
113
+ */
114
+ export function logEvent(type, data, projectPath) {
115
+ try {
116
+ const activityPath = getActivityPath(projectPath);
117
+ ensureLogsDir(projectPath);
118
+
119
+ const now = new Date();
120
+ const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
121
+
122
+ const activity = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], events: [] };
123
+ activity.events = activity.events || [];
124
+ activity.events.push({ type, data, ts });
125
+
126
+ writeFileSync(activityPath, JSON.stringify(activity, null, 2), 'utf-8');
127
+ } catch {
128
+ // Fail-silent
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Archive current activity.json to sessions/ directory for historical logging.
134
+ * Called at the start of a new session.
135
+ *
136
+ * @param {string} [projectPath] - Project root (defaults to cwd)
137
+ */
138
+ export function archiveSession(projectPath) {
139
+ try {
140
+ const activityPath = getActivityPath(projectPath);
141
+ if (!existsSync(activityPath)) return;
142
+
143
+ const data = readRaw(activityPath);
144
+ if (!data) return;
145
+
146
+ // Only archive if there are meaningful events
147
+ if ((!data.hooks || data.hooks.length === 0) && (!data.events || data.events.length === 0)) return;
148
+
149
+ const sessionsDir = join(projectPath || process.cwd(), LOGS_DIR, 'sessions');
150
+ mkdirSync(sessionsDir, { recursive: true });
151
+
152
+ const sessionId = (data.sessionId || new Date().toISOString()).replace(/[:.]/g, '-');
153
+ const archivePath = join(sessionsDir, `${sessionId}.json`);
154
+ writeFileSync(archivePath, JSON.stringify(data, null, 2), 'utf-8');
155
+
156
+ unlinkSync(activityPath);
157
+ } catch {
158
+ // Fail-silent
159
+ }
160
+ }
@@ -1,45 +1,45 @@
1
- /**
2
- * Shared hook response builders for Claude Code hooks.
3
- *
4
- * Claude Code hooks communicate via JSON to stdout.
5
- * - PreToolUse: { "decision": "block"|"approve", "reason": "..." }
6
- * - SessionStart/Stop/etc: { "additionalContext": "..." }
7
- */
8
-
9
- /**
10
- * Block a tool use with a reason.
11
- * @param {string} reason - Human-readable reason for blocking
12
- */
13
- export function block(reason) {
14
- console.log(JSON.stringify({ decision: 'block', reason }));
15
- process.exit(0);
16
- }
17
-
18
- /**
19
- * Approve a tool use, optionally injecting context.
20
- * @param {string} [context] - Additional context to inject
21
- */
22
- export function approve(context) {
23
- if (context) {
24
- console.log(JSON.stringify({ decision: 'approve', additionalContext: context }));
25
- }
26
- // No output = implicit approve
27
- process.exit(0);
28
- }
29
-
30
- /**
31
- * Inject additional context (for SessionStart, Stop, UserPromptSubmit, etc.).
32
- * @param {string} text - Context text to inject
33
- * @param {Object} [extra] - Additional fields to include in response
34
- */
35
- export function injectContext(text, extra = {}) {
36
- console.log(JSON.stringify({ additionalContext: text, ...extra }));
37
- process.exit(0);
38
- }
39
-
40
- /**
41
- * Silent exit — no output, no blocking.
42
- */
43
- export function pass() {
44
- process.exit(0);
45
- }
1
+ /**
2
+ * Shared hook response builders for Claude Code hooks.
3
+ *
4
+ * Claude Code hooks communicate via JSON to stdout.
5
+ * - PreToolUse: { "decision": "block"|"approve", "reason": "..." }
6
+ * - SessionStart/Stop/etc: { "additionalContext": "..." }
7
+ */
8
+
9
+ /**
10
+ * Block a tool use with a reason.
11
+ * @param {string} reason - Human-readable reason for blocking
12
+ */
13
+ export function block(reason) {
14
+ console.log(JSON.stringify({ decision: 'block', reason }));
15
+ process.exit(0);
16
+ }
17
+
18
+ /**
19
+ * Approve a tool use, optionally injecting context.
20
+ * @param {string} [context] - Additional context to inject
21
+ */
22
+ export function approve(context) {
23
+ if (context) {
24
+ console.log(JSON.stringify({ decision: 'approve', additionalContext: context }));
25
+ }
26
+ // No output = implicit approve
27
+ process.exit(0);
28
+ }
29
+
30
+ /**
31
+ * Inject additional context (for SessionStart, Stop, UserPromptSubmit, etc.).
32
+ * @param {string} text - Context text to inject
33
+ * @param {Object} [extra] - Additional fields to include in response
34
+ */
35
+ export function injectContext(text, extra = {}) {
36
+ console.log(JSON.stringify({ additionalContext: text, ...extra }));
37
+ process.exit(0);
38
+ }
39
+
40
+ /**
41
+ * Silent exit — no output, no blocking.
42
+ */
43
+ export function pass() {
44
+ process.exit(0);
45
+ }
@@ -1,78 +1,78 @@
1
- /**
2
- * Shared payload utilities for Claude Code Write/Edit hooks.
3
- *
4
- * Extracts file path and content from tool_input payloads,
5
- * normalizing between Write (content, file_path) and Edit (new_string, old_string, file_path).
6
- */
7
-
8
- /**
9
- * Get the file path from a hook payload, normalizing backslashes.
10
- * @param {Object} payload - Hook payload from stdin
11
- * @returns {string} Normalized file path or empty string
12
- */
13
- export function getFilePath(payload) {
14
- const raw = payload?.tool_input?.file_path || payload?.tool_input?.path || '';
15
- return raw.replace(/\\/g, '/');
16
- }
17
-
18
- /**
19
- * Get the content being written or edited.
20
- * For Write: returns `content` (full file content).
21
- * For Edit: returns `new_string` (replacement text).
22
- * @param {Object} payload - Hook payload from stdin
23
- * @returns {string} Content string or empty string
24
- */
25
- export function getContentToValidate(payload) {
26
- const input = payload?.tool_input;
27
- if (!input) return '';
28
- // Write tool uses 'content', Edit tool uses 'new_string'
29
- return input.content || input.new_string || '';
30
- }
31
-
32
- /**
33
- * Check if this is a Write operation (full file content available).
34
- * @param {Object} payload - Hook payload from stdin
35
- * @returns {boolean} True if Write (content exists), false if Edit or unknown
36
- */
37
- export function isWriteOperation(payload) {
38
- return typeof payload?.tool_input?.content === 'string';
39
- }
40
-
41
- /**
42
- * Get the tool name from a hook payload.
43
- * @param {Object} payload - Hook payload from stdin
44
- * @returns {string} Tool name or 'unknown'
45
- */
46
- export function getToolName(payload) {
47
- return payload?.tool_name || 'unknown';
48
- }
49
-
50
- /**
51
- * Build a normalized signature for loop detection.
52
- * - Edit/Write: "Edit:normalized/file/path"
53
- * - Bash: "Bash:first two words" (e.g., "Bash:npm test")
54
- * - Other: just the tool name
55
- *
56
- * @param {Object} payload - Hook payload from stdin
57
- * @returns {string} Signature string
58
- */
59
- export function getCommandSignature(payload) {
60
- const tool = getToolName(payload);
61
- if (tool === 'unknown') return 'unknown';
62
-
63
- const input = payload?.tool_input;
64
- if (!input) return tool;
65
-
66
- if (tool === 'Edit' || tool === 'Write') {
67
- const path = (input.file_path || input.path || '').replace(/\\/g, '/');
68
- return path ? `${tool}:${path}` : tool;
69
- }
70
-
71
- if (tool === 'Bash') {
72
- const cmd = (input.command || '').trim();
73
- const words = cmd.split(/\s+/).slice(0, 2).join(' ');
74
- return words ? `Bash:${words}` : 'Bash';
75
- }
76
-
77
- return tool;
78
- }
1
+ /**
2
+ * Shared payload utilities for Claude Code Write/Edit hooks.
3
+ *
4
+ * Extracts file path and content from tool_input payloads,
5
+ * normalizing between Write (content, file_path) and Edit (new_string, old_string, file_path).
6
+ */
7
+
8
+ /**
9
+ * Get the file path from a hook payload, normalizing backslashes.
10
+ * @param {Object} payload - Hook payload from stdin
11
+ * @returns {string} Normalized file path or empty string
12
+ */
13
+ export function getFilePath(payload) {
14
+ const raw = payload?.tool_input?.file_path || payload?.tool_input?.path || '';
15
+ return raw.replace(/\\/g, '/');
16
+ }
17
+
18
+ /**
19
+ * Get the content being written or edited.
20
+ * For Write: returns `content` (full file content).
21
+ * For Edit: returns `new_string` (replacement text).
22
+ * @param {Object} payload - Hook payload from stdin
23
+ * @returns {string} Content string or empty string
24
+ */
25
+ export function getContentToValidate(payload) {
26
+ const input = payload?.tool_input;
27
+ if (!input) return '';
28
+ // Write tool uses 'content', Edit tool uses 'new_string'
29
+ return input.content || input.new_string || '';
30
+ }
31
+
32
+ /**
33
+ * Check if this is a Write operation (full file content available).
34
+ * @param {Object} payload - Hook payload from stdin
35
+ * @returns {boolean} True if Write (content exists), false if Edit or unknown
36
+ */
37
+ export function isWriteOperation(payload) {
38
+ return typeof payload?.tool_input?.content === 'string';
39
+ }
40
+
41
+ /**
42
+ * Get the tool name from a hook payload.
43
+ * @param {Object} payload - Hook payload from stdin
44
+ * @returns {string} Tool name or 'unknown'
45
+ */
46
+ export function getToolName(payload) {
47
+ return payload?.tool_name || 'unknown';
48
+ }
49
+
50
+ /**
51
+ * Build a normalized signature for loop detection.
52
+ * - Edit/Write: "Edit:normalized/file/path"
53
+ * - Bash: "Bash:first two words" (e.g., "Bash:npm test")
54
+ * - Other: just the tool name
55
+ *
56
+ * @param {Object} payload - Hook payload from stdin
57
+ * @returns {string} Signature string
58
+ */
59
+ export function getCommandSignature(payload) {
60
+ const tool = getToolName(payload);
61
+ if (tool === 'unknown') return 'unknown';
62
+
63
+ const input = payload?.tool_input;
64
+ if (!input) return tool;
65
+
66
+ if (tool === 'Edit' || tool === 'Write') {
67
+ const path = (input.file_path || input.path || '').replace(/\\/g, '/');
68
+ return path ? `${tool}:${path}` : tool;
69
+ }
70
+
71
+ if (tool === 'Bash') {
72
+ const cmd = (input.command || '').trim();
73
+ const words = cmd.split(/\s+/).slice(0, 2).join(' ');
74
+ return words ? `Bash:${words}` : 'Bash';
75
+ }
76
+
77
+ return tool;
78
+ }
@@ -1,26 +1,26 @@
1
- /**
2
- * Shared stdin reader for Claude Code hooks.
3
- *
4
- * Reads JSON payload from stdin (provided by Claude Code to hooks).
5
- * Returns parsed object or null on failure.
6
- */
7
-
8
- /**
9
- * Read and parse JSON from stdin
10
- * @returns {Promise<Object|null>} Parsed JSON or null
11
- */
12
- export function readStdin() {
13
- return new Promise((resolve) => {
14
- let input = '';
15
- process.stdin.setEncoding('utf-8');
16
- process.stdin.on('data', (chunk) => { input += chunk; });
17
- process.stdin.on('end', () => {
18
- try {
19
- resolve(JSON.parse(input || '{}'));
20
- } catch {
21
- resolve(null);
22
- }
23
- });
24
- process.stdin.on('error', () => resolve(null));
25
- });
26
- }
1
+ /**
2
+ * Shared stdin reader for Claude Code hooks.
3
+ *
4
+ * Reads JSON payload from stdin (provided by Claude Code to hooks).
5
+ * Returns parsed object or null on failure.
6
+ */
7
+
8
+ /**
9
+ * Read and parse JSON from stdin
10
+ * @returns {Promise<Object|null>} Parsed JSON or null
11
+ */
12
+ export function readStdin() {
13
+ return new Promise((resolve) => {
14
+ let input = '';
15
+ process.stdin.setEncoding('utf-8');
16
+ process.stdin.on('data', (chunk) => { input += chunk; });
17
+ process.stdin.on('end', () => {
18
+ try {
19
+ resolve(JSON.parse(input || '{}'));
20
+ } catch {
21
+ resolve(null);
22
+ }
23
+ });
24
+ process.stdin.on('error', () => resolve(null));
25
+ });
26
+ }