@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
|
@@ -1,59 +1,78 @@
|
|
|
1
|
-
# Checklist do
|
|
1
|
+
# Checklist do Gate 3 — verificador, revisor, segurança
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## Saída (`4-review/evaluator-report.md`)
|
|
3
|
+
Três agentes em contexto fresco, cada um com o seu relatório em `4-review/`. Ninguém dá nota. Cada
|
|
4
|
+
relatório começa igual e é append-only (vale o último `**Verdict:**`):
|
|
7
5
|
|
|
8
6
|
```markdown
|
|
9
7
|
**Verdict:** SHIP ← ou FIX
|
|
8
|
+
**Reviewed-SHA:** <sha do dispatch>
|
|
10
9
|
|
|
11
10
|
## Bloqueadores
|
|
12
11
|
|
|
13
|
-
|
|
|
14
|
-
|
|
15
|
-
| B1 |
|
|
12
|
+
| id | tipo | onde | evidência |
|
|
13
|
+
|----|------|------|-----------|
|
|
14
|
+
| B1 | check | src/Orders/Create.cs:42 | C3: pedido sem itens é aceito — `POST /orders {items:[]}` → 201 |
|
|
16
15
|
|
|
17
16
|
## Notas
|
|
18
17
|
|
|
19
18
|
- (o que melhoraria, sem bloquear)
|
|
20
|
-
|
|
21
|
-
## Navegado
|
|
22
|
-
|
|
23
|
-
- (fluxos exercitados na app, quando o stack estava no ar; o que viu no console/network)
|
|
24
19
|
```
|
|
25
20
|
|
|
26
21
|
Sem bloqueador, escreva `Nenhum.` na seção. `SHIP` com bloqueador aberto é contradição e pausa o gate.
|
|
22
|
+
Confirmação depois de uma correção: seção nova ao fim com `**Verdict:**` e o `**Reviewed-SHA:**` novo —
|
|
23
|
+
julgando só os seus bloqueadores, ou, sem eles, só o delta `{sha anterior}..{sha novo}`. Bloqueador
|
|
24
|
+
que a correção fechou sai de `## Bloqueadores` (escreva `Nenhum.`) e o fechamento vai em `## Notas`;
|
|
25
|
+
a `## Checklist de tela` que vale é a da última seção, então repita-a inteira.
|
|
26
|
+
Parcial (limite de turnos): `PARCIAL` na 1ª linha, o feito e o que falta, SEM `**Verdict:**`.
|
|
27
|
+
|
|
28
|
+
**O que bloqueia — só com evidência de um destes tipos.** Todo o resto (estilo, nome, "eu faria
|
|
29
|
+
diferente") é nota; nota não bloqueia e não pede rodada nova. Na dúvida, sem evidência tipada, é nota.
|
|
30
|
+
|
|
31
|
+
| Tipo | Quem | Quando | Evidência mínima |
|
|
32
|
+
|------|------|--------|------------------|
|
|
33
|
+
| `check` | verificador | cenário `Cn` NÃO PROVADO ou FALHA | o que falta provar, ou o repro que falha |
|
|
34
|
+
| `teste` | verificador | teste vermelho; teste que não morde a regra que diz proteger | saída do verify, mutante sobrevivente, assert que passa pelo motivo errado |
|
|
35
|
+
| `regressao` | revisor | comportamento que funcionava e quebrou | consumidor afetado + o caminho que quebra |
|
|
36
|
+
| `escopo` | revisor | o diff faz o que nenhuma fatia pediu, ou deixa de fazer o que o contrato pede | arquivo/trecho fora das fatias, ou o item do contrato ausente |
|
|
37
|
+
| `arquitetura` | revisor | padrão ou abstração nova contra o stack e os standards | o trecho e o standard que ele contraria |
|
|
38
|
+
| `performance` | revisor | custo que o volume real torna visível | a consulta/lista e o volume que a derruba |
|
|
39
|
+
| `seguranca` | segurança | authz, segredo, injeção, PII, custo sem teto, migration destrutiva | o caminho de exploração |
|
|
40
|
+
|
|
41
|
+
## Verificador (`4-review/verifier.md`)
|
|
42
|
+
|
|
43
|
+
Missão: **provar cada cenário** do `2-plan/checks.json`. O default de todo `Cn` é NÃO PROVADO;
|
|
44
|
+
critério que você não verificou nunca vira PROVADO por inferência.
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
| Tipo | Quando | Evidência mínima |
|
|
31
|
-
|------|--------|------------------|
|
|
32
|
-
| `uc` | pós-condição de UC ou `doneCriteria` não atendida | repro (comando, request, teste) ou o trecho que prova |
|
|
33
|
-
| `teste` | teste vermelho; teste novo que não morde a regra que diz proteger | saída do verify, mutante sobrevivente, ou o assert que passa pelo motivo errado |
|
|
34
|
-
| `regressao` | comportamento que funcionava e quebrou | teste ou repro contra o comportamento anterior |
|
|
35
|
-
| `seguranca` | segredo exposto, authz faltando, injeção, dado pessoal vazando, custo sem teto | o caminho de exploração |
|
|
46
|
+
```markdown
|
|
47
|
+
## Checks
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
| Cn | status | evidência |
|
|
50
|
+
|----|--------|-----------|
|
|
51
|
+
| C1 | PROVADO | `src/Orders/Create.cs:42` + `OrdersTests.RecusaSemItens` (morde: M1 morto) |
|
|
52
|
+
| C2 | NÃO PROVADO | nenhum teste exercita o caminho de escrita |
|
|
53
|
+
```
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
Uma linha por cenário, status exato `PROVADO`, `NÃO PROVADO` ou `FALHA`. O gate pausa com cenário
|
|
56
|
+
sem linha ou não PROVADO. Numa confirmação, uma `## Checks` nova só com os Cn que mudaram: vale a
|
|
57
|
+
última linha de cada Cn. Para cada `Cn`, em ordem de dano:
|
|
41
58
|
|
|
42
|
-
1. **
|
|
59
|
+
1. **Onde vale**: `arquivo:linha` do código que entrega o cenário.
|
|
43
60
|
2. **Fiação**: existe caminho da entrada real ao efeito observável? A camada pura testada não prova
|
|
44
61
|
que o handler a chama. Inventarie quem consome o eixo novo.
|
|
45
|
-
3. **
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
3. **O teste morde?** Nasceu verde e afirma o que já era verdade; só testa a recusa e nunca o caminho
|
|
63
|
+
de escrita; guard cujos dois lados passam pelo mesmo serializador; asserção sobre texto que mede
|
|
64
|
+
vocabulário, não sentido.
|
|
48
65
|
4. **Verificações mecânicas** (.NET minimal API / Next): todo serviço injetado numa lambda de endpoint
|
|
49
66
|
tem registro em `Program.cs` (senão a app não sobe); prefixo de rota server-direct e de proxy são
|
|
50
67
|
constantes distintas, cada uma usada só do seu lado.
|
|
51
|
-
5. **
|
|
52
|
-
|
|
68
|
+
5. **Runtime**: com o stack no ar (URL no dispatch), navegue o fluxo do cenário e confira console e
|
|
69
|
+
network. Stack fora do ar é achado de infra, não defeito seu a provar.
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
A suíte inteira é do orquestrador. Você roda no máximo um filtro de testes de um cenário, em primeiro
|
|
72
|
+
plano, por `morph-spec heavy run -- <comando>`.
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
**Prova por mutação — opcional, no máximo 3, só depois que revisor e segurança terminaram.** Só
|
|
75
|
+
quando o diff tem uma linha de guarda ou regra que um cenário exige e você não achou o teste que a
|
|
57
76
|
morde. Escreva `4-review/mutants.json` e rode `morph-spec mutate {feature}` **uma vez**:
|
|
58
77
|
|
|
59
78
|
```json
|
|
@@ -63,20 +82,15 @@ morde. Escreva `4-review/mutants.json` e rode `morph-spec mutate {feature}` **um
|
|
|
63
82
|
|
|
64
83
|
`expect` é obrigatório: diga qual teste deve falhar e por quê **antes** de rodar — mutante cujo
|
|
65
84
|
efeito você não sabe prever tende a ser equivalente. Barra invertida do código vira `{{BS}}`.
|
|
66
|
-
Sobreviveu numa linha que um
|
|
67
|
-
`## Mutações` que a ferramenta imprime. A reversão é da ferramenta; rodada morta no meio:
|
|
85
|
+
Sobreviveu numa linha que um cenário exige → bloqueador `teste`. Sobreviveu em outra → nota. Cole a
|
|
86
|
+
seção `## Mutações` que a ferramenta imprime. A reversão é da ferramenta; rodada morta no meio:
|
|
68
87
|
`morph-spec mutate --restore`.
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
**Feature que gera mídia** (alguma task cita em `standards[]` `standards/ai-agents/modalities-image-gen.md`,
|
|
89
|
+
**Feature que gera mídia** (alguma fatia cita em `standards:` `standards/ai-agents/modalities-image-gen.md`,
|
|
73
90
|
`media-video.md` ou `media-pipeline.md`): grave `## Mídia gerada`, uma linha por afirmação — custo
|
|
74
91
|
controlado, revisão humana, mídia marcada, hot path não bloqueado —, cada uma com o código que a prende
|
|
75
92
|
e o teste que a prova. Afirmação falsa é bloqueador. Sem a seção, o gate pausa.
|
|
76
93
|
|
|
77
|
-
**Feature que chama LLM**: dá para reconstruir o que foi ao provedor e o que ele respondeu neste turno?
|
|
78
|
-
Não → nota, ou bloqueador quando um UC exige rastreabilidade.
|
|
79
|
-
|
|
80
94
|
**Prompt ou modelo alterado**: o nó `evals` do verify é a prova; sem ele, o gate pausa sozinho.
|
|
81
95
|
|
|
82
96
|
**Contrato de tela** (`1-design/mockups.md` com `## Tela:`): grave `## Checklist de tela`, uma linha por
|
|
@@ -86,5 +100,32 @@ item decidido de cada tela:
|
|
|
86
100
|
|
|
87
101
|
Veredito `cumpre` / `diverge` / `não verificável` (com o motivo). Paginação, ordenação ou filtro que o
|
|
88
102
|
checklist põe no servidor e a tela faz em memória, ou estado vazio/carregando/erro decidido e
|
|
89
|
-
ausente, é bloqueador `
|
|
90
|
-
item decidido, o gate pausa.
|
|
103
|
+
ausente, é bloqueador `check`. Divergência registrada em `plan.md ## Decisões` é nota. Sem a seção,
|
|
104
|
+
havendo item decidido, o gate pausa.
|
|
105
|
+
|
|
106
|
+
## Revisor (`4-review/reviewer.md`)
|
|
107
|
+
|
|
108
|
+
Read-only, sobre o commit do dispatch: `git diff {base}..{sha}`, `git show {sha}:<caminho>`. Nunca a
|
|
109
|
+
árvore de trabalho — o verificador pode estar com um mutante aplicado nela. Default SHIP.
|
|
110
|
+
|
|
111
|
+
1. **Escopo**: o diff contra as fatias (`arquivos:`) e o `## Contrato de implementação` do plano.
|
|
112
|
+
2. **Arquitetura**: padrão ou abstração nova contra o stack do projeto e os standards (camada que o
|
|
113
|
+
projeto não usa, repositório sobre o DbContext, dependência nova sem decisão no plano).
|
|
114
|
+
3. **Regressão**: para cada símbolo público alterado, `morph-spec graph affected <símbolo> --prod --json`.
|
|
115
|
+
Sem grafo (`no-graph`, `not-found`, `ambiguous`), caia para `git grep -n <símbolo> {sha}` e siga — a
|
|
116
|
+
ausência de grafo nunca bloqueia, e ausência de consumidor nunca sai do grafo.
|
|
117
|
+
4. **O que a correção abriu**: revisando a correção de uma rodada anterior, releia o comportamento
|
|
118
|
+
vizinho ao que ela mudou.
|
|
119
|
+
5. **Performance só com evidência**: lista sem paginação no servidor sobre volume real, N+1 visível.
|
|
120
|
+
6. **Chamada a LLM**: dá para reconstruir o que foi ao provedor e o que ele respondeu neste turno?
|
|
121
|
+
Não → nota.
|
|
122
|
+
|
|
123
|
+
## Segurança (`4-review/security.md`)
|
|
124
|
+
|
|
125
|
+
Despachada só quando o diff toca auth, segredo, permissão, dado pessoal, pagamento ou migration.
|
|
126
|
+
Mesmo contrato de leitura do revisor (só o SHA). Procure: authz faltando ou checada do lado errado;
|
|
127
|
+
segredo no código, no log ou no cliente; injeção (SQL, comando, prompt); dado pessoal vazando para log,
|
|
128
|
+
resposta ou provedor; custo sem teto (chamada paga em laço, sem limite por usuário); mídia publicada
|
|
129
|
+
sem revisão humana; migration destrutiva (DROP/rename de coluna com dado, branch Neon de produção).
|
|
130
|
+
Bloqueador `seguranca` exige o caminho de exploração — quem, por qual entrada, chega a quê. Sem
|
|
131
|
+
caminho, é nota.
|
|
@@ -12,7 +12,8 @@ framework/hooks/
|
|
|
12
12
|
│ │ ├── worktree-create.js # WorktreeCreate: governed tree via `morph-spec worktree setup|provision`
|
|
13
13
|
│ │ ├── worktree-remove.js # WorktreeRemove: safe teardown via `worktree remove --if-clean`
|
|
14
14
|
│ │ ├── block-destructive-worktree-removal.js # PreToolUse(Bash): raw removal follows junctions out
|
|
15
|
-
│ │
|
|
15
|
+
│ │ ├── block-raw-worktree-add.js # PreToolUse(Bash): raw `git worktree add` is ungoverned
|
|
16
|
+
│ │ └── warn-bash-source-write.js # PreToolUse(Bash): source written by redirect skips the Write/Edit guards
|
|
16
17
|
│ ├── user-prompt/
|
|
17
18
|
│ │ └── enrich-prompt.js # Context-aware prompt enrichment
|
|
18
19
|
│ ├── pre-tool-use/
|
|
@@ -32,8 +33,7 @@ framework/hooks/
|
|
|
32
33
|
├── git/ # Git hooks (Bash)
|
|
33
34
|
│ ├── pre-commit/
|
|
34
35
|
│ │ ├── orchestrator.sh # Master hook dispatcher
|
|
35
|
-
│ │
|
|
36
|
-
│ │ └── specs.sh # Validates spec.md sections
|
|
36
|
+
│ │ └── agents.sh # Validates agents.json schema
|
|
37
37
|
│ ├── commit-msg/
|
|
38
38
|
│ │ └── conventional-commits.sh # Enforces conventional commits
|
|
39
39
|
│ └── pre-push/
|
|
@@ -45,18 +45,17 @@ framework/hooks/
|
|
|
45
45
|
|
|
46
46
|
| Event | Hook | Type | Purpose |
|
|
47
47
|
|-------|------|------|---------|
|
|
48
|
-
| **SessionStart** | worktree-context.js | Context inject | Secondary worktree → feature identity (name/branch/phase) + missing-junction warning; primary root → active-feature summary (phase,
|
|
48
|
+
| **SessionStart** | worktree-context.js | Context inject | Secondary worktree → feature identity (name/branch/phase) + missing-junction warning; primary root → active-feature summary (phase, slices done/total, pending gates), silent if none. Also seeds a worktree that has no codebase graph from the root's, and launches the background refresh of the ROOT's graph (`shared/graph-refresh.js`) |
|
|
49
49
|
| **PreToolUse** (Write\|Edit) | _(native permissions.deny)_ | Block | Blocks edits to state.json and .morph/framework/ |
|
|
50
|
-
| **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to
|
|
50
|
+
| **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to `plan.md` (and the uiux artifacts) after their gate is approved; an append-only `## Errata` block at the end of `plan.md` passes |
|
|
51
51
|
| **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
|
|
52
52
|
| **PreToolUse** (Bash) | pre-bash-denylist.js | Block | Blocks `rm -rf .morph/` and direct state edits |
|
|
53
53
|
| **PreToolUse** (Bash) | block-destructive-worktree-removal.js | Block | Blocks `git worktree remove` / `rm -rf` / `Remove-Item -Recurse` on a directory holding reparse points (the infra junctions a raw removal would FOLLOW out, emptying the root's `.claude/`); names `morph-spec worktree remove` |
|
|
54
54
|
| **PreToolUse** (Bash) | block-raw-worktree-add.js | Block | Blocks raw `git worktree add` (a tree with no junctions → no hooks, no port block, no identity, outside `worktrees/`); names `morph-spec worktree setup <feature>` / `worktree provision <name> --base <ref>`. `list`/`prune`/`remove`/`lock`/`move`/`repair` pass |
|
|
55
|
+
| **PreToolUse** (Bash) | warn-bash-source-write.js | Warn | Heredoc/redirect/`tee` into project source (`isProjectSource`): context naming the file, the command still runs — Write/Edit is what phase, protected-file and comment guards see |
|
|
55
56
|
| **WorktreeCreate** | worktree-create.js | Replaces native | Fires for `claude -w`, EnterWorktree and `Agent isolation:"worktree"`; replaces Claude Code's bare `git worktree add`. Runs `morph-spec worktree setup <n>` (known feature) or `worktree provision <n>` (task tree, branch `morph-task/<n>`), bases a task tree on the dispatcher's `morph/<f>` branch when `worktree.baseRef` is unset (`"head"` = current HEAD, `"fresh"` = default branch), prints the path as its last stdout line. CLI unreachable → plain git, still under `<root>/worktrees/` |
|
|
56
57
|
| **WorktreeRemove** | worktree-remove.js | Fail-open | Routes Claude Code's own cleanup through `morph-spec worktree remove --target --if-clean` (junctions dropped first; a dirty tree is left alone). Always exit 0. Secondary net — the event was observed NOT to fire headless; the Bash guard above stays the primary defense |
|
|
57
|
-
| **PostToolUse** (Write\|Edit) |
|
|
58
|
-
| **PostToolUse** (Write\|Edit) | trace-autogen.js | State sync | Auto-generates `.morph/traces/YYYY-MM-DD-{feature}-implement.json` from tasks.json + recap.md on every tasks.json write — the skills never write traces by hand |
|
|
59
|
-
| **PostToolUse** (Write\|Edit) | comment-guard.js | Blocking | Blocks when the edit ADDED a comment the `architecture-code-comments` standard never allows: process history (UC/ADR/task/gate), banner/`#region`, a block over 3 lines, or a doc-comment outside a published package. Old comments are never reported; last link of the chain |
|
|
58
|
+
| **PostToolUse** (Write\|Edit) | comment-guard.js | Blocking | Blocks when the edit ADDED a comment the `architecture-code-comments` standard never allows: process history (ADR, scenario/slice id, gate, plan.md), banner/`#region`, a block over 3 lines, or a doc-comment outside a published package. Old comments are never reported; last link of the chain |
|
|
60
59
|
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | state-sync.js | State sync | Autonomous state updates (feature register, output tracking, gate approval) |
|
|
61
60
|
| **PostToolUse** (Write\|Edit\|AskUserQuestion) | gate-guard.js | Advisory | Warns (never blocks) when a gate action disagrees with the persisted gateDecisions; logs one `coherent`/`divergent` line per check in `.morph/logs/activity.jsonl` (issue #63) |
|
|
62
61
|
| **PostToolUse** (all tools) | telemetry-log.js | Telemetry | Appends every tool call (skill, sub-agent dispatch + prompt, knowledge-base read, bash/edit) to `.morph/logs/events.jsonl`; silent, fail-open — the NLH execution-path capture read back by `morph-spec telemetry` |
|
|
@@ -66,7 +65,7 @@ framework/hooks/
|
|
|
66
65
|
| **PostToolBatch** | telemetry-log.js | Telemetry | One `ToolBatch` event per model turn that used tools (the turn counter of `cost --metrics`) |
|
|
67
66
|
| **SubagentStop** | telemetry-log.js | Telemetry | `AgentUsage`: the sub-agent's token usage read from its own transcript, as a delta per agent; never blocks the stop |
|
|
68
67
|
| **UserPromptSubmit** | telemetry-log.js | Telemetry | Appends the user's prompt to the same `events.jsonl` stream; silent, fail-open |
|
|
69
|
-
| **Stop** | validate-completion.js | Advisory | Warns about incomplete
|
|
68
|
+
| **Stop** | validate-completion.js | Advisory | Warns about incomplete slice reports (hash-deduped, only re-injects when the warning set changes) |
|
|
70
69
|
|
|
71
70
|
## Design Principles
|
|
72
71
|
|
|
@@ -150,8 +149,8 @@ Claude Code sends JSON to stdin: { tool_input: { file_path: "..." } }
|
|
|
150
149
|
↓
|
|
151
150
|
protect-spec-files.js
|
|
152
151
|
├── Is in .morph/features/{feature}/?
|
|
153
|
-
│ ├── Is
|
|
154
|
-
│ ├── Is
|
|
152
|
+
│ ├── Is plan.md and plan gate approved? → BLOCK (unless append-only ## Errata)
|
|
153
|
+
│ ├── Is a 1-design/ artifact and uiux gate approved? → BLOCK
|
|
155
154
|
│ └── Gate not approved → pass
|
|
156
155
|
└── Not a feature file → pass
|
|
157
156
|
↓
|
|
@@ -23,7 +23,7 @@ import { isGenerated, isProjectSource } from '../../shared/source-files.js';
|
|
|
23
23
|
const MAX_LISTED = 10;
|
|
24
24
|
|
|
25
25
|
const RULE_LABEL = {
|
|
26
|
-
process: 'história de processo (
|
|
26
|
+
process: 'história de processo (ADR, cenário, fatia, gate, plan.md)',
|
|
27
27
|
banner: 'banner ou #region',
|
|
28
28
|
'doc-comment': '///·JSDoc em código que não é pacote publicado',
|
|
29
29
|
'long-block': `bloco com mais de ${MAX_BLOCK_LINES} linhas`,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.2.0",
|
|
3
3
|
"patterns": [
|
|
4
|
-
"rm -rf /",
|
|
5
|
-
"rm -rf ~",
|
|
6
4
|
"DROP TABLE",
|
|
7
5
|
"DROP DATABASE",
|
|
8
6
|
"format c:",
|
|
@@ -10,5 +8,37 @@
|
|
|
10
8
|
"> /dev/sda",
|
|
11
9
|
"dd if=/dev/zero",
|
|
12
10
|
":(){:|:&};:"
|
|
11
|
+
],
|
|
12
|
+
"executableArgs": [
|
|
13
|
+
{
|
|
14
|
+
"executable": "taskkill",
|
|
15
|
+
"args": [
|
|
16
|
+
"/im",
|
|
17
|
+
"-im"
|
|
18
|
+
],
|
|
19
|
+
"reason": "mata por nome de imagem e derruba os MCPs de todas as sessões; encerre pelo PID que você subiu (`/PID <n>`)"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"executable": "rm",
|
|
23
|
+
"flags": [
|
|
24
|
+
[
|
|
25
|
+
"-r",
|
|
26
|
+
"-R",
|
|
27
|
+
"--recursive"
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"-f",
|
|
31
|
+
"--force"
|
|
32
|
+
]
|
|
33
|
+
],
|
|
34
|
+
"args": [
|
|
35
|
+
"/",
|
|
36
|
+
"/*",
|
|
37
|
+
"~",
|
|
38
|
+
"~/",
|
|
39
|
+
"~/*"
|
|
40
|
+
],
|
|
41
|
+
"reason": "apaga a raiz ou o home inteiro; remova o caminho específico"
|
|
42
|
+
}
|
|
13
43
|
]
|
|
14
44
|
}
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Event: PreToolUse | Matcher: Write|Edit
|
|
7
7
|
*
|
|
8
8
|
* Ensures writes to .morph/features/{feature}/ go to the current phase's subfolder:
|
|
9
|
-
*
|
|
9
|
+
* plan → only 2-plan/ (and 2-plan/ is writable in every phase: the errata
|
|
10
|
+
* and plan-check live there)
|
|
10
11
|
* uiux → only 1-design/
|
|
11
|
-
* plan → only 2-plan/
|
|
12
12
|
* implement → 3-implement/ + any source code (unrestricted)
|
|
13
13
|
* review → only 4-review/
|
|
14
14
|
*
|
|
@@ -30,15 +30,16 @@ import {
|
|
|
30
30
|
* The gate that authorizes writing a phase folder's FIRST artifact. Once that
|
|
31
31
|
* gate is approved, the next phase's canonical file may be created even though
|
|
32
32
|
* the phase only advances (via derivation) once that file exists — this is the
|
|
33
|
-
* fix for the forward-transition chicken-and-egg.
|
|
34
|
-
* the optional `1-design/` and `
|
|
33
|
+
* fix for the forward-transition chicken-and-egg. The plan gate unlocks both
|
|
34
|
+
* the optional `1-design/` and `3-implement/` (the uiux fork).
|
|
35
35
|
*/
|
|
36
36
|
const PHASE_DIR_ENTRY_GATE = {
|
|
37
|
-
'1-design': '
|
|
38
|
-
'2-plan': 'proposal',
|
|
37
|
+
'1-design': 'plan',
|
|
39
38
|
'3-implement': 'plan',
|
|
40
39
|
'4-review': 'plan',
|
|
41
40
|
};
|
|
41
|
+
|
|
42
|
+
const ALWAYS_WRITABLE_DIR = '2-plan';
|
|
42
43
|
import { blockResult, runStandalone } from '../../shared/hook-response.js';
|
|
43
44
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
44
45
|
import { getFilePath, resolveMorphRoot, resolveTargetPath } from '../../shared/payload-utils.js';
|
|
@@ -67,9 +68,8 @@ export async function run(payload) {
|
|
|
67
68
|
// Resolve the tree that OWNS this file, NOT the tree the session sits in.
|
|
68
69
|
// A session rooted in the primary repo may legitimately write to a feature
|
|
69
70
|
// living in a secondary worktree; reading the session's `.morph/` there
|
|
70
|
-
// reported the ROOT's phase for the WORKTREE's file
|
|
71
|
-
// `
|
|
72
|
-
// must be asked of that same tree — a worktree carries its own state.
|
|
71
|
+
// reported the ROOT's phase for the WORKTREE's file and blocked a legal write.
|
|
72
|
+
// `stateExists` must be asked of that same tree — a worktree carries its own state.
|
|
73
73
|
const root = resolveMorphRoot(filePath, process.cwd());
|
|
74
74
|
if (!stateExists(root)) return null;
|
|
75
75
|
|
|
@@ -84,6 +84,7 @@ export async function run(payload) {
|
|
|
84
84
|
|
|
85
85
|
const targetDir = extractPhaseDir(filePath);
|
|
86
86
|
if (!targetDir) return null; // File directly in feature root — allow
|
|
87
|
+
if (targetDir === ALWAYS_WRITABLE_DIR) return null;
|
|
87
88
|
|
|
88
89
|
const allowedDir = PHASE_DIRS[phase];
|
|
89
90
|
if (!allowedDir) return null; // Unknown phase — allow
|
|
@@ -102,14 +103,9 @@ export async function run(payload) {
|
|
|
102
103
|
// advances once that file exists) — the deadlock this hook used to create.
|
|
103
104
|
const entryGate = PHASE_DIR_ENTRY_GATE[targetDir];
|
|
104
105
|
if (entryGate && isGateApproved(featureName, entryGate, root)) {
|
|
105
|
-
// uiux is
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
// HAS opted into uiux (1-design/ exists), that gate is not
|
|
109
|
-
// decorative: 2-plan/ must wait for it too, or a feature can skip
|
|
110
|
-
// straight from an approved proposal into planning with its design
|
|
111
|
-
// gate silently left pending.
|
|
112
|
-
const tookDesignBranch = targetDir === '2-plan'
|
|
106
|
+
// uiux is optional, but once 1-design/ exists its gate is not
|
|
107
|
+
// decorative: implementation waits for it too.
|
|
108
|
+
const tookDesignBranch = targetDir !== '1-design'
|
|
113
109
|
&& existsSync(resolveTargetPath(`.morph/features/${featureName}/1-design`, root));
|
|
114
110
|
|
|
115
111
|
if (!tookDesignBranch || isGateApproved(featureName, 'uiux', root)) {
|
|
@@ -119,9 +115,9 @@ export async function run(payload) {
|
|
|
119
115
|
|
|
120
116
|
logHookActivity('enforce-phase-writes', 'PreToolUse', 'blocked');
|
|
121
117
|
return blockResult(
|
|
122
|
-
`MORPH-SPEC: Writing to '
|
|
118
|
+
`MORPH-SPEC: Writing to '${targetDir}/' is not allowed yet.\n` +
|
|
123
119
|
`This feature entered the uiux branch (1-design/ exists) and the 'uiux' gate is still pending.\n\n` +
|
|
124
|
-
`Approve the design gate first, then
|
|
120
|
+
`Approve the design gate first, then move on to implementation:\n` +
|
|
125
121
|
` morph-spec approve ${featureName} uiux\n` +
|
|
126
122
|
` morph-spec advance ${featureName}`
|
|
127
123
|
);
|
|
@@ -129,7 +125,7 @@ export async function run(payload) {
|
|
|
129
125
|
|
|
130
126
|
const phaseLabel = phase.toUpperCase();
|
|
131
127
|
// Name the tree the verdict came from whenever it is NOT the session's own.
|
|
132
|
-
// The phase read is per-tree, so "which .morph/ said
|
|
128
|
+
// The phase read is per-tree, so "which .morph/ said plan?" is the first
|
|
133
129
|
// question anyone debugging this asks.
|
|
134
130
|
const treeNote = root !== process.cwd().replace(/\\/g, '/')
|
|
135
131
|
? `\n(phase read from the tree that owns the file: ${root})`
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { loadState, stateExists, getActiveFeature } from '../../shared/state-reader.js';
|
|
28
28
|
import { contextResult, runStandalone } from '../../shared/hook-response.js';
|
|
29
|
+
import { gateFromQuestion } from '../../shared/gate-question.js';
|
|
29
30
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
30
31
|
import { resolveMorphRoot, resolveTargetPath } from '../../shared/payload-utils.js';
|
|
31
32
|
import { locateFeatureForQuestion } from '../../shared/feature-locator.js';
|
|
@@ -68,16 +69,6 @@ function askedTexts(input) {
|
|
|
68
69
|
return texts;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
/** Map a gate question's text to a gate id. Mirrors state-sync.js. */
|
|
72
|
-
function gateFromQuestion(question) {
|
|
73
|
-
if (typeof question !== 'string') return null;
|
|
74
|
-
const lower = question.toLowerCase();
|
|
75
|
-
if (lower.includes('gate 1') || lower.includes('proposta')) return 'proposal';
|
|
76
|
-
if (lower.includes('gate 2') || lower.includes('plano') || lower.includes('plan')) return 'plan';
|
|
77
|
-
if (lower.includes('gate 3') || lower.includes('review')) return 'review';
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
72
|
function getDecision(state, feature, gate) {
|
|
82
73
|
const d = state.features?.[feature]?.gateDecisions?.[gate];
|
|
83
74
|
if (!d || typeof d.pause !== 'boolean') return null;
|
|
@@ -154,7 +145,7 @@ function checkStateEdit(input, sessionRoot) {
|
|
|
154
145
|
|
|
155
146
|
let checked = false;
|
|
156
147
|
const gates = state.features?.[feature]?.approvalGates ?? {};
|
|
157
|
-
for (const gate of ['
|
|
148
|
+
for (const gate of ['plan', 'uiux', 'review']) {
|
|
158
149
|
const g = gates[gate];
|
|
159
150
|
const autoApproved = g?.approved === true && g?.approvedBy === 'auto';
|
|
160
151
|
if (!autoApproved) continue;
|
|
@@ -4,8 +4,39 @@ import { join, dirname } from 'path';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { blockResult, runStandalone } from '../../shared/hook-response.js';
|
|
6
6
|
import { stateExists } from '../../shared/state-reader.js';
|
|
7
|
+
import { tokenize } from '../../shared/command-tokens.js';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const SEPARATOR = /^[;&|]+$/;
|
|
11
|
+
const executableName = (token) => token.replace(/\\/g, '/').split('/').pop().toLowerCase().replace(/\.exe$/, '');
|
|
12
|
+
|
|
13
|
+
const hasFlag = (token, flag) => (flag.startsWith('--') || token.startsWith('--')
|
|
14
|
+
? token === flag
|
|
15
|
+
: /^-[A-Za-z]+$/.test(token) && token.includes(flag.slice(1)));
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A quoted argument is one token, never the executable: mentioning the command in a message is not calling it.
|
|
19
|
+
* `args` match a whole token; each group in `flags` (case-sensitive, clusters like `-rf` count) must appear in the same command.
|
|
20
|
+
*/
|
|
21
|
+
export function matchesExecutableRule(command, rule) {
|
|
22
|
+
const exe = rule.executable.toLowerCase();
|
|
23
|
+
const args = new Set(rule.args.map((a) => a.toLowerCase()));
|
|
24
|
+
const flagGroups = rule.flags || [];
|
|
25
|
+
const tokens = tokenize(command);
|
|
26
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
27
|
+
if (executableName(tokens[i].replace(/^[;&|(]+/, '')) !== exe) continue;
|
|
28
|
+
const seen = [];
|
|
29
|
+
let argHit = false;
|
|
30
|
+
for (let j = i + 1; j < tokens.length && !SEPARATOR.test(tokens[j]); j++) {
|
|
31
|
+
const raw = tokens[j].replace(/[;&|)]+$/, '');
|
|
32
|
+
if (args.has(raw.toLowerCase())) argHit = true;
|
|
33
|
+
seen.push(raw);
|
|
34
|
+
if (tokens[j] !== raw) break;
|
|
35
|
+
}
|
|
36
|
+
if (argHit && flagGroups.every((group) => seen.some((t) => group.some((flag) => hasFlag(t, flag))))) return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
9
40
|
|
|
10
41
|
/**
|
|
11
42
|
* @param {object} payload - parsed PreToolUse stdin payload (Bash matcher)
|
|
@@ -26,6 +57,13 @@ export async function run(payload) {
|
|
|
26
57
|
}
|
|
27
58
|
}
|
|
28
59
|
|
|
60
|
+
for (const rule of denylist.executableArgs || []) {
|
|
61
|
+
if (matchesExecutableRule(command, rule)) {
|
|
62
|
+
const shown = [rule.executable, ...(rule.flags || []).map((group) => group[0]), rule.args[0]].join(' ');
|
|
63
|
+
return blockResult(`Comando bloqueado pela denylist: \`${shown}\` ${rule.reason}.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
return null;
|
|
30
68
|
} catch {
|
|
31
69
|
return null;
|