@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-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,35 +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 environment variables into .claude/settings.local.json
|
|
29
|
-
* @param {string} targetPath - Project root directory
|
|
30
|
-
* @param {Object} envVars - Key-value pairs to add to env
|
|
31
|
-
* @returns {Promise<Object>} Summary of changes
|
|
32
|
-
*/
|
|
33
10
|
export async function installEnvVars(targetPath, envVars) {
|
|
34
11
|
const settings = await loadSettings(targetPath);
|
|
35
12
|
|
|
@@ -43,7 +20,6 @@ export async function installEnvVars(targetPath, envVars) {
|
|
|
43
20
|
}
|
|
44
21
|
}
|
|
45
22
|
|
|
46
|
-
// Track in _morph_managed
|
|
47
23
|
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
48
24
|
settings[MORPH_MANAGED_KEY].env_added =
|
|
49
25
|
settings[MORPH_MANAGED_KEY].env_added || [];
|
|
@@ -59,12 +35,6 @@ export async function installEnvVars(targetPath, envVars) {
|
|
|
59
35
|
return { added, total: Object.keys(settings.env).length };
|
|
60
36
|
}
|
|
61
37
|
|
|
62
|
-
/**
|
|
63
|
-
* Remove only morph-spec managed entries from settings.
|
|
64
|
-
* Creates backup before modifying.
|
|
65
|
-
* @param {string} targetPath - Project root directory
|
|
66
|
-
* @returns {Promise<Object>} Summary of what was removed
|
|
67
|
-
*/
|
|
68
38
|
export async function resetMorphSettings(targetPath) {
|
|
69
39
|
const claudeDir = join(targetPath, '.claude');
|
|
70
40
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
@@ -80,11 +50,9 @@ export async function resetMorphSettings(targetPath) {
|
|
|
80
50
|
return { removed: false, reason: 'no _morph_managed section found' };
|
|
81
51
|
}
|
|
82
52
|
|
|
83
|
-
// Backup before modifying
|
|
84
53
|
const backupPath = settingsPath + '.backup';
|
|
85
54
|
await copyFile(settingsPath, backupPath);
|
|
86
55
|
|
|
87
|
-
// Remove managed permissions
|
|
88
56
|
if (managed.permissions_added && Array.isArray(settings.permissions)) {
|
|
89
57
|
settings.permissions = settings.permissions.filter(
|
|
90
58
|
p => !managed.permissions_added.includes(p)
|
|
@@ -94,7 +62,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
94
62
|
}
|
|
95
63
|
}
|
|
96
64
|
|
|
97
|
-
// Remove managed env vars
|
|
98
65
|
if (managed.env_added && settings.env) {
|
|
99
66
|
for (const key of managed.env_added) {
|
|
100
67
|
delete settings.env[key];
|
|
@@ -104,7 +71,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
104
71
|
}
|
|
105
72
|
}
|
|
106
73
|
|
|
107
|
-
// Remove managed hooks (v2 format: identified by path pattern)
|
|
108
74
|
if (settings.hooks) {
|
|
109
75
|
const morphPattern = /framework\/hooks\/claude-code\//;
|
|
110
76
|
const oldPattern = /agent-teams\/dispatch\.js/;
|
|
@@ -130,7 +96,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
130
96
|
}
|
|
131
97
|
}
|
|
132
98
|
|
|
133
|
-
// Remove managed MCP servers from .mcp.json (primary location)
|
|
134
99
|
if (managed.mcps_installed) {
|
|
135
100
|
const mcpJson = await loadMcpJson(targetPath);
|
|
136
101
|
if (mcpJson.mcpServers) {
|
|
@@ -140,7 +105,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
140
105
|
if (Object.keys(mcpJson.mcpServers).length === 0) {
|
|
141
106
|
delete mcpJson.mcpServers;
|
|
142
107
|
}
|
|
143
|
-
// Write back (or remove file if empty)
|
|
144
108
|
if (Object.keys(mcpJson).length === 0) {
|
|
145
109
|
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
146
110
|
if (existsSync(mcpJsonPath)) {
|
|
@@ -152,7 +116,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
152
116
|
}
|
|
153
117
|
}
|
|
154
118
|
|
|
155
|
-
// Also clean legacy entries from settings.local.json
|
|
156
119
|
if (settings.mcpServers) {
|
|
157
120
|
for (const mcpName of managed.mcps_installed) {
|
|
158
121
|
delete settings.mcpServers[mcpName];
|
|
@@ -163,7 +126,6 @@ export async function resetMorphSettings(targetPath) {
|
|
|
163
126
|
}
|
|
164
127
|
}
|
|
165
128
|
|
|
166
|
-
// Remove the managed marker
|
|
167
129
|
delete settings[MORPH_MANAGED_KEY];
|
|
168
130
|
|
|
169
131
|
await saveSettings(targetPath, settings);
|
|
@@ -179,15 +141,7 @@ export async function resetMorphSettings(targetPath) {
|
|
|
179
141
|
};
|
|
180
142
|
}
|
|
181
143
|
|
|
182
|
-
|
|
183
|
-
* Install MCP server entries into .mcp.json at the project root.
|
|
184
|
-
* Claude Code reads MCP servers from .mcp.json (not settings.local.json).
|
|
185
|
-
* Tracks installed names in settings.local.json _morph_managed.mcps_installed for safe reset.
|
|
186
|
-
*
|
|
187
|
-
* @param {string} targetPath - Project root directory
|
|
188
|
-
* @param {Object} servers - Map of server name → config { type?, command, args, env? }
|
|
189
|
-
* @returns {Promise<Object>} Summary of changes { added, skipped, total }
|
|
190
|
-
*/
|
|
144
|
+
// Claude Code reads MCP servers from .mcp.json, not settings.local.json.
|
|
191
145
|
export async function installMcpServers(targetPath, servers) {
|
|
192
146
|
const mcpJson = await loadMcpJson(targetPath);
|
|
193
147
|
|
|
@@ -207,7 +161,6 @@ export async function installMcpServers(targetPath, servers) {
|
|
|
207
161
|
|
|
208
162
|
await saveMcpJson(targetPath, mcpJson);
|
|
209
163
|
|
|
210
|
-
// Track in settings.local.json _morph_managed
|
|
211
164
|
const settings = await loadSettings(targetPath);
|
|
212
165
|
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
213
166
|
settings[MORPH_MANAGED_KEY].mcps_installed =
|
|
@@ -224,10 +177,6 @@ export async function installMcpServers(targetPath, servers) {
|
|
|
224
177
|
return { added, skipped, total: Object.keys(mcpJson.mcpServers).length };
|
|
225
178
|
}
|
|
226
179
|
|
|
227
|
-
// ============================================================================
|
|
228
|
-
// Helpers
|
|
229
|
-
// ============================================================================
|
|
230
|
-
|
|
231
180
|
async function loadMcpJson(targetPath) {
|
|
232
181
|
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
233
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,9 +124,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
156
124
|
'.morph/memory/',
|
|
157
125
|
'.morph/plans/',
|
|
158
126
|
'.morph/analytics/',
|
|
159
|
-
// Per-instance activity/session logs (activity-logger.js). Each worktree
|
|
160
|
-
// writes its own — committing this guarantees a merge conflict between
|
|
161
|
-
// feature branches.
|
|
162
127
|
'.morph/logs/',
|
|
163
128
|
'.claude/commands/',
|
|
164
129
|
'.claude/rules/',
|
|
@@ -166,25 +131,13 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
166
131
|
'.claude/agents/',
|
|
167
132
|
'.claude/CLAUDE.md',
|
|
168
133
|
'.claude/settings.local.json',
|
|
169
|
-
//
|
|
170
|
-
// kept so a project that already has `.worktrees/` on disk does not
|
|
171
|
-
// suddenly see it as untracked dirt — which would also make `finish`'s
|
|
172
|
-
// clean-root check refuse.
|
|
134
|
+
// The legacy name stays so an existing `.worktrees/` is not dirt that makes `finish` refuse.
|
|
173
135
|
...WORKTREE_DIR_NAMES.map((d) => `/${d}/`),
|
|
174
136
|
'.playwright-mcp/',
|
|
175
137
|
'docs/superpowers/',
|
|
176
|
-
// Grafo de codebase — artefato derivado, reconstruído pelo hook do
|
|
177
|
-
// graphify a cada commit. Committá-lo faria de todo commit um diff de
|
|
178
|
-
// megabytes de JSON gerado.
|
|
179
138
|
'graphify-out/',
|
|
180
139
|
];
|
|
181
140
|
|
|
182
|
-
/**
|
|
183
|
-
* @param {string} projectPath
|
|
184
|
-
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
|
|
185
|
-
* Coletor de efeitos. O default executa e registra numa lista descartada, de
|
|
186
|
-
* modo que todo chamador antigo (`setup-infra`, `init`) siga idêntico.
|
|
187
|
-
*/
|
|
188
141
|
export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
189
142
|
const gitignorePath = join(projectPath, '.gitignore');
|
|
190
143
|
|
|
@@ -193,7 +146,6 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
193
146
|
let content = '';
|
|
194
147
|
let hasMorphSection = false;
|
|
195
148
|
|
|
196
|
-
// Read existing .gitignore if it exists
|
|
197
149
|
if (await pathExists(gitignorePath)) {
|
|
198
150
|
content = await readFile(gitignorePath);
|
|
199
151
|
hasMorphSection = content.includes('# MORPH-SPEC');
|
|
@@ -201,12 +153,10 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
201
153
|
const original = content;
|
|
202
154
|
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
203
155
|
|
|
204
|
-
// If MORPH section already exists, check if rules are up to date
|
|
205
156
|
if (hasMorphSection) {
|
|
206
157
|
const lines = content.split(/\r?\n/);
|
|
207
158
|
const morphStartIndex = lines.findIndex(line => line.trim() === '# MORPH-SPEC');
|
|
208
159
|
|
|
209
|
-
// Find the end of MORPH section (next empty line or section header)
|
|
210
160
|
let morphEndIndex = morphStartIndex + 1;
|
|
211
161
|
while (morphEndIndex < lines.length &&
|
|
212
162
|
lines[morphEndIndex].trim() !== '' &&
|
|
@@ -214,22 +164,18 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
214
164
|
morphEndIndex++;
|
|
215
165
|
}
|
|
216
166
|
|
|
217
|
-
// Extract current MORPH rules
|
|
218
167
|
const currentMorphRules = lines.slice(morphStartIndex, morphEndIndex + 1);
|
|
219
|
-
const expectedMorphRules = morphRules.slice(1, -1);
|
|
168
|
+
const expectedMorphRules = morphRules.slice(1, -1);
|
|
220
169
|
|
|
221
|
-
// Check if all expected rules are present
|
|
222
170
|
const missingRules = expectedMorphRules.filter(rule =>
|
|
223
171
|
rule.startsWith('#') || !currentMorphRules.some(line => line.trim() === rule)
|
|
224
172
|
);
|
|
225
173
|
|
|
226
174
|
if (missingRules.length > 0) {
|
|
227
|
-
// Update the section with all rules
|
|
228
175
|
lines.splice(morphStartIndex, morphEndIndex - morphStartIndex, ...morphRules.slice(1, -1));
|
|
229
176
|
content = lines.join(eol);
|
|
230
177
|
}
|
|
231
178
|
} else {
|
|
232
|
-
// Add MORPH section
|
|
233
179
|
if (content && !content.endsWith('\n')) {
|
|
234
180
|
content += eol;
|
|
235
181
|
}
|
|
@@ -247,12 +193,7 @@ const ignoredDir = (line) => line.trim()
|
|
|
247
193
|
.replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
|
|
248
194
|
.toLowerCase();
|
|
249
195
|
|
|
250
|
-
/**
|
|
251
|
-
* Appends `bin/` and `obj/` to the project `.gitignore` when no line already
|
|
252
|
-
* ignores them. Never rewrites an existing line.
|
|
253
|
-
*
|
|
254
|
-
* @returns {string[]} the entries added
|
|
255
|
-
*/
|
|
196
|
+
/** Never rewrites an existing line. @returns {string[]} the entries added */
|
|
256
197
|
export function ensureDotnetBuildIgnores(projectPath) {
|
|
257
198
|
const gitignorePath = join(projectPath, '.gitignore');
|
|
258
199
|
const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
@@ -266,23 +207,8 @@ export function ensureDotnetBuildIgnores(projectPath) {
|
|
|
266
207
|
return missing;
|
|
267
208
|
}
|
|
268
209
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
*
|
|
272
|
-
* Rationale: `saveState`/`atomicWriteJson` write `.morph` JSON with LF. On
|
|
273
|
-
* Windows with `core.autocrlf=true` (the Git-for-Windows default) and no
|
|
274
|
-
* attribute pinning EOL, Git flags EVERY rewritten feature.json as modified
|
|
275
|
-
* even when the content is byte-identical. That silently breaks the whole-root
|
|
276
|
-
* clean-check in `morph-spec finish` as soon as a project has more
|
|
277
|
-
* than one feature — precisely the multi-instance / parallel-worktree flow,
|
|
278
|
-
* where one `saveState` touches sibling feature.json files and marks them dirty.
|
|
279
|
-
* This mirrors the .gitattributes this framework repo already carries for its
|
|
280
|
-
* own generated files. Scoped to `.morph/**` so it never imposes an EOL policy
|
|
281
|
-
* on the user's own source. Idempotent and non-destructive: appends a marked
|
|
282
|
-
* block, leaving any pre-existing .gitattributes rules untouched.
|
|
283
|
-
*
|
|
284
|
-
* @param {string} projectPath
|
|
285
|
-
*/
|
|
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.
|
|
286
212
|
export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
287
213
|
const gitattributesPath = join(projectPath, '.gitattributes');
|
|
288
214
|
const MARKER = '# MORPH-SPEC';
|
|
@@ -297,7 +223,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
|
297
223
|
let content = '';
|
|
298
224
|
if (await pathExists(gitattributesPath)) {
|
|
299
225
|
content = await readFile(gitattributesPath);
|
|
300
|
-
if (content.includes(MARKER)) return;
|
|
226
|
+
if (content.includes(MARKER)) return;
|
|
301
227
|
if (content && !content.endsWith('\n')) content += '\n';
|
|
302
228
|
content += '\n';
|
|
303
229
|
}
|
|
@@ -305,17 +231,7 @@ export async function ensureGitattributes(projectPath, fx = realEffects()) {
|
|
|
305
231
|
fx.writeFile(gitattributesPath, content);
|
|
306
232
|
}
|
|
307
233
|
|
|
308
|
-
/**
|
|
309
|
-
* Garante entradas no .claudeignore, criando o arquivo se preciso.
|
|
310
|
-
*
|
|
311
|
-
* Existe por causa do grafo: um `graph.json` de 12 MB reescrito a cada commit
|
|
312
|
-
* invalidaria o prompt cache do Claude Code em toda sessão. Append idempotente
|
|
313
|
-
* por linha — o que o usuário já tem nunca é reescrito.
|
|
314
|
-
*
|
|
315
|
-
* @param {string} projectPath
|
|
316
|
-
* @param {string[]} entries
|
|
317
|
-
* @returns {Promise<{added: string[], path: string}>}
|
|
318
|
-
*/
|
|
234
|
+
/** Idempotent per-line append; never rewrites what the user already has. */
|
|
319
235
|
export async function ensureClaudeignore(projectPath, entries) {
|
|
320
236
|
const claudeignorePath = join(projectPath, '.claudeignore');
|
|
321
237
|
const MARKER = '# MORPH-SPEC';
|