@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
package/src/lib/plan/checks.js
CHANGED
|
@@ -3,25 +3,21 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
3
3
|
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
4
4
|
import { frozenBody } from '../../../framework/hooks/shared/errata.js';
|
|
5
5
|
import { readAgentsJson } from '../specs/screen-checklist.js';
|
|
6
|
+
import { getFeature } from '../../../framework/hooks/shared/state-reader.js';
|
|
6
7
|
import { findArtifactTerms } from './artifact-vocabulary.js';
|
|
7
|
-
import { PROOF_KINDS, REUSE_VERDICTS, ADR_SCOPES, SECTIONS, CONFLICTS_HEADING } from './plan-parser.js';
|
|
8
|
+
import { PROOF_KINDS, RISK_KINDS, REUSE_VERDICTS, ADR_SCOPES, SECTIONS, CONFLICTS_HEADING } from './plan-parser.js';
|
|
8
9
|
|
|
9
10
|
export const CHECKS_VERSION = 1;
|
|
10
11
|
export const MAX_SLICE_FILES = 8;
|
|
11
12
|
export const MAX_SLICES = 12;
|
|
12
13
|
export const MAX_PARALLEL = 4;
|
|
13
14
|
|
|
14
|
-
/** sha256 of the plan without its errata: appending a correction never invalidates checks.
|
|
15
|
+
/** sha256 of the plan without its errata: appending a correction never invalidates the extracted checks. */
|
|
15
16
|
export function planHash(text) {
|
|
16
17
|
return createHash('sha256').update(frozenBody(text), 'utf8').digest('hex');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* Persona ids the project can dispatch: the installed agents.json, else the package's.
|
|
21
|
-
*
|
|
22
|
-
* @param {string} cwd
|
|
23
|
-
* @returns {string[]|null} null when no agents.json is readable — personas then go unchecked
|
|
24
|
-
*/
|
|
20
|
+
/** The installed agents.json, else the package's; null when unreadable (personas then go unchecked). */
|
|
25
21
|
export function knownPersonas(cwd) {
|
|
26
22
|
const agents = readAgentsJson(cwd)?.agents;
|
|
27
23
|
return agents && typeof agents === 'object' ? Object.keys(agents) : null;
|
|
@@ -54,14 +50,8 @@ export function dependencyCycle(slices) {
|
|
|
54
50
|
|
|
55
51
|
const fileKey = (f) => f.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* after every slice it depends on ran in an earlier one, and never with a
|
|
60
|
-
* slice that touches one of its files. At most MAX_PARALLEL per wave.
|
|
61
|
-
*
|
|
62
|
-
* @param {{ id: string, depends?: string[], files?: string[] }[]} slices
|
|
63
|
-
* @returns {string[][]} empty when the dependencies have a cycle
|
|
64
|
-
*/
|
|
53
|
+
// A slice joins a wave only after its dependencies ran, never with one touching its files, and at most
|
|
54
|
+
// MAX_PARALLEL per wave. Empty on a dependency cycle.
|
|
65
55
|
export function parallelGroups(slices) {
|
|
66
56
|
if (dependencyCycle(slices)) return [];
|
|
67
57
|
const ids = new Set(slices.map((s) => s.id));
|
|
@@ -94,14 +84,19 @@ export function parallelGroups(slices) {
|
|
|
94
84
|
return groups;
|
|
95
85
|
}
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
// A frozen body already extracted under older rules cannot gain a `risco:` line, so it is not demanded.
|
|
88
|
+
export function planRuleOptions(cwd, featureName) {
|
|
89
|
+
return {
|
|
90
|
+
workType: getFeature(featureName, cwd)?.workType ?? null,
|
|
91
|
+
riskRules: readPlanChecks(cwd, featureName).status !== 'fresh',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const foldRisk = (s) => s.normalize('NFD').replace(/\p{M}/gu, '').replace(/`/g, '').toLowerCase().trim();
|
|
96
|
+
const RISK_BY_FOLD = new Map(RISK_KINDS.map((r) => [foldRisk(r), r]));
|
|
97
|
+
const REGRESSION_WORK_TYPES = ['bug', 'hotfix'];
|
|
98
|
+
|
|
99
|
+
export function validatePlan(plan, { personas = null, surfaces = null, workType = null, riskRules = true } = {}) {
|
|
105
100
|
const errors = [];
|
|
106
101
|
const warnings = [];
|
|
107
102
|
const at = (line) => (line ? ` (linha ${line})` : '');
|
|
@@ -132,12 +127,26 @@ export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
|
|
|
132
127
|
} else if (!PROOF_KINDS.includes(c.proof.toLowerCase())) {
|
|
133
128
|
errors.push(`${c.id}: prova "${c.proof}" fora de ${PROOF_KINDS.join(' | ')}${at(c.line)}`);
|
|
134
129
|
}
|
|
130
|
+
const proof = c.proof?.toLowerCase();
|
|
131
|
+
const risk = c.risk === null ? null : RISK_BY_FOLD.get(foldRisk(c.risk)) ?? false;
|
|
132
|
+
if (risk === false) {
|
|
133
|
+
errors.push(`${c.id}: risco "${c.risk}" fora de ${RISK_KINDS.join(' | ')}${at(c.line)}`);
|
|
134
|
+
} else if (riskRules && proof === 'teste' && risk === null) {
|
|
135
|
+
errors.push(`${c.id}: "prova: teste" sem "risco:" (${RISK_KINDS.join(' | ')}) — sem risco, a prova é "leitura" ou "e2e"${at(c.line)}`);
|
|
136
|
+
} else if (risk && proof === 'leitura') {
|
|
137
|
+
warnings.push(`${c.id}: risco "${risk}" provado só por leitura${at(c.line)}`);
|
|
138
|
+
}
|
|
135
139
|
const terms = findArtifactTerms(c.text);
|
|
136
140
|
if (terms.length > 0 && !c.artifactWhy) {
|
|
137
141
|
errors.push(`${c.id} descreve artefato ("${terms.join(', ')}") sem "> Por que artefato:" — reescreva como efeito observável ou justifique${at(c.line)}`);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
const isRegression = (c) => ['teste', 'e2e'].includes(c.proof?.toLowerCase()) && c.risk !== null && RISK_BY_FOLD.get(foldRisk(c.risk)) === 'regressão';
|
|
146
|
+
if (riskRules && REGRESSION_WORK_TYPES.includes(workType) && plan.scenarios.length > 0 && !plan.scenarios.some(isRegression)) {
|
|
147
|
+
errors.push(`${workType} sem cenário de regressão — o sintoma do bug real vira um Cn com "prova: teste" e "risco: regressão"`);
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
const scenarioIds = new Set(plan.scenarios.map((c) => c.id));
|
|
142
151
|
const sliceIds = new Set(plan.slices.map((s) => s.id));
|
|
143
152
|
const covered = new Set();
|
|
@@ -184,7 +193,6 @@ export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
|
|
|
184
193
|
return { errors, warnings };
|
|
185
194
|
}
|
|
186
195
|
|
|
187
|
-
/** The checks.json document for a parsed plan. */
|
|
188
196
|
export function buildChecks(feature, text, plan) {
|
|
189
197
|
return {
|
|
190
198
|
version: CHECKS_VERSION,
|
|
@@ -202,14 +210,7 @@ export function buildChecks(feature, text, plan) {
|
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
/**
|
|
206
|
-
* The feature's extracted checks and whether they still describe the plan on
|
|
207
|
-
* disk. plan-check writes them only with zero errors, so `fresh` also means
|
|
208
|
-
* the plan passed every plan-check rule (no slice over MAX_SLICE_FILES, every
|
|
209
|
-
* scenario covered).
|
|
210
|
-
*
|
|
211
|
-
* @returns {{ status: 'fresh'|'no-plan'|'no-checks'|'unreadable'|'stale', checks: object|null }}
|
|
212
|
-
*/
|
|
213
|
+
/** plan-check writes the checks only with zero errors, so `fresh` also means the plan passed every rule. */
|
|
213
214
|
export function readPlanChecks(cwd, featureName) {
|
|
214
215
|
const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
|
|
215
216
|
const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
|
|
@@ -2,7 +2,8 @@ import { tableCells, isTableLine, SEPARATOR_ROW_RE } from '../specs/gfm-table.js
|
|
|
2
2
|
import { ARTIFACT_MARKER_RE } from './artifact-vocabulary.js';
|
|
3
3
|
import { markdownLines } from '../../../framework/hooks/shared/errata.js';
|
|
4
4
|
|
|
5
|
-
export const PROOF_KINDS = ['teste', 'e2e', 'eval', 'manual'];
|
|
5
|
+
export const PROOF_KINDS = ['teste', 'e2e', 'eval', 'manual', 'leitura'];
|
|
6
|
+
export const RISK_KINDS = ['regra', 'dinheiro', 'contrato', 'concorrência', 'dado', 'regressão'];
|
|
6
7
|
export const REUSE_VERDICTS = ['REUSAR', 'ESTENDER', 'CRIAR'];
|
|
7
8
|
export const ADR_SCOPES = ['feature', 'projeto'];
|
|
8
9
|
|
|
@@ -36,6 +37,7 @@ const SCENARIO_RE = new RegExp(String.raw`^[-*]\s+\*\*(C\d+)\*\*${DASH}(.+)$`);
|
|
|
36
37
|
const ADR_RE = new RegExp(String.raw`^###\s+(ADR-\d+)${DASH}(.+?)\s*$`);
|
|
37
38
|
const SLICE_RE = new RegExp(String.raw`^###\s+(F\d+)${DASH}(.+?)\s*$`);
|
|
38
39
|
const PROOF_RE = /^\s*prova\s*:\s*(.*?)\s*$/i;
|
|
40
|
+
const RISK_RE = /^\s*risco\s*:\s*(.*?)\s*$/i;
|
|
39
41
|
const WHY_RE = new RegExp(String.raw`^\s*>\s*` + ARTIFACT_MARKER_RE.source + String.raw`\s*(.*?)\s*$`, 'i');
|
|
40
42
|
const SCOPE_RE = /^\s*\*\*Alcance:\*\*\s*(.*?)\s*$/i;
|
|
41
43
|
const SLICE_LINE_RE = /^\s*[-*]\s+([\p{L}]+)\s*:\s*(.*?)\s*$/u;
|
|
@@ -69,7 +71,7 @@ function parseScenarios(lines) {
|
|
|
69
71
|
if (line.fenced) { current = null; continue; }
|
|
70
72
|
const m = SCENARIO_RE.exec(line.raw);
|
|
71
73
|
if (m) {
|
|
72
|
-
current = { id: m[1], text: m[2].trim(), proof: null, artifactWhy: null, line: line.n };
|
|
74
|
+
current = { id: m[1], text: m[2].trim(), proof: null, risk: null, artifactWhy: null, line: line.n };
|
|
73
75
|
scenarios.push(current);
|
|
74
76
|
continue;
|
|
75
77
|
}
|
|
@@ -79,6 +81,8 @@ function parseScenarios(lines) {
|
|
|
79
81
|
if (why) { current.artifactWhy = why[1] || ''; continue; }
|
|
80
82
|
const proof = PROOF_RE.exec(line.raw);
|
|
81
83
|
if (proof && current.proof === null) { current.proof = proof[1]; continue; }
|
|
84
|
+
const risk = RISK_RE.exec(line.raw);
|
|
85
|
+
if (risk && current.risk === null) { current.risk = risk[1]; continue; }
|
|
82
86
|
if (/^\s*>/.test(line.raw)) continue;
|
|
83
87
|
current.text = `${current.text} ${line.raw.trim()}`;
|
|
84
88
|
}
|
|
@@ -175,12 +179,7 @@ function parseSlices(lines) {
|
|
|
175
179
|
return slices;
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
/**
|
|
179
|
-
* Parse `2-plan/plan.md` into its structure. Pure: no validation, no I/O —
|
|
180
|
-
* `checks.js` judges what this returns.
|
|
181
|
-
*
|
|
182
|
-
* @param {string} text
|
|
183
|
-
*/
|
|
182
|
+
/** Pure: no validation, no I/O; `checks.js` judges what this returns. */
|
|
184
183
|
export function parsePlan(text) {
|
|
185
184
|
const lines = markdownLines(text);
|
|
186
185
|
const sections = splitSections(lines);
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* portfolio reads. Nothing else records it: each project only knows its own
|
|
4
|
-
* `.morph/`.
|
|
5
|
-
*
|
|
6
|
-
* Three writers register a project as a side note of what they already do —
|
|
7
|
-
* `init`, `update` (never under `--dry-run`) and `dashboard` — and the
|
|
8
|
-
* dashboard adds and removes on request. Registering is fail-open: a registry
|
|
9
|
-
* that cannot be read or written never fails the command that tried.
|
|
10
|
-
*
|
|
11
|
-
* The file lives in the user's home (`~/.morph-spec/projects.json`, or
|
|
12
|
-
* `MORPH_PROJECTS_REGISTRY`), read through `os.homedir()` at call time so a
|
|
13
|
-
* suite that isolates the home also isolates this file. Paths under the
|
|
14
|
-
* system temp dir are never registered: the test suites run `init`/`update`
|
|
15
|
-
* there by the hundred, including through spawned CLIs that keep the real home.
|
|
16
|
-
*/
|
|
1
|
+
// Paths under the system temp dir are never registered: test suites run `init`/`update` there by the
|
|
2
|
+
// hundred, through spawned CLIs that keep the real home. The home is read at call time so isolating it works.
|
|
17
3
|
|
|
18
4
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
19
5
|
import { homedir, tmpdir } from 'os';
|
|
@@ -35,7 +21,6 @@ export function projectKey(dir) {
|
|
|
35
21
|
|
|
36
22
|
const sameDir = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
37
23
|
|
|
38
|
-
/** A folder is a morph-spec project when morph-spec installed its config there. */
|
|
39
24
|
export function isMorphProject(dir) {
|
|
40
25
|
return existsSync(join(dir, '.morph', 'config', 'config.json'));
|
|
41
26
|
}
|
|
@@ -61,18 +46,13 @@ function projectName(dir) {
|
|
|
61
46
|
}
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
/**
|
|
65
|
-
* The registered projects that still exist. A folder that was deleted or lost
|
|
66
|
-
* its `.morph/` drops out on read — the file is rewritten on the next write.
|
|
67
|
-
*
|
|
68
|
-
* @returns {{ version: number, projects: Array<{ dir: string, name: string, addedAt: string, lastSeenAt: string, source: string }> }}
|
|
69
|
-
*/
|
|
49
|
+
/** Folders that were deleted or lost `.morph/` drop out on read. */
|
|
70
50
|
export function readRegistry() {
|
|
71
51
|
let data = null;
|
|
72
52
|
try {
|
|
73
53
|
const path = registryPath();
|
|
74
54
|
if (existsSync(path)) data = JSON.parse(readFileSync(path, 'utf8'));
|
|
75
|
-
} catch {
|
|
55
|
+
} catch {}
|
|
76
56
|
const projects = Array.isArray(data?.projects) ? data.projects : [];
|
|
77
57
|
return {
|
|
78
58
|
version: REGISTRY_VERSION,
|
|
@@ -84,16 +64,7 @@ function write(projects) {
|
|
|
84
64
|
atomicWriteJson(registryPath(), { version: REGISTRY_VERSION, projects });
|
|
85
65
|
}
|
|
86
66
|
|
|
87
|
-
/**
|
|
88
|
-
* Note that `dir` is a morph-spec project. Idempotent; refreshes `lastSeenAt`.
|
|
89
|
-
* Never throws.
|
|
90
|
-
*
|
|
91
|
-
* @param {string} dir
|
|
92
|
-
* @param {'init'|'update'|'dashboard'|'manual'} source
|
|
93
|
-
* @param {{ tempRoot?: string }} [opts] - test seam: the folder whose
|
|
94
|
-
* projects are never registered (the system temp dir)
|
|
95
|
-
* @returns {boolean} true when the project is in the registry afterwards
|
|
96
|
-
*/
|
|
67
|
+
/** Idempotent, never throws; `tempRoot` is the test seam for the never-registered folder. */
|
|
97
68
|
export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
|
|
98
69
|
try {
|
|
99
70
|
const key = projectKey(dir);
|
|
@@ -115,7 +86,7 @@ export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
|
|
|
115
86
|
}
|
|
116
87
|
}
|
|
117
88
|
|
|
118
|
-
/**
|
|
89
|
+
/** Never throws; the project's files are not touched. */
|
|
119
90
|
export function unregisterProject(dir) {
|
|
120
91
|
try {
|
|
121
92
|
const key = projectKey(dir);
|
|
@@ -128,15 +99,7 @@ export function unregisterProject(dir) {
|
|
|
128
99
|
}
|
|
129
100
|
}
|
|
130
101
|
|
|
131
|
-
/**
|
|
132
|
-
* morph-spec projects directly under each root that are not registered yet —
|
|
133
|
-
* the portfolio offers them, the person decides. One level deep: a repos
|
|
134
|
-
* folder is the expected root, and a deeper walk would enter node_modules and
|
|
135
|
-
* worktrees for nothing.
|
|
136
|
-
*
|
|
137
|
-
* @param {string[]} roots
|
|
138
|
-
* @returns {Array<{ dir: string, name: string }>}
|
|
139
|
-
*/
|
|
102
|
+
/** Unregistered projects one level under each root: deeper would walk node_modules and worktrees. */
|
|
140
103
|
export function discoverProjects(roots) {
|
|
141
104
|
const registered = readRegistry().projects.map((p) => p.dir);
|
|
142
105
|
const out = [];
|
|
@@ -27,10 +27,11 @@ const CHECKS_SECTION_RE = sectionRe('Checks');
|
|
|
27
27
|
const MEDIA_SECTION_RE = /^##\s+M[íi]dia\s+gerada\s*$/m;
|
|
28
28
|
const SCREEN_CHECKLIST_SECTION_RE = sectionRe('Checklist\\s+de\\s+tela');
|
|
29
29
|
|
|
30
|
-
/** Hotfix: só o verificador, escrito pelo orquestrador sem dispatch. */
|
|
30
|
+
/** Hotfix: só o verificador, escrito pelo orquestrador sem dispatch. Chore: só o revisor (review leve). */
|
|
31
31
|
export function requiredReviewRoles({ workType, securityRequired = false } = {}) {
|
|
32
32
|
if (workType === 'hotfix') return ['verifier'];
|
|
33
|
-
|
|
33
|
+
const base = workType === 'chore' ? ['reviewer'] : ['verifier', 'reviewer'];
|
|
34
|
+
return securityRequired ? [...base, 'security'] : base;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function sectionBodies(text, re) {
|
|
@@ -60,7 +61,7 @@ function tableRows(body) {
|
|
|
60
61
|
return rows;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
// `| id | tipo | onde | evidência |`; `untyped` = tipo fora de BLOCKER_TYPES.
|
|
64
65
|
function parseBlockers(body) {
|
|
65
66
|
const blockers = [];
|
|
66
67
|
const untyped = [];
|
|
@@ -74,10 +75,7 @@ function parseBlockers(body) {
|
|
|
74
75
|
|
|
75
76
|
const STATUS_BY_KEY = Object.fromEntries(CHECK_STATUSES.map((s) => [normalize(s), s]));
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
* `| Cn | PROVADO|NÃO PROVADO|FALHA | evidência |` de TODAS as seções: uma confirmação
|
|
79
|
-
* traz só os Cn que mudou, então vale a última linha de cada Cn. Status por igualdade exata.
|
|
80
|
-
*/
|
|
78
|
+
// Vale a última linha de cada Cn em todas as seções: uma confirmação traz só os Cn que mudou.
|
|
81
79
|
function parseChecks(bodies) {
|
|
82
80
|
const byId = new Map();
|
|
83
81
|
for (const cells of bodies.flatMap(tableRows)) {
|
|
@@ -110,10 +108,7 @@ export function parseReviewReport(text) {
|
|
|
110
108
|
};
|
|
111
109
|
}
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
* `missing`: sem arquivo. `partial`: `PARCIAL` na 1ª linha, com ou sem veredito.
|
|
115
|
-
* `unverdicted`: sem `**Verdict:** SHIP|FIX`. `ok`: legível — quem decide é o risk-detector.
|
|
116
|
-
*/
|
|
111
|
+
// `partial`: `PARCIAL` na 1ª linha, com ou sem veredito. `ok` só diz legível: quem decide é o risk-detector.
|
|
117
112
|
function stateOf(path, text) {
|
|
118
113
|
if (text === null) return { status: 'missing', path };
|
|
119
114
|
const parsed = parseReviewReport(text);
|
|
@@ -148,13 +143,7 @@ export function reviewReports(cwd, featureName) {
|
|
|
148
143
|
return reviewReportsFromDir(join(cwd, '.morph', 'features', featureName));
|
|
149
144
|
}
|
|
150
145
|
|
|
151
|
-
/**
|
|
152
|
-
* Todo Cn precisa de uma linha, e só `PROVADO` cobre.
|
|
153
|
-
*
|
|
154
|
-
* @param {string[]} checkIds
|
|
155
|
-
* @param {{id: string, status: string}[]|null} rows - `parseReviewReport(...).checks`
|
|
156
|
-
* @returns {{ missing: string[], notProven: {id: string, status: string}[] }}
|
|
157
|
-
*/
|
|
146
|
+
/** Todo Cn precisa de uma linha, e só `PROVADO` cobre. */
|
|
158
147
|
export function checksCoverage(checkIds, rows) {
|
|
159
148
|
const byId = new Map((rows ?? []).map((r) => [r.id, r]));
|
|
160
149
|
const missing = [];
|
|
@@ -167,12 +156,7 @@ export function checksCoverage(checkIds, rows) {
|
|
|
167
156
|
return { missing, notProven };
|
|
168
157
|
}
|
|
169
158
|
|
|
170
|
-
/**
|
|
171
|
-
* O sinal que `reviewPause` (risk-detector) lê; `checkIds: null` = cobertura não conferível.
|
|
172
|
-
*
|
|
173
|
-
* @param {ReturnType<typeof reviewReportsFromDir>} reports
|
|
174
|
-
* @param {{ required: string[], checkIds: string[]|null }} opts
|
|
175
|
-
*/
|
|
159
|
+
/** O sinal que `reviewPause` lê; `checkIds: null` = cobertura não conferível. */
|
|
176
160
|
export function reviewSignal(reports, { required, checkIds }) {
|
|
177
161
|
const out = { required, reports: {} };
|
|
178
162
|
for (const role of ROLE_ORDER) {
|
|
@@ -189,7 +173,9 @@ export function reviewSignal(reports, { required, checkIds }) {
|
|
|
189
173
|
superseded: r.superseded,
|
|
190
174
|
};
|
|
191
175
|
}
|
|
192
|
-
out.checks =
|
|
176
|
+
out.checks = !required.includes('verifier')
|
|
177
|
+
? { status: 'not-required', missing: [], notProven: [] }
|
|
178
|
+
: checkIds === null
|
|
193
179
|
? { status: 'no-checks', missing: [], notProven: [] }
|
|
194
180
|
: { status: 'ok', total: checkIds.length, ...checksCoverage(checkIds, reports.verifier.checks) };
|
|
195
181
|
return out;
|
|
@@ -205,7 +191,6 @@ export function combinedVerdict(reports, required) {
|
|
|
205
191
|
return required.every((r) => reports[r].status === 'ok' && reports[r].verdict === 'SHIP') ? 'SHIP' : null;
|
|
206
192
|
}
|
|
207
193
|
|
|
208
|
-
/** Todos os bloqueadores tipados dos relatórios presentes, com o papel que os abriu. */
|
|
209
194
|
export function allBlockers(reports) {
|
|
210
195
|
return ROLE_ORDER.flatMap((role) => (reports[role].blockers ?? []).map((b) => ({ ...b, role })));
|
|
211
196
|
}
|