@polymorphism-tech/morph-spec 2.2.0 → 2.4.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 +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- 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-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -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 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- 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/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- 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/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- 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/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 -0
- 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/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- 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/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- 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/CLAUDE.md +150 -442
- 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/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- 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/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- 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/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -1,224 +1,224 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Document Sharding
|
|
3
|
-
* Splits large spec.md files into manageable shards (BMAD-inspired)
|
|
4
|
-
* Achieves 90% token savings by loading only relevant sections
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import { logger } from '../utils/logger.js';
|
|
12
|
-
import { ensureDir, writeFile } from '../utils/file-copier.js';
|
|
13
|
-
|
|
14
|
-
// ============================================================================
|
|
15
|
-
// Helper Functions
|
|
16
|
-
// ============================================================================
|
|
17
|
-
|
|
18
|
-
function parseSpec(specContent) {
|
|
19
|
-
const sections = [];
|
|
20
|
-
const lines = specContent.split('\n');
|
|
21
|
-
|
|
22
|
-
let currentSection = null;
|
|
23
|
-
let currentContent = [];
|
|
24
|
-
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
// Detect level 2 heading (## Heading)
|
|
27
|
-
if (line.match(/^## /)) {
|
|
28
|
-
// Save previous section
|
|
29
|
-
if (currentSection) {
|
|
30
|
-
sections.push({
|
|
31
|
-
title: currentSection,
|
|
32
|
-
slug: toSlug(currentSection),
|
|
33
|
-
content: currentContent.join('\n').trim(),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Start new section
|
|
38
|
-
currentSection = line.replace(/^## /, '').trim();
|
|
39
|
-
currentContent = [line]; // Include heading in content
|
|
40
|
-
} else {
|
|
41
|
-
currentContent.push(line);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Save last section
|
|
46
|
-
if (currentSection) {
|
|
47
|
-
sections.push({
|
|
48
|
-
title: currentSection,
|
|
49
|
-
slug: toSlug(currentSection),
|
|
50
|
-
content: currentContent.join('\n').trim(),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return sections;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function toSlug(title) {
|
|
58
|
-
return title
|
|
59
|
-
.toLowerCase()
|
|
60
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
61
|
-
.replace(/^-|-$/g, '');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function generateIndex(sections, featureName) {
|
|
65
|
-
let index = `# ${toTitleCase(featureName)} - Technical Specification\n\n`;
|
|
66
|
-
index += `> **Sharded Spec:** This spec has been split into sections for optimal token usage\n`;
|
|
67
|
-
index += `> **BMAD Pattern:** Load only the section you need during implementation\n\n`;
|
|
68
|
-
index += `---\n\n`;
|
|
69
|
-
index += `## 📋 Table of Contents\n\n`;
|
|
70
|
-
|
|
71
|
-
sections.forEach((section, i) => {
|
|
72
|
-
index += `${i + 1}. **[${section.title}](${section.slug}.md)**\n`;
|
|
73
|
-
|
|
74
|
-
// Add first 100 chars as description
|
|
75
|
-
const firstLine = section.content.split('\n').find(l => l.trim() && !l.startsWith('#'));
|
|
76
|
-
const description = firstLine ? firstLine.substring(0, 100) + '...' : 'See section for details';
|
|
77
|
-
index += ` > ${description}\n\n`;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
index += `---\n\n`;
|
|
81
|
-
index += `## 💡 How to Use Sharded Specs\n\n`;
|
|
82
|
-
index += `### In Planning Phases (1-3)\n`;
|
|
83
|
-
index += `Load **all sections** to get complete context:\n`;
|
|
84
|
-
index += `\`\`\`bash\n`;
|
|
85
|
-
index += `# Read index.md + all section files\n`;
|
|
86
|
-
index += `\`\`\`\n\n`;
|
|
87
|
-
|
|
88
|
-
index += `### In Implementation Phase (4)\n`;
|
|
89
|
-
index += `Load **only the relevant section** for your current story:\n`;
|
|
90
|
-
index += `\`\`\`bash\n`;
|
|
91
|
-
index += `# Example: Working on entity design story\n`;
|
|
92
|
-
index += `# Read: spec/index.md + spec/entity-design.md (not the other 10 sections!)\n`;
|
|
93
|
-
index += `\`\`\`\n\n`;
|
|
94
|
-
|
|
95
|
-
index += `**Token Savings:** For a 10-section spec (30k tokens), selective loading = **90% savings**\n\n`;
|
|
96
|
-
index += `---\n\n`;
|
|
97
|
-
index += `*Generated by MORPH-SPEC Framework - Document Sharding*\n`;
|
|
98
|
-
index += `*Inspired by BMAD Method: 90% token reduction for large specs*\n`;
|
|
99
|
-
|
|
100
|
-
return index;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function toTitleCase(str) {
|
|
104
|
-
return str
|
|
105
|
-
.split('-')
|
|
106
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
107
|
-
.join(' ');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function estimateTokens(text) {
|
|
111
|
-
// Rough estimate: 1 token ≈ 4 characters
|
|
112
|
-
return Math.ceil(text.length / 4);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ============================================================================
|
|
116
|
-
// Command Function
|
|
117
|
-
// ============================================================================
|
|
118
|
-
|
|
119
|
-
export async function shardSpecCommand(feature, options) {
|
|
120
|
-
logger.header('MORPH-SPEC Document Sharding');
|
|
121
|
-
logger.dim(`Feature: ${feature}`);
|
|
122
|
-
logger.blank();
|
|
123
|
-
|
|
124
|
-
const spinner = ora('Analyzing spec.md...').start();
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
// Find spec.md
|
|
128
|
-
const specPath = path.join(process.cwd(), `.morph/project/outputs/${feature}/spec.md`);
|
|
129
|
-
if (!fs.existsSync(specPath)) {
|
|
130
|
-
throw new Error(`Spec not found: ${specPath}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Read spec content
|
|
134
|
-
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
135
|
-
const totalTokens = estimateTokens(specContent);
|
|
136
|
-
|
|
137
|
-
spinner.info(`Original spec.md: ~${totalTokens.toLocaleString()} tokens`);
|
|
138
|
-
logger.blank();
|
|
139
|
-
|
|
140
|
-
// Parse into sections
|
|
141
|
-
const sections = parseSpec(specContent);
|
|
142
|
-
|
|
143
|
-
if (sections.length < 3) {
|
|
144
|
-
spinner.warn(`Spec has only ${sections.length} sections - sharding not recommended`);
|
|
145
|
-
logger.dim(' Sharding is most beneficial for specs with 5+ sections');
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
spinner.succeed(`Detected ${sections.length} sections`);
|
|
150
|
-
logger.blank();
|
|
151
|
-
|
|
152
|
-
// Generate index
|
|
153
|
-
const indexContent = generateIndex(sections, feature);
|
|
154
|
-
|
|
155
|
-
// Calculate savings
|
|
156
|
-
const avgShardTokens = sections.reduce((sum, s) => sum + estimateTokens(s.content), 0) / sections.length;
|
|
157
|
-
const savingsPercent = Math.round(((totalTokens - avgShardTokens) / totalTokens) * 100);
|
|
158
|
-
|
|
159
|
-
logger.header('Token Savings (selective load):');
|
|
160
|
-
logger.info(` ${chalk.green(`~${savingsPercent}%`)} reduction`);
|
|
161
|
-
logger.dim(` - Full load: ~${totalTokens.toLocaleString()} tokens`);
|
|
162
|
-
logger.dim(` - Selective load: ~${Math.round(avgShardTokens).toLocaleString()} tokens (index + 1 shard)`);
|
|
163
|
-
logger.blank();
|
|
164
|
-
|
|
165
|
-
if (options.verbose) {
|
|
166
|
-
logger.header('Shard Details:');
|
|
167
|
-
logger.blank();
|
|
168
|
-
sections.forEach((section, i) => {
|
|
169
|
-
const tokens = estimateTokens(section.content);
|
|
170
|
-
logger.dim(` ${i + 1}. ${section.title}`);
|
|
171
|
-
logger.dim(` File: ${section.slug}.md`);
|
|
172
|
-
logger.dim(` Tokens: ~${tokens.toLocaleString()}`);
|
|
173
|
-
logger.blank();
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Output
|
|
178
|
-
const outputDir = path.join(process.cwd(), `.morph/project/outputs/${feature}/spec`);
|
|
179
|
-
|
|
180
|
-
if (options.dryRun) {
|
|
181
|
-
logger.header('DRY RUN - Would create:');
|
|
182
|
-
logger.dim(` ${outputDir}/index.md`);
|
|
183
|
-
sections.forEach(s => logger.dim(` ${outputDir}/${s.slug}.md`));
|
|
184
|
-
logger.blank();
|
|
185
|
-
logger.success('Run without --dry-run to create files');
|
|
186
|
-
} else {
|
|
187
|
-
const createSpinner = ora('Creating sharded spec files...').start();
|
|
188
|
-
|
|
189
|
-
// Create directory
|
|
190
|
-
await ensureDir(outputDir);
|
|
191
|
-
|
|
192
|
-
// Write index
|
|
193
|
-
await writeFile(path.join(outputDir, 'index.md'), indexContent);
|
|
194
|
-
createSpinner.text = 'Created index.md';
|
|
195
|
-
|
|
196
|
-
// Write shards
|
|
197
|
-
for (const section of sections) {
|
|
198
|
-
const shardPath = path.join(outputDir, `${section.slug}.md`);
|
|
199
|
-
await writeFile(shardPath, section.content);
|
|
200
|
-
createSpinner.text = `Created ${section.slug}.md`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
createSpinner.succeed('Sharded spec created!');
|
|
204
|
-
logger.blank();
|
|
205
|
-
|
|
206
|
-
// Archive original spec.md
|
|
207
|
-
const archivePath = specPath.replace('.md', '.original.md');
|
|
208
|
-
fs.renameSync(specPath, archivePath);
|
|
209
|
-
logger.dim(' Archived original: spec.original.md');
|
|
210
|
-
logger.blank();
|
|
211
|
-
|
|
212
|
-
logger.header('Next steps:');
|
|
213
|
-
logger.dim(` 1. Review sharded spec in .morph/project/outputs/${feature}/spec/`);
|
|
214
|
-
logger.dim(` 2. Create stories: morph-spec story create ${feature} SR-001`);
|
|
215
|
-
logger.dim(' 3. Stories will auto-load only relevant shards (90% token savings)');
|
|
216
|
-
logger.blank();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
} catch (error) {
|
|
220
|
-
spinner.fail('Sharding failed');
|
|
221
|
-
logger.error(error.message);
|
|
222
|
-
process.exit(1);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Document Sharding
|
|
3
|
+
* Splits large spec.md files into manageable shards (BMAD-inspired)
|
|
4
|
+
* Achieves 90% token savings by loading only relevant sections
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { ensureDir, writeFile } from '../utils/file-copier.js';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Helper Functions
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
function parseSpec(specContent) {
|
|
19
|
+
const sections = [];
|
|
20
|
+
const lines = specContent.split('\n');
|
|
21
|
+
|
|
22
|
+
let currentSection = null;
|
|
23
|
+
let currentContent = [];
|
|
24
|
+
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
// Detect level 2 heading (## Heading)
|
|
27
|
+
if (line.match(/^## /)) {
|
|
28
|
+
// Save previous section
|
|
29
|
+
if (currentSection) {
|
|
30
|
+
sections.push({
|
|
31
|
+
title: currentSection,
|
|
32
|
+
slug: toSlug(currentSection),
|
|
33
|
+
content: currentContent.join('\n').trim(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Start new section
|
|
38
|
+
currentSection = line.replace(/^## /, '').trim();
|
|
39
|
+
currentContent = [line]; // Include heading in content
|
|
40
|
+
} else {
|
|
41
|
+
currentContent.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Save last section
|
|
46
|
+
if (currentSection) {
|
|
47
|
+
sections.push({
|
|
48
|
+
title: currentSection,
|
|
49
|
+
slug: toSlug(currentSection),
|
|
50
|
+
content: currentContent.join('\n').trim(),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return sections;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toSlug(title) {
|
|
58
|
+
return title
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
61
|
+
.replace(/^-|-$/g, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function generateIndex(sections, featureName) {
|
|
65
|
+
let index = `# ${toTitleCase(featureName)} - Technical Specification\n\n`;
|
|
66
|
+
index += `> **Sharded Spec:** This spec has been split into sections for optimal token usage\n`;
|
|
67
|
+
index += `> **BMAD Pattern:** Load only the section you need during implementation\n\n`;
|
|
68
|
+
index += `---\n\n`;
|
|
69
|
+
index += `## 📋 Table of Contents\n\n`;
|
|
70
|
+
|
|
71
|
+
sections.forEach((section, i) => {
|
|
72
|
+
index += `${i + 1}. **[${section.title}](${section.slug}.md)**\n`;
|
|
73
|
+
|
|
74
|
+
// Add first 100 chars as description
|
|
75
|
+
const firstLine = section.content.split('\n').find(l => l.trim() && !l.startsWith('#'));
|
|
76
|
+
const description = firstLine ? firstLine.substring(0, 100) + '...' : 'See section for details';
|
|
77
|
+
index += ` > ${description}\n\n`;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
index += `---\n\n`;
|
|
81
|
+
index += `## 💡 How to Use Sharded Specs\n\n`;
|
|
82
|
+
index += `### In Planning Phases (1-3)\n`;
|
|
83
|
+
index += `Load **all sections** to get complete context:\n`;
|
|
84
|
+
index += `\`\`\`bash\n`;
|
|
85
|
+
index += `# Read index.md + all section files\n`;
|
|
86
|
+
index += `\`\`\`\n\n`;
|
|
87
|
+
|
|
88
|
+
index += `### In Implementation Phase (4)\n`;
|
|
89
|
+
index += `Load **only the relevant section** for your current story:\n`;
|
|
90
|
+
index += `\`\`\`bash\n`;
|
|
91
|
+
index += `# Example: Working on entity design story\n`;
|
|
92
|
+
index += `# Read: spec/index.md + spec/entity-design.md (not the other 10 sections!)\n`;
|
|
93
|
+
index += `\`\`\`\n\n`;
|
|
94
|
+
|
|
95
|
+
index += `**Token Savings:** For a 10-section spec (30k tokens), selective loading = **90% savings**\n\n`;
|
|
96
|
+
index += `---\n\n`;
|
|
97
|
+
index += `*Generated by MORPH-SPEC Framework - Document Sharding*\n`;
|
|
98
|
+
index += `*Inspired by BMAD Method: 90% token reduction for large specs*\n`;
|
|
99
|
+
|
|
100
|
+
return index;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function toTitleCase(str) {
|
|
104
|
+
return str
|
|
105
|
+
.split('-')
|
|
106
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
107
|
+
.join(' ');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function estimateTokens(text) {
|
|
111
|
+
// Rough estimate: 1 token ≈ 4 characters
|
|
112
|
+
return Math.ceil(text.length / 4);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Command Function
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
export async function shardSpecCommand(feature, options) {
|
|
120
|
+
logger.header('MORPH-SPEC Document Sharding');
|
|
121
|
+
logger.dim(`Feature: ${feature}`);
|
|
122
|
+
logger.blank();
|
|
123
|
+
|
|
124
|
+
const spinner = ora('Analyzing spec.md...').start();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Find spec.md
|
|
128
|
+
const specPath = path.join(process.cwd(), `.morph/project/outputs/${feature}/spec.md`);
|
|
129
|
+
if (!fs.existsSync(specPath)) {
|
|
130
|
+
throw new Error(`Spec not found: ${specPath}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Read spec content
|
|
134
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
135
|
+
const totalTokens = estimateTokens(specContent);
|
|
136
|
+
|
|
137
|
+
spinner.info(`Original spec.md: ~${totalTokens.toLocaleString()} tokens`);
|
|
138
|
+
logger.blank();
|
|
139
|
+
|
|
140
|
+
// Parse into sections
|
|
141
|
+
const sections = parseSpec(specContent);
|
|
142
|
+
|
|
143
|
+
if (sections.length < 3) {
|
|
144
|
+
spinner.warn(`Spec has only ${sections.length} sections - sharding not recommended`);
|
|
145
|
+
logger.dim(' Sharding is most beneficial for specs with 5+ sections');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
spinner.succeed(`Detected ${sections.length} sections`);
|
|
150
|
+
logger.blank();
|
|
151
|
+
|
|
152
|
+
// Generate index
|
|
153
|
+
const indexContent = generateIndex(sections, feature);
|
|
154
|
+
|
|
155
|
+
// Calculate savings
|
|
156
|
+
const avgShardTokens = sections.reduce((sum, s) => sum + estimateTokens(s.content), 0) / sections.length;
|
|
157
|
+
const savingsPercent = Math.round(((totalTokens - avgShardTokens) / totalTokens) * 100);
|
|
158
|
+
|
|
159
|
+
logger.header('Token Savings (selective load):');
|
|
160
|
+
logger.info(` ${chalk.green(`~${savingsPercent}%`)} reduction`);
|
|
161
|
+
logger.dim(` - Full load: ~${totalTokens.toLocaleString()} tokens`);
|
|
162
|
+
logger.dim(` - Selective load: ~${Math.round(avgShardTokens).toLocaleString()} tokens (index + 1 shard)`);
|
|
163
|
+
logger.blank();
|
|
164
|
+
|
|
165
|
+
if (options.verbose) {
|
|
166
|
+
logger.header('Shard Details:');
|
|
167
|
+
logger.blank();
|
|
168
|
+
sections.forEach((section, i) => {
|
|
169
|
+
const tokens = estimateTokens(section.content);
|
|
170
|
+
logger.dim(` ${i + 1}. ${section.title}`);
|
|
171
|
+
logger.dim(` File: ${section.slug}.md`);
|
|
172
|
+
logger.dim(` Tokens: ~${tokens.toLocaleString()}`);
|
|
173
|
+
logger.blank();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Output
|
|
178
|
+
const outputDir = path.join(process.cwd(), `.morph/project/outputs/${feature}/spec`);
|
|
179
|
+
|
|
180
|
+
if (options.dryRun) {
|
|
181
|
+
logger.header('DRY RUN - Would create:');
|
|
182
|
+
logger.dim(` ${outputDir}/index.md`);
|
|
183
|
+
sections.forEach(s => logger.dim(` ${outputDir}/${s.slug}.md`));
|
|
184
|
+
logger.blank();
|
|
185
|
+
logger.success('Run without --dry-run to create files');
|
|
186
|
+
} else {
|
|
187
|
+
const createSpinner = ora('Creating sharded spec files...').start();
|
|
188
|
+
|
|
189
|
+
// Create directory
|
|
190
|
+
await ensureDir(outputDir);
|
|
191
|
+
|
|
192
|
+
// Write index
|
|
193
|
+
await writeFile(path.join(outputDir, 'index.md'), indexContent);
|
|
194
|
+
createSpinner.text = 'Created index.md';
|
|
195
|
+
|
|
196
|
+
// Write shards
|
|
197
|
+
for (const section of sections) {
|
|
198
|
+
const shardPath = path.join(outputDir, `${section.slug}.md`);
|
|
199
|
+
await writeFile(shardPath, section.content);
|
|
200
|
+
createSpinner.text = `Created ${section.slug}.md`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
createSpinner.succeed('Sharded spec created!');
|
|
204
|
+
logger.blank();
|
|
205
|
+
|
|
206
|
+
// Archive original spec.md
|
|
207
|
+
const archivePath = specPath.replace('.md', '.original.md');
|
|
208
|
+
fs.renameSync(specPath, archivePath);
|
|
209
|
+
logger.dim(' Archived original: spec.original.md');
|
|
210
|
+
logger.blank();
|
|
211
|
+
|
|
212
|
+
logger.header('Next steps:');
|
|
213
|
+
logger.dim(` 1. Review sharded spec in .morph/project/outputs/${feature}/spec/`);
|
|
214
|
+
logger.dim(` 2. Create stories: morph-spec story create ${feature} SR-001`);
|
|
215
|
+
logger.dim(' 3. Stories will auto-load only relevant shards (90% token savings)');
|
|
216
|
+
logger.blank();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
} catch (error) {
|
|
220
|
+
spinner.fail('Sharding failed');
|
|
221
|
+
logger.error(error.message);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|