@polymorphism-tech/morph-spec 10.1.1 → 10.5.2
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/README.md +3 -7
- package/bin/morph-spec.js +10 -41
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +1 -2
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-preflight.md +3 -4
- package/framework/docs/CLI.md +45 -22
- package/framework/evals/review-checklist.md +5 -5
- package/framework/hooks/README.md +2 -2
- package/framework/hooks/claude-code/core/heavy-gate.js +21 -2
- package/framework/hooks/claude-code/core/validate-completion.js +0 -35
- package/framework/hooks/shared/e2e-check.js +44 -7
- package/framework/hooks/shared/heavy-command.js +12 -1
- package/framework/hooks/shared/heavy-config.js +12 -0
- package/framework/hooks/shared/host-services.js +9 -6
- package/framework/hooks/shared/next-step.js +12 -8
- package/framework/phases.json +4 -4
- package/framework/rules/plan-grammar.md +4 -1
- package/framework/schemas/config.schema.json +59 -1
- package/framework/schemas/feature.schema.json +15 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +8 -5
- package/framework/skills/core/morph-apply/references/hotfix.md +1 -1
- package/framework/skills/core/morph-apply/references/machine.md +3 -2
- package/framework/skills/core/morph-apply/references/review.md +9 -7
- package/framework/skills/core/morph-plan/SKILL.md +7 -6
- package/framework/skills/core/morph-plan/references/critique.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +2 -2
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- 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/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +2 -1
- package/framework/templates/examples/design-system-examples.md +0 -3
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- 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/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/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/project/doctor.js +0 -1
- package/src/commands/project/heavy.js +13 -0
- package/src/commands/state/approve.js +9 -109
- package/src/commands/state/report-check.js +44 -2
- package/src/commands/validation/verify.js +57 -7
- package/src/core/state/state-manager.js +0 -24
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/heavy/cleanup.js +8 -2
- package/src/lib/heavy/gate.js +24 -1
- package/src/lib/heavy/reclaim.js +107 -0
- package/src/lib/heavy/watchdog.js +46 -0
- package/src/lib/next-step.js +1 -0
- package/src/lib/plan/checks.js +1 -1
- package/src/lib/plan/critique.js +23 -3
- package/src/lib/procs/prune.js +23 -1
- package/src/lib/review-confirmation.js +67 -0
- package/src/lib/review-reports.js +1 -0
- package/src/lib/risk-detector.js +2 -2
- package/src/lib/tasks/test-nodes.js +10 -0
- package/src/lib/tasks/test-runner.js +37 -43
- package/src/lib/telemetry/telemetry-reader.js +1 -2
- package/src/lib/telemetry/verify-event.js +7 -0
- package/src/lib/validators/verify-cache.js +63 -0
- package/src/lib/validators/verify-runner.js +131 -26
- package/src/lib/verify/affected.js +141 -0
- package/src/lib/verify/detached.js +154 -0
- package/src/utils/agents-installer.js +2 -2
- package/src/utils/hooks-installer.js +0 -20
- package/bin/validate.js +0 -212
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
- package/src/commands/observability/telemetry.js +0 -60
- package/src/commands/validation/validate-feature.js +0 -337
package/bin/validate.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { getStackProfile } from '../src/lib/stack/stack-profile.js';
|
|
5
|
-
|
|
6
|
-
async function buildValidators(stack) {
|
|
7
|
-
const validators = {};
|
|
8
|
-
|
|
9
|
-
const isDotnet = !stack || ['dotnet', 'blazor', 'aspnet', 'dotnet-blazor'].includes(stack.toLowerCase());
|
|
10
|
-
const isNextjs = stack && ['nextjs', 'next', 'next.js'].includes(stack.toLowerCase());
|
|
11
|
-
|
|
12
|
-
if (isDotnet) {
|
|
13
|
-
const { validatePackages } = await import('../src/lib/validators/packages/package-validator.js');
|
|
14
|
-
|
|
15
|
-
validators.packages = {
|
|
16
|
-
name: 'Package Compatibility',
|
|
17
|
-
description: 'Validates NuGet package versions against .NET compatibility matrix',
|
|
18
|
-
run: validatePackages
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (isNextjs) {
|
|
23
|
-
const { validateNextComponentFiles } = await import('../src/lib/validators/nextjs/next-component-validator.js');
|
|
24
|
-
validators['next-components'] = {
|
|
25
|
-
name: 'Next.js Components',
|
|
26
|
-
description: "Validates 'use client' directives, hook usage, and file naming conventions",
|
|
27
|
-
run: (projectPath, opts) => validateNextComponentFiles(projectPath, opts)
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return validators;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function validateCommand(args = []) {
|
|
35
|
-
const validatorName = args[0] || 'all';
|
|
36
|
-
const options = parseOptions(args);
|
|
37
|
-
|
|
38
|
-
const { stack } = getStackProfile();
|
|
39
|
-
const VALIDATORS = await buildValidators(stack);
|
|
40
|
-
|
|
41
|
-
console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Validator\n'));
|
|
42
|
-
if (stack) {
|
|
43
|
-
console.log(chalk.gray(`Stack: ${stack}\n`));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (validatorName === 'all') {
|
|
47
|
-
await runAllValidators(VALIDATORS, options);
|
|
48
|
-
} else if (VALIDATORS[validatorName]) {
|
|
49
|
-
await runValidator(VALIDATORS, validatorName, options);
|
|
50
|
-
} else {
|
|
51
|
-
console.error(chalk.red(`❌ Unknown validator: ${validatorName}`));
|
|
52
|
-
console.log(chalk.gray('\nAvailable validators:'));
|
|
53
|
-
for (const [name, validator] of Object.entries(VALIDATORS)) {
|
|
54
|
-
console.log(chalk.gray(` - ${name}: ${validator.description}`));
|
|
55
|
-
}
|
|
56
|
-
console.log(chalk.gray(' - all: Run all validators'));
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function runAllValidators(VALIDATORS, options) {
|
|
62
|
-
const results = {};
|
|
63
|
-
let totalErrors = 0;
|
|
64
|
-
let totalWarnings = 0;
|
|
65
|
-
|
|
66
|
-
console.log(chalk.gray('Running all validators...\n'));
|
|
67
|
-
|
|
68
|
-
for (const [name, validator] of Object.entries(VALIDATORS)) {
|
|
69
|
-
console.log(chalk.cyan(`\n▸ ${validator.name}`));
|
|
70
|
-
console.log(chalk.gray(` ${validator.description}\n`));
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const result = await validator.run('.', {
|
|
74
|
-
verbose: options.verbose,
|
|
75
|
-
autoFix: options.autoFix
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
results[name] = result;
|
|
79
|
-
|
|
80
|
-
if (result.errors !== undefined) {
|
|
81
|
-
totalErrors += result.errors;
|
|
82
|
-
totalWarnings += result.warnings || 0;
|
|
83
|
-
} else if (result.results) {
|
|
84
|
-
const issues = result.results.flatMap(r => r.issues || []);
|
|
85
|
-
totalErrors += issues.filter(i => i.level === 'error').length;
|
|
86
|
-
totalWarnings += issues.filter(i => i.level === 'warning').length;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error(chalk.red(` ❌ Failed: ${error.message}`));
|
|
91
|
-
results[name] = { status: 'error', error: error.message };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
96
|
-
console.log(chalk.bold('📊 Validation Summary\n'));
|
|
97
|
-
|
|
98
|
-
if (totalErrors === 0 && totalWarnings === 0) {
|
|
99
|
-
console.log(chalk.green('✅ All validations passed!'));
|
|
100
|
-
} else {
|
|
101
|
-
if (totalErrors > 0) {
|
|
102
|
-
console.log(chalk.red(`❌ ${totalErrors} error(s) found`));
|
|
103
|
-
}
|
|
104
|
-
if (totalWarnings > 0) {
|
|
105
|
-
console.log(chalk.yellow(`⚠️ ${totalWarnings} warning(s) found`));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
console.log('');
|
|
110
|
-
|
|
111
|
-
if (totalErrors > 0 && !options.noFail) {
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function runValidator(VALIDATORS, name, options) {
|
|
117
|
-
const validator = VALIDATORS[name];
|
|
118
|
-
|
|
119
|
-
console.log(chalk.cyan(`▸ ${validator.name}`));
|
|
120
|
-
console.log(chalk.gray(` ${validator.description}\n`));
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const result = await validator.run('.', {
|
|
124
|
-
verbose: true,
|
|
125
|
-
autoFix: options.autoFix
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (result.status === 'ok') {
|
|
129
|
-
console.log(chalk.green('\n✅ Validation passed!\n'));
|
|
130
|
-
} else {
|
|
131
|
-
console.log(chalk.yellow('\n⚠️ Validation completed with issues\n'));
|
|
132
|
-
|
|
133
|
-
if (result.errors > 0 && !options.noFail) {
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error(chalk.red(`\n❌ Validation failed: ${error.message}\n`));
|
|
140
|
-
if (!options.noFail) {
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function parseOptions(args) {
|
|
147
|
-
const options = {
|
|
148
|
-
verbose: false,
|
|
149
|
-
autoFix: false,
|
|
150
|
-
noFail: false
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
for (const arg of args) {
|
|
154
|
-
if (arg === '--verbose' || arg === '-v') {
|
|
155
|
-
options.verbose = true;
|
|
156
|
-
} else if (arg === '--auto-fix' || arg === '--fix') {
|
|
157
|
-
options.autoFix = true;
|
|
158
|
-
} else if (arg === '--no-fail') {
|
|
159
|
-
options.noFail = true;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return options;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function showValidateHelp() {
|
|
167
|
-
console.log(`
|
|
168
|
-
${chalk.bold.cyan('morph-spec validate')} - Run project validations
|
|
169
|
-
|
|
170
|
-
${chalk.bold('USAGE')}
|
|
171
|
-
morph-spec validate [validator] [options]
|
|
172
|
-
|
|
173
|
-
${chalk.bold('VALIDATORS')}
|
|
174
|
-
all Run all validators (default)
|
|
175
|
-
packages Validate NuGet package compatibility (.NET 10)
|
|
176
|
-
|
|
177
|
-
${chalk.bold('OPTIONS')}
|
|
178
|
-
--verbose, -v Show detailed output
|
|
179
|
-
--auto-fix, --fix Auto-fix issues where possible
|
|
180
|
-
--no-fail Don't exit with error code
|
|
181
|
-
|
|
182
|
-
${chalk.bold('EXAMPLES')}
|
|
183
|
-
morph-spec validate # Run all validators
|
|
184
|
-
morph-spec validate packages --fix # Validate packages and auto-fix
|
|
185
|
-
morph-spec validate all # Run all validators
|
|
186
|
-
|
|
187
|
-
${chalk.bold('EXIT CODES')}
|
|
188
|
-
0 All validations passed
|
|
189
|
-
1 Validation errors found (unless --no-fail)
|
|
190
|
-
|
|
191
|
-
${chalk.bold('INTEGRATION')}
|
|
192
|
-
Add to pre-commit hook:
|
|
193
|
-
morph-spec validate --no-fail
|
|
194
|
-
|
|
195
|
-
CI/CD pipeline:
|
|
196
|
-
morph-spec validate || exit 1
|
|
197
|
-
`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
201
|
-
const args = process.argv.slice(2);
|
|
202
|
-
|
|
203
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
204
|
-
showValidateHelp();
|
|
205
|
-
process.exit(0);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
validateCommand(args).catch(error => {
|
|
209
|
-
console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
210
|
-
process.exit(1);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
Binary file
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
<templateSet group="MORPH Templates">
|
|
2
|
-
<template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
|
|
3
|
-
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
4
|
-
<context>
|
|
5
|
-
<option name="OTHER" value="true" />
|
|
6
|
-
</context>
|
|
7
|
-
</template>
|
|
8
|
-
|
|
9
|
-
<template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
|
|
10
|
-
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
11
|
-
<context>
|
|
12
|
-
<option name="OTHER" value="true" />
|
|
13
|
-
</context>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
|
|
17
|
-
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
18
|
-
<context>
|
|
19
|
-
<option name="OTHER" value="true" />
|
|
20
|
-
</context>
|
|
21
|
-
</template>
|
|
22
|
-
|
|
23
|
-
<template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
|
|
24
|
-
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
25
|
-
<context>
|
|
26
|
-
<option name="OTHER" value="true" />
|
|
27
|
-
</context>
|
|
28
|
-
</template>
|
|
29
|
-
|
|
30
|
-
<template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
|
|
31
|
-
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
32
|
-
<context>
|
|
33
|
-
<option name="OTHER" value="true" />
|
|
34
|
-
</context>
|
|
35
|
-
</template>
|
|
36
|
-
|
|
37
|
-
<template name="morph-if" value="{{#if $CONDITION$}} $END$ {{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
|
|
38
|
-
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
39
|
-
<context>
|
|
40
|
-
<option name="OTHER" value="true" />
|
|
41
|
-
</context>
|
|
42
|
-
</template>
|
|
43
|
-
|
|
44
|
-
<template name="morph-if-else" value="{{#if $CONDITION$}} $TRUE_BRANCH$ {{else}} $FALSE_BRANCH$ {{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
|
|
45
|
-
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
46
|
-
<variable name="TRUE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
47
|
-
<variable name="FALSE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
48
|
-
<context>
|
|
49
|
-
<option name="OTHER" value="true" />
|
|
50
|
-
</context>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<template name="morph-each" value="{{#each $ITEMS$}} {{this.$PROPERTY$}} {{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
|
|
54
|
-
<variable name="ITEMS" expression="" defaultValue=""items"" alwaysStopAt="true" />
|
|
55
|
-
<variable name="PROPERTY" expression="" defaultValue=""name"" alwaysStopAt="true" />
|
|
56
|
-
<context>
|
|
57
|
-
<option name="OTHER" value="true" />
|
|
58
|
-
</context>
|
|
59
|
-
</template>
|
|
60
|
-
|
|
61
|
-
<template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services; public class {{pascalCase FEATURE_NAME}}Service( I{{pascalCase FEATURE_NAME}}Repository repository, ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service { public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
|
|
62
|
-
<context>
|
|
63
|
-
<option name="C_SHARP" value="true" />
|
|
64
|
-
</context>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository { private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>(); public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
|
|
68
|
-
<context>
|
|
69
|
-
<option name="C_SHARP" value="true" />
|
|
70
|
-
</context>
|
|
71
|
-
</template>
|
|
72
|
-
|
|
73
|
-
<template name="morph-component" value="@page "/{{kebabCase FEATURE_NAME}}" @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service @inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger <PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle> <div class="container-fluid"> $END$ </div> @code { }" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
|
|
74
|
-
<context>
|
|
75
|
-
<option name="HTML" value="true" />
|
|
76
|
-
</context>
|
|
77
|
-
</template>
|
|
78
|
-
|
|
79
|
-
<template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "{{pascalCase FEATURE_NAME}}s", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), $END$ }, constraints: table => { table.PrimaryKey("PK_{{pascalCase FEATURE_NAME}}s", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable(name: "{{pascalCase FEATURE_NAME}}s"); } }" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
|
|
80
|
-
<context>
|
|
81
|
-
<option name="C_SHARP" value="true" />
|
|
82
|
-
</context>
|
|
83
|
-
</template>
|
|
84
|
-
|
|
85
|
-
<template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} { id: number; $FIELD1$ } export interface Create{{pascalCase FEATURE_NAME}}Request { $FIELD2$ } export interface Update{{pascalCase FEATURE_NAME}}Request { $END$ }" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
|
|
86
|
-
<variable name="FIELD1" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
87
|
-
<variable name="FIELD2" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
88
|
-
<context>
|
|
89
|
-
<option name="TypeScript" value="true" />
|
|
90
|
-
</context>
|
|
91
|
-
</template>
|
|
92
|
-
</templateSet>
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"MORPH Template - PascalCase": {
|
|
3
|
-
"prefix": "morph-pascal",
|
|
4
|
-
"body": ["{{pascalCase ${1:FEATURE_NAME}}}"],
|
|
5
|
-
"description": "Convert to PascalCase (UserAuth)"
|
|
6
|
-
},
|
|
7
|
-
"MORPH Template - camelCase": {
|
|
8
|
-
"prefix": "morph-camel",
|
|
9
|
-
"body": ["{{camelCase ${1:FEATURE_NAME}}}"],
|
|
10
|
-
"description": "Convert to camelCase (userAuth)"
|
|
11
|
-
},
|
|
12
|
-
"MORPH Template - snake_case": {
|
|
13
|
-
"prefix": "morph-snake",
|
|
14
|
-
"body": ["{{snakeCase ${1:FEATURE_NAME}}}"],
|
|
15
|
-
"description": "Convert to snake_case (user_auth)"
|
|
16
|
-
},
|
|
17
|
-
"MORPH Template - Title Case": {
|
|
18
|
-
"prefix": "morph-title",
|
|
19
|
-
"body": ["{{titleCase ${1:FEATURE_NAME}}}"],
|
|
20
|
-
"description": "Convert to Title Case (User Auth)"
|
|
21
|
-
},
|
|
22
|
-
"MORPH Template - kebab-case": {
|
|
23
|
-
"prefix": "morph-kebab",
|
|
24
|
-
"body": ["{{kebabCase ${1:FEATURE_NAME}}}"],
|
|
25
|
-
"description": "Convert to kebab-case (user-auth)"
|
|
26
|
-
},
|
|
27
|
-
"MORPH Template - UPPER_SNAKE_CASE": {
|
|
28
|
-
"prefix": "morph-upper-snake",
|
|
29
|
-
"body": ["{{upperSnakeCase ${1:FEATURE_NAME}}}"],
|
|
30
|
-
"description": "Convert to UPPER_SNAKE_CASE (USER_AUTH)"
|
|
31
|
-
},
|
|
32
|
-
"MORPH Template - Conditional If": {
|
|
33
|
-
"prefix": "morph-if",
|
|
34
|
-
"body": [
|
|
35
|
-
"{{#if ${1:condition}}}",
|
|
36
|
-
" $2",
|
|
37
|
-
"{{/if}}"
|
|
38
|
-
],
|
|
39
|
-
"description": "Handlebars if block"
|
|
40
|
-
},
|
|
41
|
-
"MORPH Template - Conditional If-Else": {
|
|
42
|
-
"prefix": "morph-if-else",
|
|
43
|
-
"body": [
|
|
44
|
-
"{{#if ${1:condition}}}",
|
|
45
|
-
" $2",
|
|
46
|
-
"{{else}}",
|
|
47
|
-
" $3",
|
|
48
|
-
"{{/if}}"
|
|
49
|
-
],
|
|
50
|
-
"description": "Handlebars if-else block"
|
|
51
|
-
},
|
|
52
|
-
"MORPH Template - Each Loop": {
|
|
53
|
-
"prefix": "morph-each",
|
|
54
|
-
"body": [
|
|
55
|
-
"{{#each ${1:items}}}",
|
|
56
|
-
" {{this.$2}}",
|
|
57
|
-
"{{/each}}"
|
|
58
|
-
],
|
|
59
|
-
"description": "Handlebars each loop"
|
|
60
|
-
},
|
|
61
|
-
"MORPH Template - Unless": {
|
|
62
|
-
"prefix": "morph-unless",
|
|
63
|
-
"body": [
|
|
64
|
-
"{{#unless ${1:condition}}}",
|
|
65
|
-
" $2",
|
|
66
|
-
"{{/unless}}"
|
|
67
|
-
],
|
|
68
|
-
"description": "Handlebars unless block"
|
|
69
|
-
},
|
|
70
|
-
"MORPH Template - .NET Service Class": {
|
|
71
|
-
"prefix": "morph-service",
|
|
72
|
-
"body": [
|
|
73
|
-
"namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services;",
|
|
74
|
-
"",
|
|
75
|
-
"public class {{pascalCase FEATURE_NAME}}Service(",
|
|
76
|
-
" I{{pascalCase FEATURE_NAME}}Repository repository,",
|
|
77
|
-
" ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service",
|
|
78
|
-
"{",
|
|
79
|
-
" public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
|
|
80
|
-
" {",
|
|
81
|
-
" $1",
|
|
82
|
-
" }",
|
|
83
|
-
"}"
|
|
84
|
-
],
|
|
85
|
-
"description": "MORPH .NET Service class template"
|
|
86
|
-
},
|
|
87
|
-
"MORPH Template - .NET Repository Class": {
|
|
88
|
-
"prefix": "morph-repository",
|
|
89
|
-
"body": [
|
|
90
|
-
"public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository",
|
|
91
|
-
"{",
|
|
92
|
-
" private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>();",
|
|
93
|
-
"",
|
|
94
|
-
" public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default)",
|
|
95
|
-
" {",
|
|
96
|
-
" $1",
|
|
97
|
-
" }",
|
|
98
|
-
"}"
|
|
99
|
-
],
|
|
100
|
-
"description": "MORPH .NET Repository class template"
|
|
101
|
-
},
|
|
102
|
-
"MORPH Template - Blazor Component": {
|
|
103
|
-
"prefix": "morph-component",
|
|
104
|
-
"body": [
|
|
105
|
-
"@page \"/{{kebabCase FEATURE_NAME}}\"",
|
|
106
|
-
"@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service",
|
|
107
|
-
"@inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger",
|
|
108
|
-
"",
|
|
109
|
-
"<PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle>",
|
|
110
|
-
"",
|
|
111
|
-
"<div class=\"container-fluid\">",
|
|
112
|
-
" $1",
|
|
113
|
-
"</div>",
|
|
114
|
-
"",
|
|
115
|
-
"@code {",
|
|
116
|
-
" $2",
|
|
117
|
-
"}"
|
|
118
|
-
],
|
|
119
|
-
"description": "MORPH Blazor component template"
|
|
120
|
-
},
|
|
121
|
-
"MORPH Template - EF Core Migration": {
|
|
122
|
-
"prefix": "morph-migration",
|
|
123
|
-
"body": [
|
|
124
|
-
"public partial class Add{{pascalCase FEATURE_NAME}} : Migration",
|
|
125
|
-
"{",
|
|
126
|
-
" protected override void Up(MigrationBuilder migrationBuilder)",
|
|
127
|
-
" {",
|
|
128
|
-
" migrationBuilder.CreateTable(",
|
|
129
|
-
" name: \"{{pascalCase FEATURE_NAME}}s\",",
|
|
130
|
-
" columns: table => new",
|
|
131
|
-
" {",
|
|
132
|
-
" Id = table.Column<int>(type: \"int\", nullable: false)",
|
|
133
|
-
" .Annotation(\"SqlServer:Identity\", \"1, 1\"),",
|
|
134
|
-
" $1",
|
|
135
|
-
" },",
|
|
136
|
-
" constraints: table =>",
|
|
137
|
-
" {",
|
|
138
|
-
" table.PrimaryKey(\"PK_{{pascalCase FEATURE_NAME}}s\", x => x.Id);",
|
|
139
|
-
" });",
|
|
140
|
-
" }",
|
|
141
|
-
"",
|
|
142
|
-
" protected override void Down(MigrationBuilder migrationBuilder)",
|
|
143
|
-
" {",
|
|
144
|
-
" migrationBuilder.DropTable(name: \"{{pascalCase FEATURE_NAME}}s\");",
|
|
145
|
-
" }",
|
|
146
|
-
"}"
|
|
147
|
-
],
|
|
148
|
-
"description": "MORPH EF Core migration template"
|
|
149
|
-
},
|
|
150
|
-
"MORPH Template - Hangfire Job": {
|
|
151
|
-
"prefix": "morph-job",
|
|
152
|
-
"body": [
|
|
153
|
-
"public class {{pascalCase FEATURE_NAME}}ProcessorJob(",
|
|
154
|
-
" I{{pascalCase FEATURE_NAME}}Service service,",
|
|
155
|
-
" ILogger<{{pascalCase FEATURE_NAME}}ProcessorJob> logger) : I{{pascalCase FEATURE_NAME}}ProcessorJob",
|
|
156
|
-
"{",
|
|
157
|
-
" [AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]",
|
|
158
|
-
" [Queue(\"default\")]",
|
|
159
|
-
" [JobDisplayName(\"{{pascalCase FEATURE_NAME}} Processing - ID: {0}\")]",
|
|
160
|
-
" public async Task ExecuteAsync(int id, CancellationToken cancellationToken)",
|
|
161
|
-
" {",
|
|
162
|
-
" $1",
|
|
163
|
-
" }",
|
|
164
|
-
"}"
|
|
165
|
-
],
|
|
166
|
-
"description": "MORPH Hangfire job template"
|
|
167
|
-
},
|
|
168
|
-
"MORPH Template - TypeScript Contract": {
|
|
169
|
-
"prefix": "morph-ts-contract",
|
|
170
|
-
"body": [
|
|
171
|
-
"export interface {{pascalCase FEATURE_NAME}} {",
|
|
172
|
-
" id: number;",
|
|
173
|
-
" $1",
|
|
174
|
-
"}",
|
|
175
|
-
"",
|
|
176
|
-
"export interface Create{{pascalCase FEATURE_NAME}}Request {",
|
|
177
|
-
" $2",
|
|
178
|
-
"}",
|
|
179
|
-
"",
|
|
180
|
-
"export interface Update{{pascalCase FEATURE_NAME}}Request {",
|
|
181
|
-
" $3",
|
|
182
|
-
"}"
|
|
183
|
-
],
|
|
184
|
-
"description": "MORPH TypeScript contract template"
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
|
|
3
|
-
|
|
4
|
-
// A rule injects its standards' digests through Claude Code's rules system,
|
|
5
|
-
// not the Read tool, so no hook sees them: a low count means "few full-text reads".
|
|
6
|
-
const STANDARDS_CAVEAT =
|
|
7
|
-
'Só reads explícitos (via Read) são observáveis; o resumo que uma rule injeta ao tocar arquivo casado por `paths:` não passa por tool call: não observável aqui.';
|
|
8
|
-
|
|
9
|
-
function section(title, rows, fmt) {
|
|
10
|
-
if (!rows.length) return;
|
|
11
|
-
console.log('\n' + chalk.white.bold(title));
|
|
12
|
-
for (const r of rows) console.log(' ' + fmt(r));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Read-only and always exits 0: an empty stream is an answer.
|
|
16
|
-
export function telemetryCommand(options = {}) {
|
|
17
|
-
const events = loadTelemetry(options.cwd || process.cwd());
|
|
18
|
-
const summary = aggregateTelemetry(events, { feature: options.feature });
|
|
19
|
-
|
|
20
|
-
if (options.json) {
|
|
21
|
-
console.log(JSON.stringify({ ...summary, notes: { standards: STANDARDS_CAVEAT } }));
|
|
22
|
-
process.exit(0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (summary.total === 0) {
|
|
26
|
-
const scope = options.feature ? ` para a feature "${options.feature}"` : '';
|
|
27
|
-
console.log(chalk.gray(`Telemetria vazia${scope} — nenhum evento capturado ainda em .morph/logs/events.jsonl.`));
|
|
28
|
-
process.exit(0);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const scope = options.feature ? chalk.cyan(` · feature ${options.feature}`) : '';
|
|
32
|
-
console.log(chalk.white.bold(`Telemetria do harness — ${summary.total} eventos${scope}`));
|
|
33
|
-
if (summary.features.length) {
|
|
34
|
-
console.log(chalk.gray(`Features: ${summary.features.join(', ')}`));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
section('Skills', summary.skills, s => `${chalk.green(s.name.padEnd(24))} ${s.count}`);
|
|
38
|
-
section('Sub-agents despachados', summary.personas, p => `${chalk.magenta(p.name.padEnd(24))} ${p.count}`);
|
|
39
|
-
section('Base de conhecimento (standards lidos explicitamente)', summary.standards,
|
|
40
|
-
s => `${chalk.yellow(String(s.count).padStart(3))}× ${s.path}`);
|
|
41
|
-
if (summary.standards.length || summary.tools.some(t => t.name === 'Read')) {
|
|
42
|
-
console.log(chalk.gray(` ⚠ ${STANDARDS_CAVEAT}`));
|
|
43
|
-
}
|
|
44
|
-
section('Mix de tools', summary.tools, t => `${t.name.padEnd(16)} ${t.count}`);
|
|
45
|
-
|
|
46
|
-
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 10;
|
|
47
|
-
const prompts = summary.prompts.slice(-limit);
|
|
48
|
-
if (prompts.length) {
|
|
49
|
-
console.log('\n' + chalk.white.bold(`Prompts (últimos ${prompts.length})`));
|
|
50
|
-
for (const p of prompts) {
|
|
51
|
-
const tag = p.source === 'user' ? chalk.blue('user ') : chalk.magenta('agent');
|
|
52
|
-
const where = p.feature ? chalk.gray(` [${p.feature}/${p.phase || '-'}]`) : '';
|
|
53
|
-
const oneLine = String(p.text || '').replace(/\s+/g, ' ').trim();
|
|
54
|
-
const preview = oneLine.length > 120 ? oneLine.slice(0, 117) + '...' : oneLine;
|
|
55
|
-
console.log(` ${tag}${where} ${preview}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|