@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,55 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* string, preservando comprimento e quebras de linha.
|
|
4
|
-
*
|
|
5
|
-
* Existe uma vez só, e serve a consumidores que fariam a MESMA coisa de
|
|
6
|
-
* jeitos ligeiramente diferentes se cada um escrevesse o seu:
|
|
7
|
-
*
|
|
8
|
-
* - `code-audit.js` — não pode acusar `AgentThread` que só aparece num
|
|
9
|
-
* comentário ou numa mensagem de log;
|
|
10
|
-
* - `registry-audit.js` — precisa ler **JSONC**, a forma que o exemplar
|
|
11
|
-
* canônico `model-registry.v2.json` usa e que o leitor C# do kit aceita de
|
|
12
|
-
* propósito (`JsonCommentHandling.Skip`, `AllowTrailingCommas`);
|
|
13
|
-
* - `dotnet-test-introspection.js` — lê o `global.json` por `jsoncToJson`;
|
|
14
|
-
* - `comment-scanner.js` (ao lado; lido pelo `code-budget` e pelo hook
|
|
15
|
-
* `comment-guard`) — acha as linhas de
|
|
16
|
-
* comentário de `.cs`/`.ts`/`.js`, e é o único que liga a opção `js`.
|
|
17
|
-
*
|
|
18
|
-
* **Por que não um regex.** O exemplar tem `"http://localhost:11434/v1"` e uma
|
|
19
|
-
* `provenance` com URL. Um `replace(/\/\/.*$/gm, '')` decapitaria as duas —
|
|
20
|
-
* apagando dado e chamando isso de comentário. Um varredor que atravessa as
|
|
21
|
-
* strings primeiro é a única forma correta, e é o que está aqui.
|
|
22
|
-
*
|
|
23
|
-
* O comprimento é preservado (os removidos viram espaço, `\n` e `\r` ficam) para
|
|
24
|
-
* que índices e números de linha do texto original continuem válidos no texto
|
|
25
|
-
* limpo. É o que permite `code-audit.js` reportar `arquivo.cs:34` sem um segundo
|
|
26
|
-
* mapeamento.
|
|
27
|
-
*/
|
|
1
|
+
// Atravessa as strings antes: um regex apagaria o `//` de `"http://localhost"`.
|
|
2
|
+
// O comprimento é preservado para índices e números de linha continuarem válidos.
|
|
28
3
|
|
|
29
4
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Apaga também o CONTEÚDO das strings. **As aspas delimitadoras ficam.**
|
|
34
|
-
*
|
|
35
|
-
* Apagá-las junto era um defeito com consequência: um elemento-string virava
|
|
36
|
-
* espaço puro, e o detector de vírgula sobrando de `jsoncToJson` passava a
|
|
37
|
-
* enxergar `[ , , ]` — separadores REAIS entre delimitadores que sumiram —
|
|
38
|
-
* e apagava uma vírgula que era sintaxe. Medido: `{"tags":["a","b"]}` saía
|
|
39
|
-
* `{"tags":["a","b" ]}`, JSON inválido, e o registry voltava a ser
|
|
40
|
-
* `○ não verificado`. O B1 reintroduzido pela correção do B1.
|
|
41
|
-
* @param {{ unterminated?: string|null }} [options.report]
|
|
42
|
-
* Se um objeto for passado, recebe `unterminated` = `'block-comment'`,
|
|
43
|
-
* `'string'`, `'verbatim-string'`, `'raw-string'`, `'template'` ou `null`. É o canal de
|
|
44
|
-
* diagnóstico que `jsoncToJson` usa para ficar tão estrito quanto o leitor
|
|
45
|
-
* de produção, sem que este lexer precise lançar (ele também varre `.cs`,
|
|
46
|
-
* onde um comentário aberto é problema do arquivo, não da varredura).
|
|
47
|
-
* @param {boolean} [options.js=false]
|
|
48
|
-
* Lê também template literal e regex literal de TS/JS. Opt-in porque só
|
|
49
|
-
* quem varre `.ts`/`.js` precisa: sem template, `https://${h}` vira
|
|
50
|
-
* comentário; sem regex, uma crase dentro de `/[`']/` abre um template que
|
|
51
|
-
* engole o resto do arquivo.
|
|
52
|
-
* @returns {string} texto do MESMO comprimento
|
|
5
|
+
* `blankStrings` apaga o conteúdo das strings, mas as ASPAS FICAM: sem elas um
|
|
6
|
+
* separador real vira vírgula sobrando. `report.unterminated` recebe o literal não
|
|
7
|
+
* fechado; `js` lê template e regex literal, sem os quais `https://${h}` vira comentário.
|
|
53
8
|
*/
|
|
54
9
|
export function stripComments(text, { blankStrings = false, report = null, js = false } = {}) {
|
|
55
10
|
const out = text.split('');
|
|
@@ -95,7 +50,7 @@ export function stripComments(text, { blankStrings = false, report = null, js =
|
|
|
95
50
|
if (end !== -1) { i = end; continue; }
|
|
96
51
|
}
|
|
97
52
|
|
|
98
|
-
//
|
|
53
|
+
// C# raw string literal
|
|
99
54
|
if (c === '"' && next === '"' && text[i + 2] === '"') {
|
|
100
55
|
const end = text.indexOf('"""', i + 3);
|
|
101
56
|
if (end === -1) flag('raw-string');
|
|
@@ -105,7 +60,7 @@ export function stripComments(text, { blankStrings = false, report = null, js =
|
|
|
105
60
|
continue;
|
|
106
61
|
}
|
|
107
62
|
|
|
108
|
-
//
|
|
63
|
+
// C# verbatim string: doubled quotes escape
|
|
109
64
|
if (c === '@' && next === '"') {
|
|
110
65
|
let j = i + 2;
|
|
111
66
|
let closed = false;
|
|
@@ -159,7 +114,6 @@ export function stripComments(text, { blankStrings = false, report = null, js =
|
|
|
159
114
|
return out.join('');
|
|
160
115
|
}
|
|
161
116
|
|
|
162
|
-
/** Índice logo após a crase que fecha o template aberto em `start`, ou -1. */
|
|
163
117
|
function templateEnd(text, start) {
|
|
164
118
|
let j = start + 1;
|
|
165
119
|
while (j < text.length) {
|
|
@@ -176,11 +130,7 @@ const REGEX_AFTER_WORD = new Set([
|
|
|
176
130
|
'throw', 'instanceof', 'yield', 'await',
|
|
177
131
|
]);
|
|
178
132
|
|
|
179
|
-
|
|
180
|
-
* Uma `/` abre regex, e não divisão, quando o que vem antes não é um valor: o
|
|
181
|
-
* mesmo critério de contexto dos tokenizers de JS. `out` já tem os comentários
|
|
182
|
-
* apagados, então o caractere anterior é código. Sem `<`: em TSX, `</b>` é tag.
|
|
183
|
-
*/
|
|
133
|
+
// `/` abre regex quando o que vem antes não é um valor. Sem `<`: em TSX, `</b>` é tag.
|
|
184
134
|
function regexMayStart(out, i) {
|
|
185
135
|
let k = i - 1;
|
|
186
136
|
while (k >= 0 && /\s/.test(out[k])) k--;
|
|
@@ -192,7 +142,6 @@ function regexMayStart(out, i) {
|
|
|
192
142
|
return REGEX_AFTER_WORD.has(out.slice(w, k + 1).join(''));
|
|
193
143
|
}
|
|
194
144
|
|
|
195
|
-
/** Índice logo após a `/` (e as flags) que fecha a regex aberta em `start`, ou -1 se a linha acaba antes. */
|
|
196
145
|
function regexEnd(text, start) {
|
|
197
146
|
let inClass = false;
|
|
198
147
|
for (let j = start + 1; j < text.length; j++) {
|
|
@@ -210,7 +159,6 @@ function regexEnd(text, start) {
|
|
|
210
159
|
return -1;
|
|
211
160
|
}
|
|
212
161
|
|
|
213
|
-
/** Índice logo após o `}` que fecha um `${`: o código do buraco aninha chaves, strings e templates. */
|
|
214
162
|
function holeEnd(text, start) {
|
|
215
163
|
let depth = 1;
|
|
216
164
|
let j = start;
|
|
@@ -235,31 +183,9 @@ function holeEnd(text, start) {
|
|
|
235
183
|
return text.length;
|
|
236
184
|
}
|
|
237
185
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
* As duas tolerâncias são **exatamente** as que `ModelRegistry.FromJson` do
|
|
242
|
-
* ai-kit declara (`CommentHandling = Skip`, `AllowTrailingCommas = true`). A
|
|
243
|
-
* ferramenta que audita o registry tem de enxergar a mesma forma que o leitor
|
|
244
|
-
* de produção enxerga — senão o framework passa a recomendar uma forma que a
|
|
245
|
-
* sua própria auditoria não lê, e as checagens ficam inertes em silêncio.
|
|
246
|
-
*
|
|
247
|
-
* A vírgula sobrando é localizada num texto onde o conteúdo das strings também
|
|
248
|
-
* foi apagado — **com as aspas preservadas**, senão um elemento-string vira
|
|
249
|
-
* espaço puro e um separador REAL passa por vírgula sobrando (o defeito B4) —
|
|
250
|
-
* e só então removida do texto real: uma vírgula dentro de `"a,}"` é dado.
|
|
251
|
-
*
|
|
252
|
-
* **Um comentário de bloco não fechado LANÇA**, e a razão é a mesma que fez
|
|
253
|
-
* esta função existir: paridade com o leitor de produção. `JsonCommentHandling.Skip`
|
|
254
|
-
* do .NET lança nesse caso, e ser mais frouxo que o kit é o defeito INVERSO do
|
|
255
|
-
* B1 — auditar, e aprovar, um documento que a aplicação recusa carregar.
|
|
256
|
-
* `readJsonSafe` captura e devolve `null`, que o relatório traduz em
|
|
257
|
-
* "não verificado" com razão.
|
|
258
|
-
*
|
|
259
|
-
* @param {string} raw
|
|
260
|
-
* @returns {string} JSON estrito, do mesmo comprimento do original
|
|
261
|
-
* @throws {SyntaxError} comentário de bloco aberto e nunca fechado
|
|
262
|
-
*/
|
|
186
|
+
// Paridade com `ModelRegistry.FromJson` do ai-kit, nos dois sentidos: comentário de
|
|
187
|
+
// bloco não fechado LANÇA, como `JsonCommentHandling.Skip`, porque ser mais frouxo
|
|
188
|
+
// que o kit é aprovar um documento que a aplicação recusa carregar.
|
|
263
189
|
export function jsoncToJson(raw) {
|
|
264
190
|
const report = {};
|
|
265
191
|
const noComments = stripComments(raw, { report }).split('');
|
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const ERRATA_HEADING = '## Errata';
|
|
2
|
+
|
|
3
|
+
/** The one normalization every reader of a frozen markdown applies. */
|
|
4
|
+
export function normalizeMarkdown(text) {
|
|
5
|
+
return String(text ?? '').replace(/^/, '').replace(/\r\n?/g, '\n');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function markdownLines(text) {
|
|
9
|
+
const out = [];
|
|
10
|
+
let fence = null;
|
|
11
|
+
normalizeMarkdown(text).split('\n').forEach((raw, i) => {
|
|
12
|
+
const opener = /^\s*(`{3,}|~{3,})/.exec(raw);
|
|
13
|
+
let fenced = Boolean(fence);
|
|
14
|
+
if (fence) {
|
|
15
|
+
if (opener && opener[1][0] === fence[0] && opener[1].length >= fence.length) fence = null;
|
|
16
|
+
} else if (opener) {
|
|
17
|
+
fence = opener[1];
|
|
18
|
+
fenced = true;
|
|
19
|
+
}
|
|
20
|
+
out.push({ raw, n: i + 1, fenced });
|
|
21
|
+
});
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const isErrataHeading = (line) => !line.fenced
|
|
26
|
+
&& /^##\s+errata\s*$/i.test(line.raw);
|
|
27
|
+
|
|
28
|
+
/** Offset in the NORMALIZED text, or -1. */
|
|
29
|
+
export function errataOffset(text) {
|
|
30
|
+
let offset = 0;
|
|
31
|
+
for (const line of markdownLines(text)) {
|
|
32
|
+
if (isErrataHeading(line)) return offset;
|
|
33
|
+
offset += line.raw.length + 1;
|
|
34
|
+
}
|
|
35
|
+
return -1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function frozenBody(text) {
|
|
39
|
+
const norm = normalizeMarkdown(text);
|
|
40
|
+
const at = errataOffset(norm);
|
|
41
|
+
return (at === -1 ? norm : norm.slice(0, at)).trimEnd();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function errataViolation(original, proposed) {
|
|
45
|
+
if (frozenBody(proposed) !== frozenBody(original)) {
|
|
46
|
+
return { ok: false, why: 'o corpo aprovado mudou — errata só ACRESCENTA ao fim, nunca altera o texto aprovado' };
|
|
47
|
+
}
|
|
48
|
+
const next = normalizeMarkdown(proposed);
|
|
49
|
+
const nextAt = errataOffset(next);
|
|
50
|
+
if (nextAt === -1) {
|
|
51
|
+
return { ok: false, why: `o conteúdo novo precisa estar sob um bloco \`${ERRATA_HEADING}\` no fim do arquivo` };
|
|
52
|
+
}
|
|
53
|
+
const prev = normalizeMarkdown(original);
|
|
54
|
+
const prevAt = errataOffset(prev);
|
|
55
|
+
if (prevAt !== -1 && !next.slice(nextAt).startsWith(prev.slice(prevAt).trimEnd())) {
|
|
56
|
+
return { ok: false, why: 'a errata existente foi reescrita — ela é append-only, entradas anteriores não se apagam' };
|
|
57
|
+
}
|
|
58
|
+
return { ok: true };
|
|
59
|
+
}
|
|
@@ -1,41 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* `resolveMorphRoot` (payload-utils) answers this from a file path. Some hook
|
|
5
|
-
* events carry no path at all — an `AskUserQuestion` gate approval is the one
|
|
6
|
-
* that matters — and those were resolved by "the first in_progress feature of
|
|
7
|
-
* whatever tree the session happens to sit in". That is how a Gate 2 approval
|
|
8
|
-
* for one feature landed on `export-hub`, an unrelated feature that merely
|
|
9
|
-
* happened to be active in the primary root.
|
|
10
|
-
*
|
|
11
|
-
* The honest signal for a pathless event is the question text itself: every
|
|
12
|
-
* gate template in the core skills names its feature — "Gate 1 — Proposta de
|
|
13
|
-
* {feature} aprovada?", "Gate 2 — Plano de {feature} aprovado?", "Gate 3 —
|
|
14
|
-
* Review de {feature} aprovado?", "Gate 3 — Hotfix de {feature} pronto para
|
|
15
|
-
* ship?". Rather than parse those sentences (which would break the moment a
|
|
16
|
-
* template is reworded), match the question against the set of feature slugs
|
|
17
|
-
* that actually exist on disk.
|
|
18
|
-
*
|
|
19
|
-
* When the text names no known feature, the caller must REFUSE TO GUESS.
|
|
20
|
-
* Stamping a gate onto the wrong feature is close to unrecoverable — it inverts
|
|
21
|
-
* gate order and invites a second stamp on the real owner; writing nothing
|
|
22
|
-
* costs one `morph-spec approve`.
|
|
23
|
-
*
|
|
24
|
-
* Fail-open by contract: every function swallows errors and returns an empty
|
|
25
|
-
* result rather than throwing.
|
|
26
|
-
*/
|
|
1
|
+
// A pathless gate question is matched against the slugs on disk, never parsed.
|
|
2
|
+
// When it names no known feature the caller must REFUSE TO GUESS: a gate on the
|
|
3
|
+
// wrong feature is close to unrecoverable, writing nothing costs one `approve`.
|
|
27
4
|
|
|
28
5
|
import { execSync } from 'child_process';
|
|
29
6
|
import { loadState } from './state-reader.js';
|
|
30
7
|
|
|
31
|
-
|
|
32
|
-
* Feature slugs known to `root`.
|
|
33
|
-
*
|
|
34
|
-
* Deliberately `loadState` and not a `readdirSync` of `.morph/features/`: state
|
|
35
|
-
* lives in BOTH shapes — the thin `.morph/state.json` index and the per-feature
|
|
36
|
-
* `feature.json` — and `loadState` is the one place that already merges them.
|
|
37
|
-
* Listing directories alone misses every feature carried only by the index.
|
|
38
|
-
*/
|
|
8
|
+
// `loadState`, not a directory listing: it also sees features only the index carries.
|
|
39
9
|
export function featureNamesIn(root) {
|
|
40
10
|
try {
|
|
41
11
|
const state = loadState(root);
|
|
@@ -45,11 +15,6 @@ export function featureNamesIn(root) {
|
|
|
45
15
|
}
|
|
46
16
|
}
|
|
47
17
|
|
|
48
|
-
/**
|
|
49
|
-
* Every working tree of this repo — the primary root plus every registered
|
|
50
|
-
* secondary worktree. Fail-open to `[sessionRoot]` when git is unavailable or
|
|
51
|
-
* the directory is not a repo.
|
|
52
|
-
*/
|
|
53
18
|
export function allWorkTrees(sessionRoot) {
|
|
54
19
|
const roots = [sessionRoot];
|
|
55
20
|
try {
|
|
@@ -64,26 +29,14 @@ export function allWorkTrees(sessionRoot) {
|
|
|
64
29
|
if (path && !roots.includes(path)) roots.push(path);
|
|
65
30
|
}
|
|
66
31
|
} catch {
|
|
67
|
-
/* fail-open — the session root alone */
|
|
68
32
|
}
|
|
69
33
|
return roots;
|
|
70
34
|
}
|
|
71
35
|
|
|
72
36
|
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
* Slug boundaries are `[^a-z0-9-]` so a longer name never matches inside a
|
|
78
|
-
* shorter one (`export` must not match the text "export-hub"). Zero matches
|
|
79
|
-
* means the text named nothing; two or more means it named two features and is
|
|
80
|
-
* ambiguous. Both are null — "I don't know" and "it's ambiguous" must lead to
|
|
81
|
-
* the same refusal, not to a coin flip.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} text
|
|
84
|
-
* @param {string[]} names - candidate slugs
|
|
85
|
-
* @returns {string|null}
|
|
86
|
-
*/
|
|
38
|
+
// `export` must not match inside "export-hub". Zero and ambiguous matches are
|
|
39
|
+
// both null: they must lead to the same refusal.
|
|
87
40
|
export function featureNamedIn(text, names) {
|
|
88
41
|
if (typeof text !== 'string' || !Array.isArray(names) || names.length === 0) return null;
|
|
89
42
|
const haystack = text.toLowerCase();
|
|
@@ -93,28 +46,8 @@ export function featureNamedIn(text, names) {
|
|
|
93
46
|
return hits.length === 1 ? hits[0] : null;
|
|
94
47
|
}
|
|
95
48
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* hyphen — `morph-spec create`'s naming convention) that names none of
|
|
99
|
-
* `names`. Signals "the text is talking about a specific feature I don't
|
|
100
|
-
* know" as opposed to "the text names nothing in particular".
|
|
101
|
-
*
|
|
102
|
-
* This is what the single-feature fallback below was missing: a gate question
|
|
103
|
-
* for a feature not yet registered (no `create`, no `proposal.md` written
|
|
104
|
-
* yet — a fresh hotfix, say) names NO known feature either, and used to fall
|
|
105
|
-
* through to "the tree's one feature must be it". That is exactly how a Gate 3
|
|
106
|
-
* for `backfill-bool-filter` landed on `export-hub`, the tree's only
|
|
107
|
-
* registered feature at the time.
|
|
108
|
-
*
|
|
109
|
-
* Deliberately narrow (requires a hyphen) to match real feature slugs
|
|
110
|
-
* (`export-hub`, `backfill-bool-filter`) without flagging ordinary prose —
|
|
111
|
-
* the fixed portions of every gate template ("Proposta", "aprovado", "Sinal",
|
|
112
|
-
* "pronto para ship"...) are single words with no internal hyphen.
|
|
113
|
-
*
|
|
114
|
-
* @param {string} text
|
|
115
|
-
* @param {string[]} names - every feature slug known across all trees
|
|
116
|
-
* @returns {string|null}
|
|
117
|
-
*/
|
|
49
|
+
// A slug-shaped token naming no known feature: the question is about a feature
|
|
50
|
+
// not yet registered. Requires a hyphen, so template prose never matches.
|
|
118
51
|
export function unrecognizedSlugLike(text, names) {
|
|
119
52
|
if (typeof text !== 'string') return null;
|
|
120
53
|
const known = new Set((names || []).map((n) => String(n).toLowerCase()));
|
|
@@ -122,17 +55,8 @@ export function unrecognizedSlugLike(text, names) {
|
|
|
122
55
|
return tokens.find((t) => !known.has(t)) ?? null;
|
|
123
56
|
}
|
|
124
57
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
*
|
|
128
|
-
* The session root is consulted first so the common case costs no git call, and
|
|
129
|
-
* so a feature present in BOTH trees resolves to the tree the session is
|
|
130
|
-
* actually in — the same tree `morph-spec approve` would write from that cwd.
|
|
131
|
-
* Only when the session root knows no such feature do we pay for enumerating
|
|
132
|
-
* the worktrees.
|
|
133
|
-
*
|
|
134
|
-
* @returns {{name: string, root: string}|null} null ⇒ caller must not guess
|
|
135
|
-
*/
|
|
58
|
+
// The session root wins a feature present in both trees: it is where
|
|
59
|
+
// `morph-spec approve` would write from that cwd.
|
|
136
60
|
export function locateFeatureForQuestion(text, sessionRoot) {
|
|
137
61
|
const trees = allWorkTrees(sessionRoot);
|
|
138
62
|
const localNames = featureNamesIn(sessionRoot);
|
|
@@ -146,18 +70,10 @@ export function locateFeatureForQuestion(text, sessionRoot) {
|
|
|
146
70
|
if (found) return { name: found, root };
|
|
147
71
|
}
|
|
148
72
|
|
|
149
|
-
// The text names a specific feature none of the trees know — refuse rather
|
|
150
|
-
// than guess it meant the tree's one registered feature (see
|
|
151
|
-
// `unrecognizedSlugLike` above).
|
|
152
73
|
const allNames = trees.flatMap(featureNamesIn);
|
|
153
74
|
if (unrecognizedSlugLike(text, allNames)) return null;
|
|
154
75
|
|
|
155
|
-
//
|
|
156
|
-
// there is no other feature the answer could have been about. This is the
|
|
157
|
-
// single-feature project — the common case — and refusing there would break
|
|
158
|
-
// gate questions phrased without a name ("Gate 1 — Proposta aprovada?").
|
|
159
|
-
// With two or more features the fallback is exactly the guess that put a
|
|
160
|
-
// Gate 2 on `export-hub`, so it stops here.
|
|
76
|
+
// With exactly ONE feature in the tree there is nothing else the answer could be about.
|
|
161
77
|
if (localNames.length === 1) return { name: localNames[0], root: sessionRoot };
|
|
162
78
|
|
|
163
79
|
return null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Explicit gate names win over words a feature slug may also carry ("plan", "design", "review"). */
|
|
2
|
+
export function gateFromQuestion(question) {
|
|
3
|
+
if (typeof question !== 'string') return null;
|
|
4
|
+
if (/gate\s+uiux/i.test(question)) return 'uiux';
|
|
5
|
+
if (/gate\s+3\b/i.test(question)) return 'review';
|
|
6
|
+
if (/gate\s+(?:[12]\b|d[oe]\s+plano|plan\b)/i.test(question)) return 'plan';
|
|
7
|
+
const lower = question.toLowerCase();
|
|
8
|
+
if (lower.includes('plano') || lower.includes('plan')) return 'plan';
|
|
9
|
+
if (lower.includes('design')) return 'uiux';
|
|
10
|
+
if (lower.includes('review') || lower.includes('ship')) return 'review';
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* `info/exclude` do git.
|
|
4
|
-
*
|
|
5
|
-
* O arquivo lido é o do COMMON dir (`git rev-parse --git-common-dir`), nunca o
|
|
6
|
-
* `.git/worktrees/<id>/info/exclude`: o git não lê o deste para uma worktree
|
|
7
|
-
* vinculada, e o do common dir vale para toda árvore do repositório — inclusive
|
|
8
|
-
* a raiz primária. Cada chamador passa o seu marcador, e o bloco só ganha as
|
|
9
|
-
* linhas que ainda faltam.
|
|
10
|
-
*
|
|
11
|
-
* Idempotente e fail-open: um exclude que não pôde ser escrito custa um
|
|
12
|
-
* arquivo aparecendo como `??`, nunca um erro.
|
|
13
|
-
*/
|
|
1
|
+
// O exclude do COMMON dir: o git não lê `.git/worktrees/<id>/info/exclude` para
|
|
2
|
+
// uma worktree vinculada. Fail-open: falhar custa um `??`, nunca um erro.
|
|
14
3
|
|
|
15
4
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';
|
|
16
5
|
import { join } from 'path';
|
|
17
6
|
import { resolveGitCommonDir } from './git-paths.js';
|
|
18
7
|
|
|
19
|
-
/**
|
|
20
|
-
* @param {string} cwd - qualquer diretório dentro do repositório
|
|
21
|
-
* @param {string[]} patterns - linhas prontas (já ancoradas, ex. `/graphify-out`)
|
|
22
|
-
* @param {string} marker - comentário que identifica o bloco do chamador
|
|
23
|
-
*/
|
|
24
8
|
export function ensureCommonExclude(cwd, patterns, marker) {
|
|
25
9
|
if (!patterns.length) return;
|
|
26
10
|
try {
|
|
27
|
-
// Pelo disco
|
|
28
|
-
// console no win32 quando o chamador era um processo destacado.
|
|
11
|
+
// Pelo disco: um `git` gerado por um processo destacado abre console no win32.
|
|
29
12
|
const common = resolveGitCommonDir(cwd);
|
|
30
13
|
if (!common) return;
|
|
31
14
|
const infoDir = join(common, 'info');
|
|
@@ -1,47 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* `telemetry-log.js`'s claim heartbeat used to pay `execSync('git rev-parse
|
|
5
|
-
* --git-common-dir')` on every tool call to learn where `claims.json` lives —
|
|
6
|
-
* a ~34ms subprocess spawn, wasted on ~99% of calls because the heartbeat
|
|
7
|
-
* write itself is throttled (see `claim-registry.js`'s `shouldHeartbeat`).
|
|
8
|
-
* This module answers the SAME question — mirroring `git rev-parse
|
|
9
|
-
* --git-common-dir` — by reading the two on-disk shapes git itself uses,
|
|
10
|
-
* without spawning `git` at all:
|
|
11
|
-
*
|
|
12
|
-
* 1. PRIMARY checkout: `<root>/.git` is a DIRECTORY. That directory IS the
|
|
13
|
-
* common dir. (A directory `.git` is never a linked worktree's own —
|
|
14
|
-
* git always represents a worktree's `.git` as a FILE — so this case is
|
|
15
|
-
* unambiguous.)
|
|
16
|
-
* 2. LINKED WORKTREE (`git worktree add`): `<root>/.git` is a FILE
|
|
17
|
-
* containing `gitdir: <path>`, pointing at this worktree's PRIVATE
|
|
18
|
-
* gitdir (normally `<primary>/.git/worktrees/<name>`). Inside that
|
|
19
|
-
* private gitdir, a `commondir` file holds a path — relative to the
|
|
20
|
-
* private gitdir, or absolute — to the real common dir (normally
|
|
21
|
-
* `../..`, i.e. back up to `<primary>/.git`).
|
|
22
|
-
*
|
|
23
|
-
* Verified against a real `git worktree add` fixture: `commondir` held
|
|
24
|
-
* `../..`, `.git`'s `gitdir:` line held an absolute path, and the resolved
|
|
25
|
-
* result matched `git rev-parse --git-common-dir` exactly (see
|
|
26
|
-
* `test/hooks/git-paths.test.js`, which asserts that parity directly rather
|
|
27
|
-
* than against a hand-computed path).
|
|
28
|
-
*
|
|
29
|
-
* Fail-open: any missing file, unreadable path, or shape that doesn't match
|
|
30
|
-
* either case above resolves to `null`. Callers must treat `null` as "not
|
|
31
|
-
* resolvable right now" and skip whatever git-dependent step they were
|
|
32
|
-
* guarding — never throw.
|
|
33
|
-
*/
|
|
1
|
+
// Answers `git rev-parse` questions from the on-disk shapes git uses, without spawning git:
|
|
2
|
+
// a primary checkout's `.git` is a DIRECTORY, a linked worktree's is a FILE holding `gitdir:`.
|
|
3
|
+
// Every unreadable or unknown shape resolves to `null`, never a throw.
|
|
34
4
|
|
|
35
5
|
import { readFileSync, statSync } from 'fs';
|
|
36
6
|
import { dirname, isAbsolute, join, resolve } from 'path';
|
|
37
7
|
|
|
38
|
-
/**
|
|
39
|
-
* Find the nearest `.git` entry (file or directory) at or above `startDir`,
|
|
40
|
-
* climbing exactly like git itself does to find the repo root.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} startDir
|
|
43
|
-
* @returns {{ path: string, isDirectory: boolean }|null}
|
|
44
|
-
*/
|
|
45
8
|
function findDotGit(startDir) {
|
|
46
9
|
let dir = resolve(startDir);
|
|
47
10
|
for (;;) {
|
|
@@ -50,21 +13,13 @@ function findDotGit(startDir) {
|
|
|
50
13
|
const st = statSync(candidate);
|
|
51
14
|
return { path: candidate, isDirectory: st.isDirectory() };
|
|
52
15
|
} catch {
|
|
53
|
-
// not here — keep climbing
|
|
54
16
|
}
|
|
55
17
|
const parent = dirname(dir);
|
|
56
|
-
if (parent === dir) return null;
|
|
18
|
+
if (parent === dir) return null;
|
|
57
19
|
dir = parent;
|
|
58
20
|
}
|
|
59
21
|
}
|
|
60
22
|
|
|
61
|
-
/**
|
|
62
|
-
* A linked worktree's `.git` FILE holds `gitdir: <path>` (absolute or relative
|
|
63
|
-
* to the file) pointing at its private gitdir. `null` when the line is absent.
|
|
64
|
-
*
|
|
65
|
-
* @param {string} dotGitFile
|
|
66
|
-
* @returns {string|null}
|
|
67
|
-
*/
|
|
68
23
|
function privateGitDirOf(dotGitFile) {
|
|
69
24
|
const match = readFileSync(dotGitFile, 'utf8').match(/^gitdir:\s*(.+?)\s*$/m);
|
|
70
25
|
if (!match) return null;
|
|
@@ -73,15 +28,7 @@ function privateGitDirOf(dotGitFile) {
|
|
|
73
28
|
|
|
74
29
|
const MORPH_BRANCH = /^ref:\s*refs\/heads\/morph\/([A-Za-z0-9._-]+)\s*$/;
|
|
75
30
|
|
|
76
|
-
/**
|
|
77
|
-
* The feature a checkout is bound to by its branch `morph/<feature>`, read from
|
|
78
|
-
* `<root>/.git` exactly (no climbing) — the primary's `.git/HEAD` or, for a
|
|
79
|
-
* linked worktree, `<gitdir>/HEAD`. `null` for any other branch (`morph-task/*`
|
|
80
|
-
* included), a detached HEAD, or no `.git` at `root`.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} root
|
|
83
|
-
* @returns {string|null}
|
|
84
|
-
*/
|
|
31
|
+
/** Reads `<root>/.git` exactly, without climbing; `null` for any branch other than `morph/<feature>`. */
|
|
85
32
|
export function morphBranchFeature(root) {
|
|
86
33
|
try {
|
|
87
34
|
const dotGit = join(root, '.git');
|
|
@@ -94,22 +41,7 @@ export function morphBranchFeature(root) {
|
|
|
94
41
|
}
|
|
95
42
|
}
|
|
96
43
|
|
|
97
|
-
/**
|
|
98
|
-
* Is `startDir` inside a LINKED (secondary) worktree?
|
|
99
|
-
*
|
|
100
|
-
* Same on-disk fact `resolveGitCommonDir` reads: git represents a linked
|
|
101
|
-
* worktree's `.git` as a FILE holding a `gitdir:` pointer, and a primary
|
|
102
|
-
* checkout's `.git` as a DIRECTORY. The e2e isolation rule keys on this —
|
|
103
|
-
* a compose that cannot be isolated is a hard failure only where a second
|
|
104
|
-
* stack could actually collide with the primary's (see e2e-check.js
|
|
105
|
-
* `decideIsolation`).
|
|
106
|
-
*
|
|
107
|
-
* Fail-open: outside any repo, or on an unreadable path, answers `false` —
|
|
108
|
-
* the lenient direction (warn instead of fail).
|
|
109
|
-
*
|
|
110
|
-
* @param {string} startDir
|
|
111
|
-
* @returns {boolean}
|
|
112
|
-
*/
|
|
44
|
+
/** Outside any repo or on an unreadable path, answers `false`: the lenient direction for e2e isolation. */
|
|
113
45
|
export function isSecondaryWorktree(startDir) {
|
|
114
46
|
try {
|
|
115
47
|
const found = findDotGit(startDir);
|
|
@@ -119,14 +51,6 @@ export function isSecondaryWorktree(startDir) {
|
|
|
119
51
|
}
|
|
120
52
|
}
|
|
121
53
|
|
|
122
|
-
/**
|
|
123
|
-
* Top of the working tree that contains `startDir` — the directory holding the
|
|
124
|
-
* nearest `.git` (file or directory), which is what `git rev-parse
|
|
125
|
-
* --show-toplevel` answers, without the subprocess. `null` outside any repo.
|
|
126
|
-
*
|
|
127
|
-
* @param {string} startDir
|
|
128
|
-
* @returns {string|null}
|
|
129
|
-
*/
|
|
130
54
|
export function workTreeTop(startDir) {
|
|
131
55
|
try {
|
|
132
56
|
const found = findDotGit(startDir);
|
|
@@ -136,16 +60,7 @@ export function workTreeTop(startDir) {
|
|
|
136
60
|
}
|
|
137
61
|
}
|
|
138
62
|
|
|
139
|
-
/**
|
|
140
|
-
* Resolve the git COMMON dir for `startDir` — pure filesystem, no subprocess.
|
|
141
|
-
* Always returns an absolute path (unlike `git rev-parse --git-common-dir`,
|
|
142
|
-
* which returns a path relative to the CALLER's cwd when run from the
|
|
143
|
-
* primary checkout — callers of this function never need their own
|
|
144
|
-
* `resolve(cwd, ...)` step afterward).
|
|
145
|
-
*
|
|
146
|
-
* @param {string} startDir
|
|
147
|
-
* @returns {string|null}
|
|
148
|
-
*/
|
|
63
|
+
/** Always absolute, unlike `git rev-parse --git-common-dir`, which is relative to the caller's cwd in the primary. */
|
|
149
64
|
export function resolveGitCommonDir(startDir) {
|
|
150
65
|
try {
|
|
151
66
|
const found = findDotGit(startDir);
|
|
@@ -158,8 +73,6 @@ export function resolveGitCommonDir(startDir) {
|
|
|
158
73
|
const privateGitDir = privateGitDirOf(found.path);
|
|
159
74
|
if (!privateGitDir) return null;
|
|
160
75
|
|
|
161
|
-
// Inside the private gitdir, `commondir` points (relative to itself, or
|
|
162
|
-
// absolute) at the real common dir.
|
|
163
76
|
const commonRaw = readFileSync(join(privateGitDir, 'commondir'), 'utf8').trim();
|
|
164
77
|
if (!commonRaw) return null;
|
|
165
78
|
return isAbsolute(commonRaw) ? commonRaw : resolve(privateGitDir, commonRaw);
|
|
@@ -170,15 +83,7 @@ export function resolveGitCommonDir(startDir) {
|
|
|
170
83
|
|
|
171
84
|
const SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
172
85
|
|
|
173
|
-
/**
|
|
174
|
-
* The commit HEAD points at in the PRIMARY checkout `root`, read from disk —
|
|
175
|
-
* `git rev-parse HEAD` without the subprocess: a detached SHA, or the branch's
|
|
176
|
-
* loose ref, then `packed-refs`. `null` for every shape it does not read (a
|
|
177
|
-
* linked worktree, reftable, a branch with no commit yet): the caller asks git.
|
|
178
|
-
*
|
|
179
|
-
* @param {string} root
|
|
180
|
-
* @returns {string|null}
|
|
181
|
-
*/
|
|
86
|
+
/** `null` for every shape it does not read (linked worktree, reftable, unborn branch): the caller asks git. */
|
|
182
87
|
export function readPrimaryHead(root) {
|
|
183
88
|
try {
|
|
184
89
|
const gitDir = join(root, '.git');
|
|
@@ -191,7 +96,6 @@ export function readPrimaryHead(root) {
|
|
|
191
96
|
const loose = readFileSync(join(gitDir, ref), 'utf8').trim();
|
|
192
97
|
if (SHA.test(loose)) return loose;
|
|
193
98
|
} catch {
|
|
194
|
-
// not loose — packed
|
|
195
99
|
}
|
|
196
100
|
for (const line of readFileSync(join(gitDir, 'packed-refs'), 'utf8').split(/\r?\n/)) {
|
|
197
101
|
const [sha, name] = line.split(' ');
|