@polymorphism-tech/morph-spec 9.4.0 → 9.6.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/bin/morph-spec.js +1 -0
- 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 +6 -7
- package/framework/docs/CLI.md +12 -8
- package/framework/hooks/README.md +2 -2
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/comment-guard.js +1 -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/heavy-gate.js +5 -3
- 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/comment-rules.js +11 -1
- package/framework/hooks/shared/feature-locator.js +7 -5
- package/framework/hooks/shared/heavy-command.js +12 -0
- package/framework/hooks/shared/heavy-config.js +28 -1
- package/framework/hooks/shared/heavy-slots.js +41 -5
- 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/frontend-standards.md +1 -2
- package/framework/rules/nextjs-standards.md +9 -8
- package/framework/schemas/config.schema.json +12 -1
- 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 +2 -2
- 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 +32 -29
- package/framework/standards/STANDARDS.meta.json +13 -15
- package/framework/standards/architecture/code-comments.md +1 -1
- 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/standards/infrastructure/docker/local-compose-isolation.md +1 -0
- package/framework/templates/README.md +4 -4
- package/framework/templates/REGISTRY.json +21 -309
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +54 -56
- package/package.json +3 -2
- package/src/commands/e2e/e2e.js +3 -0
- package/src/commands/project/env.js +3 -3
- package/src/commands/project/heavy.js +10 -3
- package/src/commands/project/worktree.js +2 -1
- package/src/commands/state/finish.js +17 -3
- package/src/commands/validation/mutate.js +2 -2
- package/src/commands/validation/verify.js +3 -0
- 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-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/e2e/compose-env.js +26 -3
- package/src/lib/graph/graphify-runtime.js +0 -1
- package/src/lib/heavy/gate.js +23 -4
- package/src/lib/heavy/live-stacks.js +64 -0
- package/src/lib/mutation/snapshot.js +1 -5
- package/src/lib/risk-detector.js +9 -6
- package/src/lib/stack/stack-profile.js +0 -4
- package/src/lib/stack/stack-scan.js +15 -4
- 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/lib/worktree/docker-audit.js +7 -3
- package/src/lib/worktree/env-allocator.js +27 -5
- package/src/lib/worktree/ignored-triage.js +8 -0
- 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/framework/hooks/shared/activity-logger.js +0 -131
- package/framework/standards/frontend/design-system/naming.md +0 -68
- 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
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',
|
|
@@ -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 +0,0 @@
|
|
|
1
|
-
export * from './contract-compliance-validator.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { validateNextComponent, validateNextComponentFiles } from './next-component-validator.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './package-validator.js';
|