Skip to content

Commit e8e6e98

Browse files
pekkahclaude[bot]claude
authored
feat: add support for "files" section type (#233)
* feat: add support for "files" section type to include dynamically generated content - Add HasFilesSections() method to scan for sections with type: files - Force PhysicalFileSystem for input paths containing files sections - Apply to both branch and tag processing for consistent behavior - Maintains backward compatibility with existing section types - Enables inclusion of generated content not in version control Fixes #78 Co-authored-by: Pekka Heikura <pekkah@users.noreply.github.com> * feat: implement clean pipeline-based architecture for files sections Replaces the previous crude ContentAggregator approach with a proper pipeline-based solution for supporting "type: files" sections that include dynamically generated content from the working directory. Key improvements: - New AugmentFilesSections pipeline step runs after CollectSections - FilesSectionAugmenter uses async enumerable streaming pattern - Only processes sections that actually need files section support - Comprehensive unit test coverage with proper mocking - End-to-end testing confirms documentation builds successfully - Updated documentation in README.md and PLANNING.md Addresses issue #78: Include dynamically generated content 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add permissions * More tools * Update claude.yml * Update claude.yml * Update claude.yml * Update claude.yml * Update claude.yml * Update claude.yml * Update claude.yml * Update claude.yml --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Pekka Heikura <pekkah@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent f71c53d commit e8e6e98

File tree

7 files changed

+509
-8
lines changed

7 files changed

+509
-8
lines changed

.github/workflows/claude.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@ jobs:
1919
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
2020
runs-on: ubuntu-latest
2121
permissions:
22-
contents: read
22+
contents: write # Changed from 'read' to 'write'
2323
pull-requests: write
2424
issues: write
25+
actions: write # Added for workflow operations
2526
id-token: write
2627
steps:
2728
- name: Checkout repository
2829
uses: actions/checkout@v4
2930
with:
30-
fetch-depth: 1
31+
fetch-depth: 0
3132

3233
- name: Run Claude Code
3334
id: claude
3435
uses: anthropics/claude-code-action@beta
36+
env:
37+
# CRITICAL: Add GH_TOKEN for gh command authentication
38+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3539
with:
3640
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
3741

@@ -44,8 +48,8 @@ jobs:
4448
# Optional: Trigger when specific user is assigned to an issue
4549
# assignee_trigger: "claude-bot"
4650

47-
# Optional: Allow Claude to run specific commands
48-
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
51+
# FIXED: Specific tool permissions for gh commands (comma-separated string)
52+
allowed_tools: "Bash(gh:*),Bash(dotnet:*)"
4953

5054
# Optional: Add custom instructions for Claude to customize its behavior for your project
5155
# custom_instructions: |
@@ -54,6 +58,5 @@ jobs:
5458
# Use TypeScript for new files
5559

5660
# Optional: Custom environment variables for Claude
57-
# claude_env: |
58-
# NODE_ENV: test
59-
61+
claude_env: |
62+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

PLANNING.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,61 @@ Then run 'tanka-docs init' again.
350350
- Plugin architecture consideration
351351
- Bundle marketplace preparation
352352

353+
### 🟢 Files Section Type - Dynamic Content Support
354+
**Status:** Completed
355+
**Priority:** High
356+
357+
#### Overview
358+
Implement support for `type: files` sections to include dynamically generated content that exists in the working directory but may not be committed to version control.
359+
360+
#### **IMPLEMENTATION COMPLETED**
361+
362+
**What Was Delivered:**
363+
-**Pipeline Architecture**: Clean separation using dedicated `AugmentFilesSections` pipeline step
364+
-**Section Type Detection**: Automatic detection of sections with `type: files`
365+
-**Working Directory Access**: FileSystemContentSource integration for dynamic content
366+
-**Async Streaming**: Memory-efficient async enumerable pattern for content collection
367+
-**Comprehensive Testing**: Full unit test coverage for all scenarios
368+
-**End-to-End Verification**: Documentation build testing with real files sections
369+
370+
**Key Features Implemented:**
371+
- AugmentFilesSections middleware with progress reporting
372+
- FilesSectionAugmenter for content collection from working directory
373+
- Integration with existing catalog and pipeline infrastructure
374+
- Error handling and logging for missing directories
375+
- Case-insensitive section type matching (`"files"` or `"FILES"`)
376+
377+
**Use Case Solved:**
378+
This directly addresses **issue #78**: "Include dynamically generated content"
379+
- **Benchmark results** generated at build time
380+
- **API documentation** generated from code analysis
381+
- **Generated reports** or metrics files
382+
- **Build artifacts** that should be included in documentation
383+
384+
**Architecture Benefits:**
385+
- **Clean Pipeline Design**: Follows existing middleware patterns
386+
- **Targeted Processing**: Only processes sections that need it
387+
- **Memory Efficient**: Streams content items without intermediate collections
388+
- **Extensible**: Easy foundation for additional section types
389+
390+
**Example Usage:**
391+
```yaml
392+
# tanka-docs-section.yml
393+
id: benchmark-results
394+
type: files # <-- New section type
395+
title: "Performance Benchmarks"
396+
includes:
397+
- "**/*.md"
398+
- "*.json"
399+
```
400+
401+
Files in the section directory are included from the working directory, allowing generated content that's not in Git history.
402+
353403
---
354404
355405
## Next Priorities (Post-Init Command)
356406
357-
With the init command completed, the following areas represent the next logical development priorities:
407+
With the init command and files section type completed, the following areas represent the next logical development priorities:
358408
359409
### 🟢 NuGet Package Publication & Distribution
360410
**Status:** Completed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Tanka Docs is a powerful technical documentation generator designed for .NET pro
99
- **Versioned Documentation**: Generate documentation from Git repositories with support for versioning using tags and branches
1010
- **Modular Structure**: Organize documentation using sections for better maintainability
1111
- **Live Code Integration**: Include C# code snippets/files using `#include` syntax with Roslyn integration
12+
- **Dynamic Content Support**: Include generated files with `type: files` sections for build artifacts and reports
1213
- **Cross-References**: Link between documents using `xref://` syntax for maintainable internal links
1314
- **Customizable UI**: Use Handlebars templates for flexible UI customization
1415
- **Git Integration**: Built-in support for Git repositories and version management
@@ -158,6 +159,10 @@ my-project/
158159
│ ├── tanka-docs-section.yml # Section configuration
159160
│ ├── index.md # Documentation files
160161
│ └── getting-started.md
162+
├── reports/ # Generated content section
163+
│ ├── tanka-docs-section.yml # type: files for dynamic content
164+
│ ├── benchmark.md # Generated at build time
165+
│ └── coverage-report.html # Generated reports
161166
├── _partials/ # Shared content
162167
│ ├── tanka-docs-section.yml
163168
│ └── common-notice.md
@@ -184,6 +189,27 @@ Include entire files or specific symbols:
184189
\```
185190
```
186191

192+
### Dynamic Content Sections
193+
194+
Include dynamically generated content that exists in your working directory but may not be committed to version control:
195+
196+
```yaml
197+
# reports/tanka-docs-section.yml
198+
id: performance-reports
199+
type: files # <-- Special section type for dynamic content
200+
title: "Performance Reports"
201+
includes:
202+
- "**/*.md"
203+
- "*.html"
204+
- "*.json"
205+
```
206+
207+
Perfect for:
208+
- Build artifacts and generated reports
209+
- Benchmark results created during CI/CD
210+
- API documentation generated from code analysis
211+
- Coverage reports and metrics
212+
187213
### Cross-Reference Links
188214
189215
Create maintainable internal links:
@@ -192,6 +218,7 @@ Create maintainable internal links:
192218
[Getting Started](xref://getting-started.md)
193219
[API Reference](xref://api:overview.md)
194220
[Version 1.0 Docs](xref://docs@1.0.0:index.md)
221+
[Latest Benchmarks](xref://performance-reports:benchmark.md)
195222
```
196223

197224
### Versioning with Git
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using Microsoft.Extensions.Logging;
2+
using Tanka.DocsTool.Catalogs;
3+
using Tanka.DocsTool.Pipelines;
4+
using Tanka.FileSystem;
5+
6+
namespace Tanka.DocsTool;
7+
8+
public class AugmentFilesSections : IMiddleware
9+
{
10+
private readonly IAnsiConsole _console;
11+
private readonly ILogger<AugmentFilesSections> _logger;
12+
13+
public string Name => "Augment files sections";
14+
15+
public AugmentFilesSections(IAnsiConsole console)
16+
{
17+
_console = console;
18+
_logger = Infra.LoggerFactory.CreateLogger<AugmentFilesSections>();
19+
}
20+
21+
public async Task Invoke(PipelineStep next, BuildContext context)
22+
{
23+
if (context.HasErrors)
24+
{
25+
_console.LogInformation($"Skipping {Name} because of previous errors.");
26+
await next(context);
27+
return;
28+
}
29+
30+
// Find sections with type: files and augment catalog with working directory content
31+
var filesSections = context.Sections?.Where(s => s.Definition.Type?.ToLowerInvariant() == "files").ToList();
32+
33+
if (filesSections?.Any() == true)
34+
{
35+
_logger.LogInformation($"Found {filesSections.Count} files sections, augmenting with working directory content");
36+
37+
await _console.Progress()
38+
.Columns(
39+
new TaskDescriptionColumn(),
40+
new ProgressBarColumn(),
41+
new ItemCountColumn(),
42+
new ElapsedTimeColumn(),
43+
new SpinnerColumn()
44+
)
45+
.StartAsync(async progress =>
46+
{
47+
var augmenter = new FilesSectionAugmenter(context.FileSystem, _console);
48+
await augmenter.AugmentCatalog(context.Catalog, filesSections, progress);
49+
});
50+
}
51+
else
52+
{
53+
_logger.LogDebug("No files sections found, skipping augmentation");
54+
}
55+
56+
await next(context);
57+
}
58+
}
59+
60+
public class FilesSectionAugmenter
61+
{
62+
private readonly IFileSystem _fileSystem;
63+
private readonly IAnsiConsole _console;
64+
private readonly IContentClassifier _classifier;
65+
66+
public FilesSectionAugmenter(IFileSystem fileSystem, IAnsiConsole console)
67+
{
68+
_fileSystem = fileSystem;
69+
_console = console;
70+
_classifier = new MimeDbClassifier();
71+
}
72+
73+
public async Task AugmentCatalog(Catalog catalog, IEnumerable<Section> filesSections, ProgressContext progress)
74+
{
75+
foreach (var section in filesSections)
76+
{
77+
var task = progress.AddTask($"Files section: {section.Id}@{section.Version}", maxValue: 0);
78+
79+
try
80+
{
81+
await AugmentSectionWithWorkingDirectoryContent(catalog, section, task);
82+
}
83+
catch (Exception ex)
84+
{
85+
_console.LogError($"Failed to augment files section '{section.Id}@{section.Version}': {ex.Message}");
86+
}
87+
finally
88+
{
89+
task.StopTask();
90+
}
91+
}
92+
}
93+
94+
private async Task AugmentSectionWithWorkingDirectoryContent(Catalog catalog, Section section, ProgressTask task)
95+
{
96+
// Get the section's directory in the working file system
97+
var sectionDirectory = await _fileSystem.GetDirectory(section.Path);
98+
if (sectionDirectory == null)
99+
{
100+
_console.LogWarning($"Section directory '{section.Path}' not found in working directory for files section '{section.Id}'");
101+
return;
102+
}
103+
104+
_console.LogInformation($"Augmenting files section '{section.Id}@{section.Version}' from working directory: {section.Path}");
105+
106+
// Stream content items directly from working directory
107+
var contentItems = CollectWorkingDirectoryContent(sectionDirectory, section, task);
108+
await catalog.Add(contentItems);
109+
110+
_console.LogInformation($"Augmented files section '{section.Id}' with working directory content");
111+
}
112+
113+
private async IAsyncEnumerable<ContentItem> CollectWorkingDirectoryContent(
114+
IReadOnlyDirectory directory,
115+
Section section,
116+
ProgressTask task)
117+
{
118+
await foreach (var node in directory.Enumerate())
119+
{
120+
switch (node)
121+
{
122+
case IReadOnlyFile file:
123+
// Create a FileSystemContentSource for this specific file
124+
var contentSource = new FileSystemContentSource(_fileSystem, section.Version, section.Path);
125+
var contentType = _classifier.Classify(file);
126+
var contentItem = new ContentItem(contentSource, contentType, file);
127+
128+
task.Increment(1);
129+
yield return contentItem;
130+
break;
131+
132+
case IReadOnlyDirectory subDirectory:
133+
// Recursively yield from subdirectories
134+
await foreach (var subItem in CollectWorkingDirectoryContent(subDirectory, section, task))
135+
{
136+
yield return subItem;
137+
}
138+
break;
139+
}
140+
}
141+
}
142+
}

src/DocsTool/Catalogs/ContentAggregator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private ContentItem CreateContentItem(IContentSource source, IReadOnlyFile file)
8787
return ci;
8888
}
8989

90+
9091
private async IAsyncEnumerable<IContentSource> BuildSources(
9192
BuildContext context,
9293
[EnumeratorCancellation] CancellationToken cancellationToken)

src/DocsTool/Pipelines/DefaultPipeline.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static PipelineBuilder UseDefault(this PipelineBuilder builder)
1010
.Use<InitializeFileSystems>()
1111
.Use<AggregateContent>()
1212
.Use<CollectSections>()
13+
.Use<AugmentFilesSections>()
1314
.Use<BuildSite>()
1415
.Use<BuildUi>();
1516
}
@@ -19,6 +20,7 @@ public static IServiceCollection AddDefaultPipeline(this IServiceCollection serv
1920
services.AddSingleton<InitializeFileSystems>();
2021
services.AddSingleton<AggregateContent>();
2122
services.AddSingleton<CollectSections>();
23+
services.AddSingleton<AugmentFilesSections>();
2224
services.AddSingleton<BuildSite>();
2325
services.AddSingleton<BuildUi>();
2426

0 commit comments

Comments
 (0)