@polymorphism-tech/morph-spec 2.4.0 → 3.0.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 +158 -26
- package/LICENSE +72 -72
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +8 -0
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -227
- package/content/.claude/commands/morph-troubleshoot.md +122 -122
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +742 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/examples/state-v3.json +188 -188
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +158 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -426
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/templates.md +418 -418
- package/package.json +1 -1
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +351 -351
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +34 -6
- package/src/commands/detect.js +104 -104
- package/src/commands/generate-context.js +40 -0
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +513 -0
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +9 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Context Generator - Auto-generates CONTEXT.md files
|
|
3
|
+
*
|
|
4
|
+
* Populates CONTEXT.md templates with real project data from:
|
|
5
|
+
* - state.json (features, phases, tasks, checkpoints)
|
|
6
|
+
* - config.json (project settings, Azure resources, agent config)
|
|
7
|
+
* - agents.json (hierarchical agent structure)
|
|
8
|
+
* - project.md (project description)
|
|
9
|
+
* - standards/*.md (project-specific standards)
|
|
10
|
+
*
|
|
11
|
+
* @module context-generator
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs/promises';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load project state from state.json
|
|
19
|
+
* @param {string} projectPath - Root path of the project
|
|
20
|
+
* @returns {Promise<Object>} Parsed state
|
|
21
|
+
*/
|
|
22
|
+
async function loadState(projectPath) {
|
|
23
|
+
const statePath = path.join(projectPath, '.morph/state.json');
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.readFile(statePath, 'utf8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return { features: [], checkpoints: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load project config from config.json
|
|
34
|
+
* @param {string} projectPath - Root path of the project
|
|
35
|
+
* @returns {Promise<Object>} Parsed config
|
|
36
|
+
*/
|
|
37
|
+
async function loadConfig(projectPath) {
|
|
38
|
+
const configPath = path.join(projectPath, '.morph/config/config.json');
|
|
39
|
+
try {
|
|
40
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
41
|
+
return JSON.parse(content);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load agents hierarchy from agents.json
|
|
49
|
+
* @param {string} projectPath - Root path of the project
|
|
50
|
+
* @returns {Promise<Object>} Parsed agents config
|
|
51
|
+
*/
|
|
52
|
+
async function loadAgents(projectPath) {
|
|
53
|
+
const agentsPath = path.join(projectPath, 'content/.morph/config/agents.json');
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(agentsPath, 'utf8');
|
|
56
|
+
return JSON.parse(content);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Load project description from project.md
|
|
64
|
+
* @param {string} projectPath - Root path of the project
|
|
65
|
+
* @returns {Promise<string>} Project description
|
|
66
|
+
*/
|
|
67
|
+
async function loadProjectDescription(projectPath) {
|
|
68
|
+
const projectMdPath = path.join(projectPath, '.morph/project.md');
|
|
69
|
+
try {
|
|
70
|
+
return await fs.readFile(projectMdPath, 'utf8');
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Load project-specific standards
|
|
78
|
+
* @param {string} projectPath - Root path of the project
|
|
79
|
+
* @returns {Promise<Array>} Array of {name, path, description}
|
|
80
|
+
*/
|
|
81
|
+
async function loadProjectStandards(projectPath) {
|
|
82
|
+
const standardsDir = path.join(projectPath, '.morph/project/standards');
|
|
83
|
+
try {
|
|
84
|
+
const files = await fs.readdir(standardsDir);
|
|
85
|
+
return files
|
|
86
|
+
.filter(f => f.endsWith('.md'))
|
|
87
|
+
.map(f => ({
|
|
88
|
+
name: f.replace('.md', ''),
|
|
89
|
+
path: `standards/${f}`,
|
|
90
|
+
description: null
|
|
91
|
+
}));
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get nested property value from object using dot notation
|
|
99
|
+
* @param {Object} obj - Object to get value from
|
|
100
|
+
* @param {string} path - Dot-notation path (e.g., 'tasks.completed')
|
|
101
|
+
* @returns {*} Value at path or empty string
|
|
102
|
+
*/
|
|
103
|
+
function getNestedValue(obj, path) {
|
|
104
|
+
const keys = path.split('.');
|
|
105
|
+
let value = obj;
|
|
106
|
+
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
if (value && typeof value === 'object' && key in value) {
|
|
109
|
+
value = value[key];
|
|
110
|
+
} else {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return value !== null && value !== undefined ? value : '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Simple template renderer (Handlebars-like syntax)
|
|
120
|
+
* @param {string} template - Template string with {{VAR}} placeholders
|
|
121
|
+
* @param {Object} data - Data object with values
|
|
122
|
+
* @returns {string} Rendered template
|
|
123
|
+
*/
|
|
124
|
+
function renderTemplate(template, data) {
|
|
125
|
+
let result = template;
|
|
126
|
+
|
|
127
|
+
// Handle {{#each VAR}}...{{/each}} blocks first (before variable replacement)
|
|
128
|
+
result = result.replace(/{{#each\s+(\w+)}}([\s\S]*?){{\/each}}/g, (_, varName, itemTemplate) => {
|
|
129
|
+
const array = data[varName];
|
|
130
|
+
if (!Array.isArray(array) || array.length === 0) return '';
|
|
131
|
+
|
|
132
|
+
return array.map(item => {
|
|
133
|
+
let itemResult = itemTemplate;
|
|
134
|
+
|
|
135
|
+
// Replace all {{prop}} and {{nested.prop}} in the item template
|
|
136
|
+
itemResult = itemResult.replace(/{{([\w.]+)}}/g, (__, propPath) => {
|
|
137
|
+
return String(getNestedValue(item, propPath));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return itemResult;
|
|
141
|
+
}).join('');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Handle {{#if VAR}}...{{/if}} blocks
|
|
145
|
+
result = result.replace(/{{#if\s+(\w+)}}([\s\S]*?){{\/if}}/g, (_, varName, content) => {
|
|
146
|
+
return data[varName] ? content : '';
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Handle {{#unless VAR}}...{{/unless}} blocks
|
|
150
|
+
result = result.replace(/{{#unless\s+(\w+)}}([\s\S]*?){{\/unless}}/g, (_, varName, content) => {
|
|
151
|
+
const value = data[varName];
|
|
152
|
+
// Check if value is falsy, empty array, or empty object
|
|
153
|
+
const isFalsy = !value ||
|
|
154
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
155
|
+
(typeof value === 'object' && Object.keys(value).length === 0);
|
|
156
|
+
return isFalsy ? content : '';
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Replace simple {{VAR}} and {{nested.prop}} placeholders
|
|
160
|
+
result = result.replace(/{{([\w.]+)}}/g, (_, path) => {
|
|
161
|
+
return String(getNestedValue(data, path));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generate project-level CONTEXT.md
|
|
169
|
+
* @param {string} projectPath - Root path of the project
|
|
170
|
+
* @returns {Promise<string>} Generated CONTEXT.md content
|
|
171
|
+
*/
|
|
172
|
+
export async function generateProjectContext(projectPath) {
|
|
173
|
+
const [state, config, agents, projectDesc, standards] = await Promise.all([
|
|
174
|
+
loadState(projectPath),
|
|
175
|
+
loadConfig(projectPath),
|
|
176
|
+
loadAgents(projectPath),
|
|
177
|
+
loadProjectDescription(projectPath),
|
|
178
|
+
loadProjectStandards(projectPath)
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
// Load template
|
|
182
|
+
const templatePath = path.join(projectPath, 'content/.morph/templates/CONTEXT.md');
|
|
183
|
+
const template = await fs.readFile(templatePath, 'utf8');
|
|
184
|
+
|
|
185
|
+
// Build data object
|
|
186
|
+
const data = {
|
|
187
|
+
PROJECT_NAME: config?.project?.name || 'Unknown Project',
|
|
188
|
+
PROJECT_TYPE: config?.project?.type || 'blazor-server',
|
|
189
|
+
PROJECT_DESCRIPTION: projectDesc || config?.project?.description || '',
|
|
190
|
+
REPOSITORY_URL: config?.project?.repository || '',
|
|
191
|
+
TIMESTAMP: new Date().toISOString(),
|
|
192
|
+
|
|
193
|
+
// Stack detection
|
|
194
|
+
BLAZOR_ENABLED: config?.project?.type?.includes('blazor') ? true : false,
|
|
195
|
+
NEXTJS_ENABLED: config?.project?.type?.includes('nextjs') ? true : false,
|
|
196
|
+
DOTNET_VERSION: '10.0',
|
|
197
|
+
EF_VERSION: '10.0',
|
|
198
|
+
|
|
199
|
+
// Azure config
|
|
200
|
+
AZURE_LOCATION: config?.azure?.location || 'eastus2',
|
|
201
|
+
CONTAINER_APPS_ENABLED: config?.azure?.resources?.containerApps?.enabled || false,
|
|
202
|
+
CONTAINER_ENV: config?.azure?.resources?.containerApps?.environment || '',
|
|
203
|
+
SQL_ENABLED: config?.azure?.resources?.sql?.enabled || false,
|
|
204
|
+
SQL_SERVER: config?.azure?.resources?.sql?.server || '',
|
|
205
|
+
SQL_DATABASE: config?.azure?.resources?.sql?.database || '',
|
|
206
|
+
OPENAI_ENABLED: config?.azure?.resources?.openai?.enabled || false,
|
|
207
|
+
OPENAI_MODEL: config?.azure?.resources?.openai?.model || '',
|
|
208
|
+
MONITORING_ENABLED: config?.azure?.resources?.monitoring?.enabled || false,
|
|
209
|
+
APP_INSIGHTS: config?.azure?.resources?.monitoring?.appInsights || '',
|
|
210
|
+
|
|
211
|
+
// Active features (convert object to array if needed)
|
|
212
|
+
ACTIVE_FEATURES: (Array.isArray(state?.features)
|
|
213
|
+
? state.features
|
|
214
|
+
: Object.entries(state?.features || {}).map(([name, data]) => ({ name, ...data }))
|
|
215
|
+
).map(f => ({
|
|
216
|
+
name: f.name,
|
|
217
|
+
phase: f.phase,
|
|
218
|
+
status: f.status,
|
|
219
|
+
complexity: f.complexity || 'medium',
|
|
220
|
+
activeAgents: (f.activeAgents || []).join(', '),
|
|
221
|
+
description: f.description || '',
|
|
222
|
+
tasks: {
|
|
223
|
+
completed: f.tasks?.completed || 0,
|
|
224
|
+
total: f.tasks?.total || 0
|
|
225
|
+
},
|
|
226
|
+
outputs: Array.isArray(f.outputs)
|
|
227
|
+
? f.outputs.join(', ')
|
|
228
|
+
: (f.outputs ? Object.keys(f.outputs).join(', ') : '')
|
|
229
|
+
})),
|
|
230
|
+
|
|
231
|
+
// Project standards
|
|
232
|
+
PROJECT_STANDARDS: standards,
|
|
233
|
+
|
|
234
|
+
// Agents by tier
|
|
235
|
+
AGENT_COUNT: agents?.total_agents || 0,
|
|
236
|
+
TIER_1_AGENTS: Object.entries(agents?.agents || {})
|
|
237
|
+
.filter(([id, agent]) => !id.startsWith('_') && agent.tier === 1)
|
|
238
|
+
.map(([id, agent]) => ({
|
|
239
|
+
id,
|
|
240
|
+
title: agent.title,
|
|
241
|
+
always_active: agent.always_active
|
|
242
|
+
})),
|
|
243
|
+
TIER_2_AGENTS: Object.entries(agents?.agents || {})
|
|
244
|
+
.filter(([id, agent]) => !id.startsWith('_') && agent.tier === 2)
|
|
245
|
+
.map(([id, agent]) => ({
|
|
246
|
+
id,
|
|
247
|
+
title: agent.title,
|
|
248
|
+
domains: (agent.domains || []).join(', ')
|
|
249
|
+
})),
|
|
250
|
+
TIER_3_AGENTS: Object.entries(agents?.agents || {})
|
|
251
|
+
.filter(([id, agent]) => !id.startsWith('_') && agent.tier === 3)
|
|
252
|
+
.map(([id, agent]) => ({
|
|
253
|
+
id,
|
|
254
|
+
title: agent.title,
|
|
255
|
+
reports_to: agent.relationships?.reports_to || 'N/A'
|
|
256
|
+
})),
|
|
257
|
+
|
|
258
|
+
// Agent Teams config
|
|
259
|
+
AGENT_TEAMS_ENABLED: config?.agentTeams?.enabled || false,
|
|
260
|
+
AGENT_TEAMS_DISPLAY_MODE: config?.agentTeams?.displayMode || 'auto',
|
|
261
|
+
AGENT_TEAMS_THRESHOLDS: config?.agentTeams?.spawnThresholds || {},
|
|
262
|
+
|
|
263
|
+
// Recent checkpoints
|
|
264
|
+
RECENT_CHECKPOINTS: (state?.checkpoints || []).slice(-5).map(cp => ({
|
|
265
|
+
timestamp: cp.timestamp,
|
|
266
|
+
feature: cp.feature,
|
|
267
|
+
phase: cp.phase,
|
|
268
|
+
note: cp.note,
|
|
269
|
+
validation: cp.validation
|
|
270
|
+
})),
|
|
271
|
+
|
|
272
|
+
// DevOps config
|
|
273
|
+
DEVOPS_ENABLED: config?.devops ? true : false,
|
|
274
|
+
DEVOPS_ORG: config?.devops?.organization || '',
|
|
275
|
+
DEVOPS_PROJECT: config?.devops?.project || '',
|
|
276
|
+
DEVOPS_BOARDS_ENABLED: config?.devops?.boards?.enabled || false,
|
|
277
|
+
DEVOPS_AREA_PATH: config?.devops?.boards?.areaPath || '',
|
|
278
|
+
DEVOPS_WIKI_ENABLED: config?.devops?.wiki?.enabled || false,
|
|
279
|
+
DEVOPS_WIKI_NAME: config?.devops?.wiki?.wikiName || '',
|
|
280
|
+
DEVOPS_BUILD_PIPELINE: config?.devops?.pipelines?.buildPipeline || '',
|
|
281
|
+
DEVOPS_RELEASE_PIPELINE: config?.devops?.pipelines?.releasePipeline || '',
|
|
282
|
+
|
|
283
|
+
// Version
|
|
284
|
+
MORPH_VERSION: '3.0.0-hierarchical'
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return renderTemplate(template, data);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Generate feature-specific CONTEXT-{feature}.md
|
|
292
|
+
* @param {string} projectPath - Root path of the project
|
|
293
|
+
* @param {string} featureName - Feature name
|
|
294
|
+
* @returns {Promise<string>} Generated CONTEXT-{feature}.md content
|
|
295
|
+
*/
|
|
296
|
+
export async function generateFeatureContext(projectPath, featureName) {
|
|
297
|
+
const [state, agents] = await Promise.all([
|
|
298
|
+
loadState(projectPath),
|
|
299
|
+
loadAgents(projectPath)
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
// Find feature in state (handle both array and object format)
|
|
303
|
+
let feature;
|
|
304
|
+
if (Array.isArray(state?.features)) {
|
|
305
|
+
feature = state.features.find(f => f.name === featureName);
|
|
306
|
+
} else if (state?.features && typeof state.features === 'object') {
|
|
307
|
+
feature = state.features[featureName];
|
|
308
|
+
if (feature) {
|
|
309
|
+
feature = { name: featureName, ...feature };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!feature) {
|
|
314
|
+
throw new Error(`Feature "${featureName}" not found in state.json`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Load feature outputs
|
|
318
|
+
const outputsDir = path.join(projectPath, `.morph/project/outputs/${featureName}`);
|
|
319
|
+
let decisions = [];
|
|
320
|
+
let tasks = [];
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const decisionsPath = path.join(outputsDir, 'decisions.md');
|
|
324
|
+
const decisionsContent = await fs.readFile(decisionsPath, 'utf8');
|
|
325
|
+
// Parse decisions.md (simplified - just extract ADR headers)
|
|
326
|
+
const adrMatches = decisionsContent.matchAll(/##\s+ADR-(\d+):\s+(.+)/g);
|
|
327
|
+
decisions = Array.from(adrMatches).map(match => ({
|
|
328
|
+
id: match[1],
|
|
329
|
+
title: match[2],
|
|
330
|
+
status: 'Accepted',
|
|
331
|
+
date: new Date().toISOString().split('T')[0],
|
|
332
|
+
context: '',
|
|
333
|
+
decision: ''
|
|
334
|
+
}));
|
|
335
|
+
} catch (err) {
|
|
336
|
+
// No decisions.md yet
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const tasksPath = path.join(outputsDir, 'tasks.json');
|
|
341
|
+
const tasksContent = await fs.readFile(tasksPath, 'utf8');
|
|
342
|
+
tasks = JSON.parse(tasksContent).tasks || [];
|
|
343
|
+
} catch (err) {
|
|
344
|
+
// No tasks.json yet
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Load template
|
|
348
|
+
const templatePath = path.join(projectPath, 'content/.morph/templates/CONTEXT-FEATURE.md');
|
|
349
|
+
const template = await fs.readFile(templatePath, 'utf8');
|
|
350
|
+
|
|
351
|
+
// Build active agents list with hierarchy
|
|
352
|
+
const activeAgentIds = feature.activeAgents || [];
|
|
353
|
+
const activeAgents = activeAgentIds
|
|
354
|
+
.filter(id => agents?.agents[id])
|
|
355
|
+
.map(id => ({ id, ...agents.agents[id] }));
|
|
356
|
+
|
|
357
|
+
// Group by tier and build squads
|
|
358
|
+
const teamLead = activeAgents.find(a => a.tier === 1 && a.role === 'orchestrator');
|
|
359
|
+
const domainLeaders = activeAgents.filter(a => a.tier === 2 && a.role === 'domain-leader');
|
|
360
|
+
const specialists = activeAgents.filter(a => a.tier === 3 && a.role === 'specialist');
|
|
361
|
+
const validators = activeAgents.filter(a => a.tier === 4 && a.role === 'validator');
|
|
362
|
+
|
|
363
|
+
const squads = domainLeaders.map(leader => ({
|
|
364
|
+
leader: {
|
|
365
|
+
id: leader.id,
|
|
366
|
+
title: leader.title
|
|
367
|
+
},
|
|
368
|
+
members: specialists
|
|
369
|
+
.filter(s => s.relationships?.reports_to === leader.id)
|
|
370
|
+
.map(s => ({
|
|
371
|
+
id: s.id,
|
|
372
|
+
title: s.title
|
|
373
|
+
}))
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
// Build data object
|
|
377
|
+
const data = {
|
|
378
|
+
FEATURE_NAME: feature.name,
|
|
379
|
+
PHASE: feature.phase,
|
|
380
|
+
WORKFLOW_TYPE: feature.workflow || 'standard',
|
|
381
|
+
STATUS: feature.status,
|
|
382
|
+
COMPLEXITY: feature.complexity || 'medium',
|
|
383
|
+
DESCRIPTION: feature.description || '',
|
|
384
|
+
TIMESTAMP: new Date().toISOString(),
|
|
385
|
+
|
|
386
|
+
// Phase tracking
|
|
387
|
+
PHASE_DESCRIPTION: `Current phase: ${feature.phase}`,
|
|
388
|
+
COMPLETED_PHASES: [], // TODO: track phase history
|
|
389
|
+
UPCOMING_PHASES: [], // TODO: calculate remaining phases
|
|
390
|
+
|
|
391
|
+
// Tasks
|
|
392
|
+
TASKS_TOTAL: feature.tasks?.total || tasks.length,
|
|
393
|
+
TASKS_COMPLETED: feature.tasks?.completed || tasks.filter(t => t.status === 'completed').length,
|
|
394
|
+
TASKS_IN_PROGRESS: tasks.filter(t => t.status === 'in_progress').length,
|
|
395
|
+
TASKS_BLOCKED: tasks.filter(t => t.status === 'blocked').length,
|
|
396
|
+
TASKS_PROGRESS: feature.tasks?.total > 0
|
|
397
|
+
? Math.round((feature.tasks.completed / feature.tasks.total) * 100)
|
|
398
|
+
: 0,
|
|
399
|
+
CURRENT_TASK: tasks.find(t => t.status === 'in_progress') || null,
|
|
400
|
+
|
|
401
|
+
// Active agents
|
|
402
|
+
ACTIVE_AGENTS_COUNT: activeAgents.length,
|
|
403
|
+
TEAM_LEAD: teamLead ? {
|
|
404
|
+
id: teamLead.id,
|
|
405
|
+
title: teamLead.title
|
|
406
|
+
} : null,
|
|
407
|
+
SQUADS: squads,
|
|
408
|
+
VALIDATORS: validators.map(v => ({
|
|
409
|
+
id: v.id,
|
|
410
|
+
title: v.title
|
|
411
|
+
})),
|
|
412
|
+
|
|
413
|
+
// Outputs (handle both object and array format)
|
|
414
|
+
OUTPUTS: (Array.isArray(feature.outputs)
|
|
415
|
+
? feature.outputs.map(output => ({
|
|
416
|
+
type: output,
|
|
417
|
+
generated: true,
|
|
418
|
+
path: `.morph/project/outputs/${featureName}/${output}`,
|
|
419
|
+
generatedAt: null
|
|
420
|
+
}))
|
|
421
|
+
: Object.entries(feature.outputs || {}).map(([type, data]) => ({
|
|
422
|
+
type,
|
|
423
|
+
generated: data.created || false,
|
|
424
|
+
path: data.path || `.morph/project/outputs/${featureName}/${type}`,
|
|
425
|
+
generatedAt: data.createdAt || null
|
|
426
|
+
}))
|
|
427
|
+
),
|
|
428
|
+
|
|
429
|
+
// Decisions
|
|
430
|
+
DECISIONS: decisions,
|
|
431
|
+
|
|
432
|
+
// Validation
|
|
433
|
+
VALIDATION_RESULTS: feature.lastValidation?.results || null,
|
|
434
|
+
VALIDATION_PASSED: feature.lastValidation?.passed || false,
|
|
435
|
+
VALIDATION_TIMESTAMP: feature.lastValidation?.timestamp || null,
|
|
436
|
+
|
|
437
|
+
// Tasks list
|
|
438
|
+
TASKS: tasks,
|
|
439
|
+
|
|
440
|
+
// Checkpoints
|
|
441
|
+
CHECKPOINTS: (state?.checkpoints || [])
|
|
442
|
+
.filter(cp => cp.feature === featureName)
|
|
443
|
+
.map(cp => ({
|
|
444
|
+
timestamp: cp.timestamp,
|
|
445
|
+
phase: cp.phase,
|
|
446
|
+
tasksCompleted: cp.tasksCompleted,
|
|
447
|
+
tasksTotal: cp.tasksTotal,
|
|
448
|
+
note: cp.note,
|
|
449
|
+
validation: cp.validation
|
|
450
|
+
})),
|
|
451
|
+
|
|
452
|
+
// Related standards
|
|
453
|
+
RELATED_STANDARDS: [], // TODO: link standards based on activeAgents
|
|
454
|
+
|
|
455
|
+
// File paths
|
|
456
|
+
CURRENT_TASK_ID: tasks.find(t => t.status === 'in_progress')?.id || '',
|
|
457
|
+
PROPOSAL_PATH: `.morph/project/outputs/${featureName}/proposal.md`,
|
|
458
|
+
SPEC_PATH: `.morph/project/outputs/${featureName}/spec.md`,
|
|
459
|
+
CONTRACTS_PATH: `.morph/project/outputs/${featureName}/contracts.cs`,
|
|
460
|
+
TASKS_PATH: `.morph/project/outputs/${featureName}/tasks.json`,
|
|
461
|
+
DECISIONS_PATH: `.morph/project/outputs/${featureName}/decisions.md`,
|
|
462
|
+
RECAP_PATH: `.morph/project/outputs/${featureName}/recap.md`,
|
|
463
|
+
|
|
464
|
+
// Version
|
|
465
|
+
MORPH_VERSION: '3.0.0-hierarchical',
|
|
466
|
+
FEATURE_ID: feature.id || featureName
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return renderTemplate(template, data);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Write generated context to file
|
|
474
|
+
* @param {string} projectPath - Root path of the project
|
|
475
|
+
* @param {string} content - Generated content
|
|
476
|
+
* @param {string} filename - Output filename
|
|
477
|
+
* @returns {Promise<string>} Path to written file
|
|
478
|
+
*/
|
|
479
|
+
export async function writeContext(projectPath, content, filename = 'CONTEXT.md') {
|
|
480
|
+
const contextDir = path.join(projectPath, '.morph/project/context');
|
|
481
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
482
|
+
|
|
483
|
+
const outputPath = path.join(contextDir, filename);
|
|
484
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
485
|
+
|
|
486
|
+
return outputPath;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Generate and write both project and feature context files
|
|
491
|
+
* @param {string} projectPath - Root path of the project
|
|
492
|
+
* @param {string} featureName - Optional feature name (if null, only generates project context)
|
|
493
|
+
* @returns {Promise<Object>} Paths to generated files
|
|
494
|
+
*/
|
|
495
|
+
export async function generateContext(projectPath, featureName = null) {
|
|
496
|
+
const results = {};
|
|
497
|
+
|
|
498
|
+
// Always generate project context
|
|
499
|
+
const projectContext = await generateProjectContext(projectPath);
|
|
500
|
+
results.projectContext = await writeContext(projectPath, projectContext, 'CONTEXT.md');
|
|
501
|
+
|
|
502
|
+
// Generate feature context if requested
|
|
503
|
+
if (featureName) {
|
|
504
|
+
const featureContext = await generateFeatureContext(projectPath, featureName);
|
|
505
|
+
results.featureContext = await writeContext(
|
|
506
|
+
projectPath,
|
|
507
|
+
featureContext,
|
|
508
|
+
`CONTEXT-${featureName}.md`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return results;
|
|
513
|
+
}
|