@polymorphism-tech/morph-spec 9.3.0 → 9.5.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 +1 -1
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +23 -33
- package/framework/agents.json +10 -9
- package/framework/docs/CLI.md +3 -3
- package/framework/evals/review-checklist.md +1 -1
- package/framework/hooks/README.md +3 -2
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -15
- package/framework/hooks/claude-code/core/comment-guard.js +1 -0
- package/framework/hooks/claude-code/core/conventional-commit.js +57 -0
- package/framework/hooks/claude-code/core/denylist.json +63 -1
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +0 -8
- package/framework/hooks/claude-code/core/gate-guard.js +0 -8
- package/framework/hooks/claude-code/core/loop-detect.js +0 -3
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +2 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +0 -7
- package/framework/hooks/claude-code/core/validate-completion.js +0 -4
- package/framework/hooks/claude-code/core/worktree-context.js +0 -2
- package/framework/hooks/claude-code/statusline.py +3 -44
- package/framework/hooks/shared/build-check.js +12 -19
- package/framework/hooks/shared/command-tokens.js +17 -0
- package/framework/hooks/shared/comment-rules.js +11 -1
- package/framework/hooks/shared/hook-response.js +0 -4
- package/framework/hooks/shared/payload-utils.js +1 -35
- package/framework/hooks/shared/phase-utils.js +0 -12
- package/framework/hooks/shared/slice-progress.js +0 -2
- package/framework/hooks/shared/state-reader.js +0 -19
- package/framework/hooks/shared/telemetry-logger.js +1 -1
- package/framework/rules/csharp-standards.md +5 -3
- package/framework/rules/dotnet-templates.md +2 -2
- package/framework/rules/frontend-standards.md +1 -2
- package/framework/rules/nextjs-standards.md +10 -8
- 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 +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +3 -3
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +87 -37
- package/framework/standards/STANDARDS.meta.json +41 -15
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/engineering-lens.md +100 -0
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +7 -9
- package/framework/standards/backend/dotnet/analyzers.md +59 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -12
- package/framework/standards/frontend/admin-app/data-table.md +1 -3
- package/framework/standards/frontend/admin-app/page-structure.md +8 -4
- package/framework/standards/frontend/nextjs/app-router.md +3 -4
- package/framework/standards/frontend/nextjs/components.md +2 -15
- package/framework/standards/frontend/nextjs/data-fetching.md +2 -3
- package/framework/standards/frontend/nextjs/lint.md +141 -0
- package/framework/standards/frontend/nextjs/motion-patterns.md +2 -7
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -2
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +5 -7
- package/framework/standards/frontend/nextjs/project-structure.md +3 -2
- package/framework/standards/frontend/nextjs/state-management.md +6 -7
- package/framework/templates/README.md +4 -5
- package/framework/templates/REGISTRY.json +21 -516
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +54 -56
- package/package.json +3 -2
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/archive.js +1 -1
- package/src/commands/state/finish.js +1 -1
- package/src/core/paths/output-schema.js +0 -9
- package/src/core/state/feature-identity.js +0 -2
- package/src/core/state/state-manager.js +1 -169
- package/src/core/templates/template-registry.js +1 -43
- package/src/core/templates/template-renderer.js +0 -23
- package/src/dashboard/app/lib/format.js +0 -7
- package/src/dashboard/app/lib/phases.js +0 -7
- package/src/dashboard/scanner/activity-scanner.js +0 -4
- package/src/lib/ai/ai-project-scan.js +1 -6
- package/src/lib/graph/graphify-runtime.js +0 -1
- package/src/lib/mutation/snapshot.js +1 -5
- package/src/lib/risk-detector.js +0 -5
- package/src/lib/stack/stack-profile.js +0 -4
- package/src/lib/stack/stack-scan.js +18 -9
- package/src/lib/tasks/eval-node.js +1 -7
- package/src/lib/validators/validation-runner.js +0 -43
- package/src/lib/validators/verify-runner.js +18 -2
- package/src/utils/agents-installer.js +12 -19
- package/src/utils/claude-settings-manager.js +0 -28
- package/src/utils/file-copier.js +0 -53
- package/src/utils/hooks-installer.js +1 -1
- package/framework/hooks/shared/activity-logger.js +0 -131
- package/framework/standards/frontend/design-system/naming.md +0 -68
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -84
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -44
- package/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -94
- package/src/commands/validation/index.js +0 -2
- package/src/lib/validators/code-budget/index.js +0 -2
- package/src/lib/validators/contracts/index.js +0 -1
- package/src/lib/validators/nextjs/index.js +0 -1
- package/src/lib/validators/packages/index.js +0 -1
- package/src/lib/validators/reuse/index.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
-
import { join, resolve
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
3
|
import { tmpdir } from 'os';
|
|
4
4
|
|
|
5
5
|
import { isInside } from './test-runner.js';
|
|
@@ -284,12 +284,6 @@ export function evalColdCacheWarning(summary) {
|
|
|
284
284
|
return null;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
export function relativeToProject(projectPath, target) {
|
|
288
|
-
const root = resolve(projectPath) + sep;
|
|
289
|
-
const full = resolve(target);
|
|
290
|
-
return full.startsWith(root) ? full.slice(root.length).replace(/\\/g, '/') : full;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
287
|
export function isDirectory(path) {
|
|
294
288
|
try {
|
|
295
289
|
return statSync(path).isDirectory();
|
|
@@ -186,46 +186,3 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
|
|
190
|
-
export function formatValidationResults(result) {
|
|
191
|
-
if (result.passed && result.warnings === 0) {
|
|
192
|
-
const ranCount = Object.keys(result.results || {}).length;
|
|
193
|
-
const countLabel = ranCount > 0 ? ` (${ranCount} validator${ranCount !== 1 ? 's' : ''})` : '';
|
|
194
|
-
console.log(chalk.green(`\n ✅ All validations passed${countLabel}!\n`));
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (result.passed && result.warnings > 0) {
|
|
199
|
-
console.log(chalk.yellow(`\n ⚠️ Passed with ${result.warnings} warning(s)\n`));
|
|
200
|
-
} else {
|
|
201
|
-
console.log(chalk.red(`\n ❌ Validation FAILED: ${result.errors} error(s), ${result.warnings} warning(s)\n`));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const errors = result.issues.filter(i => i.level === 'error');
|
|
205
|
-
const warnings = result.issues.filter(i => i.level === 'warning');
|
|
206
|
-
|
|
207
|
-
if (errors.length > 0) {
|
|
208
|
-
console.log(chalk.red.bold(' Errors:'));
|
|
209
|
-
for (const issue of errors.slice(0, 10)) {
|
|
210
|
-
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
211
|
-
if (issue.file) console.log(chalk.gray(` ${issue.file}${issue.line ? ':' + issue.line : ''}`));
|
|
212
|
-
if (issue.solution) console.log(chalk.cyan(` Fix: ${issue.solution}`));
|
|
213
|
-
}
|
|
214
|
-
if (errors.length > 10) {
|
|
215
|
-
console.log(chalk.gray(` ... and ${errors.length - 10} more errors`));
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (warnings.length > 0) {
|
|
220
|
-
console.log(chalk.yellow('\n Warnings:'));
|
|
221
|
-
for (const issue of warnings.slice(0, 5)) {
|
|
222
|
-
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
223
|
-
if (issue.file) console.log(chalk.gray(` ${issue.file}`));
|
|
224
|
-
}
|
|
225
|
-
if (warnings.length > 5) {
|
|
226
|
-
console.log(chalk.gray(` ... and ${warnings.length - 5} more warnings`));
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
console.log('');
|
|
231
|
-
}
|
|
@@ -3,6 +3,8 @@ import { existsSync, readFileSync, rmSync } from 'fs';
|
|
|
3
3
|
import { join, resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
import { runBuildCheck, getBuildSteps, isDotnetStack } from '../../../framework/hooks/shared/build-check.js';
|
|
6
|
+
import { changedPathsAgainst, detectDefaultBranch } from '../git/default-branch.js';
|
|
7
|
+
import { resolveBaseRef } from '../git/branch-classifier.js';
|
|
6
8
|
import { composeProjectName, runE2eCheck } from '../../../framework/hooks/shared/e2e-check.js';
|
|
7
9
|
import { buildComposeEnv } from '../e2e/compose-env.js';
|
|
8
10
|
import { e2eApplicability } from '../e2e/applicability.js';
|
|
@@ -218,8 +220,10 @@ async function runBuildNode({ projectPath, stack, paths: declaredPaths = {}, tim
|
|
|
218
220
|
return { status: 'skip', command: null, durationMs: 0, reason: 'requested', output: '' };
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
+
const resolved = resolveDotnetBuild(projectPath, stack, declaredPaths);
|
|
224
|
+
const { hint } = resolved;
|
|
225
|
+
const paths = { ...resolved.paths, lintFiles: featureLintFiles(projectPath, stack, resolved.paths.frontendPath) };
|
|
226
|
+
const steps = getBuildSteps(stack, { cwd: projectPath, frontendPath: paths.frontendPath, backendPath: paths.backendPath, backendTarget: paths.backendTarget, lintFiles: paths.lintFiles });
|
|
223
227
|
if (steps.length === 0) {
|
|
224
228
|
const reason = stack ? `no build command for stack "${stack}"` : 'no stack configured';
|
|
225
229
|
return { status: 'skip', command: null, durationMs: 0, reason, output: '' };
|
|
@@ -240,6 +244,18 @@ async function runBuildNode({ projectPath, stack, paths: declaredPaths = {}, tim
|
|
|
240
244
|
};
|
|
241
245
|
}
|
|
242
246
|
|
|
247
|
+
const LINTABLE = /\.(?:tsx?|jsx?|mjs|cjs)$/;
|
|
248
|
+
|
|
249
|
+
/** The feature's own source files under the frontend, relative to it; `[]` when the base does not resolve. */
|
|
250
|
+
export function featureLintFiles(projectPath, stack, frontendPath, { changed } = {}) {
|
|
251
|
+
if (!/nextjs|react|vue|node/i.test(stack ?? '')) return [];
|
|
252
|
+
const paths = changed ?? changedPathsAgainst(resolveBaseRef(detectDefaultBranch(projectPath), projectPath), projectPath);
|
|
253
|
+
const prefix = frontendPath ? `${frontendPath.replace(/\\/g, '/').replace(/\/$/, '')}/` : '';
|
|
254
|
+
return paths
|
|
255
|
+
.filter((p) => LINTABLE.test(p) && p.startsWith(prefix) && existsSync(join(projectPath, p)))
|
|
256
|
+
.map((p) => p.slice(prefix.length));
|
|
257
|
+
}
|
|
258
|
+
|
|
243
259
|
function appendBuildFeedback(feedback, buildNode) {
|
|
244
260
|
if (buildNode.status !== 'fail') return;
|
|
245
261
|
for (const line of linesOf(buildNode.output)) {
|
|
@@ -281,27 +281,23 @@ export const DISPATCH_CONTRACT = [
|
|
|
281
281
|
'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
|
|
282
282
|
'',
|
|
283
283
|
'1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
|
|
284
|
-
'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final.
|
|
285
|
-
'2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json
|
|
284
|
+
'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final.',
|
|
285
|
+
'2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json` nem `state.json`: o estado é do orquestrador.',
|
|
286
286
|
'3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
|
|
287
287
|
'um por Cn `prova: teste`, variantes em tabela (`[Theory]`/`it.each`); `leitura` sem teste. NÃO rode teste a cada marco nem faça ciclo red→green: ' +
|
|
288
288
|
'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório; se falhar, corrija e rode só os que falharam. ' +
|
|
289
289
|
'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
|
|
290
290
|
'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
|
|
291
|
-
'4.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
'não repita nem espere em laço — diga no relatório o que não rodou.',
|
|
297
|
-
'7. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
|
|
291
|
+
'4. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `chore(F{n}): wip …` a cada marco (um arquivo ou uma fatia escrita), não só ao fim — ' +
|
|
292
|
+
'e só os seus arquivos (`git add <paths>`, nunca diretório). Sem commit, a retomada recomeça do zero.',
|
|
293
|
+
'5. PROCESSOS: encerre dev server pelo PID que você mesmo subiu. Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano; ' +
|
|
294
|
+
'exit 3 é fila cheia: não repita nem espere em laço, diga no relatório o que não rodou.',
|
|
295
|
+
'6. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
|
|
298
296
|
'`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
|
|
299
297
|
'Se criar mesmo assim, diga por que em uma linha no relatório.',
|
|
300
|
-
'
|
|
301
|
-
|
|
302
|
-
'9.
|
|
303
|
-
'os guards de escrita só enxergam Write/Edit.',
|
|
304
|
-
'10. BLOQUEIO: decisão que não é sua (requisito contra o plano ou um ADR, escolha deixada ao humano) → `BLOQUEADO` na 1ª linha, ' +
|
|
298
|
+
'7. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar; a prova de que o teste morde é do verificador.',
|
|
299
|
+
'8. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash (heredoc, `sed -i`, script que grava): os guards só enxergam Write/Edit.',
|
|
300
|
+
'9. BLOQUEIO: decisão que não é sua (requisito contra o plano ou um ADR, escolha deixada ao humano) → `BLOQUEADO` na 1ª linha, ' +
|
|
305
301
|
'com a pergunta e as opções. Dificuldade técnica não é bloqueio.',
|
|
306
302
|
'',
|
|
307
303
|
'Formato do relatório (o `report-check` lê estas marcas):',
|
|
@@ -416,11 +412,8 @@ function buildBody(agent, maxTurns) {
|
|
|
416
412
|
'',
|
|
417
413
|
'## Working fast',
|
|
418
414
|
'',
|
|
419
|
-
'Independent tool calls go in the SAME response: read every listed file at once, fire the
|
|
420
|
-
'
|
|
421
|
-
'(load a deferred `mcp__` tool with ToolSearch). Write the tests the budget asks for, then run only your own ' +
|
|
422
|
-
'filter ONCE before the report — no red→green cycles, no `morph-spec verify` (the orchestrator proves it). ' +
|
|
423
|
-
'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 verifier.',
|
|
415
|
+
'Independent tool calls go in the SAME response: read every listed file at once, fire the searches together. ' +
|
|
416
|
+
'Check a library API in the `context7` MCP before guessing (load a deferred `mcp__` tool with ToolSearch).',
|
|
424
417
|
);
|
|
425
418
|
}
|
|
426
419
|
|
|
@@ -7,34 +7,6 @@ const MORPH_MANAGED_KEY = '_morph_managed';
|
|
|
7
7
|
export { installClaudeHooks, installGlobalStatusline, getHooksVersion } from './hooks-installer.js';
|
|
8
8
|
export { installAgents } from './agents-installer.js';
|
|
9
9
|
|
|
10
|
-
export async function installEnvVars(targetPath, envVars) {
|
|
11
|
-
const settings = await loadSettings(targetPath);
|
|
12
|
-
|
|
13
|
-
settings.env = settings.env || {};
|
|
14
|
-
|
|
15
|
-
const added = [];
|
|
16
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
17
|
-
if (!(key in settings.env)) {
|
|
18
|
-
settings.env[key] = value;
|
|
19
|
-
added.push(key);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
24
|
-
settings[MORPH_MANAGED_KEY].env_added =
|
|
25
|
-
settings[MORPH_MANAGED_KEY].env_added || [];
|
|
26
|
-
for (const key of added) {
|
|
27
|
-
if (!settings[MORPH_MANAGED_KEY].env_added.includes(key)) {
|
|
28
|
-
settings[MORPH_MANAGED_KEY].env_added.push(key);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
settings[MORPH_MANAGED_KEY].generated_at = new Date().toISOString();
|
|
32
|
-
|
|
33
|
-
await saveSettings(targetPath, settings);
|
|
34
|
-
|
|
35
|
-
return { added, total: Object.keys(settings.env).length };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
10
|
export async function resetMorphSettings(targetPath) {
|
|
39
11
|
const claudeDir = join(targetPath, '.claude');
|
|
40
12
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
package/src/utils/file-copier.js
CHANGED
|
@@ -58,63 +58,10 @@ export async function writeFile(path, content) {
|
|
|
58
58
|
await fs.writeFile(path, content, 'utf8');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export async function removeDir(path) {
|
|
62
|
-
await fs.remove(path);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
61
|
export async function readFile(path) {
|
|
66
62
|
return fs.readFile(path, 'utf8');
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
// Hard link first: it needs no admin on Windows (same NTFS volume).
|
|
70
|
-
export async function createSymlink(target, link, type = 'file') {
|
|
71
|
-
await fs.ensureDir(dirname(link));
|
|
72
|
-
|
|
73
|
-
if (type === 'dir') {
|
|
74
|
-
return createDirectoryLink(target, link);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await fs.link(target, link);
|
|
79
|
-
return 'hardlink';
|
|
80
|
-
} catch {
|
|
81
|
-
try {
|
|
82
|
-
await fs.ensureSymlink(target, link, 'file');
|
|
83
|
-
return 'symlink';
|
|
84
|
-
} catch {
|
|
85
|
-
await copyFile(target, link);
|
|
86
|
-
return 'copy';
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function createDirectoryLink(target, link) {
|
|
92
|
-
await fs.ensureDir(dirname(link));
|
|
93
|
-
|
|
94
|
-
if (await pathExists(link)) {
|
|
95
|
-
await fs.remove(link);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (process.platform === 'win32') {
|
|
99
|
-
// A junction needs no admin; ensureSymlink('dir') would.
|
|
100
|
-
try {
|
|
101
|
-
await fs.symlink(target, link, 'junction');
|
|
102
|
-
return 'junction';
|
|
103
|
-
} catch {
|
|
104
|
-
await copyDirectory(target, link);
|
|
105
|
-
return 'copy';
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
try {
|
|
109
|
-
await fs.ensureSymlink(target, link, 'dir');
|
|
110
|
-
return 'symlink';
|
|
111
|
-
} catch {
|
|
112
|
-
await copyDirectory(target, link);
|
|
113
|
-
return 'copy';
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
65
|
export const MORPH_GITIGNORE_ENTRIES = [
|
|
119
66
|
// Rebuilt from the committed feature.json files; committing it sweeps every feature into one commit.
|
|
120
67
|
'.morph/state.json',
|
|
@@ -64,7 +64,7 @@ const MORPH_HOOKS = [
|
|
|
64
64
|
hooks: [{
|
|
65
65
|
type: 'command',
|
|
66
66
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
67
|
-
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
|
|
67
|
+
args: ['pre-bash-denylist', 'conventional-commit', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
|
|
68
68
|
}]
|
|
69
69
|
},
|
|
70
70
|
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// Append-only on purpose: rewriting a whole JSON doc per event is O(n²) across
|
|
2
|
-
// a session. Fail-silent: never blocks a hook.
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
appendFileSync, readFileSync, mkdirSync, existsSync, statSync, renameSync, writeFileSync,
|
|
6
|
-
} from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
|
|
9
|
-
const LOGS_DIR = '.morph/logs';
|
|
10
|
-
export const ACTIVITY_FILE = join(LOGS_DIR, 'activity.jsonl');
|
|
11
|
-
|
|
12
|
-
export const ROTATE_THRESHOLD_BYTES = 2 * 1024 * 1024;
|
|
13
|
-
|
|
14
|
-
function getActivityPath(projectPath) {
|
|
15
|
-
return join(projectPath || process.cwd(), ACTIVITY_FILE);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function ensureLogsDir(projectPath) {
|
|
19
|
-
const dir = join(projectPath || process.cwd(), LOGS_DIR);
|
|
20
|
-
if (!existsSync(dir)) {
|
|
21
|
-
mkdirSync(dir, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function nowTs() {
|
|
26
|
-
return new Date().toTimeString().slice(0, 8);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function appendLine(projectPath, line) {
|
|
30
|
-
try {
|
|
31
|
-
ensureLogsDir(projectPath);
|
|
32
|
-
appendFileSync(getActivityPath(projectPath), JSON.stringify(line) + '\n', 'utf-8');
|
|
33
|
-
} catch {
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readLines(projectPath) {
|
|
38
|
-
try {
|
|
39
|
-
const activityPath = getActivityPath(projectPath);
|
|
40
|
-
if (!existsSync(activityPath)) return [];
|
|
41
|
-
const raw = readFileSync(activityPath, 'utf-8');
|
|
42
|
-
const lines = [];
|
|
43
|
-
for (const l of raw.split('\n')) {
|
|
44
|
-
const trimmed = l.trim();
|
|
45
|
-
if (!trimmed) continue;
|
|
46
|
-
try {
|
|
47
|
-
lines.push(JSON.parse(trimmed));
|
|
48
|
-
} catch {
|
|
49
|
-
// a process killed mid-write leaves a truncated line
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return lines;
|
|
53
|
-
} catch {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function logHookActivity(name, event, result, projectPath) {
|
|
59
|
-
appendLine(projectPath, { kind: 'hook', name, event, result, ts: nowTs() });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function logEvent(type, data, projectPath) {
|
|
63
|
-
appendLine(projectPath, { kind: 'event', type, data, ts: nowTs() });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** @returns {{ sessionId: string, feature: string, phase: string, hooks: Array, events: Array }|null} */
|
|
67
|
-
export function readActivity(projectPath) {
|
|
68
|
-
const lines = readLines(projectPath);
|
|
69
|
-
if (lines.length === 0) return null;
|
|
70
|
-
|
|
71
|
-
const session = lines.find((l) => l.kind === 'session') || {};
|
|
72
|
-
const strip = ({ kind, ...rest }) => rest;
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
sessionId: session.sessionId || '',
|
|
76
|
-
feature: session.feature || '',
|
|
77
|
-
phase: session.phase || '',
|
|
78
|
-
hooks: lines.filter((l) => l.kind === 'hook').map(strip),
|
|
79
|
-
events: lines.filter((l) => l.kind === 'event').map(strip),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// The one write that truncates: the 'session' header is what `readActivity()`
|
|
84
|
-
// recovers sessionId/feature/phase from.
|
|
85
|
-
export function resetActivity(sessionId, feature, phase, projectPath) {
|
|
86
|
-
try {
|
|
87
|
-
ensureLogsDir(projectPath);
|
|
88
|
-
const session = {
|
|
89
|
-
kind: 'session',
|
|
90
|
-
sessionId: sessionId || new Date().toISOString(),
|
|
91
|
-
feature: feature || '',
|
|
92
|
-
phase: phase || '',
|
|
93
|
-
};
|
|
94
|
-
writeFileSync(getActivityPath(projectPath), JSON.stringify(session) + '\n', 'utf-8');
|
|
95
|
-
} catch {
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function archiveSession(projectPath) {
|
|
100
|
-
try {
|
|
101
|
-
const activityPath = getActivityPath(projectPath);
|
|
102
|
-
if (!existsSync(activityPath)) return;
|
|
103
|
-
|
|
104
|
-
const lines = readLines(projectPath);
|
|
105
|
-
const hasMeaningfulContent = lines.some((l) => l.kind === 'hook' || l.kind === 'event');
|
|
106
|
-
if (!hasMeaningfulContent) return;
|
|
107
|
-
|
|
108
|
-
const session = lines.find((l) => l.kind === 'session') || {};
|
|
109
|
-
const sessionsDir = join(projectPath || process.cwd(), LOGS_DIR, 'sessions');
|
|
110
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
111
|
-
|
|
112
|
-
const stamp = (session.sessionId || new Date().toISOString()).replace(/[:.]/g, '-');
|
|
113
|
-
const archivePath = join(sessionsDir, `${stamp}.jsonl`);
|
|
114
|
-
renameSync(activityPath, archivePath);
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** @returns {boolean} true if a rotation was performed */
|
|
120
|
-
export function rotateActivityLogIfLarge(projectPath, thresholdBytes = ROTATE_THRESHOLD_BYTES) {
|
|
121
|
-
try {
|
|
122
|
-
const activityPath = getActivityPath(projectPath);
|
|
123
|
-
if (!existsSync(activityPath)) return false;
|
|
124
|
-
const { size } = statSync(activityPath);
|
|
125
|
-
if (size < thresholdBytes) return false;
|
|
126
|
-
archiveSession(projectPath);
|
|
127
|
-
return true;
|
|
128
|
-
} catch {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# CSS Naming Convention
|
|
2
|
-
|
|
3
|
-
> **Scope:** universal
|
|
4
|
-
> **Layer:** 0 (resumo injetado pela rule `frontend-standards` ao editar `**/*.tsx`/`**/*.css`/`**/*.scss`; texto completo sob demanda via `morph-spec standards --find`)
|
|
5
|
-
> **Keywords:** css, bem, naming, class names, conventions
|
|
6
|
-
> **Load When:** always
|
|
7
|
-
|
|
8
|
-
**Verified against:** CSS (BEM + custom properties — language-level, no framework). Last-verified: 2026-05-20.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Principles
|
|
13
|
-
|
|
14
|
-
1. **Page prefix** — Prevents conflicts
|
|
15
|
-
2. **BEM for components** — Clear hierarchy
|
|
16
|
-
3. **Namespaced variables** — No library collisions
|
|
17
|
-
4. **State suffixes** — Consistent modifiers
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Page Prefixes
|
|
22
|
-
|
|
23
|
-
| Page | Example |
|
|
24
|
-
|------|---------|
|
|
25
|
-
| Home | `.home-hero`, `.home-features` |
|
|
26
|
-
| Checkout | `.checkout-form`, `.checkout-summary` |
|
|
27
|
-
| Dashboard | `.dashboard-stats`, `.dashboard-chart` |
|
|
28
|
-
| Auth | `.auth-form`, `.auth-social-buttons` |
|
|
29
|
-
|
|
30
|
-
## BEM for Reusable Components
|
|
31
|
-
|
|
32
|
-
```css
|
|
33
|
-
.card { } /* Block */
|
|
34
|
-
.card__header { } /* Element */
|
|
35
|
-
.card__body { }
|
|
36
|
-
.card--featured { } /* Modifier */
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**Rule:** BEM for reusable components. Page prefix for page-specific elements.
|
|
40
|
-
|
|
41
|
-
## CSS Variables with Namespace
|
|
42
|
-
|
|
43
|
-
```css
|
|
44
|
-
:root {
|
|
45
|
-
--app-primary: #007bff;
|
|
46
|
-
--app-spacing-md: 1rem;
|
|
47
|
-
--app-font-heading: 'Segoe UI', sans-serif;
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## State Suffixes
|
|
52
|
-
|
|
53
|
-
| State | Suffix |
|
|
54
|
-
|-------|--------|
|
|
55
|
-
| Active | `--active` |
|
|
56
|
-
| Disabled | `--disabled` |
|
|
57
|
-
| Loading | `--loading` |
|
|
58
|
-
| Error | `--error` |
|
|
59
|
-
|
|
60
|
-
```css
|
|
61
|
-
.button { }
|
|
62
|
-
.button--active { }
|
|
63
|
-
.button--disabled { }
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Fusion Agent — {{AGENT_ID}} (F-Thread {{THREAD_INDEX}}/{{THREAD_COUNT}})
|
|
2
|
-
|
|
3
|
-
You are **Fusion Agent {{THREAD_INDEX}}** in a best-of-N execution session.
|
|
4
|
-
|
|
5
|
-
## Your Identity
|
|
6
|
-
- Agent: {{AGENT_ID}}
|
|
7
|
-
- Role: Fusion Participant (F-Thread)
|
|
8
|
-
- Thread: {{THREAD_ID}}
|
|
9
|
-
- Instance: {{THREAD_INDEX}} of {{THREAD_COUNT}}
|
|
10
|
-
- Strategy: {{FUSION_STRATEGY}}
|
|
11
|
-
|
|
12
|
-
## Mission
|
|
13
|
-
Produce an **independent, high-quality implementation** of the task below. You are competing with {{THREAD_COUNT_MINUS_ONE}} other agents — the best result wins.
|
|
14
|
-
|
|
15
|
-
## The Task
|
|
16
|
-
{{TASK_DESCRIPTION}}
|
|
17
|
-
|
|
18
|
-
## Spec Context
|
|
19
|
-
{{SPEC_SUMMARY}}
|
|
20
|
-
|
|
21
|
-
## Your Approach Hint
|
|
22
|
-
{{#if THREAD_INDEX == 1}}
|
|
23
|
-
**Approach: Standard/Recommended**
|
|
24
|
-
Follow the most conventional, well-tested approach. Prioritize clarity and maintainability over cleverness.
|
|
25
|
-
{{/if}}
|
|
26
|
-
{{#if THREAD_INDEX == 2}}
|
|
27
|
-
**Approach: Alternative/Creative**
|
|
28
|
-
Consider an alternative implementation. What would a different architectural style look like? Explore a different pattern while still meeting all requirements.
|
|
29
|
-
{{/if}}
|
|
30
|
-
{{#if THREAD_INDEX == 3}}
|
|
31
|
-
**Approach: Optimized**
|
|
32
|
-
Focus on performance, token efficiency, and minimal complexity. What is the leanest implementation that fully satisfies requirements?
|
|
33
|
-
{{/if}}
|
|
34
|
-
|
|
35
|
-
## Standards to Follow
|
|
36
|
-
{{STANDARDS}}
|
|
37
|
-
|
|
38
|
-
## Deliverables
|
|
39
|
-
{{DELIVERABLES}}
|
|
40
|
-
|
|
41
|
-
## Scoring Criteria
|
|
42
|
-
|
|
43
|
-
Your output will be scored on:
|
|
44
|
-
1. **Completeness (40%)** — Did you implement everything in the requirements?
|
|
45
|
-
2. **Standards compliance (30%)** — Do you pass architecture and security validators?
|
|
46
|
-
3. **Code quality (30%)** — Is the code clean, readable, and maintainable?
|
|
47
|
-
|
|
48
|
-
## Output Format
|
|
49
|
-
|
|
50
|
-
Structure your output as:
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
FUSION AGENT REPORT
|
|
54
|
-
Agent: {{AGENT_ID}}
|
|
55
|
-
Thread: {{THREAD_ID}}
|
|
56
|
-
Approach: [brief description of your approach]
|
|
57
|
-
|
|
58
|
-
IMPLEMENTATION:
|
|
59
|
-
[your actual implementation]
|
|
60
|
-
|
|
61
|
-
SELF-ASSESSMENT:
|
|
62
|
-
Completeness: [X/10] — [brief justification]
|
|
63
|
-
Standards: [X/10] — [validators passed/failed]
|
|
64
|
-
Quality: [X/10] — [brief justification]
|
|
65
|
-
|
|
66
|
-
FILES CREATED/MODIFIED:
|
|
67
|
-
- [list each file]
|
|
68
|
-
|
|
69
|
-
APPROACH RATIONALE:
|
|
70
|
-
[explain your key design decisions in 2-3 sentences]
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Important
|
|
74
|
-
- Work independently — do NOT coordinate with other fusion threads
|
|
75
|
-
- Be decisive — don't hedge or produce "option A / option B" — commit to one implementation
|
|
76
|
-
- Self-assess honestly — the aggregator uses your self-assessment in scoring
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Fusion Aggregator — {{SESSION_ID}}
|
|
2
|
-
|
|
3
|
-
You are the **Fusion Aggregator** — you select the best result from {{THREAD_COUNT}} competing implementations.
|
|
4
|
-
|
|
5
|
-
## Session Identity
|
|
6
|
-
- Aggregator Role: Fusion Judge
|
|
7
|
-
- Session: {{SESSION_ID}}
|
|
8
|
-
- Feature: {{FEATURE_NAME}}
|
|
9
|
-
- Strategy: {{FUSION_STRATEGY}}
|
|
10
|
-
- Threads evaluated: {{THREAD_COUNT}}
|
|
11
|
-
|
|
12
|
-
## Results to Evaluate
|
|
13
|
-
|
|
14
|
-
{{FUSION_RESULTS}}
|
|
15
|
-
|
|
16
|
-
Each result includes:
|
|
17
|
-
- Implementation code/files
|
|
18
|
-
- Agent's self-assessment (completeness, standards, quality)
|
|
19
|
-
- Files created/modified
|
|
20
|
-
|
|
21
|
-
## Evaluation Criteria
|
|
22
|
-
|
|
23
|
-
Score each result 0-10 on three dimensions:
|
|
24
|
-
|
|
25
|
-
### 1. Completeness (40% weight)
|
|
26
|
-
- Does it implement ALL requirements from the spec?
|
|
27
|
-
- Are edge cases handled?
|
|
28
|
-
- Are deliverables complete (no TODOs, no stubs)?
|
|
29
|
-
|
|
30
|
-
### 2. Standards Compliance (30% weight)
|
|
31
|
-
- Architecture patterns correct (DI, async/await, layer separation)?
|
|
32
|
-
- Security requirements met (no secrets, input validation)?
|
|
33
|
-
- Code follows project conventions?
|
|
34
|
-
|
|
35
|
-
### 3. Code Quality (30% weight)
|
|
36
|
-
- Is the code readable and maintainable?
|
|
37
|
-
- Is it appropriately simple (not over-engineered)?
|
|
38
|
-
- Naming conventions followed?
|
|
39
|
-
- No obvious bugs or anti-patterns?
|
|
40
|
-
|
|
41
|
-
## Aggregation Strategy
|
|
42
|
-
|
|
43
|
-
{{#if STRATEGY == "best-of-n"}}
|
|
44
|
-
### Best-of-N Strategy
|
|
45
|
-
Select the single highest-scoring result. Use it as-is (do not merge).
|
|
46
|
-
|
|
47
|
-
Tiebreaker: prefer the result with higher standards compliance score.
|
|
48
|
-
{{/if}}
|
|
49
|
-
|
|
50
|
-
{{#if STRATEGY == "consensus"}}
|
|
51
|
-
### Consensus Strategy
|
|
52
|
-
Identify elements that appear in 2+ results (majority vote). Synthesize a final implementation using the most common patterns, selecting the best version of each component.
|
|
53
|
-
|
|
54
|
-
Structure your output as a synthesized implementation, not a selection.
|
|
55
|
-
{{/if}}
|
|
56
|
-
|
|
57
|
-
{{#if STRATEGY == "manual-select"}}
|
|
58
|
-
### Manual Select Strategy
|
|
59
|
-
Present all results with your scoring to the user. Do NOT make a final selection — let the user choose.
|
|
60
|
-
{{/if}}
|
|
61
|
-
|
|
62
|
-
## Your Output
|
|
63
|
-
|
|
64
|
-
### Scoring Table
|
|
65
|
-
```
|
|
66
|
-
| Thread | Agent | Completeness | Standards | Quality | TOTAL |
|
|
67
|
-
|--------|-------|-------------|-----------|---------|-------|
|
|
68
|
-
| T-001 | {{AGENT_1}} | X/10 | X/10 | X/10 | XX/30 |
|
|
69
|
-
| T-002 | {{AGENT_2}} | X/10 | X/10 | X/10 | XX/30 |
|
|
70
|
-
| T-003 | {{AGENT_3}} | X/10 | X/10 | X/10 | XX/30 |
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Winner Selection
|
|
74
|
-
```
|
|
75
|
-
FUSION RESULT
|
|
76
|
-
Session: {{SESSION_ID}}
|
|
77
|
-
Winner: Thread {{WINNING_THREAD}} (Agent: {{WINNING_AGENT}})
|
|
78
|
-
Score: {{WINNING_SCORE}}/30
|
|
79
|
-
Strategy: {{FUSION_STRATEGY}}
|
|
80
|
-
|
|
81
|
-
Key strengths of winning implementation:
|
|
82
|
-
- [reason 1]
|
|
83
|
-
- [reason 2]
|
|
84
|
-
|
|
85
|
-
Areas where other implementations excelled:
|
|
86
|
-
- [any cross-thread insights worth preserving]
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Final Implementation
|
|
90
|
-
[Include the winning implementation in full, or merged implementation for consensus strategy]
|
|
91
|
-
|
|
92
|
-
### Files to Apply
|
|
93
|
-
```
|
|
94
|
-
- [each file from winning/merged result]
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## Important Notes
|
|
98
|
-
- Be objective — don't favor any particular agent
|
|
99
|
-
- If all results are poor (< 15/30), flag for human review rather than selecting
|
|
100
|
-
- Note any elements from non-winning results worth cherry-picking
|