fix: resolve CI workflow failures (#117)

- Remove unsupported 'copilot-requests' and 'models' permissions from crawl-and-publish workflow (caused workflow file validation failure) - Add Hugo module mounts to exclude data/analyzed/*.md from Hugo's data loader (fixes 'unmarshal of format "" is not supported' build error) - Add nil-check guard to cost-dashboard shortcode for graceful degradation - Fix find_latest_summary() to resolve paths relative to root when no topic is specified (fixes test_find_latest_summary_uses_week_not_mtime) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed May 19, 2026 at 17:07 UTC a13f8b1cf887c945506208413799ac98d5ce1d8f
4 files changed +40 -7
.github/workflows/crawl-and-publish.yml
-3
@@ -153,8 +153,6 @@ jobs:
153 permissions:
154 actions: read
155 contents: write
156 - copilot-requests: write
157 - models: read
156 outputs:
157 week: ${{ steps.analysis-context.outputs.week }}
158 summary_file: ${{ steps.analysis-context.outputs.output_file }}
@@ -587,7 +585,6 @@ jobs:
585 runs-on: ubuntu-latest
586 permissions:
587 contents: write
590 - models: read
588
589 steps:
590 - uses: actions/checkout@v4
hugo.toml
+28
@@ -98,6 +98,34 @@ rssLimit = 20
98 url = '/index.xml'
99 weight = 80
100
101 +ignoreFiles = ['data/analyzed/.*\\.md$', 'data/metrics/']
102 +
103 +[module]
104 + [[module.mounts]]
105 + source = 'data/raw'
106 + target = 'data/raw'
107 + [[module.mounts]]
108 + source = 'data/snapshots'
109 + target = 'data/snapshots'
110 + [[module.mounts]]
111 + source = 'data/metrics'
112 + target = 'data/metrics'
113 + [[module.mounts]]
114 + source = 'content'
115 + target = 'content'
116 + [[module.mounts]]
117 + source = 'static'
118 + target = 'static'
119 + [[module.mounts]]
120 + source = 'layouts'
121 + target = 'layouts'
122 + [[module.mounts]]
123 + source = 'assets'
124 + target = 'assets'
125 + [[module.mounts]]
126 + source = 'archetypes'
127 + target = 'archetypes'
128 +
129 [markup]
130 [markup.goldmark.renderer]
131 unsafe = true
layouts/shortcodes/cost-dashboard.html
+5 -1
@@ -1,7 +1,10 @@
1 {{- $data := site.Data.metrics.cost_summary -}}
2 +{{- if not $data -}}
3 +<div class="cost-dashboard"><p>No cost data available yet.</p></div>
4 +{{- else -}}
5 {{- $weeks := $data.weeks -}}
6 {{- $cumulative := $data.cumulative_cost -}}
4 -{{- $budget := $data.budget_limit -}}
7 +{{- $budget := $data.budget_limit | default 1.0 -}}
8 {{- $pctUsed := mul (div $cumulative $budget) 100 -}}
9
10 <div class="cost-dashboard">
@@ -88,3 +91,4 @@
91 .cost-dashboard th, .cost-dashboard td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
92 .cost-dashboard th { background: #f5f5f5; }
93 </style>
94 +{{- end -}}
scripts/generate_content.py
+7 -3
@@ -63,10 +63,14 @@ def week_from_summary_path(path: Path) -> tuple[int, int]:
63
64 def find_latest_summary(root: Path, topic_id: str | None = None) -> Path:
65 search_dir = analyzed_dir(topic_id)
66 - candidates = list(search_dir.glob(f"*{SUMMARY_SUFFIX}"))
67 - if not candidates:
68 - # Fallback: try legacy path via root
66 + # When using default (relative) path, resolve via root for backward compat
67 + if topic_id is None:
68 candidates = list(root.glob(f"data/analyzed/*{SUMMARY_SUFFIX}"))
69 + else:
70 + candidates = list(search_dir.glob(f"*{SUMMARY_SUFFIX}"))
71 + if not candidates:
72 + # Fallback: try the other approach
73 + candidates = list(search_dir.glob(f"*{SUMMARY_SUFFIX}")) if topic_id is None else list(root.glob(f"data/analyzed/*{SUMMARY_SUFFIX}"))
74 if not candidates:
75 raise GenerationError("No analyzed summaries found under data/analyzed/.")
76 return max(candidates, key=week_from_summary_path)