@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,29 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* half of the missing-report protocol.
|
|
4
|
-
*
|
|
5
|
-
* The slice report file is the sub-agent's authoritative return value; the
|
|
6
|
-
* final message is a courtesy. This command answers what the orchestrator
|
|
7
|
-
* needs when it resumes control, from evidence rather than from the agent's
|
|
8
|
-
* own word: **did the WORK land, independently of whether the REPORT did?**
|
|
9
|
-
*
|
|
10
|
-
* verdict action
|
|
11
|
-
* ───────────────────────────── ──────────────────
|
|
12
|
-
* ok proceed
|
|
13
|
-
* report-stale refresh-from-evidence
|
|
14
|
-
* report-incomplete resume | complete-degraded
|
|
15
|
-
* work-landed-report-missing accept-degraded
|
|
16
|
-
* no-work-no-report redispatch
|
|
17
|
-
* report-missing-evidence-unknown redispatch (conservative default)
|
|
18
|
-
*
|
|
19
|
-
* The observed evidence is keyed by the dispatch: the agent whose `agentId`
|
|
20
|
-
* wrote the report owns the window, and its writes after the report make the
|
|
21
|
-
* report stale. A report written inline (no sub-agent) is judged by its
|
|
22
|
-
* markers only.
|
|
23
|
-
*
|
|
24
|
-
* Exit 0 only for `ok`. The output also carries `tree` (see tree-state.js),
|
|
25
|
-
* information only — it never changes the verdict.
|
|
26
|
-
*/
|
|
1
|
+
// Judged from observed evidence, never the agent's word: did the WORK land, independently of
|
|
2
|
+
// the REPORT? The `tree` block is information only and never changes the verdict.
|
|
27
3
|
|
|
28
4
|
import chalk from 'chalk';
|
|
29
5
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
@@ -54,7 +30,7 @@ export function inspectReviewReports(cwd, feature) {
|
|
|
54
30
|
return out;
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
|
|
33
|
+
// The verifier routes to opus on prompt/model or media; security is dispatched only when required.
|
|
58
34
|
function reviewDispatch(cwd, feature, slices) {
|
|
59
35
|
let changedFiles = [];
|
|
60
36
|
try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
|
|
@@ -74,20 +50,12 @@ function reviewDispatch(cwd, feature, slices) {
|
|
|
74
50
|
};
|
|
75
51
|
}
|
|
76
52
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* from the telemetry hook. Two seconds absorb their skew and are far less
|
|
80
|
-
* than any real work done after reporting.
|
|
81
|
-
*/
|
|
53
|
+
// Two clocks: the report mtime comes from the filesystem, the event `ts` from the hook. Two
|
|
54
|
+
// seconds absorb their skew and are far less than any real work done after reporting.
|
|
82
55
|
export const STALE_TOLERANCE_MS = 2000;
|
|
83
56
|
|
|
84
57
|
const slash = (p) => String(p || '').replace(/\\/g, '/');
|
|
85
58
|
|
|
86
|
-
/**
|
|
87
|
-
* The event target relative to the tree that logged it, the same shape as the
|
|
88
|
-
* slice's `arquivos:`. `Write`/`Edit` targets are absolute; a target outside
|
|
89
|
-
* that tree comes back as it was.
|
|
90
|
-
*/
|
|
91
59
|
function toTreeRelative(target, treePath) {
|
|
92
60
|
const norm = slash(target);
|
|
93
61
|
const root = slash(treePath).replace(/\/+$/, '');
|
|
@@ -98,7 +66,6 @@ function toTreeRelative(target, treePath) {
|
|
|
98
66
|
|
|
99
67
|
const reportSuffix = (feature, sliceId) => `.morph/features/${feature}/3-implement/reports/${sliceId}.md`.toLowerCase();
|
|
100
68
|
|
|
101
|
-
/** Every successful Write/Edit of every tree of this repo, oldest first. */
|
|
102
69
|
function loadWrites(cwd) {
|
|
103
70
|
let trees = [];
|
|
104
71
|
try { trees = loadTelemetryAllTrees(cwd); } catch { trees = []; }
|
|
@@ -115,14 +82,8 @@ function loadWrites(cwd) {
|
|
|
115
82
|
return { available: trees.some((t) => t.events.length > 0), writes: dedupeEvents(events) };
|
|
116
83
|
}
|
|
117
84
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
* The dispatch that owns the slice is the sub-agent that wrote its report —
|
|
122
|
-
* the latest such write carrying an `agentId`; an orchestrator edit made
|
|
123
|
-
* afterwards does not hide it. Without a report, any write to one of the
|
|
124
|
-
* slice's files says the work landed.
|
|
125
|
-
*/
|
|
85
|
+
// The owning dispatch is the latest sub-agent write of the report; a later orchestrator edit
|
|
86
|
+
// does not hide it. Without a report, any write to one of the slice files says the work landed.
|
|
126
87
|
function observedEvidence(telemetry, feature, slice) {
|
|
127
88
|
if (!telemetry.available) return { available: false };
|
|
128
89
|
const suffix = reportSuffix(feature, slice.id);
|
|
@@ -150,7 +111,6 @@ function observedEvidence(telemetry, feature, slice) {
|
|
|
150
111
|
};
|
|
151
112
|
}
|
|
152
113
|
|
|
153
|
-
/** Is the report older than the last work its own dispatch observed? */
|
|
154
114
|
function isReportStale(reportFsPath, observed) {
|
|
155
115
|
if (!observed?.agentId || !observed.lastWriteAt) return false;
|
|
156
116
|
try {
|
|
@@ -160,7 +120,6 @@ function isReportStale(reportFsPath, observed) {
|
|
|
160
120
|
}
|
|
161
121
|
}
|
|
162
122
|
|
|
163
|
-
/** Combine report state + evidence into a verdict and the action it implies. */
|
|
164
123
|
function decide(report, observed, stale) {
|
|
165
124
|
if (report.exists && report.complete) {
|
|
166
125
|
// Structural integrity answers "is the report complete?", never "is it
|
|
@@ -200,11 +159,7 @@ function snapshotPath(cwd, feature) {
|
|
|
200
159
|
return join(cwd, '.morph', 'features', feature, '4-review', 'tree-before-eval.json');
|
|
201
160
|
}
|
|
202
161
|
|
|
203
|
-
|
|
204
|
-
* The tree block, compared with the snapshot taken before the review agents were
|
|
205
|
-
* dispatched when there is one. `beforeEval` takes a new snapshot after the
|
|
206
|
-
* comparison, so the output still shows what changed since the previous one.
|
|
207
|
-
*/
|
|
162
|
+
// `beforeEval` snapshots after comparing, so the output still shows what changed since the last one.
|
|
208
163
|
function treeFor(cwd, feature, beforeEval, slices = []) {
|
|
209
164
|
const path = snapshotPath(cwd, feature);
|
|
210
165
|
let snapshot = null;
|
|
@@ -218,11 +173,6 @@ function treeFor(cwd, feature, beforeEval, slices = []) {
|
|
|
218
173
|
return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, reviewModels };
|
|
219
174
|
}
|
|
220
175
|
|
|
221
|
-
/**
|
|
222
|
-
* @param {string} feature
|
|
223
|
-
* @param {string[]|undefined} sliceIds - audits only these slices; none audits every slice
|
|
224
|
-
* @param {{ json?: boolean, cwd?: string, beforeEval?: boolean }} [options]
|
|
225
|
-
*/
|
|
226
176
|
export function reportCheckCommand(feature, sliceIds, options = {}) {
|
|
227
177
|
const cwd = options.cwd || process.cwd();
|
|
228
178
|
const emit = (obj, code) => {
|
|
@@ -5,28 +5,8 @@ import { getFeature, setApprovalGate, updateFeature } from '../../core/state/sta
|
|
|
5
5
|
import { WORK_TYPES, isValidWorkType, POLICY_APPROVER_PREFIX } from '../../core/work-types.js';
|
|
6
6
|
import { renderPlanSkeleton } from '../../lib/plan/plan-template.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* asymmetric gate rules that preserve GENUINE human/auto approvals and only
|
|
11
|
-
* ever undo policy stamps:
|
|
12
|
-
*
|
|
13
|
-
* - Blocked outright once Gate 3 (review) is approved — the feature already
|
|
14
|
-
* shipped its pipeline, retyping it retroactively would be revisionist.
|
|
15
|
-
* - Downgrade (fewer skipped gates → more skipped gates, e.g. feature/bug
|
|
16
|
-
* → chore/hotfix) is blocked once the plan gate carries a genuine
|
|
17
|
-
* (non-policy) approval — the plan was already accepted under the old
|
|
18
|
-
* type's scrutiny; retyping around it would erase that judgment call.
|
|
19
|
-
* - Upgrade (more skipped gates → fewer, e.g. chore/hotfix → feature/bug)
|
|
20
|
-
* is always allowed before review: it only ever ADDS scrutiny.
|
|
21
|
-
*
|
|
22
|
-
* Applying a retype resets exactly the gates that were stamped
|
|
23
|
-
* `policy:{oldType}` and the NEW type no longer skips (so the human is asked
|
|
24
|
-
* again), stamps `policy:{newType}` on any of the new type's skippedGates
|
|
25
|
-
* that aren't already approved (a prior genuine approval is left standing —
|
|
26
|
-
* that's the "preserves human approvals" half of the contract), scaffolds
|
|
27
|
-
* any phase folders the new type needs that don't exist yet, and appends an
|
|
28
|
-
* audit entry to `workTypeHistory`.
|
|
29
|
-
*/
|
|
8
|
+
// Retype only ever undoes `policy:` stamps: blocked once review is approved; a downgrade
|
|
9
|
+
// is blocked once the plan carries a genuine approval; an upgrade only adds scrutiny.
|
|
30
10
|
export async function retypeCommand(featureName, newType, options = {}) {
|
|
31
11
|
try {
|
|
32
12
|
if (!isValidWorkType(newType)) {
|
|
@@ -69,8 +49,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
|
|
|
69
49
|
|
|
70
50
|
const now = new Date().toISOString();
|
|
71
51
|
|
|
72
|
-
//
|
|
73
|
-
// longer skips — a genuine (non-policy) approval is never touched.
|
|
52
|
+
// A genuine (non-policy) approval is never reset.
|
|
74
53
|
const resetGates = [];
|
|
75
54
|
for (const [gate, data] of Object.entries(feature.approvalGates || {})) {
|
|
76
55
|
const isPolicyStamped = String(data?.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
|
|
@@ -80,8 +59,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
|
|
|
80
59
|
}
|
|
81
60
|
}
|
|
82
61
|
|
|
83
|
-
//
|
|
84
|
-
// approved (preserves a prior genuine approval untouched).
|
|
62
|
+
// A gate already approved keeps its genuine approval.
|
|
85
63
|
const stampedGates = [];
|
|
86
64
|
for (const gate of newWorkType.skippedGates) {
|
|
87
65
|
if (!feature.approvalGates?.[gate]?.approved) {
|
|
@@ -15,7 +15,7 @@ import { groupByNode, loadPlan } from '../../lib/mutation/plan.js';
|
|
|
15
15
|
import { dotnetSubset, jsSubset, mutatedTypesOf, testFilesCiting, testProjectDirsOf } from '../../lib/mutation/subset.js';
|
|
16
16
|
import { isFinished } from '../../dashboard/scanner/feature-status.js';
|
|
17
17
|
import { dotnetAdapter, genericAdapter, spawnExec } from '../../lib/mutation/adapters.js';
|
|
18
|
-
import { runMutation } from '../../lib/mutation/runner.js';
|
|
18
|
+
import { noSubsetReason, runMutation } from '../../lib/mutation/runner.js';
|
|
19
19
|
import { renderSection, summarize } from '../../lib/mutation/report.js';
|
|
20
20
|
import { readJournal, restoreSnapshot } from '../../lib/mutation/snapshot.js';
|
|
21
21
|
|
|
@@ -23,11 +23,8 @@ const EXIT = { ok: 0, failed: 1, usage: 2, busy: 3, unsafe: 4 };
|
|
|
23
23
|
const KEEP_RUNS = 2;
|
|
24
24
|
const DEFAULT_JOBS = 2;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* are taken only if free right now. The run uses as many workers as it got,
|
|
29
|
-
* so a busy machine degrades to sequential instead of queueing.
|
|
30
|
-
*/
|
|
26
|
+
// One heavy slot per worker: the first waits, the others are taken only if free now,
|
|
27
|
+
// so a busy machine degrades to sequential instead of queueing.
|
|
31
28
|
export function workerGate({ root, feature, jobs }) {
|
|
32
29
|
const config = readHeavyConfig(root);
|
|
33
30
|
const gates = [createHeavyGate({ projectPath: root, label: 'mutate', feature, config })];
|
|
@@ -104,8 +101,7 @@ function testDirsOf(node, root, { projectsOnly = false } = {}) {
|
|
|
104
101
|
return testProjectDirsOf(root, projectsOnly ? targets.filter((t) => !/\.slnx?$/i.test(t)) : targets, cwd);
|
|
105
102
|
}
|
|
106
103
|
|
|
107
|
-
function subsetFor({ node, group, root
|
|
108
|
-
if (options.subset === false) return { usable: false, reason: '--no-subset' };
|
|
104
|
+
function subsetFor({ node, group, root }) {
|
|
109
105
|
const mutatedFiles = group.regions.map((r) => r.file);
|
|
110
106
|
if (node.runner === 'dotnet') {
|
|
111
107
|
const base = detectDefaultBranch(root);
|
|
@@ -196,6 +192,12 @@ export async function mutateCommand(featureArg, options = {}) {
|
|
|
196
192
|
return fail(`${named}\nNó e2e: true declarado: ${e2eIds}. ${e2eManualProtocol(feature, uncited[0].e2e[0])}`, EXIT.usage);
|
|
197
193
|
}
|
|
198
194
|
|
|
195
|
+
const subsets = groups.map((group) => subsetFor({ node: group.node, group, root }));
|
|
196
|
+
const noSubset = subsets.findIndex((s) => !s.usable);
|
|
197
|
+
if (noSubset >= 0) {
|
|
198
|
+
return fail(`suíte "${groups[noSubset].node.id}": ${noSubsetReason(subsets[noSubset].reason)}.`, EXIT.usage);
|
|
199
|
+
}
|
|
200
|
+
|
|
199
201
|
if (!options.allowDirty) {
|
|
200
202
|
const dirty = gitOut(root, ['status', '--porcelain', '--', ...new Set(regions.map((r) => r.file))]).trim();
|
|
201
203
|
if (dirty) return fail(`há alteração não commitada nos arquivos a mutar:\n${dirty}\nCommite o fix ANTES de mutar (ou use --allow-dirty sabendo que o snapshot é o único caminho de volta).`, EXIT.usage);
|
|
@@ -210,13 +212,11 @@ export async function mutateCommand(featureArg, options = {}) {
|
|
|
210
212
|
const exec = options.exec ?? spawnExec;
|
|
211
213
|
|
|
212
214
|
const merged = { wallMs: 0, mutants: [], timings: [], restore: { files: [], ok: true, treeClean: true }, binary: { proof: 'unavailable', projects: [] }, baseline: {}, method: { schemata: 0, rebuild: 0, subset: {} } };
|
|
213
|
-
const subsets = [];
|
|
214
215
|
try {
|
|
215
216
|
for (const [index, group] of groups.entries()) {
|
|
216
217
|
const workDir = join(base, runId, `g${index}`);
|
|
217
218
|
mkdirSync(workDir, { recursive: true });
|
|
218
|
-
const subset =
|
|
219
|
-
subsets.push(subset);
|
|
219
|
+
const subset = subsets[index];
|
|
220
220
|
const timeoutMs = options.timeoutMs ?? group.node.timeoutMs ?? 900_000;
|
|
221
221
|
const make = group.node.runner === 'dotnet' ? dotnetAdapter : genericAdapter;
|
|
222
222
|
const adapter = make({ node: group.node, root, workDir, exec, subset, timeoutMs });
|
|
@@ -5,7 +5,7 @@ import { atomicWriteJson } from '../../core/state/state-manager.js';
|
|
|
5
5
|
import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
|
|
6
6
|
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
7
7
|
import { parsePlan } from '../../lib/plan/plan-parser.js';
|
|
8
|
-
import { buildChecks, knownPersonas, parallelGroups, planHash, validatePlan } from '../../lib/plan/checks.js';
|
|
8
|
+
import { buildChecks, knownPersonas, parallelGroups, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
|
|
9
9
|
import { knownSurfaces } from '../../lib/specs/screen-checklist.js';
|
|
10
10
|
import { revocationWarnings } from '../../lib/specs/adr-index.js';
|
|
11
11
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
@@ -25,7 +25,9 @@ export function runPlanCheck(featureName, cwd = process.cwd()) {
|
|
|
25
25
|
|
|
26
26
|
const text = readFileSync(planPath, 'utf8');
|
|
27
27
|
const plan = parsePlan(text);
|
|
28
|
-
const { errors, warnings } = validatePlan(plan, {
|
|
28
|
+
const { errors, warnings } = validatePlan(plan, {
|
|
29
|
+
personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd), ...planRuleOptions(cwd, featureName),
|
|
30
|
+
});
|
|
29
31
|
warnings.push(...revocationWarnings(cwd, plan));
|
|
30
32
|
const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
|
|
31
33
|
const hash = planHash(text);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { auditTests, renderTestsAudit } from '../../lib/tests-audit/audit.js';
|
|
2
|
+
|
|
3
|
+
export function testsAuditCommand(options = {}) {
|
|
4
|
+
const result = auditTests(process.cwd());
|
|
5
|
+
console.log(options.json ? JSON.stringify(result, null, 2) : renderTestsAudit(result));
|
|
6
|
+
if (!result.ok) process.exit(2);
|
|
7
|
+
}
|
|
@@ -1,34 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Feature Validation Command
|
|
3
|
-
*
|
|
4
|
-
* Content-aware validation of feature outputs at each phase.
|
|
5
|
-
* Checks section presence, schema consistency, and contract correctness.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* morph-spec validate <feature-name> [--phase <phase>] [--json] [--verbose]
|
|
9
|
-
*
|
|
10
|
-
* Example:
|
|
11
|
-
* morph-spec validate user-portal --phase plan
|
|
12
|
-
* morph-spec validate user-portal --json
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
1
|
import fs from 'fs';
|
|
16
2
|
import path from 'path';
|
|
17
3
|
import chalk from 'chalk';
|
|
18
4
|
import { parsePlan } from '../../lib/plan/plan-parser.js';
|
|
19
|
-
import { knownPersonas, planHash, validatePlan } from '../../lib/plan/checks.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* (headings and line-start bold labels), not whole-file substrings; see
|
|
24
|
-
* `extractSectionMarkers`. plan.md is judged by the plan parser instead.
|
|
25
|
-
*
|
|
26
|
-
* required → ERROR if absent.
|
|
27
|
-
* recommended → WARNING if absent.
|
|
28
|
-
* optional → silent. Tracked when present, never penalized.
|
|
29
|
-
*
|
|
30
|
-
* Each entry is a string or an array of acceptable aliases (any-of).
|
|
31
|
-
*/
|
|
5
|
+
import { knownPersonas, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
|
|
6
|
+
|
|
7
|
+
// Matched against section markers (headings and line-start bold labels), never whole-file
|
|
8
|
+
// substrings. required → ERROR, recommended → WARNING, optional → never penalized; an array is any-of.
|
|
32
9
|
const OUTPUT_SECTIONS = {
|
|
33
10
|
'verifier.md': {
|
|
34
11
|
required: [
|
|
@@ -60,9 +37,6 @@ const OUTPUT_SECTIONS = {
|
|
|
60
37
|
},
|
|
61
38
|
};
|
|
62
39
|
|
|
63
|
-
/**
|
|
64
|
-
* Phase-to-output mapping (v5 NLH)
|
|
65
|
-
*/
|
|
66
40
|
const PHASE_OUTPUTS = {
|
|
67
41
|
plan: ['plan.md', 'checks.json'],
|
|
68
42
|
uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
|
|
@@ -70,9 +44,6 @@ const PHASE_OUTPUTS = {
|
|
|
70
44
|
review: ['verifier.md', 'reviewer.md'],
|
|
71
45
|
};
|
|
72
46
|
|
|
73
|
-
/**
|
|
74
|
-
* Phase-to-folder mapping (v5 filesystem convention)
|
|
75
|
-
*/
|
|
76
47
|
const PHASE_FOLDERS = {
|
|
77
48
|
plan: '2-plan',
|
|
78
49
|
uiux: '1-design',
|
|
@@ -80,20 +51,7 @@ const PHASE_FOLDERS = {
|
|
|
80
51
|
review: '4-review',
|
|
81
52
|
};
|
|
82
53
|
|
|
83
|
-
|
|
84
|
-
* Extract section markers from markdown content.
|
|
85
|
-
*
|
|
86
|
-
* A marker is a line that structurally introduces a section:
|
|
87
|
-
* - a markdown heading → `## Arquitetura da solução`
|
|
88
|
-
* - a line-start bold label → `**Status:** Aceito`
|
|
89
|
-
*
|
|
90
|
-
* Bold labels are included on purpose (deviation from heading-only): reports
|
|
91
|
-
* write `**Verdict:**` as a line-start bold label, not a heading. They are
|
|
92
|
-
* structural, not prose, so this avoids the accidental-prose-match problem that
|
|
93
|
-
* whole-file substring matching had.
|
|
94
|
-
*
|
|
95
|
-
* Returns lowercased marker texts.
|
|
96
|
-
*/
|
|
54
|
+
// Line-start bold labels count: reports write `**Verdict:**` as a label, not a heading.
|
|
97
55
|
function extractSectionMarkers(content) {
|
|
98
56
|
const markers = [];
|
|
99
57
|
for (const rawLine of content.split('\n')) {
|
|
@@ -111,9 +69,6 @@ function extractSectionMarkers(content) {
|
|
|
111
69
|
return markers;
|
|
112
70
|
}
|
|
113
71
|
|
|
114
|
-
/**
|
|
115
|
-
* Validate a single output file
|
|
116
|
-
*/
|
|
117
72
|
function validateOutput(featurePath, outputFile) {
|
|
118
73
|
const filePath = path.join(featurePath, outputFile);
|
|
119
74
|
const result = {
|
|
@@ -143,7 +98,6 @@ function validateOutput(featurePath, outputFile) {
|
|
|
143
98
|
return result;
|
|
144
99
|
}
|
|
145
100
|
|
|
146
|
-
// Skip section checks for files without defined sections
|
|
147
101
|
const sections = OUTPUT_SECTIONS[outputFile];
|
|
148
102
|
if (!sections) {
|
|
149
103
|
return result;
|
|
@@ -151,7 +105,6 @@ function validateOutput(featurePath, outputFile) {
|
|
|
151
105
|
|
|
152
106
|
validateMarkdownSections(content, sections, result);
|
|
153
107
|
|
|
154
|
-
// Size warnings
|
|
155
108
|
if (result.lineCount < 10) {
|
|
156
109
|
result.warnings.push(`File is very short (${result.lineCount} lines) — may be incomplete`);
|
|
157
110
|
}
|
|
@@ -159,19 +112,17 @@ function validateOutput(featurePath, outputFile) {
|
|
|
159
112
|
return result;
|
|
160
113
|
}
|
|
161
114
|
|
|
162
|
-
|
|
163
|
-
* plan.md is judged by the same parser and rules as `morph-spec plan-check`.
|
|
164
|
-
* `planDir` is `.morph/features/{f}/2-plan`, so the project is four levels up.
|
|
165
|
-
*/
|
|
115
|
+
// `planDir` is `.morph/features/{f}/2-plan`, so the project is four levels up.
|
|
166
116
|
function validatePlanOutput(content, planDir, result) {
|
|
167
117
|
const plan = parsePlan(content);
|
|
168
|
-
const
|
|
118
|
+
const root = path.resolve(planDir, '..', '..', '..', '..');
|
|
119
|
+
const feature = path.basename(path.resolve(planDir, '..'));
|
|
120
|
+
const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(root), ...planRuleOptions(root, feature) });
|
|
169
121
|
result.errors.push(...errors);
|
|
170
122
|
result.warnings.push(...warnings);
|
|
171
123
|
result.sections.found.push(...plan.sections.filter((x) => x.key).map((x) => x.name));
|
|
172
124
|
}
|
|
173
125
|
|
|
174
|
-
/** checks.json must parse and carry the hash of the plan.md beside it. */
|
|
175
126
|
function validateChecksOutput(content, planDir, result) {
|
|
176
127
|
let doc;
|
|
177
128
|
try {
|
|
@@ -189,16 +140,6 @@ function validateChecksOutput(content, planDir, result) {
|
|
|
189
140
|
result.sections.found.push(`${doc.checks?.length ?? 0} check(s), ${doc.slices?.length ?? 0} slice(s)`);
|
|
190
141
|
}
|
|
191
142
|
|
|
192
|
-
/**
|
|
193
|
-
* Validate markdown sections against section markers (headings + bold labels).
|
|
194
|
-
*
|
|
195
|
-
* Each `entry` is a string or array of acceptable aliases (any-of). A section
|
|
196
|
-
* is present when at least one alias is a substring of at least one marker.
|
|
197
|
-
*
|
|
198
|
-
* required → missing produces an ERROR
|
|
199
|
-
* recommended → missing produces a WARNING
|
|
200
|
-
* optional → tracked when present, never penalized
|
|
201
|
-
*/
|
|
202
143
|
function validateMarkdownSections(content, sections, result) {
|
|
203
144
|
const markers = extractSectionMarkers(content);
|
|
204
145
|
const match = (entry) => {
|
|
@@ -234,13 +175,6 @@ function validateMarkdownSections(content, sections, result) {
|
|
|
234
175
|
}
|
|
235
176
|
}
|
|
236
177
|
|
|
237
|
-
/**
|
|
238
|
-
* Resolve the list of `{ file, dir }` outputs to validate.
|
|
239
|
-
*
|
|
240
|
-
* @param {string} featurePath Absolute path to the feature directory.
|
|
241
|
-
* @param {string|null} phase Phase key, or null to validate everything.
|
|
242
|
-
* @returns {{ outputFiles?: Array<{file:string, dir:string}>, error?: string }}
|
|
243
|
-
*/
|
|
244
178
|
function resolveOutputFiles(featurePath, phase) {
|
|
245
179
|
if (phase) {
|
|
246
180
|
const files = PHASE_OUTPUTS[phase];
|
|
@@ -250,7 +184,6 @@ function resolveOutputFiles(featurePath, phase) {
|
|
|
250
184
|
return { outputFiles: files.map(file => ({ file, dir: searchPath })) };
|
|
251
185
|
}
|
|
252
186
|
|
|
253
|
-
// Validate every existing output across the feature root + phase folders.
|
|
254
187
|
const outputFiles = [];
|
|
255
188
|
const isOutput = name => name.endsWith('.md') || name.endsWith('.cs') || name === 'checks.json';
|
|
256
189
|
const entries = fs.readdirSync(featurePath, { withFileTypes: true });
|
|
@@ -267,17 +200,7 @@ function resolveOutputFiles(featurePath, phase) {
|
|
|
267
200
|
return { outputFiles };
|
|
268
201
|
}
|
|
269
202
|
|
|
270
|
-
/**
|
|
271
|
-
* Pure validation core — no console output, no process.exit.
|
|
272
|
-
*
|
|
273
|
-
* @param {string} featurePath Absolute path to the feature directory.
|
|
274
|
-
* @param {{ phase?: string }} [options]
|
|
275
|
-
* @returns {{
|
|
276
|
-
* feature: string, phase: string, valid: boolean, error?: string,
|
|
277
|
-
* totalErrors: number, totalWarnings: number, totalFound: number,
|
|
278
|
-
* results: object[]
|
|
279
|
-
* }}
|
|
280
|
-
*/
|
|
203
|
+
/** Pure core: no console output, no process.exit. */
|
|
281
204
|
export function runFeatureValidation(featurePath, options = {}) {
|
|
282
205
|
const phase = options.phase || null;
|
|
283
206
|
const feature = path.basename(featurePath);
|
|
@@ -312,9 +235,6 @@ export function runFeatureValidation(featurePath, options = {}) {
|
|
|
312
235
|
};
|
|
313
236
|
}
|
|
314
237
|
|
|
315
|
-
/**
|
|
316
|
-
* Main validate command (CLI wrapper around `runFeatureValidation`)
|
|
317
|
-
*/
|
|
318
238
|
export async function validateFeatureCommand(featureName, options = {}) {
|
|
319
239
|
if (!featureName) {
|
|
320
240
|
console.error(chalk.red('Error: Feature name required'));
|
|
@@ -340,7 +260,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
|
|
|
340
260
|
const { results, totalErrors, totalWarnings, totalFound } = validation;
|
|
341
261
|
const schemaWarnings = [];
|
|
342
262
|
|
|
343
|
-
// JSON output mode
|
|
344
263
|
if (options.json) {
|
|
345
264
|
const output = {
|
|
346
265
|
feature: featureName,
|
|
@@ -367,7 +286,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
|
|
|
367
286
|
return;
|
|
368
287
|
}
|
|
369
288
|
|
|
370
|
-
// Visual output
|
|
371
289
|
console.log(chalk.cyan('\n┌────────────────────────────────────────────────────────────┐'));
|
|
372
290
|
console.log(chalk.cyan('│ MORPH-SPEC FEATURE VALIDATOR │'));
|
|
373
291
|
console.log(chalk.cyan('└────────────────────────────────────────────────────────────┘\n'));
|
|
@@ -376,7 +294,6 @@ export async function validateFeatureCommand(featureName, options = {}) {
|
|
|
376
294
|
if (phase) console.log(chalk.gray(`Phase: ${phase}`));
|
|
377
295
|
console.log('');
|
|
378
296
|
|
|
379
|
-
// File-by-file results
|
|
380
297
|
for (const result of results) {
|
|
381
298
|
const symbol = result.exists
|
|
382
299
|
? (result.errors.length === 0 ? '✅' : '❌')
|
|
@@ -387,25 +304,21 @@ export async function validateFeatureCommand(featureName, options = {}) {
|
|
|
387
304
|
|
|
388
305
|
console.log(`${symbol} ${name}${result.exists ? chalk.gray(` (${result.lineCount} lines)`) : ''}`);
|
|
389
306
|
|
|
390
|
-
// Sections found
|
|
391
307
|
if (result.sections.found.length > 0 && (options.verbose || result.errors.length > 0)) {
|
|
392
308
|
result.sections.found.forEach(s => {
|
|
393
309
|
console.log(chalk.green(` ✓ ${s}`));
|
|
394
310
|
});
|
|
395
311
|
}
|
|
396
312
|
|
|
397
|
-
// Errors
|
|
398
313
|
result.errors.forEach(err => {
|
|
399
314
|
console.log(chalk.red(` ✗ ${err}`));
|
|
400
315
|
});
|
|
401
316
|
|
|
402
|
-
// Warnings
|
|
403
317
|
result.warnings.forEach(warn => {
|
|
404
318
|
console.log(chalk.yellow(` ⚠ ${warn}`));
|
|
405
319
|
});
|
|
406
320
|
}
|
|
407
321
|
|
|
408
|
-
// Summary
|
|
409
322
|
console.log('');
|
|
410
323
|
console.log(chalk.gray('─'.repeat(60)));
|
|
411
324
|
|