@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
package/src/utils/banner.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/utils/banner.js
|
|
2
1
|
import chalk from "chalk";
|
|
3
2
|
import { type as osType, release as osRelease, homedir } from "os";
|
|
4
3
|
import { join } from "path";
|
|
@@ -7,7 +6,6 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
7
6
|
const BRAND = chalk.hex("#7C3AED");
|
|
8
7
|
const DIM = chalk.dim;
|
|
9
8
|
|
|
10
|
-
/** Purple gradient, one tone per logo line (top → bottom). */
|
|
11
9
|
const GRADIENT = [
|
|
12
10
|
"#6D28D9",
|
|
13
11
|
"#7C3AED",
|
|
@@ -26,16 +24,8 @@ const LOGO_LINES = [
|
|
|
26
24
|
"╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝",
|
|
27
25
|
];
|
|
28
26
|
|
|
29
|
-
const LOGO_WIDTH = LOGO_LINES[0].length;
|
|
27
|
+
const LOGO_WIDTH = LOGO_LINES[0].length;
|
|
30
28
|
|
|
31
|
-
/**
|
|
32
|
-
* The morph-spec logo: gradient "MORPH" block art with a S P E C + version
|
|
33
|
-
* rule underneath. Single source for the CLI help banner and the install
|
|
34
|
-
* banner — one drawing, everywhere.
|
|
35
|
-
*
|
|
36
|
-
* @param {string} version - e.g. "7.7.2"
|
|
37
|
-
* @returns {string}
|
|
38
|
-
*/
|
|
39
29
|
export function renderLogo(version) {
|
|
40
30
|
const art = LOGO_LINES.map(
|
|
41
31
|
(line, i) => " " + chalk.hex(GRADIENT[i])(line),
|
|
@@ -56,19 +46,8 @@ export function renderLogo(version) {
|
|
|
56
46
|
return `\n${art}\n${rule}\n`;
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* after an install/upgrade. npm ≥7 swallows lifecycle-script output, so the
|
|
62
|
-
* postinstall banner is invisible on a normal `npm i -g` — this is the
|
|
63
|
-
* fallback that guarantees the next-steps guidance is actually seen.
|
|
64
|
-
*
|
|
65
|
-
* Marker: ~/.claude/morph-spec-banner-version (last version shown).
|
|
66
|
-
* Skips silently when stdout is not a TTY (piped/parsed output) or when
|
|
67
|
-
* MORPH_QUIET=1. Fail-open: any fs error just skips the banner.
|
|
68
|
-
*
|
|
69
|
-
* @param {{version: string, nodeVersion: string, markerDir?: string}} opts
|
|
70
|
-
* @returns {boolean} true if the banner was printed
|
|
71
|
-
*/
|
|
49
|
+
// npm >=7 swallows postinstall output, so the first CLI run after an upgrade
|
|
50
|
+
// shows the banner instead. Skips without a TTY or with MORPH_QUIET=1.
|
|
72
51
|
export function maybePrintFirstRunBanner({ version, nodeVersion, markerDir }) {
|
|
73
52
|
try {
|
|
74
53
|
if (!process.stdout.isTTY) return false;
|
|
@@ -117,7 +96,7 @@ export function printInstallBanner({ version, nodeVersion, results = [] }) {
|
|
|
117
96
|
sep("Next · new project"),
|
|
118
97
|
` ${chalk.cyan("1.")} ${DIM("cd")} ${chalk.white("your-project")} ${DIM("(git repo)")}`,
|
|
119
98
|
` ${chalk.cyan("2.")} Run ${chalk.bold.white("morph-spec init")}`,
|
|
120
|
-
` ${chalk.cyan("3.")} Open it in Claude Code and run ${chalk.bold.white("/morph-
|
|
99
|
+
` ${chalk.cyan("3.")} Open it in Claude Code and run ${chalk.bold.white("/morph-plan <feature>")}`,
|
|
121
100
|
"",
|
|
122
101
|
sep("Next · project already on morph-spec"),
|
|
123
102
|
` ${chalk.cyan("1.")} Run ${chalk.bold.white("morph-spec update")} ${DIM("— after a CLI upgrade or a project.stack change: full stack-aware resync")}`,
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* claude-md-injector.js
|
|
3
|
-
*
|
|
4
|
-
* Manages the root CLAUDE.md file in client projects.
|
|
5
|
-
* Instead of overwriting the user's CLAUDE.md, injects an @import line
|
|
6
|
-
* that references the morph-spec managed .claude/CLAUDE.md.
|
|
7
|
-
*
|
|
8
|
-
* The root CLAUDE.md belongs to the USER — morph-spec only adds its import.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import { pathExists, readFile } from './file-copier.js';
|
|
12
2
|
import { realEffects } from '../lib/installers/effects.js';
|
|
13
3
|
|
|
@@ -15,20 +5,7 @@ const MORPH_IMPORT = '@.claude/CLAUDE.md';
|
|
|
15
5
|
const MORPH_MARKER = 'MORPH-SPEC';
|
|
16
6
|
|
|
17
7
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* Three scenarios:
|
|
21
|
-
* 1. No CLAUDE.md exists → create minimal file with the import
|
|
22
|
-
* 2. Existing morph-only CLAUDE.md (old overwrite) → replace with minimal + import
|
|
23
|
-
* 3. Existing user CLAUDE.md → append import section at the end
|
|
24
|
-
*
|
|
25
|
-
* Idempotent — does nothing if the import already exists.
|
|
26
|
-
*
|
|
27
|
-
* @param {string} claudeMdPath - Absolute path to root CLAUDE.md
|
|
28
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
|
|
29
|
-
* Coletor de efeitos; o default executa e registra numa lista descartada.
|
|
30
|
-
* A DECISÃO (criar / migrar / anexar / nada) sai de uma LEITURA, que
|
|
31
|
-
* acontece igual nos dois modos — por isso previsão e execução concordam.
|
|
8
|
+
* Leaves the user's root CLAUDE.md in place and only adds the morph-spec @import.
|
|
32
9
|
* @returns {Promise<'created'|'migrated'|'injected'|'already-present'>}
|
|
33
10
|
*/
|
|
34
11
|
export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
|
|
@@ -48,20 +25,14 @@ export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
|
|
|
48
25
|
return 'migrated';
|
|
49
26
|
}
|
|
50
27
|
|
|
51
|
-
// User has their own content — append the import section
|
|
52
28
|
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
53
29
|
fx.writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
|
|
54
30
|
return 'injected';
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
/**
|
|
58
|
-
* Checks if CLAUDE.md contains ONLY morph-spec content (old overwrite pattern).
|
|
59
|
-
* Heuristic: contains MORPH-SPEC marker AND no substantial user content.
|
|
60
|
-
*/
|
|
61
33
|
function isMorphOnly(content) {
|
|
62
34
|
if (!content.includes(MORPH_MARKER)) return false;
|
|
63
35
|
|
|
64
|
-
// Strip morph-spec boilerplate markers and check if anything meaningful remains
|
|
65
36
|
const stripped = content
|
|
66
37
|
.replace(/# MORPH-SPEC Runtime Instructions/g, '')
|
|
67
38
|
.replace(/by Polymorphism Tech[^\n]*/g, '')
|
|
@@ -80,13 +51,6 @@ function isMorphOnly(content) {
|
|
|
80
51
|
return stripped.length < 50;
|
|
81
52
|
}
|
|
82
53
|
|
|
83
|
-
/**
|
|
84
|
-
* Checks if the root CLAUDE.md has the morph-spec import.
|
|
85
|
-
* Used by doctor.js for health checks.
|
|
86
|
-
*
|
|
87
|
-
* @param {string} claudeMdPath - Absolute path to root CLAUDE.md
|
|
88
|
-
* @returns {Promise<'ok'|'missing'|'missing-import'>}
|
|
89
|
-
*/
|
|
90
54
|
export async function checkClaudeMdImport(claudeMdPath) {
|
|
91
55
|
if (!await pathExists(claudeMdPath)) return 'missing';
|
|
92
56
|
const content = await readFile(claudeMdPath);
|
|
@@ -1,70 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Settings Manager
|
|
3
|
-
*
|
|
4
|
-
* Extends the original hooks-installer with broader settings management:
|
|
5
|
-
* permissions, env vars, and managed entry tracking.
|
|
6
|
-
*
|
|
7
|
-
* Re-exports installClaudeHooks for backward compatibility.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { join } from 'path';
|
|
11
2
|
import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';
|
|
12
3
|
import { existsSync } from 'fs';
|
|
13
4
|
|
|
14
5
|
const MORPH_MANAGED_KEY = '_morph_managed';
|
|
15
6
|
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Re-export from hooks-installer (v2 registry-based)
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
7
|
export { installClaudeHooks, installGlobalStatusline, getHooksVersion } from './hooks-installer.js';
|
|
21
8
|
export { installAgents } from './agents-installer.js';
|
|
22
9
|
|
|
23
|
-
// ============================================================================
|
|
24
|
-
// Extended settings management
|
|
25
|
-
// ============================================================================
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Install permission rules into .claude/settings.local.json
|
|
29
|
-
* @param {string} targetPath - Project root directory
|
|
30
|
-
* @param {Array<string>} permissions - Permission patterns to add
|
|
31
|
-
* @returns {Promise<Object>} Summary of changes
|
|
32
|
-
*/
|
|
33
|
-
export async function installPermissions(targetPath, permissions) {
|
|
34
|
-
const settings = await loadSettings(targetPath);
|
|
35
|
-
|
|
36
|
-
settings.permissions = settings.permissions || [];
|
|
37
|
-
|
|
38
|
-
const added = [];
|
|
39
|
-
for (const perm of permissions) {
|
|
40
|
-
if (!settings.permissions.includes(perm)) {
|
|
41
|
-
settings.permissions.push(perm);
|
|
42
|
-
added.push(perm);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Track in _morph_managed
|
|
47
|
-
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
48
|
-
settings[MORPH_MANAGED_KEY].permissions_added =
|
|
49
|
-
settings[MORPH_MANAGED_KEY].permissions_added || [];
|
|
50
|
-
for (const perm of added) {
|
|
51
|
-
if (!settings[MORPH_MANAGED_KEY].permissions_added.includes(perm)) {
|
|
52
|
-
settings[MORPH_MANAGED_KEY].permissions_added.push(perm);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
settings[MORPH_MANAGED_KEY].generated_at = new Date().toISOString();
|
|
56
|
-
|
|
57
|
-
await saveSettings(targetPath, settings);
|
|
58
|
-
|
|
59
|
-
return { added, total: settings.permissions.length };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Install environment variables into .claude/settings.local.json
|
|
64
|
-
* @param {string} targetPath - Project root directory
|
|
65
|
-
* @param {Object} envVars - Key-value pairs to add to env
|
|
66
|
-
* @returns {Promise<Object>} Summary of changes
|
|
67
|
-
*/
|
|
68
10
|
export async function installEnvVars(targetPath, envVars) {
|
|
69
11
|
const settings = await loadSettings(targetPath);
|
|
70
12
|
|
|
@@ -78,7 +20,6 @@ export async function installEnvVars(targetPath, envVars) {
|
|
|
78
20
|
}
|
|
79
21
|
}
|
|
80
22
|
|
|
81
|
-
// Track in _morph_managed
|
|
82
23
|
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
83
24
|
settings[MORPH_MANAGED_KEY].env_added =
|
|
84
25
|
settings[MORPH_MANAGED_KEY].env_added || [];
|
|
@@ -94,12 +35,6 @@ export async function installEnvVars(targetPath, envVars) {
|
|
|
94
35
|
return { added, total: Object.keys(settings.env).length };
|
|
95
36
|
}
|
|
96
37
|
|
|
97
|
-
/**
|
|
98
|
-
* Remove only morph-spec managed entries from settings.
|
|
99
|
-
* Creates backup before modifying.
|
|
100
|
-
* @param {string} targetPath - Project root directory
|
|
101
|
-
* @returns {Promise<Object>} Summary of what was removed
|
|
102
|
-
*/
|
|
103
38
|
export async function resetMorphSettings(targetPath) {
|
|
104
39
|
const claudeDir = join(targetPath, '.claude');
|
|
105
40
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
@@ -115,11 +50,9 @@ export async function resetMorphSettings(targetPath) {
|
|
|
115
50
|
return { removed: false, reason: 'no _morph_managed section found' };
|
|
116
51
|
}
|
|
117
52
|
|
|
118
|
-
// Backup before modifying
|
|
119
53
|
const backupPath = settingsPath + '.backup';
|
|
120
54
|
await copyFile(settingsPath, backupPath);
|
|
121
55
|
|
|
122
|
-
// Remove managed permissions
|
|
123
56
|
if (managed.permissions_added && Array.isArray(settings.permissions)) {
|
|
124
57
|
settings.permissions = settings.permissions.filter(
|
|
125
58
|
p => !managed.permissions_added.includes(p)
|
|
@@ -129,7 +62,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
129
62
|
}
|
|
130
63
|
}
|
|
131
64
|
|
|
132
|
-
// Remove managed env vars
|
|
133
65
|
if (managed.env_added && settings.env) {
|
|
134
66
|
for (const key of managed.env_added) {
|
|
135
67
|
delete settings.env[key];
|
|
@@ -139,7 +71,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
139
71
|
}
|
|
140
72
|
}
|
|
141
73
|
|
|
142
|
-
// Remove managed hooks (v2 format: identified by path pattern)
|
|
143
74
|
if (settings.hooks) {
|
|
144
75
|
const morphPattern = /framework\/hooks\/claude-code\//;
|
|
145
76
|
const oldPattern = /agent-teams\/dispatch\.js/;
|
|
@@ -165,7 +96,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
165
96
|
}
|
|
166
97
|
}
|
|
167
98
|
|
|
168
|
-
// Remove managed MCP servers from .mcp.json (primary location)
|
|
169
99
|
if (managed.mcps_installed) {
|
|
170
100
|
const mcpJson = await loadMcpJson(targetPath);
|
|
171
101
|
if (mcpJson.mcpServers) {
|
|
@@ -175,7 +105,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
175
105
|
if (Object.keys(mcpJson.mcpServers).length === 0) {
|
|
176
106
|
delete mcpJson.mcpServers;
|
|
177
107
|
}
|
|
178
|
-
// Write back (or remove file if empty)
|
|
179
108
|
if (Object.keys(mcpJson).length === 0) {
|
|
180
109
|
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
181
110
|
if (existsSync(mcpJsonPath)) {
|
|
@@ -187,7 +116,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
187
116
|
}
|
|
188
117
|
}
|
|
189
118
|
|
|
190
|
-
// Also clean legacy entries from settings.local.json
|
|
191
119
|
if (settings.mcpServers) {
|
|
192
120
|
for (const mcpName of managed.mcps_installed) {
|
|
193
121
|
delete settings.mcpServers[mcpName];
|
|
@@ -198,7 +126,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
198
126
|
}
|
|
199
127
|
}
|
|
200
128
|
|
|
201
|
-
// Remove the managed marker
|
|
202
129
|
delete settings[MORPH_MANAGED_KEY];
|
|
203
130
|
|
|
204
131
|
await saveSettings(targetPath, settings);
|
|
@@ -214,15 +141,7 @@ export async function resetMorphSettings(targetPath) {
|
|
|
214
141
|
};
|
|
215
142
|
}
|
|
216
143
|
|
|
217
|
-
|
|
218
|
-
* Install MCP server entries into .mcp.json at the project root.
|
|
219
|
-
* Claude Code reads MCP servers from .mcp.json (not settings.local.json).
|
|
220
|
-
* Tracks installed names in settings.local.json _morph_managed.mcps_installed for safe reset.
|
|
221
|
-
*
|
|
222
|
-
* @param {string} targetPath - Project root directory
|
|
223
|
-
* @param {Object} servers - Map of server name → config { type?, command, args, env? }
|
|
224
|
-
* @returns {Promise<Object>} Summary of changes { added, skipped, total }
|
|
225
|
-
*/
|
|
144
|
+
// Claude Code reads MCP servers from .mcp.json, not settings.local.json.
|
|
226
145
|
export async function installMcpServers(targetPath, servers) {
|
|
227
146
|
const mcpJson = await loadMcpJson(targetPath);
|
|
228
147
|
|
|
@@ -242,7 +161,6 @@ export async function installMcpServers(targetPath, servers) {
|
|
|
242
161
|
|
|
243
162
|
await saveMcpJson(targetPath, mcpJson);
|
|
244
163
|
|
|
245
|
-
// Track in settings.local.json _morph_managed
|
|
246
164
|
const settings = await loadSettings(targetPath);
|
|
247
165
|
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
248
166
|
settings[MORPH_MANAGED_KEY].mcps_installed =
|
|
@@ -259,10 +177,6 @@ export async function installMcpServers(targetPath, servers) {
|
|
|
259
177
|
return { added, skipped, total: Object.keys(mcpJson.mcpServers).length };
|
|
260
178
|
}
|
|
261
179
|
|
|
262
|
-
// ============================================================================
|
|
263
|
-
// Helpers
|
|
264
|
-
// ============================================================================
|
|
265
|
-
|
|
266
180
|
async function loadMcpJson(targetPath) {
|
|
267
181
|
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
268
182
|
try {
|
package/src/utils/file-copier.js
CHANGED
|
@@ -39,10 +39,7 @@ export async function pathExists(path) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export async function readJson(path) {
|
|
42
|
-
//
|
|
43
|
-
// WITH BOM, and JSON.parse rejects it. A hand-edited config.json on Windows
|
|
44
|
-
// would then silently fall back to empty project tags (fail-open reads),
|
|
45
|
-
// installing only universal personas. Strip the BOM before parsing.
|
|
42
|
+
// PowerShell 5.1's `Out-File -Encoding utf8` writes a BOM that JSON.parse rejects.
|
|
46
43
|
const raw = await fs.readFile(path, 'utf-8');
|
|
47
44
|
return JSON.parse(raw.replace(/^/, ''));
|
|
48
45
|
}
|
|
@@ -69,18 +66,7 @@ export async function readFile(path) {
|
|
|
69
66
|
return fs.readFile(path, 'utf8');
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
* Create a link to a file or directory with platform-aware fallbacks.
|
|
74
|
-
*
|
|
75
|
-
* For files: hard link → symlink → copy
|
|
76
|
-
* Hard links work on Windows without admin (same NTFS volume).
|
|
77
|
-
* For dirs: delegates to createDirectoryLink (junction on Windows).
|
|
78
|
-
*
|
|
79
|
-
* @param {string} target - Path to the original file/folder
|
|
80
|
-
* @param {string} link - Path where the link should be created
|
|
81
|
-
* @param {string} type - 'file' or 'dir'
|
|
82
|
-
* @returns {Promise<'hardlink' | 'symlink' | 'junction' | 'copy'>}
|
|
83
|
-
*/
|
|
69
|
+
// Hard link first: it needs no admin on Windows (same NTFS volume).
|
|
84
70
|
export async function createSymlink(target, link, type = 'file') {
|
|
85
71
|
await fs.ensureDir(dirname(link));
|
|
86
72
|
|
|
@@ -88,7 +74,6 @@ export async function createSymlink(target, link, type = 'file') {
|
|
|
88
74
|
return createDirectoryLink(target, link);
|
|
89
75
|
}
|
|
90
76
|
|
|
91
|
-
// File linking: hard link first (no admin on Windows), then symlink, then copy
|
|
92
77
|
try {
|
|
93
78
|
await fs.link(target, link);
|
|
94
79
|
return 'hardlink';
|
|
@@ -103,24 +88,15 @@ export async function createSymlink(target, link, type = 'file') {
|
|
|
103
88
|
}
|
|
104
89
|
}
|
|
105
90
|
|
|
106
|
-
/**
|
|
107
|
-
* Create a directory link (junction on Windows, symlink on other platforms)
|
|
108
|
-
* with fallback to directory copy if linking fails.
|
|
109
|
-
* @param {string} target - Path to the original directory
|
|
110
|
-
* @param {string} link - Path where the link should be created
|
|
111
|
-
* @returns {Promise<'junction' | 'symlink' | 'copy'>} - How the link was created
|
|
112
|
-
*/
|
|
113
91
|
export async function createDirectoryLink(target, link) {
|
|
114
92
|
await fs.ensureDir(dirname(link));
|
|
115
93
|
|
|
116
|
-
// Remove existing link/directory before recreating
|
|
117
94
|
if (await pathExists(link)) {
|
|
118
95
|
await fs.remove(link);
|
|
119
96
|
}
|
|
120
97
|
|
|
121
98
|
if (process.platform === 'win32') {
|
|
122
|
-
//
|
|
123
|
-
// Skip ensureSymlink('dir') fallback: it also needs admin on Windows.
|
|
99
|
+
// A junction needs no admin; ensureSymlink('dir') would.
|
|
124
100
|
try {
|
|
125
101
|
await fs.symlink(target, link, 'junction');
|
|
126
102
|
return 'junction';
|
|
@@ -139,16 +115,8 @@ export async function createDirectoryLink(target, link) {
|
|
|
139
115
|
}
|
|
140
116
|
}
|
|
141
117
|
|
|
142
|
-
/**
|
|
143
|
-
* The morph-owned block of the installed .gitignore. Exported so the worktree
|
|
144
|
-
* container canary (test/lib/worktree/dirs.test.js) can prove both names are
|
|
145
|
-
* covered without rendering a file.
|
|
146
|
-
*/
|
|
147
118
|
export const MORPH_GITIGNORE_ENTRIES = [
|
|
148
|
-
//
|
|
149
|
-
// .morph/features/{feature}/feature.json files. Never commit it: doing so
|
|
150
|
-
// sweeps every feature's accumulated diffs into one commit. The feature
|
|
151
|
-
// state that matters travels inside each feature folder (which IS committed).
|
|
119
|
+
// Rebuilt from the committed feature.json files; committing it sweeps every feature into one commit.
|
|
152
120
|
'.morph/state.json',
|
|
153
121
|
'.morph/state.json.tmp.*',
|
|
154
122
|
'.morph/state.json.lock/',
|
|
@@ -156,10 +124,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
156
124
|
'.morph/memory/',
|
|
157
125
|
'.morph/plans/',
|
|
158
126
|
'.morph/analytics/',
|
|
159
|
-
'.morph/traces/',
|
|
160
|
-
// Per-instance activity/session logs (activity-logger.js). Each worktree
|
|
161
|
-
// writes its own — committing this guarantees a merge conflict between
|
|
162
|
-
// feature branches.
|
|
163
127
|
'.morph/logs/',
|
|
164
128
|
'.claude/commands/',
|
|
165
129
|
'.claude/rules/',
|
|
@@ -167,25 +131,13 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
167
131
|
'.claude/agents/',
|
|
168
132
|
'.claude/CLAUDE.md',
|
|
169
133
|
'.claude/settings.local.json',
|
|
170
|
-
//
|
|
171
|
-
// kept so a project that already has `.worktrees/` on disk does not
|
|
172
|
-
// suddenly see it as untracked dirt — which would also make `finish`'s
|
|
173
|
-
// clean-root check refuse.
|
|
134
|
+
// The legacy name stays so an existing `.worktrees/` is not dirt that makes `finish` refuse.
|
|
174
135
|
...WORKTREE_DIR_NAMES.map((d) => `/${d}/`),
|
|
175
136
|
'.playwright-mcp/',
|
|
176
137
|
'docs/superpowers/',
|
|
177
|
-
// Grafo de codebase — artefato derivado, reconstruído pelo hook do
|
|
178
|
-
// graphify a cada commit. Committá-lo faria de todo commit um diff de
|
|
179
|
-
// megabytes de JSON gerado.
|
|
180
138
|
'graphify-out/',
|
|
181
139
|
];
|
|
182
140
|
|
|
183
|
-
/**
|
|
184
|
-
* @param {string} projectPath
|
|
185
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
|
|
186
|
-
* Coletor de efeitos. O default executa e registra numa lista descartada, de
|
|
187
|
-
* modo que todo chamador antigo (`setup-infra`, `init`) siga idêntico.
|
|
188
|
-
*/
|
|
189
141
|
export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
190
142
|
const gitignorePath = join(projectPath, '.gitignore');
|
|
191
143
|
|
|
@@ -194,7 +146,6 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
194
146
|
let content = '';
|
|
195
147
|
let hasMorphSection = false;
|
|
196
148
|
|
|
197
|
-
// Read existing .gitignore if it exists
|
|
198
149
|
if (await pathExists(gitignorePath)) {
|
|
199
150
|
content = await readFile(gitignorePath);
|
|
200
151
|
hasMorphSection = content.includes('# MORPH-SPEC');
|
|
@@ -202,12 +153,10 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
202
153
|
const original = content;
|
|
203
154
|
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
204
155
|
|
|
205
|
-
// If MORPH section already exists, check if rules are up to date
|
|
206
156
|
if (hasMorphSection) {
|
|
207
157
|
const lines = content.split(/\r?\n/);
|
|
208
158
|
const morphStartIndex = lines.findIndex(line => line.trim() === '# MORPH-SPEC');
|
|
209
159
|
|
|
210
|
-
// Find the end of MORPH section (next empty line or section header)
|
|
211
160
|
let morphEndIndex = morphStartIndex + 1;
|
|
212
161
|
while (morphEndIndex < lines.length &&
|
|
213
162
|
lines[morphEndIndex].trim() !== '' &&
|
|
@@ -215,22 +164,18 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
215
164
|
morphEndIndex++;
|
|
216
165
|
}
|
|
217
166
|
|
|
218
|
-
// Extract current MORPH rules
|
|
219
167
|
const currentMorphRules = lines.slice(morphStartIndex, morphEndIndex + 1);
|
|
220
|
-
const expectedMorphRules = morphRules.slice(1, -1);
|
|
168
|
+
const expectedMorphRules = morphRules.slice(1, -1);
|
|
221
169
|
|
|
222
|
-
// Check if all expected rules are present
|
|
223
170
|
const missingRules = expectedMorphRules.filter(rule =>
|
|
224
171
|
rule.startsWith('#') || !currentMorphRules.some(line => line.trim() === rule)
|
|
225
172
|
);
|
|
226
173
|
|
|
227
174
|
if (missingRules.length > 0) {
|
|
228
|
-
// Update the section with all rules
|
|
229
175
|
lines.splice(morphStartIndex, morphEndIndex - morphStartIndex, ...morphRules.slice(1, -1));
|
|
230
176
|
content = lines.join(eol);
|
|
231
177
|
}
|
|
232
178
|
} else {
|
|
233
|
-
// Add MORPH section
|
|
234
179
|
if (content && !content.endsWith('\n')) {
|
|
235
180
|
content += eol;
|
|
236
181
|
}
|
|
@@ -240,23 +185,30 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
240
185
|
if (content !== original) fx.writeFile(gitignorePath, content);
|
|
241
186
|
}
|
|
242
187
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
188
|
+
const DOTNET_BUILD_DIRS = ['bin', 'obj'];
|
|
189
|
+
|
|
190
|
+
const ignoredDir = (line) => line.trim()
|
|
191
|
+
.replace(/^\/|^\*\*\//, '')
|
|
192
|
+
.replace(/\/(\*\*)?$/, '')
|
|
193
|
+
.replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
|
|
194
|
+
.toLowerCase();
|
|
195
|
+
|
|
196
|
+
/** Never rewrites an existing line. @returns {string[]} the entries added */
|
|
197
|
+
export function ensureDotnetBuildIgnores(projectPath) {
|
|
198
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
199
|
+
const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
200
|
+
const present = new Set(content.split(/\r?\n/).filter((l) => !l.trim().startsWith('#')).map(ignoredDir));
|
|
201
|
+
const missing = DOTNET_BUILD_DIRS.filter((dir) => !present.has(dir)).map((dir) => `${dir}/`);
|
|
202
|
+
if (!missing.length) return [];
|
|
203
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
204
|
+
const lead = content && !content.endsWith('\n') ? eol : '';
|
|
205
|
+
const block = [...(content ? [''] : []), '# .NET build output', ...missing, ''].join(eol);
|
|
206
|
+
fs.writeFileSync(gitignorePath, content + lead + block);
|
|
207
|
+
return missing;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Without the LF pin, Windows autocrlf marks every rewritten feature.json dirty
|
|
211
|
+
// and breaks the clean-root check of `finish`. Scoped to `.morph/**` only.
|
|
260
212
|
export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
261
213
|
const gitattributesPath = join(projectPath, '.gitattributes');
|
|
262
214
|
const MARKER = '# MORPH-SPEC';
|
|
@@ -271,7 +223,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
|
271
223
|
let content = '';
|
|
272
224
|
if (await pathExists(gitattributesPath)) {
|
|
273
225
|
content = await readFile(gitattributesPath);
|
|
274
|
-
if (content.includes(MARKER)) return;
|
|
226
|
+
if (content.includes(MARKER)) return;
|
|
275
227
|
if (content && !content.endsWith('\n')) content += '\n';
|
|
276
228
|
content += '\n';
|
|
277
229
|
}
|
|
@@ -279,17 +231,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
|
279
231
|
fx.writeFile(gitattributesPath, content);
|
|
280
232
|
}
|
|
281
233
|
|
|
282
|
-
/**
|
|
283
|
-
* Garante entradas no .claudeignore, criando o arquivo se preciso.
|
|
284
|
-
*
|
|
285
|
-
* Existe por causa do grafo: um `graph.json` de 12 MB reescrito a cada commit
|
|
286
|
-
* invalidaria o prompt cache do Claude Code em toda sessão. Append idempotente
|
|
287
|
-
* por linha — o que o usuário já tem nunca é reescrito.
|
|
288
|
-
*
|
|
289
|
-
* @param {string} projectPath
|
|
290
|
-
* @param {string[]} entries
|
|
291
|
-
* @returns {Promise<{added: string[], path: string}>}
|
|
292
|
-
*/
|
|
234
|
+
/** Idempotent per-line append; never rewrites what the user already has. */
|
|
293
235
|
export async function ensureClaudeignore(projectPath, entries) {
|
|
294
236
|
const claudeignorePath = join(projectPath, '.claudeignore');
|
|
295
237
|
const MARKER = '# MORPH-SPEC';
|