@polymorphism-tech/morph-spec 4.8.19 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -1,365 +1,381 @@
|
|
|
1
|
-
import { join, dirname } from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { spawnSync } from 'child_process';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
5
|
-
|
|
6
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
import fs from 'fs-extra';
|
|
8
|
-
import ora from 'ora';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import { logger } from '../../utils/logger.js';
|
|
11
|
-
import {
|
|
12
|
-
getContentDir,
|
|
13
|
-
copyDirectory,
|
|
14
|
-
copyFile,
|
|
15
|
-
pathExists,
|
|
16
|
-
ensureDir,
|
|
17
|
-
readJson,
|
|
18
|
-
writeJson,
|
|
19
|
-
updateGitignore
|
|
20
|
-
} from '../../utils/file-copier.js';
|
|
21
|
-
import {
|
|
22
|
-
checkCLIOutdated,
|
|
23
|
-
checkProjectOutdated,
|
|
24
|
-
saveProjectMorphVersion,
|
|
25
|
-
getUpdateInstructions,
|
|
26
|
-
detectInstallMethod
|
|
27
|
-
} from '../../utils/version-checker.js';
|
|
28
|
-
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
29
|
-
import { installSkills } from '../../utils/skills-installer.js';
|
|
30
|
-
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Backup user's config.json before cleaning
|
|
34
|
-
* @param {string} morphPath - Path to .morph directory
|
|
35
|
-
* @returns {Promise<object|null>} Backup of config.json or null
|
|
36
|
-
*/
|
|
37
|
-
async function backupUserConfig(morphPath) {
|
|
38
|
-
try {
|
|
39
|
-
const configPath = join(morphPath, 'config', 'config.json');
|
|
40
|
-
if (await pathExists(configPath)) {
|
|
41
|
-
const config = await readJson(configPath);
|
|
42
|
-
return config;
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
// If backup fails, return null and continue
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Clean framework directories but preserve .morph/project/
|
|
52
|
-
* @param {string} morphPath - Path to .morph directory
|
|
53
|
-
* @param {string} targetPath - Project root path
|
|
54
|
-
*/
|
|
55
|
-
async function cleanFrameworkDirs(morphPath, targetPath) {
|
|
56
|
-
const claudeDir = join(targetPath, '.claude');
|
|
57
|
-
const dirsToClean = [
|
|
58
|
-
join(morphPath, 'framework', 'templates'),
|
|
59
|
-
join(morphPath, 'framework', 'standards'),
|
|
60
|
-
join(morphPath, 'framework', 'hooks'),
|
|
61
|
-
join(morphPath, 'config'),
|
|
62
|
-
join(claudeDir, 'commands'),
|
|
63
|
-
join(claudeDir, 'rules'),
|
|
64
|
-
join(claudeDir, 'skills'),
|
|
65
|
-
join(claudeDir, 'agents'),
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
for (const dir of dirsToClean) {
|
|
69
|
-
try {
|
|
70
|
-
if (await pathExists(dir)) {
|
|
71
|
-
await fs.remove(dir);
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
// Continue even if cleanup fails for one directory
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Restore user's config.json after reinstallation
|
|
81
|
-
* @param {string} morphPath - Path to .morph directory
|
|
82
|
-
* @param {string} targetPath - Project root path
|
|
83
|
-
* @param {object|null} configBackup - Backup of config.json
|
|
84
|
-
* @param {string} currentVersion - Current CLI version
|
|
85
|
-
*/
|
|
86
|
-
async function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion) {
|
|
87
|
-
const configDir = join(morphPath, 'config');
|
|
88
|
-
await ensureDir(configDir);
|
|
89
|
-
const configPath = join(configDir, 'config.json');
|
|
90
|
-
|
|
91
|
-
if (configBackup) {
|
|
92
|
-
// Preserve user data but update framework version
|
|
93
|
-
configBackup.frameworkVersion = currentVersion;
|
|
94
|
-
await writeJson(configPath, configBackup);
|
|
95
|
-
} else {
|
|
96
|
-
// Create basic config if none existed
|
|
97
|
-
const dirName = targetPath.split(/[\\/]/).pop();
|
|
98
|
-
const defaultConfig = {
|
|
99
|
-
framework: 'global',
|
|
100
|
-
frameworkVersion: currentVersion,
|
|
101
|
-
project: {
|
|
102
|
-
name: dirName,
|
|
103
|
-
architecture: 'unknown'
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
await writeJson(configPath, defaultConfig);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function updateCommand(options) {
|
|
111
|
-
const targetPath = process.cwd();
|
|
112
|
-
const contentDir = getContentDir();
|
|
113
|
-
const morphPath = join(targetPath, '.morph');
|
|
114
|
-
|
|
115
|
-
logger.header('MORPH-SPEC Update');
|
|
116
|
-
|
|
117
|
-
// Check if MORPH is initialized
|
|
118
|
-
let needsInitialization = false;
|
|
119
|
-
if (!(await pathExists(morphPath))) {
|
|
120
|
-
logger.warn('MORPH not fully initialized. Creating basic structure...');
|
|
121
|
-
needsInitialization = true;
|
|
122
|
-
|
|
123
|
-
// Create basic .morph structure
|
|
124
|
-
await ensureDir(join(morphPath, 'context'));
|
|
125
|
-
await ensureDir(join(morphPath, 'features'));
|
|
126
|
-
await ensureDir(join(morphPath, 'framework'));
|
|
127
|
-
await ensureDir(join(morphPath, 'config'));
|
|
128
|
-
logger.dim(' ✓ Created .morph directory structure');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check CLI version
|
|
132
|
-
const spinner = ora('Checking versions...').start();
|
|
133
|
-
const cliCheck = await checkCLIOutdated();
|
|
134
|
-
const projectCheck = await checkProjectOutdated(targetPath);
|
|
135
|
-
|
|
136
|
-
spinner.stop();
|
|
137
|
-
|
|
138
|
-
// Display version info
|
|
139
|
-
logger.blank();
|
|
140
|
-
logger.info('Version Status:');
|
|
141
|
-
logger.dim(` CLI version: ${cliCheck.current}`);
|
|
142
|
-
|
|
143
|
-
if (projectCheck.current) {
|
|
144
|
-
logger.dim(` Project MORPH version: ${projectCheck.current}`);
|
|
145
|
-
} else {
|
|
146
|
-
logger.dim(' Project MORPH version: not found (legacy installation)');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (cliCheck.latest) {
|
|
150
|
-
const latestColor = cliCheck.isOutdated ? chalk.yellow : chalk.green;
|
|
151
|
-
logger.dim(` Latest available: ${latestColor(cliCheck.latest)}`);
|
|
152
|
-
}
|
|
153
|
-
logger.blank();
|
|
154
|
-
|
|
155
|
-
// If CLI is outdated, stop and instruct user
|
|
156
|
-
if (cliCheck.isOutdated && cliCheck.latest) {
|
|
157
|
-
logger.warn(`Your CLI is outdated (${cliCheck.current} → ${cliCheck.latest})`);
|
|
158
|
-
logger.blank();
|
|
159
|
-
logger.info('Please update the CLI first:');
|
|
160
|
-
logger.blank();
|
|
161
|
-
|
|
162
|
-
const method = detectInstallMethod();
|
|
163
|
-
const instructions = getUpdateInstructions(method);
|
|
164
|
-
|
|
165
|
-
instructions.forEach(line => {
|
|
166
|
-
if (line === '') {
|
|
167
|
-
logger.blank();
|
|
168
|
-
} else if (line.startsWith('Or ')) {
|
|
169
|
-
logger.dim(line);
|
|
170
|
-
} else {
|
|
171
|
-
logger.box([line]);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
logger.blank();
|
|
176
|
-
logger.dim('Then run "morph-spec update" again.');
|
|
177
|
-
process.exit(0);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Proceed with update
|
|
181
|
-
const updateSpinner = ora('Updating MORPH-SPEC...').start();
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
// Backup user config before cleaning
|
|
185
|
-
updateSpinner.text = 'Backing up user configuration...';
|
|
186
|
-
const configBackup = await backupUserConfig(morphPath);
|
|
187
|
-
if (configBackup) {
|
|
188
|
-
logger.dim(' ✓ User config backed up');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Clean framework directories (preserve .morph/project/)
|
|
192
|
-
updateSpinner.text = 'Cleaning old framework files...';
|
|
193
|
-
await cleanFrameworkDirs(morphPath, targetPath);
|
|
194
|
-
logger.dim(' ✓ Old framework files removed');
|
|
195
|
-
|
|
196
|
-
const updateTemplates = !options.standards || options.templates;
|
|
197
|
-
const updateStandards = !options.templates || options.standards;
|
|
198
|
-
|
|
199
|
-
// Update templates
|
|
200
|
-
if (updateTemplates) {
|
|
201
|
-
updateSpinner.text = 'Updating templates...';
|
|
202
|
-
const templatesSrc = join(contentDir, 'framework', 'templates');
|
|
203
|
-
const templatesDest = join(morphPath, 'framework', 'templates');
|
|
204
|
-
await copyDirectory(templatesSrc, templatesDest);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Update standards
|
|
208
|
-
if (updateStandards) {
|
|
209
|
-
updateSpinner.text = 'Updating standards...';
|
|
210
|
-
const standardsSrc = join(contentDir, 'framework', 'standards');
|
|
211
|
-
const standardsDest = join(morphPath, 'framework', 'standards');
|
|
212
|
-
await copyDirectory(standardsSrc, standardsDest);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Update hooks (runtime hook scripts under .morph/framework/hooks/)
|
|
216
|
-
updateSpinner.text = 'Updating hooks...';
|
|
217
|
-
const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
|
|
218
|
-
const hooksDest = join(morphPath, 'framework', 'hooks');
|
|
219
|
-
if (await pathExists(hooksSrc)) {
|
|
220
|
-
await copyDirectory(hooksSrc, hooksDest);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Update agents.json (sourced from framework/ — canonical single source of truth)
|
|
224
|
-
updateSpinner.text = 'Updating agents configuration...';
|
|
225
|
-
const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
|
|
226
|
-
const agentsDest = join(morphPath, 'framework', 'agents.json');
|
|
227
|
-
if (await pathExists(agentsSrc)) {
|
|
228
|
-
await copyFile(agentsSrc, agentsDest);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js
|
|
232
|
-
// Only runs when the generator script is present (framework dev installs via npm link)
|
|
233
|
-
updateSpinner.text = 'Syncing generated refs...';
|
|
234
|
-
try {
|
|
235
|
-
const generateScript = join(__dirname, '..', '..', '..', 'scripts', 'generate-refs.js');
|
|
236
|
-
if (existsSync(generateScript)) {
|
|
237
|
-
const result = spawnSync(process.execPath, [generateScript], { encoding: 'utf8' });
|
|
238
|
-
if (result.status === 0) {
|
|
239
|
-
logger.dim(' ✓ Generated refs synced (phase-utils.js, skill outputs)');
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
} catch {
|
|
243
|
-
// Non-fatal: generator unavailable or files already in sync
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Update .claude commands and skills
|
|
247
|
-
// Source: framework/ (canonical for all stacks)
|
|
248
|
-
updateSpinner.text = 'Setting up Claude Code integration...';
|
|
249
|
-
const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
250
|
-
const claudeDest = join(targetPath, '.claude');
|
|
251
|
-
|
|
252
|
-
let commandsCopied = false;
|
|
253
|
-
|
|
254
|
-
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
255
|
-
const commandsSrc = join(frameworkDir, 'commands');
|
|
256
|
-
const commandsDest = join(claudeDest, 'commands');
|
|
257
|
-
if (await pathExists(commandsSrc)) {
|
|
258
|
-
await copyDirectory(commandsSrc, commandsDest);
|
|
259
|
-
commandsCopied = true;
|
|
260
|
-
} else {
|
|
261
|
-
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
265
|
-
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
266
|
-
await installSkills(targetPath);
|
|
267
|
-
|
|
268
|
-
// Sync native subagents to .claude/agents/
|
|
269
|
-
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
270
|
-
let projectStack = null;
|
|
271
|
-
try {
|
|
272
|
-
const cfg = await readJson(join(morphPath, 'config', 'config.json'));
|
|
273
|
-
projectStack = cfg?.project?.stack ?? null;
|
|
274
|
-
} catch { /* ignore — config may not exist yet */ }
|
|
275
|
-
await installAgents(targetPath, frameworkDir, { projectStack });
|
|
276
|
-
await installDomainAgents(targetPath, frameworkDir);
|
|
277
|
-
|
|
278
|
-
// Sync path-scoped rules to .claude/rules/
|
|
279
|
-
updateSpinner.text = 'Syncing rules to .claude/rules/...';
|
|
280
|
-
const rulesSrc = join(frameworkDir, 'rules');
|
|
281
|
-
const rulesDest = join(claudeDest, 'rules');
|
|
282
|
-
if (await pathExists(rulesSrc)) {
|
|
283
|
-
await copyDirectory(rulesSrc, rulesDest);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Sync runtime CLAUDE.md to .claude/CLAUDE.md
|
|
287
|
-
updateSpinner.text = 'Syncing .claude/CLAUDE.md...';
|
|
288
|
-
const runtimeSrc = join(frameworkDir, 'CLAUDE.md');
|
|
289
|
-
const runtimeDest = join(claudeDest, 'CLAUDE.md');
|
|
290
|
-
if (await pathExists(runtimeSrc)) {
|
|
291
|
-
await copyFile(runtimeSrc, runtimeDest);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Sync statusline globally to ~/.claude/
|
|
295
|
-
updateSpinner.text = 'Syncing statusline to ~/.claude/...';
|
|
296
|
-
const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
|
|
297
|
-
try {
|
|
298
|
-
await installGlobalStatusline(HOOKS_SRC);
|
|
299
|
-
} catch {
|
|
300
|
-
// Non-critical: global dir may not be writable in all environments
|
|
301
|
-
logger.dim(' ⚠ Could not install statusline globally (non-critical)');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Update Claude Code hooks in .claude/settings.local.json
|
|
305
|
-
updateSpinner.text = 'Updating Claude Code hooks...';
|
|
306
|
-
const hooksResult = await installClaudeHooks(targetPath);
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
updateSpinner.text = '
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
logger.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
logger.blank();
|
|
356
|
-
logger.info('
|
|
357
|
-
logger.dim('
|
|
358
|
-
logger.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
1
|
+
import { join, dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { logger } from '../../utils/logger.js';
|
|
11
|
+
import {
|
|
12
|
+
getContentDir,
|
|
13
|
+
copyDirectory,
|
|
14
|
+
copyFile,
|
|
15
|
+
pathExists,
|
|
16
|
+
ensureDir,
|
|
17
|
+
readJson,
|
|
18
|
+
writeJson,
|
|
19
|
+
updateGitignore
|
|
20
|
+
} from '../../utils/file-copier.js';
|
|
21
|
+
import {
|
|
22
|
+
checkCLIOutdated,
|
|
23
|
+
checkProjectOutdated,
|
|
24
|
+
saveProjectMorphVersion,
|
|
25
|
+
getUpdateInstructions,
|
|
26
|
+
detectInstallMethod
|
|
27
|
+
} from '../../utils/version-checker.js';
|
|
28
|
+
import { installClaudeHooks, installGlobalStatusline, installVSCodeTerminalSettings, installShellIntegration } from '../../utils/claude-settings-manager.js';
|
|
29
|
+
import { installSkills } from '../../utils/skills-installer.js';
|
|
30
|
+
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Backup user's config.json before cleaning
|
|
34
|
+
* @param {string} morphPath - Path to .morph directory
|
|
35
|
+
* @returns {Promise<object|null>} Backup of config.json or null
|
|
36
|
+
*/
|
|
37
|
+
async function backupUserConfig(morphPath) {
|
|
38
|
+
try {
|
|
39
|
+
const configPath = join(morphPath, 'config', 'config.json');
|
|
40
|
+
if (await pathExists(configPath)) {
|
|
41
|
+
const config = await readJson(configPath);
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// If backup fails, return null and continue
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Clean framework directories but preserve .morph/project/
|
|
52
|
+
* @param {string} morphPath - Path to .morph directory
|
|
53
|
+
* @param {string} targetPath - Project root path
|
|
54
|
+
*/
|
|
55
|
+
async function cleanFrameworkDirs(morphPath, targetPath) {
|
|
56
|
+
const claudeDir = join(targetPath, '.claude');
|
|
57
|
+
const dirsToClean = [
|
|
58
|
+
join(morphPath, 'framework', 'templates'),
|
|
59
|
+
join(morphPath, 'framework', 'standards'),
|
|
60
|
+
join(morphPath, 'framework', 'hooks'),
|
|
61
|
+
join(morphPath, 'config'),
|
|
62
|
+
join(claudeDir, 'commands'),
|
|
63
|
+
join(claudeDir, 'rules'),
|
|
64
|
+
join(claudeDir, 'skills'),
|
|
65
|
+
join(claudeDir, 'agents'),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const dir of dirsToClean) {
|
|
69
|
+
try {
|
|
70
|
+
if (await pathExists(dir)) {
|
|
71
|
+
await fs.remove(dir);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Continue even if cleanup fails for one directory
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Restore user's config.json after reinstallation
|
|
81
|
+
* @param {string} morphPath - Path to .morph directory
|
|
82
|
+
* @param {string} targetPath - Project root path
|
|
83
|
+
* @param {object|null} configBackup - Backup of config.json
|
|
84
|
+
* @param {string} currentVersion - Current CLI version
|
|
85
|
+
*/
|
|
86
|
+
async function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion) {
|
|
87
|
+
const configDir = join(morphPath, 'config');
|
|
88
|
+
await ensureDir(configDir);
|
|
89
|
+
const configPath = join(configDir, 'config.json');
|
|
90
|
+
|
|
91
|
+
if (configBackup) {
|
|
92
|
+
// Preserve user data but update framework version
|
|
93
|
+
configBackup.frameworkVersion = currentVersion;
|
|
94
|
+
await writeJson(configPath, configBackup);
|
|
95
|
+
} else {
|
|
96
|
+
// Create basic config if none existed
|
|
97
|
+
const dirName = targetPath.split(/[\\/]/).pop();
|
|
98
|
+
const defaultConfig = {
|
|
99
|
+
framework: 'global',
|
|
100
|
+
frameworkVersion: currentVersion,
|
|
101
|
+
project: {
|
|
102
|
+
name: dirName,
|
|
103
|
+
architecture: 'unknown'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
await writeJson(configPath, defaultConfig);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function updateCommand(options) {
|
|
111
|
+
const targetPath = process.cwd();
|
|
112
|
+
const contentDir = getContentDir();
|
|
113
|
+
const morphPath = join(targetPath, '.morph');
|
|
114
|
+
|
|
115
|
+
logger.header('MORPH-SPEC Update');
|
|
116
|
+
|
|
117
|
+
// Check if MORPH is initialized
|
|
118
|
+
let needsInitialization = false;
|
|
119
|
+
if (!(await pathExists(morphPath))) {
|
|
120
|
+
logger.warn('MORPH not fully initialized. Creating basic structure...');
|
|
121
|
+
needsInitialization = true;
|
|
122
|
+
|
|
123
|
+
// Create basic .morph structure
|
|
124
|
+
await ensureDir(join(morphPath, 'context'));
|
|
125
|
+
await ensureDir(join(morphPath, 'features'));
|
|
126
|
+
await ensureDir(join(morphPath, 'framework'));
|
|
127
|
+
await ensureDir(join(morphPath, 'config'));
|
|
128
|
+
logger.dim(' ✓ Created .morph directory structure');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check CLI version
|
|
132
|
+
const spinner = ora('Checking versions...').start();
|
|
133
|
+
const cliCheck = await checkCLIOutdated();
|
|
134
|
+
const projectCheck = await checkProjectOutdated(targetPath);
|
|
135
|
+
|
|
136
|
+
spinner.stop();
|
|
137
|
+
|
|
138
|
+
// Display version info
|
|
139
|
+
logger.blank();
|
|
140
|
+
logger.info('Version Status:');
|
|
141
|
+
logger.dim(` CLI version: ${cliCheck.current}`);
|
|
142
|
+
|
|
143
|
+
if (projectCheck.current) {
|
|
144
|
+
logger.dim(` Project MORPH version: ${projectCheck.current}`);
|
|
145
|
+
} else {
|
|
146
|
+
logger.dim(' Project MORPH version: not found (legacy installation)');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (cliCheck.latest) {
|
|
150
|
+
const latestColor = cliCheck.isOutdated ? chalk.yellow : chalk.green;
|
|
151
|
+
logger.dim(` Latest available: ${latestColor(cliCheck.latest)}`);
|
|
152
|
+
}
|
|
153
|
+
logger.blank();
|
|
154
|
+
|
|
155
|
+
// If CLI is outdated, stop and instruct user
|
|
156
|
+
if (cliCheck.isOutdated && cliCheck.latest) {
|
|
157
|
+
logger.warn(`Your CLI is outdated (${cliCheck.current} → ${cliCheck.latest})`);
|
|
158
|
+
logger.blank();
|
|
159
|
+
logger.info('Please update the CLI first:');
|
|
160
|
+
logger.blank();
|
|
161
|
+
|
|
162
|
+
const method = detectInstallMethod();
|
|
163
|
+
const instructions = getUpdateInstructions(method);
|
|
164
|
+
|
|
165
|
+
instructions.forEach(line => {
|
|
166
|
+
if (line === '') {
|
|
167
|
+
logger.blank();
|
|
168
|
+
} else if (line.startsWith('Or ')) {
|
|
169
|
+
logger.dim(line);
|
|
170
|
+
} else {
|
|
171
|
+
logger.box([line]);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
logger.blank();
|
|
176
|
+
logger.dim('Then run "morph-spec update" again.');
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Proceed with update
|
|
181
|
+
const updateSpinner = ora('Updating MORPH-SPEC...').start();
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Backup user config before cleaning
|
|
185
|
+
updateSpinner.text = 'Backing up user configuration...';
|
|
186
|
+
const configBackup = await backupUserConfig(morphPath);
|
|
187
|
+
if (configBackup) {
|
|
188
|
+
logger.dim(' ✓ User config backed up');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Clean framework directories (preserve .morph/project/)
|
|
192
|
+
updateSpinner.text = 'Cleaning old framework files...';
|
|
193
|
+
await cleanFrameworkDirs(morphPath, targetPath);
|
|
194
|
+
logger.dim(' ✓ Old framework files removed');
|
|
195
|
+
|
|
196
|
+
const updateTemplates = !options.standards || options.templates;
|
|
197
|
+
const updateStandards = !options.templates || options.standards;
|
|
198
|
+
|
|
199
|
+
// Update templates
|
|
200
|
+
if (updateTemplates) {
|
|
201
|
+
updateSpinner.text = 'Updating templates...';
|
|
202
|
+
const templatesSrc = join(contentDir, 'framework', 'templates');
|
|
203
|
+
const templatesDest = join(morphPath, 'framework', 'templates');
|
|
204
|
+
await copyDirectory(templatesSrc, templatesDest);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update standards
|
|
208
|
+
if (updateStandards) {
|
|
209
|
+
updateSpinner.text = 'Updating standards...';
|
|
210
|
+
const standardsSrc = join(contentDir, 'framework', 'standards');
|
|
211
|
+
const standardsDest = join(morphPath, 'framework', 'standards');
|
|
212
|
+
await copyDirectory(standardsSrc, standardsDest);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Update hooks (runtime hook scripts under .morph/framework/hooks/)
|
|
216
|
+
updateSpinner.text = 'Updating hooks...';
|
|
217
|
+
const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
|
|
218
|
+
const hooksDest = join(morphPath, 'framework', 'hooks');
|
|
219
|
+
if (await pathExists(hooksSrc)) {
|
|
220
|
+
await copyDirectory(hooksSrc, hooksDest);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Update agents.json (sourced from framework/ — canonical single source of truth)
|
|
224
|
+
updateSpinner.text = 'Updating agents configuration...';
|
|
225
|
+
const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
|
|
226
|
+
const agentsDest = join(morphPath, 'framework', 'agents.json');
|
|
227
|
+
if (await pathExists(agentsSrc)) {
|
|
228
|
+
await copyFile(agentsSrc, agentsDest);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js
|
|
232
|
+
// Only runs when the generator script is present (framework dev installs via npm link)
|
|
233
|
+
updateSpinner.text = 'Syncing generated refs...';
|
|
234
|
+
try {
|
|
235
|
+
const generateScript = join(__dirname, '..', '..', '..', 'scripts', 'generate-refs.js');
|
|
236
|
+
if (existsSync(generateScript)) {
|
|
237
|
+
const result = spawnSync(process.execPath, [generateScript], { encoding: 'utf8' });
|
|
238
|
+
if (result.status === 0) {
|
|
239
|
+
logger.dim(' ✓ Generated refs synced (phase-utils.js, skill outputs)');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Non-fatal: generator unavailable or files already in sync
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Update .claude commands and skills
|
|
247
|
+
// Source: framework/ (canonical for all stacks)
|
|
248
|
+
updateSpinner.text = 'Setting up Claude Code integration...';
|
|
249
|
+
const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
250
|
+
const claudeDest = join(targetPath, '.claude');
|
|
251
|
+
|
|
252
|
+
let commandsCopied = false;
|
|
253
|
+
|
|
254
|
+
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
255
|
+
const commandsSrc = join(frameworkDir, 'commands');
|
|
256
|
+
const commandsDest = join(claudeDest, 'commands');
|
|
257
|
+
if (await pathExists(commandsSrc)) {
|
|
258
|
+
await copyDirectory(commandsSrc, commandsDest);
|
|
259
|
+
commandsCopied = true;
|
|
260
|
+
} else {
|
|
261
|
+
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
265
|
+
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
266
|
+
await installSkills(targetPath);
|
|
267
|
+
|
|
268
|
+
// Sync native subagents to .claude/agents/
|
|
269
|
+
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
270
|
+
let projectStack = null;
|
|
271
|
+
try {
|
|
272
|
+
const cfg = await readJson(join(morphPath, 'config', 'config.json'));
|
|
273
|
+
projectStack = cfg?.project?.stack ?? null;
|
|
274
|
+
} catch { /* ignore — config may not exist yet */ }
|
|
275
|
+
await installAgents(targetPath, frameworkDir, { projectStack });
|
|
276
|
+
await installDomainAgents(targetPath, frameworkDir);
|
|
277
|
+
|
|
278
|
+
// Sync path-scoped rules to .claude/rules/
|
|
279
|
+
updateSpinner.text = 'Syncing rules to .claude/rules/...';
|
|
280
|
+
const rulesSrc = join(frameworkDir, 'rules');
|
|
281
|
+
const rulesDest = join(claudeDest, 'rules');
|
|
282
|
+
if (await pathExists(rulesSrc)) {
|
|
283
|
+
await copyDirectory(rulesSrc, rulesDest);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Sync runtime CLAUDE.md to .claude/CLAUDE.md
|
|
287
|
+
updateSpinner.text = 'Syncing .claude/CLAUDE.md...';
|
|
288
|
+
const runtimeSrc = join(frameworkDir, 'CLAUDE.md');
|
|
289
|
+
const runtimeDest = join(claudeDest, 'CLAUDE.md');
|
|
290
|
+
if (await pathExists(runtimeSrc)) {
|
|
291
|
+
await copyFile(runtimeSrc, runtimeDest);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Sync statusline globally to ~/.claude/
|
|
295
|
+
updateSpinner.text = 'Syncing statusline to ~/.claude/...';
|
|
296
|
+
const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
|
|
297
|
+
try {
|
|
298
|
+
await installGlobalStatusline(HOOKS_SRC);
|
|
299
|
+
} catch {
|
|
300
|
+
// Non-critical: global dir may not be writable in all environments
|
|
301
|
+
logger.dim(' ⚠ Could not install statusline globally (non-critical)');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Update Claude Code hooks in .claude/settings.local.json
|
|
305
|
+
updateSpinner.text = 'Updating Claude Code hooks...';
|
|
306
|
+
const hooksResult = await installClaudeHooks(targetPath);
|
|
307
|
+
|
|
308
|
+
// Install shell integration (terminal title watcher)
|
|
309
|
+
updateSpinner.text = 'Installing shell integration...';
|
|
310
|
+
try {
|
|
311
|
+
await installShellIntegration();
|
|
312
|
+
} catch {
|
|
313
|
+
logger.dim(' ⚠ Could not install shell integration (non-critical)');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Configure VS Code terminal settings (enable OSC title sequences)
|
|
317
|
+
updateSpinner.text = 'Configuring VS Code terminal settings...';
|
|
318
|
+
try {
|
|
319
|
+
await installVSCodeTerminalSettings();
|
|
320
|
+
} catch {
|
|
321
|
+
logger.dim(' ⚠ Could not configure VS Code terminal settings (non-critical)');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Update CLAUDE.md
|
|
325
|
+
updateSpinner.text = 'Updating CLAUDE.md...';
|
|
326
|
+
const claudeMdSrc = join(frameworkDir, 'CLAUDE.md');
|
|
327
|
+
const claudeMdDest = join(targetPath, 'CLAUDE.md');
|
|
328
|
+
await copyFile(claudeMdSrc, claudeMdDest);
|
|
329
|
+
|
|
330
|
+
// Restore user config after framework reinstallation
|
|
331
|
+
updateSpinner.text = 'Restoring user configuration...';
|
|
332
|
+
await restoreUserConfig(morphPath, targetPath, configBackup, cliCheck.current);
|
|
333
|
+
logger.dim(' ✓ User config restored with updated framework version');
|
|
334
|
+
|
|
335
|
+
// Update .gitignore with current morph rules
|
|
336
|
+
updateSpinner.text = 'Updating .gitignore...';
|
|
337
|
+
await updateGitignore(targetPath);
|
|
338
|
+
|
|
339
|
+
// Update .morphversion
|
|
340
|
+
updateSpinner.text = 'Saving version info...';
|
|
341
|
+
await saveProjectMorphVersion(targetPath, cliCheck.current);
|
|
342
|
+
|
|
343
|
+
updateSpinner.succeed('MORPH-SPEC updated successfully!');
|
|
344
|
+
logger.blank();
|
|
345
|
+
|
|
346
|
+
// Show version change
|
|
347
|
+
if (projectCheck.current && projectCheck.current !== cliCheck.current) {
|
|
348
|
+
logger.success(`Updated: ${projectCheck.current} → ${cliCheck.current}`);
|
|
349
|
+
} else if (!projectCheck.current) {
|
|
350
|
+
logger.success(`Updated to v${cliCheck.current}`);
|
|
351
|
+
} else {
|
|
352
|
+
logger.success(`Already up to date (v${cliCheck.current})`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
logger.blank();
|
|
356
|
+
logger.info('Updated files:');
|
|
357
|
+
if (updateTemplates) logger.dim(' ✓ .morph/framework/templates/');
|
|
358
|
+
if (updateStandards) logger.dim(' ✓ .morph/framework/standards/');
|
|
359
|
+
logger.dim(' ✓ .morph/framework/hooks/');
|
|
360
|
+
logger.dim(' ✓ .morph/framework/agents.json');
|
|
361
|
+
if (commandsCopied) logger.dim(' ✓ .claude/commands/');
|
|
362
|
+
|
|
363
|
+
logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
|
|
364
|
+
logger.dim(' ✓ .claude/skills/ (skills installed as directories)');
|
|
365
|
+
logger.dim(' ✓ .claude/agents/ (native subagents refreshed)');
|
|
366
|
+
logger.dim(' ✓ .claude/rules/ (path-scoped rules synced)');
|
|
367
|
+
logger.dim(' ✓ .claude/CLAUDE.md (runtime quick reference)');
|
|
368
|
+
logger.dim(' ✓ ~/.claude/statusline.sh (global statusline synced)');
|
|
369
|
+
logger.dim(' ✓ CLAUDE.md');
|
|
370
|
+
logger.dim(' ✓ .gitignore (morph rules updated)');
|
|
371
|
+
logger.blank();
|
|
372
|
+
logger.info('Your config.json was preserved.');
|
|
373
|
+
logger.dim('Review the updated files for any new features.');
|
|
374
|
+
logger.blank();
|
|
375
|
+
|
|
376
|
+
} catch (error) {
|
|
377
|
+
updateSpinner.fail('Update failed');
|
|
378
|
+
logger.error(error.message);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
}
|