@polymorphism-tech/morph-spec 8.47.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 +48 -39
- package/README.md +3 -4
- package/bin/morph-spec.js +37 -51
- package/bin/validate.js +3 -111
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- 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/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- 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 +7 -21
- 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 +64 -27
- 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 +2 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- 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 +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +77 -152
- package/framework/standards/STANDARDS.json +86 -1218
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/architecture/code-comments.md +6 -6
- 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/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/Morph.AiKit.Tests.csproj +0 -10
- 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/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 +4 -0
- 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 +17 -1
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +81 -141
- 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 +85 -84
- 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 +40 -72
- package/src/core/paths/output-schema.js +19 -66
- 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/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +5 -5
- package/src/dashboard/app/components/EntityLink.jsx +1 -1
- 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/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +5 -15
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +2 -2
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/project-api.js +1 -1
- 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 +131 -140
- 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-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 +22 -45
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/verify-runner.js +118 -235
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +9 -0
- package/src/utils/agents-installer.js +76 -9
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +27 -1
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- 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/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/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -104
- 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
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# Architecture Decision Records - {{titleCase FEATURE_NAME}}
|
|
2
|
-
|
|
3
|
-
## ADR-001: {Decision Title}
|
|
4
|
-
|
|
5
|
-
**Date:** {{DATE}}
|
|
6
|
-
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
7
|
-
**Deciders:** MORPH Agents, Developer
|
|
8
|
-
**Alcance:** feature
|
|
9
|
-
|
|
10
|
-
### Context
|
|
11
|
-
|
|
12
|
-
{Describe the context and problem that led to this decision}
|
|
13
|
-
|
|
14
|
-
### Decision
|
|
15
|
-
|
|
16
|
-
{Describe the decision made}
|
|
17
|
-
|
|
18
|
-
### Consequences
|
|
19
|
-
|
|
20
|
-
**Pros:**
|
|
21
|
-
- {Positive consequence 1}
|
|
22
|
-
- {Positive consequence 2}
|
|
23
|
-
|
|
24
|
-
**Cons:**
|
|
25
|
-
- {Negative consequence 1}
|
|
26
|
-
|
|
27
|
-
**Risks:**
|
|
28
|
-
- {Identified risk}
|
|
29
|
-
|
|
30
|
-
### Alternatives Considered
|
|
31
|
-
|
|
32
|
-
#### Option A: {Name}
|
|
33
|
-
- Pros: {advantages}
|
|
34
|
-
- Cons: {disadvantages}
|
|
35
|
-
- **Rejected because:** {reason}
|
|
36
|
-
|
|
37
|
-
#### Option B: {Name}
|
|
38
|
-
- Pros: {advantages}
|
|
39
|
-
- Cons: {disadvantages}
|
|
40
|
-
- **Rejected because:** {reason}
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
{{#if (eq STACK "nextjs-neon")}}
|
|
45
|
-
## ADR-002: Database Access Strategy
|
|
46
|
-
|
|
47
|
-
**Date:** {{DATE}}
|
|
48
|
-
**Status:** Accepted
|
|
49
|
-
|
|
50
|
-
### Context
|
|
51
|
-
|
|
52
|
-
Need to decide how the .NET API accesses Neon PostgreSQL.
|
|
53
|
-
|
|
54
|
-
### Decision
|
|
55
|
-
|
|
56
|
-
Use Dapper with Npgsql for direct SQL queries. No EF Core.
|
|
57
|
-
|
|
58
|
-
### Consequences
|
|
59
|
-
|
|
60
|
-
**Pros:**
|
|
61
|
-
- Full control over SQL queries
|
|
62
|
-
- Better performance (no ORM overhead)
|
|
63
|
-
- Direct access to PostgreSQL features (jsonb, pgvector, functions)
|
|
64
|
-
- RLS policies apply naturally through connection
|
|
65
|
-
|
|
66
|
-
**Cons:**
|
|
67
|
-
- No automatic migrations (use Neon CLI / dotnet ef instead)
|
|
68
|
-
- Manual SQL query writing
|
|
69
|
-
- No change tracking
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## ADR-003: Auth Strategy
|
|
74
|
-
|
|
75
|
-
**Date:** {{DATE}}
|
|
76
|
-
**Status:** Accepted
|
|
77
|
-
|
|
78
|
-
### Context
|
|
79
|
-
|
|
80
|
-
Need authentication for both Next.js frontend and .NET API.
|
|
81
|
-
|
|
82
|
-
### Decision
|
|
83
|
-
|
|
84
|
-
Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
|
|
85
|
-
|
|
86
|
-
### Consequences
|
|
87
|
-
|
|
88
|
-
**Pros:**
|
|
89
|
-
- Auth data in same database — no external provider, no sync delays
|
|
90
|
-
- RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
|
|
91
|
-
- Built-in social login, email/password via @neondatabase/auth SDK
|
|
92
|
-
- Branch isolation — each Neon branch gets isolated auth data
|
|
93
|
-
- No webhooks needed — query neon_auth schema directly
|
|
94
|
-
|
|
95
|
-
**Cons:**
|
|
96
|
-
- Neon Auth is currently in Beta
|
|
97
|
-
- JWT must be forwarded to .NET API for backend auth
|
|
98
|
-
{{/if}}
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Template for New ADRs
|
|
103
|
-
|
|
104
|
-
```markdown
|
|
105
|
-
## ADR-XXX: {Title}
|
|
106
|
-
|
|
107
|
-
**Date:**
|
|
108
|
-
**Status:** Proposed | Accepted | Deprecated | Superseded
|
|
109
|
-
**Alcance:** feature | projeto — `projeto` when the rule holds beyond this feature: `morph-spec finish` copies it to `.morph/specs/adrs/`, the folder brainstorming and plan read instead of archived decisions.md
|
|
110
|
-
|
|
111
|
-
### Context
|
|
112
|
-
{Why is this decision needed?}
|
|
113
|
-
|
|
114
|
-
### Decision
|
|
115
|
-
{What was decided?}
|
|
116
|
-
|
|
117
|
-
### Consequences
|
|
118
|
-
**Pros:** {list}
|
|
119
|
-
**Cons:** {list}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
*Generated by MORPH Framework*
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# Feature Recap: {{titleCase FEATURE_NAME}}
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
| Field | Value |
|
|
6
|
-
|-------|-------|
|
|
7
|
-
| **Feature ID** | {{FEATURE_NAME}} |
|
|
8
|
-
| **Completed** | {{DATE}} |
|
|
9
|
-
| **Total Tasks** | {X} |
|
|
10
|
-
| **Time Spent** | {X}h |
|
|
11
|
-
| **Agents Used** | {list} |
|
|
12
|
-
{{#if STACK}}| **Stack** | {{STACK}} |{{/if}}
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Tasks Completed
|
|
17
|
-
|
|
18
|
-
| ID | Title | Category | Duration |
|
|
19
|
-
|----|-------|----------|----------|
|
|
20
|
-
| T001 | {title} | {{#if (eq STACK "nextjs-neon")}}Database{{else}}{category}{{/if}} | {X}min |
|
|
21
|
-
| T002 | {title} | {{#if (eq STACK "nextjs-neon")}}Backend{{else}}{category}{{/if}} | {X}min |
|
|
22
|
-
| T003 | {title} | {{#if (eq STACK "nextjs-neon")}}Frontend{{else}}{category}{{/if}} | {X}min |
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
{{#if (eq STACK "nextjs-neon")}}
|
|
27
|
-
## Validation Results
|
|
28
|
-
|
|
29
|
-
| Validator | Status | Details |
|
|
30
|
-
|-----------|--------|---------|
|
|
31
|
-
| contract-compliance | Pass/Fail | {details} |
|
|
32
|
-
| architecture | Pass/Fail | {details} |
|
|
33
|
-
| design-system | Pass/Fail | {details} |
|
|
34
|
-
| coding-standards | Pass/Fail | {details} |
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
{{/if}}
|
|
39
|
-
## What Was Delivered
|
|
40
|
-
|
|
41
|
-
### Functionality
|
|
42
|
-
- {Functionality 1 delivered}
|
|
43
|
-
- {Functionality 2 delivered}
|
|
44
|
-
- {Functionality 3 delivered}
|
|
45
|
-
|
|
46
|
-
{{#if (eq STACK "nextjs-neon")}}
|
|
47
|
-
### Database (Neon PostgreSQL)
|
|
48
|
-
- Tables: {list}
|
|
49
|
-
- RLS Policies: {count} policies
|
|
50
|
-
- Migrations: {count} files
|
|
51
|
-
|
|
52
|
-
### Backend API (.NET)
|
|
53
|
-
- Endpoints: {count} endpoints
|
|
54
|
-
- Services: {list}
|
|
55
|
-
|
|
56
|
-
### Frontend (Next.js)
|
|
57
|
-
- Pages: {list}
|
|
58
|
-
- Components: {list}
|
|
59
|
-
{{/if}}
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Files Created
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
{{#if (eq STACK "nextjs-neon")}}
|
|
67
|
-
migrations/{files}
|
|
68
|
-
backend/src/{files}
|
|
69
|
-
frontend/src/{files}
|
|
70
|
-
{{else}}
|
|
71
|
-
{list of created files}
|
|
72
|
-
{{/if}}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Files Modified
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
{list of modified files}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Architecture Decisions
|
|
84
|
-
|
|
85
|
-
| Decision | Rationale |
|
|
86
|
-
|----------|-----------|
|
|
87
|
-
| {Decision 1} | {Reason} |
|
|
88
|
-
| {Decision 2} | {Reason} |
|
|
89
|
-
|
|
90
|
-
See full ADRs in `decisions.md`
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Test Coverage
|
|
95
|
-
|
|
96
|
-
| Type | Files | Coverage |
|
|
97
|
-
|------|-------|----------|
|
|
98
|
-
| {{#if (eq STACK "nextjs-neon")}}Backend Unit{{else}}Unit Tests{{/if}} | {X} | {X}% |
|
|
99
|
-
| {{#if (eq STACK "nextjs-neon")}}Frontend Component{{else}}Integration Tests{{/if}} | {X} | {X}% |
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Lessons Learned
|
|
104
|
-
|
|
105
|
-
### What Went Well
|
|
106
|
-
- {Positive point 1}
|
|
107
|
-
- {Positive point 2}
|
|
108
|
-
|
|
109
|
-
### What Could Be Improved
|
|
110
|
-
- {Improvement point 1}
|
|
111
|
-
- {Improvement point 2}
|
|
112
|
-
|
|
113
|
-
### Recommendations for Future
|
|
114
|
-
- {Recommendation 1}
|
|
115
|
-
- {Recommendation 2}
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Metrics
|
|
120
|
-
|
|
121
|
-
| Metric | Value |
|
|
122
|
-
|--------|-------|
|
|
123
|
-
| Tasks Completed | {X}/{X} |
|
|
124
|
-
| Checkpoints Passed | {X} |
|
|
125
|
-
{{#if (eq STACK "nextjs-neon")}}| Validation Passes | {X}/{X} |
|
|
126
|
-
{{else}}| Code Reviews | {X} |
|
|
127
|
-
{{/if}}| Bugs Found | {X} |
|
|
128
|
-
| Bugs Fixed | {X} |
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## Related Resources
|
|
133
|
-
|
|
134
|
-
- **Proposal:** `proposal.md`
|
|
135
|
-
- **Spec:** `spec.md`
|
|
136
|
-
{{#if (eq STACK "nextjs-neon")}}- **Contracts (.NET):** `contracts.cs`
|
|
137
|
-
- **Contracts (TS):** `contracts.ts`
|
|
138
|
-
{{else}}- **Contracts:** `contracts.cs`
|
|
139
|
-
{{/if}}- **Tasks:** `tasks.json`
|
|
140
|
-
- **Decisions:** `decisions.md`
|
|
141
|
-
- **PR:** {link}
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
*Generated by MORPH Framework on {{DATE}}*
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "1.0",
|
|
3
|
-
"feature": "{{featureName}}",
|
|
4
|
-
"tasks": [
|
|
5
|
-
{
|
|
6
|
-
"id": "T1",
|
|
7
|
-
"title": "Example task title",
|
|
8
|
-
"description": "What this task accomplishes and why",
|
|
9
|
-
"dependencies": [],
|
|
10
|
-
"effort": "M",
|
|
11
|
-
"doneCriteria": "Verifiable acceptance condition stated in one sentence",
|
|
12
|
-
"status": "pending",
|
|
13
|
-
"outputs": [],
|
|
14
|
-
"group": "backend"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `morph-spec usecase reserve [--count <n>] [--feature <name>] [--json]` —
|
|
3
|
-
* atomically mint the next N `UC-{nn}` numbers, shared across every worktree
|
|
4
|
-
* of this repo. See `usecase-registry.js` for why this exists (worktrees only
|
|
5
|
-
* ever see their own tree, so an independent max+1 scan collides).
|
|
6
|
-
*
|
|
7
|
-
* `--feature` is more than an audit label: `usecase-promoter.js`'s collision
|
|
8
|
-
* guard uses the reservation record to tell a legitimate cross-feature
|
|
9
|
-
* correction (never reserved — reusing a published number by design) from an
|
|
10
|
-
* accidental collision (both features reserved the same id). Passing it is
|
|
11
|
-
* how `morph-plan` gets that protection; omitting it degrades to "reserved,
|
|
12
|
-
* no owner recorded" — still collision-free, just unable to name the
|
|
13
|
-
* conflicting feature if one ever surfaces.
|
|
14
|
-
*
|
|
15
|
-
* `morph-spec usecase check <feature>` — see `usecaseCheckCommand` below.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import chalk from 'chalk';
|
|
19
|
-
import { existsSync, readdirSync } from 'fs';
|
|
20
|
-
import { join } from 'path';
|
|
21
|
-
import { resolveGitCommonDir } from '../../../framework/hooks/shared/git-paths.js';
|
|
22
|
-
import { reserveUseCases } from '../../lib/specs/usecase-registry.js';
|
|
23
|
-
import { checkUseCases } from '../../lib/specs/usecase-check.js';
|
|
24
|
-
import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
|
|
25
|
-
import { parseTasksJsonLenient } from '../../lib/tasks/tasks-json-parser.js';
|
|
26
|
-
import { isValidFeatureName } from '../project/worktree.js';
|
|
27
|
-
import { printLifecycleWarnings } from '../tasks/dag.js';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Best-effort feature-name inference when `--feature` is omitted: only when
|
|
31
|
-
* exactly one active feature exists is the guess unambiguous. Anything else
|
|
32
|
-
* (zero, or more than one) is left null rather than guessed wrong — a wrong
|
|
33
|
-
* guess would poison the collision guard's provenance record.
|
|
34
|
-
*/
|
|
35
|
-
function inferActiveFeature(cwd) {
|
|
36
|
-
const featuresDir = join(cwd, '.morph', 'features');
|
|
37
|
-
try {
|
|
38
|
-
if (!existsSync(featuresDir)) return null;
|
|
39
|
-
const entries = readdirSync(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
40
|
-
return entries.length === 1 ? entries[0].name : null;
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param {{ count?: string, feature?: string, json?: boolean, cwd?: string }} [options]
|
|
48
|
-
*/
|
|
49
|
-
export function usecaseReserveCommand(options = {}) {
|
|
50
|
-
const cwd = options.cwd || process.cwd();
|
|
51
|
-
const count = Number.parseInt(options.count, 10);
|
|
52
|
-
const n = Number.isInteger(count) && count > 0 ? count : 1;
|
|
53
|
-
const feature = options.feature || inferActiveFeature(cwd) || null;
|
|
54
|
-
|
|
55
|
-
let gitCommonDir = null;
|
|
56
|
-
try { gitCommonDir = resolveGitCommonDir(cwd); } catch { gitCommonDir = null; }
|
|
57
|
-
|
|
58
|
-
let result;
|
|
59
|
-
try {
|
|
60
|
-
result = reserveUseCases(cwd, { feature, count: n, gitCommonDir });
|
|
61
|
-
} catch (error) {
|
|
62
|
-
if (options.json) {
|
|
63
|
-
console.log(JSON.stringify({ reserved: false, error: error.message }));
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const warning = gitCommonDir
|
|
71
|
-
? null
|
|
72
|
-
: 'projeto fora de um repositório git — numeração protegida só localmente; paralelismo entre worktrees NÃO está garantido.';
|
|
73
|
-
|
|
74
|
-
if (options.json) {
|
|
75
|
-
console.log(JSON.stringify({ reserved: true, ...result, feature, warning }));
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
console.log(chalk.green(`\n✅ Reservado: ${result.numbers.join(' ')}`));
|
|
80
|
-
if (feature) console.log(chalk.gray(` feature: ${feature}`));
|
|
81
|
-
if (warning) console.log(chalk.yellow(`⚠️ ${warning}\n`));
|
|
82
|
-
else console.log('');
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* `morph-spec usecase check <feature> [--json]` — the use-case half of `dag`,
|
|
88
|
-
* runnable before `tasks.json` exists: the feature's use cases and the
|
|
89
|
-
* revocations its `spec.md` declares, against the acervo's lifecycle. With a
|
|
90
|
-
* `tasks.json`, also the task → use case plumbing `dag` checks.
|
|
91
|
-
*
|
|
92
|
-
* Exit 1 only for what `dag` also refuses (a task pointing at a use case that
|
|
93
|
-
* does not exist, a tasks.json that does not validate); lifecycle findings are
|
|
94
|
-
* warnings.
|
|
95
|
-
*
|
|
96
|
-
* @param {string} feature
|
|
97
|
-
* @param {{ json?: boolean, cwd?: string }} [options]
|
|
98
|
-
*/
|
|
99
|
-
export async function usecaseCheckCommand(feature, options = {}) {
|
|
100
|
-
const cwd = options.cwd || process.cwd();
|
|
101
|
-
const fail = (error) => {
|
|
102
|
-
if (options.json) console.log(JSON.stringify({ feature, error }));
|
|
103
|
-
else console.error(chalk.red(`\n❌ ${error}\n`));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (!isValidFeatureName(feature)) fail(`nome de feature inválido: "${feature}"`);
|
|
108
|
-
const featureDir = join(cwd, '.morph', 'features', feature);
|
|
109
|
-
if (!existsSync(featureDir)) fail(`feature não encontrada: .morph/features/${feature}`);
|
|
110
|
-
|
|
111
|
-
const tasksPath = join(featureDir, '2-plan', 'tasks.json');
|
|
112
|
-
let tasksData = null;
|
|
113
|
-
if (existsSync(tasksPath)) {
|
|
114
|
-
try {
|
|
115
|
-
({ data: tasksData } = await parseTasksJsonLenient(tasksPath));
|
|
116
|
-
} catch (error) {
|
|
117
|
-
fail(`tasks.json inválido: ${error.message}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const useCases = tasksData ? checkUseCases(tasksData, join(featureDir, '2-plan', 'usecases')) : null;
|
|
122
|
-
const lifecycle = checkLifecycle({ cwd, featureDir, tasksData });
|
|
123
|
-
const broken = useCases?.errors.length > 0;
|
|
124
|
-
|
|
125
|
-
if (options.json) {
|
|
126
|
-
console.log(JSON.stringify({ feature, useCases, lifecycle }, null, 2));
|
|
127
|
-
process.exit(broken ? 1 : 0);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (broken) {
|
|
131
|
-
console.error(chalk.red(`\n❌ Use cases — ${useCases.errors.length} erro(s)\n`));
|
|
132
|
-
for (const e of useCases.errors) console.error(chalk.red(` ${e}`));
|
|
133
|
-
}
|
|
134
|
-
if (useCases?.warnings.length) {
|
|
135
|
-
console.error(chalk.yellow(`\n⚠ Use cases — ${useCases.warnings.length} aviso(s)`));
|
|
136
|
-
for (const w of useCases.warnings) console.error(chalk.yellow(` ${w}`));
|
|
137
|
-
}
|
|
138
|
-
printLifecycleWarnings(lifecycle);
|
|
139
|
-
if (!broken && !useCases?.warnings.length && !lifecycle.warnings.length) {
|
|
140
|
-
console.log(chalk.green(`\n✅ ${feature}: use cases e acervo sem achados\n`));
|
|
141
|
-
}
|
|
142
|
-
process.exit(broken ? 1 : 0);
|
|
143
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { parseTasksJsonLenient, oversizedTasks, MAX_TASK_OUTPUTS } from '../../lib/tasks/tasks-json-parser.js';
|
|
5
|
-
import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
|
|
6
|
-
import { renderMermaidFlowchart } from '../../lib/tasks/mermaid-renderer.js';
|
|
7
|
-
import { checkUseCases } from '../../lib/specs/usecase-check.js';
|
|
8
|
-
import { checkArtifactCriteria } from '../../lib/specs/done-criteria-check.js';
|
|
9
|
-
import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
|
|
10
|
-
|
|
11
|
-
/** Shared by `dag` and `usecase check` — the two surfaces of the same finding. */
|
|
12
|
-
export function printLifecycleWarnings(lifecycle) {
|
|
13
|
-
if (!lifecycle?.warnings?.length) return;
|
|
14
|
-
const n = lifecycle.warnings.length;
|
|
15
|
-
console.error(chalk.yellow(`\n⚠ Ciclo de vida do acervo — ${n} ${n === 1 ? 'aviso' : 'avisos'}`));
|
|
16
|
-
for (const w of lifecycle.warnings) console.error(chalk.yellow(` ${w}`));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function planGateApproved(featureDir) {
|
|
20
|
-
try {
|
|
21
|
-
const feature = JSON.parse(readFileSync(join(featureDir, 'feature.json'), 'utf8'));
|
|
22
|
-
return feature?.approvalGates?.plan?.approved === true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
|
|
30
|
-
* sequential vs parallel dispatch decision, weighted by effort.
|
|
31
|
-
*
|
|
32
|
-
* This is the command morph-apply §2 runs instead of re-deriving the rules
|
|
33
|
-
* (and instead of the old inline JS snippets the LLM could not execute).
|
|
34
|
-
*
|
|
35
|
-
* `--mermaid` emits the DAG as a Mermaid `flowchart TD` instead (for
|
|
36
|
-
* visualizing the plan at Gate 2), bypassing the dispatch analysis entirely.
|
|
37
|
-
*/
|
|
38
|
-
export async function dagCommand(featureName, options = {}) {
|
|
39
|
-
try {
|
|
40
|
-
const featureDir = join(process.cwd(), '.morph', 'features', featureName);
|
|
41
|
-
const planDir = join(featureDir, '2-plan');
|
|
42
|
-
const tasksPath = join(planDir, 'tasks.json');
|
|
43
|
-
if (!existsSync(tasksPath)) {
|
|
44
|
-
console.error(chalk.red(`\n❌ tasks.json not found for feature "${featureName}"`));
|
|
45
|
-
console.error(chalk.gray(`Expected at: .morph/features/${featureName}/2-plan/tasks.json\n`));
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// parseTasksJsonLenient validates the schema and throws with the error
|
|
50
|
-
// list, but defaults a missing `version` (legacy plans predating the
|
|
51
|
-
// field) instead of failing — the DAG analysis doesn't depend on it.
|
|
52
|
-
const { data, versionDefaulted } = await parseTasksJsonLenient(tasksPath);
|
|
53
|
-
if (versionDefaulted) {
|
|
54
|
-
console.error(chalk.yellow(
|
|
55
|
-
`⚠ tasks.json has no 'version' field — defaulting to "1.0" for this analysis.\n` +
|
|
56
|
-
` Consider adding it back explicitly (safe: protect-spec-files allows backfilling it).`
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// --mermaid short-circuits before the dispatch analysis: visualizing the
|
|
61
|
-
// plan's DAG doesn't need a sequential-vs-parallel decision.
|
|
62
|
-
if (options.mermaid) {
|
|
63
|
-
console.log(renderMermaidFlowchart(data));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Use-case cross-check. Runs AFTER the --mermaid short-circuit on purpose:
|
|
68
|
-
// rendering the DAG is pure visualization and must never be blocked by the
|
|
69
|
-
// state of the spec. A broken `usecase` reference IS fatal here — the file
|
|
70
|
-
// either exists or it does not, so there is no false positive to weigh
|
|
71
|
-
// against a plan that points at a use case nobody can read.
|
|
72
|
-
const ucCheck = checkUseCases(data, join(planDir, 'usecases'));
|
|
73
|
-
|
|
74
|
-
// Artifact-postcondition warning (issue #87). Lexical, not wiring — it
|
|
75
|
-
// runs over ALL tasks even when the feature has no use cases at all
|
|
76
|
-
// (checkUseCases short-circuits above in that case), because a task's
|
|
77
|
-
// own doneCriteria can describe an artifact with no use case in sight.
|
|
78
|
-
const artifactCriteria = checkArtifactCriteria(data, join(planDir, 'usecases'));
|
|
79
|
-
|
|
80
|
-
// Acervo lifecycle: a task citing a superseded use case, and the spec.md
|
|
81
|
-
// revocation rows the finish would fail to apply. Warnings only.
|
|
82
|
-
const lifecycle = checkLifecycle({ cwd: process.cwd(), featureDir, tasksData: data });
|
|
83
|
-
|
|
84
|
-
// Task acima do teto não cabe num dispatch. Antes do Gate 2 é erro (o plano
|
|
85
|
-
// ainda fatia); depois é aviso, para não travar feature em voo.
|
|
86
|
-
const oversized = oversizedTasks(data);
|
|
87
|
-
const sizeBlocks = oversized.length > 0 && !planGateApproved(featureDir);
|
|
88
|
-
const taskSize = { max: MAX_TASK_OUTPUTS, oversized, blocking: sizeBlocks };
|
|
89
|
-
|
|
90
|
-
const plan = planDispatch(data);
|
|
91
|
-
|
|
92
|
-
if (options.json) {
|
|
93
|
-
console.log(JSON.stringify({ ...plan, useCases: ucCheck, artifactCriteria, lifecycle, taskSize }, null, 2));
|
|
94
|
-
if (ucCheck.errors.length || sizeBlocks) process.exit(1);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
99
|
-
|
|
100
|
-
if (ucCheck.errors.length) {
|
|
101
|
-
console.error(chalk.red(`\n❌ Use cases — ${plural(ucCheck.errors.length, 'erro', 'erros')}\n`));
|
|
102
|
-
for (const e of ucCheck.errors) console.error(chalk.red(` ${e}`));
|
|
103
|
-
console.error(chalk.gray('\n Corrija a referência no tasks.json ou restaure o use case.\n'));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
if (oversized.length) {
|
|
107
|
-
const tone = sizeBlocks ? chalk.red : chalk.yellow;
|
|
108
|
-
const head = sizeBlocks ? '❌' : '⚠';
|
|
109
|
-
console.error(tone(`\n${head} Tamanho de task — ${plural(oversized.length, 'task', 'tasks')} acima de ${MAX_TASK_OUTPUTS} outputs`));
|
|
110
|
-
for (const t of oversized) console.error(tone(` ${t.id}: ${t.outputs} outputs`));
|
|
111
|
-
console.error(chalk.gray(' Fatie por pós-condição: uma task cabe num dispatch (morph-plan, "Tamanho da task").\n'));
|
|
112
|
-
if (sizeBlocks) process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
if (ucCheck.warnings.length) {
|
|
115
|
-
console.error(chalk.yellow(`\n⚠ Use cases — ${plural(ucCheck.warnings.length, 'aviso', 'avisos')}`));
|
|
116
|
-
for (const w of ucCheck.warnings) console.error(chalk.yellow(` ${w}`));
|
|
117
|
-
}
|
|
118
|
-
if (artifactCriteria.warnings.length) {
|
|
119
|
-
console.error(chalk.yellow(`\n⚠ Critérios de artefato — ${plural(artifactCriteria.warnings.length, 'aviso', 'avisos')}`));
|
|
120
|
-
for (const w of artifactCriteria.warnings) console.error(chalk.yellow(` ${w}`));
|
|
121
|
-
console.error(chalk.gray(' Teste que lê texto de arquivo não prova comportamento (morph-plan §2, "Por que artefato").'));
|
|
122
|
-
}
|
|
123
|
-
printLifecycleWarnings(lifecycle);
|
|
124
|
-
|
|
125
|
-
console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
|
|
126
|
-
console.log('━'.repeat(60));
|
|
127
|
-
|
|
128
|
-
const { stats } = plan;
|
|
129
|
-
console.log(`\nTasks: ${stats.total} total, ${stats.pending} pendente(s)`);
|
|
130
|
-
const groupLine = Object.keys(stats.groups)
|
|
131
|
-
.map(g => `${g}:${stats.groups[g]} (peso ${stats.weights[g]})`)
|
|
132
|
-
.join(' | ');
|
|
133
|
-
if (groupLine) console.log(chalk.gray(`Grupos pendentes: ${groupLine}`));
|
|
134
|
-
|
|
135
|
-
const modeLabel = {
|
|
136
|
-
'sequential': 'SEQUENCIAL (auto-advance)',
|
|
137
|
-
'parallel-flows': 'PARALELO por fluxo de domínio',
|
|
138
|
-
'parallel-roots': 'PARALELO por raízes independentes',
|
|
139
|
-
}[plan.mode];
|
|
140
|
-
console.log(chalk.cyan(`\nDecisão: ${modeLabel}`));
|
|
141
|
-
console.log(` → ${plan.reason}`);
|
|
142
|
-
|
|
143
|
-
for (const flow of plan.flows) {
|
|
144
|
-
console.log(` ${chalk.green('▶')} Fluxo ${chalk.bold(flow.group)} (peso ${flow.weight}): ${flow.taskIds.join(', ')}`);
|
|
145
|
-
}
|
|
146
|
-
if (plan.batch.length > 0) {
|
|
147
|
-
console.log(` ${chalk.green('▶')} Batch paralelo: ${plan.batch.join(', ')}`);
|
|
148
|
-
}
|
|
149
|
-
if (plan.inline.length > 0) {
|
|
150
|
-
console.log(` ${chalk.yellow('○')} Inline (sem dispatch próprio): ${plan.inline.join(', ')}`);
|
|
151
|
-
}
|
|
152
|
-
if (plan.sequentialRemainder.length > 0) {
|
|
153
|
-
console.log(` ${chalk.gray('→')} Sequencial: ${plan.sequentialRemainder.join(', ')}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Isolamento — only meaningful when there IS a parallel batch. In a shared
|
|
157
|
-
// working tree two agents writing the same path corrupt each other; the
|
|
158
|
-
// dag recommends `isolation: "worktree"` so each runs in its own worktree
|
|
159
|
-
// (morph-apply §3b-w).
|
|
160
|
-
const iso = plan.isolation;
|
|
161
|
-
if (iso && (iso.recommended === 'worktree' || iso.unknownFootprint.length > 0)) {
|
|
162
|
-
console.log(chalk.bold('\nIsolamento:'));
|
|
163
|
-
if (iso.recommended === 'worktree') {
|
|
164
|
-
console.log(` ${chalk.yellow('⚠')} recomendado: ${chalk.bold('worktree')} — ${iso.reason}`);
|
|
165
|
-
for (const c of iso.collisions) {
|
|
166
|
-
console.log(chalk.gray(` colisão em "${c.path}": ${c.units.join(' ‖ ')} (${c.tasks.join(', ')})`));
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
console.log(` ${chalk.gray('○')} ${iso.reason}`);
|
|
170
|
-
}
|
|
171
|
-
if (iso.unknownFootprint.length > 0) {
|
|
172
|
-
console.log(chalk.gray(` footprint desconhecido (sem outputs): ${iso.unknownFootprint.join(', ')}`));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log('\n' + '━'.repeat(60) + '\n');
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.error(chalk.red(`\n❌ Error analyzing DAG: ${error.message}\n`));
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* output-collector — Collect phase output files for multi-source agent detection
|
|
3
|
-
*
|
|
4
|
-
* Reads all existing phase outputs for a feature and returns them as
|
|
5
|
-
* weighted TextSource[] for use in detectAgents().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from 'fs';
|
|
9
|
-
import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Default source weights per output type.
|
|
13
|
-
* Keys match OUTPUT_SCHEMA keys (camelCase).
|
|
14
|
-
*/
|
|
15
|
-
export const SOURCE_WEIGHTS = {
|
|
16
|
-
proposal: 1.0,
|
|
17
|
-
stackScan: 0.8,
|
|
18
|
-
spec: 1.5,
|
|
19
|
-
tasks: 1.3,
|
|
20
|
-
decisions: 1.0,
|
|
21
|
-
uiDesignSystem: 1.0,
|
|
22
|
-
uiMockups: 1.0,
|
|
23
|
-
uiComponents: 1.0,
|
|
24
|
-
uiFlows: 1.0,
|
|
25
|
-
recap: 1.0,
|
|
26
|
-
reviewReport: 1.0,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/** Phase order for sorting collected outputs (v5 NLH model) */
|
|
30
|
-
const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Collect all existing phase output files for a feature.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
36
|
-
* @param {string} featureName - Feature name
|
|
37
|
-
* @returns {Array<{source: string, content: string, weight: number}>}
|
|
38
|
-
*/
|
|
39
|
-
export function collectPhaseOutputs(projectRoot, featureName) {
|
|
40
|
-
const results = [];
|
|
41
|
-
|
|
42
|
-
for (const [key, entry] of Object.entries(OUTPUT_SCHEMA)) {
|
|
43
|
-
// Multi-file types resolve to a DIRECTORY — there is no single document to
|
|
44
|
-
// weigh as one source. Skipped explicitly rather than left to fail as an
|
|
45
|
-
// EISDIR swallowed by the catch below.
|
|
46
|
-
if (entry.multi) continue;
|
|
47
|
-
|
|
48
|
-
const absPath = getAbsoluteOutputPath(projectRoot, featureName, key);
|
|
49
|
-
if (!existsSync(absPath)) continue;
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const content = readFileSync(absPath, 'utf8');
|
|
53
|
-
if (!content.trim()) continue; // Skip empty files
|
|
54
|
-
|
|
55
|
-
results.push({
|
|
56
|
-
source: entry.filename,
|
|
57
|
-
content,
|
|
58
|
-
weight: SOURCE_WEIGHTS[key] ?? 1.0,
|
|
59
|
-
_phase: entry.phase,
|
|
60
|
-
});
|
|
61
|
-
} catch {
|
|
62
|
-
// Skip unreadable files — fail-open
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Sort by phase order
|
|
68
|
-
results.sort((a, b) => {
|
|
69
|
-
const orderA = PHASE_ORDER.indexOf(a._phase);
|
|
70
|
-
const orderB = PHASE_ORDER.indexOf(b._phase);
|
|
71
|
-
return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Remove internal _phase field
|
|
75
|
-
return results.map(({ source, content, weight }) => ({ source, content, weight }));
|
|
76
|
-
}
|