Skip to content

Commit 833afb1

Browse files
committed
fix(ci): resolve anchor validation and extend experiments test coverage
Fix link_checker _text_to_anchor to match GitHub slugger behavior by replacing each space individually instead of collapsing consecutive whitespace. This resolves broken #security--compliance-patterns and #monitoring--maintenance-patterns anchor validation. Add 23 new tests covering experiments/README.md: pattern spec compliance, Mermaid diagram validation, code block syntax, link integrity, and examples index consistency. Add experimental_pattern_parser.py utility. Fix notify-failure job: add issues:write permission and upgrade actions/github-script from v6 to v7. Add /xaudit-experiments slash command prompt for manual LLM-powered deep audits of the experiments directory.
1 parent 47e72af commit 833afb1

30 files changed

Lines changed: 897 additions & 168 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Audit and fix the experiments/ directory (experiments/README.md, experiments/NOTES.md, and all experiments/examples/**/README.md plus their supporting files). Run these checks in parallel, then fix what you find:
2+
3+
1. Runnable code audit: For every fenced code block (python, bash, yaml, json), verify:
4+
- Python: imports exist in stdlib/PyPI, function signatures match real APIs, class definitions are syntactically valid. Flag datetime.utcnow(), Pydantic v1 syntax, and other deprecated calls.
5+
- Bash: commands and flags are real (no fabricated CLI options). Verify referenced file paths (e.g., scripts/promote-test.sh, .ai/hooks/protect-golden.sh) exist on disk where claimed.
6+
- YAML: valid syntax, correct GitHub Actions schema (uses: keys reference real actions at current major versions), Docker Compose v2 syntax (not v1 version: key).
7+
- Mermaid: valid diagram syntax (matching graph/sequenceDiagram/flowchart keywords, balanced brackets, no dangling edges).
8+
Prioritize anything that would fail if copy-pasted.
9+
10+
2. Pattern spec compliance (against pattern-spec.md): Each pattern in experiments/README.md must have:
11+
- Two-word Title Case name (per naming rules)
12+
- Maturity, Description, Related Patterns fields
13+
- At least one implementation section
14+
- An Anti-pattern section
15+
- All Related Patterns hyperlinked with working anchors
16+
Flag any pattern missing required sections or violating naming rules.
17+
18+
3. Internal consistency:
19+
- Cross-check the experiments/examples/README.md index against actual subdirectories in experiments/examples/ (flag missing or extra entries).
20+
- Verify that file references in any README (e.g., "see scripts/promote-test.sh") match actual filenames on disk.
21+
- Check that Mermaid diagram node labels match the pattern names used in prose and the reference table.
22+
23+
4. Link integrity: Verify every internal anchor link resolves correctly. Apply GitHub's anchor generation rules:
24+
- lowercase, spaces to hyphens, strip punctuation except hyphens
25+
- special chars like & are omitted (not double-dash)
26+
- duplicate anchors get -1, -2 suffixes
27+
Flag any broken anchor references.
28+
29+
5. Security review of examples: In all Python, Bash, and YAML files under experiments/examples/:
30+
- No hardcoded credentials, tokens, or API keys
31+
- Shell scripts quote variables and validate inputs
32+
- File operations check for path traversal (no unsanitized user input in open()/os.path.join())
33+
- Claims of read-only or network_mode: none are actually enforced in the code/config
34+
- File permission claims (e.g., chmod 444) match what the scripts actually set
35+
36+
For each issue found, fix it directly in the file rather than just reporting it. After all fixes, provide a summary grouped by check category with file:line references.

.github/workflows/deploy-pages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
uses: actions/configure-pages@v4
2626

2727
- name: Upload artifact
28-
uses: actions/upload-pages-artifact@v3
28+
uses: actions/upload-pages-artifact@v4
2929
with:
3030
path: '.'
3131

