@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- 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 +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- 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 +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- 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.sh +11 -11
- 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 +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- 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 +1 -42
- 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 +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- 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/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/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- 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/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 +2 -2
- 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 +1 -1
- 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/testing.md +10 -4
- 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/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- 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 +5 -57
- 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.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- 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 +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- 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 +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- 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 +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- 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 +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- 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 +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- 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 +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- 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 +1 -0
- 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 +3 -15
- 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 +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- 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 +2 -24
- 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 +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- 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 +13 -95
- 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 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- 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,130 +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
|
-
* 3.13.0 adds warn-bash-source-write to the Bash PreToolUse chain: a warning,
|
|
60
|
-
* never a block, when a command writes project source through a redirect.
|
|
61
|
-
*/
|
|
62
8
|
const HOOKS_VERSION = '3.13.0';
|
|
63
9
|
|
|
64
|
-
/** Marker for old dispatch.js (v1) */
|
|
65
10
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
66
11
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
* state.json is NOT in deny because the agent edits it directly as a
|
|
71
|
-
* fallback to the state-sync hook (per v7 hybrid state model). The
|
|
72
|
-
* framework/ tree remains read-only — only the installer touches it.
|
|
73
|
-
*
|
|
74
|
-
* `Edit(path)` ONLY — never a matching `Write(path)`. Claude Code matches file
|
|
75
|
-
* permissions on `Edit(path)` rules alone; an `Edit` rule covers every
|
|
76
|
-
* file-editing tool, Write included, while a `Write(path)` rule matches nothing
|
|
77
|
-
* and is reported as a warning on every session start. Verified empirically, not
|
|
78
|
-
* assumed: with `Edit(.morph/framework/**)` as the ONLY deny entry, the Write
|
|
79
|
-
* tool was refused and the target file was never created.
|
|
80
|
-
*/
|
|
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.
|
|
81
14
|
const MORPH_DENY = [
|
|
82
|
-
// Framework files — installer-managed, immutable at runtime
|
|
83
15
|
'Edit(.morph/framework/**)',
|
|
84
|
-
// .claude/CLAUDE.md (managed copy — source is framework/CLAUDE.md)
|
|
85
16
|
'Edit(.claude/CLAUDE.md)',
|
|
86
|
-
// .claude/rules/ (copied from framework/rules/)
|
|
87
17
|
'Edit(.claude/rules/**)',
|
|
88
|
-
// .claude/agents/morph-* (generated by agents-installer)
|
|
89
18
|
'Edit(.claude/agents/morph-*)',
|
|
90
|
-
// .claude/skills/ (copied from framework/skills/)
|
|
91
19
|
'Edit(.claude/skills/**)',
|
|
92
20
|
];
|
|
93
21
|
|
|
94
|
-
/**
|
|
95
|
-
* Native permissions.allow entries managed by MORPH-SPEC.
|
|
96
|
-
*
|
|
97
|
-
* Pre-authorizes operations the agent legitimately performs during the
|
|
98
|
-
* morph-spec flow, so the user does not get permission prompts on every
|
|
99
|
-
* plan.md / slice report write.
|
|
100
|
-
*
|
|
101
|
-
* `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
|
|
102
|
-
* covers Write, and a `Write(path)` rule is inert noise.
|
|
103
|
-
*/
|
|
104
22
|
const MORPH_ALLOW = [
|
|
105
|
-
// Feature artifacts — the agent's working directory during plan/uiux/implement/review
|
|
106
23
|
'Edit(.morph/features/**)',
|
|
107
|
-
// state.json — fallback path when state-sync hook can't cover a case
|
|
108
24
|
'Edit(.morph/state.json)',
|
|
109
|
-
// Long-lived architectural context — agent may reconcile during planning
|
|
110
25
|
'Edit(.morph/context/**)',
|
|
111
|
-
// Folder creation under .morph
|
|
112
26
|
'Bash(mkdir:.morph/**)',
|
|
113
27
|
'Bash(ls:.morph/**)',
|
|
114
|
-
//
|
|
28
|
+
// An auto-mode deny here pauses the review mutation round.
|
|
115
29
|
'Bash(morph-spec mutate:*)',
|
|
116
30
|
'Bash(npx morph-spec mutate:*)',
|
|
117
31
|
];
|
|
118
32
|
|
|
119
|
-
// Backwards-compat alias used by older tests
|
|
120
33
|
const MORPH_PERMISSIONS = MORPH_DENY;
|
|
121
34
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*
|
|
126
|
-
* R2 consolidation: an entry whose group has more than one hook now declares
|
|
127
|
-
* ONE `core/chain.js` command with an `args` array (the hook base names, in
|
|
128
|
-
* the ORDER they run) instead of one command per hook — chain.js imports and
|
|
129
|
-
* calls each hook's `run(payload)` in that same process (see its own doc
|
|
130
|
-
* comment for the block-first-wins / context-concatenation rules). A group
|
|
131
|
-
* with exactly one hook stays a direct command — chaining a single link buys
|
|
132
|
-
* nothing. `args` must stay a SEPARATE field from `command`: `getHookSubpath()`
|
|
133
|
-
* below extracts the installed path from `command` alone via a regex anchored
|
|
134
|
-
* on `claude-code/`, and inlining args into that string would get captured
|
|
135
|
-
* into the quoted path.
|
|
136
|
-
*/
|
|
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.
|
|
137
38
|
const MORPH_HOOKS = [
|
|
138
|
-
// === SessionStart — worktree feature identity (secondary worktree) or
|
|
139
|
-
// active-feature summary (primary root). Fulfills the SessionStart
|
|
140
|
-
// injection role long documented but never wired (inject-morph-context.js).
|
|
141
|
-
// Solo (only hook for this event) — nothing to chain. ===
|
|
142
39
|
{
|
|
143
40
|
event: 'SessionStart',
|
|
144
41
|
matcher: null,
|
|
@@ -149,13 +46,6 @@ const MORPH_HOOKS = [
|
|
|
149
46
|
}]
|
|
150
47
|
},
|
|
151
48
|
|
|
152
|
-
// === PreToolUse: Write|Edit — phase + spec protection (silent on success).
|
|
153
|
-
// Chained in the SAME order the previous two-entry registry ran them:
|
|
154
|
-
// protect-spec-files (locked-file check) before enforce-phase-writes (phase
|
|
155
|
-
// folder check) — a block from either one wins and skips the other. Neither
|
|
156
|
-
// hook declared its own timeout (both are pure sync fs checks with no
|
|
157
|
-
// subprocess calls), so the chain declares none either — same platform
|
|
158
|
-
// default both relied on individually. ===
|
|
159
49
|
{
|
|
160
50
|
event: 'PreToolUse',
|
|
161
51
|
matcher: 'Write|Edit',
|
|
@@ -166,21 +56,8 @@ const MORPH_HOOKS = [
|
|
|
166
56
|
}]
|
|
167
57
|
},
|
|
168
58
|
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
// block-destructive-worktree-removal (dynamic: scans the target for reparse
|
|
172
|
-
// points), so the cheap check short-circuits the filesystem walk. The second
|
|
173
|
-
// one is the only defense against the raw-removal vector that does not depend
|
|
174
|
-
// on someone having read MORPH.md. block-raw-worktree-add is its way-IN
|
|
175
|
-
// counterpart (a raw `git worktree add` builds a tree with no junctions, no
|
|
176
|
-
// ports, no identity) — static, so it costs nothing after the first two.
|
|
177
|
-
// warn-bash-source-write only adds context (source written by redirect skips
|
|
178
|
-
// the Write/Edit guards); after the guards, so a blocked command is not warned.
|
|
179
|
-
// heavy-gate comes after every guard — it takes a slot, and a command a guard
|
|
180
|
-
// blocks must not hold one — and before the rewriter.
|
|
181
|
-
// worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
|
|
182
|
-
// with the worktree's port block, and a command a previous link blocked must
|
|
183
|
-
// 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.
|
|
184
61
|
{
|
|
185
62
|
event: 'PreToolUse',
|
|
186
63
|
matcher: 'Bash',
|
|
@@ -191,12 +68,6 @@ const MORPH_HOOKS = [
|
|
|
191
68
|
}]
|
|
192
69
|
},
|
|
193
70
|
|
|
194
|
-
// === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
|
|
195
|
-
// `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
|
|
196
|
-
// (measured: all three fire it). The hook prints the created path as its
|
|
197
|
-
// last stdout line; a non-zero exit fails the creation. No matcher exists
|
|
198
|
-
// for this event. 120s: it spawns the morph-spec CLI (setup or provision),
|
|
199
|
-
// which runs git, links infra, allocates ports and materializes state. ===
|
|
200
71
|
{
|
|
201
72
|
event: 'WorktreeCreate',
|
|
202
73
|
matcher: null,
|
|
@@ -207,11 +78,7 @@ const MORPH_HOOKS = [
|
|
|
207
78
|
}]
|
|
208
79
|
},
|
|
209
80
|
|
|
210
|
-
//
|
|
211
|
-
// `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
|
|
212
|
-
// git touches the tree; a dirty tree is left alone). Output ignored, always
|
|
213
|
-
// exit 0. Secondary net: in headless runs the event was observed not to
|
|
214
|
-
// fire — block-destructive-worktree-removal remains the primary defense. ===
|
|
81
|
+
// Not fired in headless runs: block-destructive-worktree-removal stays the primary defense.
|
|
215
82
|
{
|
|
216
83
|
event: 'WorktreeRemove',
|
|
217
84
|
matcher: null,
|
|
@@ -222,8 +89,7 @@ const MORPH_HOOKS = [
|
|
|
222
89
|
}]
|
|
223
90
|
},
|
|
224
91
|
|
|
225
|
-
//
|
|
226
|
-
// adds a forbidden comment. ===
|
|
92
|
+
// comment-guard must be the last Write|Edit link: a block ends the chain.
|
|
227
93
|
{
|
|
228
94
|
event: 'PostToolUse',
|
|
229
95
|
matcher: 'Write|Edit',
|
|
@@ -233,16 +99,7 @@ const MORPH_HOOKS = [
|
|
|
233
99
|
}]
|
|
234
100
|
},
|
|
235
101
|
|
|
236
|
-
//
|
|
237
|
-
// state.json updates) then gate-guard. gate-guard runs AFTER state-sync so
|
|
238
|
-
// it sees the gate that state-sync just recorded — now true within a single
|
|
239
|
-
// process too, because state-writer.js's persistFeature() invalidates
|
|
240
|
-
// state-reader.js's per-process loadState() memo on every write (R1.1 added
|
|
241
|
-
// that memo; without the invalidation, a chained gate-guard could read the
|
|
242
|
-
// pre-write snapshot). Both are silent fail-open guardrails; gate-guard only
|
|
243
|
-
// warns (never blocks) when an observed gate action disagrees with the
|
|
244
|
-
// persisted gateDecisions (issue #63). Both links declared 3000ms; sequential
|
|
245
|
-
// 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.
|
|
246
103
|
{
|
|
247
104
|
event: 'PostToolUse',
|
|
248
105
|
matcher: 'Write|Edit|AskUserQuestion',
|
|
@@ -254,15 +111,6 @@ const MORPH_HOOKS = [
|
|
|
254
111
|
}]
|
|
255
112
|
},
|
|
256
113
|
|
|
257
|
-
// === PostToolUse: All — telemetry capture (silent) + loop detection +
|
|
258
|
-
// graph refresh kick. telemetry-log is the single generic vantage point of an
|
|
259
|
-
// NLH: it appends every tool call (skill, sub-agent dispatch, knowledge-base
|
|
260
|
-
// read, …) to .morph/logs/events.jsonl. Silent and fail-open — never blocks,
|
|
261
|
-
// never injects. `morph-spec telemetry` reads the stream back.
|
|
262
|
-
// graph-refresh-kick only spawns a DETACHED worker (never waits on graphify),
|
|
263
|
-
// so it rides this chain instead of costing a process of its own per Bash
|
|
264
|
-
// call. Timeout stays the sum of the first two links (sequential worst case);
|
|
265
|
-
// the kick returns in milliseconds. ===
|
|
266
114
|
{
|
|
267
115
|
event: 'PostToolUse',
|
|
268
116
|
matcher: null,
|
|
@@ -274,9 +122,7 @@ const MORPH_HOOKS = [
|
|
|
274
122
|
}]
|
|
275
123
|
},
|
|
276
124
|
|
|
277
|
-
//
|
|
278
|
-
// duration). loop-detect stays out: its warning is shaped for PostToolUse.
|
|
279
|
-
// 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.
|
|
280
126
|
{
|
|
281
127
|
event: 'PostToolUseFailure',
|
|
282
128
|
matcher: null,
|
|
@@ -289,8 +135,6 @@ const MORPH_HOOKS = [
|
|
|
289
135
|
}]
|
|
290
136
|
},
|
|
291
137
|
|
|
292
|
-
// === PostToolBatch — one event per model turn that used tools. The event
|
|
293
|
-
// takes no matcher. ===
|
|
294
138
|
{
|
|
295
139
|
event: 'PostToolBatch',
|
|
296
140
|
matcher: null,
|
|
@@ -302,8 +146,6 @@ const MORPH_HOOKS = [
|
|
|
302
146
|
}]
|
|
303
147
|
},
|
|
304
148
|
|
|
305
|
-
// === SubagentStop — the sub-agent's token usage, read from its own
|
|
306
|
-
// transcript (the session transcript does not include it). ===
|
|
307
149
|
{
|
|
308
150
|
event: 'SubagentStop',
|
|
309
151
|
matcher: null,
|
|
@@ -315,10 +157,6 @@ const MORPH_HOOKS = [
|
|
|
315
157
|
}]
|
|
316
158
|
},
|
|
317
159
|
|
|
318
|
-
// === UserPromptSubmit — telemetry capture of the user's prompt (silent).
|
|
319
|
-
// The only vantage point on the user's own prompt; completes the execution
|
|
320
|
-
// path alongside sub-agent prompts captured on PostToolUse. Solo (the only
|
|
321
|
-
// hook registered on this event) — nothing to chain. ===
|
|
322
160
|
{
|
|
323
161
|
event: 'UserPromptSubmit',
|
|
324
162
|
matcher: null,
|
|
@@ -329,7 +167,6 @@ const MORPH_HOOKS = [
|
|
|
329
167
|
}]
|
|
330
168
|
},
|
|
331
169
|
|
|
332
|
-
// === Stop — build check + session snapshot. Solo. ===
|
|
333
170
|
{
|
|
334
171
|
event: 'Stop',
|
|
335
172
|
matcher: null,
|
|
@@ -341,25 +178,13 @@ const MORPH_HOOKS = [
|
|
|
341
178
|
];
|
|
342
179
|
|
|
343
180
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* - Idempotent: safe to call multiple times
|
|
347
|
-
* - Removes old v1 dispatch.js entry if present
|
|
348
|
-
* - Tracks installed version via _morph_managed.hooks_version
|
|
349
|
-
*
|
|
350
|
-
* @param {string} targetPath - Project root directory
|
|
351
|
-
* @param {object} [options]
|
|
352
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
353
|
-
* Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
|
|
354
|
-
* o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
|
|
355
|
-
* fim. Previsão e execução percorrem a mesma montagem.
|
|
181
|
+
* Idempotent merge of the MORPH-SPEC hooks into `.claude/settings.local.json`.
|
|
356
182
|
* @returns {Promise<{ installed: number, updated: boolean }>}
|
|
357
183
|
*/
|
|
358
184
|
export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
|
|
359
185
|
const claudeDir = join(targetPath, '.claude');
|
|
360
186
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
361
187
|
|
|
362
|
-
// Read existing settings or start empty
|
|
363
188
|
let settings = {};
|
|
364
189
|
if (existsSync(settingsPath)) {
|
|
365
190
|
try {
|
|
@@ -371,17 +196,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
371
196
|
|
|
372
197
|
settings.hooks = settings.hooks || {};
|
|
373
198
|
|
|
374
|
-
// Check if already at current version
|
|
375
199
|
const managed = settings._morph_managed || {};
|
|
376
200
|
const alreadyCurrent = managed.hooks_version === HOOKS_VERSION;
|
|
377
201
|
|
|
378
|
-
// Remove old v1 dispatch.js entry
|
|
379
202
|
removeOldDispatch(settings);
|
|
380
|
-
|
|
381
|
-
// Remove all morph-managed hooks (clean slate for idempotent update)
|
|
382
203
|
removeMorphHooks(settings);
|
|
383
204
|
|
|
384
|
-
// Install all hooks from the registry
|
|
385
205
|
let installed = 0;
|
|
386
206
|
for (const hookDef of MORPH_HOOKS) {
|
|
387
207
|
const { event, matcher, hooks } = hookDef;
|
|
@@ -395,11 +215,6 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
395
215
|
entry.matcher = matcher;
|
|
396
216
|
}
|
|
397
217
|
entry.hooks = hooks.map(h => {
|
|
398
|
-
// Command hooks: transform path to use $CLAUDE_PROJECT_DIR.
|
|
399
|
-
// Hooks are copied to .morph/framework/hooks/ during `morph-spec init/update`.
|
|
400
|
-
// `args` (chain.js's hook-name list) is appended AFTER the closing quote —
|
|
401
|
-
// getHookSubpath() below only ever sees the bare `h.command` path, never
|
|
402
|
-
// the args, so it can't accidentally capture them into the quoted command.
|
|
403
218
|
const quotedPath = `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/claude-code/${getHookSubpath(h.command)}"`;
|
|
404
219
|
const command = h.args?.length ? `${quotedPath} ${h.args.join(' ')}` : quotedPath;
|
|
405
220
|
return {
|
|
@@ -414,18 +229,12 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
414
229
|
installed++;
|
|
415
230
|
}
|
|
416
231
|
|
|
417
|
-
// Install native permissions (idempotent — preserve user-added entries)
|
|
418
232
|
settings.permissions = settings.permissions || {};
|
|
419
233
|
settings.permissions.deny = settings.permissions.deny || [];
|
|
420
234
|
settings.permissions.allow = settings.permissions.allow || [];
|
|
421
235
|
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
// that LEFT those lists would otherwise survive forever in an existing
|
|
425
|
-
// install — the upgrade path has to name it explicitly to reclaim it.
|
|
426
|
-
// - state.json: was in deny pre-v7, now lives in allow.
|
|
427
|
-
// - Write(...): matched nothing and made Claude Code warn on every session
|
|
428
|
-
// 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.
|
|
429
238
|
const STALE_DENY = [
|
|
430
239
|
'Write(.morph/state.json)',
|
|
431
240
|
'Edit(.morph/state.json)',
|
|
@@ -453,17 +262,14 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
453
262
|
settings.permissions.allow.push(perm);
|
|
454
263
|
}
|
|
455
264
|
|
|
456
|
-
// JSON Schema for IDE auto-complete and validation
|
|
457
265
|
settings['$schema'] = 'https://json.schemastore.org/claude-code-settings.json';
|
|
458
266
|
|
|
459
|
-
// Environment variables — always set MORPH_SPEC_ACTIVE + Agent Teams flag, preserve user additions
|
|
460
267
|
settings.env = {
|
|
461
268
|
...settings.env,
|
|
462
269
|
MORPH_SPEC_ACTIVE: 'true',
|
|
463
270
|
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
464
271
|
};
|
|
465
272
|
|
|
466
|
-
// Attribution — only set if not already customized by user
|
|
467
273
|
if (!settings.attribution) {
|
|
468
274
|
settings.attribution = {
|
|
469
275
|
commit: '🤖 Co-authored by MORPH-SPEC',
|
|
@@ -471,29 +277,21 @@ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}
|
|
|
471
277
|
};
|
|
472
278
|
}
|
|
473
279
|
|
|
474
|
-
// Plans directory for Claude Code plan mode
|
|
475
280
|
if (!settings.plansDirectory) {
|
|
476
281
|
settings.plansDirectory = '.morph/plans';
|
|
477
282
|
}
|
|
478
283
|
|
|
479
|
-
// Track installed hooks version
|
|
480
284
|
settings._morph_managed = settings._morph_managed || {};
|
|
481
285
|
settings._morph_managed.hooks_version = HOOKS_VERSION;
|
|
482
286
|
settings._morph_managed.hooks_installed = [...new Set(MORPH_HOOKS.map(h => h.event))];
|
|
483
287
|
settings._morph_managed.generated_at = new Date().toISOString();
|
|
484
288
|
|
|
485
|
-
// Ensure .claude directory exists
|
|
486
289
|
fx.ensureDir(claudeDir);
|
|
487
290
|
|
|
488
291
|
fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
489
292
|
return { installed, updated: !alreadyCurrent };
|
|
490
293
|
}
|
|
491
294
|
|
|
492
|
-
/**
|
|
493
|
-
* Try to find an existing Python 3 executable.
|
|
494
|
-
*
|
|
495
|
-
* @returns {string|null} Python command/path if found, null otherwise
|
|
496
|
-
*/
|
|
497
295
|
function findPython() {
|
|
498
296
|
if (process.platform !== 'win32') {
|
|
499
297
|
for (const cmd of ['python3', 'python']) {
|
|
@@ -504,15 +302,12 @@ function findPython() {
|
|
|
504
302
|
}
|
|
505
303
|
return null;
|
|
506
304
|
}
|
|
507
|
-
// Windows: try PATH-visible commands first
|
|
508
|
-
// 'py' is the Windows Python Launcher — standard on any Windows Python install
|
|
509
305
|
for (const cmd of ['python3', 'py', 'python']) {
|
|
510
306
|
try {
|
|
511
307
|
const ver = execSync(`${cmd} --version`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
512
308
|
if ((ver + '').includes('Python 3')) return cmd;
|
|
513
309
|
} catch { /* not on PATH */ }
|
|
514
310
|
}
|
|
515
|
-
// Probe known Windows install locations
|
|
516
311
|
const base = join(homedir(), 'AppData', 'Local', 'Programs', 'Python');
|
|
517
312
|
if (existsSync(base)) {
|
|
518
313
|
let dirs;
|
|
@@ -520,7 +315,7 @@ function findPython() {
|
|
|
520
315
|
const py3 = dirs
|
|
521
316
|
.filter(d => /^Python3\d*/i.test(d))
|
|
522
317
|
.sort()
|
|
523
|
-
.reverse();
|
|
318
|
+
.reverse();
|
|
524
319
|
for (const dir of py3) {
|
|
525
320
|
const exe = join(base, dir, 'python.exe');
|
|
526
321
|
if (existsSync(exe)) return exe;
|
|
@@ -529,15 +324,6 @@ function findPython() {
|
|
|
529
324
|
return null;
|
|
530
325
|
}
|
|
531
326
|
|
|
532
|
-
/**
|
|
533
|
-
* Attempt to auto-install Python 3 using the system package manager.
|
|
534
|
-
*
|
|
535
|
-
* On Windows: uses winget (built-in on Windows 10+).
|
|
536
|
-
* On macOS: uses brew if available.
|
|
537
|
-
* On Linux: tries apt-get, then dnf.
|
|
538
|
-
*
|
|
539
|
-
* @returns {boolean} true if installation succeeded
|
|
540
|
-
*/
|
|
541
327
|
function tryInstallPython() {
|
|
542
328
|
const log = (msg) => process.stdout.write(` ${msg}\n`);
|
|
543
329
|
log('⚠ Python 3 not found — attempting automatic installation...');
|
|
@@ -551,7 +337,6 @@ function tryInstallPython() {
|
|
|
551
337
|
} else if (process.platform === 'darwin') {
|
|
552
338
|
execSync('brew install python3', { stdio: 'inherit', timeout: 120_000 });
|
|
553
339
|
} else {
|
|
554
|
-
// Linux — try apt-get first, then dnf
|
|
555
340
|
try {
|
|
556
341
|
execSync('sudo apt-get update -qq && sudo apt-get install -y python3', { stdio: 'inherit', timeout: 120_000 });
|
|
557
342
|
} catch {
|
|
@@ -567,53 +352,26 @@ function tryInstallPython() {
|
|
|
567
352
|
}
|
|
568
353
|
}
|
|
569
354
|
|
|
570
|
-
/**
|
|
571
|
-
* Resolve Python executable suitable for the statusLine command.
|
|
572
|
-
*
|
|
573
|
-
* Tries to find Python 3 on the system. If not found, attempts automatic
|
|
574
|
-
* installation via the system package manager (winget/brew/apt/dnf).
|
|
575
|
-
*
|
|
576
|
-
* @returns {string} Python executable (full path on Windows if needed)
|
|
577
|
-
*/
|
|
578
355
|
function resolvePythonForStatusline() {
|
|
579
|
-
// First pass — check existing installation
|
|
580
356
|
let python = findPython();
|
|
581
357
|
if (python) return python;
|
|
582
358
|
|
|
583
|
-
// Not found — try to install
|
|
584
359
|
if (tryInstallPython()) {
|
|
585
|
-
// Re-probe after installation
|
|
586
360
|
python = findPython();
|
|
587
361
|
if (python) return python;
|
|
588
362
|
}
|
|
589
363
|
|
|
590
|
-
// Last resort: return python3 and let it fail with a clear error
|
|
591
364
|
return 'python3';
|
|
592
365
|
}
|
|
593
366
|
|
|
594
367
|
/**
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
* Copies statusline.sh and statusline.py to ~/.claude/ and registers
|
|
598
|
-
* the statusLine config in ~/.claude/settings.json so it applies to
|
|
599
|
-
* every Claude Code session, not just morph-spec projects.
|
|
600
|
-
*
|
|
601
|
-
* Python 3 must be available as `python3` on PATH (global prerequisite).
|
|
602
|
-
*
|
|
603
|
-
* @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
|
|
604
|
-
* @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
|
|
605
|
-
* @param {object} [options]
|
|
606
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
607
|
-
* Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
|
|
608
|
-
* `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
|
|
609
|
-
* adivinha da palavra "update".
|
|
368
|
+
* Writes OUTSIDE the project (`~/.claude/`), so `--dry-run` must name these effects separately.
|
|
610
369
|
* @returns {Promise<{ installed: boolean, globalDir: string }>}
|
|
611
370
|
*/
|
|
612
371
|
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
|
|
613
372
|
const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
|
|
614
373
|
fx.ensureDir(globalDir);
|
|
615
374
|
|
|
616
|
-
// Copy statusline scripts to ~/.claude/
|
|
617
375
|
const scripts = ['statusline.sh', 'statusline.py'];
|
|
618
376
|
for (const fname of scripts) {
|
|
619
377
|
const src = join(hooksSourceDir, fname);
|
|
@@ -625,21 +383,15 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
625
383
|
}
|
|
626
384
|
}
|
|
627
385
|
|
|
628
|
-
// Register statusLine in ~/.claude/settings.json (global user settings)
|
|
629
386
|
const settingsPath = join(globalDir, 'settings.json');
|
|
630
387
|
|
|
631
|
-
//
|
|
388
|
+
// Claude Code on Windows cannot execute .sh directly.
|
|
632
389
|
const statuslineCommand = process.platform === 'win32'
|
|
633
390
|
? `"${resolvePythonForStatusline()}" "${join(globalDir, 'statusline.py').replace(/\\/g, '/')}"`
|
|
634
391
|
: join(globalDir, 'statusline.sh').replace(/\\/g, '/');
|
|
635
392
|
|
|
636
|
-
// refreshInterval
|
|
637
|
-
//
|
|
638
|
-
// `↻` countdown to the 5h rate-limit reset — the only widget that moves on
|
|
639
|
-
// its own — sits frozen through a long turn. Everything else is event-driven
|
|
640
|
-
// and would be fine without this.
|
|
641
|
-
// Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
|
|
642
|
-
// `~/.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.
|
|
643
395
|
fx.mergeJson(settingsPath, {
|
|
644
396
|
statusLine: {
|
|
645
397
|
type: 'command',
|
|
@@ -651,12 +403,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
651
403
|
return { installed: true, globalDir };
|
|
652
404
|
}
|
|
653
405
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Remove old v1 agent-teams/dispatch.js hook entries.
|
|
658
|
-
* @param {Object} settings
|
|
659
|
-
*/
|
|
660
406
|
function removeOldDispatch(settings) {
|
|
661
407
|
if (!settings.hooks?.PostToolUse) return;
|
|
662
408
|
|
|
@@ -673,12 +419,6 @@ function removeOldDispatch(settings) {
|
|
|
673
419
|
}
|
|
674
420
|
}
|
|
675
421
|
|
|
676
|
-
/**
|
|
677
|
-
* Remove all morph-managed hook entries.
|
|
678
|
-
* Uses _morph:true marker (v2.2+) with legacy path-pattern fallback.
|
|
679
|
-
* Preserves any user-added hooks.
|
|
680
|
-
* @param {Object} settings
|
|
681
|
-
*/
|
|
682
422
|
function removeMorphHooks(settings) {
|
|
683
423
|
const morphPathPattern = /framework\/hooks\/claude-code\//;
|
|
684
424
|
|
|
@@ -686,14 +426,10 @@ function removeMorphHooks(settings) {
|
|
|
686
426
|
if (!Array.isArray(entries)) continue;
|
|
687
427
|
|
|
688
428
|
settings.hooks[event] = entries.filter(entry => {
|
|
689
|
-
// Remove if explicitly marked as morph-managed
|
|
690
429
|
if (entry._morph === true) return false;
|
|
691
430
|
|
|
692
431
|
if (!Array.isArray(entry.hooks)) return true;
|
|
693
|
-
//
|
|
694
|
-
// Note: legacy path only covers command-type hooks (those with a path-matching command).
|
|
695
|
-
// Agent/prompt hooks without _morph marker are NOT removed by this fallback.
|
|
696
|
-
// 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.
|
|
697
433
|
const nonMorphHooks = entry.hooks.filter(h =>
|
|
698
434
|
typeof h.command !== 'string' || !morphPathPattern.test(h.command)
|
|
699
435
|
);
|
|
@@ -706,28 +442,15 @@ function removeMorphHooks(settings) {
|
|
|
706
442
|
}
|
|
707
443
|
}
|
|
708
444
|
|
|
709
|
-
/**
|
|
710
|
-
* Extract hook subpath from a full command string.
|
|
711
|
-
* @param {string} command - e.g., 'node framework/hooks/claude-code/stop/validate-completion.js'
|
|
712
|
-
* @returns {string} e.g., 'stop/validate-completion.js'
|
|
713
|
-
*/
|
|
714
445
|
function getHookSubpath(command) {
|
|
715
446
|
const match = command.match(/claude-code\/(.+)$/);
|
|
716
447
|
return match ? match[1] : command;
|
|
717
448
|
}
|
|
718
449
|
|
|
719
|
-
/**
|
|
720
|
-
* Get the current hooks version.
|
|
721
|
-
* @returns {string}
|
|
722
|
-
*/
|
|
723
450
|
export function getHooksVersion() {
|
|
724
451
|
return HOOKS_VERSION;
|
|
725
452
|
}
|
|
726
453
|
|
|
727
|
-
/**
|
|
728
|
-
* Get the list of hook definitions (for testing/inspection).
|
|
729
|
-
* @returns {Array}
|
|
730
|
-
*/
|
|
731
454
|
export function getHookDefinitions() {
|
|
732
455
|
return MORPH_HOOKS;
|
|
733
456
|
}
|