@polymorphism-tech/morph-spec 4.7.1 → 4.8.1
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/.morph/analytics/threads-log.jsonl +54 -0
- package/.morph/state.json +198 -0
- package/LICENSE +1 -2
- package/README.md +379 -414
- package/bin/morph-spec.js +57 -403
- package/bin/validate.js +2 -26
- package/claude-plugin.json +2 -2
- package/docs/ARCHITECTURE.md +43 -46
- package/docs/CHEATSHEET.md +203 -221
- package/docs/COMMAND-FLOWS.md +319 -289
- package/docs/QUICKSTART.md +2 -8
- package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +2 -0
- package/docs/plans/2026-02-22-claude-settings.md +2 -0
- package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +2 -0
- package/docs/plans/2026-02-22-morph-spec-next.md +2 -0
- package/docs/plans/2026-02-22-native-alignment-design.md +2 -0
- package/docs/plans/2026-02-22-native-alignment-impl.md +2 -0
- package/docs/plans/2026-02-22-native-enrichment-design.md +2 -0
- package/docs/plans/2026-02-22-native-enrichment.md +2 -0
- package/docs/plans/2026-02-23-ddd-architecture-refactor.md +2 -0
- package/docs/plans/2026-02-23-ddd-nextsteps.md +2 -0
- package/docs/plans/2026-02-23-infra-architect-refactor.md +2 -0
- package/docs/plans/2026-02-23-nextjs-code-review-design.md +2 -1
- package/docs/plans/2026-02-23-nextjs-code-review-impl.md +2 -0
- package/docs/plans/2026-02-23-nextjs-standards-design.md +2 -1
- package/docs/plans/2026-02-23-nextjs-standards-impl.md +2 -0
- package/docs/plans/2026-02-24-cli-radical-simplification.md +592 -0
- package/docs/plans/2026-02-24-framework-failure-points.md +125 -0
- package/docs/plans/2026-02-24-morph-init-design.md +337 -0
- package/docs/plans/2026-02-24-morph-init-impl.md +1269 -0
- package/docs/plans/2026-02-24-tutorial-command-design.md +71 -0
- package/docs/plans/2026-02-24-tutorial-command.md +298 -0
- package/framework/CLAUDE.md +2 -2
- package/framework/commands/morph-proposal.md +3 -3
- package/framework/hooks/README.md +11 -10
- package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
- package/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +4 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +20 -5
- package/framework/hooks/claude-code/statusline.py +6 -1
- package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
- package/framework/hooks/dev/check-sync-health.js +117 -0
- package/framework/hooks/dev/guard-version-numbers.js +57 -0
- package/framework/hooks/dev/sync-standards-registry.js +60 -0
- package/framework/hooks/dev/sync-template-registry.js +60 -0
- package/framework/hooks/dev/validate-skill-format.js +70 -0
- package/framework/hooks/dev/validate-standard-format.js +73 -0
- package/framework/hooks/shared/payload-utils.js +39 -0
- package/framework/hooks/shared/state-reader.js +25 -1
- package/framework/rules/morph-workflow.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +216 -0
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +4 -4
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +192 -191
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +181 -180
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +339 -338
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +254 -253
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +168 -170
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +284 -283
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +246 -245
- package/framework/templates/examples/design-system-examples.md +1 -1
- package/framework/templates/ui/FluentDesignTheme.cs +1 -1
- package/framework/templates/ui/MudTheme.cs +1 -1
- package/framework/templates/ui/design-system.css +1 -1
- package/package.json +4 -2
- package/scripts/bump-version.js +248 -0
- package/scripts/install-dev-hooks.js +138 -0
- package/src/commands/agents/index.js +1 -2
- package/src/commands/index.js +13 -16
- package/src/commands/project/doctor.js +100 -14
- package/src/commands/project/index.js +7 -10
- package/src/commands/project/init.js +398 -555
- package/src/commands/project/install-plugin-cmd.js +28 -0
- package/src/commands/project/setup-infra-cmd.js +12 -0
- package/src/commands/project/tutorial.js +115 -0
- package/src/commands/project/update.js +22 -37
- package/src/commands/state/approve.js +213 -221
- package/src/commands/state/index.js +0 -1
- package/src/commands/state/state.js +337 -365
- package/src/commands/templates/index.js +0 -4
- package/src/commands/trust/trust.js +1 -93
- package/src/commands/utils/index.js +1 -5
- package/src/commands/validation/index.js +1 -5
- package/src/core/registry/command-registry.js +11 -285
- package/src/core/state/state-manager.js +5 -2
- package/src/lib/detectors/index.js +81 -87
- package/src/lib/detectors/structure-detector.js +275 -273
- package/src/lib/generators/recap-generator.js +232 -225
- package/src/lib/installers/mcp-installer.js +18 -3
- package/src/scripts/global-install.js +34 -0
- package/src/scripts/install-plugin.js +126 -0
- package/src/scripts/setup-infra.js +203 -0
- package/src/utils/agents-installer.js +10 -1
- package/src/utils/hooks-installer.js +70 -17
- package/CLAUDE.md +0 -77
- package/docs/claude-alignment-report.md +0 -137
- package/docs/examples/order-management/contracts.cs +0 -84
- package/docs/examples/order-management/proposal.md +0 -24
- package/docs/examples/order-management/spec.md +0 -162
- package/src/commands/feature/create-story.js +0 -362
- package/src/commands/feature/index.js +0 -6
- package/src/commands/feature/shard-spec.js +0 -225
- package/src/commands/feature/sprint-status.js +0 -250
- package/src/commands/generation/generate-onboarding.js +0 -169
- package/src/commands/generation/generate.js +0 -276
- package/src/commands/generation/index.js +0 -5
- package/src/commands/learning/capture-pattern.js +0 -121
- package/src/commands/learning/index.js +0 -5
- package/src/commands/learning/search-patterns.js +0 -126
- package/src/commands/mcp/mcp.js +0 -102
- package/src/commands/project/changes.js +0 -66
- package/src/commands/project/cost.js +0 -179
- package/src/commands/project/detect.js +0 -114
- package/src/commands/project/diff.js +0 -278
- package/src/commands/project/revert.js +0 -173
- package/src/commands/project/standards.js +0 -80
- package/src/commands/project/sync.js +0 -167
- package/src/commands/project/update-agents.js +0 -23
- package/src/commands/state/rollback-phase.js +0 -185
- package/src/commands/templates/template-customize.js +0 -87
- package/src/commands/templates/template-list.js +0 -114
- package/src/commands/templates/template-show.js +0 -129
- package/src/commands/templates/template-validate.js +0 -91
- package/src/commands/utils/troubleshoot.js +0 -222
- package/src/commands/validation/analyze-blazor-concurrency.js +0 -193
- package/src/commands/validation/lint-fluent.js +0 -352
- package/src/commands/validation/validate-blazor-state.js +0 -210
- package/src/commands/validation/validate-blazor.js +0 -156
- package/src/commands/validation/validate-css.js +0 -84
- package/src/lib/detectors/conversation-analyzer.js +0 -163
- package/src/lib/learning/index.js +0 -7
- package/src/lib/learning/learning-system.js +0 -520
- package/src/lib/troubleshooting/index.js +0 -8
- package/src/lib/troubleshooting/troubleshoot-grep.js +0 -198
- package/src/lib/troubleshooting/troubleshoot-index.js +0 -144
- package/src/llm/environment-detector.js +0 -43
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/commands/project/install-plugin-cmd.js
|
|
2
|
+
import { installPlugin } from '../../scripts/install-plugin.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
export async function installPluginCommand(pluginName, _options) {
|
|
6
|
+
if (!pluginName) {
|
|
7
|
+
logger.error('Plugin name required. Usage: morph-spec install-plugin <name>');
|
|
8
|
+
logger.dim(' Available: superpowers, context7');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const result = await installPlugin(pluginName);
|
|
13
|
+
if (result.alreadyInstalled) {
|
|
14
|
+
logger.dim(` ✓ ${pluginName} already installed (${result.version})`);
|
|
15
|
+
} else {
|
|
16
|
+
logger.success(` ✓ ${pluginName} installed (${result.version})`);
|
|
17
|
+
logger.dim(' Restart Claude Code to activate.');
|
|
18
|
+
}
|
|
19
|
+
} catch (err) {
|
|
20
|
+
logger.error(`Failed to install plugin "${pluginName}": ${err.message}`);
|
|
21
|
+
logger.blank();
|
|
22
|
+
logger.warn('Manual installation:');
|
|
23
|
+
logger.dim(' 1. Claude Code → Settings (Cmd/Ctrl+,) → Extensions');
|
|
24
|
+
logger.dim(` 2. Browse → search "${pluginName}" → Install`);
|
|
25
|
+
logger.dim(' 3. Restart Claude Code');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { setupInfra } from '../../scripts/setup-infra.js';
|
|
2
|
+
import { logger } from '../../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
export async function setupInfraCommand(options) {
|
|
5
|
+
const targetPath = options.path || process.cwd();
|
|
6
|
+
try {
|
|
7
|
+
await setupInfra(targetPath);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
logger.error(`setup-infra failed: ${err.message}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tutorial Command
|
|
3
|
+
*
|
|
4
|
+
* Prints the MORPH-SPEC workflow pipeline and getting-started steps.
|
|
5
|
+
* Pure stdout — no file I/O, no network, no side effects.
|
|
6
|
+
*
|
|
7
|
+
* Usage: morph-spec tutorial
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
const PHASES = [
|
|
13
|
+
{
|
|
14
|
+
label: 'FASE 0 · PROPOSAL',
|
|
15
|
+
what: 'Captures user story + acceptance criteria.',
|
|
16
|
+
produces: '0-proposal/proposal.md',
|
|
17
|
+
how: '/morph-proposal <feature> (in Claude Code)',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: 'FASE 1 · SETUP',
|
|
21
|
+
what: 'Detects stack, activates agents, confirms environment.',
|
|
22
|
+
produces: '.morph/state.json initialized',
|
|
23
|
+
how: 'auto-triggered inside /morph-proposal',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: 'FASE 1.5 · UI/UX',
|
|
27
|
+
what: 'Design system, mockups, component specs, user flows.',
|
|
28
|
+
produces: '2-ui/{design-system,mockups,components,flows}.md',
|
|
29
|
+
how: '/phase-uiux <feature> (in Claude Code)',
|
|
30
|
+
optional: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'FASE 2 · DESIGN',
|
|
34
|
+
what: 'Technical spec + C# contracts + architecture decisions.',
|
|
35
|
+
produces: '1-design/{spec.md, contracts-level{N}.cs, decisions.md}',
|
|
36
|
+
how: '/phase-design <feature> (in Claude Code)',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: 'FASE 3 · CLARIFY',
|
|
40
|
+
what: 'Reviews spec for ambiguities, adds edge cases.',
|
|
41
|
+
produces: '1-design/spec.md (updated with clarifications)',
|
|
42
|
+
how: '/phase-clarify <feature> (in Claude Code)',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: 'FASE 4 · TASKS',
|
|
46
|
+
what: 'Atomic task breakdown, DDD-aware.',
|
|
47
|
+
produces: '3-tasks/tasks.md',
|
|
48
|
+
how: 'auto-generated during /morph-proposal, or /phase-tasks <feature>',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'FASE 5 · IMPLEMENT',
|
|
52
|
+
what: 'Code implementation with checkpoints every 3 tasks.',
|
|
53
|
+
produces: '4-implement/recap.md + source code',
|
|
54
|
+
how: '/morph-apply <feature> (in Claude Code)',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'FASE 6 · SYNC',
|
|
58
|
+
what: 'Syncs decisions back to project standards.',
|
|
59
|
+
produces: '.morph/framework/standards/ (updated)',
|
|
60
|
+
how: 'morph-spec sync <feature>',
|
|
61
|
+
optional: true,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const SEP = chalk.dim('─'.repeat(58));
|
|
66
|
+
|
|
67
|
+
export function tutorialCommand() {
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(chalk.cyan.bold(' MORPH-SPEC Workflow Tutorial'));
|
|
70
|
+
console.log(SEP);
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(
|
|
73
|
+
chalk.white(
|
|
74
|
+
' MORPH-SPEC is spec-first: every feature goes through phases\n' +
|
|
75
|
+
' before any code is written. Each phase produces structured\n' +
|
|
76
|
+
' outputs that feed the next.'
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(SEP);
|
|
81
|
+
console.log(chalk.cyan.bold(' PIPELINE'));
|
|
82
|
+
console.log(SEP);
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(
|
|
85
|
+
chalk.white(' proposal → setup → ') +
|
|
86
|
+
chalk.dim('[uiux]') +
|
|
87
|
+
chalk.white(' → design → clarify → tasks → implement → ') +
|
|
88
|
+
chalk.dim('[sync]')
|
|
89
|
+
);
|
|
90
|
+
console.log(chalk.dim(' (phases in brackets are optional)'));
|
|
91
|
+
console.log('');
|
|
92
|
+
|
|
93
|
+
for (const phase of PHASES) {
|
|
94
|
+
console.log(SEP);
|
|
95
|
+
const label = phase.optional
|
|
96
|
+
? chalk.cyan.bold(` ${phase.label}`) + chalk.dim(' [optional]')
|
|
97
|
+
: chalk.cyan.bold(` ${phase.label}`);
|
|
98
|
+
console.log(label);
|
|
99
|
+
console.log(chalk.white(` What: ${phase.what}`));
|
|
100
|
+
console.log(chalk.dim(` Produces: ${phase.produces}`));
|
|
101
|
+
console.log(chalk.green(` How: ${phase.how}`));
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(SEP);
|
|
106
|
+
console.log(chalk.cyan.bold(' GETTING STARTED'));
|
|
107
|
+
console.log(SEP);
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.white(' 1. Open Claude Code in your project'));
|
|
110
|
+
console.log(chalk.white(' 2. Run: ') + chalk.green('/morph-proposal <your-feature-name>'));
|
|
111
|
+
console.log(chalk.white(' 3. Answer the questions — morph-spec handles the rest'));
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(chalk.dim(' Tip: run `morph-spec doctor` to verify your installation first.'));
|
|
114
|
+
console.log('');
|
|
115
|
+
}
|
|
@@ -24,11 +24,9 @@ import {
|
|
|
24
24
|
getUpdateInstructions,
|
|
25
25
|
detectInstallMethod
|
|
26
26
|
} from '../../utils/version-checker.js';
|
|
27
|
-
import { installClaudeHooks } from '../../utils/claude-settings-manager.js';
|
|
27
|
+
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
28
28
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
29
29
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
30
|
-
import { AutoContextOrchestrator } from '../../core/orchestrator.js';
|
|
31
|
-
import { detectClaudeCode } from '../../llm/environment-detector.js';
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* Backup user's config.json before cleaning
|
|
@@ -57,6 +55,7 @@ async function cleanFrameworkDirs(morphPath, targetPath) {
|
|
|
57
55
|
const dirsToClean = [
|
|
58
56
|
join(morphPath, 'framework', 'templates'),
|
|
59
57
|
join(morphPath, 'framework', 'standards'),
|
|
58
|
+
join(morphPath, 'framework', 'hooks'),
|
|
60
59
|
join(morphPath, 'config'),
|
|
61
60
|
join(targetPath, '.claude')
|
|
62
61
|
];
|
|
@@ -208,6 +207,14 @@ export async function updateCommand(options) {
|
|
|
208
207
|
await copyDirectory(standardsSrc, standardsDest);
|
|
209
208
|
}
|
|
210
209
|
|
|
210
|
+
// Update hooks (runtime hook scripts under .morph/framework/hooks/)
|
|
211
|
+
updateSpinner.text = 'Updating hooks...';
|
|
212
|
+
const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
|
|
213
|
+
const hooksDest = join(morphPath, 'framework', 'hooks');
|
|
214
|
+
if (await pathExists(hooksSrc)) {
|
|
215
|
+
await copyDirectory(hooksSrc, hooksDest);
|
|
216
|
+
}
|
|
217
|
+
|
|
211
218
|
// Update agents.json (sourced from framework/ — canonical single source of truth)
|
|
212
219
|
updateSpinner.text = 'Updating agents configuration...';
|
|
213
220
|
const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
|
|
@@ -279,6 +286,16 @@ export async function updateCommand(options) {
|
|
|
279
286
|
await copyFile(runtimeSrc, runtimeDest);
|
|
280
287
|
}
|
|
281
288
|
|
|
289
|
+
// Sync statusline globally to ~/.claude/
|
|
290
|
+
updateSpinner.text = 'Syncing statusline to ~/.claude/...';
|
|
291
|
+
const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
|
|
292
|
+
try {
|
|
293
|
+
await installGlobalStatusline(HOOKS_SRC);
|
|
294
|
+
} catch {
|
|
295
|
+
// Non-critical: global dir may not be writable in all environments
|
|
296
|
+
logger.dim(' ⚠ Could not install statusline globally (non-critical)');
|
|
297
|
+
}
|
|
298
|
+
|
|
282
299
|
// Update Claude Code hooks in .claude/settings.local.json
|
|
283
300
|
updateSpinner.text = 'Updating Claude Code hooks...';
|
|
284
301
|
const hooksResult = await installClaudeHooks(targetPath);
|
|
@@ -314,6 +331,7 @@ export async function updateCommand(options) {
|
|
|
314
331
|
logger.info('Updated files:');
|
|
315
332
|
if (updateTemplates) logger.dim(' ✓ .morph/framework/templates/');
|
|
316
333
|
if (updateStandards) logger.dim(' ✓ .morph/framework/standards/');
|
|
334
|
+
logger.dim(' ✓ .morph/framework/hooks/');
|
|
317
335
|
logger.dim(' ✓ .morph/framework/agents.json');
|
|
318
336
|
if (commandsCopied) logger.dim(' ✓ .claude/commands/');
|
|
319
337
|
|
|
@@ -322,46 +340,13 @@ export async function updateCommand(options) {
|
|
|
322
340
|
logger.dim(' ✓ .claude/agents/ (native subagents refreshed)');
|
|
323
341
|
logger.dim(' ✓ .claude/rules/ (path-scoped rules synced)');
|
|
324
342
|
logger.dim(' ✓ .claude/CLAUDE.md (runtime quick reference)');
|
|
343
|
+
logger.dim(' ✓ ~/.claude/statusline.sh (global statusline synced)');
|
|
325
344
|
logger.dim(' ✓ CLAUDE.md');
|
|
326
345
|
logger.blank();
|
|
327
346
|
logger.info('Your config.json was preserved.');
|
|
328
347
|
logger.dim('Review the updated files for any new features.');
|
|
329
348
|
logger.blank();
|
|
330
349
|
|
|
331
|
-
// Re-analyze project context (unless --skip-detection)
|
|
332
|
-
if (!options.skipDetection && detectClaudeCode()) {
|
|
333
|
-
logger.header('Re-Analyzing Project Context');
|
|
334
|
-
logger.dim('Using Claude Code LLM to update project detection...');
|
|
335
|
-
logger.blank();
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
const orchestrator = new AutoContextOrchestrator();
|
|
339
|
-
const result = await orchestrator.execute(targetPath, {
|
|
340
|
-
skipReview: false,
|
|
341
|
-
fallbackOnError: true,
|
|
342
|
-
wizardMode: options.wizard || false
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
if (result.success) {
|
|
346
|
-
logger.success('Project context re-analyzed and updated');
|
|
347
|
-
logger.dim(' ✓ .morph/project.md updated with latest detection');
|
|
348
|
-
logger.dim(' ✓ .morph/config/config.json updated with stack info');
|
|
349
|
-
} else {
|
|
350
|
-
logger.warn('Context re-analysis incomplete');
|
|
351
|
-
}
|
|
352
|
-
} catch (error) {
|
|
353
|
-
logger.warn(`Auto-detection failed: ${error.message}`);
|
|
354
|
-
logger.dim('Your existing config.json was preserved.');
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
logger.blank();
|
|
358
|
-
} else if (options.skipDetection) {
|
|
359
|
-
logger.dim('Skipped auto-detection (--skip-detection flag)');
|
|
360
|
-
} else {
|
|
361
|
-
logger.warn('⚠️ Claude Code not detected - skipping auto-detection');
|
|
362
|
-
logger.dim('Run with --wizard to configure manually.');
|
|
363
|
-
}
|
|
364
|
-
|
|
365
350
|
} catch (error) {
|
|
366
351
|
updateSpinner.fail('Update failed');
|
|
367
352
|
logger.error(error.message);
|