@polymorphism-tech/morph-spec 4.8.14 → 4.8.16

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 (72) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/task-manager.js +202 -14
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +113 -116
  8. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  9. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  10. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
  11. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  12. package/framework/hooks/claude-code/session-start/inject-morph-context.js +27 -0
  13. package/framework/hooks/claude-code/stop/validate-completion.js +17 -2
  14. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  15. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  16. package/framework/hooks/shared/phase-utils.js +1 -1
  17. package/framework/hooks/shared/state-reader.js +1 -0
  18. package/framework/skills/README.md +1 -0
  19. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  20. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  21. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  22. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  23. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  24. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  25. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  26. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  27. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  28. package/framework/skills/level-0-meta/morph-init/SKILL.md +3 -2
  29. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  30. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  31. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  32. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  33. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  34. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  35. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
  42. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  43. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  44. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
  45. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  46. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  47. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
  48. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  49. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  50. package/framework/squad-templates/backend-only.json +14 -1
  51. package/framework/squad-templates/frontend-only.json +14 -1
  52. package/framework/squad-templates/full-stack.json +25 -8
  53. package/framework/standards/STANDARDS.json +631 -86
  54. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  55. package/framework/templates/project/validate.js +122 -0
  56. package/framework/workflows/configs/zero-touch.json +7 -0
  57. package/package.json +1 -1
  58. package/src/commands/agents/dispatch-agents.js +53 -10
  59. package/src/commands/state/advance-phase.js +56 -0
  60. package/src/commands/state/index.js +2 -1
  61. package/src/commands/state/phase-runner.js +215 -0
  62. package/src/commands/tasks/task.js +23 -2
  63. package/src/core/paths/output-schema.js +1 -1
  64. package/src/lib/generators/recap-generator.js +16 -0
  65. package/src/lib/orchestration/team-orchestrator.js +171 -89
  66. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  67. package/src/lib/standards/digest-builder.js +231 -0
  68. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  69. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  70. package/src/lib/validators/validation-runner.js +2 -2
  71. package/src/utils/file-copier.js +2 -0
  72. package/src/utils/hooks-installer.js +31 -7
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Standard Digest Builder
3
+ *
4
+ * Composes agent briefings from STANDARDS.json digests.
5
+ * Reduces context per agent from ~2000-8000 tokens (file reads)
6
+ * to ~400-600 tokens (digest inline).
7
+ *
8
+ * Usage:
9
+ * buildAgentBriefing(agentId, phase) → string
10
+ * Injected into dispatch taskPrompt as "\n\nConstraints:\n" + briefing
11
+ *
12
+ * Standards refs in agents.json support two formats:
13
+ * Legacy: "core/coding.md" (treated as scope:'all', priority:'required')
14
+ * Scoped: { id, scope, priority, anchor }
15
+ */
16
+
17
+ import { readFileSync, existsSync } from 'fs';
18
+ import { join, dirname } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+
23
+ const AGENTS_JSON_PATH = join(__dirname, '../../../framework/agents.json');
24
+ const STANDARDS_JSON_PATH = join(__dirname, '../../../framework/standards/STANDARDS.json');
25
+
26
+ let _agentsCache = null;
27
+ let _standardsCache = null;
28
+
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+ // Loaders
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+
33
+ /** Load and cache agents.json */
34
+ function loadAgents() {
35
+ if (_agentsCache) return _agentsCache;
36
+ if (!existsSync(AGENTS_JSON_PATH)) return { agents: {} };
37
+ try {
38
+ _agentsCache = JSON.parse(readFileSync(AGENTS_JSON_PATH, 'utf8'));
39
+ return _agentsCache;
40
+ } catch {
41
+ return { agents: {} };
42
+ }
43
+ }
44
+
45
+ /** Load STANDARDS.json and build path/id lookup maps (module-level cache) */
46
+ function loadStandards() {
47
+ if (_standardsCache) return _standardsCache;
48
+ if (!existsSync(STANDARDS_JSON_PATH)) {
49
+ _standardsCache = { byPath: new Map(), byId: new Map() };
50
+ return _standardsCache;
51
+ }
52
+ try {
53
+ const raw = JSON.parse(readFileSync(STANDARDS_JSON_PATH, 'utf8'));
54
+ const byPath = new Map();
55
+ const byId = new Map();
56
+ for (const entry of raw.standards || []) {
57
+ byPath.set(entry.path, entry);
58
+ byId.set(entry.id, entry);
59
+ }
60
+ _standardsCache = { byPath, byId };
61
+ return _standardsCache;
62
+ } catch {
63
+ _standardsCache = { byPath: new Map(), byId: new Map() };
64
+ return _standardsCache;
65
+ }
66
+ }
67
+
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+ // Helpers
70
+ // ─────────────────────────────────────────────────────────────────────────────
71
+
72
+ /**
73
+ * Normalize a standards reference to canonical shape.
74
+ * Supports legacy string format and new scoped object format.
75
+ *
76
+ * @param {string|Object} ref
77
+ * @returns {{ id?: string, path?: string, scope: string, priority: string, anchor?: string }}
78
+ */
79
+ function normalizeRef(ref) {
80
+ if (typeof ref === 'string') {
81
+ return { path: ref, scope: 'all', priority: 'required' };
82
+ }
83
+ return {
84
+ id: ref.id,
85
+ path: ref.path,
86
+ scope: ref.scope || 'all',
87
+ priority: ref.priority || 'required',
88
+ anchor: ref.anchor,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Resolve a normalized ref to a STANDARDS.json entry.
94
+ * Tries id lookup first, then exact path, then suffix match for short paths.
95
+ *
96
+ * @param {{ id?: string, path?: string }} ref
97
+ * @param {{ byPath: Map, byId: Map }} standards
98
+ * @returns {Object|null}
99
+ */
100
+ function resolveEntry(ref, standards) {
101
+ if (ref.id) {
102
+ const entry = standards.byId.get(ref.id);
103
+ if (entry) return entry;
104
+ }
105
+ if (ref.path) {
106
+ const entry = standards.byPath.get(ref.path);
107
+ if (entry) return entry;
108
+
109
+ // Some agents use short paths like "azure.md" — try suffix match
110
+ for (const [, e] of standards.byPath) {
111
+ if (e.path.endsWith('/' + ref.path)) return e;
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Format a single standards entry as a briefing line.
119
+ *
120
+ * priority='required' with digest → inline digest text
121
+ * priority='reference' → path + optional anchor only
122
+ * no digest → path reference fallback
123
+ *
124
+ * @param {Object} entry - STANDARDS.json entry
125
+ * @param {string} priority - 'required' | 'reference'
126
+ * @param {string} [anchor] - specific anchor key from entry.anchors
127
+ * @returns {string}
128
+ */
129
+ function formatBriefingLine(entry, priority, anchor) {
130
+ const anchorFrag = anchor && entry.anchors?.[anchor] ? entry.anchors[anchor] : '';
131
+
132
+ if (priority === 'required' && entry.digest) {
133
+ const ref = anchorFrag ? ` (ref: ${entry.path}${anchorFrag})` : '';
134
+ return `[${entry.name.toUpperCase()}] ${entry.digest}${ref}`;
135
+ }
136
+
137
+ if (priority === 'reference') {
138
+ return `[REF: ${entry.path}${anchorFrag}]`;
139
+ }
140
+
141
+ // No digest — inject path reference as fallback
142
+ return `[REF: ${entry.path}${anchorFrag}]`;
143
+ }
144
+
145
+ // ─────────────────────────────────────────────────────────────────────────────
146
+ // Public API
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+
149
+ /**
150
+ * Build an agent briefing string for injection into taskPrompt.
151
+ * Returns empty string when agent not found, has no standards, or all refs
152
+ * are filtered by scope.
153
+ *
154
+ * @param {string} agentId - Agent ID from agents.json
155
+ * @param {string} phase - Current phase (design|tasks|implement)
156
+ * @returns {string} Formatted briefing or ''
157
+ */
158
+ export function buildAgentBriefing(agentId, phase) {
159
+ const agents = loadAgents();
160
+ const agentData = agents.agents?.[agentId];
161
+ if (!agentData) return '';
162
+
163
+ const standardsRefs = agentData.standards || [];
164
+ if (standardsRefs.length === 0) return '';
165
+
166
+ const standards = loadStandards();
167
+ const lines = [];
168
+
169
+ for (const rawRef of standardsRefs) {
170
+ const ref = normalizeRef(rawRef);
171
+
172
+ // Scope filtering: skip refs scoped to a different phase
173
+ if (ref.scope !== 'all' && ref.scope !== phase) continue;
174
+
175
+ const entry = resolveEntry(ref, standards);
176
+ if (!entry) {
177
+ // No entry found in registry — emit a path reference as fallback
178
+ const path = ref.path || ref.id;
179
+ if (path) lines.push(`[REF: ${path}]`);
180
+ continue;
181
+ }
182
+
183
+ const line = formatBriefingLine(entry, ref.priority, ref.anchor);
184
+ if (line) lines.push(line);
185
+ }
186
+
187
+ return lines.length > 0 ? lines.join('\n') : '';
188
+ }
189
+
190
+ /**
191
+ * Build a read-only validation task prompt for Tier-4 validator agents.
192
+ * Uses hook_behavior.validates[] from agents.json.
193
+ *
194
+ * @param {string} agentId - Agent ID (must be tier 4 with hook_behavior)
195
+ * @param {string} [taskId] - Optional task ID being validated
196
+ * @returns {string} Validator task prompt or ''
197
+ */
198
+ export function buildValidatorPrompt(agentId, taskId) {
199
+ const agents = loadAgents();
200
+ const agentData = agents.agents?.[agentId];
201
+ if (!agentData?.hook_behavior) return '';
202
+
203
+ const validates = agentData.hook_behavior.validates || [];
204
+ const severity = agentData.hook_behavior.severity || 'error';
205
+ const blocksOnFail = agentData.hook_behavior.blocks_on_fail ?? true;
206
+ const taskSuffix = taskId ? ` for task ${taskId}` : '';
207
+
208
+ const lines = [
209
+ `You are ${agentData.title || agentId} (Tier 4 Validator). Mode: READ-ONLY validation${taskSuffix}.`,
210
+ `DO NOT edit any files. Review the current implementation for the following violations:`,
211
+ ``,
212
+ `Checks (severity: ${severity}):`,
213
+ ...validates.map(v => ` - ${v}`),
214
+ ``,
215
+ `Output a single JSON line: { "passed": boolean, "issues": [{ "file": "path/to/file", "line": 0, "message": "description", "rule": "rule-name" }] }`,
216
+ blocksOnFail
217
+ ? `BLOCKING: if any issues found, implementation must be fixed before task can complete.`
218
+ : `NON-BLOCKING: issues are warnings only — task completion is not blocked.`,
219
+ ];
220
+
221
+ return lines.join('\n');
222
+ }
223
+
224
+ /**
225
+ * Invalidate module-level caches.
226
+ * Used in tests to ensure a fresh load of agents.json / STANDARDS.json.
227
+ */
228
+ export function clearCache() {
229
+ _agentsCache = null;
230
+ _standardsCache = null;
231
+ }
@@ -7,6 +7,8 @@
7
7
  * @module blazor-concurrency-analyzer
8
8
  */
9
9
 
10
+ import { readFileSync } from 'fs';
11
+ import { glob } from 'glob';
10
12
  import { countIssues } from '../shared/index.js';
11
13
 
12
14
  /**
@@ -174,6 +176,9 @@ export function checkScopedInSingleton(content, filePath) {
174
176
  const issues = [];
175
177
  const lines = content.split('\n');
176
178
 
179
+ // Type guard: filePath must be a string (validation-runner may pass options object)
180
+ if (typeof filePath !== 'string') return [];
181
+
177
182
  // Check if class might be a singleton (common naming patterns)
178
183
  const isSingletonCandidate =
179
184
  filePath.includes('BackgroundService') ||
@@ -267,8 +272,42 @@ export function analyzeConcurrency(content, filePath) {
267
272
  }
268
273
 
269
274
 
275
+ /**
276
+ * Project-level concurrency validator. Scans all .cs files in projectPath.
277
+ * Follows the same interface as other project-level validators (validateNextComponentFiles, etc.)
278
+ *
279
+ * @param {string} projectPath - Project root path
280
+ * @param {Object} [_options]
281
+ * @returns {Promise<{errors: number, warnings: number, issues: Array}>}
282
+ */
283
+ export async function validateConcurrencyFiles(projectPath, _options = {}) {
284
+ const result = { errors: 0, warnings: 0, issues: [] };
285
+ const pattern = projectPath.replace(/\\/g, '/') + '/**/*.cs';
286
+ const files = await glob(pattern, { ignore: ['**/node_modules/**', '**/obj/**', '**/bin/**'] });
287
+
288
+ for (const filePath of files) {
289
+ const content = readFileSync(filePath, 'utf8');
290
+ const relPath = filePath.replace(projectPath.replace(/\\/g, '/') + '/', '');
291
+ const fileIssues = analyzeConcurrency(content, relPath);
292
+ for (const issue of fileIssues) {
293
+ result.issues.push({
294
+ level: issue.type,
295
+ message: issue.message,
296
+ file: issue.file,
297
+ line: issue.line,
298
+ solution: issue.suggestion,
299
+ });
300
+ if (issue.type === 'error') result.errors++;
301
+ else result.warnings++;
302
+ }
303
+ }
304
+
305
+ return result;
306
+ }
307
+
270
308
  export default {
271
309
  analyzeConcurrency,
310
+ validateConcurrencyFiles,
272
311
  checkScopedDbContextInBackground,
273
312
  checkHangfireWithScopedServices,
274
313
  checkScopedInSingleton,
@@ -86,7 +86,9 @@ export function validateNextComponent(content, filePath) {
86
86
  }
87
87
 
88
88
  // Check 2: hooks used without use client
89
+ // .ts files (custom hooks, utilities) never render JSX — skip this check
89
90
  if (!hasUseClient) {
91
+ if (!filePath.endsWith('.tsx')) return issues;
90
92
  const usedHooks = CLIENT_HOOKS.filter(hook => new RegExp(`\\b${hook}\\b`).test(content));
91
93
  if (usedHooks.length > 0) {
92
94
  issues.push({
@@ -202,8 +202,8 @@ async function runSingleValidator(validatorId, projectPath, featureName, options
202
202
  }
203
203
 
204
204
  case 'blazor-concurrency': {
205
- const { analyzeConcurrency } = await import('./blazor/blazor-concurrency-analyzer.js');
206
- return await analyzeConcurrency(projectPath, options);
205
+ const { validateConcurrencyFiles } = await import('./blazor/blazor-concurrency-analyzer.js');
206
+ return await validateConcurrencyFiles(projectPath, options);
207
207
  }
208
208
 
209
209
  case 'blazor-state': {
@@ -136,6 +136,7 @@ export async function updateGitignore(projectPath) {
136
136
  '# MORPH-SPEC',
137
137
  '.morph/framework/',
138
138
  '.morph/memory/',
139
+ '.morph/plans/',
139
140
  '.morph/analytics/',
140
141
  '.claude/commands/',
141
142
  '.claude/rules/',
@@ -143,6 +144,7 @@ export async function updateGitignore(projectPath) {
143
144
  '.claude/agents/',
144
145
  '.claude/CLAUDE.md',
145
146
  '.claude/settings.local.json',
147
+ '.mcp.json',
146
148
  ''
147
149
  ];
148
150
 
@@ -15,7 +15,7 @@ import { homedir } from 'os';
15
15
  import { execSync } from 'child_process';
16
16
 
17
17
  /** Current hooks schema version — bump when hook definitions change */
18
- const HOOKS_VERSION = '2.5.1';
18
+ const HOOKS_VERSION = '2.6.0';
19
19
 
20
20
  /** Marker for old dispatch.js (v1) */
21
21
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -67,10 +67,16 @@ const MORPH_HOOKS = [
67
67
  {
68
68
  event: 'UserPromptSubmit',
69
69
  matcher: null,
70
- hooks: [{
71
- type: 'command',
72
- command: 'node framework/hooks/claude-code/user-prompt/enrich-prompt.js'
73
- }]
70
+ hooks: [
71
+ {
72
+ type: 'command',
73
+ command: 'node framework/hooks/claude-code/user-prompt/enrich-prompt.js'
74
+ },
75
+ {
76
+ type: 'command',
77
+ command: 'node framework/hooks/claude-code/user-prompt/set-terminal-title.js'
78
+ }
79
+ ]
74
80
  },
75
81
 
76
82
  // === PreToolUse: Write|Edit ===
@@ -155,6 +161,20 @@ Otherwise respond: {"ok": true}`
155
161
  type: 'command',
156
162
  command: 'node framework/hooks/claude-code/notification/approval-reminder.js'
157
163
  }]
164
+ },
165
+
166
+ // === TeammateIdle ===
167
+ // Fires when an Agent Team teammate finishes and is about to go idle.
168
+ // Exit 2 = send feedback to teammate to continue working (validation failed).
169
+ // Exit 0 = teammate may become idle (validation passed).
170
+ // Requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 (set in env above).
171
+ {
172
+ event: 'TeammateIdle',
173
+ matcher: null,
174
+ hooks: [{
175
+ type: 'command',
176
+ command: 'node framework/hooks/claude-code/teammate-idle/teammate-idle.js'
177
+ }]
158
178
  }
159
179
  ];
160
180
 
@@ -241,8 +261,12 @@ export async function installClaudeHooks(targetPath) {
241
261
  // JSON Schema for IDE auto-complete and validation
242
262
  settings['$schema'] = 'https://json.schemastore.org/claude-code-settings.json';
243
263
 
244
- // Environment variables — always set MORPH_SPEC_ACTIVE, preserve user additions
245
- settings.env = { ...settings.env, MORPH_SPEC_ACTIVE: 'true' };
264
+ // Environment variables — always set MORPH_SPEC_ACTIVE + Agent Teams flag, preserve user additions
265
+ settings.env = {
266
+ ...settings.env,
267
+ MORPH_SPEC_ACTIVE: 'true',
268
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
269
+ };
246
270
 
247
271
  // Attribution — only set if not already customized by user
248
272
  if (!settings.attribution) {