@polymorphism-tech/morph-spec 8.27.0 → 8.29.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 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +9 -4
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +3 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +8 -8
- package/framework/agents.json +14 -3
- package/framework/commands/create-umbraco-lp.md +1 -1
- package/framework/docs/CLI.md +6 -5
- package/framework/mcp/registry.json +59 -8
- package/framework/skills/core/create-umbraco-lp/SKILL.md +13 -2
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +598 -0
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +92 -7
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/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/PROVENANCE.json +41 -0
- package/framework/standards/STANDARDS.json +218 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +43 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +113 -0
- package/framework/standards/frontend/design-system/premium-finish.md +160 -0
- package/framework/standards/frontend/gsap/gsap-core.md +1 -1
- package/framework/standards/frontend/gsap/gsap-plugins.md +1 -1
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +3 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +3 -1
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +287 -0
- package/framework/standards/frontend/scroll-driven/scroll-components.md +256 -0
- package/framework/standards/frontend/scroll-driven/smooth-scroll.md +148 -0
- package/framework/standards/frontend/seo/meta-schema-audit.md +134 -0
- package/package.json +1 -1
- package/src/commands/project/doctor.js +9 -4
- package/src/commands/project/worktree.js +45 -3
- package/src/commands/state/advance.js +12 -0
- package/src/commands/state/approve.js +20 -0
- package/src/commands/state/create.js +4 -1
- package/src/commands/state/delete.js +63 -4
- package/src/commands/state/finish.js +20 -2
- package/src/commands/state/gate-check.js +13 -0
- package/src/commands/state/gate-decision.js +9 -0
- package/src/commands/state/handoff.js +48 -6
- package/src/commands/state/list.js +124 -10
- package/src/commands/state/score.js +9 -0
- package/src/lib/generators/handoff-generator.js +11 -4
- package/src/lib/worktree/feature-truth.js +248 -0
- package/src/lib/worktree/fleet-scanner.js +1 -65
- package/src/lib/worktree/worktree-audit.js +103 -17
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { execFileSync } from 'child_process';
|
|
25
|
-
import { existsSync, readdirSync, statSync } from 'fs';
|
|
25
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
26
26
|
import { join, resolve } from 'path';
|
|
27
27
|
import {
|
|
28
28
|
isGitRepo,
|
|
29
29
|
resolvePrimaryRoot,
|
|
30
30
|
parseWorktreeList,
|
|
31
31
|
WORKTREE_DIR_NAMES,
|
|
32
|
+
clearDanglingWorktreeRegistration,
|
|
32
33
|
} from '../../commands/project/worktree.js';
|
|
33
34
|
|
|
34
35
|
const norm = (p) => resolve(p).replace(/\\/g, '/');
|
|
@@ -65,6 +66,36 @@ function featureCommittedOnBranch(root, feature) {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Every feature's `worktree` registration in the primary root — the OTHER
|
|
71
|
+
* direction from `knownFeatures`/`dirsOnDisk`: not "does git/disk know about
|
|
72
|
+
* this worktree", but "does the ROOT's feature.json still point somewhere
|
|
73
|
+
* real". `create --worktree` writes this pointer once at birth
|
|
74
|
+
* (`{path, branch, createdAt}`) and nothing updates it afterwards except
|
|
75
|
+
* `finish`'s own teardown — so a directory removed any other way (by hand,
|
|
76
|
+
* `git worktree prune` run directly, a failed teardown that still cleared the
|
|
77
|
+
* field, an unrelated script) leaves it dangling with nothing to notice.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} root
|
|
80
|
+
* @returns {Array<{ feature: string, path: string, branch: string|null }>}
|
|
81
|
+
*/
|
|
82
|
+
function featureRegistrations(root) {
|
|
83
|
+
const out = [];
|
|
84
|
+
try {
|
|
85
|
+
const dir = join(root, '.morph', 'features');
|
|
86
|
+
if (!existsSync(dir)) return out;
|
|
87
|
+
for (const feature of readdirSync(dir)) {
|
|
88
|
+
const fjPath = join(dir, feature, 'feature.json');
|
|
89
|
+
if (!existsSync(fjPath)) continue;
|
|
90
|
+
try {
|
|
91
|
+
const fj = JSON.parse(readFileSync(fjPath, 'utf8'));
|
|
92
|
+
if (fj?.worktree?.path) out.push({ feature, path: fj.worktree.path, branch: fj.worktree.branch || null });
|
|
93
|
+
} catch { /* corrupt feature.json — nothing to register */ }
|
|
94
|
+
}
|
|
95
|
+
} catch { /* fail-open */ }
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
/** Directories physically present under either worktree root. */
|
|
69
100
|
function dirsOnDisk(root) {
|
|
70
101
|
const found = [];
|
|
@@ -87,14 +118,25 @@ function dirsOnDisk(root) {
|
|
|
87
118
|
* Reconcile git's registry, the disk and morph's features.
|
|
88
119
|
*
|
|
89
120
|
* Issue types:
|
|
90
|
-
* • registered-missing
|
|
91
|
-
* • non-morph-branch
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* •
|
|
121
|
+
* • registered-missing — git has a worktree whose directory is gone
|
|
122
|
+
* • non-morph-branch — a registered worktree under worktrees/ on a
|
|
123
|
+
* branch that is not `morph/*`, so `fleet`
|
|
124
|
+
* cannot see it
|
|
125
|
+
* • unregistered-dir — a directory under worktrees/ git never
|
|
126
|
+
* registered
|
|
127
|
+
* • no-feature — a `morph/{f}` worktree for a feature morph
|
|
128
|
+
* does not know
|
|
129
|
+
* • dangling-registration — a feature's ROOT feature.json still points
|
|
130
|
+
* at a `worktree.path` that no longer exists
|
|
131
|
+
* (the symmetric OTHER direction from
|
|
132
|
+
* `no-feature`: the worktree is gone, but the
|
|
133
|
+
* root's pointer to it survived)
|
|
134
|
+
* • unregistered-registration — a feature's `worktree.path` names a
|
|
135
|
+
* directory that exists but git's own
|
|
136
|
+
* registry does not know as a worktree
|
|
95
137
|
*
|
|
96
138
|
* @param {string} [cwd]
|
|
97
|
-
* @returns {Array<{ type: string, detail: string, path?: string, fix: string }>}
|
|
139
|
+
* @returns {Array<{ type: string, detail: string, path?: string, feature?: string, fix: string }>}
|
|
98
140
|
*/
|
|
99
141
|
export function auditWorktrees(cwd = process.cwd()) {
|
|
100
142
|
if (!isGitRepo(cwd)) return [];
|
|
@@ -161,25 +203,69 @@ export function auditWorktrees(cwd = process.cwd()) {
|
|
|
161
203
|
});
|
|
162
204
|
}
|
|
163
205
|
|
|
206
|
+
// The other direction: does the ROOT's own bookkeeping still point at a
|
|
207
|
+
// live worktree? `git worktree list`/disk only see what's PHYSICALLY
|
|
208
|
+
// there; a registration surviving past its worktree's removal is invisible
|
|
209
|
+
// to every check above.
|
|
210
|
+
for (const reg of featureRegistrations(root)) {
|
|
211
|
+
const path = norm(reg.path);
|
|
212
|
+
if (!existsSync(reg.path)) {
|
|
213
|
+
issues.push({
|
|
214
|
+
type: 'dangling-registration',
|
|
215
|
+
feature: reg.feature,
|
|
216
|
+
path,
|
|
217
|
+
detail: `"${reg.feature}" registra worktree.path=${path} na raiz, mas o diretório não existe`,
|
|
218
|
+
fix: `morph-spec doctor --worktrees --prune`,
|
|
219
|
+
});
|
|
220
|
+
} else if (!registeredPaths.has(path)) {
|
|
221
|
+
issues.push({
|
|
222
|
+
type: 'unregistered-registration',
|
|
223
|
+
feature: reg.feature,
|
|
224
|
+
path,
|
|
225
|
+
detail: `"${reg.feature}" registra worktree.path=${path}, mas o git não o conhece como worktree — a próxima \`worktree setup\` recusará com exit 3`,
|
|
226
|
+
fix: `inspecione o diretório; se não serve mais, apague-o e rode morph-spec doctor --worktrees --prune`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
164
231
|
return issues;
|
|
165
232
|
}
|
|
166
233
|
|
|
167
234
|
/**
|
|
168
|
-
* Drop git's admin records for worktrees whose directory is already gone
|
|
235
|
+
* Drop git's admin records for worktrees whose directory is already gone, AND
|
|
236
|
+
* clear any feature's `worktree` registration that names one of those same
|
|
237
|
+
* gone directories.
|
|
169
238
|
*
|
|
170
|
-
*
|
|
171
|
-
* everything
|
|
172
|
-
* still exists is exactly the operation that needs
|
|
239
|
+
* Both halves are the ONLY prune offered, on purpose: neither can destroy
|
|
240
|
+
* anything, because everything they touch is already absent from disk.
|
|
241
|
+
* Removing a directory that still exists is exactly the operation that needs
|
|
242
|
+
* a human looking at it.
|
|
243
|
+
*
|
|
244
|
+
* `clearDanglingWorktreeRegistration` re-checks the dangling predicate itself
|
|
245
|
+
* (path unset, or still exists → no-op) rather than trusting a pre-computed
|
|
246
|
+
* list, so this stays correct even called against a stale audit snapshot.
|
|
173
247
|
*
|
|
174
248
|
* @param {string} [cwd]
|
|
175
|
-
* @returns {{ pruned: boolean, error?: string }}
|
|
249
|
+
* @returns {Promise<{ pruned: boolean, error?: string, clearedRegistrations: Array<{feature: string, cleared: boolean}> }>}
|
|
176
250
|
*/
|
|
177
|
-
export function pruneWorktrees(cwd = process.cwd()) {
|
|
178
|
-
if (!isGitRepo(cwd)) return { pruned: false, error: 'not-a-git-repo' };
|
|
251
|
+
export async function pruneWorktrees(cwd = process.cwd()) {
|
|
252
|
+
if (!isGitRepo(cwd)) return { pruned: false, error: 'not-a-git-repo', clearedRegistrations: [] };
|
|
253
|
+
const root = resolvePrimaryRoot(cwd);
|
|
254
|
+
|
|
255
|
+
let pruned = false;
|
|
256
|
+
let error;
|
|
179
257
|
try {
|
|
180
|
-
execFileSync('git', ['worktree', 'prune'], { cwd:
|
|
181
|
-
|
|
258
|
+
execFileSync('git', ['worktree', 'prune'], { cwd: root, stdio: 'pipe' });
|
|
259
|
+
pruned = true;
|
|
182
260
|
} catch (err) {
|
|
183
|
-
|
|
261
|
+
error = (err.message || 'unknown').slice(0, 200);
|
|
184
262
|
}
|
|
263
|
+
|
|
264
|
+
const clearedRegistrations = [];
|
|
265
|
+
for (const reg of featureRegistrations(root)) {
|
|
266
|
+
const { cleared } = await clearDanglingWorktreeRegistration(root, reg.feature);
|
|
267
|
+
if (cleared) clearedRegistrations.push({ feature: reg.feature, cleared: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { pruned, ...(error ? { error } : {}), clearedRegistrations };
|
|
185
271
|
}
|