@polymorphism-tech/morph-spec 2.2.0 → 2.4.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 +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
package/src/lib/state-manager.js
CHANGED
|
@@ -1,340 +1,397 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC State Manager Library
|
|
3
|
-
*
|
|
4
|
-
* Manages state.json for tracking features, progress, agents, and checkpoints.
|
|
5
|
-
* Used both by CLI commands and internal automation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
-
import { join, dirname } from 'path';
|
|
10
|
-
|
|
11
|
-
const STATE_FILE_NAME = '.morph/state.json';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Core Functions
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get the state file path (looks in current working directory)
|
|
19
|
-
*/
|
|
20
|
-
export function getStatePath() {
|
|
21
|
-
return join(process.cwd(), STATE_FILE_NAME);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Check if state file exists
|
|
26
|
-
*/
|
|
27
|
-
export function stateExists() {
|
|
28
|
-
return existsSync(getStatePath());
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Load state from disk
|
|
33
|
-
* @param {boolean} throwOnError - If false, returns null instead of throwing
|
|
34
|
-
* @returns {Object|null} State object or null
|
|
35
|
-
*/
|
|
36
|
-
export function loadState(throwOnError = true) {
|
|
37
|
-
const statePath = getStatePath();
|
|
38
|
-
|
|
39
|
-
if (!existsSync(statePath)) {
|
|
40
|
-
if (throwOnError) {
|
|
41
|
-
throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const content = readFileSync(statePath, 'utf8');
|
|
48
|
-
return JSON.parse(content);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
if (throwOnError) {
|
|
51
|
-
throw new Error(`Failed to parse state.json: ${err.message}`);
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Save state to disk
|
|
59
|
-
* @param {Object} state - State object to save
|
|
60
|
-
*/
|
|
61
|
-
export function saveState(state) {
|
|
62
|
-
state.metadata = state.metadata || {};
|
|
63
|
-
state.metadata.lastUpdated = new Date().toISOString();
|
|
64
|
-
|
|
65
|
-
const statePath = getStatePath();
|
|
66
|
-
const stateDir = dirname(statePath);
|
|
67
|
-
|
|
68
|
-
// Ensure .morph directory exists
|
|
69
|
-
if (!existsSync(stateDir)) {
|
|
70
|
-
mkdirSync(stateDir, { recursive: true });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Initialize new state file
|
|
78
|
-
* @param {Object} options - Options
|
|
79
|
-
* @param {boolean} options.force - Overwrite existing file
|
|
80
|
-
* @param {string} options.projectName - Project name
|
|
81
|
-
* @param {string} options.projectType - Project type (e.g., 'blazor-server')
|
|
82
|
-
* @returns {Object} Initial state
|
|
83
|
-
*/
|
|
84
|
-
export function initState(options = {}) {
|
|
85
|
-
const { force = false, projectName = '{PROJECT_NAME}', projectType = 'blazor-server' } = options;
|
|
86
|
-
|
|
87
|
-
if (stateExists() && !force) {
|
|
88
|
-
throw new Error('State file already exists. Use force=true to overwrite.');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const initialState = {
|
|
92
|
-
version: "2.1.1",
|
|
93
|
-
project: {
|
|
94
|
-
name: projectName,
|
|
95
|
-
type: projectType,
|
|
96
|
-
createdAt: new Date().toISOString(),
|
|
97
|
-
updatedAt: new Date().toISOString()
|
|
98
|
-
},
|
|
99
|
-
features: {},
|
|
100
|
-
metadata: {
|
|
101
|
-
totalFeatures: 0,
|
|
102
|
-
completedFeatures: 0,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
*
|
|
129
|
-
* @
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
state
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC State Manager Library
|
|
3
|
+
*
|
|
4
|
+
* Manages state.json for tracking features, progress, agents, and checkpoints.
|
|
5
|
+
* Used both by CLI commands and internal automation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
|
|
11
|
+
const STATE_FILE_NAME = '.morph/state.json';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Core Functions
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the state file path (looks in current working directory)
|
|
19
|
+
*/
|
|
20
|
+
export function getStatePath() {
|
|
21
|
+
return join(process.cwd(), STATE_FILE_NAME);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if state file exists
|
|
26
|
+
*/
|
|
27
|
+
export function stateExists() {
|
|
28
|
+
return existsSync(getStatePath());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load state from disk
|
|
33
|
+
* @param {boolean} throwOnError - If false, returns null instead of throwing
|
|
34
|
+
* @returns {Object|null} State object or null
|
|
35
|
+
*/
|
|
36
|
+
export function loadState(throwOnError = true) {
|
|
37
|
+
const statePath = getStatePath();
|
|
38
|
+
|
|
39
|
+
if (!existsSync(statePath)) {
|
|
40
|
+
if (throwOnError) {
|
|
41
|
+
throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const content = readFileSync(statePath, 'utf8');
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (throwOnError) {
|
|
51
|
+
throw new Error(`Failed to parse state.json: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Save state to disk
|
|
59
|
+
* @param {Object} state - State object to save
|
|
60
|
+
*/
|
|
61
|
+
export function saveState(state) {
|
|
62
|
+
state.metadata = state.metadata || {};
|
|
63
|
+
state.metadata.lastUpdated = new Date().toISOString();
|
|
64
|
+
|
|
65
|
+
const statePath = getStatePath();
|
|
66
|
+
const stateDir = dirname(statePath);
|
|
67
|
+
|
|
68
|
+
// Ensure .morph directory exists
|
|
69
|
+
if (!existsSync(stateDir)) {
|
|
70
|
+
mkdirSync(stateDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize new state file
|
|
78
|
+
* @param {Object} options - Options
|
|
79
|
+
* @param {boolean} options.force - Overwrite existing file
|
|
80
|
+
* @param {string} options.projectName - Project name
|
|
81
|
+
* @param {string} options.projectType - Project type (e.g., 'blazor-server')
|
|
82
|
+
* @returns {Object} Initial state
|
|
83
|
+
*/
|
|
84
|
+
export function initState(options = {}) {
|
|
85
|
+
const { force = false, projectName = '{PROJECT_NAME}', projectType = 'blazor-server' } = options;
|
|
86
|
+
|
|
87
|
+
if (stateExists() && !force) {
|
|
88
|
+
throw new Error('State file already exists. Use force=true to overwrite.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const initialState = {
|
|
92
|
+
version: "2.1.1",
|
|
93
|
+
project: {
|
|
94
|
+
name: projectName,
|
|
95
|
+
type: projectType,
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
updatedAt: new Date().toISOString()
|
|
98
|
+
},
|
|
99
|
+
features: {},
|
|
100
|
+
metadata: {
|
|
101
|
+
totalFeatures: 0,
|
|
102
|
+
completedFeatures: 0,
|
|
103
|
+
totalTimeSpent: 0,
|
|
104
|
+
lastUpdated: new Date().toISOString()
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
saveState(initialState);
|
|
109
|
+
return initialState;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Feature Operations
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get feature from state
|
|
118
|
+
* @param {string} featureName - Feature name
|
|
119
|
+
* @returns {Object|null} Feature object or null
|
|
120
|
+
*/
|
|
121
|
+
export function getFeature(featureName) {
|
|
122
|
+
const state = loadState();
|
|
123
|
+
return state.features[featureName] || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create or get feature with default structure
|
|
128
|
+
* @param {string} featureName - Feature name
|
|
129
|
+
* @returns {Object} Feature object
|
|
130
|
+
*/
|
|
131
|
+
function ensureFeature(featureName) {
|
|
132
|
+
const state = loadState();
|
|
133
|
+
|
|
134
|
+
if (!state.features[featureName]) {
|
|
135
|
+
state.features[featureName] = {
|
|
136
|
+
status: "draft",
|
|
137
|
+
phase: "proposal",
|
|
138
|
+
workflow: "auto", // auto | fast-track | standard | full-morph
|
|
139
|
+
createdAt: new Date().toISOString(),
|
|
140
|
+
updatedAt: new Date().toISOString(),
|
|
141
|
+
activeAgents: [],
|
|
142
|
+
outputs: {
|
|
143
|
+
proposal: { created: false, path: `.morph/project/outputs/${featureName}/proposal.md` },
|
|
144
|
+
spec: { created: false, path: `.morph/project/outputs/${featureName}/spec.md` },
|
|
145
|
+
contracts: { created: false, path: `.morph/project/outputs/${featureName}/contracts.cs` },
|
|
146
|
+
tasks: { created: false, path: `.morph/project/outputs/${featureName}/tasks.json` },
|
|
147
|
+
uiDesignSystem: { created: false, path: `.morph/project/outputs/${featureName}/ui-design-system.md` },
|
|
148
|
+
uiMockups: { created: false, path: `.morph/project/outputs/${featureName}/ui-mockups.md` },
|
|
149
|
+
uiComponents: { created: false, path: `.morph/project/outputs/${featureName}/ui-components.md` },
|
|
150
|
+
uiFlows: { created: false, path: `.morph/project/outputs/${featureName}/ui-flows.md` },
|
|
151
|
+
decisions: { created: false, path: `.morph/project/outputs/${featureName}/decisions.md` },
|
|
152
|
+
recap: { created: false, path: `.morph/project/outputs/${featureName}/recap.md` }
|
|
153
|
+
},
|
|
154
|
+
tasks: {
|
|
155
|
+
total: 0,
|
|
156
|
+
completed: 0,
|
|
157
|
+
inProgress: 0,
|
|
158
|
+
pending: 0
|
|
159
|
+
},
|
|
160
|
+
checkpoints: []
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
state.metadata.totalFeatures++;
|
|
164
|
+
saveState(state);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return state.features[featureName];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Update feature property (supports nested keys like "tasks.completed")
|
|
172
|
+
* @param {string} featureName - Feature name
|
|
173
|
+
* @param {string} key - Property key (supports dot notation)
|
|
174
|
+
* @param {any} value - Value to set
|
|
175
|
+
*/
|
|
176
|
+
export function updateFeature(featureName, key, value) {
|
|
177
|
+
ensureFeature(featureName);
|
|
178
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
179
|
+
|
|
180
|
+
const keys = key.split('.');
|
|
181
|
+
let target = state.features[featureName];
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
184
|
+
if (!target[keys[i]]) {
|
|
185
|
+
target[keys[i]] = {};
|
|
186
|
+
}
|
|
187
|
+
target = target[keys[i]];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const finalKey = keys[keys.length - 1];
|
|
191
|
+
target[finalKey] = value;
|
|
192
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
193
|
+
|
|
194
|
+
saveState(state);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Update multiple feature properties at once
|
|
199
|
+
* @param {string} featureName - Feature name
|
|
200
|
+
* @param {Object} updates - Object with key-value pairs to update
|
|
201
|
+
*/
|
|
202
|
+
export function updateFeatureMultiple(featureName, updates) {
|
|
203
|
+
ensureFeature(featureName);
|
|
204
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
205
|
+
|
|
206
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
207
|
+
const keys = key.split('.');
|
|
208
|
+
let target = state.features[featureName];
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
211
|
+
if (!target[keys[i]]) {
|
|
212
|
+
target[keys[i]] = {};
|
|
213
|
+
}
|
|
214
|
+
target = target[keys[i]];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const finalKey = keys[keys.length - 1];
|
|
218
|
+
target[finalKey] = value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
222
|
+
saveState(state);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Add checkpoint to feature
|
|
227
|
+
* @param {string} featureName - Feature name
|
|
228
|
+
* @param {string} note - Checkpoint note
|
|
229
|
+
* @returns {Object} Checkpoint object
|
|
230
|
+
*/
|
|
231
|
+
export function addCheckpoint(featureName, note) {
|
|
232
|
+
ensureFeature(featureName);
|
|
233
|
+
const state = loadState();
|
|
234
|
+
const feature = state.features[featureName];
|
|
235
|
+
|
|
236
|
+
const checkpoint = {
|
|
237
|
+
timestamp: new Date().toISOString(),
|
|
238
|
+
phase: feature.phase,
|
|
239
|
+
completedTasks: feature.tasks.completed,
|
|
240
|
+
note: note
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
feature.checkpoints.push(checkpoint);
|
|
244
|
+
feature.updatedAt = new Date().toISOString();
|
|
245
|
+
|
|
246
|
+
saveState(state);
|
|
247
|
+
return checkpoint;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Add agent to feature
|
|
252
|
+
* @param {string} featureName - Feature name
|
|
253
|
+
* @param {string} agentId - Agent ID
|
|
254
|
+
* @returns {boolean} True if added, false if already exists
|
|
255
|
+
*/
|
|
256
|
+
export function addAgent(featureName, agentId) {
|
|
257
|
+
ensureFeature(featureName);
|
|
258
|
+
const state = loadState(); // Load AFTER ensuring feature exists
|
|
259
|
+
|
|
260
|
+
if (!state.features[featureName].activeAgents.includes(agentId)) {
|
|
261
|
+
state.features[featureName].activeAgents.push(agentId);
|
|
262
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
263
|
+
saveState(state);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Remove agent from feature
|
|
272
|
+
* @param {string} featureName - Feature name
|
|
273
|
+
* @param {string} agentId - Agent ID
|
|
274
|
+
* @returns {boolean} True if removed, false if not found
|
|
275
|
+
*/
|
|
276
|
+
export function removeAgent(featureName, agentId) {
|
|
277
|
+
const state = loadState();
|
|
278
|
+
|
|
279
|
+
if (!state.features[featureName]) {
|
|
280
|
+
throw new Error(`Feature '${featureName}' not found.`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const index = state.features[featureName].activeAgents.indexOf(agentId);
|
|
284
|
+
if (index > -1) {
|
|
285
|
+
state.features[featureName].activeAgents.splice(index, 1);
|
|
286
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
287
|
+
saveState(state);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Mark output as created
|
|
296
|
+
* @param {string} featureName - Feature name
|
|
297
|
+
* @param {string} outputType - Output type (proposal, spec, contracts, etc.)
|
|
298
|
+
*/
|
|
299
|
+
export function markOutput(featureName, outputType) {
|
|
300
|
+
ensureFeature(featureName);
|
|
301
|
+
const state = loadState();
|
|
302
|
+
|
|
303
|
+
if (!state.features[featureName].outputs[outputType]) {
|
|
304
|
+
throw new Error(`Output type '${outputType}' not valid. Valid types: proposal, spec, contracts, tasks, uiDesignSystem, uiMockups, uiComponents, uiFlows, decisions, recap`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
state.features[featureName].outputs[outputType].created = true;
|
|
308
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
309
|
+
|
|
310
|
+
// If marking tasks output, try to sync task count from state tasks array
|
|
311
|
+
if (outputType === 'tasks') {
|
|
312
|
+
syncTasksCount(state.features[featureName]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
saveState(state);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Sync tasks count from tasks array (if exists)
|
|
320
|
+
* @param {Object} feature - Feature object
|
|
321
|
+
*/
|
|
322
|
+
function syncTasksCount(feature) {
|
|
323
|
+
// If feature has tasks array (schema 3.0), count them
|
|
324
|
+
if (feature.tasks && Array.isArray(feature.tasks)) {
|
|
325
|
+
const tasks = feature.tasks;
|
|
326
|
+
feature.progress = {
|
|
327
|
+
total: tasks.length,
|
|
328
|
+
completed: tasks.filter(t => t.status === 'completed').length,
|
|
329
|
+
inProgress: tasks.filter(t => t.status === 'in_progress').length,
|
|
330
|
+
pending: tasks.filter(t => t.status === 'pending').length,
|
|
331
|
+
percentage: tasks.length > 0 ? Math.round((tasks.filter(t => t.status === 'completed').length / tasks.length) * 100) : 0
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Skip a phase and record it
|
|
338
|
+
* @param {string} featureName - Feature name
|
|
339
|
+
* @param {string} phase - Phase to skip
|
|
340
|
+
* @param {string} reason - Reason for skipping
|
|
341
|
+
*/
|
|
342
|
+
export function skipPhase(featureName, phase, reason = '') {
|
|
343
|
+
ensureFeature(featureName);
|
|
344
|
+
const state = loadState();
|
|
345
|
+
|
|
346
|
+
if (!state.features[featureName].skippedPhases) {
|
|
347
|
+
state.features[featureName].skippedPhases = [];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Don't add duplicates
|
|
351
|
+
const existing = state.features[featureName].skippedPhases.find(s => s.phase === phase);
|
|
352
|
+
if (!existing) {
|
|
353
|
+
state.features[featureName].skippedPhases.push({
|
|
354
|
+
phase,
|
|
355
|
+
reason,
|
|
356
|
+
timestamp: new Date().toISOString()
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
361
|
+
saveState(state);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get skipped phases for a feature
|
|
366
|
+
* @param {string} featureName - Feature name
|
|
367
|
+
* @returns {Array} Array of skipped phases
|
|
368
|
+
*/
|
|
369
|
+
export function getSkippedPhases(featureName) {
|
|
370
|
+
const state = loadState();
|
|
371
|
+
if (!state.features[featureName]) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
return state.features[featureName].skippedPhases || [];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* List all features
|
|
379
|
+
* @returns {Array} Array of [featureName, featureObject] tuples
|
|
380
|
+
*/
|
|
381
|
+
export function listFeatures() {
|
|
382
|
+
const state = loadState();
|
|
383
|
+
return Object.entries(state.features);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get project summary
|
|
388
|
+
* @returns {Object} Summary with metadata
|
|
389
|
+
*/
|
|
390
|
+
export function getSummary() {
|
|
391
|
+
const state = loadState();
|
|
392
|
+
return {
|
|
393
|
+
project: state.project,
|
|
394
|
+
metadata: state.metadata,
|
|
395
|
+
featuresCount: Object.keys(state.features).length
|
|
396
|
+
};
|
|
397
|
+
}
|