@polymorphism-tech/morph-spec 4.8.14 → 4.8.15

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 (71) 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-replicate/SKILL.md +10 -8
  29. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  30. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  31. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  32. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  33. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  34. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  35. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
  36. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  37. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  38. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  39. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  40. package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
  41. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  42. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  43. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
  44. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  45. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  46. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
  47. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  48. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  49. package/framework/squad-templates/backend-only.json +14 -1
  50. package/framework/squad-templates/frontend-only.json +14 -1
  51. package/framework/squad-templates/full-stack.json +25 -8
  52. package/framework/standards/STANDARDS.json +631 -86
  53. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  54. package/framework/templates/project/validate.js +122 -0
  55. package/framework/workflows/configs/zero-touch.json +7 -0
  56. package/package.json +1 -1
  57. package/src/commands/agents/dispatch-agents.js +53 -10
  58. package/src/commands/state/advance-phase.js +56 -0
  59. package/src/commands/state/index.js +2 -1
  60. package/src/commands/state/phase-runner.js +215 -0
  61. package/src/commands/tasks/task.js +23 -2
  62. package/src/core/paths/output-schema.js +1 -1
  63. package/src/lib/generators/recap-generator.js +16 -0
  64. package/src/lib/orchestration/team-orchestrator.js +171 -89
  65. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  66. package/src/lib/standards/digest-builder.js +231 -0
  67. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  68. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  69. package/src/lib/validators/validation-runner.js +2 -2
  70. package/src/utils/file-copier.js +1 -0
  71. package/src/utils/hooks-installer.js +31 -7
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 4.8.14
6
+ **Version:** 4.8.15
7
7
  **Requires:** Node.js 18+, Claude Code
8
8
 
9
9
  ---
@@ -376,4 +376,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
376
376
 
377
377
  ---
378
378
 
