@polymorphism-tech/morph-spec 7.9.3 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +31 -2
  3. package/bin/morph-spec.js +50 -2
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +38 -1
  7. package/docs/QUICKSTART.md +60 -1
  8. package/framework/CLAUDE.md +13 -2
  9. package/framework/MORPH.md +38 -2
  10. package/framework/agents.json +22 -3
  11. package/framework/commands/commit.md +166 -166
  12. package/framework/commands/morph-hotfix.md +39 -0
  13. package/framework/commands/morph-infra.md +215 -215
  14. package/framework/commands/morph-preflight.md +205 -205
  15. package/framework/commands/morph-troubleshoot.md +124 -124
  16. package/framework/hooks/README.md +25 -10
  17. package/framework/hooks/claude-code/core/gate-guard.js +4 -2
  18. package/framework/hooks/claude-code/core/loop-detect.js +2 -1
  19. package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
  20. package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
  21. package/framework/hooks/claude-code/core/validate-completion.js +1 -1
  22. package/framework/hooks/claude-code/core/worktree-context.js +160 -0
  23. package/framework/hooks/claude-code/statusline.py +679 -679
  24. package/framework/hooks/claude-code/statusline.sh +11 -11
  25. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  26. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  27. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  28. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  29. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  30. package/framework/hooks/shared/activity-logger.js +160 -160
  31. package/framework/hooks/shared/build-check.js +2 -2
  32. package/framework/hooks/shared/hook-response.js +17 -5
  33. package/framework/hooks/shared/payload-utils.js +42 -0
  34. package/framework/hooks/shared/state-reader.js +22 -2
  35. package/framework/hooks/shared/stdin-reader.js +26 -26
  36. package/framework/rubrics/backend.json +41 -41
  37. package/framework/rules/ai-agents-standards.md +45 -45
  38. package/framework/rules/dotnet-templates.md +86 -86
  39. package/framework/rules/frontend-standards.md +13 -13
  40. package/framework/rules/infrastructure-standards.md +27 -27
  41. package/framework/rules/neon-standards.md +25 -25
  42. package/framework/rules/nextjs-standards.md +20 -20
  43. package/framework/rules/nextjs-templates.md +40 -40
  44. package/framework/schemas/feature.schema.json +95 -1
  45. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  46. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  47. package/framework/skills/core/morph-apply/SKILL.md +39 -8
  48. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
  49. package/framework/skills/core/morph-eval/SKILL.md +6 -1
  50. package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
  51. package/framework/skills/core/morph-implement/SKILL.md +8 -4
  52. package/framework/skills/core/morph-plan/SKILL.md +5 -1
  53. package/framework/skills/core/morph-proposal/SKILL.md +24 -1
  54. package/framework/skills/core/morph-review/SKILL.md +3 -2
  55. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  56. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  57. package/framework/standards/STANDARDS.json +7 -2
  58. package/framework/standards/ai-agents/agent-session.md +160 -160
  59. package/framework/standards/ai-agents/batching.md +193 -193
  60. package/framework/standards/ai-agents/context-providers.md +188 -188
  61. package/framework/standards/ai-agents/mcp-server.md +250 -250
  62. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  63. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  64. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  65. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  66. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  67. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  68. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  69. package/framework/standards/ai-agents/production.md +275 -275
  70. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  71. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  72. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  73. package/framework/standards/ai-agents/structured-output.md +249 -249
  74. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  75. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
  76. package/framework/templates/.idea/morph-templates.xml +92 -0
  77. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  78. package/framework/templates/IDE-SNIPPETS.md +262 -262
  79. package/framework/templates/REGISTRY.json +0 -62
  80. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  81. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  82. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  83. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  84. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  85. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  86. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  87. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  88. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  89. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  90. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  91. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  92. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  93. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  96. package/framework/templates/code/typescript/contracts.ts +167 -167
  97. package/framework/templates/docs/clarifications.md +253 -253
  98. package/framework/templates/docs/schema-analysis.md +119 -119
  99. package/framework/templates/docs/ui-components.md +52 -52
  100. package/framework/templates/docs/ui-design-system.md +76 -76
  101. package/framework/templates/docs/ui-flows.md +167 -167
  102. package/framework/templates/docs/ui-mockups.md +98 -98
  103. package/framework/templates/feature/decisions.md +122 -122
  104. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  105. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  106. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  107. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  108. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  109. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  110. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  111. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  112. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  113. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  114. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  115. package/framework/templates/infrastructure/github/README.md +56 -56
  116. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  117. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  118. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  119. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  120. package/framework/templates/integrations/asaas-client.cs +387 -387
  121. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  122. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  123. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  124. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  125. package/framework/templates/project/validate.js +120 -120
  126. package/framework/templates/state.template.json +17 -17
  127. package/framework/templates/ui/design-system.css +226 -226
  128. package/package.json +1 -1
  129. package/src/commands/dashboard/dashboard.js +53 -53
  130. package/src/commands/index.js +13 -13
  131. package/src/commands/mcp/mcp-setup.js +519 -519
  132. package/src/commands/project/checkpoint.js +209 -209
  133. package/src/commands/project/doctor.js +4 -2
  134. package/src/commands/project/init.js +46 -2
  135. package/src/commands/project/update.js +5 -3
  136. package/src/commands/project/worktree.js +1005 -46
  137. package/src/commands/state/advance.js +17 -2
  138. package/src/commands/state/approve.js +18 -1
  139. package/src/commands/state/create.js +158 -14
  140. package/src/commands/state/retype.js +126 -0
  141. package/src/commands/state/score.js +4 -0
  142. package/src/commands/tasks/dag.js +32 -0
  143. package/src/commands/validation/index.js +1 -0
  144. package/src/commands/validation/validate-feature.js +33 -5
  145. package/src/commands/validation/verify.js +190 -0
  146. package/src/core/state/phase-state-machine.js +197 -197
  147. package/src/core/state/state-manager.js +35 -10
  148. package/src/core/templates/template-renderer.js +477 -477
  149. package/src/core/work-types.js +79 -0
  150. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  151. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  152. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  153. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  154. package/src/dashboard/app/main.jsx +13 -13
  155. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  156. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  157. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  158. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  159. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  160. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  161. package/src/dashboard/app/pages/Standards.jsx +332 -332
  162. package/src/dashboard/app/styles/index.css +47 -47
  163. package/src/dashboard/index.html +12 -12
  164. package/src/dashboard/package.json +24 -24
  165. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  166. package/src/dashboard/scanner/phases-scanner.js +15 -15
  167. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  168. package/src/dashboard/scanner/rules-scanner.js +37 -37
  169. package/src/dashboard/scanner/skills-scanner.js +43 -43
  170. package/src/dashboard/scanner/squads-scanner.js +13 -13
  171. package/src/dashboard/scanner/standards-scanner.js +26 -26
  172. package/src/dashboard/scanner/state-scanner.js +42 -0
  173. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  174. package/src/dashboard/vite.config.js +24 -24
  175. package/src/lib/agents/metrics-aggregator.js +56 -56
  176. package/src/lib/agents/metrics-calculator.js +125 -125
  177. package/src/lib/agents/output-collector.js +73 -73
  178. package/src/lib/detectors/claude-config-detector.js +152 -152
  179. package/src/lib/detectors/design-system-detector.js +231 -231
  180. package/src/lib/detectors/index.js +217 -217
  181. package/src/lib/generators/architecture-scanner.js +639 -639
  182. package/src/lib/generators/design-system-generator.js +508 -508
  183. package/src/lib/generators/index.js +13 -13
  184. package/src/lib/generators/settings-generator.js +209 -209
  185. package/src/lib/hooks/hook-executor.js +167 -167
  186. package/src/lib/installers/mcp-installer.js +719 -719
  187. package/src/lib/installers/memory-installer.js +1 -1
  188. package/src/lib/risk-detector.js +16 -0
  189. package/src/lib/tasks/dispatch-planner.js +122 -4
  190. package/src/lib/tasks/mermaid-renderer.js +132 -0
  191. package/src/lib/tasks/task-classifier.js +30 -30
  192. package/src/lib/tasks/task-parser.js +209 -209
  193. package/src/lib/tasks/test-runner.js +27 -6
  194. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  195. package/src/lib/validators/checkpoint-persistence.js +39 -39
  196. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  197. package/src/lib/validators/css/css-validator.js +340 -340
  198. package/src/lib/validators/packages/package-validator.js +347 -347
  199. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  200. package/src/lib/validators/shared/issue-counter.js +14 -14
  201. package/src/lib/validators/validation-runner.js +349 -349
  202. package/src/lib/validators/verify-runner.js +322 -0
  203. package/src/lib/work-type-detector.js +143 -0
  204. package/src/scripts/setup-infra.js +32 -5
  205. package/src/utils/agent-id-mapper.js +11 -11
  206. package/src/utils/agents-installer.js +10 -6
  207. package/src/utils/claude-settings-manager.js +300 -300
  208. package/src/utils/file-copier.js +43 -0
  209. package/src/utils/hooks-installer.js +41 -11
  210. package/src/utils/skills-installer.js +174 -174
  211. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  212. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  213. package/src/lib/tasks/wave-resolver.js +0 -353
