@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,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { getFeature, updateFeature } from '../../core/state/state-manager.js';
|
|
4
|
-
import { runVerify,
|
|
4
|
+
import { runVerify, VerifyUsageError } from '../../lib/validators/verify-runner.js';
|
|
5
5
|
import { resolvePrimaryRoot } from '../project/worktree.js';
|
|
6
6
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
7
7
|
import { canonicalPath, readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
@@ -12,7 +12,7 @@ import { treeFingerprint } from '../../lib/git/tree-fingerprint.js';
|
|
|
12
12
|
import { HeavyBusyError } from '../../lib/heavy/gate.js';
|
|
13
13
|
import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
|
|
14
14
|
import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
|
|
15
|
-
import { runCiVerify, CiUsageError } from '../../lib/ci/ci-verify.js';
|
|
15
|
+
import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
|
|
16
16
|
|
|
17
17
|
/** ✓ pass · ✗ fail · – skip */
|
|
18
18
|
function statusIcon(status) {
|
|
@@ -21,40 +21,12 @@ function statusIcon(status) {
|
|
|
21
21
|
return chalk.gray('–');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
* One line per test run naming what the task-scope recorte did to it (#87):
|
|
26
|
-
* narrowed (how many selectors, from how many test files), whole, out of scope
|
|
27
|
-
* or full, each with its reason. A green `tests` line alone cannot say that the
|
|
28
|
-
* task ran 12 tests instead of 3.082. Feature-scope runs carry no selection and
|
|
29
|
-
* print nothing extra; `feedback[]` is untouched either way.
|
|
30
|
-
*
|
|
31
|
-
* @param {Array<{id: string, selection: object|null}>} [runs]
|
|
32
|
-
* @returns {string[]}
|
|
33
|
-
*/
|
|
34
|
-
function selectionLines(runs = []) {
|
|
35
|
-
return runs
|
|
36
|
-
.filter((run) => run.selection)
|
|
37
|
-
.map((run) => {
|
|
38
|
-
const { mode, reason, selectors = [], files = [] } = run.selection;
|
|
39
|
-
const detail = mode === 'narrowed'
|
|
40
|
-
? `${selectors.length} selector(s) from ${files.length} test file(s)`
|
|
41
|
-
: (reason ?? '');
|
|
42
|
-
return chalk.gray(` ↳ ${run.id.padEnd(10)} ${mode.padEnd(12)} ${detail}`);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Human-readable rendering of a runVerify() result — mirrors the
|
|
48
|
-
* ✓/✗-per-node + feedback-list style of formatScoreBreakdown() in
|
|
49
|
-
* score-aggregator.js, adapted to verify's build/tests/validators nodes.
|
|
50
|
-
*/
|
|
51
24
|
function formatVerifyResult(result) {
|
|
52
25
|
const lines = [];
|
|
53
|
-
const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
|
|
54
26
|
|
|
55
27
|
lines.push('');
|
|
56
|
-
lines.push(chalk.bold(`Verify — ${
|
|
57
|
-
lines.push(chalk.gray(`
|
|
28
|
+
lines.push(chalk.bold(`Verify — ${result.feature}`));
|
|
29
|
+
if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
|
|
58
30
|
lines.push('');
|
|
59
31
|
lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
|
|
60
32
|
lines.push('');
|
|
@@ -69,8 +41,6 @@ function formatVerifyResult(result) {
|
|
|
69
41
|
let detail;
|
|
70
42
|
if (node.status === 'skip') {
|
|
71
43
|
detail = node.reason || '';
|
|
72
|
-
} else if (name === 'validators' && node.score != null) {
|
|
73
|
-
detail = `score ${node.score}/10`;
|
|
74
44
|
} else if (node.reason) {
|
|
75
45
|
// A green e2e still carries meaning worth showing: smoke-only vs specs,
|
|
76
46
|
// warm reuse vs rebuild. Without it, "pass" hides how much was proven.
|
|
@@ -79,7 +49,6 @@ function formatVerifyResult(result) {
|
|
|
79
49
|
detail = `${node.durationMs}ms`;
|
|
80
50
|
}
|
|
81
51
|
lines.push(` ${icon} ${name.padEnd(11)} ${node.status.padEnd(5)} ${chalk.gray(detail)}`);
|
|
82
|
-
if (name === 'tests') lines.push(...selectionLines(node.runs));
|
|
83
52
|
}
|
|
84
53
|
|
|
85
54
|
if (result.feedback.length > 0) {
|
|
@@ -139,7 +108,7 @@ function baselineStampBlock(baseline) {
|
|
|
139
108
|
|
|
140
109
|
/**
|
|
141
110
|
* The per-node status map, in the shape `feature.schema.json` fixes for BOTH
|
|
142
|
-
* writers of it — the
|
|
111
|
+
* writers of it — the history entry (`verifications.feature`) and the
|
|
143
112
|
* singular gate stamp (`verification.nodes`). One function because they were
|
|
144
113
|
* two literal copies twenty-five lines apart: a fifth node meant remembering
|
|
145
114
|
* both, and a stamp missing a node the history has is precisely the kind of
|
|
@@ -163,7 +132,7 @@ function nodeStatuses(result) {
|
|
|
163
132
|
*
|
|
164
133
|
* The rollup cannot answer the question Gate 3 asks. A red eval and a red unit
|
|
165
134
|
* test both arrive as `tests: 'fail'`, and — worse — an eval node that was
|
|
166
|
-
* SKIPPED (
|
|
135
|
+
* SKIPPED (`--filter`) leaves the rollup green, so a gate reading `tests` alone
|
|
167
136
|
* cannot tell "the eval ran and passed" from "the eval never ran".
|
|
168
137
|
*
|
|
169
138
|
* Four values, and the distinction between the last two is the whole point:
|
|
@@ -186,7 +155,7 @@ function evalsStatusOf(result) {
|
|
|
186
155
|
}
|
|
187
156
|
|
|
188
157
|
/**
|
|
189
|
-
* The options that make a
|
|
158
|
+
* The options that make a run partial, each with the `reason` it
|
|
190
159
|
* reports when it keeps the stamp from being written — first match wins.
|
|
191
160
|
*/
|
|
192
161
|
const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
|
|
@@ -197,10 +166,9 @@ const CI_EXCLUSIVE_FLAGS = [
|
|
|
197
166
|
['noPersist', '--no-persist'],
|
|
198
167
|
];
|
|
199
168
|
|
|
200
|
-
async function runLocalVerify(featureName,
|
|
169
|
+
async function runLocalVerify(featureName, options) {
|
|
201
170
|
try {
|
|
202
|
-
return await runVerify(process.cwd(), featureName,
|
|
203
|
-
rubric: options.rubric,
|
|
171
|
+
return await runVerify(process.cwd(), featureName, {
|
|
204
172
|
timeout: options.timeout,
|
|
205
173
|
skipTests: options.skipTests,
|
|
206
174
|
skipBuild: options.skipBuild,
|
|
@@ -211,12 +179,9 @@ async function runLocalVerify(featureName, taskId, options) {
|
|
|
211
179
|
refreshCache: options.refreshCache,
|
|
212
180
|
linux: options.linux,
|
|
213
181
|
linuxImage: options.linuxImage,
|
|
214
|
-
// Task scope: switch the recorte off (#87). At feature scope nothing is
|
|
215
|
-
// ever narrowed, so the flag is a documented no-op there, not an error.
|
|
216
|
-
full: options.full,
|
|
217
182
|
});
|
|
218
183
|
} catch (err) {
|
|
219
|
-
// A wrong invocation is exit 2, like an unknown feature
|
|
184
|
+
// A wrong invocation is exit 2, like an unknown feature — never a
|
|
220
185
|
// verdict. `--filter` that selects nothing must not print green (or red):
|
|
221
186
|
// there is no verification to report, only a command to fix.
|
|
222
187
|
if (err instanceof VerifyUsageError) {
|
|
@@ -240,7 +205,7 @@ async function runLocalVerify(featureName, taskId, options) {
|
|
|
240
205
|
}
|
|
241
206
|
|
|
242
207
|
/**
|
|
243
|
-
* `morph-spec verify <feature
|
|
208
|
+
* `morph-spec verify <feature>` — the deterministic code-before-judge
|
|
244
209
|
* gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
|
|
245
210
|
* for the node cadence; this command wraps it with CLI ergonomics
|
|
246
211
|
* (human/JSON output, exit codes) and context-aware persistence.
|
|
@@ -251,16 +216,14 @@ async function runLocalVerify(featureName, taskId, options) {
|
|
|
251
216
|
* Exit 2 is "the command cannot mean what you typed", and there are four
|
|
252
217
|
* paths to it — never a verdict, because a verdict about nothing is worse
|
|
253
218
|
* than a refusal:
|
|
254
|
-
* - unknown feature
|
|
255
|
-
* rubric, so it is a hard usage error here);
|
|
219
|
+
* - unknown feature;
|
|
256
220
|
* - `--filter` that matches no declared node (a filtered-to-empty run is
|
|
257
221
|
* green and proves nothing);
|
|
258
|
-
* - `--record-baseline` with nowhere to write (no `config.json`)
|
|
259
|
-
* task id (a recorte de task is never a fleet baseline, #87);
|
|
222
|
+
* - `--record-baseline` with nowhere to write (no `config.json`);
|
|
260
223
|
* - `--linux` without Docker.
|
|
261
224
|
* The last three arrive as `VerifyUsageError` from the runner.
|
|
262
225
|
*/
|
|
263
|
-
export async function verifyCommand(featureName,
|
|
226
|
+
export async function verifyCommand(featureName, options = {}) {
|
|
264
227
|
let feature;
|
|
265
228
|
try {
|
|
266
229
|
feature = getFeature(featureName);
|
|
@@ -273,22 +236,13 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
273
236
|
}
|
|
274
237
|
|
|
275
238
|
if (options.ci) {
|
|
276
|
-
const conflict =
|
|
239
|
+
const conflict = CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
|
|
277
240
|
if (conflict) {
|
|
278
241
|
console.error(chalk.red(`\n❌ --ci roda a suíte inteira da feature no CI; não combina com ${conflict}\n`));
|
|
279
242
|
process.exit(2);
|
|
280
243
|
}
|
|
281
244
|
}
|
|
282
245
|
|
|
283
|
-
// The runner's own reader (one reader of tasks.json for verify, #87): null
|
|
284
|
-
// means tasks.json or the task is missing, and both are this usage error.
|
|
285
|
-
if (taskId) {
|
|
286
|
-
if (!readTask(process.cwd(), featureName, taskId)) {
|
|
287
|
-
console.error(chalk.red(`\n❌ Task "${taskId}" not found in tasks.json for feature "${featureName}"\n`));
|
|
288
|
-
process.exit(2);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
246
|
let ci = null;
|
|
293
247
|
if (options.ci) {
|
|
294
248
|
try {
|
|
@@ -302,6 +256,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
302
256
|
console.error(chalk.yellow(`\n⏳ ${outcome.reason}. Não é vermelho e não há stamp: não repita em laço — siga e deixe o gate-check decidir.\n`));
|
|
303
257
|
process.exit(3);
|
|
304
258
|
} else {
|
|
259
|
+
const reused = reusableLocalStamp(process.cwd(), feature.verification);
|
|
260
|
+
if (reused) {
|
|
261
|
+
console.error(chalk.green(`\n↪ CI indisponível (${outcome.reason}) — stamp fresco reaproveitado (sha ${reused.sha.slice(0, 8)}, ${reused.ts}): nada mudou fora de .morph/ desde aquela rodada verde.\n`));
|
|
262
|
+
if (options.json) {
|
|
263
|
+
console.log(JSON.stringify({
|
|
264
|
+
feature: featureName, status: reused.status, nodes: reused.nodes, e2eReason: reused.e2eReason ?? null,
|
|
265
|
+
reused: { sha: reused.sha, ts: reused.ts, source: reused.source ?? 'local' }, persisted: false, reason: 'stamp-reused',
|
|
266
|
+
}, null, 2));
|
|
267
|
+
}
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
305
270
|
console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
|
|
306
271
|
}
|
|
307
272
|
} catch (err) {
|
|
@@ -316,45 +281,40 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
316
281
|
// Taken before the run: it is the tree the run is about to read.
|
|
317
282
|
const fingerprint = treeFingerprint(process.cwd());
|
|
318
283
|
|
|
319
|
-
const result = ci ? ci.result : await runLocalVerify(featureName,
|
|
284
|
+
const result = ci ? ci.result : await runLocalVerify(featureName, options);
|
|
320
285
|
|
|
321
286
|
const cleanup = ci ? { status: 'skipped', reason: 'ci' } : afterHeavyRun({
|
|
322
287
|
projectPath: process.cwd(),
|
|
323
288
|
config: readHeavyConfig(process.cwd()),
|
|
324
|
-
scope: taskId ? 'task' : 'feature',
|
|
325
289
|
});
|
|
326
290
|
|
|
327
|
-
const
|
|
291
|
+
const historyKey = 'verifications.feature';
|
|
328
292
|
// A run that measured only part of the suite must not stand in for the proof a gate asks for:
|
|
329
|
-
// --filter and --skip
|
|
330
|
-
|
|
331
|
-
const partialFeatureRun = (!options.noPersist && !taskId)
|
|
332
|
-
? PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null
|
|
333
|
-
: null;
|
|
293
|
+
// --filter and --skip-*, or a --record-baseline that recorded nothing; --no-persist keeps its own reason.
|
|
294
|
+
const partialRun = options.noPersist ? null : PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null;
|
|
334
295
|
const baselineBlock = options.recordBaseline ? baselineStampBlock(result.baseline) : null;
|
|
335
296
|
const { persist, reason } = baselineBlock
|
|
336
297
|
? { persist: false, reason: baselineBlock }
|
|
337
|
-
:
|
|
338
|
-
? { persist: false, reason:
|
|
298
|
+
: partialRun
|
|
299
|
+
? { persist: false, reason: partialRun }
|
|
339
300
|
: decidePersistence({ cwd: process.cwd(), featureName, noPersist: options.noPersist });
|
|
340
301
|
|
|
341
302
|
let compact = null;
|
|
342
303
|
if (persist) {
|
|
343
|
-
const prevEntry =
|
|
304
|
+
const prevEntry = feature.verifications?.feature;
|
|
344
305
|
const now = new Date().toISOString();
|
|
345
306
|
compact = {
|
|
346
307
|
status: result.status,
|
|
347
308
|
nodes: nodeStatuses(result),
|
|
348
|
-
score: result.nodes.validators.score ?? null,
|
|
349
309
|
attempts: (prevEntry?.attempts ?? 0) + 1,
|
|
350
310
|
updatedAt: now,
|
|
351
311
|
};
|
|
352
|
-
await updateFeature(featureName,
|
|
312
|
+
await updateFeature(featureName, historyKey, compact);
|
|
353
313
|
|
|
354
|
-
// Singular
|
|
355
|
-
//
|
|
314
|
+
// Singular "latest stamp" — distinct from the `verifications.feature`
|
|
315
|
+
// history above. `sha` is what lets a
|
|
356
316
|
// consumer (morph-spec gate-check) correlate this pass/fail with the
|
|
357
|
-
// EXACT commit it covers, the same way review
|
|
317
|
+
// EXACT commit it covers, the same way a review report's
|
|
358
318
|
// `**Reviewed-SHA:**` token lets approve.js detect a stale review. null
|
|
359
319
|
// in a non-git checkout (or before the first commit) — fail-open, never
|
|
360
320
|
// blocks persistence.
|
|
@@ -373,18 +333,17 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
373
333
|
e2eReason: result.nodes.e2e.status === 'skip' ? (result.nodes.e2e.reason ?? null) : null,
|
|
374
334
|
sha: ci ? ci.run.headSha : getHeadSha(process.cwd()),
|
|
375
335
|
ts: now,
|
|
376
|
-
scope: taskId ? `task:${taskId}` : 'feature',
|
|
377
336
|
...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
|
|
378
337
|
};
|
|
379
338
|
await updateFeature(featureName, 'verification', stamp);
|
|
380
339
|
}
|
|
381
340
|
|
|
382
|
-
// A
|
|
341
|
+
// A run over the exact tree of the previous one proves nothing
|
|
383
342
|
// new. Advisory only: the stamp and the exit code are the same either way.
|
|
384
343
|
let repeatOf = null;
|
|
385
344
|
try {
|
|
386
345
|
const cwd = process.cwd();
|
|
387
|
-
if (
|
|
346
|
+
if (persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, fingerprint });
|
|
388
347
|
const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
|
|
389
348
|
recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
|
|
390
349
|
} catch { /* telemetry never changes the verdict */ }
|
|
@@ -415,7 +374,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
415
374
|
console.log(chalk.gray(`${mark} dotnet build-server shutdown: ${cleanup.status}${cleanup.reason ? ` (${cleanup.reason})` : ''}\n`));
|
|
416
375
|
}
|
|
417
376
|
if (repeatOf) {
|
|
418
|
-
console.log(chalk.yellow(`⚠️ Mesma árvore do verify de
|
|
377
|
+
console.log(chalk.yellow(`⚠️ Mesma árvore do verify de ${repeatOf.ts} (${repeatOf.status}): nada mudou fora de .morph/ desde então. `
|
|
419
378
|
+ 'Esta rodada não prova nada novo — reuse o resultado anterior, a menos que o ambiente (Docker, banco, serviço externo) tenha mudado.\n'));
|
|
420
379
|
}
|
|
421
380
|
if (result.baseline?.recorded) {
|
|
@@ -426,7 +385,7 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
426
385
|
`));
|
|
427
386
|
}
|
|
428
387
|
if (persist) {
|
|
429
|
-
console.log(chalk.gray(`Persisted: ${
|
|
388
|
+
console.log(chalk.gray(`Persisted: ${historyKey} (attempts: ${compact.attempts})\n`));
|
|
430
389
|
} else {
|
|
431
390
|
console.log(chalk.yellow(`⚠️ Not persisted (${reason})\n`));
|
|
432
391
|
}
|
|
@@ -15,28 +15,24 @@ import { join } from 'path';
|
|
|
15
15
|
* Schema for output types produced by morph-spec NLH phases.
|
|
16
16
|
*
|
|
17
17
|
* Fields:
|
|
18
|
-
* filename - The file's basename (e.g. '
|
|
18
|
+
* filename - The file's basename (e.g. 'plan.md'). Single-file types only.
|
|
19
19
|
* phaseDir - Subdirectory within .morph/features/{feature}/
|
|
20
20
|
* phase - The morph-spec phase that produces this output
|
|
21
21
|
* protected - Whether the file requires an approval gate before editing
|
|
22
22
|
* approvalGate - (optional) Which gate protects this file
|
|
23
|
-
* mutableFields- (optional) For protected JSON files: top-level array-item
|
|
24
|
-
* field names that MAY still change after the gate is approved.
|
|
25
|
-
* Lets the implement phase update task progress without
|
|
26
|
-
* unfreezing the plan contract. When present, the protect
|
|
27
|
-
* hook does a semantic diff instead of a blanket block.
|
|
28
23
|
*
|
|
29
24
|
* Multi-file types (`multi: true`) hold N files in a directory instead of one
|
|
30
25
|
* fixed basename. They carry `dirname` + `filenamePattern` + `displayFilename`
|
|
31
26
|
* INSTEAD of `filename`, and every derivation keyed by basename
|
|
32
|
-
* (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES
|
|
27
|
+
* (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES) skips them —
|
|
33
28
|
* there is no single basename to key on. `getOutputPath` returns the DIRECTORY
|
|
34
29
|
* for these types.
|
|
35
30
|
*/
|
|
36
31
|
export const OUTPUT_SCHEMA = {
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
// 2-plan/ — the plan gate freezes both: plan.md accepts only an append in
|
|
33
|
+
// `## Errata`, checks.json only a rewrite by `morph-spec plan-check`.
|
|
34
|
+
plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
35
|
+
checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
40
36
|
|
|
41
37
|
// 1-design/ (optional)
|
|
42
38
|
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
@@ -44,47 +40,20 @@ export const OUTPUT_SCHEMA = {
|
|
|
44
40
|
uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
45
41
|
uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
46
42
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
51
|
-
// Veredito REUSAR/ESTENDER/CRIAR por conceito que a feature introduz, com a
|
|
52
|
-
// justificativa de cada CRIAR. É o ARTEFATO do ciclo de reuso (a prosa está
|
|
53
|
-
// em morph-plan §2b, a cobrança em src/lib/validators/reuse/). NÃO protegido,
|
|
54
|
-
// como decisions.md: é registro vivo — o implementador que descobre um alvo
|
|
55
|
-
// melhor no meio da task precisa poder anotar, e congelá-lo transformaria a
|
|
56
|
-
// correção num bloqueio de gate.
|
|
57
|
-
// N files, one per system use case — the behaviour layer the plan distils
|
|
58
|
-
// BEFORE spec.md/tasks.json. Deliberately NOT protected: editing a use case
|
|
59
|
-
// after Gate 2 is the supported way to change behaviour and re-run implement;
|
|
60
|
-
// the audit trail lives in git.
|
|
61
|
-
usecases: {
|
|
62
|
-
dirname: 'usecases',
|
|
63
|
-
filenamePattern: '^UC-\\d+-[a-z0-9-]+\\.md$',
|
|
64
|
-
displayFilename: 'UC-{nn}-{slug}.md',
|
|
65
|
-
phaseDir: '2-plan',
|
|
66
|
-
phase: 'plan',
|
|
67
|
-
protected: false,
|
|
68
|
-
multi: true,
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// 3-implement/
|
|
72
|
-
recap: { filename: 'recap.md', phaseDir: '3-implement', phase: 'implement', protected: false },
|
|
73
|
-
// N files, one per task whose doneCriteria judges what a MODEL answers
|
|
74
|
-
// (morph-plan §5). Runtime evidence: input, model, output, verdict — pointing
|
|
43
|
+
// 3-implement/ — the slice reports (`reports/F{n}.md`, see getTaskReportPath) are the record.
|
|
44
|
+
// N files, one per scenario whose proof judges what a MODEL answers
|
|
45
|
+
// (`prova: eval` in the plan). Runtime evidence: input, model, output, verdict — pointing
|
|
75
46
|
// at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
|
|
76
47
|
// construction: never in phases.json requiredOutputs. Readers: morph-review
|
|
77
|
-
// dispatch prompt
|
|
78
|
-
evidencias: { dirname: 'evidencias', filenamePattern: '^
|
|
48
|
+
// dispatch prompt.
|
|
49
|
+
evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
|
|
79
50
|
phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
|
|
80
51
|
|
|
81
52
|
// 4-review/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// the other is the orchestrator's curated Gate 3 presentation.
|
|
87
|
-
evaluatorReport:{ filename: 'evaluator-report.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
53
|
+
// Um relatório por papel; o canônico da fase é o do verificador.
|
|
54
|
+
verifierReport: { filename: 'verifier.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
55
|
+
reviewerReport: { filename: 'reviewer.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
56
|
+
securityReport: { filename: 'security.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
88
57
|
};
|
|
89
58
|
|
|
90
59
|
// ============================================================================
|
|
@@ -104,11 +73,11 @@ function leafSegment(entry) {
|
|
|
104
73
|
* Get the morph-relative path for a specific output type of a feature.
|
|
105
74
|
*
|
|
106
75
|
* For `multi` types this is the DIRECTORY holding the N files
|
|
107
|
-
* (".morph/features/my-feature/
|
|
76
|
+
* (".morph/features/my-feature/3-implement/evidencias"), not a single file.
|
|
108
77
|
*
|
|
109
78
|
* @param {string} featureName
|
|
110
79
|
* @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
|
|
111
|
-
* @returns {string} e.g. ".morph/features/my-feature/2-plan/
|
|
80
|
+
* @returns {string} e.g. ".morph/features/my-feature/2-plan/plan.md"
|
|
112
81
|
*/
|
|
113
82
|
export function getOutputPath(featureName, outputType) {
|
|
114
83
|
const entry = OUTPUT_SCHEMA[outputType];
|
|
@@ -205,27 +174,11 @@ export const PROTECTED_SPEC_FILES = Object.fromEntries(
|
|
|
205
174
|
.map(([, entry]) => [entry.filename, entry.approvalGate])
|
|
206
175
|
);
|
|
207
176
|
|
|
208
|
-
/**
|
|
209
|
-
* For protected files that carry mutable progress fields: filename → the
|
|
210
|
-
* top-level array-item fields that may change after the gate is approved.
|
|
211
|
-
* The protect-spec-files hook uses this to allow progress updates (e.g.
|
|
212
|
-
* task status) while still freezing the plan contract.
|
|
213
|
-
*/
|
|
214
|
-
export const MUTABLE_SPEC_FIELDS = Object.fromEntries(
|
|
215
|
-
Object.entries(OUTPUT_SCHEMA)
|
|
216
|
-
.filter(([, entry]) => entry.protected && entry.approvalGate && entry.mutableFields && !entry.multi)
|
|
217
|
-
.map(([, entry]) => [entry.filename, entry.mutableFields])
|
|
218
|
-
);
|
|
219
|
-
|
|
220
177
|
// ============================================================================
|
|
221
|
-
//
|
|
178
|
+
// Contracts
|
|
222
179
|
// ============================================================================
|
|
223
180
|
|
|
224
|
-
/**
|
|
225
|
-
* v4 had separate contracts.cs / contracts.ts / contracts-vsa.cs output types.
|
|
226
|
-
* In v5 contracts live inside spec.md (or in source files for VSA). This
|
|
227
|
-
* function keeps callers happy until they're updated.
|
|
228
|
-
*/
|
|
181
|
+
/** The output type whose code blocks declare the contracts the code must honour. */
|
|
229
182
|
export function resolveContractsOutputType() {
|
|
230
|
-
return '
|
|
183
|
+
return 'plan';
|
|
231
184
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Gate monotonicity — single source of truth.
|
|
3
3
|
*
|
|
4
4
|
* A gate may only be approved once its predecessors are approved. The strict
|
|
5
|
-
* chain is
|
|
6
|
-
*
|
|
5
|
+
* chain is plan → review. uiux is optional: it requires plan but gates nothing
|
|
6
|
+
* downstream, so a feature that skips UI/UX is never blocked.
|
|
7
7
|
*
|
|
8
8
|
* Policy-stamped gates (approvedBy: "policy:{type}", written at birth by
|
|
9
9
|
* `morph-spec create` for a work type's skippedGates) count as approved, so
|
|
10
|
-
* chore/hotfix (
|
|
10
|
+
* chore/hotfix (plan pre-stamped) satisfy prereqs naturally.
|
|
11
11
|
*
|
|
12
12
|
* Pure module — no filesystem/CLI side effects. Consumed by
|
|
13
13
|
* `approve`/`advance` (refuse), the state-sync hook (warn), and `list`/`doctor`
|
|
@@ -15,9 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
export const GATE_PREREQ = {
|
|
18
|
-
|
|
19
|
-
uiux: ['
|
|
20
|
-
plan: ['proposal'],
|
|
18
|
+
plan: [],
|
|
19
|
+
uiux: ['plan'],
|
|
21
20
|
review: ['plan'],
|
|
22
21
|
};
|
|
23
22
|
|
|
@@ -103,7 +103,7 @@ function lastTouched(fdir, fj) {
|
|
|
103
103
|
* Does the feature dir hold any FILE other than its own `feature.json`?
|
|
104
104
|
*
|
|
105
105
|
* Files, not entries: `create` scaffolds the phase folders empty, so an empty
|
|
106
|
-
* `
|
|
106
|
+
* `2-plan/` is the absence of work, not evidence of it.
|
|
107
107
|
*/
|
|
108
108
|
function hasArtifacts(fdir) {
|
|
109
109
|
let entries;
|
|
@@ -159,7 +159,7 @@ function ownCommitCount(baseDir, branch) {
|
|
|
159
159
|
* mean nobody ever came back to it.
|
|
160
160
|
*
|
|
161
161
|
* The artifact test is deliberately broader than the "0 tasks" the report
|
|
162
|
-
* named: a
|
|
162
|
+
* named: a plan drafted and waiting on its gate also has zero tasks and is
|
|
163
163
|
* very much alive. What a dead scaffold has is NOTHING — checked in the root
|
|
164
164
|
* and, if the feature registered one, in its worktree, where uncommitted work
|
|
165
165
|
* would otherwise be invisible to both the disk scan and `rev-list`.
|