@polymorphism-tech/morph-spec 8.28.0 → 8.31.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 +6 -1
- package/README.md +13 -2
- package/bin/morph-spec.js +48 -0
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -0
- package/framework/MORPH.md +2 -1
- package/framework/agents.json +20 -9
- package/framework/commands/create-umbraco-lp.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +18 -0
- package/framework/evals/rubrics/code-quality.md +17 -0
- package/framework/evals/rubrics/condensed.md +3 -1
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/phase-utils.js +2 -0
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/mcp/registry.json +97 -11
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/schemas/tasks.schema.json +25 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +13 -2
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +598 -0
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +92 -7
- package/framework/skills/core/morph-apply/SKILL.md +12 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +12 -2
- package/framework/skills/core/morph-plan/SKILL.md +45 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +11 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +13 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +41 -0
- package/framework/standards/STANDARDS.json +218 -7
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +231 -231
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/frontend/design-system/ai-image-generation.md +43 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +113 -0
- package/framework/standards/frontend/design-system/premium-finish.md +160 -0
- package/framework/standards/frontend/gsap/gsap-core.md +1 -1
- package/framework/standards/frontend/gsap/gsap-plugins.md +1 -1
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +3 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +3 -1
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +287 -0
- package/framework/standards/frontend/scroll-driven/scroll-components.md +256 -0
- package/framework/standards/frontend/scroll-driven/smooth-scroll.md +148 -0
- package/framework/standards/frontend/seo/meta-schema-audit.md +134 -0
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/context/ARCHITECTURE.md +20 -0
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/graph/affected.js +89 -0
- package/src/commands/graph/build.js +94 -0
- package/src/commands/graph/explain.js +96 -0
- package/src/commands/graph/status.js +93 -0
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +11 -0
- package/src/core/paths/output-schema.js +7 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +45 -4
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/graph/god-nodes.js +75 -0
- package/src/lib/graph/graph-health.js +61 -0
- package/src/lib/graph/graph-loader.js +86 -0
- package/src/lib/graph/graphify-runtime.js +100 -0
- package/src/lib/graph/graphifyignore.js +86 -0
- package/src/lib/graph/sanitize.js +280 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/tasks-json-parser.js +26 -0
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/reuse/index.js +10 -0
- package/src/lib/validators/reuse/reuse-validator.js +324 -0
- package/src/lib/validators/reuse/symbol-scanner.js +144 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +40 -1
- package/src/scripts/setup-infra.js +60 -1
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +40 -0
- package/src/utils/skills-installer.js +187 -187
|
@@ -1,209 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Checkpoint Save/Restore Command
|
|
3
|
-
*
|
|
4
|
-
* Save and restore full context at phase boundaries.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* morph-spec checkpoint save <feature-name>
|
|
8
|
-
* morph-spec checkpoint restore <feature-name>
|
|
9
|
-
* morph-spec checkpoint list <feature-name>
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import fs from 'fs';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get checkpoints directory for a feature
|
|
19
|
-
*/
|
|
20
|
-
function getCheckpointsDir(featureName) {
|
|
21
|
-
return path.join(process.cwd(), '.morph/checkpoints', featureName);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Save checkpoint command
|
|
26
|
-
*/
|
|
27
|
-
export async function checkpointSaveCommand(featureName, options = {}) {
|
|
28
|
-
if (!featureName) {
|
|
29
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const feature = getFeature(featureName);
|
|
34
|
-
if (!feature) {
|
|
35
|
-
console.error(chalk.red(`Error: Feature '${featureName}' not found`));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
40
|
-
if (!fs.existsSync(outputDir)) {
|
|
41
|
-
console.error(chalk.red(`Error: No outputs found for '${featureName}'`));
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Create checkpoint directory with timestamp
|
|
46
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
47
|
-
const label = options.label || feature.phase;
|
|
48
|
-
const checkpointName = `${label}_${timestamp}`;
|
|
49
|
-
const checkpointDir = path.join(getCheckpointsDir(featureName), checkpointName);
|
|
50
|
-
fs.mkdirSync(checkpointDir, { recursive: true });
|
|
51
|
-
|
|
52
|
-
// Copy all output files
|
|
53
|
-
const files = fs.readdirSync(outputDir);
|
|
54
|
-
let fileCount = 0;
|
|
55
|
-
for (const file of files) {
|
|
56
|
-
const src = path.join(outputDir, file);
|
|
57
|
-
const stat = fs.statSync(src);
|
|
58
|
-
if (stat.isFile()) {
|
|
59
|
-
fs.copyFileSync(src, path.join(checkpointDir, file));
|
|
60
|
-
fileCount++;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Save state snapshot
|
|
65
|
-
const stateSnapshot = {
|
|
66
|
-
feature: { ...feature },
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
phase: feature.phase,
|
|
69
|
-
label,
|
|
70
|
-
files: files.filter(f => fs.statSync(path.join(outputDir, f)).isFile())
|
|
71
|
-
};
|
|
72
|
-
fs.writeFileSync(
|
|
73
|
-
path.join(checkpointDir, '_checkpoint.json'),
|
|
74
|
-
JSON.stringify(stateSnapshot, null, 2)
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
console.log(chalk.green(`\n✓ Checkpoint saved: ${checkpointName}`));
|
|
78
|
-
console.log(chalk.gray(` Phase: ${feature.phase}`));
|
|
79
|
-
console.log(chalk.gray(` Files: ${fileCount}`));
|
|
80
|
-
console.log(chalk.gray(` Path: ${checkpointDir}\n`));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Restore checkpoint command
|
|
85
|
-
*/
|
|
86
|
-
export async function checkpointRestoreCommand(featureName, checkpointName, options = {}) {
|
|
87
|
-
if (!featureName) {
|
|
88
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const checkpointsDir = getCheckpointsDir(featureName);
|
|
93
|
-
if (!fs.existsSync(checkpointsDir)) {
|
|
94
|
-
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Find checkpoint to restore
|
|
99
|
-
let targetDir;
|
|
100
|
-
if (checkpointName) {
|
|
101
|
-
targetDir = path.join(checkpointsDir, checkpointName);
|
|
102
|
-
if (!fs.existsSync(targetDir)) {
|
|
103
|
-
console.error(chalk.red(`Error: Checkpoint '${checkpointName}' not found`));
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
// Restore latest
|
|
108
|
-
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
109
|
-
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
110
|
-
.sort()
|
|
111
|
-
.reverse();
|
|
112
|
-
|
|
113
|
-
if (checkpoints.length === 0) {
|
|
114
|
-
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
targetDir = path.join(checkpointsDir, checkpoints[0]);
|
|
119
|
-
checkpointName = checkpoints[0];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Read checkpoint metadata
|
|
123
|
-
const metaPath = path.join(targetDir, '_checkpoint.json');
|
|
124
|
-
if (!fs.existsSync(metaPath)) {
|
|
125
|
-
console.error(chalk.red('Error: Checkpoint metadata not found'));
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
130
|
-
|
|
131
|
-
// Restore output files
|
|
132
|
-
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
133
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
134
|
-
|
|
135
|
-
const files = fs.readdirSync(targetDir).filter(f => f !== '_checkpoint.json');
|
|
136
|
-
for (const file of files) {
|
|
137
|
-
fs.copyFileSync(path.join(targetDir, file), path.join(outputDir, file));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Restore state
|
|
141
|
-
const state = loadState();
|
|
142
|
-
if (state.features[featureName] && meta.feature) {
|
|
143
|
-
// Preserve some current fields, restore phase and outputs
|
|
144
|
-
state.features[featureName].phase = meta.feature.phase;
|
|
145
|
-
state.features[featureName].outputs = meta.feature.outputs;
|
|
146
|
-
state.features[featureName].tasks = meta.feature.tasks;
|
|
147
|
-
state.features[featureName].approvalGates = meta.feature.approvalGates;
|
|
148
|
-
state.features[featureName].updatedAt = new Date().toISOString();
|
|
149
|
-
saveState(state);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(chalk.green(`\n✓ Checkpoint restored: ${checkpointName}`));
|
|
153
|
-
console.log(chalk.gray(` Phase: ${meta.phase}`));
|
|
154
|
-
console.log(chalk.gray(` Files restored: ${files.length}`));
|
|
155
|
-
console.log(chalk.gray(` Original date: ${meta.timestamp}\n`));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* List checkpoints command
|
|
160
|
-
*/
|
|
161
|
-
export async function checkpointListCommand(featureName, options = {}) {
|
|
162
|
-
if (!featureName) {
|
|
163
|
-
console.error(chalk.red('Error: Feature name required'));
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const checkpointsDir = getCheckpointsDir(featureName);
|
|
168
|
-
if (!fs.existsSync(checkpointsDir)) {
|
|
169
|
-
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
174
|
-
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
175
|
-
.sort()
|
|
176
|
-
.reverse();
|
|
177
|
-
|
|
178
|
-
if (checkpoints.length === 0) {
|
|
179
|
-
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// JSON output
|
|
184
|
-
if (options.json) {
|
|
185
|
-
const list = checkpoints.map(cp => {
|
|
186
|
-
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
187
|
-
const meta = fs.existsSync(metaPath) ? JSON.parse(fs.readFileSync(metaPath, 'utf8')) : {};
|
|
188
|
-
return { name: cp, phase: meta.phase, timestamp: meta.timestamp, files: meta.files?.length || 0 };
|
|
189
|
-
});
|
|
190
|
-
console.log(JSON.stringify({ feature: featureName, checkpoints: list }, null, 2));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Visual output
|
|
195
|
-
console.log(chalk.cyan(`\nCheckpoints for '${featureName}':\n`));
|
|
196
|
-
|
|
197
|
-
for (const cp of checkpoints) {
|
|
198
|
-
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
199
|
-
let meta = {};
|
|
200
|
-
if (fs.existsSync(metaPath)) {
|
|
201
|
-
meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const date = meta.timestamp ? new Date(meta.timestamp).toLocaleString() : 'unknown';
|
|
205
|
-
console.log(chalk.white(` ${cp}`));
|
|
206
|
-
console.log(chalk.gray(` Phase: ${meta.phase || 'unknown'} | Files: ${meta.files?.length || '?'} | ${date}`));
|
|
207
|
-
}
|
|
208
|
-
console.log('');
|
|
209
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Checkpoint Save/Restore Command
|
|
3
|
+
*
|
|
4
|
+
* Save and restore full context at phase boundaries.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* morph-spec checkpoint save <feature-name>
|
|
8
|
+
* morph-spec checkpoint restore <feature-name>
|
|
9
|
+
* morph-spec checkpoint list <feature-name>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get checkpoints directory for a feature
|
|
19
|
+
*/
|
|
20
|
+
function getCheckpointsDir(featureName) {
|
|
21
|
+
return path.join(process.cwd(), '.morph/checkpoints', featureName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Save checkpoint command
|
|
26
|
+
*/
|
|
27
|
+
export async function checkpointSaveCommand(featureName, options = {}) {
|
|
28
|
+
if (!featureName) {
|
|
29
|
+
console.error(chalk.red('Error: Feature name required'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const feature = getFeature(featureName);
|
|
34
|
+
if (!feature) {
|
|
35
|
+
console.error(chalk.red(`Error: Feature '${featureName}' not found`));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
40
|
+
if (!fs.existsSync(outputDir)) {
|
|
41
|
+
console.error(chalk.red(`Error: No outputs found for '${featureName}'`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create checkpoint directory with timestamp
|
|
46
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
47
|
+
const label = options.label || feature.phase;
|
|
48
|
+
const checkpointName = `${label}_${timestamp}`;
|
|
49
|
+
const checkpointDir = path.join(getCheckpointsDir(featureName), checkpointName);
|
|
50
|
+
fs.mkdirSync(checkpointDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// Copy all output files
|
|
53
|
+
const files = fs.readdirSync(outputDir);
|
|
54
|
+
let fileCount = 0;
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
const src = path.join(outputDir, file);
|
|
57
|
+
const stat = fs.statSync(src);
|
|
58
|
+
if (stat.isFile()) {
|
|
59
|
+
fs.copyFileSync(src, path.join(checkpointDir, file));
|
|
60
|
+
fileCount++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Save state snapshot
|
|
65
|
+
const stateSnapshot = {
|
|
66
|
+
feature: { ...feature },
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
phase: feature.phase,
|
|
69
|
+
label,
|
|
70
|
+
files: files.filter(f => fs.statSync(path.join(outputDir, f)).isFile())
|
|
71
|
+
};
|
|
72
|
+
fs.writeFileSync(
|
|
73
|
+
path.join(checkpointDir, '_checkpoint.json'),
|
|
74
|
+
JSON.stringify(stateSnapshot, null, 2)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
console.log(chalk.green(`\n✓ Checkpoint saved: ${checkpointName}`));
|
|
78
|
+
console.log(chalk.gray(` Phase: ${feature.phase}`));
|
|
79
|
+
console.log(chalk.gray(` Files: ${fileCount}`));
|
|
80
|
+
console.log(chalk.gray(` Path: ${checkpointDir}\n`));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Restore checkpoint command
|
|
85
|
+
*/
|
|
86
|
+
export async function checkpointRestoreCommand(featureName, checkpointName, options = {}) {
|
|
87
|
+
if (!featureName) {
|
|
88
|
+
console.error(chalk.red('Error: Feature name required'));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const checkpointsDir = getCheckpointsDir(featureName);
|
|
93
|
+
if (!fs.existsSync(checkpointsDir)) {
|
|
94
|
+
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Find checkpoint to restore
|
|
99
|
+
let targetDir;
|
|
100
|
+
if (checkpointName) {
|
|
101
|
+
targetDir = path.join(checkpointsDir, checkpointName);
|
|
102
|
+
if (!fs.existsSync(targetDir)) {
|
|
103
|
+
console.error(chalk.red(`Error: Checkpoint '${checkpointName}' not found`));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Restore latest
|
|
108
|
+
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
109
|
+
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
110
|
+
.sort()
|
|
111
|
+
.reverse();
|
|
112
|
+
|
|
113
|
+
if (checkpoints.length === 0) {
|
|
114
|
+
console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
targetDir = path.join(checkpointsDir, checkpoints[0]);
|
|
119
|
+
checkpointName = checkpoints[0];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read checkpoint metadata
|
|
123
|
+
const metaPath = path.join(targetDir, '_checkpoint.json');
|
|
124
|
+
if (!fs.existsSync(metaPath)) {
|
|
125
|
+
console.error(chalk.red('Error: Checkpoint metadata not found'));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
130
|
+
|
|
131
|
+
// Restore output files
|
|
132
|
+
const outputDir = path.join(process.cwd(), '.morph/features', featureName);
|
|
133
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
134
|
+
|
|
135
|
+
const files = fs.readdirSync(targetDir).filter(f => f !== '_checkpoint.json');
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
fs.copyFileSync(path.join(targetDir, file), path.join(outputDir, file));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Restore state
|
|
141
|
+
const state = loadState();
|
|
142
|
+
if (state.features[featureName] && meta.feature) {
|
|
143
|
+
// Preserve some current fields, restore phase and outputs
|
|
144
|
+
state.features[featureName].phase = meta.feature.phase;
|
|
145
|
+
state.features[featureName].outputs = meta.feature.outputs;
|
|
146
|
+
state.features[featureName].tasks = meta.feature.tasks;
|
|
147
|
+
state.features[featureName].approvalGates = meta.feature.approvalGates;
|
|
148
|
+
state.features[featureName].updatedAt = new Date().toISOString();
|
|
149
|
+
saveState(state);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(chalk.green(`\n✓ Checkpoint restored: ${checkpointName}`));
|
|
153
|
+
console.log(chalk.gray(` Phase: ${meta.phase}`));
|
|
154
|
+
console.log(chalk.gray(` Files restored: ${files.length}`));
|
|
155
|
+
console.log(chalk.gray(` Original date: ${meta.timestamp}\n`));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* List checkpoints command
|
|
160
|
+
*/
|
|
161
|
+
export async function checkpointListCommand(featureName, options = {}) {
|
|
162
|
+
if (!featureName) {
|
|
163
|
+
console.error(chalk.red('Error: Feature name required'));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const checkpointsDir = getCheckpointsDir(featureName);
|
|
168
|
+
if (!fs.existsSync(checkpointsDir)) {
|
|
169
|
+
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const checkpoints = fs.readdirSync(checkpointsDir)
|
|
174
|
+
.filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
|
|
175
|
+
.sort()
|
|
176
|
+
.reverse();
|
|
177
|
+
|
|
178
|
+
if (checkpoints.length === 0) {
|
|
179
|
+
console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// JSON output
|
|
184
|
+
if (options.json) {
|
|
185
|
+
const list = checkpoints.map(cp => {
|
|
186
|
+
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
187
|
+
const meta = fs.existsSync(metaPath) ? JSON.parse(fs.readFileSync(metaPath, 'utf8')) : {};
|
|
188
|
+
return { name: cp, phase: meta.phase, timestamp: meta.timestamp, files: meta.files?.length || 0 };
|
|
189
|
+
});
|
|
190
|
+
console.log(JSON.stringify({ feature: featureName, checkpoints: list }, null, 2));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Visual output
|
|
195
|
+
console.log(chalk.cyan(`\nCheckpoints for '${featureName}':\n`));
|
|
196
|
+
|
|
197
|
+
for (const cp of checkpoints) {
|
|
198
|
+
const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
|
|
199
|
+
let meta = {};
|
|
200
|
+
if (fs.existsSync(metaPath)) {
|
|
201
|
+
meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const date = meta.timestamp ? new Date(meta.timestamp).toLocaleString() : 'unknown';
|
|
205
|
+
console.log(chalk.white(` ${cp}`));
|
|
206
|
+
console.log(chalk.gray(` Phase: ${meta.phase || 'unknown'} | Files: ${meta.files?.length || '?'} | ${date}`));
|
|
207
|
+
}
|
|
208
|
+
console.log('');
|
|
209
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { resetMorphSettings } from '../../utils/claude-settings-manager.js';
|
|
15
15
|
import { checkMissingAgents, checkAgentsDrift } from '../../utils/agents-installer.js';
|
|
16
16
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
17
|
+
import { auditGraph } from '../../lib/graph/graph-health.js';
|
|
17
18
|
import { auditFeatureLifecycle } from '../../core/state/lifecycle-audit.js';
|
|
18
19
|
|
|
19
20
|
const isWindows = platform() === 'win32';
|
|
@@ -1074,6 +1075,16 @@ export async function doctorCommand(options = {}) {
|
|
|
1074
1075
|
hasWarnings = true;
|
|
1075
1076
|
}
|
|
1076
1077
|
|
|
1078
|
+
// Codebase graph — opcional (ADR-002). Nunca 'missing': um projeto que não
|
|
1079
|
+
// usa o grafo tem de continuar com o doctor verde.
|
|
1080
|
+
const graphAudit = auditGraph(targetPath);
|
|
1081
|
+
if (graphAudit.status === 'warn') {
|
|
1082
|
+
checks.push({ name: 'codebase graph', status: 'warn', msg: graphAudit.msg });
|
|
1083
|
+
hasWarnings = true;
|
|
1084
|
+
} else {
|
|
1085
|
+
checks.push({ name: 'codebase graph', status: 'ok', msg: graphAudit.msg });
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1077
1088
|
// Check key templates exist (in framework package, not project copy)
|
|
1078
1089
|
const frameworkTemplatesRoot = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
|
|
1079
1090
|
const keyTemplates = [
|
|
@@ -49,6 +49,13 @@ export const OUTPUT_SCHEMA = {
|
|
|
49
49
|
mandate: { filename: 'mandate.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
50
50
|
tasks: { filename: 'tasks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan', mutableFields: ['status', 'outputs', 'notes'] },
|
|
51
51
|
decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
52
|
+
// Veredito REUSAR/ESTENDER/CRIAR por conceito que a feature introduz, com a
|
|
53
|
+
// justificativa de cada CRIAR. É o ARTEFATO do ciclo de reuso (a prosa está
|
|
54
|
+
// em morph-plan §2b, a cobrança em src/lib/validators/reuse/). NÃO protegido,
|
|
55
|
+
// como decisions.md: é registro vivo — o implementador que descobre um alvo
|
|
56
|
+
// melhor no meio da task precisa poder anotar, e congelá-lo transformaria a
|
|
57
|
+
// correção num bloqueio de gate.
|
|
58
|
+
reuseMap: { filename: 'reuse-map.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
52
59
|
// N files, one per system use case — the behaviour layer the plan distils
|
|
53
60
|
// BEFORE spec.md/tasks.json. Deliberately NOT protected: editing a use case
|
|
54
61
|
// after Gate 2 is the supported way to change behaviour and re-run implement;
|