
Cass
- 2.6k installs
- 1k repo stars
- Updated August 5, 2026
- dicklesworthstone/coding_agent_session_search
cass is an agent skill that Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Ge.
About
Unified high performance CLI TUI to index and search your local coding agent history Aggregates sessions from 11 agents Codex Claude Code Gemini CLI Cline OpenCode Amp Cursor ChatGPT Aider Pi Agent and Factory Droid CRITICAL Robot Mode Required for AI Agents NEVER run bare cass it launches an interactive TUI that blocks your session CORRECT JSON output for agents cass search query robot cass search query json alias The cass skill documents workflows prerequisites and usage patterns grounded in its repository SKILL md Agents should follow the documented steps respect safety and permission notes and cite only capabilities described in the source It triggers on phrases matching the skill description and integrates with the agent toolchain for the tasks outlined in the documentation
- description: "Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude C
- Unified, high-performance CLI/TUI to index and search your local coding agent history. Aggregates sessions from **11 age
- **NEVER run bare `cass`** - it launches an interactive TUI that blocks your session!
- See SKILL.md for cass operational details.
- See SKILL.md for cass operational details.
Cass by the numbers
- 2,613 all-time installs (skills.sh)
- +118 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #320 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
cass capabilities & compatibility
- Capabilities
- description: "coding agent session search unif · unified, high performance cli/tui to index and s · **never run bare `cass`** it launches an inter · see skill.md for cass operational details.
- Use cases
- orchestration
What cass says it does
description: "Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Gemini, Cursor, Aider, ChatGPT, Pi-Agent, Factory, and more. Purpose
Unified, high-performance CLI/TUI to index and search your local coding agent history. Aggregates sessions from **11 agents**: Codex, Claude Code, Gemini CLI, Cline, OpenCode, Amp, Cursor, ChatGPT, Ai
**NEVER run bare `cass`** - it launches an interactive TUI that blocks your session!
npx skills add https://github.com/dicklesworthstone/coding_agent_session_search --skill cassAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.6k |
|---|---|
| repo stars | ★ 1k |
| Security audit | 0 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | dicklesworthstone/coding_agent_session_search ↗ |
What does cass help with and when should an agent load it?
Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Gemini, Cursor, Aider, ChatGPT, Pi-Agent, Factory, and more. Purpose-built for AI
Who is it for?
Developers using cass as documented in the skill repository.
Skip if: Skip when the task falls outside the cass documented scope.
When should I use this skill?
Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Gemini, Cursor, Aider, ChatGPT, Pi-Agent, Factory, and more. Purpose-built for AI
What you get
Agent actions aligned with the cass SKILL.md workflow and documented deliverables.
- Aligned agent session context tied to current repo tasks
Files
CASS - Coding Agent Session Search
Unified, high-performance CLI/TUI to index and search your local coding agent history. Aggregates sessions from 11 agents: Codex, Claude Code, Gemini CLI, Cline, OpenCode, Amp, Cursor, ChatGPT, Aider, Pi-Agent, and Factory (Droid).
CRITICAL: Robot Mode Required for AI Agents
NEVER run bare `cass` - it launches an interactive TUI that blocks your session!
# WRONG - blocks terminal
cass
# CORRECT - JSON output for agents
cass search "query" --robot
cass search "query" --json # aliasAlways use `--robot` or `--json` flags for machine-readable output.
---
Quick Reference for AI Agents
Pre-Flight Check
# Health check (exit 0=healthy, 1=unhealthy, <50ms)
cass health
# If unhealthy, rebuild index
cass index --fullEssential Commands
# Find the current session for this workspace
cass sessions --current --json
# List recent sessions for a specific project
cass sessions --workspace "$(pwd)" --json --limit 5
# Common agent flow: find current session, then export it
cass export-html "$(cass sessions --current --json | jq -r '.sessions[0].path')" --json
# Search with JSON output
cass search "authentication error" --robot --limit 5
# Search with metadata (elapsed_ms, cache stats, freshness)
cass search "error" --robot --robot-meta
# Minimal payload (path, line, agent only)
cass search "bug" --robot --fields minimal
# View source at specific line
cass view /path/to/session.jsonl -n 42 --json
# Expand context around a line
cass expand /path/to/session.jsonl -n 42 -C 5 --json
# Capabilities discovery
cass capabilities --json
# Full API schema
cass introspect --json
# LLM-optimized documentation
cass robot-docs guide
cass robot-docs commands
cass robot-docs schemas
cass robot-docs examples
cass robot-docs exit-codes---
Why Use CASS
Cross-Agent Knowledge Transfer
Your coding agents create scattered knowledge:
- Claude Code sessions in
~/.claude/projects - Codex sessions in
~/.codex/sessions - Cursor state in SQLite databases
- Aider history in markdown files
CASS unifies all of this into a single searchable index. When you're stuck on a problem, search across ALL your past agent sessions to find relevant solutions.
Use Cases
# "I solved this before..."
cass search "TypeError: Cannot read property" --robot --days 30
# Cross-agent learning (what has ANY agent said about X?)
cass search "authentication" --robot --workspace /path/to/project
# Agent-to-agent handoff
cass search "database migration" --robot --fields summary
# Daily review
cass timeline --today --json---
Command Reference
Indexing
# Full rebuild of DB and search index
cass index --full
# Incremental update (since last scan)
cass index
# Watch mode: auto-reindex on file changes
cass index --watch
# Force rebuild even if schema unchanged
cass index --full --force-rebuild
# Safe retries with idempotency key (24h TTL)
cass index --full --idempotency-key "build-$(date +%Y%m%d)"
# JSON output with stats
cass index --full --jsonSearch
# Basic search (JSON output required for agents!)
cass search "query" --robot
# With filters
cass search "error" --robot --agent claude --days 7
cass search "bug" --robot --workspace /path/to/project
cass search "panic" --robot --today
# Time filters
cass search "auth" --robot --since 2024-01-01 --until 2024-01-31
cass search "test" --robot --yesterday
cass search "fix" --robot --week
# Wildcards
cass search "auth*" --robot # prefix: authentication, authorize
cass search "*tion" --robot # suffix: authentication, exception
cass search "*config*" --robot # substring: misconfigured
# Token budget management (critical for LLMs!)
cass search "error" --robot --fields minimal # path, line, agent only
cass search "error" --robot --fields summary # adds title, score
cass search "error" --robot --max-content-length 500 # truncate fields
cass search "error" --robot --max-tokens 2000 # soft budget (~4 chars/token)
cass search "error" --robot --limit 5 # cap results
# Pagination (cursor-based)
cass search "TODO" --robot --robot-meta --limit 20
# Use _meta.next_cursor from response:
cass search "TODO" --robot --robot-meta --limit 20 --cursor "eyJ..."
# Match highlighting
cass search "authentication error" --robot --highlight
# Query analysis/debugging
cass search "auth*" --robot --explain # parsed query, cost estimates
cass search "auth error" --robot --dry-run # validate without executing
# Aggregations (server-side counts)
cass search "error" --robot --aggregate agent,workspace,date
# Request correlation
cass search "bug" --robot --request-id "req-12345"
# Source filtering (for multi-machine setups)
cass search "auth" --robot --source laptop
cass search "error" --robot --source remote
# Traceability (for debugging agent pipelines)
cass search "error" --robot --trace-file /tmp/cass-trace.jsonSession Analysis
# Export conversation to markdown/HTML/JSON
cass export /path/to/session.jsonl --format markdown -o conversation.md
cass export /path/to/session.jsonl --format html -o conversation.html
cass export /path/to/session.jsonl --format json --include-tools
# Expand context around a line (from search result)
cass expand /path/to/session.jsonl -n 42 -C 5 --json
# Shows 5 messages before and after line 42
# View source at line
cass view /path/to/session.jsonl -n 42 --json
# Activity timeline
cass timeline --today --json --group-by hour
cass timeline --days 7 --json --agent claude
cass timeline --since 7d --json
# Find related sessions for a file
cass context /path/to/source.ts --jsonStatus & Diagnostics
# Quick health (<50ms)
cass health
cass health --json
# Full status snapshot
cass status --json
cass state --json # alias
# Statistics
cass stats --json
cass stats --by-source # for multi-machine
# Full diagnostics
cass diag --verbose---
Aggregation & Analytics
Aggregate search results server-side to get counts and distributions without transferring full result data:
# Count results by agent
cass search "error" --robot --aggregate agent
# → { "aggregations": { "agent": { "buckets": [{"key": "claude_code", "count": 45}, ...] } } }
# Multi-field aggregation
cass search "bug" --robot --aggregate agent,workspace,date
# Combine with filters
cass search "TODO" --agent claude --robot --aggregate workspace| Aggregation Field | Description |
|---|---|
agent | Group by agent type (claude_code, codex, cursor, etc.) |
workspace | Group by workspace/project path |
date | Group by date (YYYY-MM-DD) |
match_type | Group by match quality (exact, prefix, fuzzy) |
Top 10 buckets returned per field, with other_count for remaining items.
---
Remote Sources (Multi-Machine Search)
Search across sessions from multiple machines via SSH/rsync.
Setup Wizard (Recommended)
cass sources setupThe wizard: 1. Discovers SSH hosts from ~/.ssh/config 2. Probes each for agent data and cass installation 3. Optionally installs cass on remotes 4. Indexes sessions on remotes 5. Configures sources.toml 6. Syncs data locally
cass sources setup --hosts css,csd,yto # Specific hosts only
cass sources setup --dry-run # Preview without changes
cass sources setup --resume # Resume interrupted setupManual Setup
# Add a remote machine
cass sources add user@laptop.local --preset macos-defaults
cass sources add dev@workstation --path ~/.claude/projects --path ~/.codex/sessions
# List sources
cass sources list --json
# Sync sessions
cass sources sync
cass sources sync --source laptop --verbose
# Check connectivity
cass sources doctor
cass sources doctor --source laptop --json
# Path mappings (rewrite remote paths to local)
cass sources mappings list laptop
cass sources mappings add laptop --from /home/user/projects --to /Users/me/projects
cass sources mappings test laptop /home/user/projects/myapp/src/main.rs
# Remove source
cass sources remove laptop --purge -yConfiguration stored in ~/.config/cass/sources.toml (Linux) or ~/Library/Application Support/cass/sources.toml (macOS).
---
Robot Mode Deep Dive
Self-Documenting API
CASS teaches agents how to use itself:
# Quick capability check
cass capabilities --json
# Returns: features, connectors, limits
# Full API schema
cass introspect --json
# Returns: all commands, arguments, response shapes
# Topic-based docs (LLM-optimized)
cass robot-docs commands # all commands and flags
cass robot-docs schemas # response JSON schemas
cass robot-docs examples # copy-paste invocations
cass robot-docs exit-codes # error handling
cass robot-docs guide # quick-start walkthrough
cass robot-docs contracts # API versioning
cass robot-docs sources # remote sources guideForgiving Syntax (Agent-Friendly)
CASS auto-corrects common mistakes:
| What you type | What CASS understands |
|---|---|
cass serach "error" | cass search "error" (typo corrected) |
cass -robot -limit=5 | cass --robot --limit=5 (single-dash fixed) |
cass --Robot --LIMIT 5 | cass --robot --limit 5 (case normalized) |
cass find "auth" | cass search "auth" (alias resolved) |
cass --limt 5 | cass --limit 5 (Levenshtein <=2) |
Command Aliases:
find,query,q,lookup,grep→searchls,list,info,summary→statsst,state→statusreindex,idx,rebuild→indexshow,get,read→viewdocs,help-robot,robotdocs→robot-docs
Output Formats
# Pretty-printed JSON (default)
cass search "error" --robot
# Streaming JSONL (header + one hit per line)
cass search "error" --robot-format jsonl
# Compact single-line JSON
cass search "error" --robot-format compact
# With performance metadata
cass search "error" --robot --robot-metaDesign principle: stdout = JSON only; diagnostics go to stderr.
Token Budget Management
LLMs have context limits. Control output size:
| Flag | Effect |
|---|---|
--fields minimal | Only source_path, line_number, agent |
--fields summary | Adds title, score |
--fields score,title,snippet | Custom field selection |
--max-content-length 500 | Truncate long fields (UTF-8 safe) |
--max-tokens 2000 | Soft budget (~4 chars/token) |
--limit 5 | Cap number of results |
Truncated fields include *_truncated: true indicator.
---
Structured Error Handling
Errors are JSON with actionable hints:
{
"error": {
"code": 3,
"kind": "index_missing",
"message": "Search index not found",
"hint": "Run 'cass index --full' to build the index",
"retryable": false
}
}Exit Codes
| Code | Meaning | Action |
|---|---|---|
| 0 | Success | Parse stdout |
| 1 | Health check failed | Run cass index --full |
| 2 | Usage error | Fix syntax (hint provided) |
| 3 | Index/DB missing | Run cass index --full |
| 4 | Network error | Check connectivity |
| 5 | Data corruption | Run cass index --full --force-rebuild |
| 6 | Incompatible version | Update cass |
| 7 | Lock/busy | Retry later |
| 8 | Partial result | Increase --timeout |
| 9 | Unknown error | Check retryable flag |
---
Search Modes
Three search modes, selectable with --mode flag:
| Mode | Algorithm | Best For |
|---|---|---|
| lexical (default) | BM25 full-text | Exact term matching, code searches |
| semantic | Vector similarity | Conceptual queries, "find similar" |
| hybrid | Reciprocal Rank Fusion | Balanced precision and recall |
cass search "authentication" --mode lexical --robot
cass search "how to handle user login" --mode semantic --robot
cass search "auth error handling" --mode hybrid --robotHybrid combines lexical and semantic using RRF:
RRF_score = Σ 1 / (60 + rank_i)---
Pipeline Mode (Chained Search)
Chain searches by piping session paths:
# Find sessions mentioning "auth", then search within those for "token"
cass search "authentication" --robot-format sessions | \
cass search "refresh token" --sessions-from - --robot
# Build a filtered corpus from today's work
cass search --today --robot-format sessions > today_sessions.txt
cass search "bug fix" --sessions-from today_sessions.txt --robotUse cases:
- Drill-down: Broad search → narrow within results
- Cross-reference: Find sessions with term A, then find term B within them
- Corpus building: Save session lists for repeated searches
---
Query Language
Basic Queries
| Query | Matches |
|---|---|
error | Messages containing "error" (case-insensitive) |
python error | Both "python" AND "error" |
"authentication failed" | Exact phrase |
Boolean Operators
| Operator | Example | Meaning |
|---|---|---|
AND | python AND error | Both terms required (default) |
OR | error OR warning | Either term matches |
NOT | error NOT test | First term, excluding second |
- | error -test | Shorthand for NOT |
# Complex boolean query
cass search "authentication AND (error OR failure) NOT test" --robot
# Exclude test files
cass search "bug fix -test -spec" --robot
# Either error type
cass search "TypeError OR ValueError" --robotWildcard Patterns
| Pattern | Type | Performance |
|---|---|---|
auth* | Prefix | Fast (edge n-grams) |
*tion | Suffix | Slower (regex) |
*config* | Substring | Slowest (regex) |
Match Types
Results include match_type:
| Type | Meaning | Score Boost |
|---|---|---|
exact | Verbatim match | Highest |
prefix | Via prefix expansion | High |
suffix | Via suffix pattern | Medium |
substring | Via substring pattern | Lower |
fuzzy | Auto-fallback (sparse results) | Lowest |
Auto-Fuzzy Fallback
When exact query returns <3 results, CASS automatically retries with wildcards:
auth→*auth*- Results flagged with
wildcard_fallback: true
Flexible Time Input
CASS accepts a wide variety of time/date formats:
| Format | Examples |
|---|---|
| Relative | -7d, -24h, -30m, -1w |
| Keywords | now, today, yesterday |
| ISO 8601 | 2024-11-25, 2024-11-25T14:30:00Z |
| US Dates | 11/25/2024, 11-25-2024 |
| Unix Timestamp | 1732579200 (seconds or milliseconds) |
---
Ranking Modes
Cycle with F12 in TUI or use --ranking flag:
| Mode | Formula | Best For |
|---|---|---|
| Recent Heavy | relevance*0.3 + recency*0.7 | "What was I working on?" |
| Balanced | relevance*0.5 + recency*0.5 | General search |
| Relevance | relevance*0.8 + recency*0.2 | "Best explanation of X" |
| Match Quality | Penalizes fuzzy matches | Precise technical searches |
| Date Newest | Pure chronological | Recent activity |
| Date Oldest | Reverse chronological | "When did I first..." |
Score Components
- Text Relevance (BM25): Term frequency, inverse document frequency, length normalization
- Recency: Exponential decay (today ~1.0, last week ~0.7, last month ~0.3)
- Match Exactness: Exact phrase=1.0, Prefix=0.9, Suffix=0.8, Substring=0.6, Fuzzy=0.4
Blended Scoring Formula
Final_Score = BM25_Score × Match_Quality + α × Recency_Factor| Mode | α Value | Effect |
|---|---|---|
| Recent Heavy | 1.0 | Recency dominates |
| Balanced | 0.4 | Moderate recency boost |
| Relevance Heavy | 0.1 | BM25 dominates |
| Match Quality | 0.0 | Pure text matching |
---
Supported Agents (11 Connectors)
| Agent | Location | Format |
|---|---|---|
| Claude Code | ~/.claude/projects | JSONL |
| Codex | ~/.codex/sessions | JSONL (Rollout) |
| Gemini CLI | ~/.gemini/tmp | JSON |
| Cline | VS Code global storage | Task directories |
| OpenCode | .opencode directories | SQLite |
| Amp | ~/.local/share/amp + VS Code | Mixed |
| Cursor | ~/Library/Application Support/Cursor | SQLite (state.vscdb) |
| ChatGPT | ~/Library/Application Support/com.openai.chat | JSON (v1 unencrypted) |
| Aider | ~/.aider.chat.history.md + per-project | Markdown |
| Pi-Agent | ~/.pi/agent/sessions | JSONL with thinking |
| Factory (Droid) | ~/.factory/sessions | JSONL by workspace |
Note: ChatGPT v2/v3 are AES-256-GCM encrypted (keychain access required). Legacy v1 unencrypted conversations are indexed automatically.
---
TUI Features (for Humans)
Launch with cass (no flags):
Keyboard Shortcuts
Navigation:
Up/Down: Move selectionLeft/Right: Switch panesTab/Shift+Tab: Cycle focusEnter: Open in$EDITORSpace: Full-screen detail viewHome/End: Jump to first/last resultPageUp/PageDown: Scroll by page
Filtering:
F3: Agent filterF4: Workspace filterF5/F6: Time filters (from/to)Shift+F3: Scope to current result's agentShift+F4: Clear workspace filterShift+F5: Cycle presets (24h/7d/30d/all)Ctrl+Del: Clear all filters
Modes:
F2: Toggle theme (6 presets)F7: Context window size (S/M/L/XL)F9: Match mode (prefix/standard)F12: Ranking modeCtrl+B: Toggle border style
Selection & Actions:
m: Toggle selectionCtrl+A: Select allA: Bulk actions menuCtrl+Enter: Add to queueCtrl+O: Open all queuedy: Copy path/contentCtrl+Y: Copy all selected/: Find in detail panen/N: Next/prev match
Views & Palette:
Ctrl+P: Command palette1-9: Load saved viewShift+1-9: Save view to slot
Source Filtering (multi-machine):
F11: Cycle source filter (all/local/remote)Shift+F11: Source selection menu
Global:
Ctrl+C: QuitF1or?: Toggle helpCtrl+Shift+R: Force re-indexCtrl+Shift+Del: Reset all TUI state
Detail Pane Tabs
| Tab | Content | Switch With |
|---|---|---|
| Messages | Full conversation with markdown | [ / ] |
| Snippets | Keyword-extracted summaries | [ / ] |
| Raw | Unformatted JSON/text | [ / ] |
Context Window Sizing
| Size | Characters | Use Case |
|---|---|---|
| Small | ~200 | Quick scanning |
| Medium | ~400 | Default balanced view |
| Large | ~800 | Longer passages |
| XLarge | ~1600 | Full context, code review |
Peek Mode (Ctrl+Space): Temporarily expand to XL without changing default.
---
Theme Presets
Cycle through 6 built-in themes with F2:
| Theme | Description | Best For |
|---|---|---|
| Dark | Tokyo Night-inspired deep blues | Low-light environments |
| Light | High-contrast light background | Bright environments |
| Catppuccin | Warm pastels, reduced eye strain | All-day coding |
| Dracula | Purple-accented dark theme | Popular developer theme |
| Nord | Arctic-inspired cool tones | Calm, focused work |
| High Contrast | Maximum readability | Accessibility needs |
All themes validated against WCAG contrast requirements (4.5:1 minimum for text).
Role-Aware Message Styling
| Role | Visual Treatment |
|---|---|
| User | Blue-tinted background, bold |
| Assistant | Green-tinted background |
| System | Gray/muted background |
| Tool | Orange-tinted background |
---
Saved Views
Save filter configurations to 9 slots for instant recall.
What Gets Saved:
- Active filters (agent, workspace, time range)
- Current ranking mode
- The search query
Keyboard:
Shift+1throughShift+9: Save current view1through9: Load view from slot
Via Command Palette: Ctrl+P → "Save/Load view"
Views persist in tui_state.json across sessions.
---
Density Modes
Control lines per search result. Cycle with Shift+D:
| Mode | Lines | Best For |
|---|---|---|
| Compact | 3 | Maximum results visible |
| Cozy | 5 | Balanced view (default) |
| Spacious | 8 | Detailed preview |
---
Bookmark System
Save important results with notes and tags:
In TUI: Press b to bookmark, add notes and tags.
Bookmark Structure:
title: Short descriptionsource_path,line_number,agent,workspacenote: Your annotationstags: Comma-separated labelssnippet: Extracted content
Storage: ~/.local/share/coding-agent-search/bookmarks.db (SQLite)
---
Optional Semantic Search
Local-only semantic search using MiniLM (no cloud):
Required files (place in data directory):
model.onnxtokenizer.jsonconfig.jsonspecial_tokens_map.jsontokenizer_config.json
Vector index stored as vector_index/index-minilm-384.cvvi.
CASS does NOT auto-download models; you must manually install them.
Hash Embedder Fallback: When MiniLM not installed, CASS uses a hash-based embedder for approximate semantic similarity.
---
Watch Mode
Real-time index updates:
cass index --watch- Debounce: 2 seconds (wait for burst to settle)
- Max wait: 5 seconds (force flush during continuous activity)
- Incremental: Only re-scans modified files
TUI automatically starts watch mode in background.
---
Deduplication Strategy
CASS uses multi-layer deduplication:
1. Message Hash: SHA-256 of (role + content + timestamp) - identical messages stored once 2. Conversation Fingerprint: Hash of first N message hashes - detects duplicate files 3. Search-Time Dedup: Results deduplicated by content similarity
Noise Filtering:
- Empty messages and pure whitespace
- System prompts (unless searching for them)
- Repeated tool acknowledgments
---
Performance Characteristics
| Operation | Latency |
|---|---|
| Prefix search (cached) | 2-8ms |
| Prefix search (cold) | 40-60ms |
| Substring search | 80-200ms |
| Full reindex | 5-30s |
| Incremental reindex | 50-500ms |
| Health check | <50ms |
Memory: 70-140MB typical (50K messages) Disk: ~600 bytes/message (including n-gram overhead)
---
Response Shapes
Search Response:
{
"query": "error",
"limit": 10,
"count": 5,
"total_matches": 42,
"hits": [
{
"source_path": "/path/to/session.jsonl",
"line_number": 123,
"agent": "claude_code",
"workspace": "/projects/myapp",
"title": "Authentication debugging",
"snippet": "The error occurs when...",
"score": 0.85,
"match_type": "exact",
"created_at": "2024-01-15T10:30:00Z"
}
],
"_meta": {
"elapsed_ms": 12,
"cache_hit": true,
"wildcard_fallback": false,
"next_cursor": "eyJ...",
"index_freshness": { "stale": false, "age_seconds": 120 }
}
}Aggregation Response:
{
"aggregations": {
"agent": {
"buckets": [
{"key": "claude_code", "count": 120},
{"key": "codex", "count": 85}
],
"other_count": 15
}
}
}---
Environment Variables
| Variable | Purpose |
|---|---|
CASS_DATA_DIR | Override data directory |
CHATGPT_ENCRYPTION_KEY | Base64 key for encrypted ChatGPT |
PI_CODING_AGENT_DIR | Override Pi-Agent sessions path |
CASS_CACHE_SHARD_CAP | Per-shard cache entries (default 256) |
CASS_CACHE_TOTAL_CAP | Total cached hits (default 2048) |
CASS_DEBUG_CACHE_METRICS | Enable cache debug logging |
CODING_AGENT_SEARCH_NO_UPDATE_PROMPT | Skip update checks |
---
Shell Completions
cass completions bash > ~/.local/share/bash-completion/completions/cass
cass completions zsh > "${fpath[1]}/_cass"
cass completions fish > ~/.config/fish/completions/cass.fish
cass completions powershell >> $PROFILE---
API Contract & Versioning
cass api-version --json
# → { "version": "0.4.0", "contract_version": "1", "breaking_changes": [] }
cass introspect --json
# → Full schema: all commands, arguments, response typesGuaranteed Stable:
- Exit codes and their meanings
- JSON response structure for
--robotoutput - Flag names and behaviors
_metablock format
---
Integration with CASS Memory (cm)
CASS provides episodic memory (raw sessions). CM extracts procedural memory (rules and playbooks):
# 1. CASS indexes raw sessions
cass index --full
# 2. Search for relevant past experience
cass search "authentication timeout" --robot --limit 10
# 3. CM reflects on sessions to extract rules
cm reflect---
Troubleshooting
| Issue | Solution |
|---|---|
| "missing index" | cass index --full |
| Stale warning | Rerun index or enable watch |
| Empty results | Check cass stats --json, verify connectors detected |
| JSON parsing errors | Use --robot-format compact |
| Watch not triggering | Check watch_state.json, verify file event support |
| Reset TUI state | cass tui --reset-state or Ctrl+Shift+Del |
---
Installation
# One-liner install
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/coding_agent_session_search/main/install.sh \
| bash -s -- --easy-mode --verify
# Windows
irm https://raw.githubusercontent.com/Dicklesworthstone/coding_agent_session_search/main/install.ps1 | iex---
Integration with Flywheel
| Tool | Integration |
|---|---|
| CM | CASS provides episodic memory, CM extracts procedural memory |
| NTM | Robot mode flags for searching past sessions |
| Agent Mail | Search threads across agent history |
| BV | Cross-reference beads with past solutions |
# SQLite databases
*.db
*.db?*
*.db-journal
*.db-wal
*.db-shm
# Daemon runtime files
daemon.lock
daemon.log
daemon.pid
bd.sock
sync-state.json
.sync.lock
# Local version tracking (prevents upgrade notification spam after git ops)
.local_version
# Legacy database files
db.sqlite
bd.db
# Merge artifacts (temporary files from 3-way merge)
beads.base.jsonl
beads.base.meta.json
beads.left.jsonl
beads.left.meta.json
beads.right.jsonl
beads.right.meta.json
# Keep JSONL exports and config (source of truth for git)
!issues.jsonl
!metadata.json
!config.json
# Local history backups
.br_history/
# bv (beads viewer) lock file
.bv.lock
sync-branch: beads-sync
allow_legacy_ids: true
issue_prefix: coding_agent_session_search
no-auto-import: 'true'
coding_agent_session_search-cass-fleet-resilience-20260608-uojcg.11.4
{
"database": "beads.db",
"jsonl_export": "issues.jsonl",
"last_bd_version": "0.26.1"
}Beads - AI-Native Issue Tracking
Welcome to Beads! This repository uses Beads for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
What is Beads?
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
Learn more: github.com/steveyegge/beads
Quick Start
Essential Commands
# Create new issues
bd create "Add user authentication"
# View all issues
bd list
# View issue details
bd show <issue-id>
# Update issue status
bd update <issue-id> --status in_progress
bd update <issue-id> --status done
# Sync with git remote
bd syncWorking with Issues
Issues in Beads are:
- Git-native: Stored in
.beads/issues.jsonland synced like code - AI-friendly: CLI-first design works perfectly with AI coding agents
- Branch-aware: Issues can follow your branch workflow
- Always in sync: Auto-syncs with your commits
Why Beads?
✨ AI-Native Design
- Built specifically for AI-assisted development workflows
- CLI-first interface works seamlessly with AI coding agents
- No context switching to web UIs
🚀 Developer Focused
- Issues live in your repo, right next to your code
- Works offline, syncs when you push
- Fast, lightweight, and stays out of your way
🔧 Git Integration
- Automatic sync with git commits
- Branch-aware issue tracking
- Intelligent JSONL merge resolution
Get Started with Beads
Try Beads in your own projects:
# Install Beads
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
# Initialize in your repo
bd init
# Create your first issue
bd create "Try out Beads"Learn More
- Documentation: github.com/steveyegge/beads/docs
- Quick Start Guide: Run
bd quickstart - Examples: github.com/steveyegge/beads/examples
Repo-Specific Agent Defaults
This repository has legacy historical issue IDs mixed with current coding_agent_session_search-* IDs. To keep agent workflows stable:
1. Start triage with bv --robot-triage or bv --robot-next. 2. Use br ready --json to confirm actionable work. 3. Prefer explicit stale-safe flags on br commands in multi-agent sessions.
Workspace config (.beads/config.yaml) sets:
issue_prefix: coding_agent_session_searchallow_legacy_ids: trueno-auto-import: true
Runtime note: even with no-auto-import: true, some br invocations can still hit prefix-mismatch checks in this mixed-ID workspace. Use explicit flags for reliable operation:
br ready --json --no-auto-import --allow-stalebr show <id> --json --no-auto-import --allow-stalebr list --status=open --json --no-auto-import --allow-stale
This preserves access to legacy records while avoiding auto-import validation paths that can fail in shared sessions.
---
Beads: Issue tracking that moves at the speed of thought ⚡
{
"key": "db",
"value": null
}
{"ok":true,"workspace_health":"degraded","reliability_audit":{"source":"doctor.inspect","health":"degraded","anomaly_count":1,"anomalies":[{"code":"stale_recovery_artifacts","severity":"degraded","message":"stale recovery artifacts present"}]},"checks":[{"name":"jsonl.merge_artifacts","status":"ok"},{"name":"gitignore.beads_inner","status":"ok"},{"name":"sync_jsonl_path","status":"ok","message":"JSONL path is within sync allowlist","details":{"path":"/data/projects/coding_agent_session_search/.beads/issues.jsonl","beads_dir":"/data/projects/coding_agent_session_search/.beads"}},{"name":"sync_conflict_markers","status":"ok","message":"No merge conflict markers found"},{"name":"jsonl.parse","status":"ok","message":"Parsed 1700 records","details":{"path":"/data/projects/coding_agent_session_search/.beads/issues.jsonl","records":1700}},{"name":"db.recovery_artifacts","status":"warn","message":"Preserved recovery artifacts remain for this database family (10 item(s))","details":{"artifacts":["/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-shm.20260504_231541_814775036.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-shm.20260505_143326_146671209.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-shm.20260505_191340_030671941.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260504_231541_814775036.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260505_074528_852081018.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260505_143326_146671209.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260505_191340_030671941.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260506_143350_412776116.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260506_180202_516275900.truncated-wal","/data/projects/coding_agent_session_search/.beads/.br_recovery/beads.db-wal.20260508_091921_871029755.truncated-wal"]}},{"name":"db.sidecars","status":"warn","message":"WAL sidecar exists without a matching SHM sidecar at /data/projects/coding_agent_session_search/.beads/beads.db-wal (expected for frankensqlite)","details":{"findings":["WAL sidecar exists without a matching SHM sidecar at /data/projects/coding_agent_session_search/.beads/beads.db-wal (expected for frankensqlite)"]}},{"name":"schema.tables","status":"ok","details":{"tables":["issues","dependencies","labels","comments","events","config","metadata","dirty_issues","export_hashes","child_counters","blocked_issues_cache"]}},{"name":"schema.columns","status":"ok"},{"name":"db.recoverable_anomalies","status":"ok"},{"name":"db.null_defaults","status":"ok"},{"name":"sqlite.integrity_check","status":"warn","message":"database disk image is malformed: page 19 is never used"},{"name":"counts.db_vs_jsonl","status":"ok","message":"Both have 1700 records"},{"name":"sync.metadata","status":"ok","message":"Database and JSONL are in sync","details":{"dirty_issues":0,"last_import":"2026-03-12T03:09:18.011132862+00:00","last_export":"2026-05-08T20:16:18.499817590+00:00","jsonl_hash":"ee3b30bd2ce5b01f"}},{"name":"db.write_probe","status":"ok","message":"Rollback-only issue write succeeded for coding_agent_session_search-001"},{"name":"sqlite3.integrity_check","status":"warn","message":"*** in database main ***; Page 19: never used; Page 53: never used; Page 54: never used; Page 55: never used; Page 88: never used; Page 89: never used; Page 132: never used; Page 176: never used; Page 263: never used; Page 270: never used; Page 277: never used; Page 286: never used; Page 291: never used; Page 298: never used; Page 303: never used; Page 311: never used; Page 317: never used; Page 324: never used; Page 328: never used; Page 354: never used; Page 362: never used; Page 372: never used; Page 379: never used; Page 389: never used; Page 396: never used; Page 399: never used; Page 405: never used; Page 410: never used; Page 418: never used; Page 450: never used; Page 455: never used; Page 462: never used; Page 475: never used; Page 482: never used; Page 512: never used; Page 521: never used; Page 527: never used; Page 551: never used; Page 553: never used; Page 560: never used; Page 578: never used; Page 586: never used; Page 590: never used; Page 596: never used; Page 608: never used; Page 613: never used; Page 618: never used; Page 651: never used; Page 668: never used; Page 717: never used; Page 755: never used; Page 773: never used; Page 783: never used; Page 820: never used; Page 822: never used; Page 844: never used; Page 848: never used; Page 860: never used; Page 861: never used; Page 867: never used; Page 871: never used; Page 893: never used; Page 1101: never used; Page 1117: never used; Page 1123: never used; Page 1136: never used; Page 1140: never used; Page 1159: never used; Page 1163: never used; Page 1172: never used; Page 1178: never used; Page 1192: never used; Page 1196: never used; Page 1219: never used; Page 1224: never used; Page 1949: never used; Page 1959: never used; Page 1965: never used; Page 1969: never used; Page 1975: never used; Page 1980: never used; Page 1986: never used; Page 2024: never used; Page 2034: never used; Page 2046: never used; Page 2054: never used; Page 2062: never used; Page 2085: never used; Page 2096: never used; Page 2101: never used; Page 2103: never used; Page 2107: never used; Page 2108: never used; Page 2121: never used; Page 2123: never used; Page 2140: never used; Page 2141: never used; Page 2146: never used; Page 2147: never used; Page 2152: never used","details":{"messages":["*** in database main ***","Page 19: never used","Page 53: never used","Page 54: never used","Page 55: never used","Page 88: never used","Page 89: never used","Page 132: never used","Page 176: never used","Page 263: never used","Page 270: never used","Page 277: never used","Page 286: never used","Page 291: never used","Page 298: never used","Page 303: never used","Page 311: never used","Page 317: never used","Page 324: never used","Page 328: never used","Page 354: never used","Page 362: never used","Page 372: never used","Page 379: never used","Page 389: never used","Page 396: never used","Page 399: never used","Page 405: never used","Page 410: never used","Page 418: never used","Page 450: never used","Page 455: never used","Page 462: never used","Page 475: never used","Page 482: never used","Page 512: never used","Page 521: never used","Page 527: never used","Page 551: never used","Page 553: never used","Page 560: never used","Page 578: never used","Page 586: never used","Page 590: never used","Page 596: never used","Page 608: never used","Page 613: never used","Page 618: never used","Page 651: never used","Page 668: never used","Page 717: never used","Page 755: never used","Page 773: never used","Page 783: never used","Page 820: never used","Page 822: never used","Page 844: never used","Page 848: never used","Page 860: never used","Page 861: never used","Page 867: never used","Page 871: never used","Page 893: never used","Page 1101: never used","Page 1117: never used","Page 1123: never used","Page 1136: never used","Page 1140: never used","Page 1159: never used","Page 1163: never used","Page 1172: never used","Page 1178: never used","Page 1192: never used","Page 1196: never used","Page 1219: never used","Page 1224: never used","Page 1949: never used","Page 1959: never used","Page 1965: never used","Page 1969: never used","Page 1975: never used","Page 1980: never used","Page 1986: never used","Page 2024: never used","Page 2034: never used","Page 2046: never used","Page 2054: never used","Page 2062: never used","Page 2085: never used","Page 2096: never used","Page 2101: never used","Page 2103: never used","Page 2107: never used","Page 2108: never used","Page 2121: never used","Page 2123: never used","Page 2140: never used","Page 2141: never used","Page 2146: never used","Page 2147: never used","Page 2152: never used"]}}]}
[
{
"id": "coding_agent_session_search-001",
"title": "TUI style system spec",
"description": "Create docs/tui_style_spec.md: palettes (dark/light), role colors, spacing scales, gradients, motion rules, density presets, iconography grid, animation opt-out policy.",
"notes": "Spec drafted and checked against acceptance (colors, gradients, density, motion, accessibility, opt-out, perf guards).",
"status": "closed",
"priority": 2,
"issue_type": "task",
"created_at": "2025-11-29T06:00:53.488928Z",
"updated_at": "2025-11-29T06:16:18.675764Z",
"closed_at": "2025-11-29T06:16:18.675773Z",
"source_repo": ".",
"compaction_level": 0,
"original_size": 0,
"dependents": [
{
"id": "coding_agent_session_search-002",
"title": "Interaction model & keymap RFC",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-013",
"title": "Staggered reveal animations",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-008",
"title": "Role-aware theming & gradients",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-011",
"title": "Icons & status badges",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-005",
"title": "Editable filter pills",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-007",
"title": "Result drill-in modal",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-010",
"title": "Syntax-highlighted snippets in results",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
},
{
"id": "coding_agent_session_search-009",
"title": "Density toggle (Compact/Cozy/Spacious)",
"status": "closed",
"priority": 2,
"dependency_type": "blocks"
}
]
}
]
{
"dirty_count": 0,
"last_export_time": "2026-05-08T20:16:18.499817590+00:00",
"last_import_time": "2026-03-12T03:09:18.011132862+00:00",
"jsonl_content_hash": "ee3b30bd2ce5b01f7bed042bda7b344be5144901a3429b070752c9f61146c7ef",
"jsonl_exists": true,
"jsonl_newer": false,
"db_newer": false
}
sync-branch: beads-sync
allow_legacy_ids: true
issue_prefix: coding_agent_session_search
no-auto-import: 'true'
repo_path=.
timestamp=20260509T004736Z
issue_id=coding_agent_session_search-001
{
"database": "beads.db",
"jsonl_export": "issues.jsonl",
"last_bd_version": "0.26.1"
}# Cargo configuration for cass
# https://doc.rust-lang.org/cargo/reference/config.html
[build]
# Use all available CPU cores for parallel compilation
jobs = -1
[env]
# Coverage exclusion patterns for cargo-llvm-cov
# These paths are excluded from coverage reports
CARGO_LLVM_COV_EXCLUDE = "tests/*,**/test*.rs,benches/*"
[target.x86_64-unknown-linux-gnu]
# Linux x86_64: Use LLD for faster linking (available in CI)
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
# Note: Linux ARM64 (aarch64-unknown-linux-gnu) uses default linker
# The ubuntu-24.04-arm GitHub runner does not have LLD installed
# macOS targets use the default ld64 linker from Xcode
# LLD is not available by default on macOS and causes build failures
# [target.aarch64-apple-darwin] - uses default linker
# [target.x86_64-apple-darwin] - uses default linker
# Alias for coverage command
[alias]
cov = "llvm-cov --workspace --ignore-filename-regex tests/ --ignore-filename-regex benches/"
cov-html = "llvm-cov --workspace --ignore-filename-regex tests/ --ignore-filename-regex benches/ --html --open"
cov-json = "llvm-cov --workspace --ignore-filename-regex tests/ --ignore-filename-regex benches/ --json"
# cargo-nextest configuration for cass
# https://nexte.st/book/configuration.html
# No custom store directory - use workspace-relative paths
# =============================================================================
# Default Profile - Development
# =============================================================================
[profile.default]
# Fail fast during local development
fail-fast = true
# Run tests in parallel (default: number of CPUs)
test-threads = "num-cpus"
# Output format for local development
status-level = "pass"
final-status-level = "fail"
# Retry configuration
retries = 0
# Slow test thresholds
slow-timeout = { period = "30s", terminate-after = 2 }
# =============================================================================
# CI Profile - Continuous Integration
# =============================================================================
[profile.ci]
# Don't fail fast in CI - run all tests to get complete results
fail-fast = false
# Use all CPUs
test-threads = "num-cpus"
# Verbose output for CI logs
status-level = "all"
final-status-level = "all"
# Retry flaky tests once in CI
retries = 1
# Longer timeout for CI (may be slower)
slow-timeout = { period = "60s", terminate-after = 2 }
# JUnit XML output for CI integration
[profile.ci.junit]
# Output path for JUnit XML report (relative to store dir)
path = "junit.xml"
# Report name shown in CI systems
report-name = "cass-test-results"
# Store output on failure for debugging
store-success-output = false
store-failure-output = true
# =============================================================================
# CI Coverage Profile - Coverage runs
# =============================================================================
[profile.ci-coverage]
# Same settings as CI
fail-fast = false
test-threads = "num-cpus"
status-level = "all"
final-status-level = "all"
retries = 0
slow-timeout = { period = "120s", terminate-after = 2 }
[profile.ci-coverage.junit]
path = "junit.xml"
report-name = "cass-coverage-test-results"
store-success-output = false
store-failure-output = true
# =============================================================================
# E2E Profile - End-to-end tests (sequential)
# =============================================================================
[profile.e2e]
# Don't fail fast - run all E2E tests
fail-fast = false
# E2E tests often need sequential execution
test-threads = 1
status-level = "all"
final-status-level = "all"
retries = 1
# E2E tests may take longer
slow-timeout = { period = "120s", terminate-after = 2 }
[profile.e2e.junit]
path = "junit.xml"
report-name = "cass-e2e-test-results"
store-success-output = false
store-failure-output = true
# =============================================================================
# Test Filtering
# =============================================================================
# Skip install script tests by default (they need network access)
# =============================================================================
# Override Settings for Specific Tests
# =============================================================================
# Tests that should run with limited parallelism (e.g., TUI tests)
# Using threads-required to serialize UI tests that need exclusive terminal access
[[profile.default.overrides]]
filter = "test(ui_)"
threads-required = "num-cpus"
[[profile.ci.overrides]]
filter = "test(ui_)"
threads-required = "num-cpus"
# Use bd merge for beads JSONL files
.beads/beads.jsonl merge=beads
# Integrity fixtures embed byte sizes and hashes; keep checkout bytes stable.
tests/fixtures/pages_verify/** text eol=lf
name: Notify ACFS checksum monitor
on:
push:
branches: [main]
paths:
- 'install.sh'
- 'scripts/install.sh'
release:
types: [published]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
dispatch:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
ACFS_TOKEN: ${{ secrets.ACFS_REPO_DISPATCH_TOKEN }}
steps:
- name: Skip dispatch when token missing
if: ${{ env.ACFS_TOKEN == '' }}
run: echo "ACFS_REPO_DISPATCH_TOKEN not set; skipping ACFS dispatch."
- name: Dispatch to ACFS
if: ${{ env.ACFS_TOKEN != '' }}
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
with:
token: ${{ env.ACFS_TOKEN }}
repository: Dicklesworthstone/agentic_coding_flywheel_setup
event-type: upstream-changed
client-payload: |
{"repo":"${{ github.repository }}","ref":"${{ github.ref }}","sha":"${{ github.sha }}","event":"${{ github.event_name }}"}
# .github/workflows/bench.yml
# Performance benchmarks using Criterion with regression detection
#
# Features (T5.3):
# - Metric-specific thresholds: latency (10%), duration (20%), memory (15%), throughput (10%)
# - Historical trend tracking across runs
# - Test suite duration and memory tracking
name: Benchmarks
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
benchmark:
name: Performance Benchmarks
runs-on: ubuntu-latest
# The crate graph (asupersync + frankensqlite + frankensearch + ftui
# path deps) takes ~20 min just to compile on a cold cache; the full
# criterion suite across 3 benches needs another 25+ min. 45 min was
# a hard-cancel risk. Bump to 90 min with cache warm most runs will
# finish much faster.
timeout-minutes: 90
steps:
- name: Free disk space on runner
# The benchmark job compiles the full crate graph (including many
# sibling path-dep workspaces) and previously hit `No space left on
# device` during dependency download. Reclaim ~15-20 GiB by removing
# preinstalled toolchains we don't use (Android, dotnet, Haskell,
# GHC, large caches).
shell: bash
run: |
set -eux
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /usr/local/lib/android || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
sudo rm -rf /usr/local/share/boost || true
sudo rm -rf "$AGENT_TOOLSDIRECTORY" || true
sudo docker image prune --all --force || true
df -h
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # For comparing with main
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Setup Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Cache benchmark baselines
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: target/criterion
key: criterion-${{ runner.os }}-${{ hashFiles('benches/**') }}
restore-keys: |
criterion-${{ runner.os }}-
- name: Cache benchmark history
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: target/perf_history.json
key: perf-history-${{ runner.os }}
restore-keys: |
perf-history-${{ runner.os }}
- name: Run benchmarks (save baseline for main)
if: github.ref == 'refs/heads/main'
id: bench-main
run: |
start_time=$(date +%s%3N)
cargo bench --bench index_perf --bench runtime_perf --bench search_perf -- --save-baseline main
end_time=$(date +%s%3N)
echo "bench_duration_ms=$((end_time - start_time))" >> $GITHUB_OUTPUT
- name: Run benchmarks (compare with baseline for PRs)
if: github.event_name == 'pull_request'
id: bench-pr
run: |
start_time=$(date +%s%3N)
cargo bench --bench index_perf --bench runtime_perf --bench search_perf -- --save-baseline pr
end_time=$(date +%s%3N)
echo "bench_duration_ms=$((end_time - start_time))" >> $GITHUB_OUTPUT
- name: Save benchmark history (main branch)
if: github.ref == 'refs/heads/main'
run: |
python scripts/check_bench_regression.py \
--save-history \
--history-file target/perf_history.json \
--run-id "${{ github.sha }}" \
--baseline main \
--current main
- name: Check for regressions (metric-specific thresholds)
if: github.event_name == 'pull_request'
run: |
# Uses metric-specific thresholds:
# - Latency (search): 10%
# - Duration (test suite): 20%
# - Memory: 15%
# - Throughput (indexing): 10%
python scripts/check_bench_regression.py \
--latency-threshold 10 \
--duration-threshold 20 \
--memory-threshold 15 \
--throughput-threshold 10 \
--json > target/regression_report.json || true
# Pretty print results
python scripts/check_bench_regression.py \
--latency-threshold 10 \
--duration-threshold 20 \
--memory-threshold 15 \
--throughput-threshold 10
- name: Analyze historical trends
if: always()
run: |
if [ -f target/perf_history.json ]; then
echo "## Trend Analysis" >> $GITHUB_STEP_SUMMARY
python scripts/check_bench_regression.py \
--analyze-trends \
--history-file target/perf_history.json \
--trend-window 5 \
--json > target/trend_analysis.json 2>/dev/null || true
if [ -f target/trend_analysis.json ]; then
echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY
cat target/trend_analysis.json | head -50 >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Generate benchmark summary
if: always()
run: |
echo "## Benchmark Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Show metric-specific thresholds
echo "### Regression Thresholds (T5.3)" >> $GITHUB_STEP_SUMMARY
echo "| Metric Type | Threshold |" >> $GITHUB_STEP_SUMMARY
echo "|-------------|-----------|" >> $GITHUB_STEP_SUMMARY
echo "| Latency (search) | 10% |" >> $GITHUB_STEP_SUMMARY
echo "| Duration (test suite) | 20% |" >> $GITHUB_STEP_SUMMARY
echo "| Memory | 15% |" >> $GITHUB_STEP_SUMMARY
echo "| Throughput (indexing) | 10% |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Show benchmark duration
if [ -n "${{ steps.bench-main.outputs.bench_duration_ms }}" ]; then
echo "### Timing" >> $GITHUB_STEP_SUMMARY
echo "- Benchmark suite duration: ${{ steps.bench-main.outputs.bench_duration_ms }}ms" >> $GITHUB_STEP_SUMMARY
elif [ -n "${{ steps.bench-pr.outputs.bench_duration_ms }}" ]; then
echo "### Timing" >> $GITHUB_STEP_SUMMARY
echo "- Benchmark suite duration: ${{ steps.bench-pr.outputs.bench_duration_ms }}ms" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Show regression report if present
if [ -f target/regression_report.json ]; then
echo "### Regression Check" >> $GITHUB_STEP_SUMMARY
has_regressions=$(jq -r '.has_regressions' target/regression_report.json)
if [ "$has_regressions" = "true" ]; then
echo "⚠️ **Regressions detected:**" >> $GITHUB_STEP_SUMMARY
jq -r '.regressions[] | "- \(.name) [\(.metric_type)]: +\(.diff_pct | . * 10 | round / 10)% (threshold: \(.threshold)%)"' target/regression_report.json >> $GITHUB_STEP_SUMMARY
else
echo "✅ No significant regressions detected" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
# Show improvements
improvements=$(jq -r '.improvements | length' target/regression_report.json)
if [ "$improvements" -gt 0 ]; then
echo "### Improvements" >> $GITHUB_STEP_SUMMARY
jq -r '.improvements[] | "- \(.name) [\(.metric_type)]: \(.diff_pct | . * 10 | round / 10)%"' target/regression_report.json >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
fi
echo "📊 Detailed reports available in workflow artifacts." >> $GITHUB_STEP_SUMMARY
- name: Upload benchmark reports
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: benchmark-reports
path: |
target/criterion/
target/perf_history.json
target/regression_report.json
target/trend_analysis.json
retention-days: 30
name: Browser Tests
on:
push:
branches: [main]
paths:
- 'tests/**'
- 'src/pages/**'
- 'src/pages_assets/**'
- 'src/html_export/**'
- '.github/workflows/browser-tests.yml'
pull_request:
branches: [main]
paths:
- 'tests/**'
- 'src/pages/**'
- 'src/pages_assets/**'
- 'src/html_export/**'
- '.github/workflows/browser-tests.yml'
workflow_dispatch:
env:
NODE_VERSION: '20'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Install dependencies, build cass binary, and cache
setup:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Cache node modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-npm
with:
path: tests/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('tests/package-lock.json') }}
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
working-directory: tests
run: npm ci
- name: Install Playwright browsers
working-directory: tests
run: npx playwright install --with-deps
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Build cass binary
run: cargo build --release
- name: Upload cass binary
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: cass-binary
path: target/release/cass
retention-days: 1
# Run tests on Chromium
test-chromium:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-npm
with:
path: tests/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('tests/package-lock.json') }}
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
working-directory: tests
run: npm ci
- name: Install Playwright browsers
working-directory: tests
run: npx playwright install chromium --with-deps
- name: Download cass binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: cass-binary
path: target/release
- name: Make cass binary executable
run: chmod +x target/release/cass
- name: Run Chromium tests
working-directory: tests
run: npm run test:e2e:chromium
env:
CI: true
- name: Upload test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: chromium-test-results
path: tests/test-results/
retention-days: 7
- name: Upload E2E report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: chromium-e2e-report
path: |
tests/e2e-report/
tests/e2e-results.json
retention-days: 7
# Run tests on Firefox
test-firefox:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-npm
with:
path: tests/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('tests/package-lock.json') }}
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
working-directory: tests
run: npm ci
- name: Install Playwright browsers
working-directory: tests
run: npx playwright install firefox --with-deps
- name: Download cass binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: cass-binary
path: target/release
- name: Make cass binary executable
run: chmod +x target/release/cass
- name: Run Firefox tests
working-directory: tests
run: npm run test:e2e:firefox
env:
CI: true
- name: Upload test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: firefox-test-results
path: tests/test-results/
retention-days: 7
- name: Upload E2E report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: firefox-e2e-report
path: |
tests/e2e-report/
tests/e2e-results.json
retention-days: 7
# Run tests on WebKit (Safari)
test-webkit:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-npm
with:
path: tests/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('tests/package-lock.json') }}
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
working-directory: tests
run: npm ci
- name: Install Playwright browsers
working-directory: tests
run: npx playwright install webkit --with-deps
- name: Download cass binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: cass-binary
path: target/release
- name: Make cass binary executable
run: chmod +x target/release/cass
- name: Run WebKit tests
working-directory: tests
run: npm run test:e2e:webkit
env:
CI: true
- name: Upload test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: webkit-test-results
path: tests/test-results/
retention-days: 7
- name: Upload E2E report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: webkit-e2e-report
path: |
tests/e2e-report/
tests/e2e-results.json
retention-days: 7
# Run mobile emulation tests
test-mobile:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Restore node modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-npm
with:
path: tests/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('tests/package-lock.json') }}
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
working-directory: tests
run: npm ci
- name: Install Playwright browsers
working-directory: tests
run: npx playwright install chromium webkit --with-deps
- name: Download cass binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: cass-binary
path: target/release
- name: Make cass binary executable
run: chmod +x target/release/cass
- name: Run mobile tests
working-directory: tests
run: npm run test:e2e -- --project=mobile-chrome --project=mobile-safari
env:
CI: true
- name: Upload test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: mobile-test-results
path: tests/test-results/
retention-days: 7
- name: Upload E2E report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: mobile-e2e-report
path: |
tests/e2e-report/
tests/e2e-results.json
retention-days: 7
# Summary job
test-summary:
needs: [test-chromium, test-firefox, test-webkit, test-mobile]
runs-on: ubuntu-latest
if: always()
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
steps:
- name: Download test result artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
pattern: '*-test-results'
path: artifacts
merge-multiple: true
- name: Aggregate E2E JSONL logs
shell: bash
run: |
set -euo pipefail
mkdir -p test-results/e2e
mapfile -d '' logs < <(find artifacts -type f \( -path "*/test-results/e2e/*.jsonl" -o -path "*/e2e/*.jsonl" \) -print0 | sort -z)
if (( ${#logs[@]} > 0 )); then
cat "${logs[@]}" > test-results/e2e/combined.jsonl
else
: > test-results/e2e/combined.jsonl
fi
- name: Generate E2E summary report
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json
import os
from datetime import datetime, timezone
from pathlib import Path
combined_path = Path("test-results/e2e/combined.jsonl")
summary_path = Path("test-results/e2e/summary.md")
summary_path.parent.mkdir(parents=True, exist_ok=True)
total = passed = failed = skipped = flaky = 0
durations = {}
failures = []
if combined_path.exists():
for line in combined_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("event") != "test_end":
continue
runner = event.get("runner", "unknown")
result = event.get("result", {})
status = result.get("status", "unknown")
duration_ms = result.get("duration_ms", 0)
durations[runner] = durations.get(runner, 0) + int(duration_ms or 0)
total += 1
if status == "pass":
passed += 1
elif status == "skip":
skipped += 1
else:
failed += 1
retries = result.get("retries")
if status == "pass" and retries and int(retries) > 0:
flaky += 1
if status == "fail":
test = event.get("test", {})
error = event.get("error", {})
failures.append({
"runner": runner,
"suite": test.get("suite", "unknown"),
"name": test.get("name", "unknown"),
"file": test.get("file"),
"line": test.get("line"),
"message": error.get("message", "unknown error"),
})
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
lines = [
"# E2E Log Summary",
"",
f"**Generated:** {now}",
f"**Combined Log:** {combined_path.as_posix()}",
"",
"## Totals",
"",
f"- **Total Tests:** {total}",
f"- **Passed:** {passed}",
f"- **Failed:** {failed}",
f"- **Skipped:** {skipped}",
f"- **Flaky (passed on retry):** {flaky}",
"",
"## Duration by Runner",
"",
"| Runner | Duration (ms) |",
"|--------|---------------|",
]
if durations:
for runner, duration in sorted(durations.items()):
lines.append(f"| {runner} | {duration} |")
else:
lines.append("| (none) | 0 |")
lines.append("")
lines.append("## Failed Tests")
lines.append("")
if failures:
for f in failures:
location = ""
if f.get("file"):
if f.get("line"):
location = f"{f['file']}:{f['line']}"
else:
location = f"{f['file']}"
detail = f"{f['runner']} :: {f['suite']} :: {f['name']}"
if location:
detail += f" ({location})"
detail += f" — {f['message']}"
lines.append(f"- {detail}")
else:
lines.append("- None")
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {summary_path}")
PY
- name: Upload aggregated E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-log-summary
path: |
test-results/e2e/combined.jsonl
test-results/e2e/summary.md
retention-days: 14
- name: Comment summary on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const marker = '<!-- cass-e2e-summary -->';
const pwStart = '<!-- cass-e2e-playwright:start -->';
const pwEnd = '<!-- cass-e2e-playwright:end -->';
let summary = fs.readFileSync('test-results/e2e/summary.md', 'utf8');
if (summary.startsWith('# ')) {
summary = summary.replace(/^#\s+.*$/m, '## Playwright E2E Summary');
} else if (!summary.startsWith('## ')) {
summary = `## Playwright E2E Summary\n\n${summary}`;
}
const pwSection = `${pwStart}\n${summary.trim()}\n${pwEnd}`;
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find(comment => comment.body.includes(marker));
const upsertSection = (body, section) => {
if (body.includes(pwStart) && body.includes(pwEnd)) {
const regex = new RegExp(`${pwStart}[\\s\\S]*?${pwEnd}`, 'm');
return body.replace(regex, section);
}
return `${body.trim()}\n\n${section}`;
};
let body = existing ? existing.body : marker;
if (!body.includes(marker)) {
body = `${marker}\n${body}`;
}
body = upsertSection(body, pwSection);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}
- name: Check test results
run: |
if [[ "${{ needs.test-chromium.result }}" == "failure" ]]; then
echo "Chromium tests failed"
exit 1
fi
if [[ "${{ needs.test-firefox.result }}" == "failure" ]]; then
echo "Firefox tests failed"
exit 1
fi
if [[ "${{ needs.test-webkit.result }}" == "failure" ]]; then
echo "WebKit tests failed"
exit 1
fi
if [[ "${{ needs.test-mobile.result }}" == "failure" ]]; then
echo "Mobile tests failed"
exit 1
fi
echo "All browser tests passed!"
# .github/workflows/ci.yml
# Continuous Integration: lint, test, audit, and build verification
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
RUST_BACKTRACE: 1
RUST_LOG: debug
jobs:
# No-mock policy audit
no-mock-audit:
name: No-Mock Policy Audit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Run repository artifact hygiene check
run: ./scripts/validate_ci.sh --artifact-hygiene-only
- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Run no-mock audit
id: audit
shell: bash
run: |
set -euo pipefail
mkdir -p test-results
ALLOWLIST_FILE="tests/policies/no_mock_allowlist.json"
AUDIT_REPORT="test-results/no_mock_ci_audit.md"
echo "# No-Mock CI Audit" > "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Run:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$AUDIT_REPORT"
echo "**Commit:** ${{ github.sha }}" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
# Search for mock/fake/stub patterns
VIOLATIONS=$(mktemp)
rg -n "(Mock[A-Z][a-z]|Fake[A-Z][a-z]|Stub[A-Z][a-z]|mock_|fake_|stub_)" \
--glob '!**/node_modules/**' \
--glob '!target/**' \
--glob '!.git/**' \
--glob '!tests/fixtures/**' \
--glob '!test-results/**' \
--glob '!*.md' \
--glob '!*.json' \
src/ tests/ 2>/dev/null > "$VIOLATIONS" || true
VIOLATION_COUNT=$(wc -l < "$VIOLATIONS" | tr -d ' ')
if [ "$VIOLATION_COUNT" -eq 0 ]; then
echo "## Status: ✅ PASS" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "No mock/fake/stub patterns found." >> "$AUDIT_REPORT"
echo "status=pass" >> "$GITHUB_OUTPUT"
rm -f "$VIOLATIONS"
exit 0
fi
echo "Found $VIOLATION_COUNT pattern(s), checking allowlist..."
if [ ! -f "$ALLOWLIST_FILE" ]; then
echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "Allowlist file not found: $ALLOWLIST_FILE" >> "$AUDIT_REPORT"
echo "status=fail" >> "$GITHUB_OUTPUT"
rm -f "$VIOLATIONS"
exit 1
fi
ALLOWLIST_ENTRIES=$(jq -r '.entries[] | "\(.path):\(.pattern)"' "$ALLOWLIST_FILE" 2>/dev/null || echo "")
UNALLOWED_COUNT=0
UNALLOWED_LIST=""
while IFS= read -r line; do
FILE=$(echo "$line" | cut -d: -f1)
PATTERN=$(echo "$line" | grep -oiE "(Mock[A-Z][a-zA-Z]*|Fake[A-Z][a-zA-Z]*|Stub[A-Z][a-zA-Z]*|mock_[a-z_]+|fake_[a-z_]+|stub_[a-z_]+)" | head -1)
ALLOWED=false
for entry in $ALLOWLIST_ENTRIES; do
ENTRY_PATH=$(echo "$entry" | cut -d: -f1)
ENTRY_PATTERN=$(echo "$entry" | cut -d: -f2)
if [[ "$FILE" == *"$ENTRY_PATH"* ]] && [[ "$PATTERN" == *"$ENTRY_PATTERN"* || "$ENTRY_PATTERN" == *"$PATTERN"* ]]; then
ALLOWED=true
break
fi
done
if [ "$ALLOWED" = false ]; then
UNALLOWED_COUNT=$((UNALLOWED_COUNT + 1))
UNALLOWED_LIST="${UNALLOWED_LIST}\n- \`${line}\`"
fi
done < "$VIOLATIONS"
rm -f "$VIOLATIONS"
if [ "$UNALLOWED_COUNT" -gt 0 ]; then
echo "## Status: ❌ FAIL" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Unapproved patterns:** $UNALLOWED_COUNT" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### Violations" >> "$AUDIT_REPORT"
echo -e "$UNALLOWED_LIST" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### How to Fix" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "1. Replace mock/fake/stub with real fixtures (preferred)" >> "$AUDIT_REPORT"
echo "2. OR add to \`tests/policies/no_mock_allowlist.json\` with:" >> "$AUDIT_REPORT"
echo " - \`rationale\`: Why this exception is necessary" >> "$AUDIT_REPORT"
echo " - \`review_date\`: 6-month review date" >> "$AUDIT_REPORT"
echo " - \`permanent: true\` only for true platform boundaries" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "See TESTING.md 'No-Mock Policy' for details." >> "$AUDIT_REPORT"
echo "status=fail" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "## Status: ✅ PASS" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "**Total patterns found:** $VIOLATION_COUNT" >> "$AUDIT_REPORT"
echo "**All patterns allowlisted:** Yes" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
echo "### Allowlist Summary" >> "$AUDIT_REPORT"
echo "" >> "$AUDIT_REPORT"
jq -r '.entries[] | "- `\(.path)`: \(.pattern)` — \(.rationale)"' "$ALLOWLIST_FILE" >> "$AUDIT_REPORT"
echo "status=pass" >> "$GITHUB_OUTPUT"
- name: Upload audit report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: no-mock-audit
path: test-results/no_mock_ci_audit.md
retention-days: 30
# Rust linting and formatting
lint:
name: Lint
needs: [no-mock-audit]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git config --global core.longpaths true
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
# Explicit feature list excludes `strict-path-dep-validation`, which is
# intentionally opt-in and requires specific sibling-repo git revisions
# (see build.rs CONTRACTS). `--all-features` would activate it and make
# the build fail because CI clones siblings at HEAD, not pinned revs.
run: cargo clippy --all-targets --features "qr encryption backtrace" -- -D warnings
# UBS pre-merge gate per coding_agent_session_search-dpfvr.
# Runs `ubs --format=json --ci <changed-files>` against the diff for the PR
# (or push range on main). UBS scans whole files, so this gate compares the
# current file findings to the exact base versions and fails on regressions.
ubs-changed-files:
name: UBS (Ultimate Bug Scanner) Pre-Merge Gate
needs: [no-mock-audit]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout (full history for diff)
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
# `origin/main` must be reachable for `git diff origin/main...HEAD`.
fetch-depth: 0
- name: Compute changed files
id: changed
shell: bash
run: |
set -euo pipefail
# PR runs: diff against the merge-base with the target branch.
# Push runs: diff against main's previous tip.
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="$(git merge-base "origin/${{ github.base_ref }}" HEAD)"
range="$base...HEAD"
elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
base="${{ github.event.before }}"
range="$base...HEAD"
else
# workflow_dispatch / other — diff against origin/main.
base="$(git merge-base origin/main HEAD)"
range="$base...HEAD"
fi
echo "Diff range: $range"
echo "base=$base" >> "$GITHUB_OUTPUT"
# Filter to the file extensions UBS supports. Skip deleted files
# because UBS needs paths that exist in the checkout.
mapfile -t files < <(git diff --name-only --diff-filter=ACMR "$range" -- \
'*.rs' '*.toml' '*.ts' '*.tsx' '*.js' '*.jsx' '*.py' '*.sh' '*.yml' '*.yaml' '*.md' \
2>/dev/null | grep -v -E '^test-results/|^target/|^node_modules/' || true)
if [ "${#files[@]}" -eq 0 ]; then
echo "No UBS-relevant files changed; skipping gate."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
# Persist the existing changed files for the next step.
printf '%s\n' "${files[@]}" > /tmp/ubs-changed-files.txt
echo "files_count=${#files[@]}" >> "$GITHUB_OUTPUT"
echo "Files (first 20):"
head -20 /tmp/ubs-changed-files.txt
- name: Install UBS
if: steps.changed.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
# UBS is distributed via the upstream install.sh script. The script
# fetches the actual scanner from the project's main branch — its
# internal VERSION variable is cosmetic. We pin the *installer ref*
# via .github/workflows/ubs-version.txt so CI runs against a known
# installer commit/tag. A value of "latest" (or an empty file) maps
# to the main branch.
UBS_REF=""
if [ -f .github/workflows/ubs-version.txt ]; then
UBS_REF="$(tr -d '[:space:]' < .github/workflows/ubs-version.txt)"
fi
case "$UBS_REF" in
""|"latest") UBS_REF="main" ;;
esac
echo "Installing UBS using installer from ref: $UBS_REF"
if command -v ubs >/dev/null 2>&1; then
echo "ubs already on PATH: $(command -v ubs)"
ubs --version || true
else
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/${UBS_REF}/install.sh" | bash
# install.sh drops the binary in $HOME/.local/bin by default,
# which is not on PATH on GitHub-hosted runners by default.
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.local/bin:$PATH"
ubs --version || true
fi
- name: Run UBS on changed files
if: steps.changed.outputs.skip != 'true'
shell: bash
run: |
set -euo pipefail
mkdir -p test-results
# Pass the changed-file list to UBS in JSON mode. UBS scans complete
# files, and this repo has historical file-wide findings in several
# large Rust files, so the blocking gate compares the current scan
# against the exact base versions from this PR/push range and fails
# only when critical/warning counts increase.
xargs -a /tmp/ubs-changed-files.txt \
ubs --format=json --ci \
> test-results/ubs-report.json 2> test-results/ubs-stderr.log || true
base="${{ steps.changed.outputs.base }}"
base_dir="$(mktemp -d)"
: > /tmp/ubs-base-files.txt
while IFS= read -r file; do
if git cat-file -e "$base:$file" 2>/dev/null; then
mkdir -p "$base_dir/$(dirname "$file")"
git show "$base:$file" > "$base_dir/$file"
printf '%s\n' "$file" >> /tmp/ubs-base-files.txt
fi
done < /tmp/ubs-changed-files.txt
if [ -s /tmp/ubs-base-files.txt ]; then
(
cd "$base_dir"
xargs -a /tmp/ubs-base-files.txt \
ubs --format=json --ci \
> "$GITHUB_WORKSPACE/test-results/ubs-baseline.json" \
2> "$GITHUB_WORKSPACE/test-results/ubs-baseline-stderr.log" || true
)
else
printf '{"totals":{"critical":0,"warning":0,"info":0,"files":0}}\n' \
> test-results/ubs-baseline.json
: > test-results/ubs-baseline-stderr.log
fi
current_critical="$(jq -r '.totals.critical // 0' test-results/ubs-report.json)"
current_warning="$(jq -r '.totals.warning // 0' test-results/ubs-report.json)"
base_critical="$(jq -r '.totals.critical // 0' test-results/ubs-baseline.json)"
base_warning="$(jq -r '.totals.warning // 0' test-results/ubs-baseline.json)"
echo "UBS current: critical=$current_critical warning=$current_warning"
echo "UBS baseline: critical=$base_critical warning=$base_warning"
if [ "$current_critical" -gt "$base_critical" ] || \
[ "$current_warning" -gt "$base_warning" ]; then
echo "UBS regression: critical/warning findings increased on changed files."
echo "::group::UBS stderr (last 50 lines)"
tail -50 test-results/ubs-stderr.log || true
echo "::endgroup::"
echo "::group::UBS current summary"
jq -c '.' test-results/ubs-report.json | head -100
echo "::endgroup::"
echo "::group::UBS baseline summary"
jq -c '.' test-results/ubs-baseline.json | head -100
echo "::endgroup::"
exit 1
fi
echo "UBS PASS — no new critical/warning findings on changed files."
- name: Upload UBS report
if: always() && steps.changed.outputs.skip != 'true'
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: ubs-report-${{ github.run_id }}
path: |
test-results/ubs-report.json
test-results/ubs-stderr.log
test-results/ubs-baseline.json
test-results/ubs-baseline-stderr.log
if-no-files-found: ignore
retention-days: 7
# Rust unit tests
test-rust:
name: Rust Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git config --global core.longpaths true
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Configure Windows MSVC CRT for cc-rs
if: runner.os == 'Windows'
shell: pwsh
run: |
# esaxx-rs requests /MT via cc-rs, while ONNX Runtime ships /MD objects.
# Disable cc-rs defaults and choose /MD explicitly so all linked objects agree.
"CRATE_CC_NO_DEFAULTS=1" >> $env:GITHUB_ENV
"CFLAGS=/MD" >> $env:GITHUB_ENV
"CXXFLAGS=/MD" >> $env:GITHUB_ENV
- name: Run tests
# Explicit feature list excludes `strict-path-dep-validation` — see the
# Lint job for rationale.
run: cargo test --features "qr encryption backtrace" --verbose -- --nocapture
env:
RUST_LOG: debug
- name: Run doc tests
run: cargo test --doc
- name: Run Rust E2E tests with JSONL logging
shell: bash
run: |
set -euo pipefail
mapfile -t tests < <(git ls-files 'tests/e2e_*.rs' | sed 's#^tests/##; s#\\.rs$##')
if [[ "${#tests[@]}" -eq 0 ]]; then
echo "No e2e_* tests found; skipping."
exit 0
fi
args=()
for t in "${tests[@]}"; do
args+=(--test "$t")
done
# Explicit feature list excludes `strict-path-dep-validation` — see
# the Lint job for rationale.
E2E_LOG=1 cargo test --features "qr encryption backtrace" --verbose "${args[@]}" -- --nocapture
- name: Validate E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No E2E JSONL logs found to validate"
fi
- name: Upload E2E JSONL logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-jsonl-${{ matrix.os }}
path: test-results/e2e/*.jsonl
if-no-files-found: ignore
retention-days: 14
ssh-sync-docker:
name: SSH Sync Docker Tests
needs: [no-mock-audit]
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install SSH sync tools
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y openssh-client rsync
docker version
docker info
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run SSH sync integration tests
run: cargo test --features "qr encryption backtrace" --test ssh_sync_integration -- --ignored --test-threads=1 --nocapture
env:
RUST_LOG: debug
- name: Run SSH sources E2E tests
run: E2E_LOG=1 cargo test --features "qr encryption backtrace" --test e2e_ssh_sources -- --ignored --test-threads=1 --nocapture
env:
RUST_LOG: debug
- name: Validate SSH E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No SSH E2E JSONL logs found to validate"
fi
- name: Upload SSH E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: ssh-e2e-jsonl
path: test-results/e2e/*.jsonl
if-no-files-found: ignore
retention-days: 14
e2e-orchestrated:
name: E2E Orchestrator (Rust + Shell)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Install local rch compatibility shim
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/cass-rch-shim"
cat > "$RUNNER_TEMP/cass-rch-shim/rch" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" != "exec" ]]; then
echo "CI rch shim only supports: rch exec -- <command>" >&2
exit 2
fi
shift
if [[ "${1:-}" == "--" ]]; then
shift
fi
exec "$@"
EOF
chmod +x "$RUNNER_TEMP/cass-rch-shim/rch"
echo "$RUNNER_TEMP/cass-rch-shim" >> "$GITHUB_PATH"
- name: Run orchestrated E2E runner (Rust + Shell)
shell: bash
run: |
set -euo pipefail
RUN_PLAYWRIGHT=0 E2E_LOG=1 ./scripts/tests/run_all.sh
- name: Validate E2E JSONL logs
if: always()
shell: bash
run: |
if [[ -d "test-results/e2e" ]] && ls test-results/e2e/*.jsonl 1>/dev/null 2>&1; then
./scripts/validate-e2e-jsonl.sh test-results/e2e/*.jsonl
else
echo "No E2E JSONL logs found to validate"
fi
- name: Show E2E summary
if: always()
shell: bash
run: |
if [[ -f "test-results/e2e/summary.md" ]]; then
cat test-results/e2e/summary.md
else
echo "No summary.md found"
fi
- name: Upload orchestrated E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-orchestrated-logs
path: |
test-results/e2e/combined.jsonl
test-results/e2e/summary.md
test-results/e2e/*.jsonl
test-results/e2e/*.log
if-no-files-found: ignore
retention-days: 14
# TUI E2E matrix: themes × degradation × breakpoints (2dccg.11.4)
e2e-tui-matrix:
name: TUI E2E Matrix
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run TUI stress and E2E scenario tests
run: |
set -euo pipefail
cargo test --lib 'stress_' -- --nocapture 2>&1 | tee test-results-stress.txt
cargo test --lib 'e2e_scenario' -- --nocapture 2>&1 | tee test-results-e2e.txt
cargo test --lib 'cross_theme_degradation' -- --nocapture 2>&1 | tee test-results-matrix.txt
cargo test --lib 'rendering_token_affordance' -- --nocapture 2>&1 | tee test-results-affordance.txt
cargo test --lib 'density_' -- --nocapture 2>&1 | tee test-results-density.txt
env:
RUST_BACKTRACE: 1
- name: Generate TUI matrix summary
if: always()
shell: bash
run: |
set -euo pipefail
mkdir -p test-results/tui-matrix
{
echo "# TUI E2E Matrix Summary"
echo ""
echo "**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "**Commit:** ${{ github.sha }}"
echo ""
echo "## Test Categories"
echo ""
for f in test-results-*.txt; do
category=$(echo "$f" | sed 's/test-results-//; s/\.txt//')
passed=$(grep -c "^test .* ok$" "$f" 2>/dev/null || echo 0)
failed=$(grep -c "^test .* FAILED$" "$f" 2>/dev/null || echo 0)
echo "- **${category}**: ${passed} passed, ${failed} failed"
done
} > test-results/tui-matrix/summary.md
cat test-results/tui-matrix/summary.md
- name: Upload TUI matrix artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: tui-e2e-matrix
path: |
test-results-*.txt
test-results/tui-matrix/summary.md
retention-days: 14
# Crypto test vectors
crypto-vectors:
name: Crypto Test Vectors
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Run crypto vector tests
run: cargo test --test crypto_vectors -- --nocapture
env:
RUST_LOG: debug
# Security audit
security:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Install cargo-audit
uses: taiki-e/install-action@878643b9fbcb563eeb35c8d9abe2ea9c84cb55bb # cargo-audit
- name: Run cargo audit
run: cargo audit
# Build artifacts (verification only, not for release)
build:
name: Build (${{ matrix.target }})
needs: [lint, test-rust, ssh-sync-docker, crypto-vectors, security]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-15-intel
target: x86_64-apple-darwin
- os: macos-14
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
- name: Configure Windows MSVC CRT for cc-rs
if: runner.os == 'Windows'
shell: pwsh
run: |
# esaxx-rs requests /MT via cc-rs, while ONNX Runtime ships /MD objects.
# Disable cc-rs defaults and choose /MD explicitly so all linked objects agree.
"CRATE_CC_NO_DEFAULTS=1" >> $env:GITHUB_ENV
"CFLAGS=/MD" >> $env:GITHUB_ENV
"CXXFLAGS=/MD" >> $env:GITHUB_ENV
- name: Build release
run: cargo build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: cass-${{ matrix.target }}
path: target/${{ matrix.target }}/release/cass*
e2e-log-summary:
name: E2E Log Summary
needs: [test-rust]
runs-on: ubuntu-latest
if: always()
timeout-minutes: 10
permissions:
contents: read
pull-requests: write
steps:
- name: Download E2E log artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
continue-on-error: true
with:
pattern: 'e2e-jsonl-*'
path: artifacts
merge-multiple: true
- name: Aggregate E2E JSONL logs
shell: bash
run: |
set -euo pipefail
mkdir -p artifacts test-results/e2e
find artifacts -type f -name "*.jsonl" -print0 | sort -z | xargs -0 cat > test-results/e2e/combined.jsonl || true
- name: Generate E2E summary report
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json
from datetime import datetime, timezone
from pathlib import Path
combined_path = Path("test-results/e2e/combined.jsonl")
summary_path = Path("test-results/e2e/summary.md")
summary_path.parent.mkdir(parents=True, exist_ok=True)
total = passed = failed = skipped = flaky = 0
durations = {}
failures = []
if combined_path.exists():
for line in combined_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("event") != "test_end":
continue
runner = event.get("runner", "unknown")
result = event.get("result", {})
status = result.get("status", "unknown")
duration_ms = result.get("duration_ms", 0)
durations[runner] = durations.get(runner, 0) + int(duration_ms or 0)
total += 1
if status == "pass":
passed += 1
elif status == "skip":
skipped += 1
else:
failed += 1
retries = result.get("retries")
if status == "pass" and retries and int(retries) > 0:
flaky += 1
if status == "fail":
test = event.get("test", {})
error = event.get("error", {})
failures.append({
"runner": runner,
"suite": test.get("suite", "unknown"),
"name": test.get("name", "unknown"),
"file": test.get("file"),
"line": test.get("line"),
"message": error.get("message", "unknown error"),
})
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
lines = [
"# E2E Log Summary (CI)",
"",
f"**Generated:** {now}",
f"**Combined Log:** {combined_path.as_posix()}",
"",
"## Totals",
"",
f"- **Total Tests:** {total}",
f"- **Passed:** {passed}",
f"- **Failed:** {failed}",
f"- **Skipped:** {skipped}",
f"- **Flaky (passed on retry):** {flaky}",
"",
"## Duration by Runner",
"",
"| Runner | Duration (ms) |",
"|--------|---------------|",
]
if durations:
for runner, duration in sorted(durations.items()):
lines.append(f"| {runner} | {duration} |")
else:
lines.append("| (none) | 0 |")
lines.append("")
lines.append("## Failed Tests")
lines.append("")
if failures:
for f in failures:
location = ""
if f.get("file"):
if f.get("line"):
location = f"{f['file']}:{f['line']}"
else:
location = f"{f['file']}"
detail = f"{f['runner']} :: {f['suite']} :: {f['name']}"
if location:
detail += f" ({location})"
detail += f" — {f['message']}"
lines.append(f"- {detail}")
else:
lines.append("- None")
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Wrote {summary_path}")
PY
- name: Upload aggregated E2E logs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: e2e-log-summary
path: |
test-results/e2e/combined.jsonl
test-results/e2e/summary.md
retention-days: 14
if-no-files-found: ignore
- name: Comment summary on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const fs = require('fs');
const marker = '<!-- cass-e2e-summary -->';
const rustStart = '<!-- cass-e2e-rust:start -->';
const rustEnd = '<!-- cass-e2e-rust:end -->';
let summary = fs.readFileSync('test-results/e2e/summary.md', 'utf8');
if (summary.startsWith('# ')) {
summary = summary.replace(/^#\s+.*$/m, '## Rust E2E Summary');
} else if (!summary.startsWith('## ')) {
summary = `## Rust E2E Summary\n\n${summary}`;
}
const rustSection = `${rustStart}\n${summary.trim()}\n${rustEnd}`;
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find(comment => comment.body.includes(marker));
const upsertSection = (body, section) => {
if (body.includes(rustStart) && body.includes(rustEnd)) {
const regex = new RegExp(`${rustStart}[\\s\\S]*?${rustEnd}`, 'm');
return body.replace(regex, section);
}
return `${body.trim()}\n\n${section}`;
};
let body = existing ? existing.body : marker;
if (!body.includes(marker)) {
body = `${marker}\n${body}`;
}
body = upsertSection(body, rustSection);
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}
# Coverage Policy: See docs/COVERAGE_POLICY.md for full details
#
# Phased Threshold Schedule (br-2r76):
# Phase 1 (Current): 60% - Foundation
# Phase 2 (Q2 2026): 70% - Stability
# Phase 3 (Q3 2026): 80% - Confidence
# Phase 4 (Q4 2026): 90% - Excellence
#
# Update COVERAGE_THRESHOLD when advancing phases.
name: Coverage
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
# NOTE: `-D warnings` intentionally NOT set here. The coverage job compiles
# sibling path-dep crates via `[patch]` in Cargo.toml, which makes them
# behave like workspace members and bypass cargo's default `--cap-lints
# allow` for dependencies. Upstream warnings (e.g. fsqlite-pager
# dead_code) must not fail the coverage run — lint enforcement lives in
# the Lint job (ci.yml).
# Phased coverage threshold - update per docs/COVERAGE_POLICY.md schedule
COVERAGE_THRESHOLD: 60
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
coverage:
name: Test Coverage
runs-on: ubuntu-latest
# llvm-cov instrumented tests take noticeably longer than plain `cargo
# test` (2-3x). Previously the job short-circuited at the compile step
# so 30 min sufficed; now it gets through compile + ~3500 tests, which
# can push past 30 min on a cold cache. Match CI's `test-rust` matrix
# timeout (45 min) for safety.
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@8db66d64862314dae6eb34821203eb85fcbc5055 # cargo-llvm-cov
- name: Cache cargo registry
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-coverage-
${{ runner.os }}-cargo-
- name: Generate coverage report
env:
# Deterministic test execution
RUST_TEST_THREADS: 1
PROPTEST_CASES: 32 # Fixed seed count for property tests
# llvm-cov instrumentation balloons stack frames enough to overflow
# the default 2 MiB thread stack inside clap derive parsers
# (pages_cli_flag_tests). Bump to 16 MiB to match what cargo test
# uses natively for deeply nested generated code.
RUST_MIN_STACK: "16777216"
run: |
# Run with deterministic options and skip known flaky tests
cargo llvm-cov --workspace --lib -j 1 \
--ignore-filename-regex "(tests/|benches/)" \
--codecov \
--output-path codecov.json \
-- --skip install_sh --skip install_ps1
- name: Upload to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
files: codecov.json
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Generate coverage summary (JSON)
run: |
# Generate JSON coverage report (reuses instrumented data from previous step)
cargo llvm-cov report \
--ignore-filename-regex "(tests/|benches/)" \
--json \
--output-path coverage.json
# Extract coverage percentage
COVERAGE=$(jq -r '.data[0].totals.lines.percent // 0' coverage.json)
echo "## Test Coverage: ${COVERAGE}%" >> $GITHUB_STEP_SUMMARY
# Phased threshold indicators (see docs/COVERAGE_POLICY.md)
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Phase Progress" >> $GITHUB_STEP_SUMMARY
if (( $(echo "$COVERAGE >= 90" | bc -l) )); then
echo ":trophy: **Phase 4 Complete** - Excellence (90%+)" >> $GITHUB_STEP_SUMMARY
elif (( $(echo "$COVERAGE >= 80" | bc -l) )); then
echo ":star: **Phase 3 Complete** - Confidence (80%+)" >> $GITHUB_STEP_SUMMARY
elif (( $(echo "$COVERAGE >= 70" | bc -l) )); then
echo ":white_check_mark: **Phase 2 Complete** - Stability (70%+)" >> $GITHUB_STEP_SUMMARY
elif (( $(echo "$COVERAGE >= 60" | bc -l) )); then
echo ":heavy_check_mark: **Phase 1 Complete** - Foundation (60%+)" >> $GITHUB_STEP_SUMMARY
else
echo ":x: **Below Phase 1** - Needs improvement (<60%)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "_See [Coverage Policy](../docs/COVERAGE_POLICY.md) for targets_" >> $GITHUB_STEP_SUMMARY
- name: Generate gap report
run: |
COVERAGE_THRESHOLD=${{ env.COVERAGE_THRESHOLD }} \
./scripts/generate-gap-report.sh coverage.json gap-report.md
# Add top uncovered modules to job summary
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Top Coverage Gaps" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Extract top 5 uncovered modules
jq -r '.data[0].files[] |
select(.filename | contains("/src/")) |
select(.summary.lines.count > 100) |
{
filename: (.filename | split("/src/") | last | "src/" + .),
uncovered: (.summary.lines.count - .summary.lines.covered),
percent: (if .summary.lines.count > 0 then (.summary.lines.covered * 100 / .summary.lines.count | floor) else 0 end)
} |
select(.uncovered > 200) |
"| \(.percent)% | \(.uncovered) | `\(.filename)` |"' coverage.json | \
sort -t'|' -k3 -nr | head -5 | {
echo "| Coverage | Uncovered Lines | Module |"
echo "|----------|-----------------|--------|"
cat
} >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "_See gap-report.md artifact for full analysis_" >> $GITHUB_STEP_SUMMARY
- name: Check coverage threshold
if: github.event_name == 'pull_request'
run: |
COVERAGE=$(jq -r '.data[0].totals.lines.percent // 0' coverage.json)
# Use environment variable for phased threshold (see docs/COVERAGE_POLICY.md)
THRESHOLD=${{ env.COVERAGE_THRESHOLD }}
echo "Coverage: ${COVERAGE}%"
echo "Threshold: ${THRESHOLD}% (Phase 1 - Foundation)"
echo "Policy: See docs/COVERAGE_POLICY.md for phased targets"
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
echo "::error::Coverage ${COVERAGE}% is below ${THRESHOLD}% threshold"
echo "::error::Add tests for new code or see docs/COVERAGE_POLICY.md for exclusion process"
exit 1
fi
- name: Upload coverage artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-report
path: |
codecov.json
coverage.json
gap-report.md
retention-days: 14
# .github/workflows/fresh-clone-build.yml
# Fresh-clone build regression guard: ensures a clone with NO sibling repos
# still resolves dependencies. Catches accidental re-enabling of [patch]
# sections that point at non-existent sibling paths.
#
# Context: issue #181 — the [patch] blocks in Cargo.toml that override
# frankensqlite/franken_agent_detection/frankensearch to local path deps
# cause `cargo metadata` to fail on a fresh clone because the sibling repos
# aren't present. They must stay commented out by default; local devs
# uncomment for sibling-workspace builds.
name: Fresh Clone Build
on:
push:
branches: [main]
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/fresh-clone-build.yml'
pull_request:
branches: [main]
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/fresh-clone-build.yml'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
fresh-clone-metadata:
name: Fresh-clone metadata resolution
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout (no siblings)
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# Checkout into a named subdirectory so the workspace layout has
# NO sibling repos next to it — the exact condition that tripped #181.
path: cass
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Show Cargo.toml [patch] sections
working-directory: cass
run: |
echo "=== [patch] sections in Cargo.toml ==="
awk '/^[[:space:]]*\[patch/,/^[[:space:]]*$/' Cargo.toml || true
- name: Assert no active [patch] blocks for sibling repos
working-directory: cass
run: |
set -euo pipefail
# Any uncommented [patch."..."] line is a regression risk for #181.
# TOML table headers may have leading whitespace (cargo honors this),
# so match `^[[:space:]]*\[patch` rather than `^\[patch`.
# Comments start with `#`, so they still won't match this pattern.
if grep -nE '^[[:space:]]*\[patch' Cargo.toml; then
echo ""
echo "ERROR: Cargo.toml has active [patch] block(s)."
echo "These point at local sibling paths and break fresh-clone builds (#181)."
echo "Re-comment them before merging. See the note in Cargo.toml."
exit 1
fi
echo "OK: no active [patch] blocks."
- name: cargo metadata (full resolution, no siblings present)
working-directory: cass
run: |
set -euo pipefail
# Confirm no sibling repo is present — otherwise we'd resolve through
# the user's local copy and miss the regression we're trying to catch.
# Check each sibling separately (GNU `ls -d a b c` returns non-zero
# when any argument doesn't exist, so a mix of present/missing looks
# the same exit-code-wise as all-missing — not a usable discriminator).
for sibling in frankensqlite frankensearch franken_agent_detection; do
if [ -e "../$sibling" ]; then
echo "Unexpected sibling ../$sibling is present — test invalid."
exit 2
fi
done
cargo metadata --format-version 1 > /dev/null
name: Fuzzing
on:
schedule:
# Run daily at midnight UTC
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
duration:
description: 'Fuzzing duration in seconds'
required: false
default: '600'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
fuzz:
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
target:
- fuzz_decrypt
- fuzz_kdf
- fuzz_manifest
- fuzz_chunked
- fuzz_config
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Clone sibling dependencies
shell: bash
run: |
git clone --depth 1 https://github.com/Dicklesworthstone/asupersync.git ../asupersync
git clone --depth 1 https://github.com/Dicklesworthstone/frankensqlite.git ../frankensqlite
git clone --depth 1 https://github.com/Dicklesworthstone/franken_agent_detection.git ../franken_agent_detection
git clone --depth 1 https://github.com/Dicklesworthstone/frankensearch.git ../frankensearch
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@881ba7bf39a41cda34ac9e123fb41b44ed08232f # nightly
with:
components: llvm-tools-preview
- name: Install cargo-fuzz
run: cargo install cargo-fuzz
- name: Restore corpus cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
fuzz-corpus-${{ matrix.target }}-
- name: Run fuzzer
run: |
DURATION=${{ github.event.inputs.duration || '600' }}
cargo +nightly fuzz run ${{ matrix.target }} -- \
-max_total_time=$DURATION \
-max_len=65536 \
-print_final_stats=1 || true
- name: Save corpus
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
if: always()
with:
path: fuzz/corpus/${{ matrix.target }}
key: fuzz-corpus-${{ matrix.target }}-${{ github.sha }}
- name: Upload crashes
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: crashes-${{ matrix.target }}
path: fuzz/artifacts/${{ matrix.target }}
if-no-files-found: ignore
report:
runs-on: ubuntu-latest
needs: fuzz
if: always()
timeout-minutes: 10
steps:
- name: Summary
run: |
echo "## Fuzzing Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Targets fuzzed:" >> $GITHUB_STEP_SUMMARY
echo "- fuzz_decrypt" >> $GITHUB_STEP_SUMMARY
echo "- fuzz_kdf" >> $GITHUB_STEP_SUMMARY
echo "- fuzz_manifest" >> $GITHUB_STEP_SUMMARY
echo "- fuzz_chunked" >> $GITHUB_STEP_SUMMARY
echo "- fuzz_config" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Duration: ${{ github.event.inputs.duration || '600' }} seconds per target" >> $GITHUB_STEP_SUMMARY
# installer-notify.yml
# Copy this to .github/workflows/ in your project
# Notifies ACFS when install.sh changes
#
# Setup:
# 1. Create a GitHub PAT with `repo` scope
# 2. Add it as ACFS_DISPATCH_TOKEN secret in your repo
# 3. Copy this file to .github/workflows/
name: Notify ACFS of Installer Change
on:
push:
branches: [main]
paths:
- 'install.sh'
- 'scripts/install.sh'
- '**/install.sh'
pull_request:
branches: [main]
paths:
- 'install.sh'
- 'scripts/install.sh'
- '**/install.sh'
concurrency:
group: installer-notify-${{ github.ref }}
cancel-in-progress: true
jobs:
notify-acfs:
# Only notify on push to main, not PRs
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Compute installer SHA256
id: checksum
run: |
# Find the installer file
if [ -f install.sh ]; then
INSTALLER_PATH="install.sh"
elif [ -f scripts/install.sh ]; then
INSTALLER_PATH="scripts/install.sh"
else
echo "No installer found"
exit 1
fi
SHA256=$(sha256sum "$INSTALLER_PATH" | cut -d' ' -f1)
echo "sha256=$SHA256" >> $GITHUB_OUTPUT
echo "Computed SHA256: $SHA256"
- name: Notify ACFS
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ACFS_DISPATCH_TOKEN }}
repository: Dicklesworthstone/agentic_coding_flywheel_setup
event-type: installer-updated
client-payload: |
{
"tool": "${{ github.event.repository.name }}",
"repo": "${{ github.repository }}",
"commit": "${{ github.sha }}",
"new_sha256": "${{ steps.checksum.outputs.sha256 }}",
"ref": "${{ github.ref }}",
"actor": "${{ github.actor }}"
}
- name: Log notification
run: |
echo "::notice::Notified ACFS about installer change"
echo "Repository: ${{ github.repository }}"
echo "Commit: ${{ github.sha }}"
echo "SHA256: ${{ steps.checksum.outputs.sha256 }}"
# Validate installer syntax on PRs
validate-installer:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Shellcheck installer
run: |
EXIT_CODE=0
for script in install.sh scripts/install.sh; do
if [ -f "$script" ]; then
echo "Checking $script..."
shellcheck "$script" || EXIT_CODE=1
fi
done
exit $EXIT_CODE
latest
# UBS ignore patterns
# Files and directories that should not be scanned for bugs
# Build artifacts
target/
# AI coding tool artifacts
.aider.chat.history.md
.aider.input.history
.aider.tags.cache.v3/
# Temporary scratch files
temp_*.rs
# Test fixtures (not production code)
tests/fixtures/
tests/golden/html_export/*.golden
# Lock files (auto-generated)
Cargo.lock
# Editor/IDE
.idea/
.vscode/
*.swp
*.swo
# Environment
.env
.venv/
# Claude Code artifacts
.claude/
# Local backup directories
.local_backup_*/
Related skills
Forks & variants (1)
Cass has 1 known copy in the catalog totaling 81 installs. They canonicalize to this original listing.
- dicklesworthstone - 81 installs
How it compares
Pick cass for agent session alignment and recall; use standalone issue trackers when the primary need is human sprint planning rather than agent continuity.
FAQ
What is cass?
Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Gemini, Cursor, Aider, ChatGPT, Pi-Agent, Factory, and more. P
When should I use cass?
Coding Agent Session Search - unified CLI/TUI to index and search local coding agent history from Claude Code, Codex, Gemini, Cursor, Aider, ChatGPT, Pi-Agent, Factory, and more. P
Is cass safe to install?
Review the Security Audits panel on this page before installing in production.