@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, relative } from 'path';
|
|
3
|
+
import { getProjectTags } from '../stack-filter.js';
|
|
4
|
+
import { PACKAGE_LOCATIONS } from '../detectors/index.js';
|
|
5
|
+
import { isTestPath } from '../graph/sanitize.js';
|
|
6
|
+
import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
|
|
7
|
+
|
|
8
|
+
export const SEVERITIES = ['Bloqueante', 'Alto', 'Médio', 'Baixo'];
|
|
9
|
+
export const MAX_FILES = 20000;
|
|
10
|
+
|
|
11
|
+
const SKIP_DIRS = new Set([
|
|
12
|
+
'node_modules', '.git', 'bin', 'obj', 'dist', 'build', 'out', 'coverage', '.next', '.morph', '.claude', '.vs', '.idea',
|
|
13
|
+
...WORKTREE_DIR_NAMES,
|
|
14
|
+
]);
|
|
15
|
+
const SOURCE_EXT = /\.(cs|csproj|sln|props|ts|tsx)$/i;
|
|
16
|
+
const DOTNET_EXPECTED_MAJOR = 10;
|
|
17
|
+
const WHERE_LIMIT = 5;
|
|
18
|
+
|
|
19
|
+
function walk(root) {
|
|
20
|
+
const files = [];
|
|
21
|
+
const dirs = new Set();
|
|
22
|
+
let unreadable = 0;
|
|
23
|
+
let truncated = false;
|
|
24
|
+
const stack = [root];
|
|
25
|
+
while (stack.length > 0) {
|
|
26
|
+
const dir = stack.pop();
|
|
27
|
+
let entries;
|
|
28
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { unreadable++; continue; }
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
const full = join(dir, e.name);
|
|
31
|
+
if (e.isDirectory()) {
|
|
32
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
33
|
+
dirs.add(relative(root, full).replace(/\\/g, '/'));
|
|
34
|
+
stack.push(full);
|
|
35
|
+
} else if (SOURCE_EXT.test(e.name) || e.name === 'package.json') {
|
|
36
|
+
if (files.length >= MAX_FILES) { truncated = true; continue; }
|
|
37
|
+
files.push(relative(root, full).replace(/\\/g, '/'));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { files, dirs, unreadable, truncated };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function reader(root) {
|
|
45
|
+
const failed = new Set();
|
|
46
|
+
const read = (file) => {
|
|
47
|
+
try { return readFileSync(join(root, file), 'utf8'); } catch { failed.add(file); return null; }
|
|
48
|
+
};
|
|
49
|
+
return { read, failed };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const majorOf = (version) => {
|
|
53
|
+
const m = /(\d+)/.exec(String(version ?? ''));
|
|
54
|
+
return m ? Number(m[1]) : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function dotnetPackages(files, read) {
|
|
58
|
+
const packages = new Map();
|
|
59
|
+
for (const f of files.filter((x) => /\.(csproj|props)$/i.test(x))) {
|
|
60
|
+
const text = read(f);
|
|
61
|
+
if (!text) continue;
|
|
62
|
+
for (const m of text.matchAll(/<Package(?:Reference|Version)\s+Include="([^"]+)"(?:\s+Version="([^"]*)")?/g)) {
|
|
63
|
+
if (!packages.has(m[1]) || m[2]) packages.set(m[1], m[2] ?? packages.get(m[1]) ?? null);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return packages;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function grepFiles(files, read, re) {
|
|
70
|
+
const hits = [];
|
|
71
|
+
for (const f of files) {
|
|
72
|
+
const text = read(f);
|
|
73
|
+
if (text && re.test(text)) hits.push(f);
|
|
74
|
+
}
|
|
75
|
+
return hits;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function auditDotnet(files, read) {
|
|
79
|
+
const rows = [];
|
|
80
|
+
const deviations = [];
|
|
81
|
+
const add = (severity, rule, where, migration) => deviations.push({ severity, stack: 'dotnet', rule, where, migration });
|
|
82
|
+
const projects = files.filter((f) => f.endsWith('.csproj'));
|
|
83
|
+
const source = files.filter((f) => f.endsWith('.cs') && !isTestPath(f));
|
|
84
|
+
|
|
85
|
+
const frameworks = new Map();
|
|
86
|
+
for (const p of projects) {
|
|
87
|
+
const m = /<TargetFrameworks?>([^<]+)</.exec(read(p) ?? '');
|
|
88
|
+
if (m) for (const tf of m[1].split(';').map((s) => s.trim()).filter(Boolean)) frameworks.set(tf, [...(frameworks.get(tf) ?? []), p]);
|
|
89
|
+
}
|
|
90
|
+
const tfs = [...frameworks.keys()];
|
|
91
|
+
const old = tfs.filter((tf) => /^net\d/.test(tf) && majorOf(tf) < DOTNET_EXPECTED_MAJOR);
|
|
92
|
+
rows.push({ layer: 'Runtime', detected: tfs.join(', ') || 'nenhum TargetFramework', expected: `net${DOTNET_EXPECTED_MAJOR}.0`, ok: tfs.length > 0 && old.length === 0 });
|
|
93
|
+
for (const tf of old) add('Bloqueante', `Runtime diferente (${tf})`, frameworks.get(tf), `subir o TargetFramework para net${DOTNET_EXPECTED_MAJOR}.0`);
|
|
94
|
+
|
|
95
|
+
const packages = dotnetPackages(files, read);
|
|
96
|
+
const has = (re) => [...packages.keys()].filter((p) => re.test(p));
|
|
97
|
+
const ef = has(/^Microsoft\.EntityFrameworkCore/);
|
|
98
|
+
const npgsql = has(/Npgsql/);
|
|
99
|
+
const otherDb = has(/^(Microsoft\.EntityFrameworkCore\.SqlServer|Pomelo\.EntityFrameworkCore\.MySql|MySql\.|MySqlConnector|Microsoft\.Data\.SqlClient|System\.Data\.SqlClient)/);
|
|
100
|
+
rows.push({ layer: 'ORM', detected: ef.length > 0 ? 'EF Core' : (packages.has('Dapper') ? 'Dapper' : 'ausente'), expected: 'EF Core', ok: ef.length > 0 });
|
|
101
|
+
rows.push({ layer: 'Banco', detected: npgsql.length > 0 ? 'Postgres (Npgsql)' : (otherDb.join(', ') || 'ausente'), expected: 'Neon (Postgres)', ok: npgsql.length > 0 && otherDb.length === 0 });
|
|
102
|
+
rows.push({ layer: 'IA', detected: has(/^Microsoft\.(Agents\.AI|Extensions\.AI)/).join(', ') || 'ausente', expected: 'MAF quando houver agentes', ok: true });
|
|
103
|
+
rows.push({ layer: 'Jobs', detected: has(/^Hangfire/).length > 0 ? 'Hangfire' : 'ausente', expected: 'Hangfire só com razão real', ok: true });
|
|
104
|
+
if (packages.has('Dapper') && ef.length === 0) add('Bloqueante', 'ORM diferente (Dapper puro sem EF Core)', projects, 'migrar o acesso a dados para EF Core');
|
|
105
|
+
if (otherDb.length > 0) add('Bloqueante', `Banco diferente (${otherDb.join(', ')})`, projects, 'migrar para Neon (Postgres, Npgsql)');
|
|
106
|
+
for (const p of has(/^Microsoft\.(EntityFrameworkCore|AspNetCore|Extensions)(?!\.AI)/)) {
|
|
107
|
+
const major = majorOf(packages.get(p));
|
|
108
|
+
if (major !== null && major < DOTNET_EXPECTED_MAJOR) add('Baixo', `Pacote desatualizado mas compatível (${p} ${packages.get(p)})`, [], `subir para ${DOTNET_EXPECTED_MAJOR}.x`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const newGuid = grepFiles(source, read, /Guid\.NewGuid\(\)/);
|
|
112
|
+
if (newGuid.length > 0) add('Alto', '`Guid.NewGuid()`', newGuid, 'trocar por `Guid.CreateVersion7()`');
|
|
113
|
+
const repository = grepFiles(source, read, /interface\s+(IRepository\s*<|IUnitOfWork\b)/);
|
|
114
|
+
if (repository.length > 0) {
|
|
115
|
+
const di = grepFiles(source, read, /Add(Scoped|Transient|Singleton)\s*(<\s*(IRepository|IUnitOfWork)|\(\s*typeof\s*\(\s*(IRepository|IUnitOfWork))/);
|
|
116
|
+
add('Alto', 'Repository genérico (`IRepository<T>`/`IUnitOfWork`)', [...repository, ...di],
|
|
117
|
+
'código morto: deletar a pasta `Repository/` e as 2 linhas de DI; confirme os consumidores com `morph-spec graph affected IRepository --uses-only`');
|
|
118
|
+
}
|
|
119
|
+
const serviceLayer = source.filter((f) => {
|
|
120
|
+
const text = read(f);
|
|
121
|
+
return text && /\bclass\s+\w+(Service|Manager)\b/.test(text) && /DbContext/.test(text);
|
|
122
|
+
});
|
|
123
|
+
if (serviceLayer.length > 0) add('Alto', 'Camada de Service/Manager entre handler e dados', serviceLayer, 'o handler fala com o `ApplicationDbContext` direto');
|
|
124
|
+
const rawSql = grepFiles(source, read, /\b(FromSqlRaw|ExecuteSqlRaw|SqlQueryRaw|ExecuteQuery)\s*\(/);
|
|
125
|
+
if (rawSql.length > 0) add('Alto', 'SQL cru', rawSql, 'LINQ, ou store dedicado com justificativa em ADR');
|
|
126
|
+
const notSealed = source.filter((f) => {
|
|
127
|
+
const text = read(f) ?? '';
|
|
128
|
+
return [...text.matchAll(/^\s*(?:public|internal)\s+((?:\w+\s+)*)class\s+\w+/gm)]
|
|
129
|
+
.some((m) => !/\b(sealed|abstract|static|partial)\b/.test(m[1]));
|
|
130
|
+
});
|
|
131
|
+
if (notSealed.length > 0) add('Médio', 'Classes não-sealed', notSealed, '`sealed` por padrão');
|
|
132
|
+
const controllers = grepFiles(source, read, /:\s*Controller(Base)?\b/);
|
|
133
|
+
rows.push({ layer: 'Estrutura', detected: controllers.length > 0 ? `${controllers.length} controller(s) MVC` : 'sem controllers', expected: 'VSA (endpoint por slice)', ok: controllers.length === 0 });
|
|
134
|
+
return { rows, deviations };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const UI_LIBRARIES = ['@mui/material', 'antd', '@chakra-ui/react', 'bootstrap', 'react-bootstrap', '@mantine/core'];
|
|
138
|
+
|
|
139
|
+
function auditNextjs(root, read) {
|
|
140
|
+
const rows = [];
|
|
141
|
+
const deviations = [];
|
|
142
|
+
const add = (severity, rule, where, migration) => deviations.push({ severity, stack: 'nextjs', rule, where, migration });
|
|
143
|
+
const packages = PACKAGE_LOCATIONS
|
|
144
|
+
.map((loc) => ({ loc, file: loc === '.' ? 'package.json' : `${loc}/package.json` }))
|
|
145
|
+
.filter(({ file }) => existsSync(join(root, file)))
|
|
146
|
+
.map((p) => {
|
|
147
|
+
const text = read(p.file);
|
|
148
|
+
let deps = null;
|
|
149
|
+
try { const pkg = JSON.parse(text); deps = { ...pkg.dependencies, ...pkg.devDependencies }; } catch { deps = null; }
|
|
150
|
+
return { ...p, deps };
|
|
151
|
+
})
|
|
152
|
+
.filter((p) => p.deps);
|
|
153
|
+
const next = packages.find((p) => p.deps.next);
|
|
154
|
+
if (!next) {
|
|
155
|
+
const other = packages.find((p) => p.deps.react || p.deps.vue);
|
|
156
|
+
rows.push({ layer: 'Frontend', detected: other ? (other.deps.vue ? 'Vue' : 'React sem Next.js') : 'sem frontend', expected: 'Next.js App Router', ok: !other });
|
|
157
|
+
if (other) add('Bloqueante', 'Frontend sem Next.js', [other.file], 'migrar para Next.js App Router');
|
|
158
|
+
return { rows, deviations };
|
|
159
|
+
}
|
|
160
|
+
const base = next.loc === '.' ? '' : `${next.loc}/`;
|
|
161
|
+
const isDir = (p) => { try { return statSync(join(root, p)).isDirectory(); } catch { return false; } };
|
|
162
|
+
const appRouter = isDir(`${base}app`) || isDir(`${base}src/app`);
|
|
163
|
+
const pagesRouter = isDir(`${base}pages`) || isDir(`${base}src/pages`);
|
|
164
|
+
rows.push({ layer: 'Frontend', detected: `Next.js ${next.deps.next}`, expected: 'Next.js App Router', ok: true });
|
|
165
|
+
rows.push({ layer: 'Roteamento', detected: appRouter ? 'App Router' : (pagesRouter ? 'Pages Router' : 'não detectado'), expected: 'App Router', ok: appRouter || !pagesRouter });
|
|
166
|
+
if (pagesRouter && !appRouter) add('Médio', 'Pages Router em vez de App Router', [next.file], 'migrar as rotas para `app/`');
|
|
167
|
+
rows.push({ layer: 'Estilo', detected: next.deps.tailwindcss ? 'Tailwind' : 'ausente', expected: 'Tailwind', ok: Boolean(next.deps.tailwindcss) });
|
|
168
|
+
rows.push({ layer: 'Motion', detected: next.deps['framer-motion'] || next.deps.motion ? 'Framer Motion' : 'ausente', expected: 'Framer Motion', ok: true });
|
|
169
|
+
const shadcn = existsSync(join(root, base, 'components.json'));
|
|
170
|
+
const otherUi = UI_LIBRARIES.filter((lib) => next.deps[lib]);
|
|
171
|
+
rows.push({ layer: 'UI', detected: otherUi.join(', ') || (shadcn ? 'shadcn' : 'ausente'), expected: 'shadcn', ok: otherUi.length === 0 });
|
|
172
|
+
if (otherUi.length > 0) add('Médio', `Biblioteca de UI diferente (${otherUi.join(', ')})`, [next.file], 'shadcn sobre Tailwind');
|
|
173
|
+
return { rows, deviations };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const AUDITS = { dotnet: auditDotnet, nextjs: auditNextjs };
|
|
177
|
+
|
|
178
|
+
/** Only the stacks in `config.project.stack` are judged; none configured means no rule runs. */
|
|
179
|
+
export function scanStack(root, config) {
|
|
180
|
+
const stack = typeof config?.project?.stack === 'string' ? config.project.stack : null;
|
|
181
|
+
const tags = getProjectTags(config);
|
|
182
|
+
const { files, unreadable, truncated } = walk(root);
|
|
183
|
+
const { read, failed } = reader(root);
|
|
184
|
+
const hasDotnet = files.some((f) => /\.(csproj|sln|cs)$/i.test(f));
|
|
185
|
+
const hasNode = files.some((f) => f.endsWith('package.json') || /\.(ts|tsx)$/i.test(f));
|
|
186
|
+
const greenfield = !hasDotnet && !hasNode;
|
|
187
|
+
|
|
188
|
+
const rows = [];
|
|
189
|
+
const deviations = [];
|
|
190
|
+
const notes = [];
|
|
191
|
+
if (!greenfield) {
|
|
192
|
+
for (const tag of tags) {
|
|
193
|
+
const audit = AUDITS[tag];
|
|
194
|
+
if (!audit) continue;
|
|
195
|
+
const result = tag === 'nextjs' ? audit(root, read) : audit(files, read);
|
|
196
|
+
rows.push(...result.rows);
|
|
197
|
+
deviations.push(...result.deviations);
|
|
198
|
+
}
|
|
199
|
+
if (hasDotnet && !tags.includes('dotnet')) notes.push('Há código .NET, mas `dotnet` não está em `project.stack` — não auditado.');
|
|
200
|
+
if (hasNode && !tags.includes('nextjs') && files.some((f) => f.endsWith('package.json'))) notes.push('Há `package.json`, mas `nextjs` não está em `project.stack` — não auditado.');
|
|
201
|
+
}
|
|
202
|
+
const unjudged = tags.filter((t) => !AUDITS[t]);
|
|
203
|
+
if (unjudged.length > 0) notes.push(`Sem regras de varredura para: ${unjudged.join(', ')}.`);
|
|
204
|
+
if (truncated) notes.push(`Varredura parou em ${MAX_FILES} arquivos.`);
|
|
205
|
+
if (unreadable + failed.size > 0) notes.push(`${unreadable + failed.size} arquivo(s) ou pasta(s) ilegível(is) ignorado(s).`);
|
|
206
|
+
|
|
207
|
+
deviations.sort((a, b) => SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity));
|
|
208
|
+
const audited = tags.some((t) => AUDITS[t]);
|
|
209
|
+
return { stack, tags, greenfield, audited, rows, deviations, notes, filesScanned: files.length };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const cell = (s) => String(s).replace(/\|/g, '\\|');
|
|
213
|
+
|
|
214
|
+
function whereText(where) {
|
|
215
|
+
if (!where || where.length === 0) return '';
|
|
216
|
+
const unique = [...new Set(where)];
|
|
217
|
+
const shown = unique.slice(0, WHERE_LIMIT).map((w) => `\`${w}\``).join(', ');
|
|
218
|
+
return ` — onde: ${shown}${unique.length > WHERE_LIMIT ? ` (+${unique.length - WHERE_LIMIT})` : ''} (${unique.length} arquivo(s))`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function notAuditedReason(result) {
|
|
222
|
+
return result.stack
|
|
223
|
+
? `não auditado: nenhuma regra de varredura para \`${result.stack}\``
|
|
224
|
+
: 'não auditado: project.stack ausente em .morph/config/config.json';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function renderStackScan(result, title) {
|
|
228
|
+
const out = [`# Stack Scan — ${title}`, ''];
|
|
229
|
+
out.push(`**Stack configurado:** ${result.stack ? `\`${result.stack}\`` : 'ausente em `project.stack` — nenhuma regra aplicada'}`);
|
|
230
|
+
const blocking = result.deviations.filter((d) => d.severity === 'Bloqueante' || d.severity === 'Alto');
|
|
231
|
+
const status = result.greenfield ? 'GREENFIELD (sem código fonte)'
|
|
232
|
+
: !result.audited ? notAuditedReason(result)
|
|
233
|
+
: result.deviations.length === 0 ? 'Em compliance' : `Desvios detectados (${result.deviations.length})`;
|
|
234
|
+
out.push(`**Resultado:** ${status}`, '');
|
|
235
|
+
|
|
236
|
+
if (result.rows.length > 0) {
|
|
237
|
+
out.push('## Stack detectado', '', '| Camada | Detectado | Esperado | Status |', '|---|---|---|---|');
|
|
238
|
+
for (const r of result.rows) out.push(`| ${cell(r.layer)} | ${cell(r.detected)} | ${cell(r.expected)} | ${r.ok ? '✓' : '✗'} |`);
|
|
239
|
+
out.push('');
|
|
240
|
+
}
|
|
241
|
+
if (result.deviations.length > 0) {
|
|
242
|
+
out.push('## Desvios (por severidade)', '');
|
|
243
|
+
for (const severity of SEVERITIES) {
|
|
244
|
+
const list = result.deviations.filter((d) => d.severity === severity);
|
|
245
|
+
if (list.length === 0) continue;
|
|
246
|
+
out.push(`### ${severity}`, '');
|
|
247
|
+
for (const d of list) out.push(`- ${d.rule}${whereText(d.where)} — migração: ${d.migration}`);
|
|
248
|
+
out.push('');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
out.push('## Notas', '');
|
|
252
|
+
out.push('- Contagens por varredura de texto (grep): superestimam consumidores, porque não separam herança de uso. Para um símbolo, `morph-spec graph affected <símbolo> --uses-only`.');
|
|
253
|
+
for (const n of result.notes) out.push(`- ${n}`);
|
|
254
|
+
out.push('', '## Recomendação', '');
|
|
255
|
+
if (result.greenfield) out.push('Compliance trivial: aplicar o stack padrão de `MORPH.md`.');
|
|
256
|
+
else if (!result.audited) out.push('Nenhuma regra rodou, então nada aqui atesta compliance: declare `project.stack` e rode o stack-scan de novo.');
|
|
257
|
+
else if (blocking.length > 0) out.push('Há desvio Bloqueante ou Alto: entra na árvore de decisões como "a migração entra nesta feature ou vira feature separada?".');
|
|
258
|
+
else out.push('Nenhum desvio Bloqueante ou Alto: prossiga com o plano.');
|
|
259
|
+
return `${out.join('\n')}\n`;
|
|
260
|
+
}
|
package/src/lib/stack-filter.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parse `stacks:` from YAML frontmatter content.
|
|
3
|
-
* Returns ['*'] if no stacks field found (backwards compatible — always install).
|
|
4
|
-
*
|
|
5
|
-
* Supports:
|
|
6
|
-
* stacks:\n - dotnet\n - blazor (YAML list)
|
|
7
|
-
* stacks: [dotnet, blazor] (inline array)
|
|
8
|
-
*
|
|
9
|
-
* @param {string} content - Raw file content with YAML frontmatter
|
|
10
|
-
* @returns {string[]}
|
|
11
|
-
*/
|
|
1
|
+
/** `['*']` without a `stacks:` field (always install); accepts a YAML list or an inline array. */
|
|
12
2
|
export function parseStacks(content) {
|
|
13
3
|
const normalized = content.replace(/\r\n/g, '\n');
|
|
14
4
|
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -16,9 +6,7 @@ export function parseStacks(content) {
|
|
|
16
6
|
|
|
17
7
|
const fm = fmMatch[1];
|
|
18
8
|
|
|
19
|
-
//
|
|
20
|
-
// list branch below; `["umbraco"]` sem isso parseava como a string literal
|
|
21
|
-
// `"umbraco"` (com aspas) e nunca casava tag nenhuma.
|
|
9
|
+
// Quotes stripped like the list branch: `["umbraco"]` would otherwise never match a tag.
|
|
22
10
|
const inlineMatch = fm.match(/^stacks:\s*\[([^\]]*)\]/m);
|
|
23
11
|
if (inlineMatch) {
|
|
24
12
|
const items = inlineMatch[1]
|
|
@@ -28,7 +16,6 @@ export function parseStacks(content) {
|
|
|
28
16
|
return items.length > 0 ? items : ['*'];
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
// Try YAML list format: stacks:\n - dotnet\n - blazor
|
|
32
19
|
const listMatch = fm.match(/^stacks:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
33
20
|
if (listMatch) {
|
|
34
21
|
const items = listMatch[1]
|
|
@@ -41,12 +28,7 @@ export function parseStacks(content) {
|
|
|
41
28
|
return ['*'];
|
|
42
29
|
}
|
|
43
30
|
|
|
44
|
-
/**
|
|
45
|
-
* Extract project tags from config object.
|
|
46
|
-
* Combines stack tags (from `project.stack` split by '-' or '+')
|
|
47
|
-
* with integration tags (from `project.integrations` array).
|
|
48
|
-
* Deduplicates the result.
|
|
49
|
-
*/
|
|
31
|
+
/** Stack tags (`project.stack` split by '-' or '+') plus `project.integrations`, deduplicated. */
|
|
50
32
|
export function getProjectTags(config) {
|
|
51
33
|
const stack = config?.project?.stack;
|
|
52
34
|
const stackTags = (stack && typeof stack === 'string')
|
|
@@ -58,12 +40,7 @@ export function getProjectTags(config) {
|
|
|
58
40
|
return [...new Set([...stackTags, ...integrations])];
|
|
59
41
|
}
|
|
60
42
|
|
|
61
|
-
/**
|
|
62
|
-
* Determine if an asset should be installed for the given project tags.
|
|
63
|
-
* - stacks includes '*' → always install
|
|
64
|
-
* - stacks is empty → treat as '*' (backwards compatible)
|
|
65
|
-
* - Otherwise → install if intersection with projectTags is non-empty
|
|
66
|
-
*/
|
|
43
|
+
/** Empty or '*' stacks always install; otherwise any intersection with the project tags. */
|
|
67
44
|
export function shouldInstall(assetStacks, projectTags) {
|
|
68
45
|
if (assetStacks.length === 0 || assetStacks.includes('*')) return true;
|
|
69
46
|
return assetStacks.some(s => projectTags.includes(s));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
|
|
4
|
+
export const STANDARDS_FILE = 'STANDARDS.json';
|
|
5
|
+
export const STANDARDS_META_FILE = 'STANDARDS.meta.json';
|
|
6
|
+
|
|
7
|
+
/** Per-standard fields that live in the sidecar, keyed by id: no runtime reader needs them to pick a standard. */
|
|
8
|
+
export const META_FIELDS = ['aliases', 'verifiedAgainst', 'provedBy'];
|
|
9
|
+
|
|
10
|
+
export function metaPathFor(standardsJsonPath) {
|
|
11
|
+
return join(dirname(standardsJsonPath), STANDARDS_META_FILE);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readMeta(standardsJsonPath) {
|
|
15
|
+
const path = metaPathFor(standardsJsonPath);
|
|
16
|
+
if (!existsSync(path)) return { standards: {}, sharedAliases: {} };
|
|
17
|
+
try {
|
|
18
|
+
const meta = JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
return { standards: meta?.standards || {}, sharedAliases: meta?.sharedAliases || {} };
|
|
20
|
+
} catch {
|
|
21
|
+
return { standards: {}, sharedAliases: {} };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Sidecar merged by id. Missing/unreadable sidecar = no aliases; missing STANDARDS.json throws. */
|
|
26
|
+
export function loadStandardsRegistry(standardsJsonPath) {
|
|
27
|
+
const runtime = JSON.parse(readFileSync(standardsJsonPath, 'utf8'));
|
|
28
|
+
const meta = readMeta(standardsJsonPath);
|
|
29
|
+
const standards = (Array.isArray(runtime.standards) ? runtime.standards : []).map((s) => {
|
|
30
|
+
const extra = meta.standards[s.id];
|
|
31
|
+
if (!extra) return s;
|
|
32
|
+
const merged = { ...s };
|
|
33
|
+
for (const field of META_FIELDS) if (extra[field] !== undefined) merged[field] = extra[field];
|
|
34
|
+
return merged;
|
|
35
|
+
});
|
|
36
|
+
return { version: runtime.version ?? null, standards, sharedAliases: meta.sharedAliases };
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// The literal regex of the `github-slugger` package: anchors are read off the rendered .md.
|
|
2
|
+
const SLUG_STRIP = /[ -⸀-\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~ ]/g;
|
|
3
|
+
|
|
4
|
+
export function ghSlug(heading) {
|
|
5
|
+
return heading.trim().toLowerCase().replace(SLUG_STRIP, '').replace(/ /g, '-');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Headings outside code fences, repeated slugs suffixed `-1`, `-2` as GitHub does. */
|
|
9
|
+
export function markdownHeadings(text) {
|
|
10
|
+
const out = [];
|
|
11
|
+
const seen = new Map();
|
|
12
|
+
let inFence = false;
|
|
13
|
+
const lines = text.split('\n');
|
|
14
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15
|
+
const line = lines[i];
|
|
16
|
+
if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
|
|
17
|
+
if (inFence) continue;
|
|
18
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line.replace(/\r$/, ''));
|
|
19
|
+
if (!m) continue;
|
|
20
|
+
let slug = ghSlug(m[2]);
|
|
21
|
+
if (seen.has(slug)) {
|
|
22
|
+
const n = seen.get(slug) + 1;
|
|
23
|
+
seen.set(slug, n);
|
|
24
|
+
slug = `${slug}-${n}`;
|
|
25
|
+
} else {
|
|
26
|
+
seen.set(slug, 0);
|
|
27
|
+
}
|
|
28
|
+
out.push({ level: m[1].length, title: m[2], slug, line: i });
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function headingSlugs(text) {
|
|
34
|
+
return new Set(markdownHeadings(text).map((h) => h.slug));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Heading through the line before the next heading of same or higher level; slug may carry `#`. */
|
|
38
|
+
export function sectionBySlug(text, slug) {
|
|
39
|
+
const want = String(slug).replace(/^#/, '');
|
|
40
|
+
const all = markdownHeadings(text);
|
|
41
|
+
const idx = all.findIndex((h) => h.slug === want);
|
|
42
|
+
if (idx === -1) return null;
|
|
43
|
+
const start = all[idx];
|
|
44
|
+
const next = all.slice(idx + 1).find((h) => h.level <= start.level);
|
|
45
|
+
const lines = text.split('\n');
|
|
46
|
+
return lines.slice(start.line, next ? next.line : lines.length).join('\n').replace(/\s+$/, '') + '\n';
|
|
47
|
+
}
|