@polymorphism-tech/morph-spec 8.47.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,39 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* the untracked files, plus the project's `config.json` (it names the test
|
|
4
|
-
* commands). Everything else under `.morph/` stays out — verify itself writes
|
|
5
|
-
* feature.json and the logs on every run, and counting that would make two
|
|
6
|
-
* runs over the same code look different.
|
|
7
|
-
*/
|
|
1
|
+
// `.morph/` stays out except `config.json` minus `expectedCount`, as in `treeMovedSince`:
|
|
2
|
+
// committing verify's own bookkeeping must not make the same code look different.
|
|
8
3
|
|
|
9
4
|
import { execFileSync } from 'child_process';
|
|
10
5
|
import { createHash } from 'crypto';
|
|
11
6
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
12
7
|
import { join } from 'path';
|
|
8
|
+
import { withoutBaseline } from './evidence-staleness.js';
|
|
13
9
|
|
|
14
|
-
/** Pathspec for "the project minus its `.morph/` bookkeeping". */
|
|
15
10
|
export const OUTSIDE_MORPH = ['--', '.', ':(exclude).morph'];
|
|
16
11
|
const MAX_OUTPUT = 64 * 1024 * 1024;
|
|
17
12
|
|
|
18
|
-
/** git output as a Buffer; stderr dropped, throws on a non-zero exit. */
|
|
19
13
|
export function git(projectPath, args, input) {
|
|
20
14
|
return execFileSync('git', args, {
|
|
21
15
|
cwd: projectPath, stdio: [input === undefined ? 'ignore' : 'pipe', 'pipe', 'ignore'], maxBuffer: MAX_OUTPUT, input,
|
|
22
16
|
});
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
/**
|
|
26
|
-
* @param {string} projectPath
|
|
27
|
-
* @returns {string|null} sha256 hex, or null outside a git repo / on any git error
|
|
28
|
-
*/
|
|
19
|
+
/** @returns {string|null} sha256 hex, or null outside a git repo / on any git error */
|
|
29
20
|
export function treeFingerprint(projectPath) {
|
|
30
21
|
try {
|
|
31
22
|
const hash = createHash('sha256');
|
|
32
23
|
let head = null;
|
|
33
24
|
try { head = git(projectPath, ['rev-parse', 'HEAD']).toString().trim(); } catch { /* no commit yet */ }
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
const tree = head
|
|
26
|
+
? git(projectPath, ['ls-tree', '-z', 'HEAD']).toString().split('\0').filter((e) => e && e.split('\t')[1] !== '.morph')
|
|
27
|
+
: ['no-commit'];
|
|
28
|
+
hash.update(`tree\0${tree.join('\0')}\0`);
|
|
37
29
|
if (head) hash.update(git(projectPath, ['diff', 'HEAD', '--binary', ...OUTSIDE_MORPH]));
|
|
38
30
|
hash.update('\0untracked\0');
|
|
39
31
|
const untracked = git(projectPath, ['ls-files', '--others', '--exclude-standard', '-z', ...OUTSIDE_MORPH])
|
|
@@ -50,9 +42,17 @@ export function treeFingerprint(projectPath) {
|
|
|
50
42
|
}
|
|
51
43
|
const config = join(projectPath, '.morph', 'config', 'config.json');
|
|
52
44
|
hash.update('\0config\0');
|
|
53
|
-
if (existsSync(config)) hash.update(readFileSync(config));
|
|
45
|
+
if (existsSync(config)) hash.update(normalizedConfig(readFileSync(config, 'utf8')));
|
|
54
46
|
return hash.digest('hex');
|
|
55
47
|
} catch {
|
|
56
48
|
return null;
|
|
57
49
|
}
|
|
58
50
|
}
|
|
51
|
+
|
|
52
|
+
function normalizedConfig(text) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.stringify(withoutBaseline(text));
|
|
55
|
+
} catch {
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The working tree as the orchestrator needs it when it takes control back
|
|
3
|
-
* from a dispatch, in one call instead of `git status` + `git diff --stat` +
|
|
4
|
-
* `git log` + a marker grep. Everything is read outside `.morph/`, the same
|
|
5
|
-
* cut the tree fingerprint makes.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { existsSync } from 'fs';
|
|
9
2
|
import { join } from 'path';
|
|
10
3
|
import { OUTSIDE_MORPH, treeFingerprint, git as gitBuffer } from './tree-fingerprint.js';
|
|
11
4
|
import { detectDefaultBranch } from './default-branch.js';
|
|
12
5
|
import { resolveBaseRef } from './branch-classifier.js';
|
|
13
6
|
|
|
14
|
-
/** What an evaluator writes next to a mutation it has not reverted yet. */
|
|
15
7
|
export const MUTATION_MARKER = 'MUTACAO';
|
|
16
8
|
const MAX_LISTED = 50;
|
|
17
9
|
const MAX_COMMITS = 10;
|
|
@@ -24,13 +16,11 @@ function headSha(cwd) {
|
|
|
24
16
|
try { return git(cwd, ['rev-parse', 'HEAD']).trim(); } catch { return null; }
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
/** Every file that differs from HEAD outside `.morph/`, untracked ones one by one. */
|
|
28
19
|
function changedFiles(cwd) {
|
|
29
20
|
return parsePorcelain(git(cwd, ['status', '--porcelain=v1', '-z', '--untracked-files=all', ...OUTSIDE_MORPH]))
|
|
30
21
|
.map((e) => e.path);
|
|
31
22
|
}
|
|
32
23
|
|
|
33
|
-
/** Blob id of each file as it is on disk; null for a file that is gone. */
|
|
34
24
|
function diskBlobs(cwd, paths) {
|
|
35
25
|
const present = paths.filter((p) => existsSync(join(cwd, p)));
|
|
36
26
|
const ids = present.length ? git(cwd, ['hash-object', '--stdin-paths'], present.join('\n')).split('\n') : [];
|
|
@@ -39,7 +29,6 @@ function diskBlobs(cwd, paths) {
|
|
|
39
29
|
return out;
|
|
40
30
|
}
|
|
41
31
|
|
|
42
|
-
/** Blob id of each path in a commit; null where the commit does not have it. */
|
|
43
32
|
function commitBlobs(cwd, commit, paths) {
|
|
44
33
|
const out = Object.fromEntries(paths.map((p) => [p, null]));
|
|
45
34
|
if (!commit || !paths.length) return out;
|
|
@@ -49,10 +38,8 @@ function commitBlobs(cwd, commit, paths) {
|
|
|
49
38
|
}
|
|
50
39
|
|
|
51
40
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* differs from it. Kept on disk, so a session that resumes after the agent
|
|
55
|
-
* died compares against the tree before the dispatch, not its own start.
|
|
41
|
+
* HEAD plus the blob of every file that differs from it, kept on disk so a resumed
|
|
42
|
+
* session compares against the tree before the dispatch, not its own start.
|
|
56
43
|
* @returns {{ at: string, head: string|null, files: Object<string, string|null> }|null}
|
|
57
44
|
*/
|
|
58
45
|
export function snapshotTree(cwd) {
|
|
@@ -64,9 +51,8 @@ export function snapshotTree(cwd) {
|
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* unchanged does not.
|
|
54
|
+
* Compares content, not status: a mutation committed since shows up, and a file
|
|
55
|
+
* committed unchanged does not.
|
|
70
56
|
* @returns {{ at: string, headChanged: boolean, changed: string[], changedCount: number }}
|
|
71
57
|
*/
|
|
72
58
|
export function compareToSnapshot(cwd, snapshot) {
|
|
@@ -85,7 +71,7 @@ export function compareToSnapshot(cwd, snapshot) {
|
|
|
85
71
|
return { at: snapshot.at, headChanged: snapshot.head !== head, changed: changed.slice(0, MAX_LISTED), changedCount: changed.length };
|
|
86
72
|
}
|
|
87
73
|
|
|
88
|
-
|
|
74
|
+
// With `-z`, a rename or copy carries its old path as an extra entry.
|
|
89
75
|
function parsePorcelain(out) {
|
|
90
76
|
const entries = [];
|
|
91
77
|
const parts = out.split('\0');
|
|
@@ -118,7 +104,6 @@ function commitsOnTop(cwd) {
|
|
|
118
104
|
.split('\n').filter(Boolean);
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
/** Untracked files count too; gitignored ones (build output) do not. */
|
|
122
107
|
function markers(cwd) {
|
|
123
108
|
try {
|
|
124
109
|
const out = git(cwd, ['grep', '-n', '-I', '--untracked', '-F', '-e', MUTATION_MARKER, ...OUTSIDE_MORPH]);
|
|
@@ -132,19 +117,7 @@ function markers(cwd) {
|
|
|
132
117
|
}
|
|
133
118
|
}
|
|
134
119
|
|
|
135
|
-
/**
|
|
136
|
-
* @param {string} cwd
|
|
137
|
-
* @param {{ snapshot?: object|null }} [opts] - a `snapshotTree()` to compare with
|
|
138
|
-
* @returns {{ head: string|null, branch: string|null, fingerprint: string|null,
|
|
139
|
-
* dirty: {status: string, path: string}[], dirtyCount: number,
|
|
140
|
-
* diffstat: {files: number, insertions: number, deletions: number}|null,
|
|
141
|
-
* commits: string[]|null, markers: {file: string, line: number}[],
|
|
142
|
-
* sinceBeforeEval: ReturnType<typeof compareToSnapshot>|null }|null}
|
|
143
|
-
* null when git cannot answer (outside a repo, or an error): unknown, never
|
|
144
|
-
* a clean tree. `commits` is null when there is no default branch to count
|
|
145
|
-
* from; `diffstat` is null before the first commit; `sinceBeforeEval` is
|
|
146
|
-
* null without a snapshot.
|
|
147
|
-
*/
|
|
120
|
+
/** @returns {object|null} null when git cannot answer: unknown, never a clean tree. */
|
|
148
121
|
export function treeState(cwd, { snapshot = null } = {}) {
|
|
149
122
|
try {
|
|
150
123
|
const dirty = parsePorcelain(git(cwd, ['status', '--porcelain=v1', '-z', ...OUTSIDE_MORPH]));
|
|
@@ -1,31 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* O inventário do architecture-scanner diz o que EXISTE; os god nodes dizem o
|
|
5
|
-
* que é CENTRAL. No ProspectPRO, seis linhas (`Result` 223, `Abstractions` 217,
|
|
6
|
-
* `Repository` 156, `cn()` 141) orientam melhor que 665 de inventário — sem
|
|
7
|
-
* substituí-lo, porque exaustividade é um valor que o grafo não tem.
|
|
8
|
-
*
|
|
9
|
-
* Só entram nós COM source_file: um god node sem arquivo não orienta ninguém.
|
|
10
|
-
* E nada de teste: dublês e fixtures estão no grafo para serem achados, não
|
|
11
|
-
* para disputar centralidade — sem o filtro o `FakeGhlToolbelt` entra no top
|
|
12
|
-
* 10 do MORPH_GHLBrain e as arestas vindas de teste inflam o grau de `Result`
|
|
13
|
-
* de 380 para 443. Aresta com uma ponta em teste não conta para ninguém, e
|
|
14
|
-
* comunidade só de teste some da lista.
|
|
15
|
-
*
|
|
16
|
-
* @module god-nodes
|
|
17
|
-
*/
|
|
1
|
+
// Só nós com source_file, e nada de teste: dublês e fixtures estão no grafo para serem achados, não para
|
|
2
|
+
// disputar centralidade. Aresta com uma ponta em teste não conta, e comunidade só de teste some.
|
|
18
3
|
import { loadGraph } from './graph-loader.js';
|
|
19
4
|
import { buildIndex, resolveEdgeTarget, linksOf, isTestPath } from './sanitize.js';
|
|
20
5
|
|
|
21
6
|
/** Sem `graphify label`, as comunidades se chamam Community_N — ruído puro. */
|
|
22
7
|
const PLACEHOLDER_LABEL = /^Community[_ ]?\d+$/i;
|
|
23
8
|
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} projectPath
|
|
26
|
-
* @param {{top?: number, topCommunities?: number}} [opts]
|
|
27
|
-
* @returns {Promise<{available: boolean, godNodes: Array<{label: string, source_file: string, degree: number}>, communities: Array<{id: number, label: string, size: number, labeled: boolean}>}>}
|
|
28
|
-
*/
|
|
29
9
|
export async function scanGraph(projectPath, opts = {}) {
|
|
30
10
|
const top = opts.top ?? 10;
|
|
31
11
|
const topCommunities = opts.topCommunities ?? 10;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Auditoria do grafo para o `morph-spec doctor`.
|
|
3
|
-
*
|
|
4
|
-
* Nunca devolve estado de erro: o grafo é opcional (ADR-002) e não pode pintar
|
|
5
|
-
* de vermelho o doctor de um projeto que não o usa. O pior veredito é `warn`.
|
|
6
|
-
*
|
|
7
|
-
* @module graph-health
|
|
8
|
-
*/
|
|
1
|
+
// Nunca devolve erro: o grafo é opcional e não pode pintar de vermelho o doctor de quem não o usa.
|
|
9
2
|
import { execFileSync } from 'node:child_process';
|
|
10
3
|
import { readFileSync } from 'node:fs';
|
|
11
4
|
import { join, resolve } from 'node:path';
|
|
@@ -16,17 +9,8 @@ import { GRAPHIFY_VERSION, detectGraphifyVersion } from './graphify-runtime.js';
|
|
|
16
9
|
|
|
17
10
|
const GRAPHIFY_HOOK_MARKER = /# graphify-(?:checkout-)?hook-start/;
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* instalava até a 8.37) está no repositório? Ele é danoso aqui: num commit
|
|
22
|
-
* depois de um fast-forward carimba o HEAD num grafo SEM os arquivos que o
|
|
23
|
-
* merge trouxe — "fresh" falso — e disputa o graph.json com o refresh do
|
|
24
|
-
* morph-spec. Lê o diretório de hooks que o git usa (`core.hooksPath`
|
|
25
|
-
* incluído).
|
|
26
|
-
*
|
|
27
|
-
* @param {string} projectPath
|
|
28
|
-
* @returns {boolean}
|
|
29
|
-
*/
|
|
12
|
+
// O hook git do próprio graphify é danoso: depois de um fast-forward carimba o HEAD num grafo sem os
|
|
13
|
+
// arquivos do merge ("fresh" falso) e disputa o graph.json com o refresh. Respeita `core.hooksPath`.
|
|
30
14
|
export function graphifyGitHookInstalled(projectPath) {
|
|
31
15
|
let dir;
|
|
32
16
|
try {
|
|
@@ -46,11 +30,6 @@ export function graphifyGitHookInstalled(projectPath) {
|
|
|
46
30
|
});
|
|
47
31
|
}
|
|
48
32
|
|
|
49
|
-
/**
|
|
50
|
-
* @param {string} projectPath
|
|
51
|
-
* @param {{_version?: () => string|null}} [deps]
|
|
52
|
-
* @returns {{status: 'ok'|'warn'|'absent', msg: string, details: string[]}}
|
|
53
|
-
*/
|
|
54
33
|
export function auditGraph(projectPath, deps = {}) {
|
|
55
34
|
const installed = (deps._version ?? detectGraphifyVersion)();
|
|
56
35
|
const loaded = loadGraph(projectPath);
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Leitura fail-open do graph.json produzido pelo graphify.
|
|
3
|
-
*
|
|
4
|
-
* Medido em 2026-08-27 sobre o MORPH_GHLBrain (11,46 MB, 6.829 nós, 15.668
|
|
5
|
-
* arestas): JSON.parse leva 52 ms e 28 MB de heap. Streaming e cache são
|
|
6
|
-
* desnecessários nesse porte — se um dia deixarem de ser, a troca acontece só
|
|
7
|
-
* aqui, porque este módulo é a única porta de leitura do grafo.
|
|
8
|
-
*
|
|
9
|
-
* Nada aqui lança: ausência de grafo é um estado válido do projeto (ADR-002).
|
|
10
|
-
*
|
|
11
|
-
* @module graph-loader
|
|
12
|
-
*/
|
|
1
|
+
// A única porta de leitura do grafo, e nada aqui lança: ausência de grafo é um estado válido do projeto.
|
|
13
2
|
import { readFileSync, existsSync } from 'node:fs';
|
|
14
3
|
import { join } from 'node:path';
|
|
15
4
|
import {
|
|
@@ -21,18 +10,10 @@ import {
|
|
|
21
10
|
import { isSecondaryWorktree, workTreeTop } from '../../../framework/hooks/shared/git-paths.js';
|
|
22
11
|
import { GRAPHIFY_PACKAGE, GRAPHIFY_VERSION } from './graphify-runtime.js';
|
|
23
12
|
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} projectPath
|
|
26
|
-
* @returns {string} caminho canônico do graph.json do projeto
|
|
27
|
-
*/
|
|
28
13
|
export function resolveGraphPath(projectPath) {
|
|
29
14
|
return join(projectPath, 'graphify-out', 'graph.json');
|
|
30
15
|
}
|
|
31
16
|
|
|
32
|
-
/**
|
|
33
|
-
* @param {string} projectPath
|
|
34
|
-
* @returns {{ok: boolean, reason?: 'missing'|'unreadable'|'malformed', graph?: object, path: string}}
|
|
35
|
-
*/
|
|
36
17
|
export function loadGraph(projectPath) {
|
|
37
18
|
const path = resolveGraphPath(projectPath);
|
|
38
19
|
if (!existsSync(path)) return { ok: false, reason: 'missing', path };
|
|
@@ -57,18 +38,7 @@ export function loadGraph(projectPath) {
|
|
|
57
38
|
}
|
|
58
39
|
}
|
|
59
40
|
|
|
60
|
-
|
|
61
|
-
* O frescor do grafo, pela regra única (framework/hooks/shared/graph-freshness.js):
|
|
62
|
-
* o carimbo e o registro da última rodada contra o HEAD, e as regras do
|
|
63
|
-
* `.graphifyignore` por conteúdo. Um grafo velho diz "não existe" sobre o que
|
|
64
|
-
* o último commit trouxe — quem consome precisa saber, ou conclui "ninguém usa
|
|
65
|
-
* isso" a partir dele.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} projectPath
|
|
68
|
-
* @param {object} graph - o graph.json já parseado (o carimbo sai dele)
|
|
69
|
-
* @param {{_head?: (projectPath: string) => string|null}} [deps]
|
|
70
|
-
* @returns {{builtAtCommit: string|null, headCommit: string|null, state: 'fresh'|'stale'|'unknown', reason?: 'ignore-changed'|'rules-unknown'}}
|
|
71
|
-
*/
|
|
41
|
+
// Um grafo velho diz "não existe" sobre o que o último commit trouxe: quem consome precisa saber.
|
|
72
42
|
export function getFreshness(projectPath, graph, deps = {}) {
|
|
73
43
|
const f = graphFreshness(projectPath, {
|
|
74
44
|
graph,
|
|
@@ -84,12 +54,6 @@ export function getFreshness(projectPath, graph, deps = {}) {
|
|
|
84
54
|
|
|
85
55
|
const short = (sha) => (sha ? String(sha).slice(0, 8) : '?');
|
|
86
56
|
|
|
87
|
-
/**
|
|
88
|
-
* O porquê de um grafo stale, em uma linha — carimbo antigo ou regras.
|
|
89
|
-
*
|
|
90
|
-
* @param {{builtAtCommit: string|null, headCommit: string|null, reason?: string}} freshness
|
|
91
|
-
* @returns {string}
|
|
92
|
-
*/
|
|
93
57
|
export function staleCause(freshness) {
|
|
94
58
|
if (freshness.reason === 'ignore-changed') {
|
|
95
59
|
return 'as regras do .graphifyignore mudaram depois da extração';
|
|
@@ -100,15 +64,8 @@ export function staleCause(freshness) {
|
|
|
100
64
|
return `construído em ${short(freshness.builtAtCommit)}, HEAD é ${short(freshness.headCommit)}`;
|
|
101
65
|
}
|
|
102
66
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* principal (`auto`); numa worktree, `running` é sempre false — um lock vivo
|
|
106
|
-
* ali seria o refresh da raiz. `last` é a última rodada registrada para esta
|
|
107
|
-
* árvore: em background, manual ou a semeadura.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} projectPath
|
|
110
|
-
* @returns {{auto: boolean, running: boolean, since: string|null, pid: number|null, last: {ok: boolean, exit: number|null, head: string|null, ts: string, unavailable?: boolean, seeded?: boolean}|null}}
|
|
111
|
-
*/
|
|
67
|
+
// O refresh automático cobre só a árvore principal; numa worktree `running` é sempre false, porque um
|
|
68
|
+
// lock vivo ali seria o refresh da raiz.
|
|
112
69
|
export function refreshState(projectPath) {
|
|
113
70
|
const auto = isPrimaryTree(projectPath);
|
|
114
71
|
const lock = auto ? readRefreshLock(projectPath) : null;
|
|
@@ -129,15 +86,7 @@ export function refreshState(projectPath) {
|
|
|
129
86
|
};
|
|
130
87
|
}
|
|
131
88
|
|
|
132
|
-
/**
|
|
133
|
-
* A frase de grafo stale — a mesma no status, no explain, no affected e no
|
|
134
|
-
* doctor, e verdadeira sobre o mecanismo: o refresh em background só cobre a
|
|
135
|
-
* raiz, disparado pelos hooks do morph-spec; a worktree só anda à mão.
|
|
136
|
-
*
|
|
137
|
-
* @param {string} projectPath
|
|
138
|
-
* @param {{builtAtCommit?: string|null, headCommit?: string|null}} [freshness]
|
|
139
|
-
* @returns {string}
|
|
140
|
-
*/
|
|
89
|
+
/** A frase única de grafo stale do status, explain, affected e doctor. */
|
|
141
90
|
export function describeStale(projectPath, freshness = {}) {
|
|
142
91
|
const tree = workTreeTop(projectPath) ?? projectPath;
|
|
143
92
|
const state = refreshState(projectPath);
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* O binário instalado chama-se `graphify` (e `graphify-mcp` para o servidor
|
|
5
|
-
* MCP). NUNCA use `python -m graphify.serve`, que é o que o README oficial
|
|
6
|
-
* manda: com `uv tool install` o pacote vive num venv isolado e o Python do
|
|
7
|
-
* sistema não o enxerga (ModuleNotFoundError) — medido no eval, Eixo 2.
|
|
8
|
-
*
|
|
9
|
-
* Toda descoberta é injetável (`_which`, `_run`) para que o teste cubra Windows
|
|
10
|
-
* e POSIX sem depender do PATH da máquina que roda o CI.
|
|
11
|
-
*
|
|
12
|
-
* @module graphify-runtime
|
|
13
|
-
*/
|
|
1
|
+
// O binário é `graphify` (e `graphify-mcp`). NUNCA `python -m graphify.serve`, como manda o README oficial:
|
|
2
|
+
// com `uv tool install` o pacote vive num venv isolado que o Python do sistema não enxerga.
|
|
14
3
|
import { execFileSync } from 'node:child_process';
|
|
15
4
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* regressão. O graphify é pré-1.0 com release quase diária, então semver não
|
|
19
|
-
* garante nada — bump é decisão consciente, com re-run do fixture.
|
|
20
|
-
*/
|
|
5
|
+
// Pin exato: o graphify é pré-1.0 com release quase diária e semver não garante nada; subir é decisão
|
|
6
|
+
// consciente, com o fixture de regressão regerado.
|
|
21
7
|
export const GRAPHIFY_VERSION = '0.9.50';
|
|
22
8
|
export const GRAPHIFY_PACKAGE = 'graphifyy';
|
|
23
9
|
export const GRAPHIFY_BIN = 'graphify';
|
|
@@ -38,14 +24,7 @@ function whichSync(bin) {
|
|
|
38
24
|
}
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
* uv > pipx > python3 > python. `kind: null` significa "sem Python": o step do
|
|
43
|
-
* setup-infra registra e segue, e o framework continua funcionando por grep
|
|
44
|
-
* (ADR-002).
|
|
45
|
-
*
|
|
46
|
-
* @param {{_which?: (bin: string) => string|null}} [deps]
|
|
47
|
-
* @returns {{kind: 'uv'|'pipx'|'python3'|'python'|null, bin: string|null}}
|
|
48
|
-
*/
|
|
27
|
+
// `kind: null` é "sem Python": o setup registra e segue, e o framework continua funcionando por grep.
|
|
49
28
|
export function detectInstaller(deps = {}) {
|
|
50
29
|
const which = deps._which ?? whichSync;
|
|
51
30
|
for (const kind of PREFERENCE) {
|
|
@@ -60,13 +39,7 @@ export function detectInstaller(deps = {}) {
|
|
|
60
39
|
return { kind: null, bin: null };
|
|
61
40
|
}
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
* @param {'uv'|'pipx'|'python3'|'python'} kind
|
|
65
|
-
* @param {{withSql: boolean}} opts sem o extra `[sql]` os arquivos .sql são
|
|
66
|
-
* ignorados em silêncio (92 no ProspectPRO) — só um aviso no meio do output
|
|
67
|
-
* denuncia
|
|
68
|
-
* @returns {{cmd: string, args: string[]}}
|
|
69
|
-
*/
|
|
42
|
+
// Sem o extra `[sql]` os arquivos .sql são ignorados em silêncio.
|
|
70
43
|
export function installArgs(kind, { withSql }) {
|
|
71
44
|
const spec = `${GRAPHIFY_PACKAGE}${withSql ? '[sql]' : ''}==${GRAPHIFY_VERSION}`;
|
|
72
45
|
switch (kind) {
|
|
@@ -82,10 +55,7 @@ export function installArgs(kind, { withSql }) {
|
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
/**
|
|
86
|
-
* @param {{_run?: () => string}} [deps]
|
|
87
|
-
* @returns {string|null} versão instalada, ou null se o binário não responde
|
|
88
|
-
*/
|
|
58
|
+
/** Versão instalada, ou null se o binário não responde. */
|
|
89
59
|
export function detectGraphifyVersion(deps = {}) {
|
|
90
60
|
const run = deps._run ?? (() => execFileSync(GRAPHIFY_BIN, ['--version'], {
|
|
91
61
|
encoding: 'utf-8',
|
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Filtrar teste não é cosmético: sem isso o god node #1 do MORPH_GHLBrain é
|
|
5
|
-
* `FakeGhlToolbelt`, um dublê. E o padrão genérico `tests/` NÃO serve em .NET:
|
|
6
|
-
* no ProspectPRO os testes vivem em `src/backend/ProspectPRO.Tests/`. Com
|
|
7
|
-
* `**\/*.Tests/` o grafo caiu de 10.450 para 7.384 nós — 30% era ruído de teste.
|
|
8
|
-
*
|
|
9
|
-
* Mas dublê, fixture e helper de teste precisam ser ACHÁVEIS: são o que um
|
|
10
|
-
* teste novo deveria reusar, e fora do grafo `graph explain FakeX` dá
|
|
11
|
-
* not-found. Por isso o conteúdo dos projetos de teste sai e esses arquivos
|
|
12
|
-
* voltam. A forma é ditada pela regra do gitignore que o graphify 0.9.50 segue
|
|
13
|
-
* (detect.py `_is_ignored`): um `!` não reinclui nada sob um diretório já
|
|
14
|
-
* excluído. Medido num fixture: `**\/*.Tests/` + `!**\/Fakes/` não traz nada de
|
|
15
|
-
* volta; excluir o CONTEÚDO (`**\/*.Tests/**`), reincluir os diretórios
|
|
16
|
-
* (`!**\/*.Tests/**\/`) e então os helpers traz. `bin/`/`obj/` vêm DEPOIS das
|
|
17
|
-
* reinclusões — antes, a reinclusão de diretório os devolveria (`obj/` tem
|
|
18
|
-
* `.cs` gerado). No MORPH_GHLBrain: +9,6% de nós (11.591 → 12.706), 94
|
|
19
|
-
* arquivos de teste, nenhum de bin/obj.
|
|
20
|
-
*
|
|
21
|
-
* Os dublês quebram o guard de "implementador único" do `dispatches_to` do
|
|
22
|
-
* graphify (de 122 para 5 arestas no GHLBrain); o saneamento as recupera
|
|
23
|
-
* (sanitize.js `recoverDispatch`) e tira o teste dos god nodes.
|
|
24
|
-
*
|
|
25
|
-
* Em Next.js o problema é outro: metade dos nós vinha de JSON de configuração,
|
|
26
|
-
* e os god nodes viravam `dependencies` e `compilerOptions`.
|
|
27
|
-
*
|
|
28
|
-
* @module graphifyignore
|
|
29
|
-
*/
|
|
1
|
+
// Teste sai do grafo, mas dublês e helpers voltam: são o que um teste novo reusa. Pela regra do gitignore
|
|
2
|
+
// um `!` não reinclui nada sob diretório excluído, então exclui-se o conteúdo, reincluem-se os diretórios e
|
|
3
|
+
// então os helpers; `bin/`/`obj/` vêm depois, senão a reinclusão os devolveria.
|
|
30
4
|
import { writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
31
5
|
import { execFileSync } from 'node:child_process';
|
|
32
6
|
import { join } from 'node:path';
|
|
@@ -60,10 +34,7 @@ const WEB_IGNORED = [
|
|
|
60
34
|
|
|
61
35
|
const HEADER = '# Gerado por morph-spec (ADR-004). Editável: um arquivo editado nunca é sobrescrito.';
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
* O que a versão anterior gerava — os testes .NET inteiros fora. Só existe
|
|
65
|
-
* para reconhecer um arquivo que ninguém editou e trocá-lo pelo atual.
|
|
66
|
-
*/
|
|
37
|
+
// O que a geração anterior escrevia: só reconhece um arquivo que ninguém editou, para trocá-lo.
|
|
67
38
|
const PREVIOUS = {
|
|
68
39
|
header: '# Gerado por morph-spec (ADR-004). Editável: o CLI não sobrescreve.',
|
|
69
40
|
dotnetTitle: '# .NET — testes em qualquer nível (tests/ genérico não serve)',
|
|
@@ -99,11 +70,6 @@ function render(projectTags, { header, dotnetTitle, dotnet }) {
|
|
|
99
70
|
return `${lines.join('\n')}\n`;
|
|
100
71
|
}
|
|
101
72
|
|
|
102
|
-
/**
|
|
103
|
-
* @param {string[]} [projectTags] saída de `getProjectTags(config)` — stack
|
|
104
|
-
* splitado por `-`/`+` mais as integrations
|
|
105
|
-
* @returns {string} conteúdo completo do .graphifyignore
|
|
106
|
-
*/
|
|
107
73
|
export function buildGraphifyignore(projectTags = []) {
|
|
108
74
|
return render(projectTags, {
|
|
109
75
|
header: HEADER,
|
|
@@ -114,28 +80,8 @@ export function buildGraphifyignore(projectTags = []) {
|
|
|
114
80
|
|
|
115
81
|
const normalize = (text) => String(text).replace(/^/, '').replace(/\r\n/g, '\n');
|
|
116
82
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*
|
|
120
|
-
* ausente → escreve o atual
|
|
121
|
-
* idêntico ao que a versão anterior
|
|
122
|
-
* gerava para as mesmas tags → troca pelo atual (`migrated`) —
|
|
123
|
-
* só se as REGRAS mudam (em
|
|
124
|
-
* projeto sem .NET a diferença é
|
|
125
|
-
* só o cabeçalho, e reescrever
|
|
126
|
-
* seria um diff à toa num arquivo
|
|
127
|
-
* versionado)
|
|
128
|
-
* qualquer outra coisa → é do usuário: não toca, e em
|
|
129
|
-
* projeto .NET que ainda exclui os
|
|
130
|
-
* testes inteiros devolve em
|
|
131
|
-
* `outdated` a troca a fazer
|
|
132
|
-
*
|
|
133
|
-
* @param {string} projectPath
|
|
134
|
-
* @param {string[]} [projectTags]
|
|
135
|
-
* @param {{fx?: {writeFile: (path: string, content: string) => void}}} [opts]
|
|
136
|
-
* coletor de efeitos do `update` — com ele, o `--dry-run` prevê a escrita
|
|
137
|
-
* @returns {{written: boolean, migrated?: boolean, outdated?: {remove: string[], add: string[]}, path: string}}
|
|
138
|
-
*/
|
|
83
|
+
// Ausente → escreve. Idêntico ao que a geração anterior escrevia → migra, só se as regras mudam.
|
|
84
|
+
// Qualquer outra coisa é do usuário: não toca, e em .NET devolve em `outdated` a troca a fazer.
|
|
139
85
|
export function writeGraphifyignore(projectPath, projectTags = [], { fx } = {}) {
|
|
140
86
|
const path = join(projectPath, '.graphifyignore');
|
|
141
87
|
const current = buildGraphifyignore(projectTags);
|
|
@@ -172,26 +118,14 @@ export function writeGraphifyignore(projectPath, projectTags = [], { fx } = {})
|
|
|
172
118
|
return { written: false, path };
|
|
173
119
|
}
|
|
174
120
|
|
|
175
|
-
/**
|
|
176
|
-
* A frase que todo chamador imprime para um `outdated` — uma só.
|
|
177
|
-
*
|
|
178
|
-
* @param {{remove: string[], add: string[]}} outdated
|
|
179
|
-
* @returns {string}
|
|
180
|
-
*/
|
|
121
|
+
/** A frase única que todo chamador imprime para um `outdated`. */
|
|
181
122
|
export function describeOutdatedIgnore(outdated) {
|
|
182
123
|
return `.graphifyignore editado mantém os testes .NET inteiros fora do grafo ` +
|
|
183
124
|
`(dublês e fixtures dão not-found). Troque ${outdated.remove.join(' ')} por, nesta ordem e ` +
|
|
184
125
|
`antes de bin/ e obj/: ${outdated.add.join(' ')} — depois, morph-spec graph refresh`;
|
|
185
126
|
}
|
|
186
127
|
|
|
187
|
-
|
|
188
|
-
* O `.graphifyignore` é versionado? Então uma migração só chega às worktrees
|
|
189
|
-
* e ao resto do time depois de commitada — cada checkout lê o seu. Fail-open:
|
|
190
|
-
* sem git, false.
|
|
191
|
-
*
|
|
192
|
-
* @param {string} projectPath
|
|
193
|
-
* @returns {boolean}
|
|
194
|
-
*/
|
|
128
|
+
// Versionado, uma migração só chega às worktrees depois de commitada. Fail-open: sem git, false.
|
|
195
129
|
export function graphifyignoreTracked(projectPath) {
|
|
196
130
|
try {
|
|
197
131
|
execFileSync('git', ['ls-files', '--error-unmatch', '--', '.graphifyignore'], {
|