@polymorphism-tech/morph-spec 9.0.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,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(' ');
|
|
@@ -1,41 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Todo mundo que pergunta lê daqui: o refresh em background
|
|
5
|
-
* (graph-refresh.js, decide se roda), o `graph status|explain|affected` e o
|
|
6
|
-
* `doctor` (via src/lib/graph/graph-loader.js `getFreshness`) e o
|
|
7
|
-
* `graph build|refresh` (grava o resultado). Mora em hooks/shared porque hook
|
|
8
|
-
* não importa `src/`. Uma segunda cópia da regra é como o status dizia
|
|
9
|
-
* "fresco" enquanto o refresh achava "velho", ou o contrário.
|
|
10
|
-
*
|
|
11
|
-
* Duas fontes, nesta ordem:
|
|
12
|
-
* 1. o carimbo `built_at_commit` do graph.json contra o HEAD;
|
|
13
|
-
* 2. o REGISTRO da última rodada, por árvore, em
|
|
14
|
-
* `<git-common-dir>/morph-spec/graph-refresh-state.json`:
|
|
15
|
-
* `{ [árvore]: { head, exit, ignoreHash?, at, unavailable?, seeded? } }`.
|
|
16
|
-
*
|
|
17
|
-
* O registro existe porque o `graphify update` do 0.9.50 NÃO recarimba quando
|
|
18
|
-
* a topologia não muda — "No code-graph topology changes detected; outputs
|
|
19
|
-
* left untouched" (watch.py): commit só de `.morph/`, corpo de método editado,
|
|
20
|
-
* commit num arquivo excluído. O carimbo fica no commit antigo e, sem o
|
|
21
|
-
* registro, o grafo parecia velho para sempre e cada disparo rodava o graphify
|
|
22
|
-
* de novo (~80 s no MORPH_GHLBrain). Rodada com exit 0 no HEAD H vale como
|
|
23
|
-
* grafo em dia em H: o 0.9.50 sai 1 quando recusa escrever (`_check_shrink`,
|
|
24
|
-
* grafo ilegível), e "untouched" sai 0.
|
|
25
|
-
*
|
|
26
|
-
* As regras do `.graphifyignore` entram por CONTEÚDO, nunca pelo relógio: o
|
|
27
|
-
* registro guarda o hash das regras sob as quais o grafo EM DISCO foi
|
|
28
|
-
* extraído (uma rodada que falhou não o muda), e um hash diferente do das
|
|
29
|
-
* regras que valem hoje é grafo velho — numa worktree sem arquivo próprio,
|
|
30
|
-
* as da raiz, que são as que o `graph refresh` usa ali. Comentário, linha
|
|
31
|
-
* em branco, BOM e CRLF não contam — trocar o cabeçalho não pede refresh.
|
|
32
|
-
* Grafo sem registro das regras (anterior a este estado) com regras valendo é
|
|
33
|
-
* `rules-unknown`: um refresh grava o registro e encerra.
|
|
34
|
-
*
|
|
35
|
-
* Fail-open: nada aqui lança.
|
|
36
|
-
*
|
|
37
|
-
* @module graph-freshness
|
|
38
|
-
*/
|
|
1
|
+
// Regra ÚNICA de "grafo em dia". O registro da rodada existe porque o `graphify
|
|
2
|
+
// update` não recarimba `built_at_commit` quando a topologia não muda; as regras
|
|
3
|
+
// do ignore entram por CONTEÚDO, nunca pelo relógio. Fail-open: nada aqui lança.
|
|
39
4
|
|
|
40
5
|
import { createHash } from 'crypto';
|
|
41
6
|
import { execFileSync } from 'child_process';
|
|
@@ -50,19 +15,12 @@ export const GRAPH_DIR = 'graphify-out';
|
|
|
50
15
|
export const GRAPHIFY_IGNORE = '.graphifyignore';
|
|
51
16
|
const STATE_FILE = 'graph-refresh-state.json';
|
|
52
17
|
|
|
53
|
-
/** @param {string} tree */
|
|
54
18
|
export function graphFile(tree) {
|
|
55
19
|
return join(tree, GRAPH_DIR, 'graph.json');
|
|
56
20
|
}
|
|
57
21
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* a leitura do fim do arquivo evita parsear 18 MB; a leitura inteira fica de
|
|
61
|
-
* reserva para qualquer outra forma.
|
|
62
|
-
*
|
|
63
|
-
* @param {string} tree
|
|
64
|
-
* @returns {string|null}
|
|
65
|
-
*/
|
|
22
|
+
// O graphify grava `built_at_commit` como a ÚLTIMA chave: ler o fim evita parsear
|
|
23
|
+
// o grafo inteiro, que fica de reserva para outra forma.
|
|
66
24
|
export function readBuiltAtCommit(tree) {
|
|
67
25
|
const file = graphFile(tree);
|
|
68
26
|
let fd;
|
|
@@ -86,10 +44,6 @@ export function readBuiltAtCommit(tree) {
|
|
|
86
44
|
}
|
|
87
45
|
}
|
|
88
46
|
|
|
89
|
-
/**
|
|
90
|
-
* @param {string} tree
|
|
91
|
-
* @returns {string|null}
|
|
92
|
-
*/
|
|
93
47
|
export function readHead(tree) {
|
|
94
48
|
try {
|
|
95
49
|
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
@@ -100,14 +54,7 @@ export function readHead(tree) {
|
|
|
100
54
|
}
|
|
101
55
|
}
|
|
102
56
|
|
|
103
|
-
|
|
104
|
-
* As REGRAS de um `.graphifyignore`, sem o que o graphify não lê: comentário,
|
|
105
|
-
* linha em branco, espaço no fim, BOM e CRLF. A ordem fica — no gitignore ela
|
|
106
|
-
* muda o resultado.
|
|
107
|
-
*
|
|
108
|
-
* @param {string} text
|
|
109
|
-
* @returns {string}
|
|
110
|
-
*/
|
|
57
|
+
// A ordem fica: no gitignore ela muda o resultado.
|
|
111
58
|
export function ignoreRules(text) {
|
|
112
59
|
return String(text ?? '')
|
|
113
60
|
.replace(/^\uFEFF/, '')
|
|
@@ -117,37 +64,22 @@ export function ignoreRules(text) {
|
|
|
117
64
|
.join('\n');
|
|
118
65
|
}
|
|
119
66
|
|
|
120
|
-
/**
|
|
121
|
-
* @param {string|null} text
|
|
122
|
-
* @returns {string|null} null quando não há arquivo
|
|
123
|
-
*/
|
|
124
67
|
export function rulesHash(text) {
|
|
125
68
|
if (text === null || text === undefined) return null;
|
|
126
69
|
return createHash('sha256').update(ignoreRules(text)).digest('hex').slice(0, 16);
|
|
127
70
|
}
|
|
128
71
|
|
|
129
|
-
/**
|
|
130
|
-
* A árvore principal do repositório — a única que o refresh em background
|
|
131
|
-
* toca. `null` fora de um repositório e quando o common dir não é o `.git` de
|
|
132
|
-
* uma árvore (repositório bare, `--separate-git-dir`): aí não há raiz.
|
|
133
|
-
*
|
|
134
|
-
* @param {string} dir - qualquer diretório do repositório, raiz ou worktree
|
|
135
|
-
* @returns {string|null}
|
|
136
|
-
*/
|
|
72
|
+
/** `null` também quando o common dir não é o `.git` de uma árvore (bare, `--separate-git-dir`). */
|
|
137
73
|
export function primaryTree(dir) {
|
|
138
74
|
const common = dir ? resolveGitCommonDir(dir) : null;
|
|
139
75
|
if (!common || basename(common) !== '.git') return null;
|
|
140
76
|
return dirname(common);
|
|
141
77
|
}
|
|
142
78
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* durante a rodada. Comentário não muda as regras nem o hash, e é por ele que a
|
|
146
|
-
* sobra de uma rodada cujo processo morreu no meio é reconhecida.
|
|
147
|
-
*/
|
|
79
|
+
// 1ª linha da cópia temporária: reconhece a sobra de uma rodada que morreu no
|
|
80
|
+
// meio sem mudar as regras nem o hash.
|
|
148
81
|
export const TEMP_IGNORE_MARKER = '# morph-spec: cópia temporária do graph refresh, apagada ao fim da rodada';
|
|
149
82
|
|
|
150
|
-
/** @param {string|null} text */
|
|
151
83
|
export function isTemporaryIgnore(text) {
|
|
152
84
|
return String(text ?? '').replace(/^/, '').split(/\r?\n/, 1)[0] === TEMP_IGNORE_MARKER;
|
|
153
85
|
}
|
|
@@ -160,14 +92,8 @@ function readText(path) {
|
|
|
160
92
|
}
|
|
161
93
|
}
|
|
162
94
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* numa worktree sem um (ou só com a sobra temporária), o da raiz, que é o que o
|
|
166
|
-
* `graph refresh` põe no lugar durante a rodada.
|
|
167
|
-
*
|
|
168
|
-
* @param {string} tree
|
|
169
|
-
* @returns {string|null} null quando nenhum arquivo vale
|
|
170
|
-
*/
|
|
95
|
+
// Numa worktree sem arquivo próprio valem as regras da raiz: é o que o
|
|
96
|
+
// `graph refresh` põe no lugar durante a rodada.
|
|
171
97
|
export function ignoreTextOf(tree) {
|
|
172
98
|
const own = readText(join(tree, GRAPHIFY_IGNORE));
|
|
173
99
|
if (own !== null && !isTemporaryIgnore(own)) return own;
|
|
@@ -177,37 +103,18 @@ export function ignoreTextOf(tree) {
|
|
|
177
103
|
return inherited !== null && !isTemporaryIgnore(inherited) ? inherited : null;
|
|
178
104
|
}
|
|
179
105
|
|
|
180
|
-
/**
|
|
181
|
-
* @param {string} tree
|
|
182
|
-
* @returns {string|null} null quando nenhum `.graphifyignore` vale para a árvore
|
|
183
|
-
*/
|
|
184
106
|
export function ignoreRulesHash(tree) {
|
|
185
107
|
return rulesHash(ignoreTextOf(tree));
|
|
186
108
|
}
|
|
187
109
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* `.graphifyignore` temporário: ela é só uma cópia das regras da raiz, e o
|
|
191
|
-
* `?? .graphifyignore` que deixa fazia o `finish` e o
|
|
192
|
-
* `worktree remove --if-clean` recusarem. Só lê — quem confere a árvore chama
|
|
193
|
-
* isto antes das recusas, e `dropLeftoverIgnore` só depois delas.
|
|
194
|
-
*
|
|
195
|
-
* @param {string} tree
|
|
196
|
-
* @param {string[]} lines
|
|
197
|
-
* @returns {string[]}
|
|
198
|
-
*/
|
|
110
|
+
// Só lê: quem confere a árvore chama isto antes das recusas, e
|
|
111
|
+
// `dropLeftoverIgnore` só depois delas.
|
|
199
112
|
export function withoutLeftoverIgnore(tree, lines) {
|
|
200
113
|
const leftover = `?? ${GRAPHIFY_IGNORE}`;
|
|
201
114
|
if (!lines.includes(leftover) || !isTemporaryIgnore(readText(join(tree, GRAPHIFY_IGNORE)))) return lines;
|
|
202
115
|
return lines.filter((line) => line !== leftover);
|
|
203
116
|
}
|
|
204
117
|
|
|
205
|
-
/**
|
|
206
|
-
* Apaga a sobra do `.graphifyignore` temporário, reconhecida pelo marcador.
|
|
207
|
-
*
|
|
208
|
-
* @param {string} tree
|
|
209
|
-
* @returns {boolean} apagou
|
|
210
|
-
*/
|
|
211
118
|
export function dropLeftoverIgnore(tree) {
|
|
212
119
|
const path = join(tree, GRAPHIFY_IGNORE);
|
|
213
120
|
if (!isTemporaryIgnore(readText(path))) return false;
|
|
@@ -219,21 +126,12 @@ export function dropLeftoverIgnore(tree) {
|
|
|
219
126
|
}
|
|
220
127
|
}
|
|
221
128
|
|
|
222
|
-
|
|
223
|
-
* Chave de uma árvore no registro. No win32 o caminho não distingue caixa, e
|
|
224
|
-
* `R:\x` e `r:\x` são a mesma árvore.
|
|
225
|
-
*
|
|
226
|
-
* @param {string} tree
|
|
227
|
-
*/
|
|
129
|
+
// No win32 o caminho não distingue caixa.
|
|
228
130
|
export function treeKey(tree) {
|
|
229
131
|
const path = resolve(tree);
|
|
230
132
|
return process.platform === 'win32' ? path.toLowerCase() : path;
|
|
231
133
|
}
|
|
232
134
|
|
|
233
|
-
/**
|
|
234
|
-
* @param {string} dir - qualquer diretório do repositório
|
|
235
|
-
* @returns {string|null}
|
|
236
|
-
*/
|
|
237
135
|
export function refreshStatePath(dir) {
|
|
238
136
|
const common = dir ? resolveGitCommonDir(dir) : null;
|
|
239
137
|
return common ? join(common, 'morph-spec', STATE_FILE) : null;
|
|
@@ -248,10 +146,7 @@ function readState(path) {
|
|
|
248
146
|
}
|
|
249
147
|
}
|
|
250
148
|
|
|
251
|
-
/**
|
|
252
|
-
* @param {string} tree
|
|
253
|
-
* @returns {{head: string|null, exit: number|null, ignoreHash?: string|null, at: string, unavailable?: boolean, seeded?: boolean}|null}
|
|
254
|
-
*/
|
|
149
|
+
/** @returns {{head: string|null, exit: number|null, ignoreHash?: string|null, at: string, unavailable?: boolean, seeded?: boolean}|null} */
|
|
255
150
|
export function readRefreshRecord(tree) {
|
|
256
151
|
const path = refreshStatePath(tree);
|
|
257
152
|
if (!path) return null;
|
|
@@ -263,15 +158,7 @@ function pause(ms) {
|
|
|
263
158
|
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch { /* sem espera */ }
|
|
264
159
|
}
|
|
265
160
|
|
|
266
|
-
|
|
267
|
-
* Grava o registro de `tree` (substitui o anterior). Por tmp + rename: quem lê
|
|
268
|
-
* no meio vê o arquivo velho ou o novo, nunca meio. As árvores que não existem
|
|
269
|
-
* mais saem na mesma escrita — uma worktree removida não deixa lixo.
|
|
270
|
-
*
|
|
271
|
-
* @param {string} tree
|
|
272
|
-
* @param {object} record
|
|
273
|
-
* @returns {object|null} o registro gravado, ou null (sem git, disco)
|
|
274
|
-
*/
|
|
161
|
+
// As árvores que não existem mais saem na mesma escrita.
|
|
275
162
|
export function writeRefreshRecord(tree, record) {
|
|
276
163
|
const path = refreshStatePath(tree);
|
|
277
164
|
if (!path) return null;
|
|
@@ -286,8 +173,7 @@ export function writeRefreshRecord(tree, record) {
|
|
|
286
173
|
}
|
|
287
174
|
state[key] = entry;
|
|
288
175
|
writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`);
|
|
289
|
-
// No win32 o rename sobre um arquivo aberto por outro leitor falha por um
|
|
290
|
-
// instante (EPERM/EBUSY); o registro vale umas tentativas.
|
|
176
|
+
// No win32 o rename sobre um arquivo aberto por outro leitor falha por um instante.
|
|
291
177
|
for (let attempt = 0; ; attempt++) {
|
|
292
178
|
try {
|
|
293
179
|
renameSync(tmp, path);
|
|
@@ -303,15 +189,8 @@ export function writeRefreshRecord(tree, record) {
|
|
|
303
189
|
}
|
|
304
190
|
}
|
|
305
191
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
* uma rodada que falhou não mexeu no grafo, e o hash descreve o grafo em
|
|
309
|
-
* disco, não a tentativa.
|
|
310
|
-
*
|
|
311
|
-
* @param {string} tree
|
|
312
|
-
* @param {{head: string|null, exit: number|null, ignoreHash: string|null, unavailable?: boolean}} run
|
|
313
|
-
* `head` e `ignoreHash` lidos ANTES da rodada — é o que o graphify leu
|
|
314
|
-
*/
|
|
192
|
+
// O hash das regras só muda com exit 0: descreve o grafo em disco, não a
|
|
193
|
+
// tentativa. `head` e `ignoreHash` são lidos ANTES da rodada.
|
|
315
194
|
export function recordRefreshRun(tree, { head, exit, ignoreHash, unavailable = false }) {
|
|
316
195
|
const previous = readRefreshRecord(tree);
|
|
317
196
|
let rules = {};
|
|
@@ -325,23 +204,7 @@ export function recordRefreshRun(tree, { head, exit, ignoreHash, unavailable = f
|
|
|
325
204
|
});
|
|
326
205
|
}
|
|
327
206
|
|
|
328
|
-
/**
|
|
329
|
-
* O frescor do grafo de `tree`.
|
|
330
|
-
*
|
|
331
|
-
* missing sem graph.json
|
|
332
|
-
* unknown sem git, ou sem carimbo nem registro que diga o commit
|
|
333
|
-
* stale atrás do HEAD (`reason` ausente), ou em dia no commit mas com as
|
|
334
|
-
* regras do ignore mudadas (`ignore-changed`) ou desconhecidas
|
|
335
|
-
* (`rules-unknown`)
|
|
336
|
-
* fresh carimbo = HEAD, ou rodada com exit 0 neste HEAD — e as regras
|
|
337
|
-
* que valem para a árvore são as do grafo (`ignoreTextOf`; nenhum
|
|
338
|
-
* arquivo valendo não afirma nada)
|
|
339
|
-
*
|
|
340
|
-
* @param {string} tree - o diretório que contém `graphify-out/`
|
|
341
|
-
* @param {{graph?: object, head?: string|null}} [known] o que o chamador já
|
|
342
|
-
* leu: o grafo parseado (o carimbo sai dele) e o HEAD
|
|
343
|
-
* @returns {{state: 'missing'|'unknown'|'stale'|'fresh', built: string|null, head: string|null, reason?: 'ignore-changed'|'rules-unknown', record: object|null}}
|
|
344
|
-
*/
|
|
207
|
+
/** @returns {{state: 'missing'|'unknown'|'stale'|'fresh', built: string|null, head: string|null, reason?: 'ignore-changed'|'rules-unknown', record: object|null}} */
|
|
345
208
|
export function graphFreshness(tree, { graph, head } = {}) {
|
|
346
209
|
if (!graph && !existsSync(graphFile(tree))) {
|
|
347
210
|
return { state: 'missing', built: null, head: null, record: null };
|
|
@@ -363,15 +226,8 @@ export function graphFreshness(tree, { graph, head } = {}) {
|
|
|
363
226
|
return { state: 'fresh', ...base };
|
|
364
227
|
}
|
|
365
228
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
* FALHOU neste mesmo HEAD — essa só se repete quando o HEAD andar (ou por
|
|
369
|
-
* `morph-spec graph refresh`, que é explícito). "graphify ausente" não conta
|
|
370
|
-
* como falha: instalado, ele roda no próximo disparo.
|
|
371
|
-
*
|
|
372
|
-
* @param {{state: string, head: string|null, record: object|null}} freshness
|
|
373
|
-
* @returns {boolean}
|
|
374
|
-
*/
|
|
229
|
+
// Uma rodada que FALHOU neste HEAD só se repete quando o HEAD andar;
|
|
230
|
+
// "graphify ausente" não conta como falha.
|
|
375
231
|
export function refreshPending(freshness) {
|
|
376
232
|
if (freshness?.state !== 'stale') return false;
|
|
377
233
|
const r = freshness.record;
|