@polymorphism-tech/morph-spec 8.33.0 → 8.34.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/CLAUDE.md +3 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +25 -1
- 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/CLAUDE.md +2 -2
- package/framework/MORPH.md +4 -5
- package/framework/agents.json +1 -0
- package/framework/docs/CLI.md +5 -2
- package/framework/hooks/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- 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/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/infrastructure-standards.md +3 -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 +3 -3
- 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 +3 -1
- package/framework/skills/core/morph-plan/SKILL.md +6 -3
- 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/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +30 -0
- 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 +231 -231
- 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/infrastructure/docker/local-compose-isolation.md +88 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +1 -1
- 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 +24 -14
- 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/e2e/e2e.js +77 -39
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +8 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/worktree.js +266 -52
- package/src/commands/state/delete.js +8 -0
- package/src/commands/state/finish.js +29 -34
- 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 +430 -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 +5 -1
- 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/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +6 -11
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/graph/graphifyignore.js +2 -1
- 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 +21 -4
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- 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/verify-runner.js +7 -0
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/env-allocator.js +45 -11
- package/src/lib/worktree/worktree-audit.js +40 -2
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +18 -13
- package/src/utils/hooks-installer.js +46 -3
- package/src/utils/skills-installer.js +187 -187
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Settings Generator
|
|
3
|
-
*
|
|
4
|
-
* Generates .claude/settings.local.json for projects based on detected
|
|
5
|
-
* stack, MCPs, and morph-spec configuration. Uses _morph_managed marker
|
|
6
|
-
* to track managed entries for safe reset.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
-
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
12
|
-
|
|
13
|
-
const MORPH_MANAGED_KEY = '_morph_managed';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Generate and merge settings for a project
|
|
17
|
-
* @param {string} targetPath - Project root directory
|
|
18
|
-
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
19
|
-
* @param {Object} morphConfig - morph-spec config (from .morph/config/config.json)
|
|
20
|
-
* @returns {Promise<Object>} Generated settings summary
|
|
21
|
-
*/
|
|
22
|
-
export async function generateSettings(targetPath, detectedConfig, morphConfig = {}) {
|
|
23
|
-
const claudeDir = join(targetPath, '.claude');
|
|
24
|
-
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
25
|
-
|
|
26
|
-
// Read existing settings
|
|
27
|
-
const existing = await readSettingsSafe(settingsPath);
|
|
28
|
-
|
|
29
|
-
// Build stack-based permissions
|
|
30
|
-
const stack = morphConfig?.project?.architecture || 'unknown';
|
|
31
|
-
const permissions = buildPermissions(stack, detectedConfig);
|
|
32
|
-
|
|
33
|
-
// Build generated settings
|
|
34
|
-
const generated = {};
|
|
35
|
-
|
|
36
|
-
if (permissions.length > 0) {
|
|
37
|
-
generated.permissions = permissions;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Track what we manage
|
|
41
|
-
const managed = {
|
|
42
|
-
permissions_added: permissions,
|
|
43
|
-
mcps_detected: detectedConfig.mcpServers.map(s => s.name),
|
|
44
|
-
plugins_detected: detectedConfig.plugins.map(p => p.name),
|
|
45
|
-
generated_at: new Date().toISOString()
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Merge without overwriting user config
|
|
49
|
-
const merged = mergeSettings(existing, generated);
|
|
50
|
-
merged[MORPH_MANAGED_KEY] = managed;
|
|
51
|
-
|
|
52
|
-
// Ensure directory exists
|
|
53
|
-
if (!existsSync(claudeDir)) {
|
|
54
|
-
await mkdir(claudeDir, { recursive: true });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
await writeFile(settingsPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
settingsPath,
|
|
61
|
-
permissionsAdded: permissions.length,
|
|
62
|
-
managed
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Build permission rules based on detected stack
|
|
68
|
-
* @param {string} stack - Detected project stack
|
|
69
|
-
* @param {Object} detectedConfig - Detected Claude config
|
|
70
|
-
* @returns {Array<string>} Permission patterns
|
|
71
|
-
*/
|
|
72
|
-
export function buildPermissions(stack, detectedConfig = {}) {
|
|
73
|
-
const permissions = [];
|
|
74
|
-
|
|
75
|
-
// Stack-based permissions
|
|
76
|
-
const stackPermissions = {
|
|
77
|
-
nodejs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
78
|
-
typescript: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)', 'Bash(tsc:*)'],
|
|
79
|
-
nextjs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
80
|
-
react: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
81
|
-
vue: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
82
|
-
dotnet: ['Bash(dotnet:*)', 'Bash(nuget:*)'],
|
|
83
|
-
python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)'],
|
|
84
|
-
go: ['Bash(go:*)']
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
if (stackPermissions[stack]) {
|
|
88
|
-
permissions.push(...stackPermissions[stack]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// morph-spec CLI always needs node/npx
|
|
92
|
-
if (!permissions.includes('Bash(npx:*)')) {
|
|
93
|
-
permissions.push('Bash(npx:*)');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return permissions;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Deep merge settings without overwriting user entries
|
|
101
|
-
* @param {Object} existing - Current settings.local.json
|
|
102
|
-
* @param {Object} generated - Generated settings to merge
|
|
103
|
-
* @returns {Object} Merged settings
|
|
104
|
-
*/
|
|
105
|
-
export function mergeSettings(existing, generated) {
|
|
106
|
-
const result = { ...existing };
|
|
107
|
-
|
|
108
|
-
for (const [key, value] of Object.entries(generated)) {
|
|
109
|
-
if (key === MORPH_MANAGED_KEY) continue;
|
|
110
|
-
|
|
111
|
-
if (Array.isArray(value) && Array.isArray(result[key])) {
|
|
112
|
-
// Merge arrays without duplicates
|
|
113
|
-
const set = new Set(result[key]);
|
|
114
|
-
for (const item of value) {
|
|
115
|
-
set.add(item);
|
|
116
|
-
}
|
|
117
|
-
result[key] = [...set];
|
|
118
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
119
|
-
// Recursively merge objects
|
|
120
|
-
result[key] = mergeSettings(result[key] || {}, value);
|
|
121
|
-
} else if (!(key in result)) {
|
|
122
|
-
// Only add new keys, never overwrite existing
|
|
123
|
-
result[key] = value;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Remove only morph-spec managed entries from settings
|
|
132
|
-
* @param {string} settingsPath - Path to settings.local.json
|
|
133
|
-
* @returns {Promise<Object>} Summary of what was removed
|
|
134
|
-
*/
|
|
135
|
-
export async function resetMorphSettings(settingsPath) {
|
|
136
|
-
if (!existsSync(settingsPath)) {
|
|
137
|
-
return { removed: false, reason: 'file not found' };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const settings = await readSettingsSafe(settingsPath);
|
|
141
|
-
const managed = settings[MORPH_MANAGED_KEY];
|
|
142
|
-
|
|
143
|
-
if (!managed) {
|
|
144
|
-
return { removed: false, reason: 'no _morph_managed section found' };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Remove managed permissions
|
|
148
|
-
if (managed.permissions_added && Array.isArray(settings.permissions)) {
|
|
149
|
-
settings.permissions = settings.permissions.filter(
|
|
150
|
-
p => !managed.permissions_added.includes(p)
|
|
151
|
-
);
|
|
152
|
-
if (settings.permissions.length === 0) {
|
|
153
|
-
delete settings.permissions;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Remove the managed marker itself
|
|
158
|
-
delete settings[MORPH_MANAGED_KEY];
|
|
159
|
-
|
|
160
|
-
// Write back
|
|
161
|
-
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
removed: true,
|
|
165
|
-
permissionsRemoved: managed.permissions_added || [],
|
|
166
|
-
previouslyGenerated: managed.generated_at
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Save integrations.json with detected config
|
|
172
|
-
* @param {string} targetPath - Project root directory
|
|
173
|
-
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
174
|
-
*/
|
|
175
|
-
export async function saveIntegrations(targetPath, detectedConfig) {
|
|
176
|
-
const integrationsPath = join(targetPath, '.morph', 'config', 'integrations.json');
|
|
177
|
-
const integrationsDir = join(targetPath, '.morph', 'config');
|
|
178
|
-
|
|
179
|
-
if (!existsSync(integrationsDir)) {
|
|
180
|
-
await mkdir(integrationsDir, { recursive: true });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const integrations = {
|
|
184
|
-
version: '1.0.0',
|
|
185
|
-
detectedAt: new Date().toISOString(),
|
|
186
|
-
plugins: detectedConfig.plugins,
|
|
187
|
-
mcpServers: detectedConfig.mcpServers,
|
|
188
|
-
superpowers: detectedConfig.superpowers,
|
|
189
|
-
customSkills: detectedConfig.customSkills.length,
|
|
190
|
-
customCommands: detectedConfig.customCommands.length,
|
|
191
|
-
hooks: detectedConfig.hooks.length
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
await writeFile(integrationsPath, JSON.stringify(integrations, null, 2) + '\n', 'utf-8');
|
|
195
|
-
return integrationsPath;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ============================================================================
|
|
199
|
-
// Helpers
|
|
200
|
-
// ============================================================================
|
|
201
|
-
|
|
202
|
-
async function readSettingsSafe(settingsPath) {
|
|
203
|
-
try {
|
|
204
|
-
if (!existsSync(settingsPath)) return {};
|
|
205
|
-
return JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
206
|
-
} catch {
|
|
207
|
-
return {};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Settings Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates .claude/settings.local.json for projects based on detected
|
|
5
|
+
* stack, MCPs, and morph-spec configuration. Uses _morph_managed marker
|
|
6
|
+
* to track managed entries for safe reset.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
12
|
+
|
|
13
|
+
const MORPH_MANAGED_KEY = '_morph_managed';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate and merge settings for a project
|
|
17
|
+
* @param {string} targetPath - Project root directory
|
|
18
|
+
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
19
|
+
* @param {Object} morphConfig - morph-spec config (from .morph/config/config.json)
|
|
20
|
+
* @returns {Promise<Object>} Generated settings summary
|
|
21
|
+
*/
|
|
22
|
+
export async function generateSettings(targetPath, detectedConfig, morphConfig = {}) {
|
|
23
|
+
const claudeDir = join(targetPath, '.claude');
|
|
24
|
+
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
25
|
+
|
|
26
|
+
// Read existing settings
|
|
27
|
+
const existing = await readSettingsSafe(settingsPath);
|
|
28
|
+
|
|
29
|
+
// Build stack-based permissions
|
|
30
|
+
const stack = morphConfig?.project?.architecture || 'unknown';
|
|
31
|
+
const permissions = buildPermissions(stack, detectedConfig);
|
|
32
|
+
|
|
33
|
+
// Build generated settings
|
|
34
|
+
const generated = {};
|
|
35
|
+
|
|
36
|
+
if (permissions.length > 0) {
|
|
37
|
+
generated.permissions = permissions;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Track what we manage
|
|
41
|
+
const managed = {
|
|
42
|
+
permissions_added: permissions,
|
|
43
|
+
mcps_detected: detectedConfig.mcpServers.map(s => s.name),
|
|
44
|
+
plugins_detected: detectedConfig.plugins.map(p => p.name),
|
|
45
|
+
generated_at: new Date().toISOString()
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Merge without overwriting user config
|
|
49
|
+
const merged = mergeSettings(existing, generated);
|
|
50
|
+
merged[MORPH_MANAGED_KEY] = managed;
|
|
51
|
+
|
|
52
|
+
// Ensure directory exists
|
|
53
|
+
if (!existsSync(claudeDir)) {
|
|
54
|
+
await mkdir(claudeDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await writeFile(settingsPath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
settingsPath,
|
|
61
|
+
permissionsAdded: permissions.length,
|
|
62
|
+
managed
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build permission rules based on detected stack
|
|
68
|
+
* @param {string} stack - Detected project stack
|
|
69
|
+
* @param {Object} detectedConfig - Detected Claude config
|
|
70
|
+
* @returns {Array<string>} Permission patterns
|
|
71
|
+
*/
|
|
72
|
+
export function buildPermissions(stack, detectedConfig = {}) {
|
|
73
|
+
const permissions = [];
|
|
74
|
+
|
|
75
|
+
// Stack-based permissions
|
|
76
|
+
const stackPermissions = {
|
|
77
|
+
nodejs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
78
|
+
typescript: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)', 'Bash(tsc:*)'],
|
|
79
|
+
nextjs: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
80
|
+
react: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
81
|
+
vue: ['Bash(node:*)', 'Bash(npm:*)', 'Bash(npx:*)'],
|
|
82
|
+
dotnet: ['Bash(dotnet:*)', 'Bash(nuget:*)'],
|
|
83
|
+
python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)'],
|
|
84
|
+
go: ['Bash(go:*)']
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (stackPermissions[stack]) {
|
|
88
|
+
permissions.push(...stackPermissions[stack]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// morph-spec CLI always needs node/npx
|
|
92
|
+
if (!permissions.includes('Bash(npx:*)')) {
|
|
93
|
+
permissions.push('Bash(npx:*)');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return permissions;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Deep merge settings without overwriting user entries
|
|
101
|
+
* @param {Object} existing - Current settings.local.json
|
|
102
|
+
* @param {Object} generated - Generated settings to merge
|
|
103
|
+
* @returns {Object} Merged settings
|
|
104
|
+
*/
|
|
105
|
+
export function mergeSettings(existing, generated) {
|
|
106
|
+
const result = { ...existing };
|
|
107
|
+
|
|
108
|
+
for (const [key, value] of Object.entries(generated)) {
|
|
109
|
+
if (key === MORPH_MANAGED_KEY) continue;
|
|
110
|
+
|
|
111
|
+
if (Array.isArray(value) && Array.isArray(result[key])) {
|
|
112
|
+
// Merge arrays without duplicates
|
|
113
|
+
const set = new Set(result[key]);
|
|
114
|
+
for (const item of value) {
|
|
115
|
+
set.add(item);
|
|
116
|
+
}
|
|
117
|
+
result[key] = [...set];
|
|
118
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
119
|
+
// Recursively merge objects
|
|
120
|
+
result[key] = mergeSettings(result[key] || {}, value);
|
|
121
|
+
} else if (!(key in result)) {
|
|
122
|
+
// Only add new keys, never overwrite existing
|
|
123
|
+
result[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Remove only morph-spec managed entries from settings
|
|
132
|
+
* @param {string} settingsPath - Path to settings.local.json
|
|
133
|
+
* @returns {Promise<Object>} Summary of what was removed
|
|
134
|
+
*/
|
|
135
|
+
export async function resetMorphSettings(settingsPath) {
|
|
136
|
+
if (!existsSync(settingsPath)) {
|
|
137
|
+
return { removed: false, reason: 'file not found' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const settings = await readSettingsSafe(settingsPath);
|
|
141
|
+
const managed = settings[MORPH_MANAGED_KEY];
|
|
142
|
+
|
|
143
|
+
if (!managed) {
|
|
144
|
+
return { removed: false, reason: 'no _morph_managed section found' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Remove managed permissions
|
|
148
|
+
if (managed.permissions_added && Array.isArray(settings.permissions)) {
|
|
149
|
+
settings.permissions = settings.permissions.filter(
|
|
150
|
+
p => !managed.permissions_added.includes(p)
|
|
151
|
+
);
|
|
152
|
+
if (settings.permissions.length === 0) {
|
|
153
|
+
delete settings.permissions;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remove the managed marker itself
|
|
158
|
+
delete settings[MORPH_MANAGED_KEY];
|
|
159
|
+
|
|
160
|
+
// Write back
|
|
161
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
removed: true,
|
|
165
|
+
permissionsRemoved: managed.permissions_added || [],
|
|
166
|
+
previouslyGenerated: managed.generated_at
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Save integrations.json with detected config
|
|
172
|
+
* @param {string} targetPath - Project root directory
|
|
173
|
+
* @param {Object} detectedConfig - Output from detectClaudeConfig()
|
|
174
|
+
*/
|
|
175
|
+
export async function saveIntegrations(targetPath, detectedConfig) {
|
|
176
|
+
const integrationsPath = join(targetPath, '.morph', 'config', 'integrations.json');
|
|
177
|
+
const integrationsDir = join(targetPath, '.morph', 'config');
|
|
178
|
+
|
|
179
|
+
if (!existsSync(integrationsDir)) {
|
|
180
|
+
await mkdir(integrationsDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const integrations = {
|
|
184
|
+
version: '1.0.0',
|
|
185
|
+
detectedAt: new Date().toISOString(),
|
|
186
|
+
plugins: detectedConfig.plugins,
|
|
187
|
+
mcpServers: detectedConfig.mcpServers,
|
|
188
|
+
superpowers: detectedConfig.superpowers,
|
|
189
|
+
customSkills: detectedConfig.customSkills.length,
|
|
190
|
+
customCommands: detectedConfig.customCommands.length,
|
|
191
|
+
hooks: detectedConfig.hooks.length
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await writeFile(integrationsPath, JSON.stringify(integrations, null, 2) + '\n', 'utf-8');
|
|
195
|
+
return integrationsPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Helpers
|
|
200
|
+
// ============================================================================
|
|
201
|
+
|
|
202
|
+
async function readSettingsSafe(settingsPath) {
|
|
203
|
+
try {
|
|
204
|
+
if (!existsSync(settingsPath)) return {};
|
|
205
|
+
return JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
206
|
+
} catch {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { writeFileSync, existsSync } from 'node:fs';
|
|
19
19
|
import { join } from 'node:path';
|
|
20
|
+
import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
|
|
20
21
|
|
|
21
22
|
export const ALWAYS_IGNORED = [
|
|
22
|
-
'.git/', '.morph/', 'artifacts/',
|
|
23
|
+
'.git/', '.morph/', 'artifacts/', ...WORKTREE_DIR_NAMES.map((d) => `${d}/`),
|
|
23
24
|
'coverage/', 'graphify-out/', 'dist/', 'out/',
|
|
24
25
|
];
|
|
25
26
|
|