@polymorphism-tech/morph-spec 8.47.0 → 9.1.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 +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Hooks Installer (v2)
|
|
3
|
-
*
|
|
4
|
-
* Registry-based installer for all MORPH-SPEC Claude Code hooks.
|
|
5
|
-
* Declarative MORPH_HOOKS array defines all hooks.
|
|
6
|
-
* Idempotent merge into .claude/settings.local.json.
|
|
7
|
-
*
|
|
8
|
-
* Called by `morph-spec init` and `morph-spec update`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import { join, dirname } from 'path';
|
|
12
2
|
import { readFile } from 'fs/promises';
|
|
13
3
|
import { existsSync, chmodSync, readdirSync } from 'fs';
|
|
@@ -15,124 +5,37 @@ import { homedir } from 'os';
|
|
|
15
5
|
import { execSync } from 'child_process';
|
|
16
6
|
import { realEffects } from '../lib/installers/effects.js';
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
* Current hooks schema version — bump when hook definitions change.
|
|
20
|
-
*
|
|
21
|
-
* 3.6.0 (R2 of the performance plan) consolidates spawns: every core hook now
|
|
22
|
-
* exports `run(payload)` (framework/hooks/claude-code/core/*.js) and the
|
|
23
|
-
* registry below routes multi-hook event+matcher groups through the new
|
|
24
|
-
* `core/chain.js`, which imports and runs several hooks' `run()` in ONE node
|
|
25
|
-
* process instead of one process per hook. Same events, same matchers, same
|
|
26
|
-
* observable behavior (block/warn/silent) — only the process count per
|
|
27
|
-
* Write/Edit drops (9 → 4). The hook files are re-copied unconditionally
|
|
28
|
-
* (`copyDirectory` defaults to `overwrite: true`), so the bump does not gate
|
|
29
|
-
* delivery — it makes `morph-spec update` report the change honestly instead
|
|
30
|
-
* of "already current".
|
|
31
|
-
*
|
|
32
|
-
* 3.8.0 (worktree governance) registers Claude Code's WorktreeCreate /
|
|
33
|
-
* WorktreeRemove events — the native `claude -w` / EnterWorktree / Agent
|
|
34
|
-
* isolation worktree is replaced by `morph-spec worktree setup|provision`
|
|
35
|
-
* (junctions, port block, primary-root landing) and its removal routed
|
|
36
|
-
* through the safe `worktree remove --if-clean` — and appends
|
|
37
|
-
* block-raw-worktree-add to the PreToolUse:Bash chain.
|
|
38
|
-
*
|
|
39
|
-
* 3.9.0 binds telemetry-log to PostToolUseFailure (a Bash command that exits
|
|
40
|
-
* non-zero fires only that event, so red runs used to vanish from the log),
|
|
41
|
-
* PostToolBatch (once per model turn that used tools) and SubagentStop (the
|
|
42
|
-
* sub-agent's own token usage). The three only record, so they run with
|
|
43
|
-
* `async: true`: a node process per model turn never holds the turn up.
|
|
44
|
-
*
|
|
45
|
-
* 3.10.0 appends graph-refresh-kick to the all-tools PostToolUse chain: after a
|
|
46
|
-
* Bash or PowerShell command that runs git/gh/morph-spec it launches the
|
|
47
|
-
* detached refresh of the ROOT's codebase graph
|
|
48
|
-
* (framework/hooks/shared/graph-refresh.js). `update` re-runs
|
|
49
|
-
* this installer, so installed projects get the refresh without re-running
|
|
50
|
-
* `setup-infra` — the step that never reached them.
|
|
51
|
-
*
|
|
52
|
-
* 3.11.0 writes every `timeout` in seconds, the unit Claude Code reads: the
|
|
53
|
-
* values were milliseconds, so a hung hook held the session for hours (120000
|
|
54
|
-
* on WorktreeCreate is 33 h) instead of the seconds they meant.
|
|
55
|
-
*
|
|
56
|
-
* 3.12.0 adds heavy-gate to the Bash PreToolUse chain and heavy-release to the
|
|
57
|
-
* PostToolUse and PostToolUseFailure chains: the opt-in `heavy.gateBash`.
|
|
58
|
-
*/
|
|
59
|
-
const HOOKS_VERSION = '3.12.0';
|
|
8
|
+
const HOOKS_VERSION = '3.13.0';
|
|
60
9
|
|
|
61
|
-
/** Marker for old dispatch.js (v1) */
|
|
62
10
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
63
11
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
* state.json is NOT in deny because the agent edits it directly as a
|
|
68
|
-
* fallback to the state-sync hook (per v7 hybrid state model). The
|
|
69
|
-
* framework/ tree remains read-only — only the installer touches it.
|
|
70
|
-
*
|
|
71
|
-
* `Edit(path)` ONLY — never a matching `Write(path)`. Claude Code matches file
|
|
72
|
-
* permissions on `Edit(path)` rules alone; an `Edit` rule covers every
|
|
73
|
-
* file-editing tool, Write included, while a `Write(path)` rule matches nothing
|
|
74
|
-
* and is reported as a warning on every session start. Verified empirically, not
|
|
75
|
-
* assumed: with `Edit(.morph/framework/**)` as the ONLY deny entry, the Write
|
|
76
|
-
* tool was refused and the target file was never created.
|
|
77
|
-
*/
|
|
12
|
+
// `Edit(path)` only: an Edit rule also covers Write, while a `Write(path)` rule
|
|
13
|
+
// matches nothing and makes Claude Code warn on every session start.
|
|
78
14
|
const MORPH_DENY = [
|
|
79
|
-
// Framework files — installer-managed, immutable at runtime
|
|
80
15
|
'Edit(.morph/framework/**)',
|
|
81
|
-
// .claude/CLAUDE.md (managed copy — source is framework/CLAUDE.md)
|
|
82
16
|
'Edit(.claude/CLAUDE.md)',
|
|
83
|
-
// .claude/rules/ (copied from framework/rules/)
|
|
84
17
|
'Edit(.claude/rules/**)',
|
|
85
|
-
// .claude/agents/morph-* (generated by agents-installer)
|
|
86
18
|
'Edit(.claude/agents/morph-*)',
|
|
87
|
-
// .claude/skills/ (copied from framework/skills/)
|
|
88
19
|
'Edit(.claude/skills/**)',
|
|
89
20
|
];
|
|
90
21
|
|
|
91
|
-
/**
|
|
92
|
-
* Native permissions.allow entries managed by MORPH-SPEC.
|
|
93
|
-
*
|
|
94
|
-
* Pre-authorizes operations the agent legitimately performs during the
|
|
95
|
-
* morph-spec flow, so the user does not get permission prompts on every
|
|
96
|
-
* proposal.md / spec.md / tasks.json write.
|
|
97
|
-
*
|
|
98
|
-
* `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
|
|
99
|
-
* covers Write, and a `Write(path)` rule is inert noise.
|
|
100
|
-
*/
|
|
101
22
|
const MORPH_ALLOW = [
|
|
102
|
-
// Feature artifacts — the agent's working directory during proposal/plan/implement/review
|
|
103
23
|
'Edit(.morph/features/**)',
|
|
104
|
-
// state.json — fallback path when state-sync hook can't cover a case
|
|
105
24
|
'Edit(.morph/state.json)',
|
|
106
|
-
// Long-lived architectural context — agent may reconcile during brainstorming
|
|
107
25
|
'Edit(.morph/context/**)',
|
|
108
|
-
// Folder creation under .morph
|
|
109
26
|
'Bash(mkdir:.morph/**)',
|
|
110
27
|
'Bash(ls:.morph/**)',
|
|
28
|
+
// An auto-mode deny here pauses the review mutation round.
|
|
29
|
+
'Bash(morph-spec mutate:*)',
|
|
30
|
+
'Bash(npx morph-spec mutate:*)',
|
|
111
31
|
];
|
|
112
32
|
|
|
113
|
-
// Backwards-compat alias used by older tests
|
|
114
33
|
const MORPH_PERMISSIONS = MORPH_DENY;
|
|
115
34
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*
|
|
120
|
-
* R2 consolidation: an entry whose group has more than one hook now declares
|
|
121
|
-
* ONE `core/chain.js` command with an `args` array (the hook base names, in
|
|
122
|
-
* the ORDER they run) instead of one command per hook — chain.js imports and
|
|
123
|
-
* calls each hook's `run(payload)` in that same process (see its own doc
|
|
124
|
-
* comment for the block-first-wins / context-concatenation rules). A group
|
|
125
|
-
* with exactly one hook stays a direct command — chaining a single link buys
|
|
126
|
-
* nothing. `args` must stay a SEPARATE field from `command`: `getHookSubpath()`
|
|
127
|
-
* below extracts the installed path from `command` alone via a regex anchored
|
|
128
|
-
* on `claude-code/`, and inlining args into that string would get captured
|
|
129
|
-
* into the quoted path.
|
|
130
|
-
*/
|
|
35
|
+
// A multi-hook group is ONE `core/chain.js` command whose `args` list the hooks in
|
|
36
|
+
// run order (block-first-wins). `args` stays separate from `command`: getHookSubpath()
|
|
37
|
+
// would capture inlined args into the quoted path. `timeout` is in SECONDS.
|
|
131
38
|
const MORPH_HOOKS = [
|
|
132
|
-
// === SessionStart — worktree feature identity (secondary worktree) or
|
|
133
|
-
// active-feature summary (primary root). Fulfills the SessionStart
|
|
134
|
-
// injection role long documented but never wired (inject-morph-context.js).
|
|
135
|
-
// Solo (only hook for this event) — nothing to chain. ===
|
|
136
39
|
{
|
|
137
40
|
event: 'SessionStart',
|
|
138
41
|
matcher: null,
|
|
@@ -143,13 +46,6 @@ const MORPH_HOOKS = [
|
|
|
143
46
|
}]
|
|
144
47
|
},
|
|
145
48
|
|
|
146
|
-
// === PreToolUse: Write|Edit — phase + spec protection (silent on success).
|
|
147
|
-
// Chained in the SAME order the previous two-entry registry ran them:
|
|
148
|
-
// protect-spec-files (locked-file check) before enforce-phase-writes (phase
|
|
149
|
-
// folder check) — a block from either one wins and skips the other. Neither
|
|
150
|
-
// hook declared its own timeout (both are pure sync fs checks with no
|
|
151
|
-
// subprocess calls), so the chain declares none either — same platform
|
|
152
|
-
// default both relied on individually. ===
|
|
153
49
|
{
|
|
154
50
|
event: 'PreToolUse',
|
|
155
51
|
matcher: 'Write|Edit',
|
|
@@ -160,35 +56,18 @@ const MORPH_HOOKS = [
|
|
|
160
56
|
}]
|
|
161
57
|
},
|
|
162
58
|
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// block-destructive-worktree-removal (dynamic: scans the target for reparse
|
|
166
|
-
// points), so the cheap check short-circuits the filesystem walk. The second
|
|
167
|
-
// one is the only defense against the raw-removal vector that does not depend
|
|
168
|
-
// on someone having read MORPH.md. block-raw-worktree-add is its way-IN
|
|
169
|
-
// counterpart (a raw `git worktree add` builds a tree with no junctions, no
|
|
170
|
-
// ports, no identity) — static, so it costs nothing after the first two.
|
|
171
|
-
// heavy-gate comes after every guard — it takes a slot, and a command a guard
|
|
172
|
-
// blocks must not hold one — and before the rewriter.
|
|
173
|
-
// worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
|
|
174
|
-
// with the worktree's port block, and a command a previous link blocked must
|
|
175
|
-
// never be rewritten. Block-first-wins guarantees it never sees one.
|
|
59
|
+
// heavy-gate after every guard (a blocked command must not hold a slot);
|
|
60
|
+
// worktree-env-inject LAST so a command a previous link blocked is never rewritten.
|
|
176
61
|
{
|
|
177
62
|
event: 'PreToolUse',
|
|
178
63
|
matcher: 'Bash',
|
|
179
64
|
hooks: [{
|
|
180
65
|
type: 'command',
|
|
181
66
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
182
|
-
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'heavy-gate', 'worktree-env-inject']
|
|
67
|
+
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
|
|
183
68
|
}]
|
|
184
69
|
},
|
|
185
70
|
|
|
186
|
-
// === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
|
|
187
|
-
// `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
|
|
188
|
-
// (measured: all three fire it). The hook prints the created path as its
|
|
189
|
-
// last stdout line; a non-zero exit fails the creation. No matcher exists
|
|
190
|
-
// for this event. 120s: it spawns the morph-spec CLI (setup or provision),
|
|
191
|
-
// which runs git, links infra, allocates ports and materializes state. ===
|
|
192
71
|
{
|
|
193
72
|
event: 'WorktreeCreate',
|
|
194
73
|
matcher: null,
|
|
@@ -199,11 +78,7 @@ const MORPH_HOOKS = [
|
|
|
199
78
|
}]
|
|
200
79
|
},
|
|
201
80
|
|
|
202
|
-
//
|
|
203
|
-
// `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
|
|
204
|
-
// git touches the tree; a dirty tree is left alone). Output ignored, always
|
|
205
|
-
// exit 0. Secondary net: in headless runs the event was observed not to
|
|
206
|
-
// fire — block-destructive-worktree-removal remains the primary defense. ===
|
|
81
|
+
// Not fired in headless runs: block-destructive-worktree-removal stays the primary defense.
|
|
207
82
|
{
|
|
208
83
|
event: 'WorktreeRemove',
|
|
209
84
|
matcher: null,
|
|
@@ -214,28 +89,17 @@ const MORPH_HOOKS = [
|
|
|
214
89
|
}]
|
|
215
90
|
},
|
|
216
91
|
|
|
217
|
-
//
|
|
218
|
-
// comment-guard LAST: it blocks, and a block ends the chain. ===
|
|
92
|
+
// comment-guard must be the last Write|Edit link: a block ends the chain.
|
|
219
93
|
{
|
|
220
94
|
event: 'PostToolUse',
|
|
221
95
|
matcher: 'Write|Edit',
|
|
222
96
|
hooks: [{
|
|
223
97
|
type: 'command',
|
|
224
|
-
command: 'node framework/hooks/claude-code/core/
|
|
225
|
-
args: ['tasks-json-guard', 'trace-autogen', 'comment-guard']
|
|
98
|
+
command: 'node framework/hooks/claude-code/core/comment-guard.js'
|
|
226
99
|
}]
|
|
227
100
|
},
|
|
228
101
|
|
|
229
|
-
//
|
|
230
|
-
// state.json updates) then gate-guard. gate-guard runs AFTER state-sync so
|
|
231
|
-
// it sees the gate that state-sync just recorded — now true within a single
|
|
232
|
-
// process too, because state-writer.js's persistFeature() invalidates
|
|
233
|
-
// state-reader.js's per-process loadState() memo on every write (R1.1 added
|
|
234
|
-
// that memo; without the invalidation, a chained gate-guard could read the
|
|
235
|
-
// pre-write snapshot). Both are silent fail-open guardrails; gate-guard only
|
|
236
|
-
// warns (never blocks) when an observed gate action disagrees with the
|
|
237
|
-
// persisted gateDecisions (issue #63). Both links declared 3000ms; sequential
|
|
238
|
-
// execution means the worst case is additive, so the chain declares the sum. ===
|
|
102
|
+
// gate-guard after state-sync so it sees the gate state-sync just recorded.
|
|
239
103
|
{
|
|
240
104
|
event: 'PostToolUse',
|
|
241
105
|
matcher: 'Write|Edit|AskUserQuestion',
|
|
@@ -247,15 +111,6 @@ const MORPH_HOOKS = [
|
|
|
247
111
|
}]
|
|
248
112
|
},
|
|
249
113
|
|
|
250
|
-
// === PostToolUse: All — telemetry capture (silent) + loop detection +
|
|
251
|
-
// graph refresh kick. telemetry-log is the single generic vantage point of an
|
|
252
|
-
// NLH: it appends every tool call (skill, sub-agent dispatch, knowledge-base
|
|
253
|
-
// read, …) to .morph/logs/events.jsonl. Silent and fail-open — never blocks,
|
|
254
|
-
// never injects. `morph-spec telemetry` reads the stream back.
|
|
255
|
-
// graph-refresh-kick only spawns a DETACHED worker (never waits on graphify),
|
|
256
|
-
// so it rides this chain instead of costing a process of its own per Bash
|
|
257
|
-
// call. Timeout stays the sum of the first two links (sequential worst case);
|
|
258
|
-
// the kick returns in milliseconds. ===
|
|
259
114
|
{
|
|
260
115
|
event: 'PostToolUse',
|
|
261
116
|
matcher: null,
|
|
@@ -267,9 +122,7 @@ const MORPH_HOOKS = [
|
|
|
267
122
|
}]
|
|
268
123
|
},
|
|
269
124
|
|
|
270
|
-
//
|
|
271
|
-
// duration). loop-detect stays out: its warning is shaped for PostToolUse.
|
|
272
|
-
// heavy-release gives back the slot of a heavy Bash call that failed.
|
|
125
|
+
// Recording-only bindings are `async: true`; a hook that decides is never async.
|
|
273
126
|
{
|
|
274
127
|
event: 'PostToolUseFailure',
|
|
275
128
|
matcher: null,
|
|
@@ -282,8 +135,6 @@ const MORPH_HOOKS = [
|
|
|
282
135
|
}]
|
|
283
136
|
},
|
|
284
137
|
|
|
285
|
-
// === PostToolBatch — one event per model turn that used tools. The event
|
|
286
|
-
// takes no matcher. ===
|
|
287
138
|
{
|
|
288
139
|
event: 'PostToolBatch',
|
|
289
140
|
matcher: null,
|
|
@@ -295,8 +146,6 @@ const MORPH_HOOKS = [
|
|
|
295
146
|
}]
|
|
296
147
|
},
|
|
297
148
|
|
|
298
|
-
// === SubagentStop — the sub-agent's token usage, read from its own
|
|
299
|
-
// transcript (the session transcript does not include it). ===
|
|
300
149
|
{
|
|
301
150
|
event: 'SubagentStop',
|
|
302
151
|
matcher: null,
|
|
@@ -308,10 +157,6 @@ const MORPH_HOOKS = [
|
|
|
308
157
|
}]
|
|
309
158
|
},
|
|
310
159
|
|
|
311
|
-
// === UserPromptSubmit — telemetry capture of the user's prompt (silent).
|
|
312
|
-
// The only vantage point on the user's own prompt; completes the execution
|
|
313
|
-
// path alongside sub-agent prompts captured on PostToolUse. Solo (the only
|
|
314
|
-
// hook registered on this event) — nothing to chain. ===
|
|
315
160
|
{
|
|
316
161
|
event: 'UserPromptSubmit',
|
|
317
162
|
matcher: null,
|
|
@@ -322,7 +167,6 @@ const MORPH_HOOKS = [
|
|
|
322
167
|
}]
|
|
323
168
|
},
|
|
324
169
|
|
|
325
|
-
// === Stop — build check + session snapshot. Solo. ===
|
|
326
170
|
{
|
|
327
171
|
event: 'Stop',
|
|
328
172
|
matcher: null,
|
|
@@ -334,25 +178,13 @@ const MORPH_HOOKS = [
|
|
|
334
178
|
];
|
|
335
179
|
|
|
336
180
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* - Idempotent: safe to call multiple times
|
|
340
|
-
* - Removes old v1 dispatch.js entry if present
|
|
341
|
-
* - Tracks installed version via _morph_managed.hooks_version
|
|
342
|
-
*
|
|
343
|
-
* @param {string} targetPath - Project root directory
|
|
344
|
-
* @param {object} [options]
|
|
345
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
346
|
-
* Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
|
|
347
|
-
* o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
|
|
348
|
-
* fim. Previsão e execução percorrem a mesma montagem.
|
|
181
|
+
* Idempotent merge of the MORPH-SPEC hooks into `.claude/settings.local.json`.
|
|
349
182
|
* @returns {Promise<{ installed: number, updated: boolean }>}
|
|
350
183
|
*/
|
|
351
184
|
export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
|
|
352
185
|
const claudeDir = join(targetPath, '.claude');
|
|
353
186
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
354
187
|
|
|
355
|
-
// Read existing settings or start empty
|
|
356
188
|
let settings = {};
|
|
357
189
|
if (existsSync(settingsPath)) {
|
|
358
190
|
try {
|
|
@@ -364,17 +196,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
364
196
|
|
|
365
197
|
settings.hooks = settings.hooks || {};
|
|
366
198
|
|
|
367
|
-
// Check if already at current version
|
|
368
199
|
const managed = settings._morph_managed || {};
|
|
369
200
|
const alreadyCurrent = managed.hooks_version === HOOKS_VERSION;
|
|
370
201
|
|
|
371
|
-
// Remove old v1 dispatch.js entry
|
|
372
202
|
removeOldDispatch(settings);
|
|
373
|
-
|
|
374
|
-
// Remove all morph-managed hooks (clean slate for idempotent update)
|
|
375
203
|
removeMorphHooks(settings);
|
|
376
204
|
|
|
377
|
-
// Install all hooks from the registry
|
|
378
205
|
let installed = 0;
|
|
379
206
|
for (const hookDef of MORPH_HOOKS) {
|
|
380
207
|
const { event, matcher, hooks } = hookDef;
|
|
@@ -388,11 +215,6 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
388
215
|
entry.matcher = matcher;
|
|
389
216
|
}
|
|
390
217
|
entry.hooks = hooks.map(h => {
|
|
391
|
-
// Command hooks: transform path to use $CLAUDE_PROJECT_DIR.
|
|
392
|
-
// Hooks are copied to .morph/framework/hooks/ during `morph-spec init/update`.
|
|
393
|
-
// `args` (chain.js's hook-name list) is appended AFTER the closing quote —
|
|
394
|
-
// getHookSubpath() below only ever sees the bare `h.command` path, never
|
|
395
|
-
// the args, so it can't accidentally capture them into the quoted command.
|
|
396
218
|
const quotedPath = `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/claude-code/${getHookSubpath(h.command)}"`;
|
|
397
219
|
const command = h.args?.length ? `${quotedPath} ${h.args.join(' ')}` : quotedPath;
|
|
398
220
|
return {
|
|
@@ -407,18 +229,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
407
229
|
installed++;
|
|
408
230
|
}
|
|
409
231
|
|
|
410
|
-
// Install native permissions (idempotent — preserve user-added entries)
|
|
411
232
|
settings.permissions = settings.permissions || {};
|
|
412
233
|
settings.permissions.deny = settings.permissions.deny || [];
|
|
413
234
|
settings.permissions.allow = settings.permissions.allow || [];
|
|
414
235
|
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
// that LEFT those lists would otherwise survive forever in an existing
|
|
418
|
-
// install — the upgrade path has to name it explicitly to reclaim it.
|
|
419
|
-
// - state.json: was in deny pre-v7, now lives in allow.
|
|
420
|
-
// - Write(...): matched nothing and made Claude Code warn on every session
|
|
421
|
-
// start; the paired Edit(...) rule is what actually enforced them.
|
|
236
|
+
// An entry that left MORPH_* would survive forever in an existing install
|
|
237
|
+
// unless named here.
|
|
422
238
|
const STALE_DENY = [
|
|
423
239
|
'Write(.morph/state.json)',
|
|
424
240
|
'Edit(.morph/state.json)',
|
|
@@ -446,17 +262,14 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
446
262
|
settings.permissions.allow.push(perm);
|
|
447
263
|
}
|
|
448
264
|
|
|
449
|
-
// JSON Schema for IDE auto-complete and validation
|
|
450
265
|
settings['$schema'] = 'https://json.schemastore.org/claude-code-settings.json';
|
|
451
266
|
|
|
452
|
-
// Environment variables — always set MORPH_SPEC_ACTIVE + Agent Teams flag, preserve user additions
|
|
453
267
|
settings.env = {
|
|
454
268
|
...settings.env,
|
|
455
269
|
MORPH_SPEC_ACTIVE: 'true',
|
|
456
270
|
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
457
271
|
};
|
|
458
272
|
|
|
459
|
-
// Attribution — only set if not already customized by user
|
|
460
273
|
if (!settings.attribution) {
|
|
461
274
|
settings.attribution = {
|
|
462
275
|
commit: '🤖 Co-authored by MORPH-SPEC',
|
|
@@ -464,29 +277,21 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
464
277
|
};
|
|
465
278
|
}
|
|
466
279
|
|
|
467
|
-
// Plans directory for Claude Code plan mode
|
|
468
280
|
if (!settings.plansDirectory) {
|
|
469
281
|
settings.plansDirectory = '.morph/plans';
|
|
470
282
|
}
|
|
471
283
|
|
|
472
|
-
// Track installed hooks version
|
|
473
284
|
settings._morph_managed = settings._morph_managed || {};
|
|
474
285
|
settings._morph_managed.hooks_version = HOOKS_VERSION;
|
|
475
286
|
settings._morph_managed.hooks_installed = [...new Set(MORPH_HOOKS.map(h => h.event))];
|
|
476
287
|
settings._morph_managed.generated_at = new Date().toISOString();
|
|
477
288
|
|
|
478
|
-
// Ensure .claude directory exists
|
|
479
289
|
fx.ensureDir(claudeDir);
|
|
480
290
|
|
|
481
291
|
fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
482
292
|
return { installed, updated: !alreadyCurrent };
|
|
483
293
|
}
|
|
484
294
|
|
|
485
|
-
/**
|
|
486
|
-
* Try to find an existing Python 3 executable.
|
|
487
|
-
*
|
|
488
|
-
* @returns {string|null} Python command/path if found, null otherwise
|
|
489
|
-
*/
|
|
490
295
|
function findPython() {
|
|
491
296
|
if (process.platform !== 'win32') {
|
|
492
297
|
for (const cmd of ['python3', 'python']) {
|
|
@@ -497,15 +302,12 @@ function findPython() {
|
|
|
497
302
|
}
|
|
498
303
|
return null;
|
|
499
304
|
}
|
|
500
|
-
// Windows: try PATH-visible commands first
|
|
501
|
-
// 'py' is the Windows Python Launcher — standard on any Windows Python install
|
|
502
305
|
for (const cmd of ['python3', 'py', 'python']) {
|
|
503
306
|
try {
|
|
504
307
|
const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
505
308
|
if ((ver + '').includes('Python 3')) return cmd;
|
|
506
309
|
} catch { /* not on PATH */ }
|
|
507
310
|
}
|
|
508
|
-
// Probe known Windows install locations
|
|
509
311
|
const base = join(homedir(), 'AppData', 'Local', 'Programs', 'Python');
|
|
510
312
|
if (existsSync(base)) {
|
|
511
313
|
let dirs;
|
|
@@ -513,7 +315,7 @@ function findPython() {
|
|
|
513
315
|
const py3 = dirs
|
|
514
316
|
.filter(d => /^Python3\d*/i.test(d))
|
|
515
317
|
.sort()
|
|
516
|
-
.reverse();
|
|
318
|
+
.reverse();
|
|
517
319
|
for (const dir of py3) {
|
|
518
320
|
const exe = join(base, dir, 'python.exe');
|
|
519
321
|
if (existsSync(exe)) return exe;
|
|
@@ -522,15 +324,6 @@ function findPython() {
|
|
|
522
324
|
return null;
|
|
523
325
|
}
|
|
524
326
|
|
|
525
|
-
/**
|
|
526
|
-
* Attempt to auto-install Python 3 using the system package manager.
|
|
527
|
-
*
|
|
528
|
-
* On Windows: uses winget (built-in on Windows 10+).
|
|
529
|
-
* On macOS: uses brew if available.
|
|
530
|
-
* On Linux: tries apt-get, then dnf.
|
|
531
|
-
*
|
|
532
|
-
* @returns {boolean} true if installation succeeded
|
|
533
|
-
*/
|
|
534
327
|
function tryInstallPython() {
|
|
535
328
|
const log = (msg) => process.stdout.write(` ${msg}\n`);
|
|
536
329
|
log('⚠ Python 3 not found — attempting automatic installation...');
|
|
@@ -544,7 +337,6 @@ function tryInstallPython() {
|
|
|
544
337
|
} else if (process.platform === 'darwin') {
|
|
545
338
|
execSync('brew install python3', { stdio: 'inherit', timeout: 120_000 });
|
|
546
339
|
} else {
|
|
547
|
-
// Linux — try apt-get first, then dnf
|
|
548
340
|
try {
|
|
549
341
|
execSync('sudo apt-get update -qq && sudo apt-get install -y python3', { stdio: 'inherit', timeout: 120_000 });
|
|
550
342
|
} catch {
|
|
@@ -560,53 +352,26 @@ function tryInstallPython() {
|
|
|
560
352
|
}
|
|
561
353
|
}
|
|
562
354
|
|
|
563
|
-
/**
|
|
564
|
-
* Resolve Python executable suitable for the statusLine command.
|
|
565
|
-
*
|
|
566
|
-
* Tries to find Python 3 on the system. If not found, attempts automatic
|
|
567
|
-
* installation via the system package manager (winget/brew/apt/dnf).
|
|
568
|
-
*
|
|
569
|
-
* @returns {string} Python executable (full path on Windows if needed)
|
|
570
|
-
*/
|
|
571
355
|
function resolvePythonForStatusline() {
|
|
572
|
-
// First pass — check existing installation
|
|
573
356
|
let python = findPython();
|
|
574
357
|
if (python) return python;
|
|
575
358
|
|
|
576
|
-
// Not found — try to install
|
|
577
359
|
if (tryInstallPython()) {
|
|
578
|
-
// Re-probe after installation
|
|
579
360
|
python = findPython();
|
|
580
361
|
if (python) return python;
|
|
581
362
|
}
|
|
582
363
|
|
|
583
|
-
// Last resort: return python3 and let it fail with a clear error
|
|
584
364
|
return 'python3';
|
|
585
365
|
}
|
|
586
366
|
|
|
587
367
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
* Copies statusline.sh and statusline.py to ~/.claude/ and registers
|
|
591
|
-
* the statusLine config in ~/.claude/settings.json so it applies to
|
|
592
|
-
* every Claude Code session, not just morph-spec projects.
|
|
593
|
-
*
|
|
594
|
-
* Python 3 must be available as `python3` on PATH (global prerequisite).
|
|
595
|
-
*
|
|
596
|
-
* @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
|
|
597
|
-
* @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
|
|
598
|
-
* @param {object} [options]
|
|
599
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
600
|
-
* Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
|
|
601
|
-
* `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
|
|
602
|
-
* adivinha da palavra "update".
|
|
368
|
+
* Writes OUTSIDE the project (`~/.claude/`), so `--dry-run` must name these effects separately.
|
|
603
369
|
* @returns {Promise<{ installed: boolean, globalDir: string }>}
|
|
604
370
|
*/
|
|
605
371
|
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
|
|
606
372
|
const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
|
|
607
373
|
fx.ensureDir(globalDir);
|
|
608
374
|
|
|
609
|
-
// Copy statusline scripts to ~/.claude/
|
|
610
375
|
const scripts = ['statusline.sh', 'statusline.py'];
|
|
611
376
|
for (const fname of scripts) {
|
|
612
377
|
const src = join(hooksSourceDir, fname);
|
|
@@ -618,21 +383,15 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
618
383
|
}
|
|
619
384
|
}
|
|
620
385
|
|
|
621
|
-
// Register statusLine in ~/.claude/settings.json (global user settings)
|
|
622
386
|
const settingsPath = join(globalDir, 'settings.json');
|
|
623
387
|
|
|
624
|
-
//
|
|
388
|
+
// Claude Code on Windows cannot execute .sh directly.
|
|
625
389
|
const statuslineCommand = process.platform === 'win32'
|
|
626
390
|
? `"${resolvePythonForStatusline()}" "${join(globalDir, 'statusline.py').replace(/\\/g, '/')}"`
|
|
627
391
|
: join(globalDir, 'statusline.sh').replace(/\\/g, '/');
|
|
628
392
|
|
|
629
|
-
// refreshInterval
|
|
630
|
-
//
|
|
631
|
-
// `↻` countdown to the 5h rate-limit reset — the only widget that moves on
|
|
632
|
-
// its own — sits frozen through a long turn. Everything else is event-driven
|
|
633
|
-
// and would be fine without this.
|
|
634
|
-
// Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
|
|
635
|
-
// `~/.claude/settings.json` é do usuário e sobrevive intocado.
|
|
393
|
+
// Without refreshInterval the status line only re-renders on events, freezing
|
|
394
|
+
// the rate-limit countdown through a long turn. Shallow merge: only `statusLine` is ours.
|
|
636
395
|
fx.mergeJson(settingsPath, {
|
|
637
396
|
statusLine: {
|
|
638
397
|
type: 'command',
|
|
@@ -644,12 +403,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
644
403
|
return { installed: true, globalDir };
|
|
645
404
|
}
|
|
646
405
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Remove old v1 agent-teams/dispatch.js hook entries.
|
|
651
|
-
* @param {Object} settings
|
|
652
|
-
*/
|
|
653
406
|
function removeOldDispatch(settings) {
|
|
654
407
|
if (!settings.hooks?.PostToolUse) return;
|
|
655
408
|
|
|
@@ -666,12 +419,6 @@ function removeOldDispatch(settings) {
|
|
|
666
419
|
}
|
|
667
420
|
}
|
|
668
421
|
|
|
669
|
-
/**
|
|
670
|
-
* Remove all morph-managed hook entries.
|
|
671
|
-
* Uses _morph:true marker (v2.2+) with legacy path-pattern fallback.
|
|
672
|
-
* Preserves any user-added hooks.
|
|
673
|
-
* @param {Object} settings
|
|
674
|
-
*/
|
|
675
422
|
function removeMorphHooks(settings) {
|
|
676
423
|
const morphPathPattern = /framework\/hooks\/claude-code\//;
|
|
677
424
|
|
|
@@ -679,14 +426,10 @@ function removeMorphHooks(settings) {
|
|
|
679
426
|
if (!Array.isArray(entries)) continue;
|
|
680
427
|
|
|
681
428
|
settings.hooks[event] = entries.filter(entry => {
|
|
682
|
-
// Remove if explicitly marked as morph-managed
|
|
683
429
|
if (entry._morph === true) return false;
|
|
684
430
|
|
|
685
431
|
if (!Array.isArray(entry.hooks)) return true;
|
|
686
|
-
//
|
|
687
|
-
// Note: legacy path only covers command-type hooks (those with a path-matching command).
|
|
688
|
-
// Agent/prompt hooks without _morph marker are NOT removed by this fallback.
|
|
689
|
-
// This is acceptable since all morph hooks since v2.2.0 carry the _morph marker.
|
|
432
|
+
// Fallback for entries written before the `_morph` marker existed.
|
|
690
433
|
const nonMorphHooks = entry.hooks.filter(h =>
|
|
691
434
|
typeof h.command !== 'string' || !morphPathPattern.test(h.command)
|
|
692
435
|
);
|
|
@@ -699,28 +442,15 @@ function removeMorphHooks(settings) {
|
|
|
699
442
|
}
|
|
700
443
|
}
|
|
701
444
|
|
|
702
|
-
/**
|
|
703
|
-
* Extract hook subpath from a full command string.
|
|
704
|
-
* @param {string} command - e.g., 'node framework/hooks/claude-code/stop/validate-completion.js'
|
|
705
|
-
* @returns {string} e.g., 'stop/validate-completion.js'
|
|
706
|
-
*/
|
|
707
445
|
function getHookSubpath(command) {
|
|
708
446
|
const match = command.match(/claude-code\/(.+)$/);
|
|
709
447
|
return match ? match[1] : command;
|
|
710
448
|
}
|
|
711
449
|
|
|
712
|
-
/**
|
|
713
|
-
* Get the current hooks version.
|
|
714
|
-
* @returns {string}
|
|
715
|
-
*/
|
|
716
450
|
export function getHooksVersion() {
|
|
717
451
|
return HOOKS_VERSION;
|
|
718
452
|
}
|
|
719
453
|
|
|
720
|
-
/**
|
|
721
|
-
* Get the list of hook definitions (for testing/inspection).
|
|
722
|
-
* @returns {Array}
|
|
723
|
-
*/
|
|
724
454
|
export function getHookDefinitions() {
|
|
725
455
|
return MORPH_HOOKS;
|
|
726
456
|
}
|