Skip to content

Commit 71f5539

Browse files
jeremyederclaude
andcommitted
feat: Phase 2 Task 5 - Reduce template duplication via Jinja2 inheritance
Consolidates Bootstrap templates using inheritance, reducing duplication and improving maintainability. Implements base template infrastructure for Python, JavaScript, and Go language support. Changes: - Create base templates in templates/bootstrap/_base/ - workflows/tests.yml.j2 (common test workflow skeleton) - workflows/security.yml.j2 (common security scan structure) - precommit.yaml.j2 (common pre-commit hook structure) - Refactor language-specific templates to use {% extends %} - Python, JavaScript, Go templates use {% block %} overrides for: * setup_environment (e.g., actions/setup-python@v5) * install_dependencies (e.g., pip install -e ".[dev]") * run_tests (e.g., pytest --cov) * run_linters (e.g., black, eslint, gofmt) * language_security (e.g., safety, npm audit, gosec) - Update Bootstrap service to use new template paths - Changed from workflows/tests-{language}.yml.j2 - To {language}/workflows/tests.yml.j2 - Fallback to python if language template missing - Add comprehensive template inheritance tests - 11 tests covering all template × language combinations - DRY compliance tests verify no structure duplication - All 24 Bootstrap tests pass (no regressions) Benefits: - Easier maintenance (update base → affects all languages) - Guaranteed consistency across languages - Cleaner diffs for language-specific changes - Faster new language support (3 template files + blocks) LOC Impact: - Language templates: ~350 lines → ~190 lines (45% reduction) - Template structure: Inheritance-based, highly maintainable - Test coverage: +259 lines of comprehensive tests Tests: All 24 Bootstrap tests pass (11 new, 13 integration) End-to-end: Verified Python and JavaScript bootstrap generation Phase 2 Task 5 of 6 complete. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 396e79d commit 71f5539

File tree

14 files changed

+550
-65
lines changed

14 files changed

+550
-65
lines changed

src/agentready/services/bootstrap.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def _generate_workflows(self, dry_run: bool) -> List[Path]:
7878
# Determine test workflow language (fallback to python if template doesn't exist)
7979
test_language = self.language
8080
try:
81-
self.env.get_template(f"workflows/tests-{test_language}.yml.j2")
81+
self.env.get_template(f"{test_language}/workflows/tests.yml.j2")
8282
except Exception:
8383
# Template doesn't exist, fallback to python
8484
test_language = "python"
@@ -91,14 +91,14 @@ def _generate_workflows(self, dry_run: bool) -> List[Path]:
9191

9292
# Tests workflow
9393
tests_workflow = workflows_dir / "tests.yml"
94-
template = self.env.get_template(f"workflows/tests-{test_language}.yml.j2")
94+
template = self.env.get_template(f"{test_language}/workflows/tests.yml.j2")
9595
content = template.render()
9696
created.append(self._write_file(tests_workflow, content, dry_run))
9797

9898
# Security workflow
9999
security_workflow = workflows_dir / "security.yml"
100-
template = self.env.get_template("workflows/security.yml.j2")
101-
content = template.render(language=test_language)
100+
template = self.env.get_template(f"{test_language}/workflows/security.yml.j2")
101+
content = template.render()
102102
created.append(self._write_file(security_workflow, content, dry_run))
103103

104104
return created
@@ -141,11 +141,11 @@ def _generate_precommit_config(self, dry_run: bool) -> List[Path]:
141141
# Determine language for template (fallback to python)
142142
template_language = self.language
143143
try:
144-
template = self.env.get_template(f"precommit-{template_language}.yaml.j2")
144+
template = self.env.get_template(f"{template_language}/precommit.yaml.j2")
145145
except Exception:
146146
# Template doesn't exist, fallback to python
147147
template_language = "python"
148-
template = self.env.get_template(f"precommit-{template_language}.yaml.j2")
148+
template = self.env.get_template(f"{template_language}/precommit.yaml.j2")
149149

150150
content = template.render()
151151
return [self._write_file(precommit_file, content, dry_run)]