@@ -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
+ }
@@ -23,11 +23,11 @@ export function getBuildCommand(stack, hasTsConfig = false) {
23
23
  const s = stack.toLowerCase();
24
24
 
25
25
  if (s.includes('dotnet')) {
26
- return 'dotnet build --no-restore --nologo -v q 2>&1 | tail -20';
26
+ return 'dotnet build --no-restore --nologo -v q';
27
27
  }
28
28
 
29
29
  if ((s.includes('nextjs') || s.includes('node') || s.includes('react')) && hasTsConfig) {
30
- return 'npx tsc --noEmit 2>&1 | tail -20';
30
+ return 'npx tsc --noEmit';
31
31
  }
32
32
 
33
33
  return null;
@@ -3,7 +3,11 @@
3
3
  *
4
4
  * Claude Code hooks communicate via JSON to stdout.
5
5
  * - PreToolUse: { "decision": "block"|"approve", "reason": "..." }
6
- * - SessionStart/Stop/etc: { "additionalContext": "..." }
6
+ * - SessionStart/Stop/PostToolUse/etc (non-blocking context injection):
7
+ * { "hookSpecificOutput": { "hookEventName": "...", "additionalContext": "..." } }
8
+ * A top-level `additionalContext` is NOT honored by Claude Code for any
9
+ * event — it must be nested under `hookSpecificOutput` alongside the
10
+ * event name (see https://code.claude.com/docs/en/hooks.md).
7
11
  */
8
12
 
9
13
  /**
@@ -28,12 +32,20 @@ export function approve(context) {
28
32
  }
29
33
 
30
34
  /**
31
- * Inject additional context (for SessionStart, Stop, UserPromptSubmit, etc.).
35
+ * Inject additional (non-blocking) context for the current hook event.
36
+ *
37
+ * Must be nested under `hookSpecificOutput` — Claude Code does not honor a
38
+ * top-level `additionalContext` for any event.
39
+ *
32
40
  * @param {string} text - Context text to inject
33
- * @param {Object} [extra] - Additional fields to include in response
41
+ * @param {string} eventName - The hook event this is responding to (e.g.
42
+ * 'SessionStart', 'Stop', 'PostToolUse') — required, echoed back as
43
+ * `hookSpecificOutput.hookEventName`.
34
44
  */
35
- export function injectContext(text, extra = {}) {
36
- console.log(JSON.stringify({ additionalContext: text, ...extra }));
45
+ export function injectContext(text, eventName) {
46
+ console.log(JSON.stringify({
47
+ hookSpecificOutput: { hookEventName: eventName, additionalContext: text },
48
+ }));
37
49
  process.exit(0);
38
50
  }
39
51
 
@@ -5,6 +5,48 @@
5
5
  * normalizing between Write (content, file_path) and Edit (new_string, old_string, file_path).
6
6
  */
7
7
 
8
+ import { existsSync } from 'fs';
9
+ import { dirname, join, isAbsolute } from 'path';
10
+
11
+ /**
12
+ * Resolve the project root that OWNS a given file: walk up from the file's
13
+ * directory to the nearest ancestor containing one of `markers` (default
14
+ * `package.json` — the tsc/build root). Falls back to `fallbackCwd` when
15
+ * nothing matches or on any error.
16
+ *
17
+ * This exists so a PostToolUse hook typechecks/builds in the CORRECT root when
18
+ * a subagent edits a file inside its own git worktree (`isolation: "worktree"`,
19
+ * morph-apply §3b-w) instead of always assuming the main working tree. In the
20
+ * normal (non-worktree) case it resolves to the same root as `process.cwd()`.
21
+ *
22
+ * Fail-open: never throws — returns `fallbackCwd` on any error.
23
+ *
24
+ * @param {string} filePath - edited file path (absolute or relative)
25
+ * @param {string} fallbackCwd - root to use if resolution fails (usually process.cwd())
26
+ * @param {string[]} [markers] - ancestor marker filenames to look for
27
+ * @returns {string} the resolved root directory
28
+ */
29
+ export function resolveRootForFile(filePath, fallbackCwd, markers = ['package.json']) {
30
+ try {
31
+ if (!filePath) return fallbackCwd;
32
+ let p = String(filePath).replace(/\\/g, '/');
33
+ if (!isAbsolute(p)) p = join(fallbackCwd, p);
34
+
35
+ let dir = dirname(p);
36
+ let prev = null;
37
+ while (dir && dir !== prev) {
38
+ for (const marker of markers) {
39
+ if (existsSync(join(dir, marker))) return dir;
40
+ }
41
+ prev = dir;
42
+ dir = dirname(dir);
43
+ }
44
+ return fallbackCwd;
45
+ } catch {
46
+ return fallbackCwd;
47
+ }
48
+ }
49
+
8
50
  /**
9
51
  * Get the file path from a hook payload, normalizing backslashes.
10
52
  * @param {Object} payload - Hook payload from stdin
@@ -69,12 +69,32 @@ export function loadState(projectPath) {
69
69
  }
70
70
 
71
71
  /**
72
- * Check if state.json exists.
72
+ * Check if there is state to read: either the thin `.morph/state.json` index
73
+ * exists, or `.morph/features/` (committed, gitignore-exempt) already has at
74
+ * least one feature with a `feature.json`. The latter case covers a freshly
75
+ * checked-out worktree where state.json (gitignored) hasn't been materialized
76
+ * yet, but `loadState()` can already self-heal from feature.json — so hooks
77
+ * gating on `stateExists()` should be "on" there too, not dark.
78
+ * Fail-open: any unexpected error (e.g. permission denied) returns false.
73
79
  * @param {string} [projectPath]
74
80
  * @returns {boolean}
75
81
  */
76
82
  export function stateExists(projectPath) {
77
- return existsSync(join(projectPath || process.cwd(), STATE_FILE));
83
+ try {
84
+ const base = projectPath || process.cwd();
85
+ if (existsSync(join(base, STATE_FILE))) return true;
86
+
87
+ const featuresDir = join(base, FEATURES_DIR);
88
+ if (!existsSync(featuresDir)) return false;
89
+
90
+ const names = readdirSync(featuresDir, { withFileTypes: true })
91
+ .filter((e) => e.isDirectory())
92
+ .map((e) => e.name);
93
+
94
+ return names.some((name) => existsSync(join(featuresDir, name, 'feature.json')));
95
+ } catch {
96
+ return false;
97
+ }
78
98
  }
79
99
 
80
100
  /**
@@ -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
+ }
@@ -1,41 +1,41 @@
1
- {
2
- "id": "backend",
3
- "version": "1.1.0",
4
- "description": "Backend-focused rubric. Higher weight on architecture, security, and package health. Deterministic-validator rubric — approve threshold aligned to the single bar 9 from MORPH.md §0.",
5
- "categories": {
6
- "functionality": {
7
- "weight": 0.25,
8
- "validators": ["contract-compliance"],
9
- "description": "Core feature correctness and contract compliance"
10
- },
11
- "code_quality": {
12
- "weight": 0.25,
13
- "validators": ["architecture"],
14
- "description": "Architecture patterns, dependency direction, and code structure"
15
- },
16
- "design_ui": {
17
- "weight": 0.05,
18
- "validators": ["design-system", "css", "contrast", "nextjs-component"],
19
- "description": "Design system compliance (minimal weight for backend projects)"
20
- },
21
- "security": {
22
- "weight": 0.15,
23
- "validators": [],
24
- "description": "Security posture (agent-evaluated, no automated validator)"
25
- },
26
- "packages": {
27
- "weight": 0.20,
28
- "validators": ["packages"],
29
- "description": "Dependency health, version compatibility, and deprecated packages"
30
- },
31
- "maf_compliance": {
32
- "weight": 0.10,
33
- "validators": [],
34
- "description": "MAF agent compliance (agent-evaluated, applies only when the feature touches AI agents): Microsoft.Agents.AI GA packages used; Model Registry alias referenced not hardcoded models; structured output for pipelined agents; prompts from DB not hardcoded; no Semantic Kernel; no hosted agent services."
35
- }
36
- },
37
- "thresholds": {
38
- "approve": 9.0,
39
- "retry": 6.0
40
- }
41
- }
1
+ {
2
+ "id": "backend",
3
+ "version": "1.1.0",
4
+ "description": "Backend-focused rubric. Higher weight on architecture, security, and package health. Deterministic-validator rubric — approve threshold aligned to the single bar 9 from MORPH.md §0.",
5
+ "categories": {
6
+ "functionality": {
7
+ "weight": 0.25,
8
+ "validators": ["contract-compliance"],
9
+ "description": "Core feature correctness and contract compliance"
10
+ },
11
+ "code_quality": {
12
+ "weight": 0.25,
13
+ "validators": ["architecture"],
14
+ "description": "Architecture patterns, dependency direction, and code structure"
15
+ },
16
+ "design_ui": {
17
+ "weight": 0.05,
18
+ "validators": ["design-system", "css", "contrast", "nextjs-component"],
19
+ "description": "Design system compliance (minimal weight for backend projects)"
20
+ },
21
+ "security": {
22
+ "weight": 0.15,
23
+ "validators": [],
24
+ "description": "Security posture (agent-evaluated, no automated validator)"
25
+ },
26
+ "packages": {
27
+ "weight": 0.20,
28
+ "validators": ["packages"],
29
+ "description": "Dependency health, version compatibility, and deprecated packages"
30
+ },
31
+ "maf_compliance": {
32
+ "weight": 0.10,
33
+ "validators": [],
34
+ "description": "MAF agent compliance (agent-evaluated, applies only when the feature touches AI agents): Microsoft.Agents.AI GA packages used; Model Registry alias referenced not hardcoded models; structured output for pipelined agents; prompts from DB not hardcoded; no Semantic Kernel; no hosted agent services."
35
+ }
36
+ },
37
+ "thresholds": {
38
+ "approve": 9.0,
39
+ "retry": 6.0
40
+ }
41
+ }