@polymorphism-tech/morph-spec 8.1.0 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/commands/commit.md +166 -166
- 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/claude-code/core/validate-completion.js +2 -1
- 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 +97 -24
- 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/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 +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- 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/templates/IDE-SNIPPETS.md +262 -262
- 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/worktree.js +83 -1
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- 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/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/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/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- 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 +24 -7
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Activity Logger — Session-scoped hook event log
|
|
3
|
-
*
|
|
4
|
-
* Writes hook activity to .morph/logs/activity.json for live consumption
|
|
5
|
-
* by `morph-spec monitor`.
|
|
6
|
-
*
|
|
7
|
-
* Design constraints:
|
|
8
|
-
* - Synchronous only (hooks run in process, no async overhead)
|
|
9
|
-
* - Fail-silent (never throws — hooks must not be blocked by logger)
|
|
10
|
-
* - ≤5ms per call (no retries, no network)
|
|
11
|
-
* - Windows-safe paths via path.join
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
|
|
15
|
-
import { join } from 'path';
|
|
16
|
-
|
|
17
|
-
const LOGS_DIR = '.morph/logs';
|
|
18
|
-
const ACTIVITY_FILE = join(LOGS_DIR, 'activity.json');
|
|
19
|
-
|
|
20
|
-
function getActivityPath(projectPath) {
|
|
21
|
-
return join(projectPath || process.cwd(), ACTIVITY_FILE);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function ensureLogsDir(projectPath) {
|
|
25
|
-
const dir = join(projectPath || process.cwd(), LOGS_DIR);
|
|
26
|
-
if (!existsSync(dir)) {
|
|
27
|
-
mkdirSync(dir, { recursive: true });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function readRaw(activityPath) {
|
|
32
|
-
try {
|
|
33
|
-
if (!existsSync(activityPath)) return null;
|
|
34
|
-
return JSON.parse(readFileSync(activityPath, 'utf-8'));
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Log a hook firing event to the session activity log.
|
|
42
|
-
*
|
|
43
|
-
* @param {string} name - Hook name (e.g. 'dispatch.js', 'enrich-prompt')
|
|
44
|
-
* @param {string} event - Claude Code event (e.g. 'PostToolUse', 'UserPromptSubmit')
|
|
45
|
-
* @param {string} result - Outcome summary (e.g. 'ok', 'blocked', 'checkpoint_saved')
|
|
46
|
-
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
47
|
-
*/
|
|
48
|
-
export function logHookActivity(name, event, result, projectPath) {
|
|
49
|
-
try {
|
|
50
|
-
const activityPath = getActivityPath(projectPath);
|
|
51
|
-
ensureLogsDir(projectPath);
|
|
52
|
-
|
|
53
|
-
const now = new Date();
|
|
54
|
-
const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
|
|
55
|
-
|
|
56
|
-
const data = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], skills: [] };
|
|
57
|
-
|
|
58
|
-
data.hooks = data.hooks || [];
|
|
59
|
-
data.hooks.push({ name, event, result, ts });
|
|
60
|
-
|
|
61
|
-
writeFileSync(activityPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
62
|
-
} catch {
|
|
63
|
-
// Fail-silent — never block hooks
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Read the current activity log.
|
|
70
|
-
*
|
|
71
|
-
* @param {string} [projectPath]
|
|
72
|
-
* @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, skills: Array }|null}
|
|
73
|
-
*/
|
|
74
|
-
export function readActivity(projectPath) {
|
|
75
|
-
try {
|
|
76
|
-
return readRaw(getActivityPath(projectPath));
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Reset the activity log for a new session.
|
|
84
|
-
* Called from SessionStart hook to clear stale data.
|
|
85
|
-
*
|
|
86
|
-
* @param {string} sessionId - ISO timestamp of new session
|
|
87
|
-
* @param {string} feature - Active feature name (empty string if none)
|
|
88
|
-
* @param {string} phase - Current phase (empty string if none)
|
|
89
|
-
* @param {string} [projectPath]
|
|
90
|
-
*/
|
|
91
|
-
export function resetActivity(sessionId, feature, phase, projectPath) {
|
|
92
|
-
try {
|
|
93
|
-
ensureLogsDir(projectPath);
|
|
94
|
-
const data = {
|
|
95
|
-
sessionId: sessionId || new Date().toISOString(),
|
|
96
|
-
feature: feature || '',
|
|
97
|
-
phase: phase || '',
|
|
98
|
-
hooks: [],
|
|
99
|
-
skills: []
|
|
100
|
-
};
|
|
101
|
-
writeFileSync(getActivityPath(projectPath), JSON.stringify(data, null, 2), 'utf-8');
|
|
102
|
-
} catch {
|
|
103
|
-
// Fail-silent
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Log a structured event to the session activity log.
|
|
109
|
-
*
|
|
110
|
-
* @param {string} type - Event type (e.g., 'task_start', 'loop_detected')
|
|
111
|
-
* @param {Object} data - Event data payload
|
|
112
|
-
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
113
|
-
*/
|
|
114
|
-
export function logEvent(type, data, projectPath) {
|
|
115
|
-
try {
|
|
116
|
-
const activityPath = getActivityPath(projectPath);
|
|
117
|
-
ensureLogsDir(projectPath);
|
|
118
|
-
|
|
119
|
-
const now = new Date();
|
|
120
|
-
const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
|
|
121
|
-
|
|
122
|
-
const activity = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], events: [] };
|
|
123
|
-
activity.events = activity.events || [];
|
|
124
|
-
activity.events.push({ type, data, ts });
|
|
125
|
-
|
|
126
|
-
writeFileSync(activityPath, JSON.stringify(activity, null, 2), 'utf-8');
|
|
127
|
-
} catch {
|
|
128
|
-
// Fail-silent
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Archive current activity.json to sessions/ directory for historical logging.
|
|
134
|
-
* Called at the start of a new session.
|
|
135
|
-
*
|
|
136
|
-
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
137
|
-
*/
|
|
138
|
-
export function archiveSession(projectPath) {
|
|
139
|
-
try {
|
|
140
|
-
const activityPath = getActivityPath(projectPath);
|
|
141
|
-
if (!existsSync(activityPath)) return;
|
|
142
|
-
|
|
143
|
-
const data = readRaw(activityPath);
|
|
144
|
-
if (!data) return;
|
|
145
|
-
|
|
146
|
-
// Only archive if there are meaningful events
|
|
147
|
-
if ((!data.hooks || data.hooks.length === 0) && (!data.events || data.events.length === 0)) return;
|
|
148
|
-
|
|
149
|
-
const sessionsDir = join(projectPath || process.cwd(), LOGS_DIR, 'sessions');
|
|
150
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
151
|
-
|
|
152
|
-
const sessionId = (data.sessionId || new Date().toISOString()).replace(/[:.]/g, '-');
|
|
153
|
-
const archivePath = join(sessionsDir, `${sessionId}.json`);
|
|
154
|
-
writeFileSync(archivePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
155
|
-
|
|
156
|
-
unlinkSync(activityPath);
|
|
157
|
-
} catch {
|
|
158
|
-
// Fail-silent
|
|
159
|
-
}
|
|
160
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Activity Logger — Session-scoped hook event log
|
|
3
|
+
*
|
|
4
|
+
* Writes hook activity to .morph/logs/activity.json for live consumption
|
|
5
|
+
* by `morph-spec monitor`.
|
|
6
|
+
*
|
|
7
|
+
* Design constraints:
|
|
8
|
+
* - Synchronous only (hooks run in process, no async overhead)
|
|
9
|
+
* - Fail-silent (never throws — hooks must not be blocked by logger)
|
|
10
|
+
* - ≤5ms per call (no retries, no network)
|
|
11
|
+
* - Windows-safe paths via path.join
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
|
|
17
|
+
const LOGS_DIR = '.morph/logs';
|
|
18
|
+
const ACTIVITY_FILE = join(LOGS_DIR, 'activity.json');
|
|
19
|
+
|
|
20
|
+
function getActivityPath(projectPath) {
|
|
21
|
+
return join(projectPath || process.cwd(), ACTIVITY_FILE);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureLogsDir(projectPath) {
|
|
25
|
+
const dir = join(projectPath || process.cwd(), LOGS_DIR);
|
|
26
|
+
if (!existsSync(dir)) {
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readRaw(activityPath) {
|
|
32
|
+
try {
|
|
33
|
+
if (!existsSync(activityPath)) return null;
|
|
34
|
+
return JSON.parse(readFileSync(activityPath, 'utf-8'));
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Log a hook firing event to the session activity log.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} name - Hook name (e.g. 'dispatch.js', 'enrich-prompt')
|
|
44
|
+
* @param {string} event - Claude Code event (e.g. 'PostToolUse', 'UserPromptSubmit')
|
|
45
|
+
* @param {string} result - Outcome summary (e.g. 'ok', 'blocked', 'checkpoint_saved')
|
|
46
|
+
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
47
|
+
*/
|
|
48
|
+
export function logHookActivity(name, event, result, projectPath) {
|
|
49
|
+
try {
|
|
50
|
+
const activityPath = getActivityPath(projectPath);
|
|
51
|
+
ensureLogsDir(projectPath);
|
|
52
|
+
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
|
|
55
|
+
|
|
56
|
+
const data = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], skills: [] };
|
|
57
|
+
|
|
58
|
+
data.hooks = data.hooks || [];
|
|
59
|
+
data.hooks.push({ name, event, result, ts });
|
|
60
|
+
|
|
61
|
+
writeFileSync(activityPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
62
|
+
} catch {
|
|
63
|
+
// Fail-silent — never block hooks
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read the current activity log.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} [projectPath]
|
|
72
|
+
* @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, skills: Array }|null}
|
|
73
|
+
*/
|
|
74
|
+
export function readActivity(projectPath) {
|
|
75
|
+
try {
|
|
76
|
+
return readRaw(getActivityPath(projectPath));
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reset the activity log for a new session.
|
|
84
|
+
* Called from SessionStart hook to clear stale data.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} sessionId - ISO timestamp of new session
|
|
87
|
+
* @param {string} feature - Active feature name (empty string if none)
|
|
88
|
+
* @param {string} phase - Current phase (empty string if none)
|
|
89
|
+
* @param {string} [projectPath]
|
|
90
|
+
*/
|
|
91
|
+
export function resetActivity(sessionId, feature, phase, projectPath) {
|
|
92
|
+
try {
|
|
93
|
+
ensureLogsDir(projectPath);
|
|
94
|
+
const data = {
|
|
95
|
+
sessionId: sessionId || new Date().toISOString(),
|
|
96
|
+
feature: feature || '',
|
|
97
|
+
phase: phase || '',
|
|
98
|
+
hooks: [],
|
|
99
|
+
skills: []
|
|
100
|
+
};
|
|
101
|
+
writeFileSync(getActivityPath(projectPath), JSON.stringify(data, null, 2), 'utf-8');
|
|
102
|
+
} catch {
|
|
103
|
+
// Fail-silent
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Log a structured event to the session activity log.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} type - Event type (e.g., 'task_start', 'loop_detected')
|
|
111
|
+
* @param {Object} data - Event data payload
|
|
112
|
+
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
113
|
+
*/
|
|
114
|
+
export function logEvent(type, data, projectPath) {
|
|
115
|
+
try {
|
|
116
|
+
const activityPath = getActivityPath(projectPath);
|
|
117
|
+
ensureLogsDir(projectPath);
|
|
118
|
+
|
|
119
|
+
const now = new Date();
|
|
120
|
+
const ts = now.toTimeString().slice(0, 8); // HH:MM:SS
|
|
121
|
+
|
|
122
|
+
const activity = readRaw(activityPath) || { sessionId: '', feature: '', phase: '', hooks: [], events: [] };
|
|
123
|
+
activity.events = activity.events || [];
|
|
124
|
+
activity.events.push({ type, data, ts });
|
|
125
|
+
|
|
126
|
+
writeFileSync(activityPath, JSON.stringify(activity, null, 2), 'utf-8');
|
|
127
|
+
} catch {
|
|
128
|
+
// Fail-silent
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Archive current activity.json to sessions/ directory for historical logging.
|
|
134
|
+
* Called at the start of a new session.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} [projectPath] - Project root (defaults to cwd)
|
|
137
|
+
*/
|
|
138
|
+
export function archiveSession(projectPath) {
|
|
139
|
+
try {
|
|
140
|
+
const activityPath = getActivityPath(projectPath);
|
|
141
|
+
if (!existsSync(activityPath)) return;
|
|
142
|
+
|
|
143
|
+
const data = readRaw(activityPath);
|
|
144
|
+
if (!data) return;
|
|
145
|
+
|
|
146
|
+
// Only archive if there are meaningful events
|
|
147
|
+
if ((!data.hooks || data.hooks.length === 0) && (!data.events || data.events.length === 0)) return;
|
|
148
|
+
|
|
149
|
+
const sessionsDir = join(projectPath || process.cwd(), LOGS_DIR, 'sessions');
|
|
150
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
151
|
+
|
|
152
|
+
const sessionId = (data.sessionId || new Date().toISOString()).replace(/[:.]/g, '-');
|
|
153
|
+
const archivePath = join(sessionsDir, `${sessionId}.json`);
|
|
154
|
+
writeFileSync(archivePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
155
|
+
|
|
156
|
+
unlinkSync(activityPath);
|
|
157
|
+
} catch {
|
|
158
|
+
// Fail-silent
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build/Typecheck verification utility.
|
|
3
3
|
*
|
|
4
|
-
* Stack-aware: determines the correct build command based on project stack.
|
|
5
|
-
* Used by the Stop hook to verify code health
|
|
4
|
+
* Stack-aware: determines the correct build command(s) based on project stack.
|
|
5
|
+
* Used by the Stop hook and `morph-spec verify` to verify code health.
|
|
6
6
|
*
|
|
7
7
|
* Pure functions — no I/O except the actual build execution.
|
|
8
8
|
*/
|
|
@@ -11,8 +11,31 @@ import { execSync } from 'child_process';
|
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
|
|
14
|
+
const DOTNET_BUILD_CMD = 'dotnet build --no-restore --nologo -v q';
|
|
15
|
+
const TSC_BUILD_CMD = 'npx tsc --noEmit';
|
|
16
|
+
|
|
17
|
+
function isDotnetStack(s) {
|
|
18
|
+
return s.includes('dotnet') || s.includes('aspnet');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isFrontendStack(s) {
|
|
22
|
+
return s.includes('nextjs') || s.includes('node') || s.includes('react') || s.includes('vue');
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
/**
|
|
15
|
-
* Determine the build command for a
|
|
26
|
+
* Determine the build command for a SINGLE (non-monorepo) stack string.
|
|
27
|
+
*
|
|
28
|
+
* Kept for backward compatibility with single-stack projects and existing
|
|
29
|
+
* callers. A monorepo stack that mixes a frontend token with "dotnet" (e.g.
|
|
30
|
+
* the detector's own `${frontendStack}+dotnet` format, or a hyphenated
|
|
31
|
+
* `dotnet-nextjs`) should resolve through `getBuildSteps()` instead: this
|
|
32
|
+
* function alone can only ever return ONE command, and — because it checks
|
|
33
|
+
* `dotnet` before the frontend tokens — a compound stack always resolves to
|
|
34
|
+
* `dotnet build` even when the frontend is what actually needs checking (or
|
|
35
|
+
* when there's no root-level `.sln`/`.csproj`, e.g. in a monorepo where the
|
|
36
|
+
* backend lives in a subfolder). `getBuildSteps()` runs BOTH sides, each
|
|
37
|
+
* from its own working directory, instead of one command winning by
|
|
38
|
+
* substring match order.
|
|
16
39
|
*
|
|
17
40
|
* @param {string} stack - Project stack (e.g., 'dotnet-nextjs', 'nextjs')
|
|
18
41
|
* @param {boolean} [hasTsConfig] - Whether tsconfig.json exists (for TS projects)
|
|
@@ -22,17 +45,53 @@ export function getBuildCommand(stack, hasTsConfig = false) {
|
|
|
22
45
|
if (!stack) return null;
|
|
23
46
|
const s = stack.toLowerCase();
|
|
24
47
|
|
|
25
|
-
if (s
|
|
26
|
-
return
|
|
48
|
+
if (isDotnetStack(s)) {
|
|
49
|
+
return DOTNET_BUILD_CMD;
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
if ((s
|
|
30
|
-
return
|
|
52
|
+
if (isFrontendStack(s) && hasTsConfig) {
|
|
53
|
+
return TSC_BUILD_CMD;
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
return null;
|
|
34
57
|
}
|
|
35
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the build step(s) to run for a project, monorepo-aware.
|
|
61
|
+
*
|
|
62
|
+
* A monorepo stack mixes a frontend token and "dotnet" (see
|
|
63
|
+
* `detectProject()`'s own `${frontendStack}+dotnet` format) — in that case
|
|
64
|
+
* BOTH sides are checked, each from its own working directory
|
|
65
|
+
* (`frontendPath` / `backendPath`, as stored in
|
|
66
|
+
* `.morph/config/config.json → project`), instead of one command silently
|
|
67
|
+
* winning — or running from the wrong cwd — by substring match order.
|
|
68
|
+
* Single-stack projects (no `frontendPath`/`backendPath`, or a stack that
|
|
69
|
+
* only matches one side) resolve to exactly one step, from `cwd`, matching
|
|
70
|
+
* `getBuildCommand()`'s existing single-stack behavior.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} stack
|
|
73
|
+
* @param {{ cwd: string, frontendPath?: string, backendPath?: string }} opts
|
|
74
|
+
* @returns {Array<{ command: string, cwd: string }>}
|
|
75
|
+
*/
|
|
76
|
+
export function getBuildSteps(stack, { cwd, frontendPath, backendPath } = {}) {
|
|
77
|
+
if (!stack || !cwd) return [];
|
|
78
|
+
const s = stack.toLowerCase();
|
|
79
|
+
const steps = [];
|
|
80
|
+
|
|
81
|
+
if (isDotnetStack(s)) {
|
|
82
|
+
steps.push({ command: DOTNET_BUILD_CMD, cwd: backendPath ? join(cwd, backendPath) : cwd });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isFrontendStack(s)) {
|
|
86
|
+
const frontendDir = frontendPath ? join(cwd, frontendPath) : cwd;
|
|
87
|
+
if (existsSync(join(frontendDir, 'tsconfig.json'))) {
|
|
88
|
+
steps.push({ command: TSC_BUILD_CMD, cwd: frontendDir });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return steps;
|
|
93
|
+
}
|
|
94
|
+
|
|
36
95
|
/**
|
|
37
96
|
* Parse build command result into a structured object.
|
|
38
97
|
*
|
|
@@ -52,28 +111,42 @@ export function parseBuildResult(exitCode, output) {
|
|
|
52
111
|
}
|
|
53
112
|
|
|
54
113
|
/**
|
|
55
|
-
* Run build check for a project.
|
|
114
|
+
* Run build check for a project — monorepo-aware (see `getBuildSteps()`).
|
|
115
|
+
*
|
|
116
|
+
* For a compound stack (e.g. "nextjs+dotnet"), runs each resolved step in
|
|
117
|
+
* its own working directory and fails if any step fails; output from all
|
|
118
|
+
* failing steps is concatenated (prefixed by the command that produced it)
|
|
119
|
+
* so a frontend typecheck failure and a backend build failure are never
|
|
120
|
+
* silently masked by one another.
|
|
56
121
|
*
|
|
57
122
|
* @param {string} cwd - Project root directory
|
|
58
123
|
* @param {string} stack - Project stack string
|
|
59
124
|
* @param {number} [timeout=30000] - Timeout in ms
|
|
125
|
+
* @param {{ frontendPath?: string, backendPath?: string }} [paths] - Monorepo sub-paths, from config.json → project
|
|
60
126
|
* @returns {{ status: 'pass'|'fail'|'skip', output: string }}
|
|
61
127
|
*/
|
|
62
|
-
export function runBuildCheck(cwd, stack, timeout = 30000) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
export function runBuildCheck(cwd, stack, timeout = 30000, paths = {}) {
|
|
129
|
+
const steps = getBuildSteps(stack, { cwd, frontendPath: paths.frontendPath, backendPath: paths.backendPath });
|
|
130
|
+
if (steps.length === 0) return { status: 'skip', output: '' };
|
|
131
|
+
|
|
132
|
+
const failures = [];
|
|
133
|
+
for (const step of steps) {
|
|
134
|
+
try {
|
|
135
|
+
execSync(step.command, {
|
|
136
|
+
cwd: step.cwd,
|
|
137
|
+
encoding: 'utf-8',
|
|
138
|
+
timeout,
|
|
139
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
const output = (err.stdout || '') + (err.stderr || '');
|
|
143
|
+
const parsed = parseBuildResult(err.status || 1, output);
|
|
144
|
+
failures.push(`[${step.command}] ${parsed.output}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (failures.length > 0) {
|
|
149
|
+
return { status: 'fail', output: failures.join('\n\n') };
|
|
78
150
|
}
|
|
151
|
+
return { status: 'pass', output: '' };
|
|
79
152
|
}
|
|
@@ -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
|
+
}
|