@polymorphism-tech/morph-spec 4.7.0 → 4.7.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/.morph/.morphversion +5 -0
- package/.morph/analytics/threads-log.jsonl +5 -0
- package/.morph/config/config.json +8 -0
- package/.morph/framework/agents.json +1815 -0
- package/.morph/framework/hooks/README.md +205 -0
- package/.morph/framework/hooks/claude-code/notification/approval-reminder.js +54 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/dispatch.js +83 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +42 -0
- package/.morph/framework/hooks/claude-code/pre-compact/save-morph-context.js +61 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +71 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +58 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +64 -0
- package/.morph/framework/hooks/claude-code/session-start/inject-morph-context.js +94 -0
- package/.morph/framework/hooks/claude-code/statusline.py +538 -0
- package/.morph/framework/hooks/claude-code/statusline.sh +7 -0
- package/.morph/framework/hooks/claude-code/stop/validate-completion.js +88 -0
- package/.morph/framework/hooks/claude-code/user-prompt/enrich-prompt.js +91 -0
- package/.morph/framework/hooks/git/commit-msg/conventional-commits.sh +33 -0
- package/.morph/framework/hooks/git/pre-commit/agents.sh +25 -0
- package/.morph/framework/hooks/git/pre-commit/orchestrator.sh +64 -0
- package/.morph/framework/hooks/git/pre-commit/specs.sh +50 -0
- package/.morph/framework/hooks/git/pre-push/run-tests.sh +44 -0
- package/.morph/framework/hooks/shared/hook-response.js +45 -0
- package/.morph/framework/hooks/shared/phase-utils.js +129 -0
- package/.morph/framework/hooks/shared/state-reader.js +138 -0
- package/.morph/framework/hooks/shared/stdin-reader.js +26 -0
- package/.morph/framework/standards/STANDARDS.json +933 -0
- package/.morph/framework/standards/ai-agents/blazor-ui.md +364 -0
- package/.morph/framework/standards/ai-agents/production.md +415 -0
- package/.morph/framework/standards/ai-agents/setup.md +418 -0
- package/.morph/framework/standards/ai-agents/team-orchestration.md +479 -0
- package/.morph/framework/standards/ai-agents/workflows.md +354 -0
- package/.morph/framework/standards/architecture/ddd/aggregates.md +120 -0
- package/.morph/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
- package/.morph/framework/standards/architecture/ddd/complexity-levels.md +108 -0
- package/.morph/framework/standards/architecture/ddd/entities.md +99 -0
- package/.morph/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
- package/.morph/framework/standards/architecture/ddd/value-objects.md +124 -0
- package/.morph/framework/standards/backend/api/minimal-api.md +494 -0
- package/.morph/framework/standards/backend/api/rest.md +492 -0
- package/.morph/framework/standards/backend/api/validation.md +88 -0
- package/.morph/framework/standards/backend/authentication/passkeys.md +428 -0
- package/.morph/framework/standards/backend/database/ef-core.md +199 -0
- package/.morph/framework/standards/backend/database/migrations.md +393 -0
- package/.morph/framework/standards/backend/database/postgresql/database.md +352 -0
- package/.morph/framework/standards/backend/database/repository-patterns.md +528 -0
- package/.morph/framework/standards/backend/database/vector-search-rag.md +541 -0
- package/.morph/framework/standards/backend/dotnet/async.md +366 -0
- package/.morph/framework/standards/backend/dotnet/core.md +117 -0
- package/.morph/framework/standards/backend/dotnet/di.md +439 -0
- package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/.morph/framework/standards/backend/integrations/resend/resend-email.md +385 -0
- package/.morph/framework/standards/context/analytics.md +96 -0
- package/.morph/framework/standards/context/bundles.md +110 -0
- package/.morph/framework/standards/context/priming.md +78 -0
- package/.morph/framework/standards/core/architecture.md +185 -0
- package/.morph/framework/standards/core/coding.md +214 -0
- package/.morph/framework/standards/core/git-branching-strategy.md +403 -0
- package/.morph/framework/standards/core/git.md +185 -0
- package/.morph/framework/standards/core/testing.md +295 -0
- package/.morph/framework/standards/data/nosql/blob-storage.md +102 -0
- package/.morph/framework/standards/data/nosql/cache/redis.md +97 -0
- package/.morph/framework/standards/data/nosql/cosmos-db.md +118 -0
- package/.morph/framework/standards/data/vector-search/azure-ai-search.md +121 -0
- package/.morph/framework/standards/data/vector-search/rag-chunking.md +104 -0
- package/.morph/framework/standards/frontend/blazor/design-checklist.md +222 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui.md +137 -0
- package/.morph/framework/standards/frontend/blazor/html-conversion.md +184 -0
- package/.morph/framework/standards/frontend/blazor/lifecycle.md +195 -0
- package/.morph/framework/standards/frontend/blazor/pitfalls.md +198 -0
- package/.morph/framework/standards/frontend/blazor/state.md +191 -0
- package/.morph/framework/standards/frontend/design-system/animations.md +151 -0
- package/.morph/framework/standards/frontend/design-system/naming.md +64 -0
- package/.morph/framework/standards/frontend/nextjs/app-router.md +123 -0
- package/.morph/framework/standards/frontend/nextjs/components.md +132 -0
- package/.morph/framework/standards/frontend/nextjs/data-fetching.md +126 -0
- package/.morph/framework/standards/frontend/nextjs/forms.md +128 -0
- package/.morph/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
- package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +215 -0
- package/.morph/framework/standards/frontend/nextjs/project-structure.md +102 -0
- package/.morph/framework/standards/frontend/nextjs/state-management.md +72 -0
- package/.morph/framework/standards/frontend/nextjs/testing.md +111 -0
- package/.morph/framework/standards/infrastructure/azure/azure.md +624 -0
- package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/.morph/framework/standards/infrastructure/azure/services/functions.md +486 -0
- package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/.morph/framework/standards/infrastructure/azure/services/storage.md +407 -0
- package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/.morph/framework/standards/integration/api/graphql.md +91 -0
- package/.morph/framework/standards/integration/api/grpc.md +114 -0
- package/.morph/framework/standards/integration/api/rest-design.md +95 -0
- package/.morph/framework/standards/integration/event-driven/cqrs.md +101 -0
- package/.morph/framework/standards/integration/event-driven/event-sourcing.md +124 -0
- package/.morph/framework/standards/integration/event-driven/service-bus.md +95 -0
- package/.morph/framework/standards/integration/mcp/mcp-tools.md +384 -0
- package/.morph/framework/standards/observability/logging.md +131 -0
- package/.morph/framework/standards/observability/metrics.md +121 -0
- package/.morph/framework/standards/observability/monitoring.md +114 -0
- package/.morph/framework/standards/observability/tracing.md +132 -0
- package/.morph/framework/standards/workflows/parallel-execution.md +112 -0
- package/.morph/framework/standards/workflows/thread-management.md +113 -0
- package/.morph/framework/templates/.idea/morph-templates.xml +92 -0
- package/.morph/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/.morph/framework/templates/IDE-SNIPPETS.md +266 -0
- package/.morph/framework/templates/README.md +814 -0
- package/.morph/framework/templates/REGISTRY.json +1888 -0
- package/.morph/framework/templates/code/dotnet/backend/repository.cs +141 -0
- package/.morph/framework/templates/code/dotnet/backend/service.cs +139 -0
- package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +25 -0
- package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/README.md +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
- package/.morph/framework/templates/code/dotnet/database/migration.cs +83 -0
- package/.morph/framework/templates/code/dotnet/frontend/component.razor +239 -0
- package/.morph/framework/templates/code/dotnet/jobs/agent.cs +163 -0
- package/.morph/framework/templates/code/dotnet/jobs/job.cs +171 -0
- package/.morph/framework/templates/code/dotnet/test.cs +239 -0
- package/.morph/framework/templates/code/sql/rls-policy.sql +57 -0
- package/.morph/framework/templates/code/sql/supabase-migration.sql +100 -0
- package/.morph/framework/templates/code/sql/supabase-migration.template.sql +113 -0
- package/.morph/framework/templates/code/typescript/contracts.ts +168 -0
- package/.morph/framework/templates/context/CONTEXT-FEATURE.md +276 -0
- package/.morph/framework/templates/context/CONTEXT.md +181 -0
- package/.morph/framework/templates/docs/clarifications.md +253 -0
- package/.morph/framework/templates/docs/onboarding.md +123 -0
- package/.morph/framework/templates/docs/proposal.md +182 -0
- package/.morph/framework/templates/docs/schema-analysis.md +119 -0
- package/.morph/framework/templates/docs/spec.md +198 -0
- package/.morph/framework/templates/docs/ui-components.md +124 -0
- package/.morph/framework/templates/docs/ui-design-system.md +76 -0
- package/.morph/framework/templates/docs/ui-flows.md +167 -0
- package/.morph/framework/templates/docs/ui-mockups.md +98 -0
- package/.morph/framework/templates/docs/user-stories.md +34 -0
- package/.morph/framework/templates/examples/design-system-examples.md +357 -0
- package/.morph/framework/templates/examples/spec-examples.md +90 -0
- package/.morph/framework/templates/feature/decisions.md +187 -0
- package/.morph/framework/templates/feature/recap.md +146 -0
- package/.morph/framework/templates/feature/tasks.md +199 -0
- package/.morph/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
- package/.morph/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
- package/.morph/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
- package/.morph/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
- package/.morph/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
- package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +82 -0
- package/.morph/framework/templates/infrastructure/azure/README.md +286 -0
- package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +63 -0
- package/.morph/framework/templates/infrastructure/azure/app-service.bicep +164 -0
- package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +49 -0
- package/.morph/framework/templates/infrastructure/azure/container-app.bicep +156 -0
- package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +426 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +229 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.sh +208 -0
- package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +91 -0
- package/.morph/framework/templates/infrastructure/azure/main.bicep +189 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +103 -0
- package/.morph/framework/templates/infrastructure/azure/storage.bicep +106 -0
- package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +58 -0
- package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -0
- package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +54 -0
- package/.morph/framework/templates/infrastructure/github/README.md +593 -0
- package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +22 -0
- package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -0
- package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +61 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +31 -0
- package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -0
- package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -0
- package/.morph/framework/templates/integrations/asaas-client.cs +387 -0
- package/.morph/framework/templates/integrations/asaas-webhook.cs +351 -0
- package/.morph/framework/templates/integrations/azure-identity-config.cs +288 -0
- package/.morph/framework/templates/integrations/clerk-config.cs +258 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +78 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +97 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +36 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +126 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +43 -0
- package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -0
- package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -0
- package/.morph/framework/templates/project-structure/dotnet-ddd.md +70 -0
- package/.morph/framework/templates/saas/subscription.cs +347 -0
- package/.morph/framework/templates/saas/tenant.cs +338 -0
- package/.morph/framework/templates/state.template.json +17 -0
- package/.morph/framework/templates/ui/FluentDesignTheme.cs +149 -0
- package/.morph/framework/templates/ui/MudTheme.cs +281 -0
- package/.morph/framework/templates/ui/design-system.css +226 -0
- package/.morph/logs/tool-failures.log +17 -0
- package/.morph/memory/pre-compact-2026-02-24T17-43-30-049Z.json +16 -0
- package/.morph/plans/eager-watching-bunny.md +105 -0
- package/.morph/plans/temporal-seeking-nebula.md +45 -0
- package/.morph/state.json +48 -0
- package/CLAUDE.md +1 -1
- package/README.md +119 -99
- package/bin/morph-spec.js +0 -9
- package/framework/CLAUDE.md +1 -1
- package/framework/hooks/README.md +10 -6
- 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/stop/validate-completion.js +1 -1
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
- package/package.json +1 -1
- package/src/commands/project/init.js +15 -42
- package/src/commands/project/update.js +22 -37
- package/src/lib/installers/mcp-installer.js +18 -3
- package/src/utils/hooks-installer.js +5 -15
- package/src/commands/project/detect.js +0 -114
package/framework/CLAUDE.md
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
proposal → setup → [uiux] → design → clarify → tasks → implement → [sync]
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Use `morph-spec
|
|
59
|
+
Use `morph-spec status {feature}` to see current phase and pending approval gates.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -47,13 +47,13 @@ framework/hooks/
|
|
|
47
47
|
|-------|------|------|---------|
|
|
48
48
|
| **SessionStart** | inject-morph-context.js | Inject context | Shows active feature, phase, pending approvals |
|
|
49
49
|
| **UserPromptSubmit** | enrich-prompt.js | Inject context | Warns about wrong-phase work, injects commands |
|
|
50
|
-
| **PreToolUse** (Write\|Edit) |
|
|
50
|
+
| **PreToolUse** (Write\|Edit) | _(native permissions.deny)_ | Block | Blocks edits to state.json and .morph/framework/ |
|
|
51
51
|
| **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to spec files after approval |
|
|
52
52
|
| **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
|
|
53
53
|
| **PreToolUse** (Bash) | _(prompt-type inline guard)_ | Block | Blocks `rm -rf .morph/` and direct state edits via Claude's reasoning |
|
|
54
|
-
| **PostToolUse** (Write) | track-output-creation.js | Auto-state | Marks outputs as created when files are written |
|
|
55
54
|
| **PostToolUse** (Bash) | dispatch.js | Dispatch | Triggers checkpoints on task completion |
|
|
56
|
-
| **
|
|
55
|
+
| **PostToolUseFailure** | handle-tool-failure.js | Logging | Appends structured JSON to .morph/logs/tool-failures.log |
|
|
56
|
+
| **Stop** | validate-completion.js | Advisory | Warns about incomplete tasks/missing outputs/pending gates |
|
|
57
57
|
| **PreCompact** | save-morph-context.js | Snapshot | Saves state to .morph/memory/ before compaction |
|
|
58
58
|
| **Notification** | approval-reminder.js | Advisory | Reminds about pending approval gates |
|
|
59
59
|
|
|
@@ -69,6 +69,10 @@ framework/hooks/
|
|
|
69
69
|
|
|
70
70
|
Hooks are automatically installed by `morph-spec init` and updated by `morph-spec update`.
|
|
71
71
|
|
|
72
|
+
During init/update, the entire `framework/hooks/` directory is copied to `.morph/framework/hooks/`.
|
|
73
|
+
Hook commands in `.claude/settings.local.json` reference `$CLAUDE_PROJECT_DIR/.morph/framework/hooks/`
|
|
74
|
+
so they work correctly in any project regardless of how morph-spec was installed.
|
|
75
|
+
|
|
72
76
|
The installer writes to `.claude/settings.local.json`:
|
|
73
77
|
|
|
74
78
|
```json
|
|
@@ -122,10 +126,10 @@ Claude calls Write/Edit tool
|
|
|
122
126
|
↓
|
|
123
127
|
Claude Code sends JSON to stdin: { tool_input: { file_path: "..." } }
|
|
124
128
|
↓
|
|
125
|
-
|
|
129
|
+
[native permissions.deny]
|
|
126
130
|
├── Is .morph/state.json? → BLOCK (use CLI)
|
|
127
131
|
├── Is .morph/framework/**? → BLOCK (read-only)
|
|
128
|
-
└── Other →
|
|
132
|
+
└── Other → continue
|
|
129
133
|
↓
|
|
130
134
|
protect-spec-files.js
|
|
131
135
|
├── Is in .morph/features/{feature}/?
|
|
@@ -198,4 +202,4 @@ morph-spec doctor --reset
|
|
|
198
202
|
|
|
199
203
|
---
|
|
200
204
|
|
|
201
|
-
*MORPH-SPEC v4.
|
|
205
|
+
*MORPH-SPEC v4.5.0 — Hooks Architecture v2.5*
|
|
@@ -46,7 +46,7 @@ function dispatch(command) {
|
|
|
46
46
|
const completed = (feature.tasks.completed || 0) + 1; // +1 because task-done hasn't executed yet
|
|
47
47
|
if (completed > 0 && completed % 3 === 0) {
|
|
48
48
|
const checkpointNum = Math.floor(completed / 3);
|
|
49
|
-
run(`
|
|
49
|
+
run(`morph-spec checkpoint-save ${featureName} --note "Auto-checkpoint #${checkpointNum} at task ${completed}"`);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
} catch {
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
// Check pending approval gates
|
|
59
59
|
if (feature.approvalGates) {
|
|
60
60
|
const pendingGates = Object.entries(feature.approvalGates)
|
|
61
|
-
.filter(([, gate]) => !gate.approved && gate.timestamp
|
|
61
|
+
.filter(([, gate]) => !gate.approved && !gate.timestamp)
|
|
62
62
|
.map(([name]) => name);
|
|
63
63
|
|
|
64
64
|
// Only warn about gates relevant to current/past phases
|
|
@@ -24,7 +24,7 @@ try {
|
|
|
24
24
|
const payload = await readStdin();
|
|
25
25
|
if (!payload) pass();
|
|
26
26
|
|
|
27
|
-
const userPrompt = payload?.user_prompt || payload?.content || '';
|
|
27
|
+
const userPrompt = payload?.prompt || payload?.user_prompt || payload?.content || '';
|
|
28
28
|
if (!userPrompt || userPrompt.length < 3) pass();
|
|
29
29
|
|
|
30
30
|
const promptLower = userPrompt.toLowerCase();
|
package/package.json
CHANGED
|
@@ -18,8 +18,6 @@ import { saveProjectMorphVersion, getInstalledCLIVersion } from '../../utils/ver
|
|
|
18
18
|
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
19
19
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
20
20
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
21
|
-
import { AutoContextOrchestrator } from '../../core/orchestrator.js';
|
|
22
|
-
import { detectClaudeCode } from '../../llm/environment-detector.js';
|
|
23
21
|
import inquirer from 'inquirer';
|
|
24
22
|
import { detectProject } from '../../lib/detectors/index.js';
|
|
25
23
|
import { detectClaudeConfig, mapMcpsToPhases, formatConfigSummary } from '../../lib/detectors/claude-config-detector.js';
|
|
@@ -102,11 +100,11 @@ export async function initCommand(options) {
|
|
|
102
100
|
|
|
103
101
|
## Overview
|
|
104
102
|
|
|
105
|
-
This file
|
|
103
|
+
This file describes your project context for MORPH-SPEC agents.
|
|
106
104
|
|
|
107
105
|
## Stack
|
|
108
106
|
|
|
109
|
-
|
|
107
|
+
Edit this file to describe your project context.
|
|
110
108
|
|
|
111
109
|
## Technologies
|
|
112
110
|
|
|
@@ -139,6 +137,14 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
139
137
|
logger.dim(' ✓ Copied framework standards: core/, backend/, frontend/, infrastructure/');
|
|
140
138
|
}
|
|
141
139
|
|
|
140
|
+
// 6c. Copy hooks to .morph/framework/hooks/ (required for hook commands at runtime)
|
|
141
|
+
spinner.text = 'Copying hooks...';
|
|
142
|
+
const hooksSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'hooks');
|
|
143
|
+
const hooksDest = join(frameworkDestDir, 'hooks');
|
|
144
|
+
if (await pathExists(hooksSrc)) {
|
|
145
|
+
await copyDirectory(hooksSrc, hooksDest);
|
|
146
|
+
}
|
|
147
|
+
|
|
142
148
|
// 7. Copy agents.json (sourced from framework/ — canonical single source of truth)
|
|
143
149
|
spinner.text = 'Copying agents configuration...';
|
|
144
150
|
const agentsSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'agents.json');
|
|
@@ -467,10 +473,9 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
467
473
|
logger.blank();
|
|
468
474
|
logger.header('Next Steps');
|
|
469
475
|
|
|
470
|
-
logger.step(1, '
|
|
471
|
-
logger.step(2, '
|
|
472
|
-
logger.step(3, '
|
|
473
|
-
logger.step(4, 'Start your first feature:');
|
|
476
|
+
logger.step(1, 'Review .morph/config/config.json');
|
|
477
|
+
logger.step(2, 'Open project in VS Code with Claude Code');
|
|
478
|
+
logger.step(3, 'Start your first feature:');
|
|
474
479
|
logger.blank();
|
|
475
480
|
logger.box([
|
|
476
481
|
'Ask Claude Code to implement a feature'
|
|
@@ -480,7 +485,7 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
480
485
|
logger.info('Files installed:');
|
|
481
486
|
logger.dim(` ✓ CLAUDE.md`);
|
|
482
487
|
logger.dim(` ✓ .morph/config/ (config.json)`);
|
|
483
|
-
logger.dim(` ✓ .morph/framework/ (agents.json, standards/, templates/)`);
|
|
488
|
+
logger.dim(` ✓ .morph/framework/ (agents.json, standards/, templates/, hooks/)`);
|
|
484
489
|
logger.dim(` ✓ .morph/context/ (project context)`);
|
|
485
490
|
logger.dim(` ✓ .morph/features/ (feature outputs)`);
|
|
486
491
|
if (commandsCopied) {
|
|
@@ -503,42 +508,10 @@ Run \`morph-spec detect\` to analyze your project.
|
|
|
503
508
|
|
|
504
509
|
logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
|
|
505
510
|
logger.dim(' ✓ .claude/agents/ (native subagents installed)');
|
|
511
|
+
logger.dim(' ✓ ~/.claude/statusline.sh (global statusline installed)');
|
|
506
512
|
|
|
507
513
|
logger.blank();
|
|
508
514
|
|
|
509
|
-
// 13. LLM-based auto-detect project context (if Claude Code is available and not --skip-detection)
|
|
510
|
-
if (!options.skipDetection && detectClaudeCode()) {
|
|
511
|
-
logger.blank();
|
|
512
|
-
logger.header('Auto-Detecting Project Context');
|
|
513
|
-
logger.dim('Using Claude Code LLM to analyze your project...');
|
|
514
|
-
logger.blank();
|
|
515
|
-
|
|
516
|
-
try {
|
|
517
|
-
const orchestrator = new AutoContextOrchestrator();
|
|
518
|
-
const result = await orchestrator.execute(targetPath, {
|
|
519
|
-
skipReview: false,
|
|
520
|
-
fallbackOnError: true,
|
|
521
|
-
wizardMode: options.wizard || false
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
if (result.success) {
|
|
525
|
-
logger.success('Project context detected and saved to .morph/project.md');
|
|
526
|
-
} else {
|
|
527
|
-
logger.warn('Auto-detection incomplete. Run "morph-spec update" to retry.');
|
|
528
|
-
}
|
|
529
|
-
} catch (error) {
|
|
530
|
-
logger.warn(`Auto-detection failed: ${error.message}`);
|
|
531
|
-
logger.dim('You can run "morph-spec update" later to re-analyze your project.');
|
|
532
|
-
}
|
|
533
|
-
} else if (options.skipDetection) {
|
|
534
|
-
logger.dim('\nSkipped auto-detection (--skip-detection flag)');
|
|
535
|
-
logger.dim('Run "morph-spec update" later to analyze your project.');
|
|
536
|
-
} else {
|
|
537
|
-
logger.warn('\n⚠️ Claude Code not detected');
|
|
538
|
-
logger.dim('Auto-detection requires Claude Code CLI.');
|
|
539
|
-
logger.dim('Run "morph-spec update --wizard" to configure manually.');
|
|
540
|
-
}
|
|
541
|
-
|
|
542
515
|
// Suggest tutorial for first-time users
|
|
543
516
|
if (integrationsCreated) {
|
|
544
517
|
logger.blank();
|
|
@@ -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);
|
|
@@ -96,6 +96,21 @@ export function checkPrerequisites(mcpEntry) {
|
|
|
96
96
|
return results;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Patch MCP config for the current platform.
|
|
101
|
+
* On Windows, npx must be invoked via `cmd /C` to avoid ENOENT errors.
|
|
102
|
+
* @param {Object} config - MCP server config { command, args, env? }
|
|
103
|
+
* @returns {Object} Patched config (new object, original unmodified)
|
|
104
|
+
*/
|
|
105
|
+
export function patchConfigForPlatform(config) {
|
|
106
|
+
if (process.platform !== 'win32' || config.command !== 'npx') return config;
|
|
107
|
+
return {
|
|
108
|
+
...config,
|
|
109
|
+
command: 'cmd',
|
|
110
|
+
args: ['/C', 'npx', ...(config.args || [])]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
99
114
|
/**
|
|
100
115
|
* Install auto-installable MCPs (no credentials, no prerequisites)
|
|
101
116
|
* @param {string} targetPath - Project root directory
|
|
@@ -106,12 +121,12 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
|
|
|
106
121
|
const servers = {};
|
|
107
122
|
|
|
108
123
|
for (const [name, entry] of Object.entries(mcpsToInstall)) {
|
|
109
|
-
|
|
124
|
+
let config = { ...entry.install.config };
|
|
110
125
|
// Only include env if it has actual values
|
|
111
126
|
if (config.env && Object.values(config.env).every(v => !v)) {
|
|
112
127
|
delete config.env;
|
|
113
128
|
}
|
|
114
|
-
servers[name] = config;
|
|
129
|
+
servers[name] = patchConfigForPlatform(config);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
return installMcpServers(targetPath, servers);
|
|
@@ -126,7 +141,7 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
|
|
|
126
141
|
* @returns {Promise<Object>} Result from installMcpServers
|
|
127
142
|
*/
|
|
128
143
|
export async function installMcpWithCredentials(targetPath, name, mcpEntry, credentialValues) {
|
|
129
|
-
const config = { ...mcpEntry.install.config };
|
|
144
|
+
const config = patchConfigForPlatform({ ...mcpEntry.install.config });
|
|
130
145
|
config.env = { ...config.env, ...credentialValues };
|
|
131
146
|
return installMcpServers(targetPath, { [name]: config });
|
|
132
147
|
}
|
|
@@ -14,7 +14,7 @@ import { existsSync, chmodSync } from 'fs';
|
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
|
|
16
16
|
/** Current hooks schema version — bump when hook definitions change */
|
|
17
|
-
const HOOKS_VERSION = '2.
|
|
17
|
+
const HOOKS_VERSION = '2.5.0';
|
|
18
18
|
|
|
19
19
|
/** Marker for old dispatch.js (v1) */
|
|
20
20
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -104,19 +104,8 @@ Otherwise respond: {"ok": true}`
|
|
|
104
104
|
event: 'Stop',
|
|
105
105
|
matcher: null,
|
|
106
106
|
hooks: [{
|
|
107
|
-
type: '
|
|
108
|
-
|
|
109
|
-
1. Read the file .morph/state.json to find features with status "in_progress".
|
|
110
|
-
2. For each in_progress feature, check if required output files for the current phase exist and are non-empty.
|
|
111
|
-
- proposal phase: .morph/features/{feature}/0-proposal/proposal.md
|
|
112
|
-
- design phase: .morph/features/{feature}/1-design/spec.md
|
|
113
|
-
- tasks phase: .morph/features/{feature}/3-tasks/tasks.md
|
|
114
|
-
- implement phase: check tasks.completed vs tasks.total from state.json
|
|
115
|
-
3. If all required outputs exist and tasks are complete, return {"ok": true}.
|
|
116
|
-
4. If any required output is missing or empty, return {"ok": false, "reason": "Missing output: <path>"}.
|
|
117
|
-
5. If state.json does not exist or no feature is in_progress, return {"ok": true}.
|
|
118
|
-
Do NOT modify any files. Read only.`,
|
|
119
|
-
timeout: 60
|
|
107
|
+
type: 'command',
|
|
108
|
+
command: 'node framework/hooks/claude-code/stop/validate-completion.js'
|
|
120
109
|
}]
|
|
121
110
|
},
|
|
122
111
|
|
|
@@ -209,9 +198,10 @@ export async function installClaudeHooks(targetPath) {
|
|
|
209
198
|
return agentHook;
|
|
210
199
|
}
|
|
211
200
|
// Command hooks: transform path to use $CLAUDE_PROJECT_DIR
|
|
201
|
+
// Hooks are copied to .morph/framework/hooks/ during `morph-spec init/update`
|
|
212
202
|
return {
|
|
213
203
|
type: h.type,
|
|
214
|
-
command: `node "$CLAUDE_PROJECT_DIR/framework/hooks/claude-code/${getHookSubpath(h.command)}"`,
|
|
204
|
+
command: `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/claude-code/${getHookSubpath(h.command)}"`,
|
|
215
205
|
...(h.timeout !== undefined ? { timeout: h.timeout } : {})
|
|
216
206
|
};
|
|
217
207
|
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { logger } from '../../utils/logger.js';
|
|
5
|
-
import { detectProject, getDetectionSummary } from '../../lib/detectors/index.js';
|
|
6
|
-
import { ensureDir, writeFile, readJson, writeJson, pathExists } from '../../utils/file-copier.js';
|
|
7
|
-
|
|
8
|
-
export async function detectCommand(options) {
|
|
9
|
-
const targetPath = options.path || process.cwd();
|
|
10
|
-
|
|
11
|
-
logger.header('MORPH-SPEC Project Detection');
|
|
12
|
-
logger.dim(`Analyzing: ${targetPath}`);
|
|
13
|
-
logger.blank();
|
|
14
|
-
|
|
15
|
-
const spinner = ora('Detecting project structure...').start();
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
// Run detection
|
|
19
|
-
const results = await detectProject(targetPath, {
|
|
20
|
-
structure: true,
|
|
21
|
-
config: true,
|
|
22
|
-
conversation: true,
|
|
23
|
-
generateStandards: true
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
spinner.succeed('Detection complete!');
|
|
27
|
-
logger.blank();
|
|
28
|
-
|
|
29
|
-
// Display summary
|
|
30
|
-
logger.header('Detection Results');
|
|
31
|
-
logger.blank();
|
|
32
|
-
|
|
33
|
-
// Stack
|
|
34
|
-
logger.info(`Stack: ${chalk.cyan(results.structure.stack)}`);
|
|
35
|
-
logger.info(`Architecture: ${chalk.cyan(results.structure.architecture)}`);
|
|
36
|
-
if (results.structure.uiLibrary) {
|
|
37
|
-
logger.info(`UI Library: ${chalk.cyan(results.structure.uiLibrary)}`);
|
|
38
|
-
}
|
|
39
|
-
logger.blank();
|
|
40
|
-
|
|
41
|
-
// Technologies
|
|
42
|
-
if (results.config.technologies.length > 0) {
|
|
43
|
-
logger.header('Technologies:');
|
|
44
|
-
results.config.technologies.forEach(tech => {
|
|
45
|
-
logger.dim(` - ${tech}`);
|
|
46
|
-
});
|
|
47
|
-
logger.blank();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Patterns
|
|
51
|
-
if (results.structure.patterns.length > 0) {
|
|
52
|
-
logger.header('Patterns:');
|
|
53
|
-
results.structure.patterns.forEach(pattern => {
|
|
54
|
-
logger.dim(` - ${pattern}`);
|
|
55
|
-
});
|
|
56
|
-
logger.blank();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Recommendations
|
|
60
|
-
if (results.inferred.recommendations.length > 0) {
|
|
61
|
-
logger.header('Recommendations:');
|
|
62
|
-
results.inferred.recommendations.forEach(rec => {
|
|
63
|
-
logger.warn(` ⚠ ${rec}`);
|
|
64
|
-
});
|
|
65
|
-
logger.blank();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Save results if requested
|
|
69
|
-
if (options.save !== false) {
|
|
70
|
-
spinner.start('Saving detection results...');
|
|
71
|
-
|
|
72
|
-
const outputDir = join(targetPath, '.morph', 'project', 'context');
|
|
73
|
-
await ensureDir(outputDir);
|
|
74
|
-
|
|
75
|
-
// Save detection log
|
|
76
|
-
const logPath = join(outputDir, 'detection-log.md');
|
|
77
|
-
const summary = getDetectionSummary(results);
|
|
78
|
-
await writeFile(logPath, summary);
|
|
79
|
-
|
|
80
|
-
// Save inferred standards
|
|
81
|
-
const standardsDir = join(targetPath, '.morph', 'project', 'standards');
|
|
82
|
-
await ensureDir(standardsDir);
|
|
83
|
-
|
|
84
|
-
const standardsPath = join(standardsDir, 'inferred.md');
|
|
85
|
-
await writeFile(standardsPath, results.inferred.markdown);
|
|
86
|
-
|
|
87
|
-
// Update config.json with detected stack and architecture
|
|
88
|
-
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
89
|
-
if (await pathExists(configPath)) {
|
|
90
|
-
const projectConfig = await readJson(configPath);
|
|
91
|
-
projectConfig.project = projectConfig.project || {};
|
|
92
|
-
projectConfig.project.stack = results.structure.stack;
|
|
93
|
-
projectConfig.project.architecture = results.structure.architecture;
|
|
94
|
-
await writeJson(configPath, projectConfig);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
spinner.succeed('Results saved!');
|
|
98
|
-
logger.dim(` - ${logPath}`);
|
|
99
|
-
logger.dim(` - ${standardsPath}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Verbose output
|
|
103
|
-
if (options.verbose) {
|
|
104
|
-
logger.blank();
|
|
105
|
-
logger.header('Detailed Results (JSON):');
|
|
106
|
-
console.log(JSON.stringify(results, null, 2));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
} catch (error) {
|
|
110
|
-
spinner.fail('Detection failed');
|
|
111
|
-
logger.error(error.message);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
}
|