@polymorphism-tech/morph-spec 9.0.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- 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 +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- 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 +11 -67
- 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/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/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 +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- 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 +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- 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 +34 -12
- 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/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/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- 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 +14 -96
- 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 +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- 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/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/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 +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- 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/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -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/validators/code-budget/code-budget-validator.js +3 -43
- 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 +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- 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 +28 -275
- 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/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,30 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* mobile, abas) escritas por tela, na fase uiux.
|
|
5
|
-
*
|
|
6
|
-
* Existe porque o que o design não pergunta não vira cenário, e o que
|
|
7
|
-
* não é cenário ninguém entrega: telas de admin saíam listando tudo, sem
|
|
8
|
-
* paginar nem ordenar, com o design aprovado. Os leitores do MESMO parser:
|
|
9
|
-
*
|
|
10
|
-
* - `gate-check uiux` pausa com os problemas nomeados;
|
|
11
|
-
* - `approve`/`advance` com assinatura `auto` recusam a mesma pausa sem uma
|
|
12
|
-
* decisão gravada (`uiuxAutoApprovalBlock`) — a regra é a do risk-detector;
|
|
13
|
-
* - `gate-check review` exige que o verificador registre a conferência de cada
|
|
14
|
-
* item decidido contra a tela entregue (`checklistCoverage`).
|
|
15
|
-
*
|
|
16
|
-
* Mockup no formato ANTERIOR ao checklist (telas `## S001: …`, sem nenhuma
|
|
17
|
-
* `## Tela:`) é legado e isento, com a isenção gravada no sinal.
|
|
18
|
-
*
|
|
19
|
-
* O formato é o que a `morph-uiux` ensina: cada tela é um `## Tela: <nome>`, e
|
|
20
|
-
* dentro dela um `### Checklist de tela` com uma linha por item — tabela
|
|
21
|
-
* (`| Paginação | servidor, 25/página |`) ou lista (`- **Paginação:** …`).
|
|
22
|
-
* "Não se aplica" é resposta válida; vazio, "?" ou "a definir" não são.
|
|
23
|
-
*/
|
|
1
|
+
// Cada tela é `## Tela: <nome>` com um `### Checklist de tela`, uma linha por item (tabela ou lista).
|
|
2
|
+
// "Não se aplica" é resposta válida; vazio, "?" ou "a definir" não são. Telas `## S001: …` sem
|
|
3
|
+
// nenhuma `## Tela:` são o formato legado, isento.
|
|
24
4
|
|
|
25
5
|
import { createHash } from 'crypto';
|
|
26
6
|
import { existsSync, readFileSync } from 'fs';
|
|
27
|
-
import { join } from 'path';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
28
8
|
import { fileURLToPath } from 'url';
|
|
29
9
|
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
30
10
|
import { verifierScreenChecklist } from '../review-reports.js';
|
|
@@ -32,12 +12,7 @@ import { LEGACY_MOCKUP_EXEMPTION, uiuxChecklistPause } from '../risk-detector.js
|
|
|
32
12
|
|
|
33
13
|
const PACKAGE_AGENTS = fileURLToPath(new URL('../../../framework/agents.json', import.meta.url));
|
|
34
14
|
|
|
35
|
-
/**
|
|
36
|
-
* Os itens, na ordem do template. `match` roda sobre o rótulo NORMALIZADO
|
|
37
|
-
* (minúsculas, sem acento): "Estado de erro", "Erro" e "**Erro:**" são o mesmo
|
|
38
|
-
* item. Um rótulo que casa com vários ("Estados (vazio/carregando/erro)")
|
|
39
|
-
* responde a todos eles.
|
|
40
|
-
*/
|
|
15
|
+
/** `match` roda sobre o rótulo normalizado; um rótulo que casa com vários itens responde a todos. */
|
|
41
16
|
export const SCREEN_CHECKLIST_ITEMS = [
|
|
42
17
|
{ id: 'superficie', label: 'Superfície', match: /\bsuperficie\b/ },
|
|
43
18
|
{ id: 'volume', label: 'Volume esperado', match: /\bvolume\b/ },
|
|
@@ -51,26 +26,24 @@ export const SCREEN_CHECKLIST_ITEMS = [
|
|
|
51
26
|
{ id: 'abas', label: 'Abas', match: /\babas\b/ },
|
|
52
27
|
];
|
|
53
28
|
|
|
54
|
-
|
|
55
|
-
* Itens que o verificador não confere contra a tela: a superfície decide QUAIS
|
|
56
|
-
* standards a tela lê, não um comportamento que a tela entregue exiba.
|
|
57
|
-
*/
|
|
29
|
+
// A superfície decide quais standards a tela lê, não um comportamento que a tela entregue exiba.
|
|
58
30
|
const NOT_VERIFIED_AT_REVIEW = new Set(['superficie']);
|
|
59
31
|
|
|
60
|
-
|
|
61
|
-
* Respostas que ocupam a célula sem decidir nada: as exatas, e as que COMEÇAM
|
|
62
|
-
* adiando a decisão ("a definir depois", "TBD.", "? talvez").
|
|
63
|
-
*/
|
|
32
|
+
// As exatas, e as que começam adiando a decisão ("a definir depois", "TBD.").
|
|
64
33
|
const PLACEHOLDERS = new Set(['', '-', '--', '—', '–', '...', 'x', 'todo', 'definir', 'pendente']);
|
|
65
34
|
const PLACEHOLDER_PREFIX_RE = /^(a definir|tbd|\?|…)/;
|
|
66
35
|
|
|
67
|
-
/** O formato de mockup anterior ao checklist: telas `## S001: <nome>`. */
|
|
68
36
|
const LEGACY_SCREEN_HEADING_RE = /^##\s+S\d+\s*:/i;
|
|
69
37
|
|
|
70
38
|
const NOT_APPLICABLE_RE = /^(nao se aplica|n\/a)\b/;
|
|
71
39
|
|
|
72
40
|
const SCREEN_HEADING_RE = /^##\s+tela\s*[:—–-]\s*(.+?)\s*#*\s*$/i;
|
|
73
41
|
const CHECKLIST_HEADING_RE = /^(#{3,6})\s+checklist\b/i;
|
|
42
|
+
const PROTOTYPE_LINE_RE = /prot[oó]tipo/i;
|
|
43
|
+
const PROTOTYPE_PATH_RE = /(?:1-design\/)?(prototipos\/[^\s`|)\]]+\.html)/i;
|
|
44
|
+
|
|
45
|
+
/** As larguras dos prints que o laço de autocrítica deixa ao lado do protótipo. */
|
|
46
|
+
export const PROTOTYPE_WIDTHS = [1280, 390];
|
|
74
47
|
|
|
75
48
|
/** Minúsculas, sem acento, sem marcação inline, espaços colapsados. */
|
|
76
49
|
export function normalizeLabel(text) {
|
|
@@ -83,21 +56,15 @@ export function normalizeLabel(text) {
|
|
|
83
56
|
.trim();
|
|
84
57
|
}
|
|
85
58
|
|
|
86
|
-
/** Nome de tela comparável entre o mockup e o relatório do avaliador. */
|
|
87
59
|
function screenKey(name) {
|
|
88
60
|
return normalizeLabel(name).replace(/^tela\s*[:—–-]\s*/, '').replace(/[.:;,]+$/, '');
|
|
89
61
|
}
|
|
90
62
|
|
|
91
|
-
/** Os itens que um rótulo responde. */
|
|
92
63
|
function itemsOf(label) {
|
|
93
64
|
const norm = normalizeLabel(label);
|
|
94
65
|
return SCREEN_CHECKLIST_ITEMS.filter((item) => item.match.test(norm)).map((item) => item.id);
|
|
95
66
|
}
|
|
96
67
|
|
|
97
|
-
/**
|
|
98
|
-
* Uma linha de checklist como `[rótulo, resposta]`, ou null: linha de tabela
|
|
99
|
-
* (separador `|---|` fora) ou item de lista `- Rótulo: resposta`.
|
|
100
|
-
*/
|
|
101
68
|
function checklistRow(line) {
|
|
102
69
|
const trimmed = line.trim();
|
|
103
70
|
if (trimmed.startsWith('|')) {
|
|
@@ -112,13 +79,12 @@ function checklistRow(line) {
|
|
|
112
79
|
return [bullet[1].slice(0, colon), bullet[1].slice(colon + 1).trim()];
|
|
113
80
|
}
|
|
114
81
|
|
|
115
|
-
/** As células de uma linha de tabela markdown, sem as bordas. */
|
|
116
82
|
export function tableCells(line) {
|
|
117
83
|
const inner = line.trim().replace(/^\|/, '').replace(/\|$/, '');
|
|
118
84
|
return inner.split(/(?<!\\)\|/).map((cell) => cell.trim());
|
|
119
85
|
}
|
|
120
86
|
|
|
121
|
-
|
|
87
|
+
// Um exemplo dentro de ``` não é uma tela.
|
|
122
88
|
function proseLines(markdown) {
|
|
123
89
|
const out = [];
|
|
124
90
|
let fenced = false;
|
|
@@ -129,12 +95,6 @@ function proseLines(markdown) {
|
|
|
129
95
|
return out;
|
|
130
96
|
}
|
|
131
97
|
|
|
132
|
-
/**
|
|
133
|
-
* As telas do mockup e o que o checklist de cada uma responde.
|
|
134
|
-
*
|
|
135
|
-
* @param {string} markdown - conteúdo do `1-design/mockups.md`
|
|
136
|
-
* @returns {Array<{ name: string, hasChecklist: boolean, answers: Map<string, string> }>}
|
|
137
|
-
*/
|
|
138
98
|
export function parseScreens(markdown) {
|
|
139
99
|
const screens = [];
|
|
140
100
|
let screen = null;
|
|
@@ -146,7 +106,7 @@ export function parseScreens(markdown) {
|
|
|
146
106
|
const level = heading[1].length;
|
|
147
107
|
const named = SCREEN_HEADING_RE.exec(line);
|
|
148
108
|
if (level <= 2) {
|
|
149
|
-
screen = named && named[1].trim() ? { name: named[1].trim(), hasChecklist: false, answers: new Map() } : null;
|
|
109
|
+
screen = named && named[1].trim() ? { name: named[1].trim(), hasChecklist: false, answers: new Map(), prototype: null } : null;
|
|
150
110
|
if (screen) screens.push(screen);
|
|
151
111
|
checklistLevel = 0;
|
|
152
112
|
continue;
|
|
@@ -160,6 +120,9 @@ export function parseScreens(markdown) {
|
|
|
160
120
|
}
|
|
161
121
|
continue;
|
|
162
122
|
}
|
|
123
|
+
if (screen && !screen.prototype && PROTOTYPE_LINE_RE.test(line)) {
|
|
124
|
+
screen.prototype = PROTOTYPE_PATH_RE.exec(line)?.[1] ?? null;
|
|
125
|
+
}
|
|
163
126
|
if (!screen || !checklistLevel) continue;
|
|
164
127
|
const row = checklistRow(line);
|
|
165
128
|
if (!row) continue;
|
|
@@ -178,16 +141,8 @@ const isNotApplicable = (answer) => NOT_APPLICABLE_RE.test(normalizeLabel(answer
|
|
|
178
141
|
|
|
179
142
|
const labelOf = (id) => SCREEN_CHECKLIST_ITEMS.find((item) => item.id === id)?.label ?? id;
|
|
180
143
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* vocabulário válido do item `Superfície`, que o `morph-plan` usa para
|
|
184
|
-
* somar os standards da tela no `standards:` da fatia. Lê a cópia instalada do
|
|
185
|
-
* projeto; sem ela (este repositório), a do pacote. Persona sem `surfaces`
|
|
186
|
-
* (instalação anterior ao campo) devolve null: o valor não é validado.
|
|
187
|
-
*
|
|
188
|
-
* @param {string} cwd
|
|
189
|
-
* @returns {string[]|null}
|
|
190
|
-
*/
|
|
144
|
+
// O vocabulário válido de `Superfície`: as `surfaces` da `ui-designer`, da cópia instalada ou do pacote.
|
|
145
|
+
// null quando a persona não declara o campo, e então o valor não é validado.
|
|
191
146
|
export function knownSurfaces(cwd) {
|
|
192
147
|
const surfaces = readAgentsJson(cwd)?.agents?.['ui-designer']?.surfaces;
|
|
193
148
|
return surfaces && typeof surfaces === 'object' && !Array.isArray(surfaces) ? Object.keys(surfaces) : null;
|
|
@@ -202,24 +157,13 @@ export function readAgentsJson(cwd) {
|
|
|
202
157
|
return null;
|
|
203
158
|
}
|
|
204
159
|
|
|
205
|
-
/** A resposta de `Superfície` nomeia uma das superfícies conhecidas? */
|
|
206
160
|
function namesSurface(answer, surfaces) {
|
|
207
161
|
const norm = normalizeLabel(answer);
|
|
208
162
|
return surfaces.some((key) => new RegExp(`(^|[^a-z0-9-])${key}([^a-z0-9-]|$)`).test(norm));
|
|
209
163
|
}
|
|
210
164
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
* resposta escrita — e, quando `surfaces` é dado, a `Superfície` é uma delas.
|
|
214
|
-
*
|
|
215
|
-
* Sem nenhuma `## Tela:`, o mockup é `legacy` (isento) só quando declara telas
|
|
216
|
-
* no formato antigo; qualquer outro formato é o problema "nenhuma tela
|
|
217
|
-
* declarada" — senão trocar o heading seria o atalho para pular o checklist.
|
|
218
|
-
*
|
|
219
|
-
* @param {string} markdown
|
|
220
|
-
* @returns {{ legacy: boolean, screens: Array<{ name: string, hasChecklist: boolean, missing: string[], unanswered: string[] }>,
|
|
221
|
-
* problems: string[] }} `problems` vazio ⇔ checklist completo (ou isento)
|
|
222
|
-
*/
|
|
165
|
+
// Sem nenhuma `## Tela:`, só o formato antigo é legado; qualquer outro heading é "nenhuma tela
|
|
166
|
+
// declarada", senão trocar o heading pularia o checklist.
|
|
223
167
|
export function auditScreenChecklist(markdown, { surfaces = null } = {}) {
|
|
224
168
|
const parsed = parseScreens(markdown);
|
|
225
169
|
const problems = [];
|
|
@@ -247,31 +191,38 @@ export function auditScreenChecklist(markdown, { surfaces = null } = {}) {
|
|
|
247
191
|
problems.push(`tela "${screen.name}": Superfície "${surface}" não é uma das superfícies da ui-designer (${surfaces.join(', ')})`);
|
|
248
192
|
}
|
|
249
193
|
}
|
|
250
|
-
return { name: screen.name, hasChecklist: screen.hasChecklist, missing, unanswered };
|
|
194
|
+
return { name: screen.name, hasChecklist: screen.hasChecklist, missing, unanswered, prototype: screen.prototype };
|
|
251
195
|
});
|
|
252
196
|
return { legacy: false, screens, problems };
|
|
253
197
|
}
|
|
254
198
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
199
|
+
// Linha **Protótipo:** da tela + o HTML + os prints do laço de autocrítica ao lado dele.
|
|
200
|
+
export function auditPrototypes(screens, designDir) {
|
|
201
|
+
const problems = [];
|
|
202
|
+
for (const screen of screens) {
|
|
203
|
+
if (!screen.prototype) {
|
|
204
|
+
problems.push(`tela "${screen.name}" sem a linha \`**Protótipo:** 1-design/prototipos/<tela>.html\``);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const base = screen.prototype.replace(/\.html$/i, '');
|
|
208
|
+
const expected = [screen.prototype, ...PROTOTYPE_WIDTHS.map((width) => `${base}-${width}.png`)];
|
|
209
|
+
const missing = expected.filter((rel) => !existsSync(join(designDir, rel)));
|
|
210
|
+
if (missing.length) problems.push(`tela "${screen.name}": falta ${missing.map((rel) => `1-design/${rel}`).join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
return problems;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Coleta, nunca decide: quem pausa é o risk-detector. No review cobra-se a conferência (`uncovered`), não o design.
|
|
269
216
|
export function collectScreenChecklist(cwd, featureName, gateName) {
|
|
270
217
|
const mockups = readMockups(cwd, featureName);
|
|
271
218
|
if (mockups === null) return { mockups: false, legacy: false, screens: [], problems: [] };
|
|
272
219
|
const audit = auditScreenChecklist(mockups, { surfaces: knownSurfaces(cwd) });
|
|
273
220
|
const signal = { mockups: true, legacy: audit.legacy, screens: audit.screens.map((s) => s.name), problems: audit.problems };
|
|
274
221
|
if (audit.legacy) signal.exempt = LEGACY_MOCKUP_EXEMPTION;
|
|
222
|
+
if (gateName === 'uiux' && !audit.legacy) {
|
|
223
|
+
const designDir = dirname(getAbsoluteOutputPath(cwd, featureName, 'uiMockups'));
|
|
224
|
+
signal.prototypes = auditPrototypes(audit.screens, designDir);
|
|
225
|
+
}
|
|
275
226
|
if (gateName === 'review') {
|
|
276
227
|
const report = verifierScreenChecklist(cwd, featureName);
|
|
277
228
|
signal.section = report.section;
|
|
@@ -280,36 +231,15 @@ export function collectScreenChecklist(cwd, featureName, gateName) {
|
|
|
280
231
|
return signal;
|
|
281
232
|
}
|
|
282
233
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
* que o `gate-check uiux` daria pelo checklist —, ou null. Lido por `approve`
|
|
286
|
-
* e `advance`, para que uma aprovação automática nunca passe onde o gate-check
|
|
287
|
-
* pausaria. Uma decisão gravada com `pause: false` (o `gate-decision` manual,
|
|
288
|
-
* ou um gate-check que não pausou) é a resposta a essa pausa e destrava — só
|
|
289
|
-
* para o mockup que ela viu: a decisão guarda `mockupsHash`, e um mockup
|
|
290
|
-
* escrito ou mudado depois dela volta a ser conferido.
|
|
291
|
-
*
|
|
292
|
-
* @param {string} cwd
|
|
293
|
-
* @param {string} featureName
|
|
294
|
-
* @param {object} feature - o feature.json (lê `gateDecisions.uiux`)
|
|
295
|
-
* @returns {string|null}
|
|
296
|
-
*/
|
|
234
|
+
// Lido por approve e advance para que o `auto` nunca passe onde o gate-check pausaria. Uma decisão com
|
|
235
|
+
// `pause: false` destrava só o design que ela viu: guarda `mockupsHash`, e mudar o arquivo volta a conferir.
|
|
297
236
|
export function uiuxAutoApprovalBlock(cwd, featureName, feature) {
|
|
298
237
|
const decision = feature?.gateDecisions?.uiux;
|
|
299
238
|
if (decision?.pause === false && decision.mockupsHash === mockupsHash(cwd, featureName)) return null;
|
|
300
239
|
return uiuxChecklistPause(collectScreenChecklist(cwd, featureName, 'uiux'));
|
|
301
240
|
}
|
|
302
241
|
|
|
303
|
-
|
|
304
|
-
* O que o verificador deixou de conferir: por tela do mockup, os itens com
|
|
305
|
-
* decisão (nem "não se aplica", nem a superfície) sem linha na seção
|
|
306
|
-
* `## Checklist de tela` do relatório. Uma linha que o avaliador marca como
|
|
307
|
-
* não verificável conta como conferida — o que pausa é a AUSÊNCIA da linha.
|
|
308
|
-
*
|
|
309
|
-
* @param {string} designMarkdown - o `1-design/mockups.md`
|
|
310
|
-
* @param {string} reportSection - o corpo da seção do `verifier.md`
|
|
311
|
-
* @returns {string[]} `"<tela>: <item>, <item>"` por tela com lacuna
|
|
312
|
-
*/
|
|
242
|
+
// Linha marcada como não verificável conta como conferida: o que pausa é a ausência da linha.
|
|
313
243
|
export function checklistCoverage(designMarkdown, reportSection) {
|
|
314
244
|
const covered = new Map();
|
|
315
245
|
for (const line of proseLines(reportSection)) {
|
|
@@ -333,14 +263,7 @@ export function checklistCoverage(designMarkdown, reportSection) {
|
|
|
333
263
|
return gaps;
|
|
334
264
|
}
|
|
335
265
|
|
|
336
|
-
|
|
337
|
-
* O `1-design/mockups.md` por conteúdo: BOM e CRLF não contam, para que o
|
|
338
|
-
* checkout de outra máquina seja o mesmo mockup.
|
|
339
|
-
*
|
|
340
|
-
* @param {string} cwd
|
|
341
|
-
* @param {string} featureName
|
|
342
|
-
* @returns {string|null} null sem mockup
|
|
343
|
-
*/
|
|
266
|
+
// BOM e CRLF não contam: o checkout de outra máquina é o mesmo arquivo.
|
|
344
267
|
export function mockupsHash(cwd, featureName) {
|
|
345
268
|
const text = readMockups(cwd, featureName);
|
|
346
269
|
if (text === null) return null;
|
|
@@ -27,17 +27,13 @@ function planHasDotnetSlice(tree, featureName) {
|
|
|
27
27
|
return (checks?.slices || []).some((slice) => DOTNET_IMPLEMENTERS.includes(String(slice.persona || '').replace(/^morph-/, '')));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
* "Is this a .NET project?" from what is known before the first code commit:
|
|
32
|
-
* the declared `project.stack`, a .NET implementer in the plan's slices, or a
|
|
33
|
-
* .csproj/.sln/.slnx already in the tree. Greenfield has only the second.
|
|
34
|
-
*/
|
|
30
|
+
// The plan's slices are checked because a greenfield tree has no stack or .csproj yet.
|
|
35
31
|
export function isDotnetProject(tree, { featureName, configRoot = tree } = {}) {
|
|
36
32
|
if (getProjectTags(readMorphConfig(configRoot)).includes('dotnet')) return true;
|
|
37
33
|
return planHasDotnetSlice(tree, featureName) || hasDotnetProjectFile(tree);
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
/**
|
|
36
|
+
/** Returns the `.gitignore` entries added; `[]` when not .NET or on any error. */
|
|
41
37
|
export function seedDotnetBuildIgnores(tree, options = {}) {
|
|
42
38
|
try {
|
|
43
39
|
return isDotnetProject(tree, options) ? ensureDotnetBuildIgnores(tree) : [];
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
|
-
/** @typedef {'nextjs'|'dotnet'|'nodejs-cli'|'unknown'} StackName */
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {Object} StackProfile
|
|
8
|
-
* @property {StackName} stack
|
|
9
|
-
* @property {boolean} isDotnet
|
|
10
|
-
* @property {boolean} isNextjs
|
|
11
|
-
* @property {string[]} defaultValidators
|
|
12
|
-
* @property {string|null} buildCommand
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
4
|
const DOTNET_STACKS = new Set(['dotnet', 'aspnet']);
|
|
16
5
|
const NEXTJS_STACKS = new Set(['nextjs', 'next', 'next.js']);
|
|
17
6
|
const NO_VALIDATOR_STACKS = new Set(['nodejs-cli', 'node-cli', 'cli']);
|
|
@@ -34,9 +23,6 @@ const PROFILES = {
|
|
|
34
23
|
},
|
|
35
24
|
};
|
|
36
25
|
|
|
37
|
-
/**
|
|
38
|
-
* Read project stack from .morph/config/config.json.
|
|
39
|
-
*/
|
|
40
26
|
function readStack(cwd) {
|
|
41
27
|
const configPath = join(cwd, '.morph', 'config', 'config.json');
|
|
42
28
|
if (!existsSync(configPath)) return null;
|
|
@@ -47,7 +33,6 @@ function readStack(cwd) {
|
|
|
47
33
|
}
|
|
48
34
|
}
|
|
49
35
|
|
|
50
|
-
/** Merge two or more profiles' validator lists (deduped) and boolean flags. */
|
|
51
36
|
function mergeProfiles(profiles) {
|
|
52
37
|
return {
|
|
53
38
|
isDotnet: profiles.some((p) => p.isDotnet),
|
|
@@ -57,16 +42,7 @@ function mergeProfiles(profiles) {
|
|
|
57
42
|
};
|
|
58
43
|
}
|
|
59
44
|
|
|
60
|
-
/**
|
|
61
|
-
* Get the StackProfile for the current project.
|
|
62
|
-
*
|
|
63
|
-
* `stack` may be a compound value (e.g. "dotnet-nextjs", "dotnet+nextjs") —
|
|
64
|
-
* the framework's own convention for monorepos (see stack-filter.js's
|
|
65
|
-
* getProjectTags, which splits project.stack the same way). Each segment is
|
|
66
|
-
* resolved independently and matching profiles are merged, instead of the
|
|
67
|
-
* whole compound string failing every exact-match check and silently
|
|
68
|
-
* collapsing to the empty profile (0 default validators).
|
|
69
|
-
*/
|
|
45
|
+
/** A compound stack ("dotnet-nextjs", "dotnet+nextjs") merges the profile of each segment. */
|
|
70
46
|
export function getStackProfile(cwd, stackOverride) {
|
|
71
47
|
const root = cwd ?? process.cwd();
|
|
72
48
|
const raw = stackOverride ?? readStack(root);
|
|
@@ -74,8 +50,6 @@ export function getStackProfile(cwd, stackOverride) {
|
|
|
74
50
|
|
|
75
51
|
const segments = stack.split(/[-+]/).filter(Boolean);
|
|
76
52
|
|
|
77
|
-
// Single-segment stack (the overwhelming common case): resolve exactly as
|
|
78
|
-
// before, byte-for-byte, including the 'unknown' → dotnet fallback.
|
|
79
53
|
if (segments.length <= 1) {
|
|
80
54
|
let profile;
|
|
81
55
|
if (NEXTJS_STACKS.has(stack)) {
|
|
@@ -90,7 +64,6 @@ export function getStackProfile(cwd, stackOverride) {
|
|
|
90
64
|
return { stack, ...profile };
|
|
91
65
|
}
|
|
92
66
|
|
|
93
|
-
// Compound stack: resolve each recognized segment and merge.
|
|
94
67
|
const matched = [];
|
|
95
68
|
if (segments.some((s) => DOTNET_STACKS.has(s))) matched.push(PROFILES.dotnet);
|
|
96
69
|
if (segments.some((s) => NEXTJS_STACKS.has(s))) matched.push(PROFILES.nextjs);
|
|
@@ -175,13 +175,7 @@ function auditNextjs(root, read) {
|
|
|
175
175
|
|
|
176
176
|
const AUDITS = { dotnet: auditDotnet, nextjs: auditNextjs };
|
|
177
177
|
|
|
178
|
-
/**
|
|
179
|
-
* Deterministic stack audit of the tree at `root`, driven by `config.project.stack`:
|
|
180
|
-
* only the configured stacks are judged; no configured stack means no rule runs.
|
|
181
|
-
*
|
|
182
|
-
* @param {string} root
|
|
183
|
-
* @param {object|null} config the parsed `.morph/config/config.json`
|
|
184
|
-
*/
|
|
178
|
+
/** Only the stacks in `config.project.stack` are judged; none configured means no rule runs. */
|
|
185
179
|
export function scanStack(root, config) {
|
|
186
180
|
const stack = typeof config?.project?.stack === 'string' ? config.project.stack : null;
|
|
187
181
|
const tags = getProjectTags(config);
|
|
@@ -230,7 +224,6 @@ export function notAuditedReason(result) {
|
|
|
230
224
|
: 'não auditado: project.stack ausente em .morph/config/config.json';
|
|
231
225
|
}
|
|
232
226
|
|
|
233
|
-
/** The scan as the markdown report the morph-plan skill reads. */
|
|
234
227
|
export function renderStackScan(result, title) {
|
|
235
228
|
const out = [`# Stack Scan — ${title}`, ''];
|
|
236
229
|
out.push(`**Stack configurado:** ${result.stack ? `\`${result.stack}\`` : 'ausente em `project.stack` — nenhuma regra aplicada'}`);
|
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));
|
|
@@ -22,13 +22,7 @@ function readMeta(standardsJsonPath) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* The registry as every reader sees it: runtime entries with their sidecar
|
|
27
|
-
* fields merged back by id. A missing or unreadable sidecar degrades to "no
|
|
28
|
-
* aliases"; a missing or unreadable STANDARDS.json throws.
|
|
29
|
-
* @param {string} standardsJsonPath
|
|
30
|
-
* @returns {{ version: string|null, standards: object[], sharedAliases: Record<string, string[]> }}
|
|
31
|
-
*/
|
|
25
|
+
/** Sidecar merged by id. Missing/unreadable sidecar = no aliases; missing STANDARDS.json throws. */
|
|
32
26
|
export function loadStandardsRegistry(standardsJsonPath) {
|
|
33
27
|
const runtime = JSON.parse(readFileSync(standardsJsonPath, 'utf8'));
|
|
34
28
|
const meta = readMeta(standardsJsonPath);
|
|
@@ -5,12 +5,7 @@ export function ghSlug(heading) {
|
|
|
5
5
|
return heading.trim().toLowerCase().replace(SLUG_STRIP, '').replace(/ /g, '-');
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Every heading outside code fences, with the slug GitHub gives it (`-1`, `-2`
|
|
10
|
-
* for repeats) and the line it sits on.
|
|
11
|
-
* @param {string} text
|
|
12
|
-
* @returns {Array<{ level: number, title: string, slug: string, line: number }>}
|
|
13
|
-
*/
|
|
8
|
+
/** Headings outside code fences, repeated slugs suffixed `-1`, `-2` as GitHub does. */
|
|
14
9
|
export function markdownHeadings(text) {
|
|
15
10
|
const out = [];
|
|
16
11
|
const seen = new Map();
|
|
@@ -39,13 +34,7 @@ export function headingSlugs(text) {
|
|
|
39
34
|
return new Set(markdownHeadings(text).map((h) => h.slug));
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
/**
|
|
43
|
-
* The section a slug opens: its heading through the line before the next
|
|
44
|
-
* heading of the same or a higher level. `null` when no heading has the slug.
|
|
45
|
-
* @param {string} text
|
|
46
|
-
* @param {string} slug - with or without the leading `#`
|
|
47
|
-
* @returns {string|null}
|
|
48
|
-
*/
|
|
37
|
+
/** Heading through the line before the next heading of same or higher level; slug may carry `#`. */
|
|
49
38
|
export function sectionBySlug(text, slug) {
|
|
50
39
|
const want = String(slug).replace(/^#/, '');
|
|
51
40
|
const all = markdownHeadings(text);
|