@polymorphism-tech/morph-spec 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,84 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output Schema — Single Source of Truth (v5)
|
|
3
|
-
*
|
|
4
|
-
* ALL output type definitions live here. Edit ONLY this file to add, rename,
|
|
5
|
-
* or relocate output types.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { join } from 'path';
|
|
9
2
|
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Schema Definition
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Schema for output types produced by morph-spec NLH phases.
|
|
16
|
-
*
|
|
17
|
-
* Fields:
|
|
18
|
-
* filename - The file's basename (e.g. 'plan.md'). Single-file types only.
|
|
19
|
-
* phaseDir - Subdirectory within .morph/features/{feature}/
|
|
20
|
-
* phase - The morph-spec phase that produces this output
|
|
21
|
-
* protected - Whether the file requires an approval gate before editing
|
|
22
|
-
* approvalGate - (optional) Which gate protects this file
|
|
23
|
-
*
|
|
24
|
-
* Multi-file types (`multi: true`) hold N files in a directory instead of one
|
|
25
|
-
* fixed basename. They carry `dirname` + `filenamePattern` + `displayFilename`
|
|
26
|
-
* INSTEAD of `filename`, and every derivation keyed by basename
|
|
27
|
-
* (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES) skips them —
|
|
28
|
-
* there is no single basename to key on. `getOutputPath` returns the DIRECTORY
|
|
29
|
-
* for these types.
|
|
30
|
-
*/
|
|
31
3
|
export const OUTPUT_SCHEMA = {
|
|
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
4
|
plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
35
5
|
checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
36
6
|
|
|
37
|
-
// 1-design/ (optional)
|
|
38
7
|
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
39
8
|
uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
40
9
|
uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
41
10
|
uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
42
11
|
|
|
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
|
|
46
|
-
// at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
|
|
47
|
-
// construction: never in phases.json requiredOutputs. Readers: morph-review
|
|
48
|
-
// dispatch prompt.
|
|
49
12
|
evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
|
|
50
13
|
phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
|
|
51
14
|
|
|
52
|
-
// 4-review/
|
|
53
|
-
// Um relatório por papel; o canônico da fase é o do verificador.
|
|
54
15
|
verifierReport: { filename: 'verifier.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
55
16
|
reviewerReport: { filename: 'reviewer.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
56
17
|
securityReport: { filename: 'security.md', phaseDir: '4-review', phase: 'review', protected: false },
|
|
57
18
|
};
|
|
58
19
|
|
|
59
|
-
// ============================================================================
|
|
60
|
-
// Builder Functions (Windows-safe via path.join)
|
|
61
|
-
// ============================================================================
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Leaf segment of a schema entry: the basename for single-file types, the
|
|
65
|
-
* directory name for multi-file ones. Keeps every path builder from having to
|
|
66
|
-
* know about `multi` on its own.
|
|
67
|
-
*/
|
|
68
20
|
function leafSegment(entry) {
|
|
69
21
|
return entry.multi ? entry.dirname : entry.filename;
|
|
70
22
|
}
|
|
71
23
|
|
|
72
|
-
/**
|
|
73
|
-
* Get the morph-relative path for a specific output type of a feature.
|
|
74
|
-
*
|
|
75
|
-
* For `multi` types this is the DIRECTORY holding the N files
|
|
76
|
-
* (".morph/features/my-feature/3-implement/evidencias"), not a single file.
|
|
77
|
-
*
|
|
78
|
-
* @param {string} featureName
|
|
79
|
-
* @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
|
|
80
|
-
* @returns {string} e.g. ".morph/features/my-feature/2-plan/plan.md"
|
|
81
|
-
*/
|
|
24
|
+
/** For `multi` types this is the directory holding the N files. */
|
|
82
25
|
export function getOutputPath(featureName, outputType) {
|
|
83
26
|
const entry = OUTPUT_SCHEMA[outputType];
|
|
84
27
|
if (!entry) {
|
|
@@ -88,10 +31,6 @@ export function getOutputPath(featureName, outputType) {
|
|
|
88
31
|
.replace(/\\/g, '/');
|
|
89
32
|
}
|
|
90
33
|
|
|
91
|
-
/**
|
|
92
|
-
* Get the absolute filesystem path for a specific output type.
|
|
93
|
-
* For `multi` types this is the directory — see {@link getOutputPath}.
|
|
94
|
-
*/
|
|
95
34
|
export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
|
|
96
35
|
const entry = OUTPUT_SCHEMA[outputType];
|
|
97
36
|
if (!entry) {
|
|
@@ -100,42 +39,20 @@ export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
|
|
|
100
39
|
return join(projectRoot, '.morph', 'features', featureName, entry.phaseDir, leafSegment(entry));
|
|
101
40
|
}
|
|
102
41
|
|
|
103
|
-
/**
|
|
104
|
-
* Get the feature base directory (morph-relative, forward slashes).
|
|
105
|
-
*/
|
|
106
42
|
export function getFeatureDir(featureName) {
|
|
107
43
|
return join('.morph', 'features', featureName).replace(/\\/g, '/');
|
|
108
44
|
}
|
|
109
45
|
|
|
110
|
-
/**
|
|
111
|
-
* Per-task sub-agent report — the durable "return value" channel for a
|
|
112
|
-
* dispatched implementation task (MORPH.md §6). A sub-agent's final text
|
|
113
|
-
* message may never reach the orchestrator (idle_notification with no text
|
|
114
|
-
* is an observed failure mode); the file it writes here always does. Doesn't
|
|
115
|
-
* fit OUTPUT_SCHEMA's one-fixed-filename-per-type model since the filename
|
|
116
|
-
* is parameterized by taskId, so it's a sibling helper instead of a schema
|
|
117
|
-
* entry.
|
|
118
|
-
*
|
|
119
|
-
* @param {string} featureName
|
|
120
|
-
* @param {string} taskId - e.g. "T7"
|
|
121
|
-
* @returns {string} morph-relative, forward-slash path
|
|
122
|
-
*/
|
|
123
46
|
export function getTaskReportPath(featureName, taskId) {
|
|
124
47
|
return join('.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`)
|
|
125
48
|
.replace(/\\/g, '/');
|
|
126
49
|
}
|
|
127
50
|
|
|
128
|
-
/** Absolute filesystem variant of {@link getTaskReportPath}. */
|
|
129
51
|
export function getAbsoluteTaskReportPath(projectRoot, featureName, taskId) {
|
|
130
52
|
return join(projectRoot, '.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`);
|
|
131
53
|
}
|
|
132
54
|
|
|
133
|
-
/**
|
|
134
|
-
* Get all output paths for a feature, ready for state.json `outputs` block.
|
|
135
|
-
*
|
|
136
|
-
* Multi-file types contribute their DIRECTORY, so `created` for them means
|
|
137
|
-
* "the folder exists" — the state index tracks presence, not file count.
|
|
138
|
-
*/
|
|
55
|
+
/** `multi` types contribute their directory, so `created` means "the folder exists". */
|
|
139
56
|
export function getAllOutputPaths(featureName) {
|
|
140
57
|
const outputs = {};
|
|
141
58
|
for (const [key] of Object.entries(OUTPUT_SCHEMA)) {
|
|
@@ -147,14 +64,6 @@ export function getAllOutputPaths(featureName) {
|
|
|
147
64
|
return outputs;
|
|
148
65
|
}
|
|
149
66
|
|
|
150
|
-
// ============================================================================
|
|
151
|
-
// Derived Constants
|
|
152
|
-
// ============================================================================
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* filename → output type. Multi-file types are absent by construction: they
|
|
156
|
-
* have N basenames, so there is nothing to key on.
|
|
157
|
-
*/
|
|
158
67
|
export const FILENAME_TO_OUTPUT = Object.fromEntries(
|
|
159
68
|
Object.entries(OUTPUT_SCHEMA)
|
|
160
69
|
.filter(([, entry]) => !entry.multi)
|
|
@@ -165,20 +74,12 @@ export const OUTPUT_PHASE_MAP = Object.fromEntries(
|
|
|
165
74
|
Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [key, entry.phase])
|
|
166
75
|
);
|
|
167
76
|
|
|
168
|
-
/**
|
|
169
|
-
* Protected spec files and the approval gate that locks them.
|
|
170
|
-
*/
|
|
171
77
|
export const PROTECTED_SPEC_FILES = Object.fromEntries(
|
|
172
78
|
Object.entries(OUTPUT_SCHEMA)
|
|
173
79
|
.filter(([, entry]) => entry.protected && entry.approvalGate && !entry.multi)
|
|
174
80
|
.map(([, entry]) => [entry.filename, entry.approvalGate])
|
|
175
81
|
);
|
|
176
82
|
|
|
177
|
-
// ============================================================================
|
|
178
|
-
// Contracts
|
|
179
|
-
// ============================================================================
|
|
180
|
-
|
|
181
|
-
/** The output type whose code blocks declare the contracts the code must honour. */
|
|
182
83
|
export function resolveContractsOutputType() {
|
|
183
84
|
return 'plan';
|
|
184
85
|
}
|
|
@@ -1,32 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feature identity — the fields that keep a feature.json from being an
|
|
3
|
-
* anonymous shell (the {status, updatedAt}-only scaffolds nobody could later
|
|
4
|
-
* identify). name/createdAt/workType are auto-stamped at birth (explicit
|
|
5
|
-
* `morph-spec create` or the state-sync hook's implicit registration);
|
|
6
|
-
* `description` is the one field a human must supply.
|
|
7
|
-
*
|
|
8
|
-
* `missingIdentity` powers the audit surfaces (`list`, `doctor`) that flag
|
|
9
|
-
* pre-existing anonymous features LENIENTLY (warn, never invalidate old
|
|
10
|
-
* feature.json). `create` enforces `description` HARD at birth — the two-tier
|
|
11
|
-
* split the schema's `required` array can't express (adding fields there would
|
|
12
|
-
* invalidate every feature.json that predates this).
|
|
13
|
-
*
|
|
14
|
-
* Pure module — no filesystem/CLI side effects.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
1
|
export const IDENTITY_FIELDS = ['name', 'description', 'workType', 'createdAt'];
|
|
18
2
|
|
|
19
3
|
const blank = (v) => v === undefined || v === null || String(v).trim() === '';
|
|
20
4
|
|
|
21
|
-
|
|
22
|
-
* Identity fields absent from a feature.json (empty array = fully identified).
|
|
23
|
-
* `description` leads because its absence is the real anonymity signal.
|
|
24
|
-
* `workType` is intentionally excluded — absent workType defaults to 'feature',
|
|
25
|
-
* so it is never a marker of an anonymous shell.
|
|
26
|
-
*
|
|
27
|
-
* @param {object} feature - feature.json contents
|
|
28
|
-
* @returns {string[]} missing identity field names
|
|
29
|
-
*/
|
|
5
|
+
// `workType` is excluded on purpose: absent, it defaults to `feature`.
|
|
30
6
|
export function missingIdentity(feature) {
|
|
31
7
|
const missing = [];
|
|
32
8
|
if (blank(feature?.description)) missing.push('description');
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gate monotonicity — single source of truth.
|
|
3
|
-
*
|
|
4
|
-
* A gate may only be approved once its predecessors are approved. The strict
|
|
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
|
-
*
|
|
8
|
-
* Policy-stamped gates (approvedBy: "policy:{type}", written at birth by
|
|
9
|
-
* `morph-spec create` for a work type's skippedGates) count as approved, so
|
|
10
|
-
* chore/hotfix (plan pre-stamped) satisfy prereqs naturally.
|
|
11
|
-
*
|
|
12
|
-
* Pure module — no filesystem/CLI side effects. Consumed by
|
|
13
|
-
* `approve`/`advance` (refuse), the state-sync hook (warn), and `list`/`doctor`
|
|
14
|
-
* (report).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
1
|
export const GATE_PREREQ = {
|
|
18
2
|
plan: [],
|
|
19
3
|
uiux: ['plan'],
|
|
@@ -22,22 +6,11 @@ export const GATE_PREREQ = {
|
|
|
22
6
|
|
|
23
7
|
const isApproved = (gates, name) => gates?.[name]?.approved === true;
|
|
24
8
|
|
|
25
|
-
/**
|
|
26
|
-
* Prerequisite gates NOT yet approved that block approving `gate`.
|
|
27
|
-
* @param {string} gate - gate about to be approved
|
|
28
|
-
* @param {object} gates - feature.approvalGates
|
|
29
|
-
* @returns {string[]} unmet prerequisite gate names (empty = clear to approve)
|
|
30
|
-
*/
|
|
31
9
|
export function unmetPrereqs(gate, gates) {
|
|
32
10
|
const prereqs = GATE_PREREQ[gate] || [];
|
|
33
11
|
return prereqs.filter((p) => !isApproved(gates, p));
|
|
34
12
|
}
|
|
35
13
|
|
|
36
|
-
/**
|
|
37
|
-
* All monotonicity violations currently present in a feature's gates.
|
|
38
|
-
* @param {object} gates - feature.approvalGates
|
|
39
|
-
* @returns {Array<{gate: string, missing: string[]}>} empty when in order
|
|
40
|
-
*/
|
|
41
14
|
export function gateOrderViolations(gates) {
|
|
42
15
|
if (!gates || typeof gates !== 'object') return [];
|
|
43
16
|
const out = [];
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feature-lifecycle audit — the consolidated inconsistency scanner behind
|
|
3
|
-
* `morph-spec doctor`. Read-only, non-throwing: scans `.morph/features/*` and
|
|
4
|
-
* the thin root index and reports what's already wrong in the repo, without
|
|
5
|
-
* touching state (never calls loadState — a diagnostic must not mutate).
|
|
6
|
-
*
|
|
7
|
-
* Issue types:
|
|
8
|
-
* • gate-order — a gate approved with a predecessor pending (item 4)
|
|
9
|
-
* • identity — a feature missing description/name/createdAt (item 3)
|
|
10
|
-
* • orphan-folder — a feature folder with no feature.json
|
|
11
|
-
* • stale-index — a root-index entry with no feature folder (auto-heals on
|
|
12
|
-
* the next loadState, but surfaced so it's not a mystery)
|
|
13
|
-
* • branch-drift — the root's copy of a feature.json disagrees with the copy
|
|
14
|
-
* committed on `morph/{f}` (B5 — the root's view lags)
|
|
15
|
-
* • abandoned-scaffold — an `in_progress` feature that never produced
|
|
16
|
-
* anything: no artifacts, no approved gate, and a branch
|
|
17
|
-
* with no commit of its own (see abandonedScaffold)
|
|
18
|
-
* • corrupt — a feature.json that won't parse
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
1
|
import { execFileSync } from 'child_process';
|
|
22
2
|
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
23
3
|
import { join } from 'path';
|
|
@@ -26,40 +6,19 @@ import { missingIdentity } from './feature-identity.js';
|
|
|
26
6
|
import { detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
27
7
|
import { commitsAhead, resolveBaseRef } from '../../lib/git/branch-classifier.js';
|
|
28
8
|
|
|
29
|
-
/** The operational truth of a feature.json: its status and which gates are approved. */
|
|
30
9
|
function operationalShape(fj) {
|
|
31
10
|
const gates = {};
|
|
32
11
|
for (const [g, v] of Object.entries(fj?.approvalGates || {})) gates[g] = v?.approved === true;
|
|
33
12
|
return JSON.stringify({ status: fj?.status ?? null, gates });
|
|
34
13
|
}
|
|
35
14
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* COMMITTED on that feature's own branch?
|
|
39
|
-
*
|
|
40
|
-
* `.morph/state.json` is gitignored and derived, and every worktree keeps its
|
|
41
|
-
* own — so "the index diverges between worktrees" is by design, not a defect to
|
|
42
|
-
* fix by making the index authoritative (that would undo the whole per-feature
|
|
43
|
-
* state model). The defect is that the divergence was INVISIBLE: since v8.4 the
|
|
44
|
-
* authoritative feature.json travels committed on `morph/{f}`, so a primary root
|
|
45
|
-
* parked on the default holds a copy that silently lags, and everything derived
|
|
46
|
-
* from it (status, gates, and the index rebuilt from it) lags with it.
|
|
47
|
-
*
|
|
48
|
-
* Comparison is on the OPERATIONAL fields only — status and approved-gate flags
|
|
49
|
-
* — not on bytes: reformatting or a timestamp bump is not a divergence anyone
|
|
50
|
-
* needs to hear about.
|
|
51
|
-
*
|
|
52
|
-
* Fail-open at every step: no repo, no branch, no blob, unparseable JSON → no
|
|
53
|
-
* issue. A diagnostic that invents problems is worse than one that misses some.
|
|
54
|
-
*
|
|
55
|
-
* @returns {string|null} the issue detail, or null when the copies agree
|
|
56
|
-
*/
|
|
15
|
+
// Compares only status and approved gates (bytes and timestamps are not drift).
|
|
16
|
+
// Fail-open: no repo, branch, blob or parseable JSON means no issue.
|
|
57
17
|
function branchDrift(baseDir, feature, rootFj) {
|
|
58
18
|
const branch = `morph/${feature}`;
|
|
59
19
|
try {
|
|
60
20
|
const current = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: baseDir, stdio: 'pipe' })
|
|
61
21
|
.toString().trim();
|
|
62
|
-
// On the feature's own branch the working tree IS the branch — nothing to compare.
|
|
63
22
|
if (current === branch) return null;
|
|
64
23
|
|
|
65
24
|
execFileSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: baseDir, stdio: 'pipe' });
|
|
@@ -77,18 +36,9 @@ function branchDrift(baseDir, feature, rootFj) {
|
|
|
77
36
|
}
|
|
78
37
|
}
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
* How long a scaffold has to sit untouched before it counts as abandoned.
|
|
82
|
-
*
|
|
83
|
-
* A dead scaffold and a feature born five minutes ago have the SAME shape:
|
|
84
|
-
* `create` forks `morph/{f}` off the default branch without committing, so at
|
|
85
|
-
* birth the branch has no commit of its own and the folder holds nothing but
|
|
86
|
-
* `feature.json`. Only elapsed time separates the two — and a check that fires
|
|
87
|
-
* on every fresh `create` is one people learn to scroll past.
|
|
88
|
-
*/
|
|
39
|
+
// A dead scaffold and a feature born minutes ago have the same shape; only age tells them apart.
|
|
89
40
|
const ABANDONED_SCAFFOLD_MIN_AGE_MS = 24 * 60 * 60 * 1000;
|
|
90
41
|
|
|
91
|
-
/** Newest timestamp the feature carries, in ms; falls back to the folder's mtime. */
|
|
92
42
|
function lastTouched(fdir, fj) {
|
|
93
43
|
let newest = 0;
|
|
94
44
|
for (const ts of [fj?.updatedAt, fj?.createdAt]) {
|
|
@@ -99,12 +49,7 @@ function lastTouched(fdir, fj) {
|
|
|
99
49
|
try { return statSync(fdir).mtimeMs; } catch { return 0; }
|
|
100
50
|
}
|
|
101
51
|
|
|
102
|
-
|
|
103
|
-
* Does the feature dir hold any FILE other than its own `feature.json`?
|
|
104
|
-
*
|
|
105
|
-
* Files, not entries: `create` scaffolds the phase folders empty, so an empty
|
|
106
|
-
* `2-plan/` is the absence of work, not evidence of it.
|
|
107
|
-
*/
|
|
52
|
+
// Files, not entries: `create` scaffolds the phase folders empty.
|
|
108
53
|
function hasArtifacts(fdir) {
|
|
109
54
|
let entries;
|
|
110
55
|
try { entries = readdirSync(fdir, { withFileTypes: true }); } catch { return false; }
|
|
@@ -119,22 +64,7 @@ function hasArtifacts(fdir) {
|
|
|
119
64
|
return false;
|
|
120
65
|
}
|
|
121
66
|
|
|
122
|
-
|
|
123
|
-
* Commits on the feature's branch that the base does not already contain, or
|
|
124
|
-
* `null` when git cannot answer (no repo, no resolvable base, no branch, no
|
|
125
|
-
* rev-list). "Cannot prove" must never read as "proved zero" — the predicate
|
|
126
|
-
* below hinges on this number, so an unanswerable repo has to stay silent.
|
|
127
|
-
*
|
|
128
|
-
* The counting itself is `branch-classifier.js`'s `commitsAhead` against its
|
|
129
|
-
* `resolveBaseRef`, NOT a second copy: `origin/{default}` is the authority on
|
|
130
|
-
* what actually landed, and two drifting answers to "does this branch carry
|
|
131
|
-
* anything of its own?" is exactly what the `doctor`/`branch prune` split was
|
|
132
|
-
* unified to avoid.
|
|
133
|
-
*
|
|
134
|
-
* Note the field evidence this exists for: the branch pointed at the merge
|
|
135
|
-
* commit of somebody else's PR. That is the base's own tip — reachable from
|
|
136
|
-
* the base, and therefore zero commits of its own.
|
|
137
|
-
*/
|
|
67
|
+
// `null` when git cannot answer: "cannot prove" must never read as "proved zero".
|
|
138
68
|
function ownCommitCount(baseDir, branch) {
|
|
139
69
|
try {
|
|
140
70
|
execFileSync('git', ['rev-parse', '--git-dir'], { cwd: baseDir, stdio: 'pipe' });
|
|
@@ -146,31 +76,15 @@ function ownCommitCount(baseDir, branch) {
|
|
|
146
76
|
try {
|
|
147
77
|
execFileSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: baseDir, stdio: 'pipe' });
|
|
148
78
|
} catch {
|
|
149
|
-
return 0; // the branch never existed
|
|
79
|
+
return 0; // the branch never existed: nothing was committed on it
|
|
150
80
|
}
|
|
151
81
|
return commitsAhead(baseRef, branch, baseDir);
|
|
152
82
|
}
|
|
153
83
|
|
|
154
|
-
/**
|
|
155
|
-
* The orphan skeleton `create --worktree` can leave behind: a feature.json on
|
|
156
|
-
* its own, `status: in_progress`, no tasks, no gates, and a branch parked on
|
|
157
|
-
* somebody else's commit. Every one of those alone is normal at some point in
|
|
158
|
-
* a feature's life; together, and only after the feature has gone stale, they
|
|
159
|
-
* mean nobody ever came back to it.
|
|
160
|
-
*
|
|
161
|
-
* The artifact test is deliberately broader than the "0 tasks" the report
|
|
162
|
-
* named: a plan drafted and waiting on its gate also has zero tasks and is
|
|
163
|
-
* very much alive. What a dead scaffold has is NOTHING — checked in the root
|
|
164
|
-
* and, if the feature registered one, in its worktree, where uncommitted work
|
|
165
|
-
* would otherwise be invisible to both the disk scan and `rev-list`.
|
|
166
|
-
*
|
|
167
|
-
* @returns {string|null} the issue detail, or null when the feature is alive
|
|
168
|
-
*/
|
|
169
84
|
function abandonedScaffold(baseDir, feature, fdir, fj) {
|
|
170
85
|
if (fj?.status !== 'in_progress') return null;
|
|
171
86
|
|
|
172
|
-
//
|
|
173
|
-
// never a decision anyone took, so it must not read as progress.
|
|
87
|
+
// A gate stamped `policy:{workType}` at birth was never a decision, so it is not progress.
|
|
174
88
|
const decided = Object.values(fj.approvalGates || {}).some(
|
|
175
89
|
(g) => g?.approved === true && !String(g?.approvedBy ?? '').startsWith('policy:'),
|
|
176
90
|
);
|
|
@@ -185,8 +99,6 @@ function abandonedScaffold(baseDir, feature, fdir, fj) {
|
|
|
185
99
|
const age = Date.now() - lastTouched(fdir, fj);
|
|
186
100
|
if (age < ABANDONED_SCAFFOLD_MIN_AGE_MS) return null;
|
|
187
101
|
|
|
188
|
-
// The registered branch when the feature has one, `morph/{f}` otherwise —
|
|
189
|
-
// same name `branchDrift` above assumes.
|
|
190
102
|
const branch = fj.worktree?.branch || `morph/${feature}`;
|
|
191
103
|
if (ownCommitCount(baseDir, branch) !== 0) return null;
|
|
192
104
|
|
|
@@ -196,10 +108,6 @@ function abandonedScaffold(baseDir, feature, fdir, fj) {
|
|
|
196
108
|
+ 'ou retome a feature';
|
|
197
109
|
}
|
|
198
110
|
|
|
199
|
-
/**
|
|
200
|
-
* @param {string} [baseDir] - project root (defaults to cwd)
|
|
201
|
-
* @returns {Array<{ feature: string, type: string, detail: string }>} issues
|
|
202
|
-
*/
|
|
203
111
|
export function auditFeatureLifecycle(baseDir = process.cwd()) {
|
|
204
112
|
const issues = [];
|
|
205
113
|
const featuresDir = join(baseDir, '.morph', 'features');
|
|
@@ -256,7 +164,7 @@ export function auditFeatureLifecycle(baseDir = process.cwd()) {
|
|
|
256
164
|
issues.push({ feature: name, type: 'stale-index', detail: 'in state.json index but no feature folder' });
|
|
257
165
|
}
|
|
258
166
|
}
|
|
259
|
-
} catch {
|
|
167
|
+
} catch { }
|
|
260
168
|
}
|
|
261
169
|
|
|
262
170
|
return issues;
|