379
- *morph-spec v4.8.14 by [Polymorphism Tech](https://polymorphism.tech)*
379
+ *morph-spec v4.8.15 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { program } from 'commander';
4
4
  import chalk from 'chalk';
@@ -22,12 +22,13 @@ import { stateCommand } from '../src/commands/state/state.js';
22
22
  import { validatePhaseCommand } from '../src/commands/state/validate-phase.js';
23
23
  import { advancePhaseCommand } from '../src/commands/state/advance-phase.js';
24
24
  import { approveCommand, approvalStatusCommand, unapproveCommand } from '../src/commands/state/approve.js';
25
+ import { phaseRunCommand } from '../src/commands/state/phase-runner.js';
25
26
 
26
27
  // Agent commands
27
28
  import { dispatchAgentsCommand } from '../src/commands/agents/dispatch-agents.js';
28
29
 
29
30
  // Task commands
30
- import { taskDoneCommand, taskStartCommand, taskNextCommand } from '../src/commands/tasks/task.js';
31
+ import { taskDoneCommand, taskStartCommand, taskNextCommand, taskBulkDoneCommand } from '../src/commands/tasks/task.js';
31
32
 
32
33
  // Validation commands
33
34
  import { validateCommand } from './validate.js';
@@ -129,8 +130,19 @@ taskCommand
129
130
  .command('done <feature> <task-ids...>')
130
131
  .description('Mark tasks as completed (runs validation first)')
131
132
  .option('--skip-validation', 'Skip code validation (not recommended)')
133
+ .option('--dry-run', 'Show validation results without marking tasks as complete')
132
134
  .action((feature, taskIds, options) => taskDoneCommand(feature, taskIds, options));
133
135
 
136
+ taskCommand
137
+ .command('bulk-done <feature> [range]')
138
+ .description('Bulk-complete tasks (--all | --from T001 --to T053 | T001..T082)')
139
+ .option('--all', 'Complete all pending tasks')
140
+ .option('--from <id>', 'Start task ID for range')
141
+ .option('--to <id>', 'End task ID for range')
142
+ .option('--skip-validation', 'Skip code validation (not recommended)')
143
+ .option('--dry-run', 'Show validation results without marking tasks as complete')
144
+ .action((feature, range, options) => taskBulkDoneCommand(feature, range, options));
145
+
134
146
  taskCommand
135
147
  .command('start <feature> <task-id>')
136
148
  .description('Start a task (mark as in_progress)')
@@ -191,6 +203,14 @@ phaseCommand
191
203
  .option('--skip-optional', 'Skip optional phases (uiux, sync)')
192
204
  .action((feature, options) => advancePhaseCommand(feature, options));
193
205
 
206
+ phaseCommand
207
+ .command('run <feature>')
208
+ .description('Automated phase chain runner — advances phases until blocker or max reached')
209
+ .option('--dry-run', 'Show decision tree without executing')
210
+ .option('--max-phases <n>', 'Max phases to auto-advance (default: 6)', parseInt)
211
+ .option('--skip-approval', 'Skip approval gate checks')
212
+ .action((feature, options) => phaseRunCommand(feature, options));
213
+
194
214
  // Phase validation command (also available as standalone)
195
215
  program
196
216
  .command('validate-phase <feature> <phase>')
@@ -294,6 +314,7 @@ program
294
314
  .command('dispatch-agents <feature> <phase>')
295
315
  .description('Build dispatch config for parallel agent orchestration (design | tasks | implement)')
296
316
  .option('--table', 'Human-readable table output instead of JSON')
317
+ .option('--mode <mode>', 'Dispatch mode: validate (includes Tier-4 validators as read-only)')
297
318
  .option('-v, --verbose', 'Show stack trace on error')
298
319
  .action((feature, phase, options) => dispatchAgentsCommand(feature, phase, options));
299
320
 
@@ -144,13 +144,60 @@ class TaskManager {
144
144
 
145
145
  // Run validation BEFORE marking tasks as complete
146
146
  if (tasksToComplete.length > 0 && !options.skipValidation) {
147
- const validationPassed = await this.runValidation(featureName);
148
- if (!validationPassed) {
149
- console.error(chalk.red('\n Validation failed — tasks NOT marked as complete'));
150
- console.log(chalk.gray(' Fix the issues above, then run task done again'));
147
+ const validationResult = await this.runValidation(featureName);
148
+ if (options.dryRun) {
149
+ console.log(chalk.cyan('\n ℹ️ Dry-run — tasks NOT marked as complete'));
150
+ return [];
151
+ }
152
+ if (!validationResult.passed) {
153
+ // Reload state to ensure we have the latest (may have been modified by validation)
154
+ const currentState = loadState();
155
+ const currentFeature = currentState.features[featureName];
156
+ if (currentFeature) {
157
+ for (const task of tasksToComplete) {
158
+ this.persistValidationHistory(currentFeature, task.id, validationResult);
159
+ }
160
+ saveState(currentState);
161
+
162
+ // Check escalation status
163
+ const blockedTasks = tasksToComplete.filter(t => {
164
+ const hist = currentFeature.validationHistory?.[t.id];
165
+ return hist?.status === 'blocked';
166
+ });
167
+ if (blockedTasks.length > 0) {
168
+ console.error(chalk.red(`\n⛔ ESCALATION — Task(s) ${blockedTasks.map(t => t.id).join(', ')} have failed 3 times`));
169
+ console.error(chalk.red(' Human review required. Mark resolved with: morph-spec state set <feature> validationHistory.<taskId>.status passed'));
170
+ } else {
171
+ const attempt = currentFeature.validationHistory?.[tasksToComplete[0]?.id]?.attempt || 1;
172
+ console.error(chalk.red(`\n❌ Validation failed (attempt ${attempt}/3) — tasks NOT marked as complete`));
173
+ console.log(chalk.gray(' Fix the issues above, then run task done again'));
174
+ if (attempt >= 2) {
175
+ console.log(chalk.yellow(` ⚠️ Next failure will escalate to human review`));
176
+ }
177
+ }
178
+ } else {
179
+ console.error(chalk.red('\n❌ Validation failed — tasks NOT marked as complete'));
180
+ console.log(chalk.gray(' Fix the issues above, then run task done again'));
181
+ }
151
182
  console.log(chalk.gray(' Or use --skip-validation to bypass (not recommended)\n'));
152
183
  process.exit(1);
153
184
  }
185
+
186
+ // Validation passed — mark any pending history as passed
187
+ const successState = loadState();
188
+ const successFeature = successState.features[featureName];
189
+ if (successFeature) {
190
+ for (const task of tasksToComplete) {
191
+ if (successFeature.validationHistory?.[task.id]) {
192
+ successFeature.validationHistory[task.id].status = 'passed';
193
+ successFeature.validationHistory[task.id].updatedAt = new Date().toISOString();
194
+ }
195
+ }
196
+ saveState(successState);
197
+ }
198
+ } else if (options.dryRun) {
199
+ console.log(chalk.cyan('\n ℹ️ Dry-run — tasks NOT marked as complete (validation skipped)'));
200
+ return [];
154
201
  }
155
202
 
156
203
  // Breaking change detection (non-blocking warning)
@@ -242,9 +289,11 @@ class TaskManager {
242
289
  }
243
290
 
244
291
  /**
245
- * Run validation for a feature using the ValidationRunner (ESM dynamic import)
292
+ * Run validation for a feature using the ValidationRunner (ESM dynamic import).
293
+ * Returns a structured result with per-validator breakdown for validationHistory.
294
+ *
246
295
  * @param {string} featureName - Feature name
247
- * @returns {boolean} True if validation passed
296
+ * @returns {{ passed: boolean, validators: Object, passRate: number }}
248
297
  */
249
298
  async runValidation(featureName) {
250
299
  try {
@@ -254,15 +303,74 @@ class TaskManager {
254
303
  const result = await runValidation('.', featureName, { verbose: true });
255
304
 
256
305
  formatValidationResults(result);
257
- return result.passed;
306
+
307
+ // Build structured validators map from result
308
+ const validators = {};
309
+ if (result.results && typeof result.results === 'object') {
310
+ for (const [name, vResult] of Object.entries(result.results)) {
311
+ const issues = (vResult.errors || []).map(e => ({
312
+ message: typeof e === 'string' ? e : (e.message || String(e)),
313
+ file: e.file,
314
+ line: e.line,
315
+ rule: e.rule,
316
+ }));
317
+ validators[name] = {
318
+ passed: vResult.passed ?? (issues.length === 0),
319
+ issues,
320
+ };
321
+ }
322
+ } else if (!result.passed && result.errors?.length > 0) {
323
+ // Flat format fallback — wrap as single 'validation' validator
324
+ validators['validation'] = {
325
+ passed: false,
326
+ issues: result.errors.map(e => ({
327
+ message: typeof e === 'string' ? e : (e.message || String(e)),
328
+ file: e.file,
329
+ line: e.line,
330
+ rule: e.rule || 'validation',
331
+ })),
332
+ };
333
+ }
334
+
335
+ const validatorEntries = Object.values(validators);
336
+ const passRate = validatorEntries.length > 0
337
+ ? validatorEntries.filter(v => v.passed).length / validatorEntries.length
338
+ : (result.passed ? 1.0 : 0.0);
339
+
340
+ return { passed: result.passed, validators, passRate };
258
341
  } catch (error) {
259
342
  // If validation runner fails to load, warn but don't block task completion.
260
343
  // This is fail-open by design: a broken validator shouldn't block commits.
261
344
  // Common cause: missing optional deps.
262
345
  console.log(chalk.yellow(`\n⚠️ Validation skipped (${error.message})`));
263
346
  console.log(chalk.gray(' Run manually: npx morph-spec validate --verbose'));
264
- return true;
347
+ return { passed: true, validators: {}, passRate: 1.0 };
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Persist a validation result to feature.validationHistory[taskId].
353
+ * Increments attempt counter and sets status to 'failed' or 'blocked' (attempt >= 3).
354
+ *
355
+ * @param {Object} feature - Mutable feature state object
356
+ * @param {string} taskId - Task ID
357
+ * @param {{ passed: boolean, validators: Object, passRate: number }} validationResult
358
+ */
359
+ persistValidationHistory(feature, taskId, validationResult) {
360
+ if (!feature.validationHistory) {
361
+ feature.validationHistory = {};
265
362
  }
363
+ const existing = feature.validationHistory[taskId] || { attempt: 0 };
364
+ const attempt = (existing.attempt || 0) + 1;
365
+ const status = attempt >= 3 ? 'blocked' : 'failed';
366
+
367
+ feature.validationHistory[taskId] = {
368
+ attempt,
369
+ validators: validationResult.validators || {},
370
+ passRate: validationResult.passRate || 0,
371
+ status,
372
+ updatedAt: new Date().toISOString(),
373
+ };
266
374
  }
267
375
 
268
376
  /**
@@ -511,6 +619,58 @@ class TaskManager {
511
619
  console.log(chalk.blue(`▶️ Task ${taskId} started: ${task.title}`));
512
620
  }
513
621
 
622
+ /**
623
+ * Bulk-complete tasks with a single validation pass.
624
+ * @param {string} featureName
625
+ * @param {Object} opts
626
+ * @param {boolean} [opts.all] - Complete all pending tasks
627
+ * @param {string} [opts.from] - Start of range (e.g. T001)
628
+ * @param {string} [opts.to] - End of range (e.g. T053)
629
+ * @param {string} [opts.range] - Compact range string (e.g. T001..T082)
630
+ * @param {boolean} [opts.skipValidation]
631
+ * @param {boolean} [opts.dryRun]
632
+ */
633
+ async bulkCompleteTasks(featureName, opts = {}) {
634
+ const state = loadState();
635
+ const feature = state.features[featureName];
636
+ if (!feature) throw new Error(`Feature '${featureName}' not found in state.json`);
637
+
638
+ const taskList = await ensureTaskList(feature, featureName);
639
+ if (taskList.length === 0) throw new Error(`No tasks found for '${featureName}'`);
640
+
641
+ // Resolve task IDs from range options
642
+ let targetIds;
643
+ if (opts.all) {
644
+ targetIds = taskList.filter(t => t.status !== 'completed').map(t => t.id);
645
+ } else if (opts.from && opts.to) {
646
+ targetIds = this.expandRange(taskList, opts.from, opts.to);
647
+ } else if (opts.range && opts.range.includes('..')) {
648
+ const [from, to] = opts.range.split('..');
649
+ targetIds = this.expandRange(taskList, from.trim(), to.trim());
650
+ } else {
651
+ throw new Error('bulk-done requires --all, --from/--to, or a T001..T082 range argument');
652
+ }
653
+
654
+ console.log(chalk.cyan(`\n📦 Bulk-done: ${targetIds.length} task(s) targeted`));
655
+ await this.completeTasks(featureName, targetIds, { skipValidation: opts.skipValidation, dryRun: opts.dryRun });
656
+ }
657
+
658
+ /**
659
+ * Expand a task range like T001..T053 using zero-padded numeric IDs.
660
+ */
661
+ expandRange(taskList, from, to) {
662
+ const numOf = id => parseInt(id.replace(/\D/g, ''), 10);
663
+ const prefix = from.replace(/\d+$/, '');
664
+ const padLen = from.replace(prefix, '').length;
665
+ const fromNum = numOf(from);
666
+ const toNum = numOf(to);
667
+ const ids = [];
668
+ for (let n = fromNum; n <= toNum; n++) {
669
+ ids.push(prefix + String(n).padStart(padLen, '0'));
670
+ }
671
+ return ids.filter(id => taskList.some(t => t.id === id));
672
+ }
673
+
514
674
  /**
515
675
  * Get next task suggestion
516
676
  */
@@ -555,16 +715,40 @@ async function main() {
555
715
  case 'done':
556
716
  case 'complete': {
557
717
  const skipValidation = args.includes('--skip-validation');
558
- const filteredArgs = args.filter(a => a !== '--skip-validation');
718
+ const dryRun = args.includes('--dry-run');
719
+ const filteredArgs = args.filter(a => !['--skip-validation', '--dry-run'].includes(a));
559
720
  const featureName = filteredArgs[1];
560
721
  const taskIds = filteredArgs.slice(2);
561
722
 
562
723
  if (!featureName || taskIds.length === 0) {
563
- console.error(chalk.red('Usage: npx morph-spec task done <feature> <task-id> [task-id...] [--skip-validation]'));
724
+ console.error(chalk.red('Usage: npx morph-spec task done <feature> <task-id> [task-id...] [--skip-validation] [--dry-run]'));
725
+ process.exit(1);
726
+ }
727
+
728
+ await manager.completeTasks(featureName, taskIds, { skipValidation, dryRun });
729
+ break;
730
+ }
731
+
732
+ case 'bulk-done': {
733
+ const skipValidation = args.includes('--skip-validation');
734
+ const dryRun = args.includes('--dry-run');
735
+ const allFlag = args.includes('--all');
736
+ const fromIdx = args.indexOf('--from');
737
+ const toIdx = args.indexOf('--to');
738
+ const filteredArgs = args.filter(a => !['--skip-validation', '--dry-run', '--all'].includes(a)
739
+ && !a.startsWith('--from') && !a.startsWith('--to'));
740
+ const featureName = filteredArgs[1];
741
+ const rangeArg = filteredArgs[2]; // e.g. T001..T082
742
+
743
+ if (!featureName) {
744
+ console.error(chalk.red('Usage: npx morph-spec task bulk-done <feature> [--all | --from T001 --to T053 | T001..T082] [--skip-validation] [--dry-run]'));
564
745
  process.exit(1);
565
746
  }
566
747
 
567
- await manager.completeTasks(featureName, taskIds, { skipValidation });
748
+ const fromId = fromIdx !== -1 ? args[fromIdx + 1] : null;
749
+ const toId = toIdx !== -1 ? args[toIdx + 1] : null;
750
+
751
+ await manager.bulkCompleteTasks(featureName, { all: allFlag, from: fromId, to: toId, range: rangeArg, skipValidation, dryRun });
568
752
  break;
569
753
  }
570
754
 
@@ -596,9 +780,13 @@ async function main() {
596
780
  default:
597
781
  console.error(chalk.red(`Unknown command: ${command}`));
598
782
  console.log(chalk.gray('\nAvailable commands:'));
599
- console.log(chalk.gray(' done <feature> <task-id...> - Mark tasks as completed'));
600
- console.log(chalk.gray(' start <feature> <task-id> - Start a task (mark as in_progress)'));
601
- console.log(chalk.gray(' next <feature> - Show next suggested task'));
783
+ console.log(chalk.gray(' done <feature> <task-id...> - Mark tasks as completed'));
784
+ console.log(chalk.gray(' bulk-done <feature> --all - Mark all pending tasks as completed'));
785
+ console.log(chalk.gray(' bulk-done <feature> --from T001 --to T053 - Mark range as completed'));
786
+ console.log(chalk.gray(' bulk-done <feature> T001..T082 - Mark range as completed'));
787
+ console.log(chalk.gray(' start <feature> <task-id> - Start a task (mark as in_progress)'));
788
+ console.log(chalk.gray(' next <feature> - Show next suggested task'));
789
+ console.log(chalk.gray('\nFlags: --skip-validation, --dry-run'));
602
790
  process.exit(1);
603
791
  }
604
792
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "4.8.14",
3
+ "version": "4.8.15",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -200,4 +200,4 @@ These files are never edited directly. Use CLI commands or `morph-spec update` i
200
200
 
201
201
  ---
202
202
 
203
- *morph-spec v4.8.14 by Polymorphism Tech*
203
+ *morph-spec v4.8.15 by Polymorphism Tech*
@@ -203,4 +203,4 @@ morph-spec doctor
203
203
 
204
204
  ---
205
205
 
206
- *morph-spec v4.8.14 by Polymorphism Tech*
206
+ *morph-spec v4.8.15 by Polymorphism Tech*