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.
Files changed (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CLAUDE.md +205 -148
  3. package/README.md +0 -2
  4. package/bin/specweave.js +11 -0
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/commands/init.js.map +1 -1
  7. package/dist/src/cli/commands/update-instructions.d.ts +16 -0
  8. package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
  9. package/dist/src/cli/commands/update-instructions.js +134 -0
  10. package/dist/src/cli/commands/update-instructions.js.map +1 -0
  11. package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
  12. package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/init/directory-structure.js +163 -33
  14. package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
  15. package/dist/src/cli/helpers/init/index.d.ts +2 -1
  16. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  17. package/dist/src/cli/helpers/init/index.js +3 -1
  18. package/dist/src/cli/helpers/init/index.js.map +1 -1
  19. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
  20. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
  21. package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
  22. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
  23. package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
  24. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  25. package/dist/src/config/types.d.ts +2 -2
  26. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
  27. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
  28. package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
  29. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
  30. package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
  31. package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
  32. package/dist/src/core/living-docs/scaffolding/index.js +15 -0
  33. package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
  34. package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
  35. package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
  36. package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
  37. package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
  38. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
  39. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
  40. package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
  41. package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
  42. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
  43. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
  44. package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
  45. package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
  46. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
  47. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
  49. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  50. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  51. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  52. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  53. package/dist/src/core/tools/index.d.ts +11 -0
  54. package/dist/src/core/tools/index.d.ts.map +1 -0
  55. package/dist/src/core/tools/index.js +10 -0
  56. package/dist/src/core/tools/index.js.map +1 -0
  57. package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
  58. package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
  59. package/dist/src/core/tools/tool-event-bus.js +84 -0
  60. package/dist/src/core/tools/tool-event-bus.js.map +1 -0
  61. package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
  62. package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
  63. package/dist/src/core/tools/tool-index-builder.js +289 -0
  64. package/dist/src/core/tools/tool-index-builder.js.map +1 -0
  65. package/dist/src/core/tools/tool-registry.d.ts +51 -0
  66. package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
  67. package/dist/src/core/tools/tool-registry.js +224 -0
  68. package/dist/src/core/tools/tool-registry.js.map +1 -0
  69. package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
  70. package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
  71. package/dist/src/core/tools/tool-search-engine.js +174 -0
  72. package/dist/src/core/tools/tool-search-engine.js.map +1 -0
  73. package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
  74. package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
  75. package/dist/src/core/tools/types/tool-registry-types.js +7 -0
  76. package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
  77. package/dist/src/init/compliance/types.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/plugins/specweave/hooks/hooks.json +3 -13
  80. package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
  81. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
  82. package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
  83. package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
  84. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
  85. package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
  86. package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
  87. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
  88. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
  89. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
  90. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
  91. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
  92. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
  93. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
  94. package/plugins/specweave/hooks/v2/session-end.sh +3 -1
  95. package/plugins/specweave/hooks/v2/session-start.sh +3 -1
  96. package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
  97. package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
  98. package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
  99. package/plugins/specweave-mobile/README.md +55 -35
  100. package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
  101. package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
  102. package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
  103. package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
  104. package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
  105. package/plugins/specweave-release/commands/npm.md +61 -17
  106. package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
  107. package/src/templates/AGENTS.md.template +34 -0
  108. package/src/templates/CLAUDE.md.template +121 -155
  109. package/plugins/specweave/hooks/config-env-separator.sh +0 -99
  110. package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
  111. package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
  112. package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
  113. package/plugins/specweave/hooks/lib/logging.sh +0 -231
  114. package/plugins/specweave/hooks/lib/metrics.sh +0 -347
  115. package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
  116. package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
  117. package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
  118. package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
  119. package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
  120. package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
  121. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
  122. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
  123. 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":"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"}
@@ -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