@polymorphism-tech/morph-spec 8.46.0 → 9.0.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 +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/project/validate.js +0 -97
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
package/CLAUDE.md
CHANGED
|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code when working with code in **this repo
|
|
|
4
4
|
|
|
5
5
|
## What This Is
|
|
6
6
|
|
|
7
|
-
morph-spec is a spec-driven development harness CLI (`@polymorphism-tech/morph-spec`
|
|
7
|
+
morph-spec is a spec-driven development harness CLI (`@polymorphism-tech/morph-spec`). The CLI installs the harness and owns the deterministic steps (state, gates, `plan-check`, `verify`, `report-check`, worktrees); Claude Code runs the workflow from `framework/MORPH.md` (the NLH charter) and the core skills.
|
|
8
8
|
|
|
9
9
|
This repo is the **framework source code** — not a project that uses morph-spec.
|
|
10
10
|
|
|
@@ -40,30 +40,32 @@ No build step — pure ESM JavaScript, runs directly via Node.js 18+.
|
|
|
40
40
|
|
|
41
41
|
### Module Layout
|
|
42
42
|
|
|
43
|
-
- **`bin/morph-spec.js`** — Commander.js CLI entry point
|
|
43
|
+
- **`bin/morph-spec.js`** — Commander.js CLI entry point: install/inspect commands plus the deterministic steps of the loop (`create`, `plan-check`, `gate-check`, `approve`/`advance`, `verify`, `report-check`, `mutate`, `finish`).
|
|
44
44
|
- **`src/commands/`** — CLI command handlers organized by domain (project/, state/, validation/, scaffold/, mcp/, context/, dashboard/).
|
|
45
45
|
- **`src/core/`** — Core domain logic:
|
|
46
46
|
- `state/state-manager.js` — Atomic state I/O with process-ID temp files
|
|
47
|
-
- `paths/output-schema.js` — **Single source of truth** for the
|
|
47
|
+
- `paths/output-schema.js` — **Single source of truth** for the output types (`plan`, `checks`, the `1-design/` docs, `recap`, `evidencias`, the three review reports)
|
|
48
48
|
- `templates/` — Handlebars template registry and renderer
|
|
49
49
|
- `workflows/` — Workflow type detector
|
|
50
50
|
- **`src/lib/`** — Libraries and subsystems:
|
|
51
|
-
- `validators/` —
|
|
51
|
+
- `validators/` — Validators run by the `verify` validators node (code-budget, contracts, nextjs, packages, reuse)
|
|
52
52
|
- `generators/` — Code generation (recap, context, handoff)
|
|
53
53
|
- `stack-filter.js` — Stack-aware asset filtering via YAML frontmatter `stacks:` tags
|
|
54
54
|
- `installers/` — Agents, skills, hooks installers
|
|
55
55
|
- **`src/utils/`** — Shared utilities (logger, banner, file-copier, claude-settings-manager, agents-installer, skills-installer, hooks-installer)
|
|
56
56
|
- **`framework/`** — Metadata installed into target projects (READ-ONLY at runtime):
|
|
57
|
-
- `MORPH.md` — NLH charter (the single source of behavioral truth)
|
|
58
|
-
- `CLAUDE.md` — Runtime instructions injected into target's `.claude/CLAUDE.md`
|
|
59
|
-
- `agents.json`
|
|
60
|
-
- `phases.json` —
|
|
57
|
+
- `MORPH.md` — NLH charter (the single source of behavioral truth; ≤ 20k chars, `resident-size` canary)
|
|
58
|
+
- `CLAUDE.md` — Runtime instructions injected into target's `.claude/CLAUDE.md` (≤ 4k chars)
|
|
59
|
+
- `agents.json` — 10 flat personas (see below); `maxTurns` per persona (ADR-004)
|
|
60
|
+
- `phases.json` — 4 phases (plan, uiux, implement, review)
|
|
61
61
|
- `standards/STANDARDS.json` — index of available standards
|
|
62
|
-
- `hooks/claude-code/core/` —
|
|
63
|
-
- `skills/core/` —
|
|
62
|
+
- `hooks/claude-code/core/` — fail-open guardrail hooks (+ `hooks/shared/` for what hooks and `src/` both read)
|
|
63
|
+
- `skills/core/` — core NLH skills (single source of truth; listed on the `**Skills core**` line of MORPH.md §7)
|
|
64
|
+
- `rules/` — path-scoped stack rules only (`paths:` frontmatter); no always-on rule
|
|
65
|
+
- `docs/CLI.md` — the CLI reference, one `###` per command, installed as `.morph/framework/CLI.md`
|
|
64
66
|
- `templates/` — Handlebars templates for code generation
|
|
65
67
|
- `mcp/registry.json` — MCP server registry
|
|
66
|
-
- `evals/review-checklist.md` — o que
|
|
68
|
+
- `evals/review-checklist.md` — o que os agentes do Gate 3 procuram (veredito SHIP/FIX + bloqueador com evidência)
|
|
67
69
|
|
|
68
70
|
### Key Architectural Patterns
|
|
69
71
|
|
|
@@ -77,19 +79,19 @@ No build step — pure ESM JavaScript, runs directly via Node.js 18+.
|
|
|
77
79
|
|
|
78
80
|
**Stack-aware filtering:** Assets declare `stacks: ['dotnet']` in YAML frontmatter or `stack: ['dotnet']` in `agents.json`. `setup-infra.js` reads `config.json → project.stack`, splits into tags, and installs only matching assets.
|
|
79
81
|
|
|
80
|
-
**Personas, not tiers:** `agents.json` has
|
|
82
|
+
**Personas, not tiers:** `agents.json` has 10 flat personas — 6 domain implementers (dotnet-senior, ef-modeler, maf-expert, nextjs-expert, ui-designer, infra-engineer), the 3 read-only Gate 3 roles (verifier, reviewer, security) and 1 stage persona (`scout`, read-only). `model` is sized per stage: `sonnet` for implementers, `scout`, `verifier` and `reviewer`; `security` is `opus`; the dispatch routes the verifier to `opus` when `verifierModelFor` (risk-detector.js, read by `report-check --before-eval`) sees prompt/model or media (measured against haiku in `docs/specs/scout-evidence/decisions.md`). No tier system — Claude assumes the 3 NLH roles (Orchestrator/Specialist/Evaluator) per MORPH.md.
|
|
81
83
|
|
|
82
84
|
### Phase Sequence
|
|
83
85
|
|
|
84
86
|
```
|
|
85
|
-
|
|
87
|
+
plan → [uiux] → implement → review
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
Gates: `plan` (plan.md + `checks.json` from `plan-check`), `uiux` (optional, after the plan gate, `1-design/`), `review` (Gate 3: `verifier.md` + `reviewer.md` [+ `security.md`]). Folders keep their ids: `2-plan/`, `1-design/`, `3-implement/`, `4-review/`. `chore`/`hotfix` are born with `plan` stamped by policy. Implementation unit: the slices `F1..Fn` of plan.md; progress = `3-implement/reports/F{n}.md`.
|
|
89
91
|
|
|
90
|
-
### State Schema
|
|
92
|
+
### State Schema
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
Authoritative, committed state: `.morph/features/{f}/feature.json` (status, approvalGates, gateDecisions, outputs, verification), shape in `framework/schemas/feature.schema.json`. `.morph/state.json` is a thin gitignored index rebuilt on load. Phase is derived from the filesystem and the gates, never stored.
|
|
93
95
|
|
|
94
96
|
## Testing Conventions
|
|
95
97
|
|
|
@@ -116,15 +118,16 @@ When modifying framework components, verify the full scope of related files.
|
|
|
116
118
|
|
|
117
119
|
| When you modify... | Also verify... |
|
|
118
120
|
|----------------------------------|-------------------------------------------------------------------------------------------|
|
|
119
|
-
| `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS). **`maxTurns` (ADR-004):** UMA régua de validade, `maxTurnsError` em `agents-installer.js`, com QUATRO leitores — `installAgents` (recusa antes de escrever), `checkAgentsDrift` (`invalid`), `validateAgentsJson` do `doctor` (error) e o Check 6 do `validate-framework.js` (`test/framework/validate-framework-agents.test.js`); uma segunda noção de "valor válido" faz o mesmo arquivo passar num e ser recusado no outro. Sem o campo, o frontmatter sai byte a byte igual ao de antes (canário COMPAT). O corpo gerado anuncia o teto ao sub-agent, e a cláusula 5 dos templates de dispatch depende disso. O drift lê o registry MCP do PACOTE (`mcpRegistryDir`): a cópia instalada não tem `mcp/`, e ler dela dava toda persona com `mcp` como stale numa instalação limpa. O marcador `PARCIAL` (1ª linha) que
|
|
120
|
-
| `framework/standards/**` | STANDARDS.json entry exists (o **digest** e o que `standards --find` devolve — corrigir a prosa e esquecer o digest e o caso mais comum), agents reference standard, .md file exists. **Bloco de codigo `csharp` num standard e afirmacao verificavel:** extraia com `node scripts/extract-doc-csharp.mjs <std.md> "## Secao" out.cs` e compile numa sonda com o pin + `TreatWarningsAsErrors` (sem ele `MEAI001` vira aviso e o build fica verde sobre codigo que o consumidor nao compila). Medido: um exemplo carimbado sem compilar tinha 3 erros independentes |
|
|
121
|
+
| `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS). **`maxTurns` (ADR-004):** UMA régua de validade, `maxTurnsError` em `agents-installer.js`, com QUATRO leitores — `installAgents` (recusa antes de escrever), `checkAgentsDrift` (`invalid`), `validateAgentsJson` do `doctor` (error) e o Check 6 do `validate-framework.js` (`test/framework/validate-framework-agents.test.js`); uma segunda noção de "valor válido" faz o mesmo arquivo passar num e ser recusado no outro. Sem o campo, o frontmatter sai byte a byte igual ao de antes (canário COMPAT). O corpo gerado anuncia o teto ao sub-agent, e a cláusula 5 dos templates de dispatch depende disso. O drift lê o registry MCP do PACOTE (`mcpRegistryDir`): a cópia instalada não tem `mcp/`, e ler dela dava toda persona com `mcp` como stale numa instalação limpa. O marcador `PARCIAL` (1ª linha) que o corpo da persona manda gravar é lido por `PARTIAL_MARKER` de `framework/hooks/shared/slice-progress.js` (no `report-check`: `report-incomplete`, ação `resume`; no progresso: fatia `parcial`): mudar a grafia num lado só devolve o parcial lido como `ok`. **`effort`:** mesma régua única em quatro leitores (`effortError`); ausente, o frontmatter não ganha linha e o sub-agent herda o effort da sessão (COMPAT intacto). Implementadores e `scout` `medium`; `verifier`, `reviewer` e `security` sem o campo — o Gate 3 não é barateado. `reviewer`/`security` são read-only sobre o SHA (`git diff {base}..{sha}`, nunca a árvore): o corpo da persona diz isso, e é o que torna seguro o paralelo com a mutação do verificador (F46). **`surfaces` (ADR-005):** mesma régua única em quatro leitores (`surfacesError`), e `personaStandardIds` é a leitura única de "todo standard da persona" para o Check 1, o drift e os scanners do dashboard (`agents-scanner.js`, `standards-scanner.js`) — iterar só `standards` deixa id de superfície sem validar e a aresta do grafo sumida. Sem o campo, o `.md` sai byte a byte o de antes: `test/fixtures/agents/ui-designer-5.3.0.{json,md}` é a persona do 5.3.0 e o render do installer de main, comparados com `strictEqual` — nunca regere a fixture para fazer o teste passar. A chave da superfície é a que o checklist de tela do `morph-uiux` escreve em `Superfície` e que o `morph-plan` usa para somar a lista em `standards:` da fatia. **Forma do corpo (`buildBody`):** só a persona no formato do 5.3.0 — implementadora (Write/Edit), sem `effort` e sem `surfaces` (`legacyShape`) — mantém `## Domains` e a lista de standards sem a linha de busca; no `agents.json` atual NENHUMA cai nesse caso (todo implementador tem `effort`; `verifier`/`reviewer`/`security` são read-only), e o formato vive pela fixture COMPAT acima. Toda outra persona perde `## Domains`, ganha `STANDARDS_HINT` (`--find <id>` e `--show <id>#<âncora>`) no topo de `## Standards to consult on demand` e, com `neon` no `mcp` (hoje `ef-modeler` e `infra-engineer`), o aviso `NEON_CAUTION` (ensaio de migração em branch descartável; tool destrutiva só com confirmação humana). Canários: `test/utils/agents-installer.test.js` e `test/framework/maf-expert-persona.test.js` |
|
|
122
|
+
| `framework/standards/**` | STANDARDS.json entry exists (o **digest** e o que `standards --find` devolve — corrigir a prosa e esquecer o digest e o caso mais comum), e o bullet do standard na rule `framework/rules/*-standards.md` que o roteia (resumo inline; `@`-import em rule resolve relativo ao arquivo da rule e, quando resolve, carrega em TODA sessão ignorando `paths:` — medido; canário `test/framework/rules-no-imports.test.js`), agents reference standard, .md file exists. **Bloco de codigo `csharp` num standard e afirmacao verificavel:** extraia com `node scripts/extract-doc-csharp.mjs <std.md> "## Secao" out.cs` e compile numa sonda com o pin + `TreatWarningsAsErrors` (sem ele `MEAI001` vira aviso e o build fica verde sobre codigo que o consumidor nao compila). Medido: um exemplo carimbado sem compilar tinha 3 erros independentes |
|
|
121
123
|
| `isImplementer` (Check 6 de `scripts/validate-framework.js`) | toda persona com Write/Edit no escopo efetivo de tools tem de alcançar, pelos `standards`, as âncoras `comment-policy` e `test-budget` do STANDARDS.json (`IMPLEMENTER_ANCHORS`). Renomear a chave da âncora no registry derruba o `validate`, e persona implementadora nova sem os dois standards também. Installer e `doctor` não cobram isso de propósito: um `agents.json` instalado antigo continua valendo |
|
|
122
|
-
| `scripts/generate-standards-registry.js` | `framework/standards/STANDARDS.json` (REGENERE, nunca patch textual
|
|
124
|
+
| `scripts/generate-standards-registry.js` | Escreve DOIS arquivos: `framework/standards/STANDARDS.json` (o que o runtime lê: `id`, `name`, `path`, `category`, `stacks`, `tags`, `anchors`, `digest`) e o sidecar `STANDARDS.meta.json` (`aliases`, `verifiedAgainst`, `provedBy` por **id**, e `sharedAliases` no topo). REGENERE os dois, nunca patch textual. Os `aliases` do sidecar se preservam pelo id da entrada: renomear um id sem levar a chave do sidecar perde os aliases dele. `--check` compara os DOIS arquivos com o que geraria (drift em qualquer um reprova o `npm run generate:check`). As âncoras são validadas contra os headings pelo `headingSlugs` de `src/lib/standards/sections.js`, a mesma resolução do `--show`. `src/lib/standards/verified-against.js` (`parseVerifiedAgainst`/`deriveProvedBy`: `verifiedAgainst` e `provedBy` sao DERIVADOS a cada run e NAO entram em `PRESERVE_FIELDS`), `test/framework/standards-registry.test.js`. **Renomear um standard e uma PARADA, nao um exit 0**: path sumido + path novo faz o gerador recusar nomeando os dois conjuntos e prescrevendo a edicao do campo `path` da entrada antiga — ele nao adivinha o par. `MORPH_STANDARDS_BASE` e o unico seam de teste do script e nada em producao o define |
|
|
123
125
|
| `framework/hooks/claude-code/**` | hooks-installer.js MORPH_HOOKS array, hook follows fail-open pattern. O `timeout` de cada entrada é em SEGUNDOS, a unidade que o Claude Code lê — em milissegundos ele passa do default de 600 e prende a sessão por horas (canário em `test/hooks/hooks-installer.test.js`); binding `async: true` não tem timeout aplicado |
|
|
126
|
+
| `framework/hooks/claude-code/core/denylist.json` (`patterns` e `executableArgs`) | `pre-bash-denylist.js` lê os dois blocos: `patterns` por substring; `executableArgs` por TOKEN (`tokenize` de `framework/hooks/shared/command-tokens.js`) — o executável normalizado (caminho, caixa e `.exe` fora) com um dos `args` depois dele. Com `flags` (grupos, caixa respeitada, `-rf` conta), cada grupo também tem de aparecer no mesmo comando: é o que bloqueia `rm -rf /` e `rm -fr ~` e deixa passar `rm -rf /var/lib/apt/lists/*` (o `args` é token inteiro, nunca prefixo). É o que bloqueia `taskkill /IM` (com `/F` antes, `.exe` ou caminho completo) e deixa passar `taskkill /PID n` e a menção entre aspas numa mensagem de commit (argumento citado é um token só, nunca o executável). A regra é DADO de propósito: o canário de `test/commands/doctor-procs.test.js` reprova `taskkill`/`/IM` em todo código de `src/` e dos hooks, e o corpo da persona só passa pela exceção nomeada da frase de proibição. Regra nova entra em `test/hooks/pre-bash-denylist.test.js` ao lado de uma forma parecida que NÃO pode ser bloqueada |
|
|
124
127
|
| `framework/hooks/shared/telemetry-logger.js` (esquema v2 do `events.jsonl`) | os leitores do MESMO evento: `telemetry-reader.js` (`ingestedBytes`), `src/lib/telemetry/metrics.js` (`cost --metrics`), `verify-event.js`, `report-check.js`, os hooks que gravam e o `activity-scanner.js` do dashboard (lê por `loadTelemetryAllTrees` e soma só por `aggregateCost` — nunca uma segunda soma). `isFailedToolCall` é a régua única de "a chamada falhou: não leu nem mudou nada" — um segundo teste de `PostToolUseFailure` volta a contar os bytes de um Read que falhou e a edição que não entrou. Evento v1 (sem `schemaVersion`) nunca vira zero: cai em "sem agente", `unknown-*` ou "sem duração". O esquema mora em `framework/docs/CLI.md` (`cost --metrics`); campo novo aqui entra lá. Os bindings que só registram (`PostToolUseFailure`, `PostToolBatch`, `SubagentStop`) são `async: true` em `hooks-installer.js`; hook que decide nunca é async (canário em `test/hooks/hooks-installer.test.js`) |
|
|
125
128
|
| `framework/hooks/shared/command-tokens.js` (`producerOf`, `exitCodeMasked`) | `telemetry-log.js` (grava `exitMasked`), `metrics.js` (o rerun agrupa pelo comando produtor e manda o exit mascarado para `unknown-*`) e os guards de Bash que leem `tokenize`. A tabela de `test/hooks/command-tokens.test.js` é o contrato: cada forma que a cláusula 3 do dispatch manda evitar (`\| tail`, `; echo`, `\|\| true`, `&` de fundo) tem linha lá, ao lado de uma parecida que não mascara (`cd src; dotnet test`, `> log 2>&1`) |
|
|
126
129
|
| `framework/hooks/shared/redact-secrets.js` | `telemetry-logger.js` (redige os campos de texto livre, `FREE_TEXT_FIELDS`) e a lista de formas redigidas em `framework/docs/CLI.md`. Regra nova entra em `test/hooks/redact-secrets.test.js` com um caso parecido que NÃO pode ser redigido (`docker run -u 1000:1000`, "Bearer authentication", `git push origin HEAD:refs/heads/main`): regra frouxa apaga o comando que a métrica de rerun lê |
|
|
127
|
-
| `framework/skills/core/**` | MORPH.md
|
|
130
|
+
| `framework/skills/core/**` | a linha `**Skills core**` do MORPH.md §7 (canário `test/framework/morph-skills-table.test.js` e o hook local `morph-md-sync-guard`, que leem a MESMA linha: toda skill `morph-*` do disco nela, e nenhuma fantasma), skills-installer.js, SKILL.md frontmatter (a `description` carrega os gatilhos — é ela que o Claude Code mostra) |
|
|
128
131
|
| `framework/phases.json` | output-schema.js (phase mappings), test/framework/phases.test.js |
|
|
129
132
|
| `framework/templates/**` | REGISTRY.json (template registered), valid Handlebars helpers |
|
|
130
133
|
| `framework/templates/REGISTRY.json` (o campo `kind`) | os QUATRO checks de `scripts/validate-framework.js` que leem `kind`/`path`: **4** (resolve arquivo × diretório), **9** (shape exigido por kind), **10** (só lê `path` que é ARQUIVO — `readFileSync` num diretório derruba o validador inteiro com EISDIR, não vira erro de validação) e **12** (cobertura por prefixo; projeto registrado sem arquivo rastreado é erro). `test/framework/validate-framework-registry.test.js` cobra os quatro |
|
|
@@ -132,40 +135,44 @@ When modifying framework components, verify the full scope of related files.
|
|
|
132
135
|
| `framework/templates/dotnet/ai-kit/**` (o projeto .NET) | `REGISTRY.json` (a entrada `ai-kit`, `kind: "project"` — sem ela o Check 12 cobra arquivo por arquivo; com ela apontando para diretorio vazio/inexistente o validate tambem reprova), o **`.gitignore` ANINHADO em `ai-kit/`** (unico jeito de manter `bin/`/`obj/` fora do tarball: `package.json` `files` lista `framework/`, e o npm NAO deixa o `.npmignore` da RAIZ excluir nada sob um diretorio de `files` — medido, 220 arquivos vazavam), `Directory.Packages.props` + `global.json` (GERADOS de `framework/ai-pin.json`, nunca editados a mao) e o job `ai-kit` do CI (T15). O `global.json` carrega `test.runner` porque no SDK .NET 10 `dotnet test` recusa um projeto Microsoft.Testing.Platform (xunit.v3 4.x) sem esse opt-in, e global.json e o unico arquivo de onde o SDK o le. Mexeu em `PackageReference` do `Morph.AiKit.csproj`? `test/framework/ai-kit-package-coverage.test.js` exige que cada pacote esteja OU exercitado por um `using` real em `src/` OU listado na constante `RESTORE_ONLY` do teste (contrato de versao, regua = restore/NU1102) — e a mesma classificacao aparece na tabela de garantias do `README.md` do kit e na Errata E10 do `spec.md`. Desde `src/Morph.AiKit/Media/`, so **um** dos cinco pacotes do pin (`ModelContextProtocol`) segue sem cobertura de compilacao: `OpenAI` e `Google.GenAI` subiram para compilacao quando os adaptadores de midia passaram a usa-los (`using OpenAI.Images;` / `using Google.GenAI.Types;`), e o proprio canario apontou a mudanca ficando vermelho sobre `RESTORE_ONLY`. Mexer no `Media/` que remova esses `using` derruba a garantia de volta |
|
|
133
136
|
| `.github/workflows/*.yml` (o job `ai-kit`) | os DOIS workflows carregam o MESMO job — `ci.yml` (toda PR) e `release.yml`, onde `publish` declara `needs: [test, ai-kit]` e continua **sem matrix** (matrix no job que publica roda `npm publish` 1x por OS). Tres detalhes sao load-bearing: `working-directory: framework/templates/dotnet/ai-kit` (o `dotnet test` resolve o `global.json` pelo CWD, nunca pelo `.sln` do argumento — da raiz ele FALHA, enquanto o `dotnet build` funciona, que e o que torna a armadilha facil), `global-json-file` em vez de `dotnet-version` (a banda do SDK vem do pin, nunca digitada no YAML) e o passo `generate-ai-pin-props.js --check` antes do restore (sem ele o job compila props dessincronizadas do pin e reporta verde). `test/framework/ai-kit-ci-packaging.test.js` cobra a forma do job E mede o tarball |
|
|
134
137
|
| `src/lib/standards/verified-against.js` | os leitores do MESMO fato "quao velho e este standard": `scripts/check-freshness.js` (hard fail por `Last-verified`), `test/framework/standard-header.test.js` (campos obrigatorios no header) e `framework/ai-pin.json` (a data de referencia). Uma segunda copia do regex e como o contador de `// VERIFY:` ficou inerte: `/^\s*\/\/\s*VERIFY:/` exigia inicio de linha e dois-pontos e enxergava 0 de 10 marcadores reais. **`deriveProvedBy` mora aqui pela mesma razao**, e o quarto leitor e `scripts/generate-standards-registry.js`, que DERIVA `verifiedAgainst`/`provedBy` a cada run — **TRES** tipografias da clausula sao reais no acervo e a regua e a SOMA de paths (27 em 16 entradas), nunca a contagem de entradas: a crase abre DEPOIS de "provado por", ANTES dela, e a clausula tambem aparece em negrito no MEIO da frase (`**provado por**`). Ignorar a segunda perdia 4 das 16 provas; ignorar a terceira perdia 2 paths de `llm-runtime-defaults` **sem mover contador nenhum** — a entrada seguia entre as 16 e o exit era 0. Por isso `test/framework/standards-registry.test.js` pina os paths ESCRITOS A MAO, e nunca chama `deriveProvedBy` para gerar a expectativa do acervo real |
|
|
138
|
+
| `src/lib/standards/registry.js` (`loadStandardsRegistry`) | O carregador ÚNICO do registro: lê `STANDARDS.json` e junta por id os campos do sidecar (`META_FIELDS`), devolvendo `{ version, standards, sharedAliases }`. Sidecar ausente ou ilegível = sem aliases (busca pior, nunca quebra); `STANDARDS.json` ausente lança. Leitores: `src/commands/project/standards.js` (`--find` pontua aliases), `src/dashboard/scanner/standards-scanner.js`, `scripts/validate-framework.js` (Checks 1, 2 e 3 sobre `standards`; Check 11 sobre `aliases` + `sharedAliases`) e os testes (`test/lib/standards/registry.test.js`, `test/framework/standards-registry.test.js`, `test/framework/dotnet-testing-standard.test.js`). Um `JSON.parse` direto do `STANDARDS.json` num leitor novo enxerga o registro sem aliases, e o Check 11 lido assim não valida nada. `STANDARDS_FILE`/`STANDARDS_META_FILE` são a grafia única dos nomes, que o gerador importa |
|
|
139
|
+
| `src/lib/standards/sections.js` (`ghSlug`, `markdownHeadings`, `sectionBySlug`) | A resolução ÚNICA de heading → slug → seção, com os dois lados que precisam concordar: o gerador (`headingSlugs` valida cada âncora do registro) e o `standards --show <id>#<âncora>` (`resolveStandardSection` em `standards.js`: chave de `anchors` ou slug de heading, seção até o próximo heading de nível igual ou maior, cercas de código ignoradas, slug repetido com sufixo `-n`). Um slugger diferente num lado deixa o gerador aprovar uma âncora que o `--show` não acha. Canário: `test/lib/standards/sections.test.js` |
|
|
135
140
|
| `src/core/paths/output-schema.js`| Run `npm run generate` to update phase-utils.js |
|
|
136
|
-
| `src/lib/specs/
|
|
137
|
-
| `
|
|
138
|
-
|
|
|
141
|
+
| `src/lib/specs/adr-index.js` (o índice de ADRs do projeto) | lê o `2-plan/plan.md` da feature que fecha **pelo `parsePlan`** (`src/lib/plan/plan-parser.js`, a régua única do plano — o ADR leva `body` para o arquivo do índice) e as revogações pela tabela `ADR | Mudança | Escopo` de `### Conflitos com o acervo` (`revocations.js`). Leitores: `archive.js` (dentro do commit do archive, fail-open), `finish.js` (`adrIndexNote` → `adrIndex`/`revocationProblems` no payload) e `plan-check.js` (`revocationWarnings`, enquanto o plano ainda muda — ele congela no gate do plano). Um arquivo por ADR (`{feature}--ADR-{nnn}.md`) para features paralelas não se tocarem no merge. A tabela de revogação que o template `framework/templates/docs/plan.md` ensina (cercada, dentro do comentário) passa pelos parsers reais em `test/framework/adr-revocation-docs.test.js` |
|
|
142
|
+
| `src/lib/plan/plan-parser.js` (`parsePlan`) + `src/lib/plan/checks.js` (o `plan-check`) | O parser ÚNICO do `plan.md` (forma: `framework/templates/docs/plan.md`; `SECTIONS`, `PROOF_KINDS`, `REUSE_VERDICTS`, `ADR_SCOPES`) e a validação/extração ÚNICA (`validatePlan`, `buildChecks`, `planHash` sem a `## Errata` de `framework/hooks/shared/errata.js`, `parallelGroups`, `MAX_SLICE_FILES`/`MAX_SLICES`/`MAX_PARALLEL`). Leitores do parser: `plan-check.js`, `validate-feature.js` (a MESMA `validatePlan` com `knownPersonas`) e `adr-index.js`. O `checks.json`, que só o `plan-check` escreve (o `protect-spec-files` recusa à mão), é lido por `readPlanChecks` — `gate-check.js` (`planChecks` → `planChecksPause` de `risk-detector.js`, que também lê `MAX_SLICES`), `report-check.js`, `reuse-validator.js` e `traceability-check.js` — e, direto do arquivo, por `slice-progress.js` (e por ele `fleet-scanner.js`), `handoff-generator.js`, o `features-scanner.js` do dashboard e o `statusline.py`. Campo novo no `checks.json` sobe `CHECKS_VERSION`; uma segunda leitura do `plan.md` (regex própria numa skill ou hook) é como o plano aprovado e o checado passam a discordar. Canários: `test/lib/plan/{plan-parser,checks}.test.js`, `test/commands/validation/plan-check.test.js` |
|
|
143
|
+
| `framework/MORPH.md` / `framework/CLAUDE.md` | All core skills (cross-references) e os **tetos de 20 000 e 4 000 chars** (`test/framework/resident-size.test.js`). Os dois vivem perto do teto: linha nova só entra liberando pelo menos o mesmo número de caracteres **na mesma seção**, com o antes/depois medido — `MORPH_MD_LIMIT`/`CLAUDE_MD_LIMIT` não sobem. Mecânica que um comando decide (sinais de gate, vereditos do `report-check`, roteador por palavra-chave, `approvedBy`) mora na saída do comando e na sua seção `###` do `framework/docs/CLI.md`, nunca aqui. Os títulos `## 3.`…`## 7.`, `### 2.2` e `### Protocolo de …` são lidos por canários de prosa (`worktree-*-prose`, `report-check-wiring`, `plan-contract-freshness`) |
|
|
144
|
+
| a divisão sub-agent × suíte (o sub-agent roda só o filtro dos próprios testes; `verify {f}` roda a suíte inteira, sem filtro, uma vez por feature e no Gate 3/CI) | os textos que a declaram e não podem discordar: `framework/docs/CLI.md` (seção "Suítes de teste" — a tabela canônica e a forma de `project.tests[]`), `morph-apply` e `morph-review` (o Gate 3 roda **sem** filtro). Uma superfície que ensine filtrar o verify da feature devolve o F30 pela porta da frente; canário: `test/framework/verify-stack-docs-prose.test.js` |
|
|
139
145
|
| `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish/merge-worktree.test.js` (e o pré-voo de `finish/dry-run.test.js`) que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
|
|
140
146
|
| `src/lib/worktree/finish-backup.js` (backups do finish e a retenção) | os QUATRO pontos que escrevem ou leem `.git/morph-spec/finish-backup/`: `finish.js` (o shed da raiz e a poda do fim, `enforceBackupRetention`), `worktree.js` (o backup do desmonte, o shed do esqueleto da raiz de feature já arquivada — `clearRootWorktreeRegistration`, que só commita registro RASTREADO — e a poda do fim do `worktree remove`), `worktree-audit.js` (`auditFinishBackups` → `doctor --worktrees`) e `doctor.js` (a linha `.git/morph-spec` do padrão, nunca `✗`, e o `--prune`, a ÚNICA poda que inclui legado). `resolveBackupRetention` é a autoridade do `finishBackup.retention` de `framework/schemas/config.schema.json`: política que não se lê PULA a poda, nunca cai no padrão, porque a operação apaga. Backup sem manifesto é legado, e a poda automática nunca o apaga nem o conta. Um finish grava as duas pastas com o MESMO carimbo, e o carimbo da execução vai em `protectStamps`. Só `task/` é namespace. Apagar é PROVE-or-DECLINE, como a triagem de `ignored-triage.js` (que decide o que entra no backup) |
|
|
141
|
-
| a nota `memoryNote` do `finish` (`MEMORY_NOTE` em `finish.js`) | os DOIS leitores da mesma frase: `framework/docs/CLI.md` (entrada do `finish`) e `test/commands/finish/memory-note.test.js`. A nota é carimbada no **`emit`**, ponto único por onde passa todo `finished: true`; o canário do `--pr` existe por isso: aquele payload é montado À MÃO, fora do `mergeOutput`, então mover a nota para o construtor do merge deixa o teste do `--merge` verde e só ESSE morde (medido por mutação). A frase nomeia o que o repositório NÃO guarda; medir só a presença do campo aprovaria uma nota vazia |
|
|
147
|
+
| a nota `memoryNote` do `finish` (`MEMORY_NOTE` em `finish.js`) | os DOIS leitores da mesma frase: `framework/docs/CLI.md` (entrada do `finish`) e `test/commands/finish/memory-note.test.js`. A nota é carimbada no **`emit`**, ponto único por onde passa todo `finished: true`; o canário do `--pr` existe por isso: aquele payload é montado À MÃO, fora do `mergeOutput`, então mover a nota para o construtor do merge deixa o teste do `--merge` verde e só ESSE morde (medido por mutação). A frase nomeia o que o repositório NÃO guarda (e o que ele guarda: archive, `plan.md`, índice de ADRs, `git log` — o teste reprova a nota que ainda cita use cases); medir só a presença do campo aprovaria uma nota vazia |
|
|
142
148
|
| `src/lib/git/pr-index.js` | os QUATRO leitores do mesmo fato "esta branch tem PR": `fleet-scanner.js` (coluna PR), `branch-classifier.js` (o que NÃO apagar), `worktree-audit.js` (o que NÃO sugerir remover) e `pending-prs.js` (a síntese "aguardando merge"). `null` é UNKNOWN, nunca "sem PR" |
|
|
143
149
|
| `src/lib/git/branch-classifier.js` | `src/commands/project/branch.js`, `worktree-audit.js` (`unintegratedWork` reusa `classifyBranch` — o `doctor` e o `branch prune` não podem discordar sobre a mesma branch), `test/commands/branch-prune.test.js`, e `lifecycle-audit.js` (`abandoned-scaffold` conta commit próprio pelo mesmo `resolveBaseRef` + `commitsAhead`) |
|
|
144
150
|
| `src/lib/git/pending-prs.js` | os TRÊS consumidores da mesma síntese "aguardando merge": `list.js` (linha sintetizada), `fleet-scanner.js` (entrada `pr-pending`) e `doctor.js` (`auditPullRequests`). Uma feature visível numa superfície e invisível na outra é o bug que este módulo existe para impedir |
|
|
145
151
|
| `src/lib/git/gh.js` | `pr-index.js`, `pr-files.js` e `finish.js` — as DUAS formas de invocar o `gh` viraram uma; uma segunda cópia de `resolveGh` volta a tornar o caminho de sucesso do índice intestável |
|
|
146
|
-
| `src/lib/git/evidence-staleness.js` (`treeMovedSince`, "a árvore andou desde o commit da prova") | os DOIS leitores do mesmo fato: `gate-check.js` (`verificationStale`, o carimbo do `verify`) e `approve.js` (`reviewStaleness`, o `Reviewed-SHA` do review-report). Uma régua só: comparar SHA com HEAD fazia todo commit de estado (o `feature.json` do próprio verify,
|
|
152
|
+
| `src/lib/git/evidence-staleness.js` (`treeMovedSince`, "a árvore andou desde o commit da prova") | os DOIS leitores do mesmo fato: `gate-check.js` (`verificationStale`, o carimbo do `verify`) e `approve.js` (`reviewStaleness`, o `Reviewed-SHA` do review-report). Uma régua só: comparar SHA com HEAD fazia todo commit de estado (o `feature.json` do próprio verify, um relatório de fatia, `recap.md`) invalidar a prova sem nenhum byte de código diferente. Conta tudo fora de `.morph/` e, dentro dele, só o `config.json` — sem `expectedCount`, que a rodada de baseline escreve e não é evidência; um comando de teste trocado conta. `--no-renames` é load-bearing: um arquivo movido para dentro de `.morph/` aparece como criação lá e a deleção fora some. SHA que o git não resolve = andou (a resposta de antes); sem SHA ou sem HEAD = não andou (fail-open). `treeFingerprint` (`tree-fingerprint.js`) segue a MESMA régua (árvore commitada sem `.morph/`, `config.json` sem `expectedCount`) para `repeatOf` e para o `--ci` sem CI reaproveitar o stamp verde fresco; mudar uma sem a outra faz o reaproveitamento e o gate discordarem. Canários: `test/lib/git/evidence-staleness.test.js` (mutação: sem a normalização e sem `--no-renames` cada um derruba um teste) e os de `gate-check.test.js`/`approve.test.js` |
|
|
147
153
|
| `src/lib/git/tree-fingerprint.js` (a impressão da árvore) | `verify.js` (grava `fingerprint` no evento `Verify`; `findRepeatedVerify`, de `verify-event.js`, compara com o último verify de feature COMPLETO — rodada com `persisted: false` nunca é a prova que uma completa substitui) e `metrics.js` (`verify.unchangedRepeats`, pela mesma regra). `.morph/` fica fora, exceto `config/config.json`: o próprio verify escreve feature.json e logs a cada rodada, e contá-los faria duas rodadas sobre o mesmo código parecerem diferentes. `repeatOf` é informação e nenhum gate o lê: ligá-lo ao Gate 3 é aceitar a prova de uma rodada anterior no lugar da atual |
|
|
148
|
-
| `
|
|
154
|
+
| `framework/hooks/shared/slice-progress.js` (progresso e relatório de fatia) | A régua ÚNICA de "em que pé está a fatia" (`sliceProgress`: relatório ausente = pendente, `PARTIAL_MARKER` na 1ª linha = parcial, senão feito) e de "o relatório está completo" (`inspectReport`/`REPORT_MARKERS`: prosa, `**Arquivos:**`, cada `C{n}` de `cobre:`). Leitores: `report-check.js` (reexporta), o Stop hook `validate-completion.js` (`auditSliceReports`), `worktree-context.js`, `handoff-generator.js`/`handoff.js`, `fleet-scanner.js` (pelo `git show` da branch da feature), `status.js` e o `features-scanner.js` do dashboard — nenhum reinterpreta relatório nem tem status próprio de fatia. O `statusline.py` é Python e carrega cópia do marcador: o canário de paridade em `test/hooks/statusline.test.js` a compara com a de JS. O exemplo de relatório da `morph-apply` passa pelos `REPORT_MARKERS` (`test/framework/slice-report-format-rule.test.js`), o Stop hook diz o que falta em cada marca (`MISSING_HINT`), e o do corpo da persona pelo `inspectReport` (`test/framework/morph-apply-skill.test.js`) |
|
|
155
|
+
| `src/lib/git/tree-state.js` (o bloco `tree` do `report-check`) | `report-check.js` (toda saída, inclusive o batch `report-check <f> F1 F2`, e a foto `--before-eval` em `4-review/tree-before-eval.json`) e as skills que leem os campos: `morph-apply` (pós-dispatch e retomada do PARCIAL) e `morph-review` (higiene contra `tree.sinceBeforeEval`). Campo renomeado aqui sem as skills é instrução que lê `undefined`: `test/framework/report-check-wiring.test.js` confere que todo `tree.*` citado existe na saída real. A foto compara CONTEÚDO (blob por arquivo), não o `git status`: a mutação que o verificador commitou também aparece. Fora de git o bloco é `null`, nunca árvore vazia. `MUTATION_MARKER` tem a mesma grafia que as skills mandam gravar |
|
|
149
156
|
| `src/lib/worktree/feature-truth.js` | The six readers of the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, the mutator guard (`approve.js`/`advance.js`/`gate-decision.js`/`gate-check.js`), `verify.js` (`decidePersistence`, asked from the PRIMARY ROOT — asked from cwd, step 4 would make any scratch tree carrying a `feature.json` persist) and the dashboard's `features-scanner.js` (only for ACTIVE features — it spawns git — and a worktree owner without the feature files is shown as `rootStale`, never silently replaced by the root skeleton) |
|
|
150
|
-
| `src/lib/tasks/test-runner.js` | `verify-runner.js` (o nó `tests` repassa o `--timeout` e lê `timedOut`), `src/commands/project/worktree.js` (as constantes `WORKTREES_DIR`/`LEGACY_WORKTREES_DIR` que a descoberta ignora — duplicar as strings aqui é como o bug volta), `test/lib/test-runner.test.js` (o canário do ignore de worktrees e o do timeout escalado). Um worktree é o checkout de OUTRA feature: varrê-lo dobra o relógio e reporta sobre código fora da árvore avaliada. `stripAnsi` roda antes de TODO extrator de contagem (ponto único em `parseTestCount`)
|
|
151
|
-
| `src/lib/specs/screen-checklist.js` (checklist de tela, #129) | o sinal mora aqui (`collectScreenChecklist`) e tem QUATRO leitores: `gate-check.js` na `uiux` (`problems` → pausa) e no `review` (`uncovered`: item decidido sem linha na seção `## Checklist de tela`
|
|
152
|
-
| `src/lib/scout/scout-check.js` (o mapa do scout) | os DOIS escritores do mesmo formato e os que mandam rodá-lo: a persona `scout` do `framework/agents.json` (o CONTRATO DE EVIDÊNCIA e seus exemplos `Formato:`), MORPH.md §6 ("
|
|
153
|
-
| `src/lib/
|
|
154
|
-
| `
|
|
157
|
+
| `src/lib/tasks/test-runner.js` | `verify-runner.js` (o nó `tests` repassa o `--timeout` e lê `timedOut`), `src/commands/project/worktree.js` (as constantes `WORKTREES_DIR`/`LEGACY_WORKTREES_DIR` que a descoberta ignora — duplicar as strings aqui é como o bug volta), `test/lib/test-runner.test.js` (o canário do ignore de worktrees e o do timeout escalado). Um worktree é o checkout de OUTRA feature: varrê-lo dobra o relógio e reporta sobre código fora da árvore avaliada. `stripAnsi` roda antes de TODO extrator de contagem (ponto único em `parseTestCount`) — uma cor SGR no meio do sumário derrotava os regexes ancorados (#110). O extrator `dotnet` lê os DOIS sumários (#115): a linha VSTest e o bloco MTP (cabeçalho `Test run summary:`/`Resumo da execução de teste:` na coluna 0, `total:` indentado), em pt-BR e en-US — UM bloco por invocação de `dotnet test` (uma solução de dois projetos dá um bloco agregado, medido), então somar blocos é somar links `&&`; um terceiro locale é `null` (capturas medidas em `test/fixtures/test-output/`). `isInside` e `canonicalPath` também são lidos por `mutation/dotnet-introspection.js`, pelo registro de projetos e pelo dashboard — um segundo canonicalizador é como dois leitores passam a discordar sobre o mesmo path |
|
|
158
|
+
| `src/lib/specs/screen-checklist.js` (checklist de tela, #129) | o sinal mora aqui (`collectScreenChecklist`) e tem QUATRO leitores: `gate-check.js` na `uiux` (`problems` → pausa) e no `review` (`uncovered`: item decidido sem linha na seção `## Checklist de tela` do `verifier.md`, lida por `verifierScreenChecklist` (`review-reports.js`) — última ocorrência; tela toda "não se aplica" não deve nada), e `approve.js`/`advance.js` com assinatura `auto` (`uiuxAutoApprovalBlock`). Política só em `risk-detector.js`: `uiuxChecklistPause` é o predicado ÚNICO do gate-check pausar e do auto recusar — uma segunda regra no advance fez approve e advance discordarem da mesma feature — e `gateDecisions.uiux.pause === false` destrava os dois só para o mockup que a decisão viu: `persistGateDecision` (`gate-decision.js`, o escritor único de `gateDecisions`) grava `mockupsHash` e `uiuxAutoApprovalBlock` o compara com o arquivo atual — os dois leem `mockupsHash` daqui, e um segundo cálculo do hash (outra normalização de BOM/CRLF) faz toda decisão parecer de outro mockup. `--force` não destrava em nenhum dos dois. Mockup legado (`## S\d+:` e nenhuma `## Tela:`, o template anterior) é isento nas duas fases com `exempt` = `LEGACY_MOCKUP_EXEMPTION` à vista no `getRiskSignals`; qualquer outro heading é "nenhuma tela declarada", senão trocar o heading pularia o checklist (fixture: `test/fixtures/mockups/legacy-ui-mockups.md`, o template de main). Os rótulos dos itens casam com DOIS textos que o parser não vê, os dois com canário em `test/lib/specs/screen-checklist.test.js`: o template do prompt da `morph-uiux` §3 e `framework/templates/docs/ui-mockups.md`; mais o formato de linha que `framework/evals/review-checklist.md` (seção "Verificador", "Contrato de tela") prescreve. A `Superfície` é validada contra as chaves de `surfaces` da `ui-designer` (`knownSurfaces`) — é a chave que o `morph-plan` §5 (`superfície:` da fatia) usa para somar standards |
|
|
159
|
+
| `src/lib/scout/scout-check.js` (o mapa do scout) | os DOIS escritores do mesmo formato e os que mandam rodá-lo: a persona `scout` do `framework/agents.json` (o CONTRATO DE EVIDÊNCIA e seus exemplos `Formato:`), MORPH.md §6 ("Scout"), `morph-plan` §4, `morph-hotfix` §2 (+ `framework/commands/morph-hotfix.md`) e a entrada do `framework/docs/CLI.md`. Persona que ensina uma forma que o checker reprova faz TODO mapa voltar: `test/framework/scout-persona-format.test.js` passa os exemplos da persona pelo checker. Os relaxamentos (linha já citada, bloco cercado logo depois, listagem sob comando de busca, subseção de Inferido, menção entre aspas) têm cada um o caso parecido que continua reprovando em `test/lib/scout/scout-check.test.js`; crase descritiva depois de citação reprova de propósito, é a forma do erro de campo. Raiz = `--root` ou topo da worktree, nunca a raiz primária. Medição de modelo/effort: `docs/specs/scout-evidence/decisions.md` |
|
|
160
|
+
| `src/lib/stack/stack-scan.js` (`morph-spec stack-scan`) | Varredura determinística contra `config.project.stack` (`getProjectTags` de `stack-filter.js`; só stacks configurados são julgados), com `SEVERITIES` e regras por stack. O que fica fora vem de leitores únicos — `isTestPath` (`src/lib/graph/sanitize.js`), `WORKTREE_DIR_NAMES` (`src/lib/worktree/dirs.js`) e `PACKAGE_LOCATIONS` (`src/lib/detectors/index.js`) —, nunca listas próprias. `src/commands/project/stack-scan.js` é fino: com `<feature>` grava `2-plan/stack-scan.md` e recusa fora da árvore dona. Leitores do relatório: `morph-plan` (Bloqueante/Alto vira nó da árvore de decisões) e `morph-uiux`; regras e severidades descritas no bullet `stack-scan` de `framework/docs/CLI.md`, que muda junto. Canários: `test/lib/stack/stack-scan.test.js`, `test/commands/stack-scan.test.js` |
|
|
161
|
+
| `framework/hooks/shared/spawn-command.js` (`resolveSpawn`) | a regra ÚNICA de "como executar `npx`/`npm`/`pnpm`/`yarn` neste SO", com dois leitores: `defaultExec` de `e2e-check.js` (o spec do e2e, detectado ou `e2e.command`, e o seed do auth) e `heavy run` (`heavy.js`). No win32 o shim `.cmd` sem shell é ENOENT e o Node recusa `.cmd` com `shell: false`: `npx playwright` vira `node node_modules/@playwright/test/cli.js` quando o arquivo existe, o resto vai ao `.cmd` pelo shell. Canário: `test/hooks/spawn-command.test.js` (inclui um `npx --version` real) |
|
|
155
162
|
| `src/lib/tasks/linux-runner.js` | `verify-runner.js` (`runNodesInLinux` faz as MESMAS pré-decisões do laço local e julga o resultado pelo MESMO `judgeRun` — uma segunda cópia dessa regra é como container e host passam a discordar da mesma suíte), `framework/hooks/shared/e2e-check.js` (`defaultExec`/`isDockerAvailable` são reusados, nunca recriados), `src/lib/worktree/dirs.js` (nomes do contêiner de worktree nas exclusões do `tar`), `framework/schemas/config.schema.json` (`linuxImage`) e `test/lib/tasks/linux-runner.test.js` (o canário do bind mount: montar a árvore em `/work` é v9fs case-insensitive e não reproduz F35). Os labels `morph-spec.verify`/`morph-spec.root` do `docker run` e `reapLabeledContainers` (`docker rm -f` por label, nunca por nome) são o que limpa o container efêmero sem tocar nos de outros projetos: `test/lib/tasks/linux-runner.test.js` pina o argv de `run` (filtra `args[0]==='run'`, porque o `ps`/`rm` do reap passam pelo mesmo `exec`). |
|
|
156
|
-
| `src/lib/mutation/**` (`morph-spec mutate`, a mutação dirigida do Gate 3) | `src/commands/validation/mutate.js` (fina), `bin/morph-spec.js`, o bullet `mutate` de `framework/docs/CLI.md`, a
|
|
157
|
-
| `--record-baseline` (`recordTestBaselines` em `src/lib/tasks/test-nodes.js`) | `verify-runner.js` (`baselineTargetProblem` recusa ANTES do build; `recordBaselineFrom` só grava com todos os `runs[]` verdes), `src/commands/validation/verify.js` (`baselineStampBlock`: só a rodada que GRAVOU a baseline persiste o stamp — sem gravação, o `reason` nomeia o nó), `framework/schemas/config.schema.json` (`expectedCount`) e `atomicWriteJson` de `state-manager.js` — o número é MEDIDO por comando, nunca digitado (D8)
|
|
163
|
+
| `src/lib/mutation/**` (`morph-spec mutate`, a mutação dirigida do Gate 3) | `src/commands/validation/mutate.js` (fina), `bin/morph-spec.js`, o bullet `mutate` de `framework/docs/CLI.md`, a "Prova por mutação" da seção "Verificador" de `framework/evals/review-checklist.md`, a persona `verifier` e o dispatch em `morph-review` §3 (mutação só depois que revisor e segurança voltam), com os canários `test/lib/mutation/*` e `test/commands/mutate.test.js`. O mundo falso `test/helpers/mutation-world.js` reproduz o build INCREMENTAL do MSBuild (sem essa propriedade o teste da armadilha do mtime não morde). Invariantes: **no máximo 3 mutantes, cada um com `expect`** (`manifest.js` recusa com exit 2 — mutante cujo efeito o verificador não prevê tende a ser equivalente); **com subconjunto utilizável, ele decide** (sobrevivente fica `survived` com escopo `subset`, sem escalar à suíte inteira — o subconjunto é escolhido pela ferramenta, nunca pelo verificador); sem subconjunto, a suíte inteira roda e a morte tem de se repetir; restaurar é **escrita nova**, nunca `os.utime`; `killed` exige falha NOVA contra a rodada-base do MESMO escopo, e `inconclusive`/`build-error` nunca contam como morte; `proveBinary` compara a DLL final com a construída da árvore limpa. `dotnetBuildCommandFor` é a única grafia de "dotnet build" e `csharpClasses` (`mutation/dotnet-introspection.js`, que só LÊ: dono de arquivo, modo de teste do SDK e tipos de um `.cs`) a única leitura de classe de teste; o filtro do subconjunto é composto por `withFilter`, que soma (`&`) a um `--filter` já declarado. **A varredura C# falha para o lado SEGURO:** toda palavra-chave de tipo do texto CRU tem de ser uma declaração nomeada, senão o arquivo é `scannable: false` e fica fora do subconjunto; a garantia e o que ela NÃO cobre moram no JSDoc de `csharpClasses` — mexeu no varredor, reescreva-o junto e prove por mutação que o tripwire de `test/lib/mutation/dotnet-introspection.test.js` ainda morde. Dois parsers de TRX, ou um segundo "dotnet build" digitado, divergem |
|
|
164
|
+
| `--record-baseline` (`recordTestBaselines` em `src/lib/tasks/test-nodes.js`) | `verify-runner.js` (`baselineTargetProblem` recusa ANTES do build; `recordBaselineFrom` só grava com todos os `runs[]` verdes), `src/commands/validation/verify.js` (`baselineStampBlock`: só a rodada que GRAVOU a baseline persiste o stamp — sem gravação, o `reason` nomeia o nó), `framework/schemas/config.schema.json` (`expectedCount`) e `atomicWriteJson` de `state-manager.js` — o número é MEDIDO por comando, nunca digitado (D8) |
|
|
158
165
|
| `src/lib/tasks/eval-node.js` | os leitores do MESMO nó `runner: "evals"`: `test-nodes.js` (o enum de `TEST_RUNNERS` e a validação fail-closed do bloco `evals`), `verify-runner.js` (`preDecideRun` faz o pré-voo — os três estados de cache reprovam ANTES de `dotnet test`; `judgeEvalRun` lê o sumário depois), `verify.js` (`evalsStatusOf` deriva `nodes.evals` do stamp) e `framework/schemas/config.schema.json`. O pin se resolve em UM lugar, `resolveAiPin` (`src/lib/ai/pin-drift.js`, o mesmo do `doctor --ai`): `.morph/framework/ai-pin.json` do projeto e, sem ele, o do pacote da CLI. O `update` nunca copia o pin, então o fallback é o caso comum — um leitor próprio do eval deixou `MORPH_EVAL_PIN` sem ser injetado em todo projeto instalado, e com ele a régua do carimbo desligada sem aviso. Subir o pin do pacote reprova (`eval-cache-stale-pin`) toda cache carimbada no anterior: é o efeito, não um defeito. **`null` é DESCONHECIDO, nunca 0**: sumário ilegível não pode fabricar queda contra `expectedCount`, e miss de cache é `fail` nomeado, JAMAIS `skip`. A unidade do nó é o CENÁRIO: só `judgeEvalRun` compara com `expectedCount` — `judgeRun` passa baseline `null` para `runner: "evals"` (#110 parte 2: os `[Fact]` do extrator contra a baseline de cenários reprovavam um 24/24), e sem sumário legível a contagem é `null`, nunca a do extrator — nem compara nem vira baseline. O sumário (`morph-eval-{id}-{pid}.json` no temp) é APAGADO antes e depois da rodada: um arquivo herdado sob o mesmo pid era lido como desta. `--linux` com nó de eval é erro de uso: o canal `MORPH_EVAL_*` carrega caminhos absolutos do host e o container roda uma cópia em `/work` |
|
|
159
166
|
| `src/lib/ci/**` (`verify {f} --ci`, a prova do Gate 3 no CI) | O CI é **outra fonte do MESMO stamp**, nunca uma regra de gate: `verify.js` grava o `verify --json` que o run publicou (artefato `morph-verify`, `CI_RESULT_FILE`) pelo mesmo caminho do local, com `source: 'ci'`, `ciRun` e `sha` = `headSha` do run — `gate-check`/`risk-detector`/`approve` não mudam. Só o ARTEFATO vira prova, nunca a `conclusion` do run (workflow de lint verde não compra o Gate 3), e `findCiWorkflow` só aceita workflow que chama `morph-spec verify` E publica `morph-verify.json` — o `renderCiWorkflow` (`workflow-scaffold.js`) tem de continuar passando nele (canário em `test/lib/ci/workflow-scaffold.test.js`). O run vale por ÁRVORE, não por SHA (`treeMovedSince`): commit só de `.morph/` não invalida, e o gatilho do workflow exclui `.morph/**` menos o `config.json` pela mesma razão (senão `cancel-in-progress` cancela o run do código). `cancelled`/`skipped`/`startup_failure` não são prova. Todo "não dá" é `mode: 'local'` com razão (fallback, nunca pausa); código fora de commit é exit 2; run pendente no prazo é exit 3 sem stamp. A URL do origin é lida CRUA (`remote.origin.url`): `git remote get-url` aplica `insteadOf`, que é como os testes apontam o push para um bare local. Canários: `test/commands/verify-ci.test.js` (stub `test/helpers/gh-ci-stub.js`), `test/lib/ci/*` |
|
|
160
|
-
| `nodeStatuses()` em `src/commands/validation/verify.js` | os
|
|
167
|
+
| `nodeStatuses()` em `src/commands/validation/verify.js` | os DOIS blocos `nodes` de `framework/schemas/feature.schema.json`, todos `additionalProperties: false` — `verification.nodes` e `verifications.feature.nodes`. A função é escritora ÚNICA dos dois: um campo novo aqui sem os dois no schema faz o histórico violar o próprio contrato. Canário: "os DOIS escritores do mapa de nós concordam" em `test/commands/verify.test.js` |
|
|
161
168
|
| `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b. `isTestPath` (`sanitize.js`) é o predicado ÚNICO de teste: `god-nodes.js`, `recoverDispatch`, `graph status\|explain` e `symbol-scanner.js` (`isTestFile`, que o `code-budget` também lê) o importam, e uma segunda régua faz o grafo e o validador discordarem sobre o mesmo arquivo. A fixture `test-doubles.graph.json` saiu do graphify real (passo a passo em `docs/specs/graphify-integration/fixture-test-doubles/`) — regerar, nunca editar à mão |
|
|
162
169
|
| `framework/hooks/shared/graph-freshness.js` (a regra única de "grafo em dia") | os três lados que perguntam: `graph-refresh.js` (decide se roda e se o disparo solta processo), `graph-loader.js` `getFreshness` (`graph status\|explain\|affected` e o `doctor`) e `graph build\|refresh` (gravam a rodada em `<git-common-dir>/morph-spec/graph-refresh-state.json`). O registro da rodada existe porque o `graphify update` 0.9.50 não recarimba `built_at_commit` quando a topologia não muda: sem ele, o grafo parecia velho para sempre e cada disparo rodava o graphify de novo (~80 s no MORPH_GHLBrain). As regras do `.graphifyignore` entram por CONTEÚDO (hash sem comentário, BOM e CRLF), nunca pelo relógio, e `ignoreTextOf` é a ÚNICA leitura de "as regras desta árvore": numa worktree sem arquivo próprio, as da raiz — as mesmas que o `graph refresh` põe ali durante a rodada (`prepareIgnore` em `build.js`). O arquivo temporário leva `TEMP_IGNORE_MARKER` na 1ª linha; nas duas triagens de sujeira (`finish` e `worktree remove`) `withoutLeftoverIgnore` o tira da conta ANTES das recusas e `dropLeftoverIgnore` o apaga só DEPOIS delas — apagar antes é mutação atrás de uma precondição. Mudar a grafia do marcador num lado só devolve o `worktree-dirty` de uma rodada morta no meio. Mora em `hooks/shared` porque hook não importa `src/` |
|
|
163
170
|
| `framework/hooks/shared/graph-refresh.js` (o refresh em background) | os dois disparos (`graph-refresh-kick`, depois de Bash/PowerShell com `git`/`gh`/`morph-spec`, e o SessionStart de `worktree-context.js`), `graph build\|refresh` (o MESMO lock por repositório) e `worktree.js` (`seedGraphFromRoot`: a worktree nasce com o grafo da raiz e o registro dela — em dia no HEAD só se a raiz estava). **Só a raiz**: o worker destacado nasce com `cwd` no git common dir e nunca recebe o caminho de uma worktree — no win32 um diretório em uso não se apaga, e o `finish` de uma worktree com refresh correndo falhava no meio. A worktree anda por `morph-spec graph refresh`, em primeiro plano. `runGraphify` roda o graphify com `cwd` no git common dir, nunca na árvore (ele lê o alvo, o ignore e o git pelo argumento — medido no 0.9.50). O disparo lê o HEAD pelo disco (`readPrimaryHead`, `git-paths.js`) e não solta processo quando o grafo não pede refresh: ele roda a cada comando git do agente. O step 18b de `setup-infra.js` não instala o hook git do graphify: ele não roda em worktree, não tem `post-merge` e carimba como em dia um grafo sem os arquivos do merge |
|
|
164
171
|
| `framework/hooks/shared/git-exclude.js` | ÚNICO escritor dos blocos do morph-spec no `info/exclude` do COMMON dir — o `.git/worktrees/<id>/info/exclude` o git não lê para worktree vinculada. Leitores que passam o seu marcador: `worktree.js`, `handoff-generator.js` e `graph-refresh.js`. Resolve o common dir pelo disco, sem subprocesso: um `git` gerado por um processo destacado abria janela de console no win32 |
|
|
165
|
-
| `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `
|
|
166
|
-
| `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/
|
|
167
|
-
| `framework/hooks/shared/comment-rules.js` / `comment-scanner.js` / `source-files.js` (o hook `comment-guard`) | Os dois leitores do MESMO comentário: o hook `comment-guard` (PostToolUse `Write\|Edit`, bloqueia na hora da edição) e o `code-budget` (avisa no `verify`). Moram em `hooks/shared` porque hook não importa `src/`; o `src/` importa daqui, nunca uma cópia. O hook julga só o comentário ADICIONADO (`tool_response.originalFile`, medido: `null` no Write que cria) e só FORMA — processo, banner/`#region`, bloco > 3 linhas, doc-comment fora de pacote publicado —, nunca "repete o código". É o ÚLTIMO elo da chain `Write\|Edit` (um block encerra a chain; canário em `test/hooks/hooks-installer.test.js`). Marcador que template precisa manter (`READS STANDARD(S):`, `VERIFY:`) entra em `ALLOWED`; o canário de template em `test/hooks/comment-rules.test.js` reprova template que dispararia o hook no scaffold |
|
|
168
|
-
| o ciclo de reuso (`
|
|
172
|
+
| `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `test/fixtures/graph/prospectpro-reuse.graph.json` (recorte do grafo real — regerar com `build-reuse-fixture.mjs`, nunca editar à mão) |
|
|
173
|
+
| `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/docs/CLI.md` (o que conta como teste e os limites 1,5×/40 linhas) e `test/integration/code-budget-verify.test.js`. Lê `addedLinesAgainst` (`default-branch.js`), `isTestFile` (`symbol-scanner.js`, a mesma régua de teste do `reuse`), `isProjectSource`/`isGenerated` (`hooks/shared/source-files.js`) e `commentLines`/`citesProcess` (`hooks/shared/comment-scanner.js`, sobre `stripComments` com `js`). Sempre `warning`. Não conseguir medir é skip `unavailable` com razão, e o `validation-runner` não põe validador pulado em `results`: pulado não conta como verde |
|
|
174
|
+
| `framework/hooks/shared/comment-rules.js` / `comment-scanner.js` / `source-files.js` (o hook `comment-guard`) | Os dois leitores do MESMO comentário: o hook `comment-guard` (PostToolUse `Write\|Edit`, bloqueia na hora da edição) e o `code-budget` (avisa no `verify`). Moram em `hooks/shared` porque hook não importa `src/`; o `src/` importa daqui, nunca uma cópia. O hook julga só o comentário ADICIONADO (`tool_response.originalFile`, medido: `null` no Write que cria) e só FORMA — processo, banner/`#region`, bloco > 3 linhas, doc-comment fora de pacote publicado —, nunca "repete o código". É o ÚLTIMO elo da chain `Write\|Edit` (um block encerra a chain; canário em `test/hooks/hooks-installer.test.js`). O vocabulário de processo (`citesProcess`) inclui o id de cenário/fatia do plano (`C{n}`/`F{n}`), `plan.md` e `checks.json`; a tabela de `test/hooks/comment-scanner.test.js` põe cada forma ao lado do vizinho que NÃO pode casar (tecla `F5`/`KeyCode.F1`, `F1 score`, `C4 model`, célula `C3`, `#F5F5F5`, `0xC1`) — e a mensagem do `code-budget` lista o mesmo vocabulário. Marcador que template precisa manter (`READS STANDARD(S):`, `VERIFY:`) entra em `ALLOWED`; o canário de template em `test/hooks/comment-rules.test.js` reprova template que dispararia o hook no scaffold |
|
|
175
|
+
| o ciclo de reuso (tabela `## Reuso` do `plan.md`) | `src/lib/plan/plan-parser.js` + `checks.js` (o `plan-check` extrai `reuse` com `why` no CRIAR para o `checks.json`), `morph-plan`, o corpo de dispatch da persona (`DISPATCH_CONTRACT` em `agents-installer.js`, cláusula de reuso), o validador `reuse` (`src/lib/validators/reuse/`: vereditos e arquivos da feature lidos por `readPlanChecks`, nunca do `plan.md` cru) e `test/framework/graph-fallback.test.js`. Canário ponta a ponta: `test/integration/reuse-verify.test.js` (CRIAR declarado no plano rebaixa a duplicata a aviso pelo `verify` real) |
|
|
169
176
|
| `framework/mcp/registry.json` | mcp-installer.js, claude-config-detector.js, agents-installer.js (personas' `mcp` field), `scripts/validate-framework.js` Check 8, `test/framework/mcp-registry.test.js` |
|
|
170
177
|
| `src/lib/detectors/claude-config-detector.js` (`readEnabledPlugins`) e `src/lib/installers/mcp-installer.js` (`getInstalledMcps`) | os QUATRO chamadores de `orchestrateMcpSetup` (`mcp-setup.js` ×3, `init.js`): chamada sem `enabledPlugins` recria a duplicata `mcp__X` × `mcp__plugin_X_X` (canário de fiação em `test/lib/installers/mcp-installer.test.js`). `readEnabledPlugins` é a leitura ÚNICA de "plugin ligado" — o `doctor --procs` a reusa em `procs/mcp-config.js`; o prefixo `plugin_<nome>_` sai de `serverNames` do registry via `mcpServerNames` |
|
|
171
178
|
| `src/lib/procs/**` (`doctor --procs`) | `src/commands/project/doctor-procs.js`, o ramo `--procs` de `doctor.js` (entra DEPOIS do de `--worktrees`, fora das fatias que `test/commands/doctor-ai.test.js` corta), `bin/morph-spec.js` (`--prune` é UMA flag para os dois modos) e `test/commands/doctor-procs.test.js`. **Só `prune.js` mata, e por PID:** o canário varre o grafo estático do comando (`prune.js` só entra por import dinâmico dentro de `if (options.prune)`), reprova escrita, spawn e `process.kill` no caminho de leitura — com exceção nomeada ao `execFileSync` de `list.js` — e reprova `taskkill`/`Stop-Process`/`pkill`/`killall`/`/IM` em qualquer código de `src/` e dos hooks. As proteções de `classify.js` e a checagem de PID reaproveitado de `prune.js` têm teste por mutação sobre processo real: tirar uma delas mata um sleeper de teste. `null` é desconhecido, nunca lista vazia. `MORPH_PROCS_FIXTURE` é o único seam de teste; nada em produção o define |
|
|
@@ -175,16 +182,18 @@ When modifying framework components, verify the full scope of related files.
|
|
|
175
182
|
| `framework/hooks/shared/worktree-root.js` (`worktree.root`) | o resolvedor ÚNICO de "onde as worktrees moram" e de "está no contêiner" — a UNIÃO do layout configurado com `worktrees/` e `.worktrees/`, para a árvore antiga seguir reconhecida. Todo leitor precisa dele, e um que volte ao literal fica cego para a externa ou acusa `out-of-container` em árvore boa: `worktree.js` (`buildWorktreePath`, mkdir do pai, `findWorktreeForFeature`), `worktree-audit.js` (`inContainer` e `dirsOnDisk`, que só varre `<root>/<repo>-<hash>/`), `agent-reaper.js`, `worktree-context.js` (`isOutsideContainer`), `worktree-create.js` (fallback em git puro), `registry.js` (`insideWorktreeContainer` lê o `.git` da árvore) e os scanners do dashboard (`features-scanner`, `project-api`). `src/lib/worktree/dirs.js` só reexporta as constantes daqui. **Root dentro da árvore é recusado com aviso:** as listas orientadas pelo nome `worktrees/` (`.gitignore`, `graphifyignore`, `test-runner`, `linux-runner`, `architecture-scanner`, `ai-project-scan`, globs do editor) não o enxergariam. A chave do repositório é `basename-<hash da raiz>`: só o basename colidiria entre repositórios. `buildWorktreePath` preserva a string como veio quando não há root (o teste dele passa `/project`). Canários: `test/hooks/worktree-root.test.js`, `test/commands/worktree-root.test.js` (medido por mutação, 14 de 14) |
|
|
176
183
|
| `src/lib/worktree/worktree-include.js` (`.worktreeinclude`) e o `locked` de `parseWorktreeList` | O hook `WorktreeCreate` **substitui** a criação nativa, e a doc do Claude Code diz que com hook o `.worktreeinclude` "não é processado": `copyWorktreeIncludes` é a paridade, chamado por `runWorktreeSetup` e `runWorktreeProvision` (que ainda copia `.env*` por `copyRootEnvFiles`; a união é de propósito, e o include não repete o que o `.env*` já trouxe). Casa o padrão **e** é ignorado NA ÁRVORE (`check-ignore` lá: arquivo que casa mas não é ignorado sujaria o worktree e o `finish` recusa), nunca `node_modules`/artefato de build (`isDisposableBuildArtifact`, de `ignored-triage.js`)/`.git` aninhado, nunca sobrescreve. Uma segunda noção de "o que copiar" faria o nativo e o morph discordarem. `parseWorktreeList` expõe `locked` (a linha `locked` do porcelain) e o `agent-reaper.js` o lê: `git worktree lock` é a marca que o Claude Code usa em agente rodando, e a árvore travada nunca é tocada (razão `locked`). O SessionStart segue o `cwd` do payload (`sessionDir` em `worktree-context.js`) só para worktree vinculado do MESMO repositório — `CLAUDE_PROJECT_DIR` fica na raiz numa sessão que entra no worktree —, e usa o `isSecondaryWorktree` de `git-paths.js` (a cópia local comparava `--git-dir` com `--git-common-dir` em formas diferentes e tratava uma SUBPASTA da raiz como worktree). Canários: `test/lib/worktree/worktree-include.test.js`, `test/hooks/worktree-context.test.js`, `test/lib/worktree/agent-reaper.test.js` |
|
|
177
184
|
| Any `framework/` file that calls `morph-spec graph` | The fallback to grep must be declared in the SAME file — `test/framework/graph-fallback.test.js` enforces it (ADR-002: a skill that requires the graph with no fallback is a bug) |
|
|
178
|
-
| `framework/hooks/claude-code/core/worktree-create.js` / `worktree-remove.js` / `block-raw-worktree-add.js` / `worktree-env-inject.js` | `hooks-installer.js` (`MORPH_HOOKS`: eventos `WorktreeCreate`/`WorktreeRemove` + a ORDEM da chain Bash — inject por último, um comando bloqueado nunca é reescrito), `hooks-scanner.js` `EVENT_KEYS`, `chain.js` (`updatedInput` só viaja com rewrite — `permissionDecision:'allow'` incondicional pularia o prompt de todo Bash), `worktree.js` (`runWorktreeProvision`, `--if-clean`, `envKeyForWorktree`), `worktree-audit.js` (`morph-task/*`, `out-of-container`), `worktree-context.js`, MORPH.md §2.2, `morph-apply` §
|
|
185
|
+
| `framework/hooks/claude-code/core/worktree-create.js` / `worktree-remove.js` / `block-raw-worktree-add.js` / `worktree-env-inject.js` | `hooks-installer.js` (`MORPH_HOOKS`: eventos `WorktreeCreate`/`WorktreeRemove` + a ORDEM da chain Bash — inject por último, um comando bloqueado nunca é reescrito), `hooks-scanner.js` `EVENT_KEYS`, `chain.js` (`updatedInput` só viaja com rewrite — `permissionDecision:'allow'` incondicional pularia o prompt de todo Bash), `worktree.js` (`runWorktreeProvision`, `--if-clean`, `envKeyForWorktree`), `worktree-audit.js` (`morph-task/*`, `out-of-container`), `worktree-context.js`, MORPH.md §2.2, `morph-apply` §4, `test/framework/worktree-governance-prose.test.js`, `resident-size.test.js` (MORPH.md perto do teto de 20k). **Âncora de sessão:** `anchorSessionCommand` (`TEARDOWN_RE`) exporta `MORPH_SESSION_PROJECT_DIR`, que `sessionAnchoredInWorktree` (`worktree.js`) lê e que o `finish` (`--merge`/`--pr` adiam o desmonte) e o `worktree remove` (recusa `session-anchored-in-worktree`) consomem — mudar a chave ou o formato do comando num lado só devolve o bug de 48 hooks `MODULE_NOT_FOUND` depois do `finish` |
|
|
179
186
|
| `src/lib/worktree/env-allocator.js` (`portLayout`) | os DOIS escritores do bloco — `buildEnvFileContent` (`.morph/worktree.env`) e `compose-env.js` (`buildComposeEnv`, usado por `e2e up`, pelo nó `e2e` do `verify`, por `stack-teardown.js` e por `morph-spec env`) — e os leitores `worktree-context.js`, `handoff-generator.js`, `worktree-env-inject.js`, o template `docker-compose.template.yml` (variáveis que ele publica) e o standard `local-compose-isolation.md` (tabela de offsets). Canário: "worktree.env e compose env concordam byte a byte" em `test/lib/e2e/compose-env.test.js`. Offset novo aqui sem entrar no template é porta que ninguém publica. **Alias por projeto** (`e2e.portEnvNames`, `resolvePortAliases`): é só outro NOME para as mesmas quatro portas, então não cria offset nem toca o template; entra pelo `portLayout` e por isso os dois escritores o levam, o `layoutKeys` do `compose-env.js` o protege do `.env.e2e` herdado, e o hook `worktree-env-inject` injeta toda chave do manifesto (menos `MORPH_FEATURE`) em vez de uma lista literal que divergiria |
|
|
180
|
-
| `src/lib/tasks/test-nodes.js` | os DOIS leitores de `validateTestNodes` — `verify-runner.js` (fail-closed: nó inválido avermelha o gate) e `doctor.js` (`doctor --full`, camada 3 do contrato do spec §2.2: WARN, e **pula sem imprimir nada** sem `config.json` ou sem `project.tests`, senão quebra `test/commands/doctor.test.js:46`). Uma segunda noção de "nó válido" faz o mesmo config passar num lugar e falhar no outro. `morphConfigPath` deriva de `CONFIG_FILE_NAME` (`core/state/state-manager.js`), a mesma constante de `readMorphConfig` — o JSDoc promete "one spelling" e agora é literal.
|
|
181
|
-
| `src/lib/worktree/docker-audit.js` | `worktree-audit.js` (`auditWorktrees` concatena os achados e é quem PASSA as features conhecidas — um segundo scan aqui discordaria sobre o que é órfão), `doctor.js` (a classificação `info`/`warn` = não-falha em `doctorWorktreesCommand`; e `samePathCase`, a regra de caixa de path que o WARN de frontend do `doctor --full`
|
|
182
|
-
| `src/lib/risk-detector.js` | os DOIS lados da mesma decisão de gate: `gate-check.js` (`collectSignals` COLETA do disco; nenhum sinal que descreva um FATO pode entrar em `OVERRIDABLE_SIGNAL_KEYS` — `gate`/`workType`/`e2e*` são o precedente), `src/commands/validation/verify.js` (o stamp `verification` é a ÚNICA coisa que o gate lê: campo novo aqui só vira sinal se o stamp o gravar), `framework/schemas/feature.schema.json` (`verification` é `additionalProperties: false`), `framework/
|
|
183
|
-
| `src/lib/
|
|
187
|
+
| `src/lib/tasks/test-nodes.js` | os DOIS leitores de `validateTestNodes` — `verify-runner.js` (fail-closed: nó inválido avermelha o gate) e `doctor.js` (`doctor --full`, camada 3 do contrato do spec §2.2: WARN, e **pula sem imprimir nada** sem `config.json` ou sem `project.tests`, senão quebra `test/commands/doctor.test.js:46`). Uma segunda noção de "nó válido" faz o mesmo config passar num lugar e falhar no outro. `morphConfigPath` deriva de `CONFIG_FILE_NAME` (`core/state/state-manager.js`), a mesma constante de `readMorphConfig` — o JSDoc promete "one spelling" e agora é literal. `discoverFrontendPackages` (a união do fallback de `resolveTestNodes` e o WARN do `doctor --full`) varre as pastas de `PACKAGE_LOCATIONS`, de `framework/hooks/shared/frontend-locations.js` (re-exportada por `src/lib/detectors/index.js`; o e2e lê `E2E_SPEC_LOCATIONS` = a mesma lista + `web`) — uma segunda lista de pastas é como dois detectores começam a discordar |
|
|
188
|
+
| `src/lib/worktree/docker-audit.js` | `worktree-audit.js` (`auditWorktrees` concatena os achados e é quem PASSA as features conhecidas — um segundo scan aqui discordaria sobre o que é órfão), `doctor.js` (a classificação `info`/`warn` = não-falha em `doctorWorktreesCommand`; e `samePathCase`, a regra de caixa de path que o WARN de frontend do `doctor --full` reusa — uma segunda cópia dela foi removida), `env-allocator.js` (`allocatedFeatures` é read-only de propósito: `allocateEnvBlock` ESCREVE, e uma auditoria que aloca inventa o estado que reporta), `test/lib/worktree/docker-audit.test.js`. Nunca no `doctor --full` (D10): `test/commands/doctor.test.js:46` exige zero `✗` neste repo |
|
|
189
|
+
| `src/lib/risk-detector.js` | os DOIS lados da mesma decisão de gate: `gate-check.js` (`collectSignals` COLETA do disco; nenhum sinal que descreva um FATO pode entrar em `OVERRIDABLE_SIGNAL_KEYS` — `gate`/`workType`/`e2e*` são o precedente), `src/commands/validation/verify.js` (o stamp `verification` é a ÚNICA coisa que o gate lê: campo novo aqui só vira sinal se o stamp o gravar), `framework/schemas/feature.schema.json` (`verification` é `additionalProperties: false`), a tabela `#### Sinais` sob `### gate-check` do `framework/docs/CLI.md` (a régua escrita do comando; o MORPH.md §5 só lista os sinais que a sessão passa por `--signals` — canário em `test/framework/verify-stack-docs-prose.test.js`). Política escrita no coletor cria duas fontes para o mesmo fato — foi assim que `gate-check` e `approve` divergiram uma vez sobre o Gate 3. Os relatórios do Gate 3 são lidos por UM leitor (`src/lib/review-reports.js`) e decididos por UMA regra (`reviewPause`): `approve` exige cada relatório exigido com `status: ok`, `gate-check` pausa em `FIX`, bloqueador aberto ou sem tipo, ou `Cn` sem linha PROVADO. **O plano entra pelo `checks.json`:** `planChecksPause` é o predicado ÚNICO de "o plano não foi extraído até zero erro" — o `gate-check` pausa o gate do plano por ele e `approve`/`advance` automáticos recusam por ele (`planAutoApprovalBlock`); `sliceCount > MAX_SLICES` pausa. `featureGeneratesMedia` lê os `standards` das fatias por id ou caminho (`standardIdOf`), e o `report-check --before-eval` usa o mesmo predicado para rotear o verificador a opus — canário ponta a ponta em `test/framework/media-claims.test.js`. A prova de verify é sempre da feature inteira: `--filter` de escopo de feature não persiste o stamp (`reason: 'filtered'`), nem `--skip-tests`/`--skip-build` (`'skip-tests'`/`'skip-build'`, #113 — nó pulado não reprova o rollup e nenhuma regra do gate lê `tests`/`build` pulados; a lista é `PARTIAL_RUN_FLAGS`) |
|
|
190
|
+
| `src/lib/review-reports.js` (os relatórios do Gate 3) | O leitor ÚNICO de `4-review/{verifier,reviewer,security}.md`: veredito (o último, append-only), bloqueadores tipados (`BLOCKER_TYPES`), `PARCIAL` (o `PARTIAL_MARKER` de `slice-progress.js`, nunca uma cópia), `**Reviewed-SHA:**` e a `## Checks` do verificador. Leitores: `gate-check.js` (`collectReviewSignal` → `reviewSignal`), `approve.js` (status por papel, `--self-assessed` e o `Reviewed-SHA`, o pior vence), `report-check.js` (`reviews` por papel; `reviewModels` no `--before-eval`), `risk-detector.js` (`reviewPause` lê o sinal e `BLOCKER_TYPES`), `screen-checklist.js` (`verifierScreenChecklist`), o dashboard (`features-scanner.js`, por `reviewReportsFromDir` — sem git, `security` conta só presente) e o hook `state-sync` (que, por não importar `src/`, só testa a existência de `verifier.md`/`reviewer.md`). Quais papéis são exigidos é UMA regra: `requiredReviewRoles` + `securityReviewFor` via `collectReviewRequirement` (`gate-check.js`), lida pelos três comandos — uma segunda noção faz o approve aprovar o que o gate pausa. Cobertura: `checksCoverage` — status por igualdade EXATA (`NÃO PROVADO` contém `PROVADO`), Cn sem linha falta; `checks.json` ilegível pausa. Canários: `test/lib/review-reports.test.js` (medido por mutação: aceitar `NAO PROVADO` como prova e tratar Cn sem linha como coberto derrubam 3 testes cada) e o bloco "relatórios de review no Gate 3" de `test/commands/gate-check.test.js`; o fixture é `test/helpers/review-fixtures.js` |
|
|
191
|
+
| `src/lib/installers/effects.js` | os NOVE pontos de emissão que atravessam o caminho de `update`: `update.js` (clean, payloads, spawn de `generate-refs.js`, `gitIndex`), `skills-installer.js`, `agents-installer.js`, `setup-infra.js` (`installRulesForStack`, `installCommandsForStack`, `updateClaudeMdAgentsBlock`), `hooks-installer.js` (`installClaudeHooks`, `installGlobalStatusline`), `file-copier.js` (`updateGitignore`, `ensureGitattributes`), `claude-md-injector.js`, `version-checker.js` (`saveProjectMorphVersion`) e `templates-reconcile.js`. **O caminho real também emite** — o resumo de uma execução real sai de `list()`, e é por isso que um ponto de emissão esquecido some das DUAS saídas ao mesmo tempo. Canários: `test/lib/installers/effects.test.js` (igualdade dry × real do MESMO roteiro) e `test/commands/update-dry-run.test.js` — que tem DOIS canários e precisa dos dois: **igualdade** prova que os dois lados percorrem o mesmo caminho, **diferencial** (mudar `--templates` muda a previsão) prova que o caminho lê a flag. Só igualdade aprova uma previsão e uma execução que ignoram a MESMA flag — foi medido nesta feature, por mutação. Um laço de limpeza de órfão novo tem de nascer calado em `fx.dryRun` (D6): o clean o subsome. E a lista de `frameworkDirsToClean` é pinada **à mão** no teste (`DIRETORIOS_APAGADOS_POR_INTEIRO`, dez caminhos, `deepEqual` ordenado): derivar a expectativa chamando a própria função deixou a suíte inteira cega — medido, dava para remover 2 das 10 entradas sem matar nenhum dos 207 arquivos de teste |
|
|
192
|
+
| `src/lib/specs/old-format.js` (feature em voo no formato anterior) | A regra ÚNICA (`isOldFormatFeature`: `2-plan/tasks.json` ou `0-proposal/` sem `2-plan/plan.md`, status fora de `completed`/`abandoned`, raiz + toda worktree do `git worktree list`) e a mensagem ÚNICA (`oldFormatGuardLines`, que prescreve a 8.47 ou `archive <feature> --force`). As portas de escrita a aplicam ANTES de qualquer mutação, nenhuma dispensa por flag: `update.js` (`--force` não dispensa; `--dry-run` relata no topo) e `setupInfra` (`src/scripts/setup-infra.js`, também sob `force` e `reinstall`), por onde passam `init --force` e `setup-infra [--reinstall]`, que saem 1. Uma porta nova que sobrescreva `.morph/`/`.claude/` sem chamar a regra reabre o contorno. Texto da recusa em `framework/docs/CLI.md` (`### update`). Canários: `test/lib/specs/old-format.test.js` e `test/commands/update-old-format.test.js` (update, `setupInfra` com `force`/`reinstall`, `initCommand --force`, a CLI `setup-infra --reinstall`, e o `archive --force` que a mensagem prescreve rodando de verdade) |
|
|
184
193
|
| `src/lib/ai/**` (`doctor --ai`) | `src/commands/project/doctor.js` (`doctorAiCommand` + a guarda de uso indevido), `bin/morph-spec.js` (`--ai`), `framework/docs/CLI.md`. **`--ai` NUNCA entra no `doctor --full` nem no default** — o renderer do `--full` manda tudo que não é `ok`/`warn` para `✗` e `test/commands/doctor.test.js` exige zero `✗` neste repositório. **Nada alcançável por `--ai` pode escrever**: `test/commands/doctor-ai.test.js` varre o grafo de imports E o corpo de `doctorAiCommand` (com os imports dinâmicos fixados nos dois permitidos), reprovando `writeFileSync`/`spawn`/`fs-extra` em CÓDIGO — comentários apagados antes, porque os módulos EXPLICAM por escrito que não fazem isso e medir a menção seria medir uma cópia do objeto. `null` é DESCONHECIDO e vira `skip` **com razão obrigatória**; `[]` é "perguntei e não achei" e vira `ok` |
|
|
185
194
|
| `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `framework/hooks/shared/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor, e o `code-budget` também, o único com a opção `js` — template e regex literal de TS/JS; sem ela o varredor é o de antes) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
|
|
186
195
|
| `src/lib/worktree/dirs.js` | ÚNICA fonte do nome do container (`worktrees/`, sem ponto — Jest esconde diretório com ponto e reporta 0 testes sem erro). `worktree.js` re-exporta; `graphifyignore.js`, `file-copier.js`, `architecture-scanner.js`, `test-runner.js`, `worktree-audit.js`, os scanners do dashboard (`features-scanner.js`, `project-api.js`), `tasks/linux-runner.js` (exclusões do `tar` do `--linux`) importam. `test/lib/worktree/dirs.test.js` cobra cada leitor |
|
|
187
|
-
| `src/dashboard/**` (páginas e scanners) | `test/dashboard/pages-smoke.test.js` renderiza as 7 rotas e os painéis de detalhe pela config Vite do dashboard com os dados REAIS do scanner — build verde não prova nada (8.38.0 saiu com `ReferenceError` na rota inicial). Campo que a página lê tem de sair do scanner: `activePhases` é DERIVADO das linhas de dispatch das skills de fase (`personaPhases`, com `isImplementer`), nunca uma lista à mão. Edge do React Flow só existe no cliente (chave duplicada não aparece no SSR — o dedupe de `standardRefs` mora no scanner). `src/dashboard/.gitignore` é o que mantém `node_modules` fora do tarball (`test/dashboard/packaging.test.js`). `scanner/feature-status.js` (`isFinished`) é a regra ÚNICA de "feature encerrada" do servidor (portfólio, `awaiting`, `
|
|
196
|
+
| `src/dashboard/**` (páginas e scanners) | `test/dashboard/pages-smoke.test.js` renderiza as 7 rotas e os painéis de detalhe pela config Vite do dashboard com os dados REAIS do scanner — build verde não prova nada (8.38.0 saiu com `ReferenceError` na rota inicial). Campo que a página lê tem de sair do scanner: `activePhases` é DERIVADO das linhas de dispatch das skills de fase (`personaPhases`, com `isImplementer`), nunca uma lista à mão. Edge do React Flow só existe no cliente (chave duplicada não aparece no SSR — o dedupe de `standardRefs` mora no scanner). `src/dashboard/.gitignore` é o que mantém `node_modules` fora do tarball (`test/dashboard/packaging.test.js`). `scanner/feature-status.js` (`isFinished`) é a regra ÚNICA de "feature encerrada" do servidor (portfólio, `awaiting`, `sliceIndex`) e do cliente (lista e quadro) — arquivada conta como encerrada seja qual for o `status` legado. O plugin engole o `full-reload` do Vite para `.html` sob `.morph/` (`isMorphDataReload`): `handleHotUpdate` devolvendo `[]` NÃO segura `.html` no Vite 6, e um agente gravando um mockup recarregava o dashboard. `resolveFeatureTree` (git, ~0,3 s por chamada) é lembrado por feature no `project-api.js` e só é esquecido por evento estrutural (`add`/`unlink`/`addDir`/`unlinkDir`) ou TTL de 60 s — um scan síncrono trava o event loop do dev server inteiro |
|
|
188
197
|
| `src/lib/projects/registry.js` (projetos da máquina, `~/.morph-spec/projects.json`) | os TRÊS escritores automáticos — `init`, `update` (nunca sob `--dry-run`) e `dashboard` — mais o add/remove do portfólio (`src/dashboard/scanner/portfolio.js`). Registrar é fail-open: registro ilegível ou não gravável nunca derruba o comando. Nunca registra pasta sob o temp do sistema (as suítes rodam `init`/`update` ali às centenas, inclusive por CLI em spawn que herda o home real) nem checkout de worktree (`WORKTREE_DIR_NAMES`). Teste que registra usa `MORPH_PROJECTS_REGISTRY` apontando para um temp, ou o seam `tempRoot` — nenhuma suíte escreve o `~/.morph-spec` de quem roda. Leitura poda projeto cuja pasta sumiu; descoberta é UM nível abaixo das pastas-mãe e só sugere, nunca adiciona |
|
|
189
198
|
|
|
190
199
|
### Test Coverage
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:**
|
|
6
|
+
**Version:** 9.0.0
|
|
7
7
|
**Requires:** Node.js 22+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -259,11 +259,10 @@ Protected files (via `permissions.deny`):
|
|
|
259
259
|
|
|
260
260
|
## Rules
|
|
261
261
|
|
|
262
|
-
Rules are installed to `.claude/rules/` based on your detected stack (`stacks:` frontmatter, same tagging mechanism as agents and templates) and activate automatically based on file path patterns (`paths:` frontmatter). A subset of the current set:
|
|
262
|
+
Rules are installed to `.claude/rules/` based on your detected stack (`stacks:` frontmatter, same tagging mechanism as agents and templates) and activate automatically based on file path patterns (`paths:` frontmatter); no rule is always active — the workflow lives in `MORPH.md` and the skills. A subset of the current set:
|
|
263
263
|
|
|
264
264
|
| Rule file | Applies to | Stack |
|
|
265
265
|
| ----------------------------- | ------------------------------------------------- | --------- |
|
|
266
|
-
| `morph-workflow.md` | Always active — spec-first workflow | any |
|
|
267
266
|
| `csharp-standards.md` | `**/*.cs`, `**/*.csproj` | dotnet |
|
|
268
267
|
| `frontend-standards.md` | `**/*.tsx`, `**/*.css`, `**/*.scss` | nextjs |
|
|
269
268
|
| `nextjs-standards.md` | Next.js-specific patterns | nextjs |
|
|
@@ -379,4 +378,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
379
378
|
|
|
380
379
|
---
|
|
381
380
|
|
|
382
|
-
*morph-spec
|
|
381
|
+
*morph-spec v9.0.0 by [Polymorphism Tech](https://polymorphism.tech)*
|