@polymorphism-tech/morph-spec 4.8.19 → 4.9.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 +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +15 -56
- package/bin/task-manager.js +115 -14
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +21 -0
- package/framework/agents.json +698 -176
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +2 -2
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +40 -8
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
- package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +1 -5
- package/package.json +2 -6
- package/src/commands/agents/dispatch-agents.js +55 -4
- package/src/commands/project/doctor.js +16 -47
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/state/advance-phase.js +120 -30
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +15 -0
- package/src/core/state/state-manager.js +28 -54
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/phase-chain/phase-validator.js +330 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +240 -224
- package/src/utils/agents-installer.js +2 -2
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +258 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/templates/template-validator.js +0 -296
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
|
@@ -1,99 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task management commands
|
|
3
|
-
* Spawns bin/task-manager.js (ESM) as a subprocess.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { spawn } from 'child_process';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import { dirname, join } from 'path';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.js');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Execute task-manager.js with given arguments
|
|
16
|
-
*/
|
|
17
|
-
function executeTaskManager(args) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
// Note: shell: false (default) is intentional to preserve arguments with spaces
|
|
20
|
-
const child = spawn('node', [taskManagerPath, ...args], {
|
|
21
|
-
stdio: 'inherit'
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
child.on('close', (code) => {
|
|
25
|
-
if (code === 0) {
|
|
26
|
-
resolve();
|
|
27
|
-
} else {
|
|
28
|
-
reject(new Error(`Task manager exited with code ${code}`));
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
child.on('error', (error) => {
|
|
33
|
-
reject(error);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Complete tasks command
|
|
40
|
-
*/
|
|
41
|
-
export async function taskDoneCommand(featureName, taskIds, options) {
|
|
42
|
-
try {
|
|
43
|
-
const args = ['done', featureName, ...taskIds];
|
|
44
|
-
if (options.skipValidation) args.push('--skip-validation');
|
|
45
|
-
if (options.dryRun) args.push('--dry-run');
|
|
46
|
-
await executeTaskManager(args);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const args = ['start', featureName, taskId];
|
|
81
|
-
await executeTaskManager(args);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get next task command
|
|
90
|
-
*/
|
|
91
|
-
export async function taskNextCommand(featureName, options) {
|
|
92
|
-
try {
|
|
93
|
-
const args = ['next', featureName];
|
|
94
|
-
await executeTaskManager(args);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Task management commands
|
|
3
|
+
* Spawns bin/task-manager.js (ESM) as a subprocess.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { dirname, join } from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Execute task-manager.js with given arguments
|
|
16
|
+
*/
|
|
17
|
+
function executeTaskManager(args) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
// Note: shell: false (default) is intentional to preserve arguments with spaces
|
|
20
|
+
const child = spawn('node', [taskManagerPath, ...args], {
|
|
21
|
+
stdio: 'inherit'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
child.on('close', (code) => {
|
|
25
|
+
if (code === 0) {
|
|
26
|
+
resolve();
|
|
27
|
+
} else {
|
|
28
|
+
reject(new Error(`Task manager exited with code ${code}`));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
child.on('error', (error) => {
|
|
33
|
+
reject(error);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Complete tasks command
|
|
40
|
+
*/
|
|
41
|
+
export async function taskDoneCommand(featureName, taskIds, options) {
|
|
42
|
+
try {
|
|
43
|
+
const args = ['done', featureName, ...taskIds];
|
|
44
|
+
if (options.skipValidation) args.push('--skip-validation');
|
|
45
|
+
if (options.dryRun) args.push('--dry-run');
|
|
46
|
+
await executeTaskManager(args);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
/**
|
|
55
|
+
* Start task command
|
|
56
|
+
*/
|
|
57
|
+
export async function taskStartCommand(featureName, taskId, options) {
|
|
58
|
+
try {
|
|
59
|
+
const args = ['start', featureName, taskId];
|
|
60
|
+
await executeTaskManager(args);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get next task command
|
|
69
|
+
*/
|
|
70
|
+
export async function taskNextCommand(featureName, options) {
|
|
71
|
+
try {
|
|
72
|
+
const args = ['next', featureName];
|
|
73
|
+
await executeTaskManager(args);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,173 +1,93 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Render Command - Render a template by ID with variables
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { writeFileSync, mkdirSync } from 'fs';
|
|
6
|
-
import { dirname, join } from 'path';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { logger } from '../../utils/logger.js';
|
|
9
|
-
import { getTemplateById, resolveTemplatePathById } from '../../core/templates/template-registry.js';
|
|
10
|
-
import { renderTemplate } from '../../core/templates/template-renderer.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
export async function templateRenderCommand(templateId, outputPath, variablesJson, options) {
|
|
95
|
-
logger.header(`Rendering Template: ${templateId}`);
|
|
96
|
-
logger.blank();
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
// 1. Validate template exists
|
|
100
|
-
const template = getTemplateById(templateId);
|
|
101
|
-
|
|
102
|
-
if (!template) {
|
|
103
|
-
logger.error(`Template not found: ${templateId}`);
|
|
104
|
-
logger.dim('\n💡 Use `morph-spec template list` to see available templates');
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// 2. Resolve template path
|
|
109
|
-
const projectPath = options.projectPath || process.cwd();
|
|
110
|
-
const templatePath = resolveTemplatePathById(templateId, projectPath);
|
|
111
|
-
|
|
112
|
-
if (!templatePath) {
|
|
113
|
-
logger.error(`Template file not found for ${templateId}`);
|
|
114
|
-
logger.dim(' Searched in: .morph/framework/templates/ and framework/templates/');
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
logger.dim(`Template: ${templatePath}`);
|
|
119
|
-
|
|
120
|
-
// 3. Parse variables
|
|
121
|
-
let variables = {};
|
|
122
|
-
try {
|
|
123
|
-
variables = JSON.parse(variablesJson);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
logger.error('Invalid JSON in variables argument');
|
|
126
|
-
logger.dim(` ${error.message}`);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 4. Add default variables
|
|
131
|
-
const defaults = {
|
|
132
|
-
DATE: new Date().toISOString().split('T')[0],
|
|
133
|
-
YEAR: new Date().getFullYear().toString(),
|
|
134
|
-
TIMESTAMP: new Date().toISOString()
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const allVariables = { ...defaults, ...variables };
|
|
138
|
-
|
|
139
|
-
logger.dim(`Variables: ${Object.keys(allVariables).length} provided`);
|
|
140
|
-
logger.blank();
|
|
141
|
-
|
|
142
|
-
// 5. Render template
|
|
143
|
-
const renderedContent = renderTemplate(templatePath, allVariables);
|
|
144
|
-
|
|
145
|
-
// 6. Output
|
|
146
|
-
if (options.dryRun) {
|
|
147
|
-
logger.info(chalk.cyan('📋 DRY RUN - Preview (file will NOT be written)'));
|
|
148
|
-
logger.blank();
|
|
149
|
-
console.log(renderedContent);
|
|
150
|
-
logger.blank();
|
|
151
|
-
logger.dim(`Would be written to: ${outputPath}`);
|
|
152
|
-
} else {
|
|
153
|
-
// Create output directory if needed
|
|
154
|
-
const outputDir = dirname(outputPath);
|
|
155
|
-
mkdirSync(outputDir, { recursive: true });
|
|
156
|
-
|
|
157
|
-
// Write file
|
|
158
|
-
writeFileSync(outputPath, renderedContent, 'utf-8');
|
|
159
|
-
|
|
160
|
-
logger.success(`✅ Template rendered successfully`);
|
|
161
|
-
logger.dim(` Output: ${chalk.cyan(outputPath)}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
logger.blank();
|
|
165
|
-
|
|
166
|
-
} catch (error) {
|
|
167
|
-
logger.error(`Failed to render template: ${error.message}`);
|
|
168
|
-
if (options.verbose) {
|
|
169
|
-
console.error(error.stack);
|
|
170
|
-
}
|
|
171
|
-
process.exit(1);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Template Render Command - Render a template by ID with variables
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { logger } from '../../utils/logger.js';
|
|
9
|
+
import { getTemplateById, resolveTemplatePathById } from '../../core/templates/template-registry.js';
|
|
10
|
+
import { renderTemplate } from '../../core/templates/template-renderer.js';
|
|
11
|
+
/**
|
|
12
|
+
* Render template command
|
|
13
|
+
*/
|
|
14
|
+
export async function templateRenderCommand(templateId, outputPath, variablesJson, options) {
|
|
15
|
+
logger.header(`Rendering Template: ${templateId}`);
|
|
16
|
+
logger.blank();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 1. Validate template exists
|
|
20
|
+
const template = getTemplateById(templateId);
|
|
21
|
+
|
|
22
|
+
if (!template) {
|
|
23
|
+
logger.error(`Template not found: ${templateId}`);
|
|
24
|
+
logger.dim('\n💡 Available template IDs are listed in framework/templates/ — check subdirectories code/, frontend/, meta-prompts/');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. Resolve template path
|
|
29
|
+
const projectPath = options.projectPath || process.cwd();
|
|
30
|
+
const templatePath = resolveTemplatePathById(templateId, projectPath);
|
|
31
|
+
|
|
32
|
+
if (!templatePath) {
|
|
33
|
+
logger.error(`Template file not found for ${templateId}`);
|
|
34
|
+
logger.dim(' Searched in: .morph/framework/templates/ and framework/templates/');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
logger.dim(`Template: ${templatePath}`);
|
|
39
|
+
|
|
40
|
+
// 3. Parse variables
|
|
41
|
+
let variables = {};
|
|
42
|
+
try {
|
|
43
|
+
variables = JSON.parse(variablesJson);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.error('Invalid JSON in variables argument');
|
|
46
|
+
logger.dim(` ${error.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 4. Add default variables
|
|
51
|
+
const defaults = {
|
|
52
|
+
DATE: new Date().toISOString().split('T')[0],
|
|
53
|
+
YEAR: new Date().getFullYear().toString(),
|
|
54
|
+
TIMESTAMP: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const allVariables = { ...defaults, ...variables };
|
|
58
|
+
|
|
59
|
+
logger.dim(`Variables: ${Object.keys(allVariables).length} provided`);
|
|
60
|
+
logger.blank();
|
|
61
|
+
|
|
62
|
+
// 5. Render template
|
|
63
|
+
const renderedContent = renderTemplate(templatePath, allVariables);
|
|
64
|
+
|
|
65
|
+
// 6. Output
|
|
66
|
+
if (options.dryRun) {
|
|
67
|
+
logger.info(chalk.cyan('📋 DRY RUN - Preview (file will NOT be written)'));
|
|
68
|
+
logger.blank();
|
|
69
|
+
console.log(renderedContent);
|
|
70
|
+
logger.blank();
|
|
71
|
+
logger.dim(`Would be written to: ${outputPath}`);
|
|
72
|
+
} else {
|
|
73
|
+
// Create output directory if needed
|
|
74
|
+
const outputDir = dirname(outputPath);
|
|
75
|
+
mkdirSync(outputDir, { recursive: true });
|
|
76
|
+
|
|
77
|
+
// Write file
|
|
78
|
+
writeFileSync(outputPath, renderedContent, 'utf-8');
|
|
79
|
+
|
|
80
|
+
logger.success(`✅ Template rendered successfully`);
|
|
81
|
+
logger.dim(` Output: ${chalk.cyan(outputPath)}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logger.blank();
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.error(`Failed to render template: ${error.message}`);
|
|
88
|
+
if (options.verbose) {
|
|
89
|
+
console.error(error.stack);
|
|
90
|
+
}
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* morph-spec trust status [feature]
|
|
6
6
|
* morph-spec trust set <feature> <level> [reason]
|
|
7
|
+
*
|
|
8
|
+
* Levels:
|
|
9
|
+
* manual — nothing auto-approved (default)
|
|
10
|
+
* high — auto-approve low-risk gates (design, tasks)
|
|
11
|
+
* auto — auto-approve all gates
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
import chalk from 'chalk';
|
|
@@ -11,10 +16,15 @@ import {
|
|
|
11
16
|
getTrust,
|
|
12
17
|
setTrust,
|
|
13
18
|
getTrustHistory,
|
|
14
|
-
TRUST_THRESHOLDS,
|
|
15
19
|
AUTO_APPROVE_GATES
|
|
16
20
|
} from '../../lib/trust/trust-manager.js';
|
|
17
21
|
|
|
22
|
+
const LEVEL_DESCRIPTIONS = {
|
|
23
|
+
manual: 'manual approval required for all gates',
|
|
24
|
+
high: 'auto-approves low-risk gates (design, tasks)',
|
|
25
|
+
auto: 'auto-approves all gates'
|
|
26
|
+
};
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Trust status — current trust level for a feature or all features
|
|
20
30
|
*/
|
|
@@ -25,15 +35,13 @@ export async function trustStatusCommand(featureName, options) {
|
|
|
25
35
|
console.log(chalk.bold(`\n Trust Status — ${featureName}`));
|
|
26
36
|
console.log(' ' + '─'.repeat(50));
|
|
27
37
|
|
|
28
|
-
const levelColor = trust.level === '
|
|
38
|
+
const levelColor = trust.level === 'auto' ? chalk.green
|
|
29
39
|
: trust.level === 'high' ? chalk.cyan
|
|
30
|
-
:
|
|
31
|
-
: chalk.red;
|
|
40
|
+
: chalk.yellow;
|
|
32
41
|
|
|
33
42
|
console.log(` Level: ${levelColor(trust.level.toUpperCase())}`);
|
|
34
|
-
console.log(`
|
|
35
|
-
console.log(`
|
|
36
|
-
console.log(` Source: ${trust.source || 'calculated'}`);
|
|
43
|
+
console.log(` Description: ${LEVEL_DESCRIPTIONS[trust.level] || trust.level}`);
|
|
44
|
+
console.log(` Source: ${trust.source || 'default'}`);
|
|
37
45
|
|
|
38
46
|
if (trust.source === 'manual') {
|
|
39
47
|
console.log(chalk.yellow(` Override: ${trust.overrideReason || 'No reason provided'}`));
|
|
@@ -46,11 +54,12 @@ export async function trustStatusCommand(featureName, options) {
|
|
|
46
54
|
trust.autoApprove.forEach(g => console.log(chalk.green(` ✓ ${g}`)));
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
console.log(`\n
|
|
50
|
-
for (const [level,
|
|
57
|
+
console.log(`\n Available trust levels:`);
|
|
58
|
+
for (const [level, desc] of Object.entries(LEVEL_DESCRIPTIONS)) {
|
|
51
59
|
const gates = AUTO_APPROVE_GATES[level] || [];
|
|
52
60
|
const marker = trust.level === level ? chalk.bold(' ← current') : '';
|
|
53
|
-
|
|
61
|
+
const gateList = gates.length > 0 ? gates.join(', ') : 'none';
|
|
62
|
+
console.log(` ${level.padEnd(10)} auto-approves: ${gateList}${marker}`);
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
console.log();
|
|
@@ -63,21 +72,18 @@ export async function trustStatusCommand(featureName, options) {
|
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
console.log(chalk.bold('\n Trust Overview'));
|
|
66
|
-
console.log(' ' + '─'.repeat(
|
|
67
|
-
console.log(` ${'Feature'.padEnd(25)} ${'Level'.padEnd(10)}
|
|
68
|
-
console.log(' ' + '─'.repeat(
|
|
75
|
+
console.log(' ' + '─'.repeat(60));
|
|
76
|
+
console.log(` ${'Feature'.padEnd(25)} ${'Level'.padEnd(10)} Phase`);
|
|
77
|
+
console.log(' ' + '─'.repeat(60));
|
|
69
78
|
|
|
70
79
|
for (const f of history) {
|
|
71
|
-
const levelColor = f.level === '
|
|
80
|
+
const levelColor = f.level === 'auto' ? chalk.green
|
|
72
81
|
: f.level === 'high' ? chalk.cyan
|
|
73
|
-
:
|
|
74
|
-
: chalk.red;
|
|
82
|
+
: chalk.yellow;
|
|
75
83
|
|
|
76
84
|
console.log(
|
|
77
85
|
` ${f.feature.padEnd(25)} ` +
|
|
78
86
|
`${levelColor(f.level.padEnd(10))} ` +
|
|
79
|
-
`${String(Math.round(f.passRate * 100) + '%').padEnd(8)} ` +
|
|
80
|
-
`${String(f.checkpointsTotal).padEnd(8)} ` +
|
|
81
87
|
`${f.phase || 'N/A'}`
|
|
82
88
|
);
|
|
83
89
|
}
|
|
@@ -91,7 +97,7 @@ export async function trustStatusCommand(featureName, options) {
|
|
|
91
97
|
export async function trustSetCommand(featureName, level, reason, options) {
|
|
92
98
|
if (!featureName || !level) {
|
|
93
99
|
console.error(chalk.red('Usage: morph-spec trust set <feature> <level> [reason]'));
|
|
94
|
-
console.error(chalk.gray('Levels:
|
|
100
|
+
console.error(chalk.gray('Levels: manual, high, auto'));
|
|
95
101
|
process.exit(1);
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -104,10 +110,9 @@ export async function trustSetCommand(featureName, level, reason, options) {
|
|
|
104
110
|
console.log(` Level: ${result.level}`);
|
|
105
111
|
console.log(` Auto-approves: ${result.autoApprove.join(', ') || 'none'}`);
|
|
106
112
|
console.log(` Reason: ${result.reason}`);
|
|
107
|
-
console.log(
|
|
113
|
+
console.log();
|
|
108
114
|
} catch (err) {
|
|
109
115
|
console.error(chalk.red(`Error: ${err.message}`));
|
|
110
116
|
process.exit(1);
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
|
-
|
|
@@ -28,6 +28,7 @@ export const OUTPUT_SCHEMA = {
|
|
|
28
28
|
spec: { filename: 'spec.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
29
29
|
clarifications:{ filename: 'clarifications.md', phaseDir: '2-clarify', phase: 'clarify', protected: false },
|
|
30
30
|
contracts: { filename: 'contracts.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
31
|
+
contractsTs: { filename: 'contracts.ts', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
31
32
|
contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
32
33
|
tasks: { filename: 'tasks.md', phaseDir: '3-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
|
|
33
34
|
uiDesignSystem:{ filename: 'design-system.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
@@ -134,3 +135,17 @@ export const PROTECTED_SPEC_FILES = Object.fromEntries(
|
|
|
134
135
|
.filter(([, entry]) => entry.protected && entry.approvalGate)
|
|
135
136
|
.map(([, entry]) => [entry.filename, entry.approvalGate])
|
|
136
137
|
);
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Stack-aware Helpers
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve the correct contracts output type key for a given stack.
|
|
145
|
+
* @param {string|null} stack
|
|
146
|
+
* @returns {'contracts'|'contractsTs'}
|
|
147
|
+
*/
|
|
148
|
+
export function resolveContractsOutputType(stack) {
|
|
149
|
+
const NEXTJS_STACKS = new Set(['nextjs', 'next', 'next.js']);
|
|
150
|
+
return NEXTJS_STACKS.has(stack?.toLowerCase?.()) ? 'contractsTs' : 'contracts';
|
|
151
|
+
}
|