@polymorphism-tech/morph-spec 4.8.12 → 4.8.14
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 +379 -379
- package/bin/{task-manager.cjs → task-manager.js} +47 -158
- package/claude-plugin.json +14 -14
- package/docs/CHEATSHEET.md +203 -203
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +111 -24
- package/framework/hooks/README.md +202 -202
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +7 -0
- package/framework/hooks/claude-code/statusline.py +6 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +21 -2
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +55 -0
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +57 -1
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +23 -1
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +25 -2
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
- package/package.json +87 -87
- package/src/commands/state/advance-phase.js +32 -13
- package/src/commands/tasks/task.js +2 -2
- package/src/core/paths/output-schema.js +1 -0
- package/src/lib/detectors/design-system-detector.js +5 -4
- package/src/lib/tasks/task-parser.js +94 -0
- package/src/lib/validators/content/content-validator.js +34 -106
|
@@ -23,16 +23,19 @@ export function validateSpecContent(specPath) {
|
|
|
23
23
|
|
|
24
24
|
const content = readFileSync(specPath, 'utf8');
|
|
25
25
|
|
|
26
|
-
// Required sections for a complete spec
|
|
26
|
+
// Required sections for a complete spec (prefix-match to handle variants like
|
|
27
|
+
// "## Functional Requirements" and "## Technical Architecture")
|
|
27
28
|
const requiredSections = [
|
|
28
29
|
'## Overview',
|
|
29
|
-
'## Requirements',
|
|
30
|
-
'## Technical
|
|
30
|
+
'## Functional Requirements',
|
|
31
|
+
'## Technical',
|
|
31
32
|
'## Data Model',
|
|
32
|
-
'## API Contracts'
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
const missing = requiredSections.filter(section =>
|
|
37
|
+
!lines.some(line => line.startsWith(section))
|
|
38
|
+
);
|
|
36
39
|
|
|
37
40
|
// Additional quality checks
|
|
38
41
|
const errors = [];
|
|
@@ -79,8 +82,8 @@ export function validateSpecContent(specPath) {
|
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
/**
|
|
82
|
-
* Validate tasks.
|
|
83
|
-
* @param {string} tasksPath - Path to tasks.
|
|
85
|
+
* Validate tasks.md structure
|
|
86
|
+
* @param {string} tasksPath - Path to tasks.md file
|
|
84
87
|
* @returns {Object} Validation result
|
|
85
88
|
*/
|
|
86
89
|
export function validateTasksContent(tasksPath) {
|
|
@@ -91,111 +94,36 @@ export function validateTasksContent(tasksPath) {
|
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const content = readFileSync(tasksPath, 'utf8');
|
|
97
|
-
tasks = JSON.parse(content);
|
|
98
|
-
} catch (error) {
|
|
99
|
-
return {
|
|
100
|
-
valid: false,
|
|
101
|
-
errors: ['Invalid JSON in tasks file: ' + error.message]
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
97
|
+
const content = readFileSync(tasksPath, 'utf8');
|
|
105
98
|
const errors = [];
|
|
106
99
|
const warnings = [];
|
|
107
100
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
101
|
+
// Extract task IDs from headings: ### T001 — title OR ### T001: title
|
|
102
|
+
const taskRe = /^###\s+(T\d{3})\s*[—–:\-]\s*(.+)$/gm;
|
|
103
|
+
const checkpointRe = /^###\s+(CHECKPOINT[_-]\d+)\s*[—–:\-]\s*(.+)$/gm;
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return { valid: false, errors, warnings };
|
|
116
|
-
}
|
|
105
|
+
const tasks = [...content.matchAll(taskRe)];
|
|
106
|
+
const checkpoints = [...content.matchAll(checkpointRe)];
|
|
117
107
|
|
|
118
|
-
if (tasks.
|
|
119
|
-
errors.push('
|
|
108
|
+
if (tasks.length === 0) {
|
|
109
|
+
errors.push('No tasks found in tasks.md — expected headings like "### T001 — Title" or "### T001: Title"');
|
|
120
110
|
return { valid: false, errors, warnings };
|
|
121
111
|
}
|
|
122
112
|
|
|
123
|
-
// Validate
|
|
124
|
-
tasks.
|
|
125
|
-
|
|
113
|
+
// Validate no duplicate IDs
|
|
114
|
+
const ids = tasks.map(m => m[1]);
|
|
115
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
116
|
+
dupes.forEach(id => errors.push(`Duplicate task ID: ${id}`));
|
|
126
117
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
warnings.push(`${taskId}: ID should follow format T### or CHECKPOINT_###`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!task.title) {
|
|
135
|
-
errors.push(`${taskId}: Missing "title" field`);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!task.description) {
|
|
139
|
-
errors.push(`${taskId}: Missing "description" field`);
|
|
140
|
-
}
|
|
118
|
+
// Check for dependency references (look for "Dependencies: T###")
|
|
119
|
+
const depRe = /\*\*Dependenc(?:y|ies):\*\*\s*([T\d, ]+)/gi;
|
|
120
|
+
const allDepRefs = [...content.matchAll(depRe)]
|
|
121
|
+
.flatMap(m => m[1].split(/[,\s]+/).filter(s => /^T\d{3}$/.test(s)));
|
|
141
122
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// For regular tasks (not checkpoints)
|
|
147
|
-
if (task.id && task.id.startsWith('T')) {
|
|
148
|
-
if (!task.category) {
|
|
149
|
-
warnings.push(`${taskId}: Missing "category" field`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (!task.estimatedMinutes) {
|
|
153
|
-
warnings.push(`${taskId}: Missing "estimatedMinutes" field`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!task.files || task.files.length === 0) {
|
|
157
|
-
warnings.push(`${taskId}: No files specified - consider adding affected files`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// For checkpoints
|
|
162
|
-
if (task.id && task.id.startsWith('CHECKPOINT')) {
|
|
163
|
-
if (!task.afterTasks || task.afterTasks.length === 0) {
|
|
164
|
-
warnings.push(`${taskId}: Checkpoint should specify "afterTasks"`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!task.validations || task.validations.length === 0) {
|
|
168
|
-
warnings.push(`${taskId}: Checkpoint should specify "validations"`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Check for orphaned tasks (missing dependencies)
|
|
174
|
-
const taskIds = new Set(tasks.tasks.map(t => t.id));
|
|
175
|
-
tasks.tasks.forEach(task => {
|
|
176
|
-
if (task.dependencies && Array.isArray(task.dependencies)) {
|
|
177
|
-
task.dependencies.forEach(depId => {
|
|
178
|
-
if (depId && !taskIds.has(depId)) {
|
|
179
|
-
errors.push(`${task.id}: References non-existent dependency "${depId}"`);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Check for circular dependencies (simple check)
|
|
186
|
-
const hasCycle = (taskId, visited = new Set()) => {
|
|
187
|
-
if (visited.has(taskId)) return true;
|
|
188
|
-
visited.add(taskId);
|
|
189
|
-
|
|
190
|
-
const task = tasks.tasks.find(t => t.id === taskId);
|
|
191
|
-
if (!task || !task.dependencies) return false;
|
|
192
|
-
|
|
193
|
-
return task.dependencies.some(depId => hasCycle(depId, new Set(visited)));
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
tasks.tasks.forEach(task => {
|
|
197
|
-
if (task.id && hasCycle(task.id)) {
|
|
198
|
-
errors.push(`Circular dependency detected involving task ${task.id}`);
|
|
123
|
+
const idSet = new Set(ids);
|
|
124
|
+
allDepRefs.forEach(depId => {
|
|
125
|
+
if (!idSet.has(depId)) {
|
|
126
|
+
warnings.push(`Dependency reference "${depId}" not found in task list`);
|
|
199
127
|
}
|
|
200
128
|
});
|
|
201
129
|
|
|
@@ -204,10 +132,10 @@ export function validateTasksContent(tasksPath) {
|
|
|
204
132
|
errors,
|
|
205
133
|
warnings,
|
|
206
134
|
stats: {
|
|
207
|
-
totalTasks: tasks.
|
|
208
|
-
regularTasks: tasks.
|
|
209
|
-
checkpoints:
|
|
210
|
-
}
|
|
135
|
+
totalTasks: tasks.length + checkpoints.length,
|
|
136
|
+
regularTasks: tasks.length,
|
|
137
|
+
checkpoints: checkpoints.length,
|
|
138
|
+
},
|
|
211
139
|
};
|
|
212
140
|
}
|
|
213
141
|
|