src/agentready/templates/bootstrap/precommit-python.yaml.j2 renamed to src/agentready/templates/bootstrap/_base/precommit.yaml.j2

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{# Base pre-commit configuration - common hooks for all languages #}
12
repos:
23
- repo: https://github.com/pre-commit/pre-commit-hooks
34
rev: v4.5.0
@@ -11,23 +12,13 @@ repos:
1112
- id: check-json
1213
- id: detect-private-key
1314

14-
- repo: https://github.com/psf/black
15-
rev: 24.1.1
16-
hooks:
17-
- id: black
18-
language_version: python3.12
19-
20-
- repo: https://github.com/pycqa/isort
21-
rev: 5.13.2
22-
hooks:
23-
- id: isort
24-
args: ["--profile", "black"]
15+
{% block language_formatters %}
16+
{# Language-specific formatters and linters (black, prettier, gofmt, etc.) #}
17+
{% endblock %}
2518

26-
- repo: https://github.com/astral-sh/ruff-pre-commit
27-
rev: v0.1.14
28-
hooks:
29-
- id: ruff
30-
args: ["--fix"]
19+
{% block language_linters %}
20+
{# Language-specific linters (ruff, eslint, golangci-lint, etc.) #}
21+
{% endblock %}
3122

3223
- repo: https://github.com/compilerla/conventional-pre-commit
3324
rev: v3.0.0

src/agentready/templates/bootstrap/workflows/security.yml.j2 renamed to src/agentready/templates/bootstrap/_base/workflows/security.yml.j2

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{# Base GitHub Actions security workflow - common structure for all languages #}
12
name: Security
23

34
on:
@@ -24,33 +25,14 @@ jobs:
2425
- name: Initialize CodeQL
2526
uses: github/codeql-action/init@v3
2627
with:
27-
languages: {{ 'python' if language == 'python' else 'javascript' }}
28+
languages: {% block codeql_language %}{{ 'python' }}{% endblock %}
2829

2930
- name: Autobuild
3031
uses: github/codeql-action/autobuild@v3
3132

3233
- name: Perform CodeQL Analysis
3334
uses: github/codeql-action/analyze@v3
3435

35-
{% if language == 'python' %}
36-
safety:
37-
name: Dependency Security Scan
38-
runs-on: ubuntu-latest
39-
40-
steps:
41-
- name: Checkout code
42-
uses: actions/checkout@v4
43-
44-
- name: Set up Python
45-
uses: actions/setup-python@v5
46-
with:
47-
python-version: '3.12'
48-
49-
- name: Install dependencies
50-
run: |
51-
pip install safety
52-
53-
- name: Run safety check
54-
run: |
55-
safety check --json || true
56-
{% endif %}
36+
{% block language_security %}
37+
{# Language-specific security scanning (e.g., safety for Python, npm audit for JS) #}
38+
{% endblock %}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{# Base GitHub Actions test workflow - common structure for all languages #}
2+
name: Tests
3+
4+
on:
5+
pull_request:
6+
push:
7+
branches: [main, master]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
{% block strategy %}
14+
{# Language-specific matrix strategy (e.g., Python versions) #}
15+
{% endblock %}
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
{% block setup_environment %}
22+
{# Language-specific environment setup (Python, Node, Go, etc.) #}
23+
{% endblock %}
24+
25+
{% block install_dependencies %}
26+
{# Language-specific dependency installation #}
27+
{% endblock %}
28+
29+
{% block run_linters %}
30+
{# Language-specific linters and formatters #}
31+
{% endblock %}
32+
33+
{% block run_tests %}
34+
{# Language-specific test command #}
35+
{% endblock %}
36+
37+
{% block upload_coverage %}
38+
- name: Upload coverage to Codecov
39+
uses: codecov/codecov-action@v4
40+
{% block coverage_condition %}if: true{% endblock %}
41+
with:
42+
files: ./coverage.xml
43+
fail_ci_if_error: false
44+
{% endblock %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends "_base/precommit.yaml.j2" %}
2+
3+
{% block language_formatters %}
4+
- repo: https://github.com/dnephin/pre-commit-golang
5+
rev: v0.5.1
6+
hooks:
7+
- id: go-fmt
8+
- id: go-imports
9+
{% endblock %}
10+
11+
{% block language_linters %}
12+
- repo: https://github.com/golangci/golangci-lint
13+
rev: v1.55.2
14+
hooks:
15+
- id: golangci-lint
16+
args: ['--fix']
17+
18+
- repo: https://github.com/dnephin/pre-commit-golang
19+
rev: v0.5.1
20+
hooks:
21+
- id: go-vet
22+
- id: go-unit-tests
23+
{% endblock %}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends "_base/workflows/security.yml.j2" %}
2+
3+
{% block codeql_language %}go{% endblock %}
4+
5+
{% block language_security %}
6+
gosec:
7+
name: Go Security Scanner
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@v4
13+
14+
- name: Set up Go
15+
uses: actions/setup-go@v5
16+
with:
17+
go-version: '1.22'
18+
19+
- name: Run Gosec Security Scanner
20+
uses: securego/gosec@master
21+
with:
22+
args: '-no-fail -fmt sarif -out results.sarif ./...'
23+
{% endblock %}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{% extends "_base/workflows/tests.yml.j2" %}
2+
3+
{% block strategy %}
4+
strategy:
5+
matrix:
6+
go-version: ['1.21', '1.22']
7+
{% endblock %}
8+
9+
{% block setup_environment %}
10+
- name: Set up Go {{ '${{ matrix.go-version }}' }}
11+
uses: actions/setup-go@v5
12+
with:
13+
go-version: {{ '${{ matrix.go-version }}' }}
14+
cache: true
15+
{% endblock %}
16+
17+
{% block install_dependencies %}
18+
- name: Download dependencies
19+
run: go mod download
20+
{% endblock %}
21+
22+
{% block run_linters %}
23+
- name: Run gofmt
24+
run: |
25+
gofmt -s -w .
26+
git diff --exit-code
27+
28+
- name: Run go vet
29+
run: go vet ./...
30+
31+
- name: Run golangci-lint
32+
uses: golangci/golangci-lint-action@v3
33+
with:
34+
version: latest
35+
{% endblock %}
36+
37+
{% block run_tests %}
38+
- name: Run tests
39+
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
40+
{% endblock %}
41+
42+
{% block coverage_condition %}if: matrix.go-version == '1.22'{% endblock %}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% extends "_base/precommit.yaml.j2" %}
2+
3+
{% block language_formatters %}
4+
- repo: https://github.com/pre-commit/mirrors-prettier
5+
rev: v3.1.0
6+
hooks:
7+
- id: prettier
8+
types_or: [javascript, jsx, ts, tsx, json, yaml, markdown]
9+
{% endblock %}
10+
11+
{% block language_linters %}
12+
- repo: https://github.com/pre-commit/mirrors-eslint
13+
rev: v8.56.0
14+
hooks:
15+
- id: eslint
16+
files: \.(js|jsx|ts|tsx)$
17+
additional_dependencies:
18+
- eslint@8.56.0
19+
- eslint-config-prettier@9.1.0
20+
{% endblock %}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% extends "_base/workflows/security.yml.j2" %}
2+
3+
{% block codeql_language %}javascript{% endblock %}
4+
5+
{% block language_security %}
6+
npm-audit:
7+
name: NPM Audit
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@v4
13+
14+
- name: Set up Node.js
15+
uses: actions/setup-node@v4
16+
with:
17+
node-version: '20'
18+
cache: 'npm'
19+
20+
- name: Install dependencies
21+
run: npm ci
22+
23+
- name: Run npm audit
24+
run: npm audit --audit-level=moderate || true
25+
{% endblock %}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{% extends "_base/workflows/tests.yml.j2" %}
2+
3+
{% block strategy %}
4+
strategy:
5+
matrix:
6+
node-version: ['18', '20', '22']
7+
{% endblock %}
8+
9+
{% block setup_environment %}
10+
- name: Set up Node.js {{ '${{ matrix.node-version }}' }}
11+
uses: actions/setup-node@v4
12+
with:
13+
node-version: {{ '${{ matrix.node-version }}' }}
14+
cache: 'npm'
15+
{% endblock %}
16+
17+
{% block install_dependencies %}
18+
- name: Install dependencies
19+
run: npm ci
20+
{% endblock %}
21+
22+
{% block run_linters %}
23+
- name: Run ESLint
24+
run: npm run lint
25+
26+
- name: Run Prettier
27+
run: npm run format:check
28+
{% endblock %}
29+
30+
{% block run_tests %}
31+
- name: Run tests
32+
run: npm test -- --coverage
33+
{% endblock %}
34+
35+
{% block coverage_condition %}if: matrix.node-version == '20'{% endblock %}

0 commit comments

Comments
 (0)