@polymorphism-tech/morph-spec 7.9.0 → 7.9.2

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 (171) hide show
  1. package/README.md +4 -4
  2. package/bin/validate.js +379 -379
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +6 -2
  5. package/docs/QUICKSTART.md +4 -4
  6. package/framework/CLAUDE.md +1 -1
  7. package/framework/MORPH.md +2 -2
  8. package/framework/commands/commit.md +166 -166
  9. package/framework/commands/morph-infra.md +215 -215
  10. package/framework/commands/morph-preflight.md +205 -205
  11. package/framework/commands/morph-troubleshoot.md +124 -124
  12. package/framework/hooks/claude-code/statusline.py +679 -679
  13. package/framework/hooks/claude-code/statusline.sh +11 -11
  14. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  15. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  16. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  17. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  18. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  19. package/framework/hooks/shared/activity-logger.js +160 -160
  20. package/framework/hooks/shared/hook-response.js +45 -45
  21. package/framework/hooks/shared/payload-utils.js +78 -78
  22. package/framework/hooks/shared/stdin-reader.js +26 -26
  23. package/framework/rubrics/backend.json +41 -41
  24. package/framework/rules/ai-agents-standards.md +45 -45
  25. package/framework/rules/dotnet-templates.md +86 -86
  26. package/framework/rules/frontend-standards.md +13 -13
  27. package/framework/rules/infrastructure-standards.md +27 -27
  28. package/framework/rules/neon-standards.md +25 -25
  29. package/framework/rules/nextjs-standards.md +20 -20
  30. package/framework/rules/nextjs-templates.md +40 -40
  31. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  32. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  33. package/framework/skills/core/morph-apply/SKILL.md +13 -8
  34. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  35. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  36. package/framework/skills/core/morph-implement/SKILL.md +13 -8
  37. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  38. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  39. package/framework/skills/core/morph-review/SKILL.md +10 -7
  40. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  41. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  42. package/framework/standards/ai-agents/agent-session.md +160 -160
  43. package/framework/standards/ai-agents/batching.md +193 -193
  44. package/framework/standards/ai-agents/context-providers.md +188 -188
  45. package/framework/standards/ai-agents/mcp-server.md +250 -250
  46. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  47. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  48. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  49. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  50. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  51. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  52. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  53. package/framework/standards/ai-agents/production.md +275 -275
  54. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  55. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  56. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  57. package/framework/standards/ai-agents/structured-output.md +249 -249
  58. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  59. package/framework/templates/IDE-SNIPPETS.md +262 -262
  60. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  61. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  62. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  63. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  64. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  65. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  66. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  67. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  68. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  69. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  70. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  71. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  72. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  73. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  74. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  75. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  76. package/framework/templates/code/typescript/contracts.ts +167 -167
  77. package/framework/templates/docs/clarifications.md +253 -253
  78. package/framework/templates/docs/schema-analysis.md +119 -119
  79. package/framework/templates/docs/ui-components.md +52 -52
  80. package/framework/templates/docs/ui-design-system.md +76 -76
  81. package/framework/templates/docs/ui-flows.md +167 -167
  82. package/framework/templates/docs/ui-mockups.md +98 -98
  83. package/framework/templates/feature/decisions.md +122 -122
  84. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  85. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  86. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  87. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  88. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  89. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  90. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  91. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  92. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  93. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  94. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  95. package/framework/templates/infrastructure/github/README.md +56 -56
  96. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  97. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  98. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  99. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  100. package/framework/templates/integrations/asaas-client.cs +387 -387
  101. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  102. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  103. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  104. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -113
  105. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  106. package/framework/templates/project/validate.js +120 -120
  107. package/framework/templates/state.template.json +17 -17
  108. package/framework/templates/ui/design-system.css +226 -226
  109. package/package.json +2 -1
  110. package/src/commands/dashboard/dashboard.js +53 -53
  111. package/src/commands/index.js +13 -13
  112. package/src/commands/mcp/mcp-setup.js +519 -519
  113. package/src/commands/project/checkpoint.js +209 -209
  114. package/src/commands/project/doctor.js +1 -1
  115. package/src/commands/project/update.js +4 -1
  116. package/src/core/state/phase-state-machine.js +197 -197
  117. package/src/core/templates/template-renderer.js +477 -477
  118. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  119. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  120. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  121. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  122. package/src/dashboard/app/main.jsx +13 -13
  123. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  124. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  125. package/src/dashboard/app/pages/LiveState.jsx +239 -239
  126. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  127. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  128. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  129. package/src/dashboard/app/pages/Standards.jsx +332 -332
  130. package/src/dashboard/app/styles/index.css +47 -47
  131. package/src/dashboard/index.html +12 -12
  132. package/src/dashboard/package.json +24 -24
  133. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  134. package/src/dashboard/scanner/phases-scanner.js +15 -15
  135. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  136. package/src/dashboard/scanner/rules-scanner.js +37 -37
  137. package/src/dashboard/scanner/skills-scanner.js +43 -43
  138. package/src/dashboard/scanner/squads-scanner.js +13 -13
  139. package/src/dashboard/scanner/standards-scanner.js +26 -26
  140. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  141. package/src/dashboard/vite.config.js +24 -24
  142. package/src/lib/agents/metrics-aggregator.js +56 -56
  143. package/src/lib/agents/metrics-calculator.js +125 -125
  144. package/src/lib/agents/output-collector.js +73 -73
  145. package/src/lib/detectors/claude-config-detector.js +152 -152
  146. package/src/lib/detectors/design-system-detector.js +231 -231
  147. package/src/lib/detectors/index.js +217 -217
  148. package/src/lib/generators/architecture-scanner.js +639 -639
  149. package/src/lib/generators/design-system-generator.js +508 -508
  150. package/src/lib/generators/index.js +13 -13
  151. package/src/lib/generators/settings-generator.js +209 -209
  152. package/src/lib/hooks/hook-executor.js +167 -167
  153. package/src/lib/installers/mcp-installer.js +719 -719
  154. package/src/lib/tasks/task-classifier.js +30 -30
  155. package/src/lib/tasks/task-parser.js +209 -209
  156. package/src/lib/tasks/test-runner.js +81 -81
  157. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  158. package/src/lib/validators/checkpoint-persistence.js +39 -39
  159. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  160. package/src/lib/validators/css/css-validator.js +340 -340
  161. package/src/lib/validators/packages/package-validator.js +347 -347
  162. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  163. package/src/lib/validators/shared/issue-counter.js +14 -14
  164. package/src/lib/validators/validation-runner.js +349 -349
  165. package/src/scripts/setup-infra.js +385 -385
  166. package/src/utils/agent-id-mapper.js +11 -11
  167. package/src/utils/claude-settings-manager.js +300 -300
  168. package/src/utils/skills-installer.js +174 -174
  169. package/src/utils/version-checker.js +2 -2
  170. package/framework/templates/.idea/morph-templates.xml +0 -92
  171. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
@@ -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
+ }