specweave 1.0.31 → 1.0.33
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-plugin/marketplace.json +1 -1
- package/CLAUDE.md +205 -148
- package/README.md +0 -2
- package/bin/specweave.js +11 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts +16 -0
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
- package/dist/src/cli/commands/update-instructions.js +134 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +163 -33
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +2 -1
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +3 -1
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
- package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
- package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
- package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/index.js +15 -0
- package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
- package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/tools/index.d.ts +11 -0
- package/dist/src/core/tools/index.d.ts.map +1 -0
- package/dist/src/core/tools/index.js +10 -0
- package/dist/src/core/tools/index.js.map +1 -0
- package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
- package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
- package/dist/src/core/tools/tool-event-bus.js +84 -0
- package/dist/src/core/tools/tool-event-bus.js.map +1 -0
- package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
- package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
- package/dist/src/core/tools/tool-index-builder.js +289 -0
- package/dist/src/core/tools/tool-index-builder.js.map +1 -0
- package/dist/src/core/tools/tool-registry.d.ts +51 -0
- package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
- package/dist/src/core/tools/tool-registry.js +224 -0
- package/dist/src/core/tools/tool-registry.js.map +1 -0
- package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
- package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
- package/dist/src/core/tools/tool-search-engine.js +174 -0
- package/dist/src/core/tools/tool-search-engine.js.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.js +7 -0
- package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +3 -13
- package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
- package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
- package/plugins/specweave/hooks/v2/session-end.sh +3 -1
- package/plugins/specweave/hooks/v2/session-start.sh +3 -1
- package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
- package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
- package/plugins/specweave-mobile/README.md +55 -35
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
- package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
- package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
- package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
- package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
- package/plugins/specweave-release/commands/npm.md +61 -17
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
- package/src/templates/AGENTS.md.template +34 -0
- package/src/templates/CLAUDE.md.template +121 -155
- package/plugins/specweave/hooks/config-env-separator.sh +0 -99
- package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
- package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
- package/plugins/specweave/hooks/lib/logging.sh +0 -231
- package/plugins/specweave/hooks/lib/metrics.sh +0 -347
- package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
- package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
- package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Living Docs Scaffold - Creates documentation structure for user projects
|
|
3
|
+
*
|
|
4
|
+
* This module creates the .specweave/docs/ directory structure for ANY user project.
|
|
5
|
+
* It's project-agnostic and uses templates with variable substitution.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: This runs during `specweave init` to set up the docs structure.
|
|
8
|
+
*
|
|
9
|
+
* @module core/living-docs/scaffolding/scaffold
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { consoleLogger } from '../../../utils/logger.js';
|
|
14
|
+
import { TemplateEngine, createDefaultContext } from './template-engine.js';
|
|
15
|
+
/**
|
|
16
|
+
* Living Docs directory structure
|
|
17
|
+
* This is the canonical structure for all user projects.
|
|
18
|
+
*/
|
|
19
|
+
const DOCS_STRUCTURE = [
|
|
20
|
+
{
|
|
21
|
+
path: 'internal',
|
|
22
|
+
readme: '# Internal Documentation\n\nInternal project documentation - not for public consumption.\n',
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
path: 'specs',
|
|
26
|
+
readme: '# Specifications\n\nFeature specifications and user stories.\n\nEach project has its own folder: `{project}/FS-XXX/`\n',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: 'architecture',
|
|
30
|
+
readme: '# Architecture Documentation\n\nSystem architecture, design decisions, and technical documentation.\n',
|
|
31
|
+
children: [
|
|
32
|
+
{
|
|
33
|
+
path: 'adr',
|
|
34
|
+
readme: '# Architecture Decision Records\n\nDocumented architecture decisions using ADR format.\n\nFormat: `XXXX-decision-title.md` (4-digit number, NO adr- prefix)\n',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: 'diagrams',
|
|
38
|
+
readme: '# Architecture Diagrams\n\nMermaid diagrams for system architecture visualization.\n',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: 'modules',
|
|
44
|
+
readme: '# Module Documentation\n\nPer-module documentation generated from codebase analysis.\n',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: 'organization',
|
|
48
|
+
readme: '# Organization Structure\n\nTeam structure, microservices, and domain groupings.\n',
|
|
49
|
+
children: [
|
|
50
|
+
{ path: 'teams' },
|
|
51
|
+
{ path: 'microservices' },
|
|
52
|
+
{ path: 'domains' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
path: 'delivery',
|
|
57
|
+
readme: '# Delivery Documentation\n\nRelease history, CI/CD pipelines, and deployment guides.\n',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
path: 'operations',
|
|
61
|
+
readme: '# Operations Documentation\n\nRunbooks, monitoring, and operational procedures.\n',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: 'strategy',
|
|
65
|
+
readme: '# Strategy Documentation\n\nProduct requirements, business cases, and strategic planning.\n',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
path: 'review-needed',
|
|
69
|
+
readme: '# Review Needed\n\nIssues and inconsistencies that need attention.\n',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: 'public',
|
|
75
|
+
readme: '# Public Documentation\n\nUser-facing documentation.\n',
|
|
76
|
+
children: [
|
|
77
|
+
{
|
|
78
|
+
path: 'overview',
|
|
79
|
+
readme: '# Project Overview\n\nHigh-level project documentation and getting started guides.\n',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: 'api',
|
|
83
|
+
readme: '# API Documentation\n\nAPI reference and usage guides.\n',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: 'guides',
|
|
87
|
+
readme: '# User Guides\n\nHow-to guides and tutorials.\n',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Scaffold class - creates living docs structure
|
|
94
|
+
*/
|
|
95
|
+
export class LivingDocsScaffold {
|
|
96
|
+
constructor(options) {
|
|
97
|
+
this.projectPath = options.projectPath;
|
|
98
|
+
this.projectId = options.projectId || this.detectProjectId();
|
|
99
|
+
this.logger = options.logger ?? consoleLogger;
|
|
100
|
+
this.templateEngine = new TemplateEngine({ logger: this.logger });
|
|
101
|
+
this.overwrite = options.overwrite ?? false;
|
|
102
|
+
this.dryRun = options.dryRun ?? false;
|
|
103
|
+
// Create context
|
|
104
|
+
this.context = {
|
|
105
|
+
...createDefaultContext(this.projectId),
|
|
106
|
+
projectName: options.projectName || this.projectId,
|
|
107
|
+
...options.additionalContext,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Detect project ID from git remote or directory name
|
|
112
|
+
*/
|
|
113
|
+
detectProjectId() {
|
|
114
|
+
// Try to get from git remote
|
|
115
|
+
try {
|
|
116
|
+
const gitConfigPath = path.join(this.projectPath, '.git', 'config');
|
|
117
|
+
if (fs.existsSync(gitConfigPath)) {
|
|
118
|
+
const gitConfig = fs.readFileSync(gitConfigPath, 'utf-8');
|
|
119
|
+
const match = gitConfig.match(/url\s*=\s*.*[\/:]([^\/]+?)(?:\.git)?$/m);
|
|
120
|
+
if (match) {
|
|
121
|
+
return match[1].toLowerCase();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Ignore errors
|
|
127
|
+
}
|
|
128
|
+
// Fall back to directory name
|
|
129
|
+
return path.basename(this.projectPath).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Create the full living docs structure
|
|
133
|
+
*/
|
|
134
|
+
async scaffold() {
|
|
135
|
+
const result = {
|
|
136
|
+
success: true,
|
|
137
|
+
filesCreated: [],
|
|
138
|
+
filesSkipped: [],
|
|
139
|
+
dirsCreated: [],
|
|
140
|
+
errors: [],
|
|
141
|
+
context: this.context,
|
|
142
|
+
};
|
|
143
|
+
const docsBase = path.join(this.projectPath, '.specweave', 'docs');
|
|
144
|
+
this.logger.info('Scaffolding living docs structure...');
|
|
145
|
+
this.logger.info(' Project ID: ' + this.projectId);
|
|
146
|
+
this.logger.info(' Docs path: ' + docsBase);
|
|
147
|
+
try {
|
|
148
|
+
// Create base docs directory
|
|
149
|
+
await this.ensureDir(docsBase, result);
|
|
150
|
+
// Create root README
|
|
151
|
+
await this.createRootReadme(docsBase, result);
|
|
152
|
+
// Recursively create structure
|
|
153
|
+
await this.createStructure(docsBase, DOCS_STRUCTURE, result);
|
|
154
|
+
// Create project-specific specs folder
|
|
155
|
+
await this.createProjectSpecsFolder(docsBase, result);
|
|
156
|
+
// Create SUGGESTIONS.md placeholder
|
|
157
|
+
await this.createSuggestionsFile(docsBase, result);
|
|
158
|
+
this.logger.info('Scaffold complete!');
|
|
159
|
+
this.logger.info(' Directories created: ' + result.dirsCreated.length);
|
|
160
|
+
this.logger.info(' Files created: ' + result.filesCreated.length);
|
|
161
|
+
this.logger.info(' Files skipped: ' + result.filesSkipped.length);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
result.success = false;
|
|
165
|
+
result.errors.push(String(error));
|
|
166
|
+
this.logger.error('Scaffold failed: ' + error);
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create directory structure recursively
|
|
172
|
+
*/
|
|
173
|
+
async createStructure(basePath, structure, result) {
|
|
174
|
+
for (const dir of structure) {
|
|
175
|
+
const dirPath = path.join(basePath, dir.path);
|
|
176
|
+
// Create directory
|
|
177
|
+
await this.ensureDir(dirPath, result);
|
|
178
|
+
// Create README if defined
|
|
179
|
+
if (dir.readme) {
|
|
180
|
+
const readmePath = path.join(dirPath, 'README.md');
|
|
181
|
+
await this.createFile(readmePath, dir.readme, result);
|
|
182
|
+
}
|
|
183
|
+
// Process children
|
|
184
|
+
if (dir.children) {
|
|
185
|
+
await this.createStructure(dirPath, dir.children, result);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Create root README.md
|
|
191
|
+
*/
|
|
192
|
+
async createRootReadme(docsBase, result) {
|
|
193
|
+
const content = `# ${this.context.projectName} - Living Documentation
|
|
194
|
+
|
|
195
|
+
This directory contains the living documentation for **${this.context.projectName}**.
|
|
196
|
+
|
|
197
|
+
## Structure
|
|
198
|
+
|
|
199
|
+
- \`internal/\` - Internal documentation (specs, architecture, operations)
|
|
200
|
+
- \`specs/\` - Feature specifications and user stories
|
|
201
|
+
- \`architecture/\` - System architecture and ADRs
|
|
202
|
+
- \`modules/\` - Per-module documentation
|
|
203
|
+
- \`organization/\` - Team and domain structure
|
|
204
|
+
- \`delivery/\` - Release and deployment docs
|
|
205
|
+
- \`operations/\` - Runbooks and monitoring
|
|
206
|
+
|
|
207
|
+
- \`public/\` - User-facing documentation
|
|
208
|
+
- \`overview/\` - Project overview and getting started
|
|
209
|
+
- \`api/\` - API documentation
|
|
210
|
+
- \`guides/\` - How-to guides
|
|
211
|
+
|
|
212
|
+
## Quick Start
|
|
213
|
+
|
|
214
|
+
1. **Create a feature spec**: \`/sw:increment "my feature"\`
|
|
215
|
+
2. **Sync to living docs**: \`/sw:sync-specs\`
|
|
216
|
+
3. **View documentation**: \`/sw-docs:view\`
|
|
217
|
+
|
|
218
|
+
## Generated by SpecWeave
|
|
219
|
+
|
|
220
|
+
This documentation structure was created by [SpecWeave](https://github.com/specweave/specweave).
|
|
221
|
+
|
|
222
|
+
Last updated: ${this.context.lastUpdatedDate}
|
|
223
|
+
`;
|
|
224
|
+
await this.createFile(path.join(docsBase, 'README.md'), content, result);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Create project-specific specs folder
|
|
228
|
+
*/
|
|
229
|
+
async createProjectSpecsFolder(docsBase, result) {
|
|
230
|
+
const projectSpecsPath = path.join(docsBase, 'internal', 'specs', this.projectId);
|
|
231
|
+
await this.ensureDir(projectSpecsPath, result);
|
|
232
|
+
const content = `# ${this.context.projectName} - Specifications
|
|
233
|
+
|
|
234
|
+
Feature specifications for **${this.context.projectName}**.
|
|
235
|
+
|
|
236
|
+
## Features
|
|
237
|
+
|
|
238
|
+
Features are organized by ID: \`FS-XXX/\`
|
|
239
|
+
|
|
240
|
+
Each feature folder contains:
|
|
241
|
+
- \`FEATURE.md\` - Feature overview and implementation history
|
|
242
|
+
- \`us-XXX-*.md\` - User story files
|
|
243
|
+
|
|
244
|
+
## Creating Features
|
|
245
|
+
|
|
246
|
+
Features are automatically created when you sync increments:
|
|
247
|
+
|
|
248
|
+
\`\`\`bash
|
|
249
|
+
/sw:sync-specs
|
|
250
|
+
\`\`\`
|
|
251
|
+
|
|
252
|
+
Or sync a specific increment:
|
|
253
|
+
|
|
254
|
+
\`\`\`bash
|
|
255
|
+
/sw:sync-specs 0001
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
Last updated: ${this.context.lastUpdatedDate}
|
|
261
|
+
`;
|
|
262
|
+
await this.createFile(path.join(projectSpecsPath, 'README.md'), content, result);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Create SUGGESTIONS.md placeholder
|
|
266
|
+
*/
|
|
267
|
+
async createSuggestionsFile(docsBase, result) {
|
|
268
|
+
const content = `# Documentation Suggestions
|
|
269
|
+
|
|
270
|
+
This file will be populated with documentation improvement suggestions after running the Living Docs Builder.
|
|
271
|
+
|
|
272
|
+
## How to Generate Suggestions
|
|
273
|
+
|
|
274
|
+
Run the living docs analysis:
|
|
275
|
+
|
|
276
|
+
\`\`\`bash
|
|
277
|
+
/sw:living-docs
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
Or with specific options:
|
|
281
|
+
|
|
282
|
+
\`\`\`bash
|
|
283
|
+
/sw:living-docs --depth standard --priority auth,payments
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
## What Gets Analyzed
|
|
287
|
+
|
|
288
|
+
- **Codebase structure** - Modules, dependencies, patterns
|
|
289
|
+
- **Existing documentation** - READMEs, JSDoc, inline comments
|
|
290
|
+
- **Specifications** - User stories, acceptance criteria
|
|
291
|
+
- **Architecture** - Detected patterns and decisions
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
*Generated by SpecWeave Living Docs Builder*
|
|
296
|
+
`;
|
|
297
|
+
await this.createFile(path.join(docsBase, 'SUGGESTIONS.md'), content, result);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Ensure directory exists
|
|
301
|
+
*/
|
|
302
|
+
async ensureDir(dirPath, result) {
|
|
303
|
+
if (this.dryRun) {
|
|
304
|
+
if (!fs.existsSync(dirPath)) {
|
|
305
|
+
result.dirsCreated.push(dirPath);
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (!fs.existsSync(dirPath)) {
|
|
310
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
311
|
+
result.dirsCreated.push(dirPath);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Create a file with content
|
|
316
|
+
*/
|
|
317
|
+
async createFile(filePath, content, result) {
|
|
318
|
+
if (fs.existsSync(filePath) && !this.overwrite) {
|
|
319
|
+
result.filesSkipped.push(filePath);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (this.dryRun) {
|
|
323
|
+
result.filesCreated.push(filePath + ' (dry-run)');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Render template if it has variables
|
|
327
|
+
const rendered = this.templateEngine.render(content, this.context);
|
|
328
|
+
fs.writeFileSync(filePath, rendered, 'utf-8');
|
|
329
|
+
result.filesCreated.push(filePath);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Scaffold living docs structure (convenience function)
|
|
334
|
+
*/
|
|
335
|
+
export async function scaffoldLivingDocs(options) {
|
|
336
|
+
const scaffold = new LivingDocsScaffold(options);
|
|
337
|
+
return scaffold.scaffold();
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Check if living docs structure exists
|
|
341
|
+
*/
|
|
342
|
+
export function hasLivingDocsStructure(projectPath) {
|
|
343
|
+
const docsPath = path.join(projectPath, '.specweave', 'docs');
|
|
344
|
+
return fs.existsSync(docsPath) && fs.existsSync(path.join(docsPath, 'internal'));
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../../../src/core/living-docs/scaffolding/scaffold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAU,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAiElG;;;GAGG;AACH,MAAM,cAAc,GAAmB;IACrC;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,4FAA4F;QACpG,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,wHAAwH;aACjI;YACD;gBACE,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,uGAAuG;gBAC/G,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,KAAK;wBACX,MAAM,EAAE,+JAA+J;qBACxK;oBACD;wBACE,IAAI,EAAE,UAAU;wBAChB,MAAM,EAAE,sFAAsF;qBAC/F;iBACF;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,wFAAwF;aACjG;YACD;gBACE,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,oFAAoF;gBAC5F,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,OAAO,EAAE;oBACjB,EAAE,IAAI,EAAE,eAAe,EAAE;oBACzB,EAAE,IAAI,EAAE,SAAS,EAAE;iBACpB;aACF;YACD;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,wFAAwF;aACjG;YACD;gBACE,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,mFAAmF;aAC5F;YACD;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,6FAA6F;aACtG;YACD;gBACE,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,sEAAsE;aAC/E;SACF;KACF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,wDAAwD;QAChE,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,sFAAsF;aAC/F;YACD;gBACE,IAAI,EAAE,KAAK;gBACX,MAAM,EAAE,0DAA0D;aACnE;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,iDAAiD;aAC1D;SACF;KACF;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAS7B,YAAY,OAAwB;QAClC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;QAC9C,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;QAEtC,iBAAiB;QACjB,IAAI,CAAC,OAAO,GAAG;YACb,GAAG,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC;YACvC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS;YAClD,GAAG,OAAO,CAAC,iBAAiB;SAC7B,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,eAAe;QACrB,6BAA6B;QAC7B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACpE,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBACjC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;gBAC1D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;gBACxE,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;QAED,8BAA8B;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IACnF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,MAAM,GAAmB;YAC7B,OAAO,EAAE,IAAI;YACb,YAAY,EAAE,EAAE;YAChB,YAAY,EAAE,EAAE;YAChB,WAAW,EAAE,EAAE;YACf,MAAM,EAAE,EAAE;YACV,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC;QAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;QAEnE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,CAAC;QAE7C,IAAI,CAAC;YACH,6BAA6B;YAC7B,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAEvC,qBAAqB;YACrB,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAE9C,+BAA+B;YAC/B,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;YAE7D,uCAAuC;YACvC,MAAM,IAAI,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAEtD,oCAAoC;YACpC,MAAM,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAEnD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACxE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACnE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAErE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;YACvB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,eAAe,CAC3B,QAAgB,EAChB,SAAyB,EACzB,MAAsB;QAEtB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAE9C,mBAAmB;YACnB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAEtC,2BAA2B;YAC3B,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;gBACnD,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACxD,CAAC;YAED,mBAAmB;YACnB,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;gBACjB,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,gBAAgB,CAAC,QAAgB,EAAE,MAAsB;QACrE,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW;;yDAEQ,IAAI,CAAC,OAAO,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA2BjE,IAAI,CAAC,OAAO,CAAC,eAAe;CAC3C,CAAC;QAEE,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC3E,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,wBAAwB,CAAC,QAAgB,EAAE,MAAsB;QAC7E,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAClF,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QAE/C,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW;;+BAElB,IAAI,CAAC,OAAO,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;gBA0BvC,IAAI,CAAC,OAAO,CAAC,eAAe;CAC3C,CAAC;QAEE,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACnF,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CAAC,QAAgB,EAAE,MAAsB;QAC1E,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BnB,CAAC;QAEE,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAChF,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,SAAS,CAAC,OAAe,EAAE,MAAsB;QAC7D,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU,CACtB,QAAgB,EAChB,OAAe,EACf,MAAsB;QAEtB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/C,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,sCAAsC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAEnE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAwB;IAC/D,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9D,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Engine for Living Docs Scaffolding
|
|
3
|
+
*
|
|
4
|
+
* Provides variable substitution for .md.template files.
|
|
5
|
+
* Works with ANY user project - no hardcoded paths or project names.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Simple variables: ${variableName}
|
|
9
|
+
* - Conditional blocks: ${if:condition}...${endif} (supports nesting)
|
|
10
|
+
* - Loop blocks: ${each:items}...${endeach} (supports nesting)
|
|
11
|
+
* - Default values: ${variableName:defaultValue}
|
|
12
|
+
* - Nested conditionals and loops (recursive processing)
|
|
13
|
+
*
|
|
14
|
+
* @module core/living-docs/scaffolding/template-engine
|
|
15
|
+
*/
|
|
16
|
+
import { Logger } from '../../../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Template variables context
|
|
19
|
+
*/
|
|
20
|
+
export interface TemplateContext {
|
|
21
|
+
projectId: string;
|
|
22
|
+
projectName: string;
|
|
23
|
+
projectDescription?: string;
|
|
24
|
+
createdDate: string;
|
|
25
|
+
lastUpdatedDate: string;
|
|
26
|
+
featureId?: string;
|
|
27
|
+
featureTitle?: string;
|
|
28
|
+
featureStatus?: string;
|
|
29
|
+
featurePriority?: string;
|
|
30
|
+
incrementId?: string;
|
|
31
|
+
incrementsRelativePath?: string;
|
|
32
|
+
userStories?: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
fileName: string;
|
|
36
|
+
status?: string;
|
|
37
|
+
}>;
|
|
38
|
+
increments?: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
link: string;
|
|
41
|
+
status: string;
|
|
42
|
+
statusEmoji: string;
|
|
43
|
+
date: string;
|
|
44
|
+
}>;
|
|
45
|
+
overview?: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Default template context values
|
|
50
|
+
*/
|
|
51
|
+
export declare function createDefaultContext(projectId: string): TemplateContext;
|
|
52
|
+
/**
|
|
53
|
+
* Template Engine class
|
|
54
|
+
*
|
|
55
|
+
* Supports deeply nested conditionals and loops through recursive processing.
|
|
56
|
+
*/
|
|
57
|
+
export declare class TemplateEngine {
|
|
58
|
+
private logger;
|
|
59
|
+
constructor(options?: {
|
|
60
|
+
logger?: Logger;
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Render a template with the given context
|
|
64
|
+
*
|
|
65
|
+
* Processes nested structures by recursively rendering until no more
|
|
66
|
+
* template syntax remains (up to MAX_RECURSION_DEPTH iterations).
|
|
67
|
+
*/
|
|
68
|
+
render(template: string, context: TemplateContext): string;
|
|
69
|
+
/**
|
|
70
|
+
* Process conditional blocks with proper nesting support
|
|
71
|
+
*
|
|
72
|
+
* Uses a non-greedy innermost-first approach to handle nesting:
|
|
73
|
+
* ${if:outer}${if:inner}...${endif}${endif}
|
|
74
|
+
*/
|
|
75
|
+
private processConditionals;
|
|
76
|
+
/**
|
|
77
|
+
* Process loop blocks with proper nesting support
|
|
78
|
+
*
|
|
79
|
+
* Uses a non-greedy innermost-first approach to handle nesting:
|
|
80
|
+
* ${each:outer}${each:inner}...${endeach}${endeach}
|
|
81
|
+
*/
|
|
82
|
+
private processLoops;
|
|
83
|
+
/**
|
|
84
|
+
* Check if a value is truthy for template conditionals
|
|
85
|
+
*/
|
|
86
|
+
private isTruthy;
|
|
87
|
+
/**
|
|
88
|
+
* Get a nested value from an object using dot notation
|
|
89
|
+
* Supports: item.user.name, item.tags.0, etc.
|
|
90
|
+
*/
|
|
91
|
+
private getNestedValue;
|
|
92
|
+
private processVariables;
|
|
93
|
+
private getContextValue;
|
|
94
|
+
/**
|
|
95
|
+
* Validate template syntax for common errors
|
|
96
|
+
*
|
|
97
|
+
* Checks for:
|
|
98
|
+
* - Mismatched if/endif blocks
|
|
99
|
+
* - Mismatched each/endeach blocks
|
|
100
|
+
* - Detects potential infinite loops (self-referencing)
|
|
101
|
+
*/
|
|
102
|
+
validateTemplate(template: string): string[];
|
|
103
|
+
}
|
|
104
|
+
export declare function getTemplateEngine(options?: {
|
|
105
|
+
logger?: Logger;
|
|
106
|
+
}): TemplateEngine;
|
|
107
|
+
export declare function renderTemplate(template: string, context: TemplateContext): string;
|
|
108
|
+
//# sourceMappingURL=template-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-engine.d.ts","sourceRoot":"","sources":["../../../../../src/core/living-docs/scaffolding/template-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAiB,MAAM,0BAA0B,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,eAAe;IAE9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAG5B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IAGxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IAGH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,CASvE;AAOD;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAI7C;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM;IAuB1D;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA0BpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,eAAe;IAUvB;;;;;;;OAOG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;CA2B7C;AAID,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAK/E;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAEjF"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Engine for Living Docs Scaffolding
|
|
3
|
+
*
|
|
4
|
+
* Provides variable substitution for .md.template files.
|
|
5
|
+
* Works with ANY user project - no hardcoded paths or project names.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Simple variables: ${variableName}
|
|
9
|
+
* - Conditional blocks: ${if:condition}...${endif} (supports nesting)
|
|
10
|
+
* - Loop blocks: ${each:items}...${endeach} (supports nesting)
|
|
11
|
+
* - Default values: ${variableName:defaultValue}
|
|
12
|
+
* - Nested conditionals and loops (recursive processing)
|
|
13
|
+
*
|
|
14
|
+
* @module core/living-docs/scaffolding/template-engine
|
|
15
|
+
*/
|
|
16
|
+
import { consoleLogger } from '../../../utils/logger.js';
|
|
17
|
+
/**
|
|
18
|
+
* Default template context values
|
|
19
|
+
*/
|
|
20
|
+
export function createDefaultContext(projectId) {
|
|
21
|
+
const today = new Date().toISOString().split('T')[0];
|
|
22
|
+
return {
|
|
23
|
+
projectId,
|
|
24
|
+
projectName: projectId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
25
|
+
createdDate: today,
|
|
26
|
+
lastUpdatedDate: today,
|
|
27
|
+
incrementsRelativePath: '../../../../increments',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Maximum recursion depth for nested templates (prevents infinite loops)
|
|
32
|
+
*/
|
|
33
|
+
const MAX_RECURSION_DEPTH = 10;
|
|
34
|
+
/**
|
|
35
|
+
* Template Engine class
|
|
36
|
+
*
|
|
37
|
+
* Supports deeply nested conditionals and loops through recursive processing.
|
|
38
|
+
*/
|
|
39
|
+
export class TemplateEngine {
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.logger = options.logger ?? consoleLogger;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Render a template with the given context
|
|
45
|
+
*
|
|
46
|
+
* Processes nested structures by recursively rendering until no more
|
|
47
|
+
* template syntax remains (up to MAX_RECURSION_DEPTH iterations).
|
|
48
|
+
*/
|
|
49
|
+
render(template, context) {
|
|
50
|
+
let result = template;
|
|
51
|
+
let depth = 0;
|
|
52
|
+
// Process recursively until stable (handles nested blocks)
|
|
53
|
+
let previousResult = '';
|
|
54
|
+
while (previousResult !== result && depth < MAX_RECURSION_DEPTH) {
|
|
55
|
+
previousResult = result;
|
|
56
|
+
result = this.processConditionals(result, context);
|
|
57
|
+
result = this.processLoops(result, context);
|
|
58
|
+
depth++;
|
|
59
|
+
}
|
|
60
|
+
// Final pass for variables (after all conditionals/loops are resolved)
|
|
61
|
+
result = this.processVariables(result, context);
|
|
62
|
+
if (depth >= MAX_RECURSION_DEPTH) {
|
|
63
|
+
this.logger.warn('Template engine reached max recursion depth. Possible infinite loop in template.');
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Process conditional blocks with proper nesting support
|
|
69
|
+
*
|
|
70
|
+
* Uses a non-greedy innermost-first approach to handle nesting:
|
|
71
|
+
* ${if:outer}${if:inner}...${endif}${endif}
|
|
72
|
+
*/
|
|
73
|
+
processConditionals(template, context) {
|
|
74
|
+
// Match innermost conditionals first (non-greedy, no nested ${if:} inside)
|
|
75
|
+
const innermostPattern = /\$\{if:(\w+)\}((?:(?!\$\{if:)[\s\S])*?)\$\{endif\}/g;
|
|
76
|
+
return template.replace(innermostPattern, (_match, varName, content) => {
|
|
77
|
+
const value = this.getContextValue(context, varName);
|
|
78
|
+
const isTruthy = this.isTruthy(value);
|
|
79
|
+
return isTruthy ? content : '';
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Process loop blocks with proper nesting support
|
|
84
|
+
*
|
|
85
|
+
* Uses a non-greedy innermost-first approach to handle nesting:
|
|
86
|
+
* ${each:outer}${each:inner}...${endeach}${endeach}
|
|
87
|
+
*/
|
|
88
|
+
processLoops(template, context) {
|
|
89
|
+
// Match innermost loops first (non-greedy, no nested ${each:} inside)
|
|
90
|
+
const innermostPattern = /\$\{each:(\w+)\}((?:(?!\$\{each:)[\s\S])*?)\$\{endeach\}/g;
|
|
91
|
+
return template.replace(innermostPattern, (_match, arrayName, content) => {
|
|
92
|
+
const array = this.getContextValue(context, arrayName);
|
|
93
|
+
if (!Array.isArray(array) || array.length === 0)
|
|
94
|
+
return '';
|
|
95
|
+
return array.map((item, index) => {
|
|
96
|
+
let itemContent = content;
|
|
97
|
+
// Replace item properties (supports nested paths like item.user.name)
|
|
98
|
+
const itemPattern = /\$\{item\.([.\w]+)\}/g;
|
|
99
|
+
itemContent = itemContent.replace(itemPattern, (_m, prop) => {
|
|
100
|
+
const value = this.getNestedValue(item, prop);
|
|
101
|
+
return value !== undefined ? String(value) : '';
|
|
102
|
+
});
|
|
103
|
+
// Replace index
|
|
104
|
+
itemContent = itemContent.replace(/\$\{index\}/g, String(index));
|
|
105
|
+
return itemContent;
|
|
106
|
+
}).join('');
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if a value is truthy for template conditionals
|
|
111
|
+
*/
|
|
112
|
+
isTruthy(value) {
|
|
113
|
+
return value !== undefined &&
|
|
114
|
+
value !== null &&
|
|
115
|
+
value !== false &&
|
|
116
|
+
value !== '' &&
|
|
117
|
+
value !== 0 &&
|
|
118
|
+
!(Array.isArray(value) && value.length === 0);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a nested value from an object using dot notation
|
|
122
|
+
* Supports: item.user.name, item.tags.0, etc.
|
|
123
|
+
*/
|
|
124
|
+
getNestedValue(obj, path) {
|
|
125
|
+
const parts = path.split('.');
|
|
126
|
+
let current = obj;
|
|
127
|
+
for (const part of parts) {
|
|
128
|
+
if (current === null || current === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
current = current[part];
|
|
131
|
+
}
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
134
|
+
processVariables(template, context) {
|
|
135
|
+
const variablePattern = /\$\{(\w+)(?::([^}]*))?\}/g;
|
|
136
|
+
return template.replace(variablePattern, (_match, varName, defaultValue) => {
|
|
137
|
+
// Skip template keywords
|
|
138
|
+
if (['if', 'endif', 'each', 'endeach', 'item', 'index'].includes(varName)) {
|
|
139
|
+
return _match;
|
|
140
|
+
}
|
|
141
|
+
const value = this.getContextValue(context, varName);
|
|
142
|
+
if (value === undefined || value === null || value === '') {
|
|
143
|
+
return defaultValue !== undefined ? defaultValue : '';
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(value))
|
|
146
|
+
return value.join(', ');
|
|
147
|
+
if (typeof value === 'object')
|
|
148
|
+
return JSON.stringify(value);
|
|
149
|
+
return String(value);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
getContextValue(context, path) {
|
|
153
|
+
const parts = path.split('.');
|
|
154
|
+
let current = context;
|
|
155
|
+
for (const part of parts) {
|
|
156
|
+
if (current === null || current === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
current = current[part];
|
|
159
|
+
}
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validate template syntax for common errors
|
|
164
|
+
*
|
|
165
|
+
* Checks for:
|
|
166
|
+
* - Mismatched if/endif blocks
|
|
167
|
+
* - Mismatched each/endeach blocks
|
|
168
|
+
* - Detects potential infinite loops (self-referencing)
|
|
169
|
+
*/
|
|
170
|
+
validateTemplate(template) {
|
|
171
|
+
const errors = [];
|
|
172
|
+
// Check balanced conditionals
|
|
173
|
+
const ifCount = (template.match(/\$\{if:\w+\}/g) || []).length;
|
|
174
|
+
const endifCount = (template.match(/\$\{endif\}/g) || []).length;
|
|
175
|
+
if (ifCount !== endifCount) {
|
|
176
|
+
errors.push('Mismatched conditionals: ' + ifCount + ' if blocks but ' + endifCount + ' endif');
|
|
177
|
+
}
|
|
178
|
+
// Check balanced loops
|
|
179
|
+
const eachCount = (template.match(/\$\{each:\w+\}/g) || []).length;
|
|
180
|
+
const endeachCount = (template.match(/\$\{endeach\}/g) || []).length;
|
|
181
|
+
if (eachCount !== endeachCount) {
|
|
182
|
+
errors.push('Mismatched loops: ' + eachCount + ' each blocks but ' + endeachCount + ' endeach');
|
|
183
|
+
}
|
|
184
|
+
// Check for common mistakes
|
|
185
|
+
if (template.includes('${endif}${endif}') && ifCount < 2) {
|
|
186
|
+
errors.push('Suspicious double endif - check nesting structure');
|
|
187
|
+
}
|
|
188
|
+
if (template.includes('${endeach}${endeach}') && eachCount < 2) {
|
|
189
|
+
errors.push('Suspicious double endeach - check nesting structure');
|
|
190
|
+
}
|
|
191
|
+
return errors;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
let defaultEngine = null;
|
|
195
|
+
export function getTemplateEngine(options) {
|
|
196
|
+
if (!defaultEngine) {
|
|
197
|
+
defaultEngine = new TemplateEngine(options);
|
|
198
|
+
}
|
|
199
|
+
return defaultEngine;
|
|
200
|
+
}
|
|
201
|
+
export function renderTemplate(template, context) {
|
|
202
|
+
return getTemplateEngine().render(template, context);
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=template-engine.js.map
|