@polymorphism-tech/morph-spec 4.8.19 → 4.10.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 +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HOP Composer — Higher-Order Prompt rendering and nesting
|
|
3
|
-
*
|
|
4
|
-
* Compiles Handlebars templates for agent prompts (HOPs).
|
|
5
|
-
* Supports:
|
|
6
|
-
* - renderHOP(templatePath, variables) → compiled prompt string
|
|
7
|
-
* - composeHOPs(hopChain[], variables) → nested HOPs (outer wraps inner)
|
|
8
|
-
* - listAvailableHOPs() → scan meta-prompts/ and return HOPDescriptor[]
|
|
9
|
-
*
|
|
10
|
-
* Standard HOP variables auto-injected:
|
|
11
|
-
* FEATURE_NAME, AGENT_ID, MISSION, SPEC_SUMMARY, STANDARDS, TASKS,
|
|
12
|
-
* DELIVERABLES, CONSTRAINTS, DATE, TIMESTAMP
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
16
|
-
import { join, relative, extname, basename } from 'path';
|
|
17
|
-
|
|
18
|
-
const META_PROMPTS_DIR = join(process.cwd(), 'framework/templates/meta-prompts');
|
|
19
|
-
const REGISTRY_PATH = join(process.cwd(), 'framework/templates/REGISTRY.json');
|
|
20
|
-
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Handlebars-Compatible Mini-Compiler
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Compile a Handlebars template with variables (subset of Handlebars features)
|
|
27
|
-
* Supports: {{variable}}, {{#if condition}}...{{/if}}, {{#each array}}...{{/each}}
|
|
28
|
-
* Also supports built-in helpers: {{pascalCase}}, {{camelCase}}, {{snakeCase}}
|
|
29
|
-
*
|
|
30
|
-
* @param {string} template - Handlebars template string
|
|
31
|
-
* @param {Object} variables - Variables to inject
|
|
32
|
-
* @returns {string} Compiled template
|
|
33
|
-
*/
|
|
34
|
-
export function compileTemplate(template, variables) {
|
|
35
|
-
const vars = {
|
|
36
|
-
DATE: new Date().toISOString().split('T')[0],
|
|
37
|
-
TIMESTAMP: new Date().toISOString(),
|
|
38
|
-
...injectCasedHelpers(variables),
|
|
39
|
-
...variables
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
let result = template;
|
|
43
|
-
|
|
44
|
-
// 1. Process {{#if VAR}}...{{/if}} blocks
|
|
45
|
-
result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
|
|
46
|
-
return vars[key] ? content : '';
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// 2. Process {{#each ARRAY}}...{{/each}} blocks
|
|
50
|
-
result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
|
|
51
|
-
const arr = vars[key];
|
|
52
|
-
if (!Array.isArray(arr)) return '';
|
|
53
|
-
return arr.map(item => {
|
|
54
|
-
const itemVars = typeof item === 'object' ? { ...vars, ...item, this: item } : { ...vars, this: item };
|
|
55
|
-
return compileTemplate(content, itemVars);
|
|
56
|
-
}).join('');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// 3. Process helper calls: {{pascalCase VARIABLE}}, {{camelCase VARIABLE}}, etc.
|
|
60
|
-
result = result.replace(/\{\{(pascalCase|camelCase|snakeCase|titleCase|pluralize)\s+(\w+)\}\}/g, (_, helper, key) => {
|
|
61
|
-
const value = String(vars[key] || '');
|
|
62
|
-
return applyHelper(helper, value);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// 4. Process simple {{VARIABLE}} substitutions
|
|
66
|
-
result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
67
|
-
return vars[key] !== undefined ? String(vars[key]) : `{{${key}}}`;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function injectCasedHelpers(variables) {
|
|
74
|
-
const extras = {};
|
|
75
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
76
|
-
if (typeof value === 'string') {
|
|
77
|
-
extras[`${key}_PASCAL`] = applyHelper('pascalCase', value);
|
|
78
|
-
extras[`${key}_CAMEL`] = applyHelper('camelCase', value);
|
|
79
|
-
extras[`${key}_SNAKE`] = applyHelper('snakeCase', value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return extras;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function applyHelper(helper, value) {
|
|
86
|
-
const words = value.replace(/[-_\s]+/g, ' ').trim().split(' ');
|
|
87
|
-
switch (helper) {
|
|
88
|
-
case 'pascalCase':
|
|
89
|
-
return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
90
|
-
case 'camelCase':
|
|
91
|
-
return words[0].toLowerCase() + words.slice(1).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
92
|
-
case 'snakeCase':
|
|
93
|
-
return value.replace(/[-\s]+/g, '_').replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
|
|
94
|
-
case 'titleCase':
|
|
95
|
-
return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
|
96
|
-
case 'pluralize':
|
|
97
|
-
const last = value.slice(-1);
|
|
98
|
-
if (last === 'y') return value.slice(0, -1) + 'ies';
|
|
99
|
-
if (last === 's') return value;
|
|
100
|
-
return value + 's';
|
|
101
|
-
default:
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// HOP Rendering
|
|
108
|
-
// ============================================================================
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Render a HOP template with variables
|
|
112
|
-
* @param {string} templatePath - Path to template (relative to meta-prompts/ or absolute)
|
|
113
|
-
* @param {Object} variables - Variables to inject
|
|
114
|
-
* @returns {string} Rendered prompt string
|
|
115
|
-
*/
|
|
116
|
-
export function renderHOP(templatePath, variables = {}) {
|
|
117
|
-
// Resolve template path
|
|
118
|
-
const paths = [
|
|
119
|
-
templatePath,
|
|
120
|
-
join(META_PROMPTS_DIR, templatePath),
|
|
121
|
-
join(META_PROMPTS_DIR, templatePath + '.md'),
|
|
122
|
-
join(process.cwd(), templatePath)
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
let resolved = null;
|
|
126
|
-
for (const p of paths) {
|
|
127
|
-
if (existsSync(p)) {
|
|
128
|
-
resolved = p;
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!resolved) {
|
|
134
|
-
throw new Error(`HOP template not found: ${templatePath}\nSearched: ${paths.join(', ')}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const template = readFileSync(resolved, 'utf8');
|
|
138
|
-
return compileTemplate(template, variables);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Compose nested HOPs (outer wraps inner, variables bubble down)
|
|
143
|
-
* @param {Array} hopChain - Array of { templatePath, variables } objects (outer first)
|
|
144
|
-
* @param {Object} [sharedVariables] - Variables shared across all HOPs
|
|
145
|
-
* @returns {string} Composed prompt string
|
|
146
|
-
*/
|
|
147
|
-
export function composeHOPs(hopChain, sharedVariables = {}) {
|
|
148
|
-
if (!hopChain || hopChain.length === 0) {
|
|
149
|
-
throw new Error('hopChain must be a non-empty array');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (hopChain.length === 1) {
|
|
153
|
-
return renderHOP(hopChain[0].templatePath, { ...sharedVariables, ...hopChain[0].variables });
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Render inner HOPs first, then wrap with outer
|
|
157
|
-
let innerContent = '';
|
|
158
|
-
for (let i = hopChain.length - 1; i >= 1; i--) {
|
|
159
|
-
const hop = hopChain[i];
|
|
160
|
-
const vars = { ...sharedVariables, ...hop.variables, INNER_PROMPT: innerContent };
|
|
161
|
-
innerContent = renderHOP(hop.templatePath, vars);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Render outermost HOP with inner content
|
|
165
|
-
const outerHop = hopChain[0];
|
|
166
|
-
return renderHOP(outerHop.templatePath, {
|
|
167
|
-
...sharedVariables,
|
|
168
|
-
...outerHop.variables,
|
|
169
|
-
INNER_PROMPT: innerContent
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ============================================================================
|
|
174
|
-
// HOP Discovery
|
|
175
|
-
// ============================================================================
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* List all available HOPs from the meta-prompts directory
|
|
179
|
-
* @returns {Array} Array of HOPDescriptor objects
|
|
180
|
-
*/
|
|
181
|
-
export function listAvailableHOPs() {
|
|
182
|
-
if (!existsSync(META_PROMPTS_DIR)) return [];
|
|
183
|
-
|
|
184
|
-
const hops = [];
|
|
185
|
-
scanDir(META_PROMPTS_DIR, META_PROMPTS_DIR, hops);
|
|
186
|
-
return hops;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function scanDir(dir, baseDir, hops) {
|
|
190
|
-
if (!existsSync(dir)) return;
|
|
191
|
-
|
|
192
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
193
|
-
const fullPath = join(dir, entry.name);
|
|
194
|
-
if (entry.isDirectory()) {
|
|
195
|
-
scanDir(fullPath, baseDir, hops);
|
|
196
|
-
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.hbs')) {
|
|
197
|
-
const relPath = relative(baseDir, fullPath);
|
|
198
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
199
|
-
const firstLine = content.split('\n')[0] || '';
|
|
200
|
-
const description = firstLine.startsWith('#')
|
|
201
|
-
? firstLine.replace(/^#+\s*/, '')
|
|
202
|
-
: 'Higher-Order Prompt template';
|
|
203
|
-
|
|
204
|
-
// Extract variables from template
|
|
205
|
-
const variables = [...new Set(
|
|
206
|
-
(content.match(/\{\{(\w+)\}\}/g) || [])
|
|
207
|
-
.map(m => m.replace(/\{\{|\}\}/g, ''))
|
|
208
|
-
.filter(v => !['if', 'each', 'this', 'else', 'pascalCase', 'camelCase', 'snakeCase', 'titleCase', 'pluralize'].includes(v))
|
|
209
|
-
)];
|
|
210
|
-
|
|
211
|
-
hops.push({
|
|
212
|
-
id: relPath.replace(/\\/g, '/').replace(/\.(md|hbs)$/, ''),
|
|
213
|
-
path: fullPath,
|
|
214
|
-
relativePath: relPath.replace(/\\/g, '/'),
|
|
215
|
-
description,
|
|
216
|
-
variables,
|
|
217
|
-
category: relPath.split(/[\\/]/)[0]
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Resolver — Resolves active agents for a given phase
|
|
3
|
-
*
|
|
4
|
-
* Reads .morph/framework/agents.json (installed copy) or falls back to
|
|
5
|
-
* framework/agents.json (source). Filters agents by phase and VSA config.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
|
|
11
|
-
const AGENTS_PATHS = [
|
|
12
|
-
'.morph/framework/agents.json',
|
|
13
|
-
'framework/agents.json',
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Load agents.json from the project or framework source.
|
|
18
|
-
*
|
|
19
|
-
* @param {string} [cwd]
|
|
20
|
-
* @returns {Object|null}
|
|
21
|
-
*/
|
|
22
|
-
export function loadAgentsJson(cwd = process.cwd()) {
|
|
23
|
-
for (const rel of AGENTS_PATHS) {
|
|
24
|
-
const full = join(cwd, rel);
|
|
25
|
-
if (existsSync(full)) {
|
|
26
|
-
try {
|
|
27
|
-
return JSON.parse(readFileSync(full, 'utf-8'));
|
|
28
|
-
} catch {
|
|
29
|
-
// Try next path
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Determine which agents are active for the given phase.
|
|
38
|
-
*
|
|
39
|
-
* Rules:
|
|
40
|
-
* - always_active: true → always included
|
|
41
|
-
* - Agents with phase-scoped keywords matching the current phase
|
|
42
|
-
* - VSA architecture: include vsa-architect, exclude domain-architect in phase-setup
|
|
43
|
-
* - Tier-4 validators are always included in implement phase
|
|
44
|
-
*
|
|
45
|
-
* @param {string} [cwd]
|
|
46
|
-
* @param {string} [phase]
|
|
47
|
-
* @returns {{ tier1: Agent[], tier2: Agent[], tier3: Agent[], tier4: Agent[] }}
|
|
48
|
-
*/
|
|
49
|
-
export function getActiveAgents(cwd = process.cwd(), phase = '') {
|
|
50
|
-
const data = loadAgentsJson(cwd);
|
|
51
|
-
if (!data?.agents) {
|
|
52
|
-
return { tier1: [], tier2: [], tier3: [], tier4: [] };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Detect VSA config
|
|
56
|
-
let isVsa = false;
|
|
57
|
-
try {
|
|
58
|
-
const configPath = join(cwd, '.morph/config/config.json');
|
|
59
|
-
if (existsSync(configPath)) {
|
|
60
|
-
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
61
|
-
isVsa = cfg?.config?.architecture?.style === 'vertical-slice';
|
|
62
|
-
}
|
|
63
|
-
} catch {
|
|
64
|
-
// Non-blocking
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Phase → active domains heuristic
|
|
68
|
-
const PHASE_DOMAINS = {
|
|
69
|
-
proposal: ['architecture', 'developer-experience', 'ai-orchestration'],
|
|
70
|
-
setup: ['architecture', 'developer-experience', 'ai-orchestration'],
|
|
71
|
-
design: ['architecture', 'dotnet', 'frontend', 'ai-orchestration'],
|
|
72
|
-
uiux: ['frontend', 'ui', 'architecture'],
|
|
73
|
-
clarify: ['architecture', 'developer-experience'],
|
|
74
|
-
tasks: ['architecture', 'dotnet', 'frontend', 'testing'],
|
|
75
|
-
implement: ['dotnet', 'frontend', 'testing', 'devops', 'blazor'],
|
|
76
|
-
sync: ['architecture', 'developer-experience'],
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const activeDomains = new Set(PHASE_DOMAINS[phase] || Object.values(PHASE_DOMAINS).flat());
|
|
80
|
-
|
|
81
|
-
const result = { tier1: [], tier2: [], tier3: [], tier4: [] };
|
|
82
|
-
|
|
83
|
-
for (const [name, agent] of Object.entries(data.agents)) {
|
|
84
|
-
if (name.startsWith('_comment')) continue;
|
|
85
|
-
|
|
86
|
-
const tier = agent.tier;
|
|
87
|
-
if (!tier || tier < 1 || tier > 4) continue;
|
|
88
|
-
|
|
89
|
-
const tierKey = `tier${tier}`;
|
|
90
|
-
|
|
91
|
-
// Always-active agents
|
|
92
|
-
if (agent.always_active === true) {
|
|
93
|
-
// VSA: swap domain-architect for vsa-architect
|
|
94
|
-
if (name === 'domain-architect' && isVsa) continue;
|
|
95
|
-
result[tierKey].push({ name, ...agent });
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// VSA architect: only active when VSA config is set
|
|
100
|
-
if (name === 'vsa-architect') {
|
|
101
|
-
if (isVsa && (phase === 'setup' || phase === 'design')) {
|
|
102
|
-
result[tierKey].push({ name, ...agent });
|
|
103
|
-
}
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Tier-4 validators: active in implement phase
|
|
108
|
-
if (tier === 4) {
|
|
109
|
-
if (phase === 'implement' || phase === 'sync') {
|
|
110
|
-
result[tierKey].push({ name, ...agent });
|
|
111
|
-
}
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Domain-based activation
|
|
116
|
-
const agentDomains = agent.domains || [];
|
|
117
|
-
const isActive = agentDomains.some(d => activeDomains.has(d));
|
|
118
|
-
if (isActive) {
|
|
119
|
-
result[tierKey].push({ name, ...agent });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get icon for an agent based on its teammate config or tier.
|
|
128
|
-
*
|
|
129
|
-
* @param {Object} agent
|
|
130
|
-
* @returns {string}
|
|
131
|
-
*/
|
|
132
|
-
export function getAgentIcon(agent) {
|
|
133
|
-
return agent.teammate?.icon || '';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Count total active agents across all tiers.
|
|
138
|
-
*
|
|
139
|
-
* @param {{ tier1: any[], tier2: any[], tier3: any[], tier4: any[] }} agentsByTier
|
|
140
|
-
* @returns {number}
|
|
141
|
-
*/
|
|
142
|
-
export function countActiveAgents(agentsByTier) {
|
|
143
|
-
return Object.values(agentsByTier).flat().length;
|
|
144
|
-
}
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Monitor Renderer — TUI rendering utilities for `morph-spec monitor`
|
|
3
|
-
*
|
|
4
|
-
* Uses chalk (already a project dependency) + native box-drawing characters.
|
|
5
|
-
* No additional dependencies required.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
|
|
10
|
-
// Box-drawing characters
|
|
11
|
-
const BOX = {
|
|
12
|
-
tl: '┌', tr: '┐', bl: '└', br: '┘',
|
|
13
|
-
h: '─', v: '│',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const TIER_ICONS = {
|
|
17
|
-
1: '🎯',
|
|
18
|
-
2: '⚙️ ',
|
|
19
|
-
3: '🔧',
|
|
20
|
-
4: '✅',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const EVENT_COLORS = {
|
|
24
|
-
SessionStart: 'cyan',
|
|
25
|
-
UserPromptSubmit: 'yellow',
|
|
26
|
-
PreToolUse: 'magenta',
|
|
27
|
-
PostToolUse: 'blue',
|
|
28
|
-
PostToolUseFailure: 'red',
|
|
29
|
-
Stop: 'gray',
|
|
30
|
-
PreCompact: 'gray',
|
|
31
|
-
Notification: 'white',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Render a bordered box with a title and lines of content.
|
|
36
|
-
*
|
|
37
|
-
* @param {string[]} lines - Content lines
|
|
38
|
-
* @param {string} color - chalk color name ('green'|'blue'|'yellow'|'magenta'|'cyan'|'red'|'white')
|
|
39
|
-
* @param {string} [title] - Optional title shown in top border
|
|
40
|
-
* @param {number} [width] - Box width (default: 70)
|
|
41
|
-
* @returns {string}
|
|
42
|
-
*/
|
|
43
|
-
export function renderBox(lines, color, title = '', width = 70) {
|
|
44
|
-
const paint = chalk[color] || chalk.white;
|
|
45
|
-
const innerWidth = width - 2;
|
|
46
|
-
|
|
47
|
-
// Top border with optional title
|
|
48
|
-
let topBorder;
|
|
49
|
-
if (title) {
|
|
50
|
-
const titleText = ` ${title} `;
|
|
51
|
-
const remaining = innerWidth - titleText.length;
|
|
52
|
-
const left = Math.max(0, Math.floor(remaining / 2));
|
|
53
|
-
const right = Math.max(0, remaining - left);
|
|
54
|
-
topBorder = paint(BOX.tl + BOX.h.repeat(left) + titleText + BOX.h.repeat(right) + BOX.tr);
|
|
55
|
-
} else {
|
|
56
|
-
topBorder = paint(BOX.tl + BOX.h.repeat(innerWidth) + BOX.tr);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const bottomBorder = paint(BOX.bl + BOX.h.repeat(innerWidth) + BOX.br);
|
|
60
|
-
|
|
61
|
-
const contentLines = lines.map(line => {
|
|
62
|
-
const truncated = line.length > innerWidth - 2
|
|
63
|
-
? line.slice(0, innerWidth - 5) + '...'
|
|
64
|
-
: line;
|
|
65
|
-
const padded = truncated.padEnd(innerWidth - 2);
|
|
66
|
-
return paint(BOX.v) + ' ' + padded + paint(BOX.v);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return [topBorder, ...contentLines, bottomBorder].join('\n');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Render the chronological hook event feed.
|
|
74
|
-
*
|
|
75
|
-
* @param {Array<{name: string, event: string, result: string, ts: string}>} hooks
|
|
76
|
-
* @param {number} [maxItems] - Max events to show (most recent first)
|
|
77
|
-
* @returns {string}
|
|
78
|
-
*/
|
|
79
|
-
export function renderFeed(hooks, maxItems = 12) {
|
|
80
|
-
if (!hooks || hooks.length === 0) {
|
|
81
|
-
return renderBox([' No hook events yet this session.'], 'blue', '🪝 HOOK EVENTS');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const recent = [...hooks].reverse().slice(0, maxItems);
|
|
85
|
-
const lines = recent.map(h => {
|
|
86
|
-
const color = EVENT_COLORS[h.event] || 'white';
|
|
87
|
-
const eventLabel = chalk[color] ? chalk[color](h.event) : h.event;
|
|
88
|
-
return `${h.ts} ${chalk.bold(h.name)} ${eventLabel} → ${h.result}`;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return renderBox(lines, 'blue', '🪝 HOOK EVENTS');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Render the agents view, grouped by tier.
|
|
96
|
-
*
|
|
97
|
-
* @param {Object} agentsByTier - { tier1: [...], tier2: [...], tier3: [...], tier4: [...] }
|
|
98
|
-
* @param {string} phase
|
|
99
|
-
* @returns {string}
|
|
100
|
-
*/
|
|
101
|
-
export function renderAgents(agentsByTier, phase) {
|
|
102
|
-
const lines = [`Phase: ${chalk.bold(phase || 'unknown')}`];
|
|
103
|
-
lines.push('');
|
|
104
|
-
|
|
105
|
-
for (const [tierKey, agents] of Object.entries(agentsByTier)) {
|
|
106
|
-
const tierNum = parseInt(tierKey.replace('tier', ''));
|
|
107
|
-
const icon = TIER_ICONS[tierNum] || ' ';
|
|
108
|
-
if (!agents || agents.length === 0) continue;
|
|
109
|
-
const names = agents.map(a => a.name).join(' · ');
|
|
110
|
-
lines.push(`T${tierNum} ${icon} ${names}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (lines.length <= 2) {
|
|
114
|
-
lines.push(' No active agents for this phase.');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const total = Object.values(agentsByTier).flat().length;
|
|
118
|
-
return renderBox(lines, 'magenta', `🤖 AGENTS ATIVOS (${total} de 39)`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Render the skills view.
|
|
123
|
-
*
|
|
124
|
-
* @param {Array<{name: string, ts: string}>} skills - Skills invoked this session
|
|
125
|
-
* @param {string[]} [available] - Available skill names
|
|
126
|
-
* @returns {string}
|
|
127
|
-
*/
|
|
128
|
-
export function renderSkills(skills, available = []) {
|
|
129
|
-
const lines = [];
|
|
130
|
-
|
|
131
|
-
if (skills && skills.length > 0) {
|
|
132
|
-
lines.push('Invocadas nesta sessão:');
|
|
133
|
-
for (const s of [...skills].reverse()) {
|
|
134
|
-
lines.push(` ${s.ts} 🎯 ${s.name}`);
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
lines.push(' Nenhuma skill invocada nesta sessão.');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (available.length > 0) {
|
|
141
|
-
lines.push('');
|
|
142
|
-
lines.push(`Disponíveis: ${available.slice(0, 8).join(', ')}${available.length > 8 ? ` (+${available.length - 8})` : ''}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return renderBox(lines, 'yellow', '🎯 SKILLS');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Render the rules view.
|
|
150
|
-
*
|
|
151
|
-
* @param {Array<{name: string, glob: string}>} rules
|
|
152
|
-
* @returns {string}
|
|
153
|
-
*/
|
|
154
|
-
export function renderRules(rules) {
|
|
155
|
-
if (!rules || rules.length === 0) {
|
|
156
|
-
return renderBox([' No rules found.'], 'cyan', '📏 RULES ATIVAS');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const lines = rules.map(r => ` ${chalk.bold(r.name).padEnd(35)} ${chalk.gray(r.glob || '*')}`);
|
|
160
|
-
return renderBox(lines, 'cyan', `📏 RULES ATIVAS (${rules.length})`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Render the overview (compact summary of all views).
|
|
165
|
-
*
|
|
166
|
-
* @param {Object} data - { feature, phase, agentsByTier, hooks, skills, rules }
|
|
167
|
-
* @returns {string}
|
|
168
|
-
*/
|
|
169
|
-
export function renderOverview(data) {
|
|
170
|
-
const { feature, phase, agentsByTier, hooks, skills, rules } = data;
|
|
171
|
-
const totalAgents = Object.values(agentsByTier || {}).flat().length;
|
|
172
|
-
const lines = [];
|
|
173
|
-
|
|
174
|
-
lines.push(`Feature: ${chalk.bold.green(feature || '(none)')} | Phase: ${chalk.bold(phase || 'unknown')}`);
|
|
175
|
-
lines.push('');
|
|
176
|
-
lines.push(`🤖 Agents ativos: ${totalAgents} de 39`);
|
|
177
|
-
|
|
178
|
-
// Tier breakdown
|
|
179
|
-
for (const [tierKey, agents] of Object.entries(agentsByTier || {})) {
|
|
180
|
-
if (!agents || agents.length === 0) continue;
|
|
181
|
-
const tierNum = parseInt(tierKey.replace('tier', ''));
|
|
182
|
-
lines.push(` T${tierNum}: ${agents.map(a => a.name).join(', ')}`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
lines.push('');
|
|
186
|
-
lines.push(`🪝 Hooks disparados: ${hooks?.length || 0}`);
|
|
187
|
-
if (hooks && hooks.length > 0) {
|
|
188
|
-
const last = hooks[hooks.length - 1];
|
|
189
|
-
lines.push(` Último: ${last.name} (${last.ts})`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
lines.push('');
|
|
193
|
-
lines.push(`🎯 Skills invocadas: ${skills?.length || 0}`);
|
|
194
|
-
lines.push(`📏 Rules ativas: ${rules?.length || 0}`);
|
|
195
|
-
|
|
196
|
-
return renderBox(lines, 'green', '● MORPH-SPEC OVERVIEW');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Render the bottom status bar.
|
|
201
|
-
*
|
|
202
|
-
* @param {string} mode - Current view mode
|
|
203
|
-
* @param {string[]} modes - All available modes
|
|
204
|
-
* @returns {string}
|
|
205
|
-
*/
|
|
206
|
-
export function renderStatusBar(mode, modes) {
|
|
207
|
-
const modeList = modes.map(m =>
|
|
208
|
-
m === mode ? chalk.bold.white.bgBlue(` ${m} `) : chalk.gray(` ${m} `)
|
|
209
|
-
).join(' ');
|
|
210
|
-
|
|
211
|
-
return '\n' + modeList + chalk.gray(' shift+tab: cycle | q: quit');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Render the feature header (top of screen).
|
|
216
|
-
*
|
|
217
|
-
* @param {string} feature
|
|
218
|
-
* @param {string} phase
|
|
219
|
-
* @param {Object} tasks - { completed, total }
|
|
220
|
-
* @returns {string}
|
|
221
|
-
*/
|
|
222
|
-
export function renderHeader(feature, phase, tasks) {
|
|
223
|
-
const taskInfo = tasks && tasks.total > 0
|
|
224
|
-
? ` [${phase} ${tasks.completed}/${tasks.total}]`
|
|
225
|
-
: ` [${phase}]`;
|
|
226
|
-
const title = feature
|
|
227
|
-
? chalk.bold.green(`● Watching ${feature}${taskInfo}`)
|
|
228
|
-
: chalk.bold.yellow('● morph-spec monitor — no active feature');
|
|
229
|
-
return title + '\n';
|
|
230
|
-
}
|