@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.
- package/CLAUDE.md +2 -2
- package/README.md +31 -2
- package/bin/morph-spec.js +50 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +38 -1
- package/docs/QUICKSTART.md +60 -1
- package/framework/CLAUDE.md +13 -2
- package/framework/MORPH.md +38 -2
- package/framework/agents.json +22 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-hotfix.md +39 -0
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/README.md +25 -10
- package/framework/hooks/claude-code/core/gate-guard.js +4 -2
- package/framework/hooks/claude-code/core/loop-detect.js +2 -1
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
- package/framework/hooks/claude-code/core/validate-completion.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +160 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/build-check.js +2 -2
- package/framework/hooks/shared/hook-response.js +17 -5
- package/framework/hooks/shared/payload-utils.js +42 -0
- package/framework/hooks/shared/state-reader.js +22 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +95 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +39 -8
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
- package/framework/skills/core/morph-eval/SKILL.md +6 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
- package/framework/skills/core/morph-implement/SKILL.md +8 -4
- package/framework/skills/core/morph-plan/SKILL.md +5 -1
- package/framework/skills/core/morph-proposal/SKILL.md +24 -1
- package/framework/skills/core/morph-review/SKILL.md +3 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +7 -2
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +0 -62
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +4 -2
- package/src/commands/project/init.js +46 -2
- package/src/commands/project/update.js +5 -3
- package/src/commands/project/worktree.js +1005 -46
- package/src/commands/state/advance.js +17 -2
- package/src/commands/state/approve.js +18 -1
- package/src/commands/state/create.js +158 -14
- package/src/commands/state/retype.js +126 -0
- package/src/commands/state/score.js +4 -0
- package/src/commands/tasks/dag.js +32 -0
- package/src/commands/validation/index.js +1 -0
- package/src/commands/validation/validate-feature.js +33 -5
- package/src/commands/validation/verify.js +190 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +35 -10
- package/src/core/templates/template-renderer.js +477 -477
- package/src/core/work-types.js +79 -0
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +11 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/scanner/state-scanner.js +42 -0
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/risk-detector.js +16 -0
- package/src/lib/tasks/dispatch-planner.js +122 -4
- package/src/lib/tasks/mermaid-renderer.js +132 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +27 -6
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/lib/validators/verify-runner.js +322 -0
- package/src/lib/work-type-detector.js +143 -0
- package/src/scripts/setup-infra.js +32 -5
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +10 -6
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +43 -0
- package/src/utils/hooks-installer.js +41 -11
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- 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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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,
|
|
36
|
-
console.log(JSON.stringify({
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|