@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.
Files changed (27) hide show
  1. package/README.md +379 -379
  2. package/bin/{task-manager.cjs → task-manager.js} +47 -158
  3. package/claude-plugin.json +14 -14
  4. package/docs/CHEATSHEET.md +203 -203
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/agents.json +111 -24
  7. package/framework/hooks/README.md +202 -202
  8. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
  9. package/framework/hooks/claude-code/session-start/inject-morph-context.js +7 -0
  10. package/framework/hooks/claude-code/statusline.py +6 -0
  11. package/framework/hooks/claude-code/stop/validate-completion.js +21 -2
  12. package/framework/hooks/shared/phase-utils.js +3 -0
  13. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +55 -0
  14. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
  15. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  16. package/framework/skills/level-1-workflows/phase-design/SKILL.md +57 -1
  17. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +23 -1
  18. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
  19. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +25 -2
  20. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
  21. package/package.json +87 -87
  22. package/src/commands/state/advance-phase.js +32 -13
  23. package/src/commands/tasks/task.js +2 -2
  24. package/src/core/paths/output-schema.js +1 -0
  25. package/src/lib/detectors/design-system-detector.js +5 -4
  26. package/src/lib/tasks/task-parser.js +94 -0
  27. 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 Design',
30
+ '## Functional Requirements',
31
+ '## Technical',
31
32
  '## Data Model',
32
- '## API Contracts'
33
33
  ];
34
34
 
35
- const missing = requiredSections.filter(section => !content.includes(section));
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.json structure
83
- * @param {string} tasksPath - Path to tasks.json file
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
- let tasks;
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
- // Check required top-level fields
109
- if (!tasks.feature) {
110
- errors.push('Missing "feature" field in tasks.json');
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
- if (!tasks.tasks || !Array.isArray(tasks.tasks)) {
114
- errors.push('Missing or invalid "tasks" array in tasks.json');
115
- return { valid: false, errors, warnings };
116
- }
105
+ const tasks = [...content.matchAll(taskRe)];
106
+ const checkpoints = [...content.matchAll(checkpointRe)];
117
107
 
118
- if (tasks.tasks.length === 0) {
119
- errors.push('Tasks array is empty - no tasks defined');
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 individual tasks
124
- tasks.tasks.forEach((task, index) => {
125
- const taskId = task.id || `Task ${index}`;
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
- // Check required fields
128
- if (!task.id) {
129
- errors.push(`${taskId}: Missing "id" field`);
130
- } else if (!/^(T\d{3}|CHECKPOINT_\d{3})$/.test(task.id)) {
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
- if (!task.dependencies) {
143
- errors.push(`${taskId}: Missing "dependencies" field (use empty array if no deps)`);
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.tasks.length,
208
- regularTasks: tasks.tasks.filter(t => t.id?.startsWith('T')).length,
209
- checkpoints: tasks.tasks.filter(t => t.id?.startsWith('CHECKPOINT')).length
210
- }
135
+ totalTasks: tasks.length + checkpoints.length,
136
+ regularTasks: tasks.length,
137
+ checkpoints: checkpoints.length,
138
+ },
211
139
  };
212
140
  }
213
141