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