.github/workflows/pattern-validation.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,14 @@ jobs:
316316
notify-failure:
317317
runs-on: ubuntu-latest
318318
name: Notify on Failure
319+
permissions:
320+
issues: write
319321
needs: [pattern-compliance, readme-accuracy, link-validation, example-validation, dependency-validation, diagram-validation, yaml-readme-sync]
320322
if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main'
321-
323+
322324
steps:
323325
- name: Create Issue on Failure
324-
uses: actions/github-script@v6
326+
uses: actions/github-script@v7
325327
with:
326328
script: |
327329
const title = `Pattern Validation Failed - ${new Date().toISOString().split('T')[0]}`;

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,8 @@ git worktree remove ../agent-tests
12681268
**Shared Memory & Coordination**
12691269

12701270
```python
1271+
import fcntl
1272+
12711273
# Agent coordination with shared knowledge
12721274
class AgentMemory:
12731275
def record_learning(self, agent_id, key, value):
@@ -1760,15 +1762,15 @@ Combine with [Event Automation](#event-automation) to auto-load the right rules
17601762
FILE_PATH="$TOOL_INPUT_FILE_PATH"
17611763
LOADED_RULES=""
17621764

1763-
if echo "$FILE_PATH" | rg -q "(\\.env|credentials|secrets|auth/)"; then
1765+
if echo "$FILE_PATH" | grep -Eq "(\\.env|credentials|secrets|auth/)"; then
17641766
LOADED_RULES="$LOADED_RULES .ai/rules/security/"
17651767
fi
17661768

1767-
if echo "$FILE_PATH" | rg -q "(api/|routes/|controllers/)"; then
1769+
if echo "$FILE_PATH" | grep -Eq "(api/|routes/|controllers/)"; then
17681770
LOADED_RULES="$LOADED_RULES .ai/rules/development/api-design.md"
17691771
fi
17701772

1771-
if echo "$FILE_PATH" | rg -q "(tests?/|spec/|\\.test\\.|\\.spec\\.)"; then
1773+
if echo "$FILE_PATH" | grep -Eq "(tests?/|spec/|\\.test\\.|\\.spec\\.)"; then
17721774
LOADED_RULES="$LOADED_RULES .ai/rules/development/testing.md"
17731775
fi
17741776

examples/atomic-decomposition/task-decomposer.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import argparse
1111
import sys
1212
from datetime import datetime
13-
from typing import List, Dict, Any, Set
13+
from typing import Any
1414
from dataclasses import dataclass, asdict
1515
from pathlib import Path
1616

@@ -20,15 +20,15 @@ class TaskContract:
2020
id: str
2121
name: str
2222
description: str
23-
inputs: Dict[str, str]
24-
outputs: Dict[str, str]
25-
side_effects: List[str]
23+
inputs: dict[str, str]
24+
outputs: dict[str, str]
25+
side_effects: list[str]
2626
estimated_hours: float
27-
dependencies: List[str]
28-
acceptance_criteria: List[str]
29-
agent_requirements: List[str]
27+
dependencies: list[str]
28+
acceptance_criteria: list[str]
29+
agent_requirements: list[str]
3030

31-
def validate_atomic(self) -> List[str]:
31+
def validate_atomic(self) -> list[str]:
3232
"""Validate that task meets atomic criteria."""
3333
violations = []
3434

@@ -62,7 +62,7 @@ def __init__(self):
6262
self.min_hours = 0.5
6363
self.task_counter = 0
6464

65-
def decompose_feature(self, feature_description: str) -> List[TaskContract]:
65+
def decompose_feature(self, feature_description: str) -> list[TaskContract]:
6666
"""
6767
Decompose a feature into atomic tasks.
6868
@@ -84,7 +84,7 @@ def decompose_feature(self, feature_description: str) -> List[TaskContract]:
8484
else:
8585
return self._decompose_generic(feature_description)
8686

87-
def _decompose_authentication(self, description: str) -> List[TaskContract]:
87+
def _decompose_authentication(self, description: str) -> list[TaskContract]:
8888
"""Decompose authentication system into atomic tasks."""
8989
tasks = []
9090

@@ -204,7 +204,7 @@ def _decompose_authentication(self, description: str) -> List[TaskContract]:
204204

205205
return tasks
206206

207-
def _decompose_payment(self, description: str) -> List[TaskContract]:
207+
def _decompose_payment(self, description: str) -> list[TaskContract]:
208208
"""Decompose payment processing into atomic tasks."""
209209
tasks = []
210210

@@ -263,7 +263,7 @@ def _decompose_payment(self, description: str) -> List[TaskContract]:
263263

264264
return tasks
265265

266-
def _decompose_dashboard(self, description: str) -> List[TaskContract]:
266+
def _decompose_dashboard(self, description: str) -> list[TaskContract]:
267267
"""Decompose dashboard feature into atomic tasks."""
268268
tasks = []
269269

@@ -296,7 +296,7 @@ def _decompose_dashboard(self, description: str) -> List[TaskContract]:
296296

297297
return tasks
298298

299-
def _decompose_generic(self, description: str) -> List[TaskContract]:
299+
def _decompose_generic(self, description: str) -> list[TaskContract]:
300300
"""Generic decomposition for unknown features."""
301301
tasks = []
302302

@@ -326,7 +326,7 @@ def _next_task_id(self) -> str:
326326
self.task_counter += 1
327327
return f"task-{self.task_counter:03d}"
328328

329-
def validate_decomposition(self, tasks: List[TaskContract]) -> Dict[str, Any]:
329+
def validate_decomposition(self, tasks: list[TaskContract]) -> dict[str, Any]:
330330
"""Validate entire task decomposition for atomicity."""
331331
validation_report = {
332332
"total_tasks": len(tasks),
@@ -366,7 +366,7 @@ def validate_decomposition(self, tasks: List[TaskContract]) -> Dict[str, Any]:
366366

367367
return validation_report
368368

369-
def optimize_for_parallelization(self, tasks: List[TaskContract]) -> List[TaskContract]:
369+
def optimize_for_parallelization(self, tasks: list[TaskContract]) -> list[TaskContract]:
370370
"""Analyze and suggest optimizations for better parallelization."""
371371
optimized_tasks = []
372372

@@ -384,7 +384,7 @@ def optimize_for_parallelization(self, tasks: List[TaskContract]) -> List[TaskCo
384384

385385
return optimized_tasks
386386

387-
def generate_execution_plan(self, tasks: List[TaskContract], num_agents: int = 3) -> Dict[str, Any]:
387+
def generate_execution_plan(self, tasks: list[TaskContract], num_agents: int = 3) -> dict[str, Any]:
388388
"""Generate execution plan for parallel agents."""
389389
# Simple topological sort for dependency ordering
390390
dependencies = {task.id: task.dependencies for task in tasks}

examples/guided-refactoring/quality-metrics.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
import sys
1111
from datetime import datetime
1212
from pathlib import Path
13-
from typing import Dict, Any
13+
from typing import Any
1414

1515
class QualityMetrics:
1616
def __init__(self, source_dir: str = "src"):
1717
self.source_dir = source_dir
1818
self.metrics_file = Path("quality_metrics.json")
1919

20-
def measure_complexity(self) -> Dict[str, Any]:
20+
def measure_complexity(self) -> dict[str, Any]:
2121
"""Measure cyclomatic complexity using radon."""
2222
try:
2323
result = subprocess.run(
@@ -50,7 +50,7 @@ def measure_complexity(self) -> Dict[str, Any]:
5050
except (subprocess.CalledProcessError, FileNotFoundError):
5151
return {"error": "radon not available"}
5252

53-
def measure_coverage(self) -> Dict[str, Any]:
53+
def measure_coverage(self) -> dict[str, Any]:
5454
"""Measure test coverage using coverage.py."""
5555
try:
5656
# Run tests with coverage
@@ -76,7 +76,7 @@ def measure_coverage(self) -> Dict[str, Any]:
7676
except (subprocess.CalledProcessError, FileNotFoundError):
7777
return {"error": "coverage not available"}
7878

79-
def measure_duplication(self) -> Dict[str, Any]:
79+
def measure_duplication(self) -> dict[str, Any]:
8080
"""Measure code duplication using pylint."""
8181
try:
8282
result = subprocess.run(
@@ -99,7 +99,7 @@ def measure_duplication(self) -> Dict[str, Any]:
9999
except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError):
100100
return {"error": "pylint not available"}
101101

102-
def measure_maintainability(self) -> Dict[str, Any]:
102+
def measure_maintainability(self) -> dict[str, Any]:
103103
"""Measure maintainability index using radon."""
104104
try:
105105
result = subprocess.run(
@@ -136,7 +136,7 @@ def _grade_maintainability(self, mi_score: float) -> str:
136136
elif mi_score >= 25: return "D"
137137
else: return "F"
138138

139-
def collect_all_metrics(self) -> Dict[str, Any]:
139+
def collect_all_metrics(self) -> dict[str, Any]:
140140
"""Collect all quality metrics."""
141141
return {
142142
"timestamp": datetime.now().isoformat(),
@@ -201,7 +201,7 @@ def measure_improvement(self):
201201
# Calculate and display improvements
202202
self._display_improvements(baseline, current)
203203

204-
def _display_improvements(self, baseline: Dict, current: Dict):
204+
def _display_improvements(self, baseline: dict, current: dict):
205205
"""Display improvement comparison."""
206206
print("\n📊 Refactoring Impact Analysis")
207207
print("=" * 50)
@@ -246,7 +246,7 @@ def _display_improvements(self, baseline: Dict, current: Dict):
246246
print(f"\n✅ Metrics collected at: {current['timestamp']}")
247247
print(f"📁 Data saved to: {self.metrics_file}")
248248

249-
def _print_metrics_summary(self, metrics: Dict):
249+
def _print_metrics_summary(self, metrics: dict):
250250
"""Print a summary of current metrics."""
251251
print("\n📊 Current Quality Metrics")
252252
print("=" * 30)

examples/issue-generation/issue-generator.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
import sys
1212
from datetime import datetime
1313
from pathlib import Path
14-
from typing import List, Dict, Any
14+
from typing import Any
1515

1616
class KanbanIssueGenerator:
1717
def __init__(self):
1818
self.max_task_hours = 8
1919
self.min_task_hours = 2
2020
self.templates_dir = Path("kanban-templates")
2121

22-
def generate_feature_issues(self, feature_description: str, platform: str = "github") -> List[Dict[str, Any]]:
22+
def generate_feature_issues(self, feature_description: str, platform: str = "github") -> list[dict[str, Any]]:
2323
"""
2424
Generate Kanban-ready issues for a feature request.
2525
@@ -51,7 +51,7 @@ def generate_feature_issues(self, feature_description: str, platform: str = "git
5151

5252
return issues
5353

54-
def _create_epic_issue(self, feature_description: str, platform: str) -> Dict[str, Any]:
54+
def _create_epic_issue(self, feature_description: str, platform: str) -> dict[str, Any]:
5555
"""Create an epic-level issue for the feature."""
5656
template = self._load_template("epic", platform)
5757

@@ -84,7 +84,7 @@ def _create_epic_issue(self, feature_description: str, platform: str) -> Dict[st
8484

8585
return self._apply_platform_format(epic, platform)
8686

87-
def _break_down_feature(self, feature_description: str, platform: str) -> List[Dict[str, Any]]:
87+
def _break_down_feature(self, feature_description: str, platform: str) -> list[dict[str, Any]]:
8888
"""Break down feature into Kanban-optimized stories (4-8 hours each)."""
8989

9090
# Example breakdown for "Password reset via email"
@@ -95,7 +95,7 @@ def _break_down_feature(self, feature_description: str, platform: str) -> List[D
9595
else:
9696
return self._generic_breakdown(feature_description, platform)
9797

98-
def _password_reset_breakdown(self, platform: str) -> List[Dict[str, Any]]:
98+
def _password_reset_breakdown(self, platform: str) -> list[dict[str, Any]]:
9999
"""Specific breakdown for password reset feature."""
100100
stories = [
101101
{
@@ -196,7 +196,7 @@ def _password_reset_breakdown(self, platform: str) -> List[Dict[str, Any]]:
196196

197197
return [self._apply_platform_format(story, platform) for story in stories]
198198

199-
def _dashboard_breakdown(self, platform: str) -> List[Dict[str, Any]]:
199+
def _dashboard_breakdown(self, platform: str) -> list[dict[str, Any]]:
200200
"""Breakdown for dashboard features."""
201201
stories = [
202202
{
@@ -239,7 +239,7 @@ def _dashboard_breakdown(self, platform: str) -> List[Dict[str, Any]]:
239239

240240
return [self._apply_platform_format(story, platform) for story in stories]
241241

242-
def _generic_breakdown(self, feature_description: str, platform: str) -> List[Dict[str, Any]]:
242+
def _generic_breakdown(self, feature_description: str, platform: str) -> list[dict[str, Any]]:
243243
"""Generic breakdown for unknown features."""
244244
stories = [
245245
{
@@ -288,7 +288,7 @@ def _generic_breakdown(self, feature_description: str, platform: str) -> List[Di
288288

289289
return [self._apply_platform_format(story, platform) for story in stories]
290290

291-
def _generate_technical_tasks(self, feature_description: str, platform: str) -> List[Dict[str, Any]]:
291+
def _generate_technical_tasks(self, feature_description: str, platform: str) -> list[dict[str, Any]]:
292292
"""Generate supporting technical tasks."""
293293
tasks = [
294294
{
@@ -337,7 +337,7 @@ def _generate_technical_tasks(self, feature_description: str, platform: str) ->
337337

338338
return [self._apply_platform_format(task, platform) for task in tasks]
339339

340-
def _apply_platform_format(self, issue: Dict[str, Any], platform: str) -> Dict[str, Any]:
340+
def _apply_platform_format(self, issue: dict[str, Any], platform: str) -> dict[str, Any]:
341341
"""Apply platform-specific formatting."""
342342
if platform == "github":
343343
return self._format_for_github(issue)
@@ -348,7 +348,7 @@ def _apply_platform_format(self, issue: Dict[str, Any], platform: str) -> Dict[s
348348
else:
349349
return issue
350350

351-
def _format_for_github(self, issue: Dict[str, Any]) -> Dict[str, Any]:
351+
def _format_for_github(self, issue: dict[str, Any]) -> dict[str, Any]:
352352
"""Format issue for GitHub Issues API."""
353353
github_issue = {
354354
"title": issue["title"],
@@ -364,7 +364,7 @@ def _format_for_github(self, issue: Dict[str, Any]) -> Dict[str, Any]:
364364

365365
return github_issue
366366

367-
def _format_for_jira(self, issue: Dict[str, Any]) -> Dict[str, Any]:
367+
def _format_for_jira(self, issue: dict[str, Any]) -> dict[str, Any]:
368368
"""Format issue for JIRA API."""
369369
return {
370370
"fields": {
@@ -380,7 +380,7 @@ def _format_for_jira(self, issue: Dict[str, Any]) -> Dict[str, Any]:
380380
}
381381
}
382382

383-
def _format_for_azure(self, issue: Dict[str, Any]) -> Dict[str, Any]:
383+
def _format_for_azure(self, issue: dict[str, Any]) -> dict[str, Any]:
384384
"""Format issue for Azure DevOps API."""
385385
return {
386386
"op": "add",
@@ -394,15 +394,15 @@ def _format_for_azure(self, issue: Dict[str, Any]) -> Dict[str, Any]:
394394
}
395395
}
396396

397-
def _load_template(self, template_type: str, platform: str) -> Dict[str, Any]:
397+
def _load_template(self, template_type: str, platform: str) -> dict[str, Any]:
398398
"""Load issue template from file."""
399399
template_file = self.templates_dir / f"{template_type}-template.json"
400400
if template_file.exists():
401401
with open(template_file) as f:
402402
return json.load(f)
403403
return {}
404404

405-
def validate_kanban_readiness(self, issues: List[Dict[str, Any]]) -> List[str]:
405+
def validate_kanban_readiness(self, issues: list[dict[str, Any]]) -> list[str]:
406406
"""Validate that issues meet Kanban flow criteria."""
407407
warnings = []
408408

0 commit comments

Comments
 (0)