@polymorphism-tech/morph-spec 7.9.0 → 7.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +6 -2
- package/docs/QUICKSTART.md +4 -4
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +2 -2
- 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/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/hook-response.js +45 -45
- package/framework/hooks/shared/payload-utils.js +78 -78
- 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 +13 -8
- 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-implement/SKILL.md +13 -8
- 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 +10 -7
- 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/parallel-workers/parallel-coordinator.md +113 -113
- 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 +2 -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 +1 -1
- package/src/commands/project/update.js +4 -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/LiveState.jsx +239 -239
- 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/tasks/test-runner.js +81 -81
- 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/scripts/setup-infra.js +385 -385
- 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/src/utils/version-checker.js +2 -2
- 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,45 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared hook response builders for Claude Code hooks.
|
|
3
|
-
*
|
|
4
|
-
* Claude Code hooks communicate via JSON to stdout.
|
|
5
|
-
* - PreToolUse: { "decision": "block"|"approve", "reason": "..." }
|
|
6
|
-
* - SessionStart/Stop/etc: { "additionalContext": "..." }
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Block a tool use with a reason.
|
|
11
|
-
* @param {string} reason - Human-readable reason for blocking
|
|
12
|
-
*/
|
|
13
|
-
export function block(reason) {
|
|
14
|
-
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
15
|
-
process.exit(0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Approve a tool use, optionally injecting context.
|
|
20
|
-
* @param {string} [context] - Additional context to inject
|
|
21
|
-
*/
|
|
22
|
-
export function approve(context) {
|
|
23
|
-
if (context) {
|
|
24
|
-
console.log(JSON.stringify({ decision: 'approve', additionalContext: context }));
|
|
25
|
-
}
|
|
26
|
-
// No output = implicit approve
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Inject additional context (for SessionStart, Stop, UserPromptSubmit, etc.).
|
|
32
|
-
* @param {string} text - Context text to inject
|
|
33
|
-
* @param {Object} [extra] - Additional fields to include in response
|
|
34
|
-
*/
|
|
35
|
-
export function injectContext(text, extra = {}) {
|
|
36
|
-
console.log(JSON.stringify({ additionalContext: text, ...extra }));
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Silent exit — no output, no blocking.
|
|
42
|
-
*/
|
|
43
|
-
export function pass() {
|
|
44
|
-
process.exit(0);
|
|
45
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared hook response builders for Claude Code hooks.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code hooks communicate via JSON to stdout.
|
|
5
|
+
* - PreToolUse: { "decision": "block"|"approve", "reason": "..." }
|
|
6
|
+
* - SessionStart/Stop/etc: { "additionalContext": "..." }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Block a tool use with a reason.
|
|
11
|
+
* @param {string} reason - Human-readable reason for blocking
|
|
12
|
+
*/
|
|
13
|
+
export function block(reason) {
|
|
14
|
+
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Approve a tool use, optionally injecting context.
|
|
20
|
+
* @param {string} [context] - Additional context to inject
|
|
21
|
+
*/
|
|
22
|
+
export function approve(context) {
|
|
23
|
+
if (context) {
|
|
24
|
+
console.log(JSON.stringify({ decision: 'approve', additionalContext: context }));
|
|
25
|
+
}
|
|
26
|
+
// No output = implicit approve
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Inject additional context (for SessionStart, Stop, UserPromptSubmit, etc.).
|
|
32
|
+
* @param {string} text - Context text to inject
|
|
33
|
+
* @param {Object} [extra] - Additional fields to include in response
|
|
34
|
+
*/
|
|
35
|
+
export function injectContext(text, extra = {}) {
|
|
36
|
+
console.log(JSON.stringify({ additionalContext: text, ...extra }));
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Silent exit — no output, no blocking.
|
|
42
|
+
*/
|
|
43
|
+
export function pass() {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared payload utilities for Claude Code Write/Edit hooks.
|
|
3
|
-
*
|
|
4
|
-
* Extracts file path and content from tool_input payloads,
|
|
5
|
-
* normalizing between Write (content, file_path) and Edit (new_string, old_string, file_path).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get the file path from a hook payload, normalizing backslashes.
|
|
10
|
-
* @param {Object} payload - Hook payload from stdin
|
|
11
|
-
* @returns {string} Normalized file path or empty string
|
|
12
|
-
*/
|
|
13
|
-
export function getFilePath(payload) {
|
|
14
|
-
const raw = payload?.tool_input?.file_path || payload?.tool_input?.path || '';
|
|
15
|
-
return raw.replace(/\\/g, '/');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Get the content being written or edited.
|
|
20
|
-
* For Write: returns `content` (full file content).
|
|
21
|
-
* For Edit: returns `new_string` (replacement text).
|
|
22
|
-
* @param {Object} payload - Hook payload from stdin
|
|
23
|
-
* @returns {string} Content string or empty string
|
|
24
|
-
*/
|
|
25
|
-
export function getContentToValidate(payload) {
|
|
26
|
-
const input = payload?.tool_input;
|
|
27
|
-
if (!input) return '';
|
|
28
|
-
// Write tool uses 'content', Edit tool uses 'new_string'
|
|
29
|
-
return input.content || input.new_string || '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check if this is a Write operation (full file content available).
|
|
34
|
-
* @param {Object} payload - Hook payload from stdin
|
|
35
|
-
* @returns {boolean} True if Write (content exists), false if Edit or unknown
|
|
36
|
-
*/
|
|
37
|
-
export function isWriteOperation(payload) {
|
|
38
|
-
return typeof payload?.tool_input?.content === 'string';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get the tool name from a hook payload.
|
|
43
|
-
* @param {Object} payload - Hook payload from stdin
|
|
44
|
-
* @returns {string} Tool name or 'unknown'
|
|
45
|
-
*/
|
|
46
|
-
export function getToolName(payload) {
|
|
47
|
-
return payload?.tool_name || 'unknown';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Build a normalized signature for loop detection.
|
|
52
|
-
* - Edit/Write: "Edit:normalized/file/path"
|
|
53
|
-
* - Bash: "Bash:first two words" (e.g., "Bash:npm test")
|
|
54
|
-
* - Other: just the tool name
|
|
55
|
-
*
|
|
56
|
-
* @param {Object} payload - Hook payload from stdin
|
|
57
|
-
* @returns {string} Signature string
|
|
58
|
-
*/
|
|
59
|
-
export function getCommandSignature(payload) {
|
|
60
|
-
const tool = getToolName(payload);
|
|
61
|
-
if (tool === 'unknown') return 'unknown';
|
|
62
|
-
|
|
63
|
-
const input = payload?.tool_input;
|
|
64
|
-
if (!input) return tool;
|
|
65
|
-
|
|
66
|
-
if (tool === 'Edit' || tool === 'Write') {
|
|
67
|
-
const path = (input.file_path || input.path || '').replace(/\\/g, '/');
|
|
68
|
-
return path ? `${tool}:${path}` : tool;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (tool === 'Bash') {
|
|
72
|
-
const cmd = (input.command || '').trim();
|
|
73
|
-
const words = cmd.split(/\s+/).slice(0, 2).join(' ');
|
|
74
|
-
return words ? `Bash:${words}` : 'Bash';
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return tool;
|
|
78
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared payload utilities for Claude Code Write/Edit hooks.
|
|
3
|
+
*
|
|
4
|
+
* Extracts file path and content from tool_input payloads,
|
|
5
|
+
* normalizing between Write (content, file_path) and Edit (new_string, old_string, file_path).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the file path from a hook payload, normalizing backslashes.
|
|
10
|
+
* @param {Object} payload - Hook payload from stdin
|
|
11
|
+
* @returns {string} Normalized file path or empty string
|
|
12
|
+
*/
|
|
13
|
+
export function getFilePath(payload) {
|
|
14
|
+
const raw = payload?.tool_input?.file_path || payload?.tool_input?.path || '';
|
|
15
|
+
return raw.replace(/\\/g, '/');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the content being written or edited.
|
|
20
|
+
* For Write: returns `content` (full file content).
|
|
21
|
+
* For Edit: returns `new_string` (replacement text).
|
|
22
|
+
* @param {Object} payload - Hook payload from stdin
|
|
23
|
+
* @returns {string} Content string or empty string
|
|
24
|
+
*/
|
|
25
|
+
export function getContentToValidate(payload) {
|
|
26
|
+
const input = payload?.tool_input;
|
|
27
|
+
if (!input) return '';
|
|
28
|
+
// Write tool uses 'content', Edit tool uses 'new_string'
|
|
29
|
+
return input.content || input.new_string || '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if this is a Write operation (full file content available).
|
|
34
|
+
* @param {Object} payload - Hook payload from stdin
|
|
35
|
+
* @returns {boolean} True if Write (content exists), false if Edit or unknown
|
|
36
|
+
*/
|
|
37
|
+
export function isWriteOperation(payload) {
|
|
38
|
+
return typeof payload?.tool_input?.content === 'string';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the tool name from a hook payload.
|
|
43
|
+
* @param {Object} payload - Hook payload from stdin
|
|
44
|
+
* @returns {string} Tool name or 'unknown'
|
|
45
|
+
*/
|
|
46
|
+
export function getToolName(payload) {
|
|
47
|
+
return payload?.tool_name || 'unknown';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a normalized signature for loop detection.
|
|
52
|
+
* - Edit/Write: "Edit:normalized/file/path"
|
|
53
|
+
* - Bash: "Bash:first two words" (e.g., "Bash:npm test")
|
|
54
|
+
* - Other: just the tool name
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} payload - Hook payload from stdin
|
|
57
|
+
* @returns {string} Signature string
|
|
58
|
+
*/
|
|
59
|
+
export function getCommandSignature(payload) {
|
|
60
|
+
const tool = getToolName(payload);
|
|
61
|
+
if (tool === 'unknown') return 'unknown';
|
|
62
|
+
|
|
63
|
+
const input = payload?.tool_input;
|
|
64
|
+
if (!input) return tool;
|
|
65
|
+
|
|
66
|
+
if (tool === 'Edit' || tool === 'Write') {
|
|
67
|
+
const path = (input.file_path || input.path || '').replace(/\\/g, '/');
|
|
68
|
+
return path ? `${tool}:${path}` : tool;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (tool === 'Bash') {
|
|
72
|
+
const cmd = (input.command || '').trim();
|
|
73
|
+
const words = cmd.split(/\s+/).slice(0, 2).join(' ');
|
|
74
|
+
return words ? `Bash:${words}` : 'Bash';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return tool;
|
|
78
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared stdin reader for Claude Code hooks.
|
|
3
|
-
*
|
|
4
|
-
* Reads JSON payload from stdin (provided by Claude Code to hooks).
|
|
5
|
-
* Returns parsed object or null on failure.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Read and parse JSON from stdin
|
|
10
|
-
* @returns {Promise<Object|null>} Parsed JSON or null
|
|
11
|
-
*/
|
|
12
|
-
export function readStdin() {
|
|
13
|
-
return new Promise((resolve) => {
|
|
14
|
-
let input = '';
|
|
15
|
-
process.stdin.setEncoding('utf-8');
|
|
16
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
17
|
-
process.stdin.on('end', () => {
|
|
18
|
-
try {
|
|
19
|
-
resolve(JSON.parse(input || '{}'));
|
|
20
|
-
} catch {
|
|
21
|
-
resolve(null);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
process.stdin.on('error', () => resolve(null));
|
|
25
|
-
});
|
|
26
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared stdin reader for Claude Code hooks.
|
|
3
|
+
*
|
|
4
|
+
* Reads JSON payload from stdin (provided by Claude Code to hooks).
|
|
5
|
+
* Returns parsed object or null on failure.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read and parse JSON from stdin
|
|
10
|
+
* @returns {Promise<Object|null>} Parsed JSON or null
|
|
11
|
+
*/
|
|
12
|
+
export function readStdin() {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
let input = '';
|
|
15
|
+
process.stdin.setEncoding('utf-8');
|
|
16
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
17
|
+
process.stdin.on('end', () => {
|
|
18
|
+
try {
|
|
19
|
+
resolve(JSON.parse(input || '{}'));
|
|
20
|
+
} catch {
|
|
21
|
+
resolve(null);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
process.stdin.on('error', () => resolve(null));
|
|
25
|
+
});
|
|
26
|
+
}
|