A reusable, deterministic GitHub operations and intelligence platform — now frozen for maintenance.
"A personal GitHub operations layer running on GitHub Actions."
Implementation status: COMPLETE / FROZEN 🔒
All planned implementation phases and the post-Phase-8 hardening/intelligence line are complete. The phase labels below are historical milestones only. Phase 8 is the final numbered milestone; the complete frozen system is represented by the post-Phase-8 batches and the 1,495-test baseline.
| Historical milestone | Status | Tests |
|---|---|---|
| Phase 0 — Architecture | Complete | — |
| Phase 1 — Core Runtime | Complete | 112/112 |
| Phase 2 — GitHub API Client | Complete | 73/73 |
| Phase 3 — State & Event Engine | Complete | 106/106 |
| Phase 4 — Repository Monitoring | ✅ Complete | 388/388 tests |
| Phase 5 — OSS Intelligence | ✅ Complete | 494/494 tests |
| Phase 6 — Developer Intelligence | ✅ Complete | 569/569 tests |
| Phase 7 — Telegram Notifications | ✅ Complete | 766/766 tests |
| Phase 8 — GitHub Actions | ✅ Complete | 1084/1084 tests |
| Security Intelligence batch | ✅ Complete | 1150/1150 tests |
| Reporting + product-hardening batch | ✅ Complete | 1260/1260 tests |
| Final intelligence hardening batch | ✅ Complete | 1324/1324 tests |
| Laya + LLM interpretation batch | ✅ Complete | 1367/1367 tests |
| Deterministic intelligence layer + OSS Radar | ✅ Complete | 1495/1495 tests |
Total: 1495/1495 tests passing.
Figures are cumulative historical checkpoints. The complete system is the final 1,495-test frozen baseline; the security, reporting, repository/event, Telegram, Laya/LLM, and deterministic-intelligence work all landed after the original Phase 8 milestone.
Implementation: Complete · Architecture: Complete · Intelligence: Complete · Telegram: Hardened · OSS Radar: Complete
The deterministic core, intelligence layer, reporting pipeline, Telegram contracts, OSS Radar, optional Laya/LLM interpretation, GitHub Actions boundaries, and documentation are complete. The repository is now in maintenance mode: bug fixes and evidence-driven hardening are welcome; no further feature expansion is planned for this frozen baseline.
Final verification: 1495/1495 tests passing · compileall clean · touched-path Ruff clean · Telegram contracts green · secret audit clean · master == origin/master.
See ARCHITECTURE.md for the full system design.
# Install dependencies
pip install -r requirements.txt
# Configure secrets locally (the file is gitignored)
cp .env.example .env
# then edit .env and fill in the values
# Run locally
python scripts/run_local.py daily
# Or run a job directly — the same entry point the workflows use
python -m src.jobs status # read-only: report current state + config
python -m src.jobs daily
# Check Telegram delivery configuration (prints no secrets)
python scripts/check_telegram.pyThe dispatcher is the same code path in CI and locally:
python -m src.jobs <job_name> # daily | monitoring | weekly-report
# oss-hunt | security | status
GHOPS_JOB=weekly-report python -m src.jobs
python -m src.jobs # prints usage and exits 1
python -m src.jobs not-a-job # prints available jobs, exits 1Jobs read GITHUB_TOKEN for collection and TELEGRAM_BOT_TOKEN for delivery.
A job never exits 0 without having executed one.
Monitoring alerts remain event-driven. When telegram.run_summary is enabled, successful daily and weekly runs may send briefs built from the shared reporting ledger (only when meaningful events exist; a quiet run sends nothing). Briefs render in a fixed attention-first order: 🚨 Attention (action-required only) → 🛡️ Security → CI → 🧭 OSS → 🧑💻 Developer → 🚀 Releases & Repositories → Overall: line, followed by deterministic intelligence blocks (🎯 Focus, 📦 Repository Signals, 📈 Trends) built from the bounded evidence pack. Data-quality warnings are ledger-gated: each distinct state is briefed once, then silent until it changes. Successful CI runs (and gh-ops's own workflow runs) are silenced at conversion; failures of gh-ops's own enumerated workflows render as GH-OPS · SYSTEM alerts. Completion summaries (run/oss/security) are no longer sent by jobs; their formatters remain exported for API compatibility. Repository counts come only from config/repositories.yml; accessible account repositories are never added automatically. OSS opportunities are deduplicated across runs via the oss_opportunities resource key in Phase 3 state (at-least-once: identities are marked only after successful delivery); a previously delivered opportunity whose comments, state, stars, or updated timestamp change is re-announced exactly once under the distinct 🧭 GH-OPS · OSS RADAR title, while unchanged opportunities stay silent. Security findings are likewise deduplicated across runs via the security_notified resource key (findings live under security; only actionable severities are batch-delivered, marked only after successful delivery; recovery events clear the mark and may send a RESOLVED report).
Secrets live in environment variables, never in source or config. For local
development you can keep them in a gitignored .env file:
cp .env.example .env.env is gitignored (.gitignore: .env, .env.*) — confirm with
git check-ignore -v .env. Only .env.example, which holds no values, is tracked.
| Variable | Used by | Notes |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Phase 7 Telegram delivery | The only variable consulted for the bot token |
GITHUB_TOKEN / GH_TOKEN |
Phases 2–6 GitHub collection | Either name works |
GH_OPS_LLM_ENABLED |
Brief interpretation | Kill-switch (default true); with no keys configured nothing is ever called |
GH_OPS_LLM_PROVIDERS |
Brief interpretation | Provider try-order, default groq,gemini |
GH_OPS_LLM_TIMEOUT_S |
Brief interpretation | Per-request timeout, default 12 |
GROQ_API_KEY / GROQ_MODEL |
Groq cloud provider | Base URL has a built-in default |
GEMINI_API_KEY / GEMINI_MODEL |
Gemini cloud provider | Base URL has a built-in default; model gemini-3.8-flash verified live |
LAYA_ENABLED |
Laya System-1 triage | Default true; checkpoint loads lazily on first brief and is cached |
Loading rules (src/utils/env.py, standard library only — no new dependency):
- The process environment always wins.
.envnever overrides a variable that is already set, so a CI secret cannot be shadowed by a stale local file. - Values are never logged or printed.
load_env_file()returns variable names. - A missing
.envis a no-op, so production runs are unaffected. - Comment lines, blank lines, an
exportprefix, quoted values, and#inline comments are supported.
python scripts/check_telegram.py reports readiness using booleans, counts, and
the token length only — it never prints the token, so its output is safe to paste
into a terminal or a CI log. It exits non-zero when delivery is not ready.
Daily and weekly briefs gain one advisory 🧠 headline line — an addition, not
a replacement: event collection, ordering, deduplication, priority, lifecycle,
delivery, and every contract test stay exactly as they were, and the brief is
byte-identical to the deterministic rendering whenever interpretation is
unavailable.
built brief (deterministic)
→ intelligence context: evidence pack → temporal state → correlations
→ deterministic focus (attention | security | operations | … | quiet)
→ non-meaningful/quiet context stops here: no model is consulted
→ Laya System-1 triage proposes a label from a fixed allow-list
(local, lazy, one ~2.2 GB checkpoint, cached, runs only at brief time)
→ reconcile: deterministic attention/security always win; Laya "quiet"
may only silence a non-urgent focus
→ "quiet" skips the LLM; otherwise the cloud LLM writes one ≤60-word
sentence (provider chain groq → gemini, OpenAI-compatible, env credentials)
→ grounding: every URL, repository, and item number in the sentence must
exist in the evidence pack, else the claim is dropped
→ strict validation (length, printable text, no JSON, no internal strings)
→ 🧠 line inserted after Coverage, display-only
Rules:
- Fully functional with neither layer: no keys, dead provider, timeout, or a
missing Laya install all collapse to
Noneand the brief renders unchanged. - A context that is not meaningful (quiet) never reaches either layer, so a calm day costs zero model calls.
- Laya's numeric score is an uncalibrated model score, never a probability.
- Laya is advisory: its label is validated against a fixed allow-list before use; it never mutates state, executes anything, or alters event output.
- Credentials come from the environment only (
.envis gitignored; never committed, never logged). No Ollama, no local LLM — cloud providers only. - GitHub Actions runs stay deterministic: workflows configure no LLM keys and never install the Laya extra, so interpretation is a no-op there by design.
gh-ops resolves GitHub tokens from environment variables:
| Variable | Priority | Description |
|---|---|---|
GITHUB_TOKEN |
1 (preferred) | Standard GitHub token env var |
GH_TOKEN |
2 (fallback) | Alternative GitHub token env var |
- Token is never logged, printed, or serialized
AuthConfig.__repr__shows only the source variable name, not the token- If no token is found,
resolve_auth()raisesGhOpsError - Whitespace-only tokens are treated as empty
src/github/client.py is the single HTTP exit point for all GitHub API communication.
gh-ops V1 is read-only with respect to GitHub. The client enforces this:
client.execute("POST", ...)raisesWriteDeniedErrorclient.execute("PUT", ...)raisesWriteDeniedErrorclient.execute("PATCH", ...)raisesWriteDeniedErrorclient.execute("DELETE", ...)raisesWriteDeniedError
Only GET requests are permitted. This is a hard boundary — no collectors, handlers, or utilities can bypass it.
client.get_paginated() handles pagination automatically:
- Follows
Linkheaderrel="next"references - Configurable
per_page(max 100) andmax_pages(safety limit, default 10) - Returns
PaginatedResultwith all items collected across pages - Supports ETag conditional requests (304 Not Modified)
- Pass
etagparameter toget_paginated()for conditional requests - On 304 Not Modified, returns
PaginatedResultwithnot_modified=True - ETag values are passed through for the state layer to persist (Phase 3)
- Reads
X-RateLimit-Remaining,X-RateLimit-Limit,X-RateLimit-Resetheaders - Updates
RateLimitTrackeron every response - On 429 (rate limited): parses
Retry-Afterheader, retries with exponential backoff - On 5xx (server errors): retries with exponential backoff + jitter
- Exponential backoff:
min(1.0 * 2^attempt + random(0, 0.5), 30.0)seconds - Maximum 3 retries (configurable via
max_retries) on_rate_limitcallback for monitoring integration
| HTTP Status | Error Code | Description |
|---|---|---|
| 401 | API_AUTH_FAILED |
Invalid/missing token |
| 403 | API_FORBIDDEN |
Insufficient permissions |
| 404 | API_NOT_FOUND |
Resource not found |
| 429 | API_RATE_LIMITED |
Rate limit exceeded |
| 500+ | INTERNAL_ERROR |
Server error |
All errors are typed GitHubClientError instances with status code, endpoint, and rate-limit metadata.
Collectors are thin wrappers that call client.get() or client.get_paginated() and return normalized models.
| Collector | Endpoint | Returns |
|---|---|---|
repos |
/user/repos, /orgs/{org}/repos, /repos/{owner}/{repo} |
Repository |
issues |
/repos/{owner}/{repo}/issues, /issues, /search/issues |
Issue |
pulls |
/repos/{owner}/{repo}/pulls |
PullRequest |
releases |
/repos/{owner}/{repo}/releases |
Release |
workflows |
/repos/{owner}/{repo}/actions/runs |
WorkflowRun |
security |
/repos/{owner}/{repo}/dependabot/alerts, /code-scanning/alerts |
SecurityAlert |
user |
/user, /users/{username}, /rate_limit |
User |
- No
requestsimport — collectors use onlyGitHubClientand models - No credential access — collectors never see the token
- No HTTP logic — pagination, retries, rate limits are in
client.py - Deterministic parsing —
Model.from_api()handles all field extraction - Frozen dataclasses — models are immutable after creation
All API responses are parsed into frozen dataclasses in src/github/models.py:
| Model | Key Fields |
|---|---|
Repository |
id, full_name, owner, name, stars, forks, language, topics |
Issue |
id, number, title, state, labels, user, comments |
PullRequest |
id, number, title, state, head_branch, base_branch, merged |
Release |
id, tag_name, name, author, assets_count |
WorkflowRun |
id, name, status, conclusion, event, is_success, is_failure |
SecurityAlert |
id, package_name, severity, summary, state |
User |
id, login, name, public_repos, followers |
PaginatedResult |
items, total_count, has_next, etag, not_modified |
All models provide to_dict() for serialization and from_api() for parsing.
gh-ops V1 performs zero write operations against the GitHub API.
- No issue creation, commenting, or labeling
- No pull request creation, merging, or reviewing
- No release creation or asset uploading
- No workflow triggering
- No repository starring, watching, or forking
The WriteDeniedError enforcement in client.py makes this a hard guarantee, not a convention.
gh-ops implements a deterministic state engine that detects changes between collection runs.
| Concept | Description |
|---|---|
| Snapshot | What was observed during a single collection run |
| CurrentState | The latest successfully accepted observation per resource type |
| Event | The deterministic transition between observations |
| HistoryEntry | What has previously been observed/emitted |
diff_resources() produces NEW/CHANGED/REMOVED/UNCHANGED events with field-level change tracking:
from src.core.events import diff_resources
events = diff_resources(
resource_type="issues",
previous_items={"#1": {"state": "open", "title": "bug"}},
current_items={"#1": {"state": "closed", "title": "bug"}},
)
# events[0].event_type == EventType.CHANGED
# events[0].changes == (FieldChange(field="state", before="open", after="closed"),)A failed collector MUST NOT cause valid previous state to disappear:
- SUCCESS — overwrite that collector's items
- NOT_MODIFIED — preserve existing items (304 response)
- FAILURE — preserve previous items, record the failure
- Schema versioning with
STATE_SCHEMA_VERSION = 1 - Atomic writes: temp file → flush → fsync → atomic replace
- Corruption handling: MISSING / VALID / CORRUPT / UNSUPPORTED_VERSION
- ETag persistence for conditional requests
from src.core.state import load_current_state, apply_snapshot
from src.core.models import Snapshot, CollectorResult, CollectorStatus
# Load previous state
previous = load_current_state()
# Apply a snapshot (partial failure safe)
snapshot = Snapshot(results={
"repos": CollectorResult(collector="repos", status=CollectorStatus.SUCCESS, items={...}),
"releases": CollectorResult(collector="releases", status=CollectorStatus.FAILURE, error="timeout"),
})
new_state = apply_snapshot(previous, snapshot)src/developer/ turns data already collected by Phase 2/3 into factual,
descriptive activity statistics. It performs no network calls of its own,
introduces no LLM, and makes no judgement about the developer.
CurrentState (resources) + Phase 3 events
↓
activity.py — normalize into ActivityRecord
↓
statistics.py — descriptive counts and breakdowns
↓
reports.py — structured DeveloperReport
↓
Phase 7 Telegram delivery (escaped, split, per-chat isolation)
A record is emitted only when the corresponding resource field exists and parses to a UTC datetime. Nothing is fabricated.
ActivityType |
Timestamp source | Emitted when |
|---|---|---|
issue_created |
created_at |
NEW issue event |
issue_closed |
closed_at |
state changed to closed |
issue_commented |
updated_at |
comments count increased |
pr_opened |
created_at |
NEW pull event |
pr_merged |
merged_at |
merged changed to true |
pr_closed |
closed_at |
state changed to closed, with no merge evidence |
release_published |
published_at |
NEW release event |
workflow_run |
run_started_at |
NEW or CHANGED workflow event |
- Closed PR ≠ merged PR.
pr_mergedrequires the source data to establish a merge (mergedtrue /merged_atpresent). A closed PR without merge evidence ispr_closed. - Current state ≠ historical event. Timestamps come from the resource fields
(
created_at,closed_at,merged_at), never from collection time. - Missing ≠ zero. A failed collector produces a
DataQualityentry withis_complete=Falseand its error message; the report reports completeness rather than reporting zero activity. - First observation. When an issue is already closed, or a PR is already merged, at first observation, both the create (or open) and the close/merge are emitted using the resource's own timestamps.
Pure aggregation over ActivityRecord lists — no I/O:
ActivityCounts— per-type counts plustotalcompute_by_repository()—RepoActivityper repositorycompute_by_period()— fixed-widthPeriodActivitybucketscompute_active_days(),compute_active_repositories(),compute_date_range()summarize_data_quality()— collector completeness percentage
ReportPeriod presets: last(days), this_week() (Monday 00:00 UTC), and
this_month() (1st 00:00 UTC).
generate_report() deduplicates records, bounds them to the period
(since and until both inclusive), then computes counts, the per-repository
breakdown, by_period buckets, and data-quality context.
The aggregation is a pure function of its input:
- Frozen dataclasses; no randomness in aggregation
- Records sorted by timestamp;
by_repositorysorted by total descending then repository name;by_periodchronological;active_repositoriessorted - Dedup key is
(activity_type, item_id, timestamp)
Two values are generation-time by construction and differ between runs:
generated_at, and the trailing by_period bucket's period_end (clamped to
the reference time). Pass an explicit reference_time to compute_by_period()
to make bucket boundaries reproducible.
- Coverage is bounded by what Phase 2/3 collected; there is no backfill
- Workflow runs are not attributed to a developer, so per-user reports still include collected workflow runs
- Releases and workflow runs carry no repository name in the normalized record,
so they group under
(unknown)in per-repository breakdowns - A PR already closed-without-merge at first observation yields only
pr_opened, because that close transition was never observed - A report describes one period from one snapshot; no history or trends
src/notifications/telegram.py delivers already-computed results to Telegram.
It is a thin outbound delivery layer: it does not query GitHub, contains no
intelligence or scoring, does not mutate state, performs no GitHub writes,
accepts no inbound control, and calls no LLM/agent/MCP/UEA runtime.
structured results (MonitorResult / Opportunity / DeveloperReport)
↓
formatters — escaped MarkdownV2, split to fit Telegram's limit
↓
TelegramTransport — finite timeout, bounded retries, 429 / Retry-After
↓
Telegram Bot API — https://api.telegram.org (fixed module constant)
- Read from
TELEGRAM_BOT_TOKENonly — never from config, CLI arguments, or state - Config that sets
bot_token_refto any other variable is rejected at load time - The token is never logged, never placed in errors or error context, and never persisted
- The token is embedded in the request URL, so raw URLs are excluded from logs and errors
- Raw exception text is excluded too, since third-party exceptions can embed the URL
logging.pyadditionally redacts Telegram token patterns as defence-in-depth
telegram:
enabled: false
bot_token_ref: "TELEGRAM_BOT_TOKEN"
chat_ids:
- chat_id: 123456789
topics: [alerts, oss_opportunities]
- chat_id: 987654321 # no topics = receives everything
message:
max_length: 4096
parse_mode: "MarkdownV2"
retry_attempts: 3
retry_delay_seconds: 5
timeout_seconds: 10| Topic | Entry point |
|---|---|
alerts |
notify_monitor_alerts — ALERT and ERROR monitor results only (includes GH-OPS · SYSTEM for gh-ops's own failing workflow runs; successful CI/system runs send nothing) |
oss_opportunities |
notify_oss_opportunities |
developer_report |
notify_developer_report — a Phase 6 DeveloperReport |
run_summary |
notify_report — daily/weekly briefs built from the reporting ledger (telegram.run_summary) |
oss_summary |
reserved; jobs no longer send OSS completion summaries (formatter kept for API compatibility) |
security |
notify_security_alerts — open findings batch; recovery events via notify_report |
| Condition | Behavior |
|---|---|
2xx ok: true |
delivered |
2xx ok: false |
structured error |
| 400 / 404 | structured error, no retry |
| 401 / 403 | structured error, no retry |
| 429 | retried after Retry-After, else exponential backoff |
| 5xx | retried with exponential backoff |
| timeout / network | retried, then structured error |
- Attempts are bounded at
retry_attempts + 1; every request has a finite timeout - Delivery is a safe no-op when disabled, when no chat matches the topic, or when no token is set — it never raises for a delivery problem
- Failures are isolated per chat: one failing chat does not block the others, and a failing chat receives no further messages in that run
- All text — including the message title — goes through
escape_markdown_v2, so a hostile repository name or issue title cannot open or close a MarkdownV2 entity - Blocks are escaped independently and then packed, so packing never splits an escape sequence; oversized blocks are split on their escaped form with a dangling backslash carried onto the next chunk
- Every message is at most
max_lengthcharacters, even when escaping doubles the text - Output is byte-identical for identical input
- Outbound only: no commands, no polling, no webhooks
- URLs are sent as escaped plain text rather than inline links
- Digest scheduling (daily/weekly jobs) belongs to Phase 8; this phase provides formatting and delivery only
Actions is the scheduler; the application stays in Python.
trigger (cron / workflow_dispatch)
↓
python -m src.jobs ← job name read from GHOPS_JOB
↓
core/dispatcher.py
↓
src/jobs/jobs.py ← collect → state/events → monitors/intelligence/developer
↓
Telegram ← outbound only
The only substantive step in any workflow is python -m src.jobs. No expression
is interpolated into a shell command, and no business logic lives in YAML.
| Workflow | Schedule (UTC) | Schedule (IST) | Job | Purpose |
|---|---|---|---|---|
security.yml |
0 2 * * * |
07:30 | security |
Dependabot + code scanning radar, at-least-once alerts |
oss-hunter.yml |
0 12 * * * |
17:30 | oss-hunt |
OSS opportunity discovery |
daily.yml |
30 15 * * * |
21:00 | daily |
Collection, release/CI detection, alert digest, daily brief |
weekly-report.yml |
0 16 * * 1 |
Mon 21:30 | weekly-report |
Personal activity report + weekly brief |
monitoring.yml |
30 */2 * * * |
every 2h at :30 | monitoring |
CI failures + security alerts |
manual.yml |
on demand | — | any | workflow_dispatch with a choice job input |
Schedules are staggered so no two state-writing jobs share a minute
(monitoring uses :30, others :00/:30 on non-overlapping slots).
IST = UTC+5:30 (fixed offset; no tzdata dependency).
All six also support manual dispatch. Every job has a 25-minute timeout.
Every workflow declares explicit read-only scopes. Nothing has write.
| Workflow | contents |
issues |
pull-requests |
actions |
security-events |
|---|---|---|---|---|---|
daily.yml |
read | read | read | read | — |
monitoring.yml |
read | — | — | read | read |
weekly-report.yml |
read | read | read | — | — |
oss-hunter.yml |
read | read | — | — | — |
security.yml |
read | — | — | — | read |
manual.yml |
read | read | read | read | read |
Note the key is security-events (hyphen); security_events is not a valid
permission name.
| Secret | Used for |
|---|---|
GITHUB_TOKEN |
Repository data collection (read-only scopes above) |
TELEGRAM_BOT_TOKEN |
Outbound delivery |
Both are provided through the env: block only — never as CLI arguments, never
in a cache key, never in run: text. Add TELEGRAM_BOT_TOKEN under
Settings → Secrets and variables → Actions. GITHUB_TOKEN is automatic.
Actions cache carries data/state/ between runs. It is a rolling snapshot, not
a database:
- Restore uses the shared prefix
gh-ops-state-, which resolves to the most recently created snapshot regardless of which workflow wrote it. - Save uses a fresh immutable key
(
gh-ops-state-<job>-${{ github.run_id }}-${{ github.run_attempt }}), because cache entries cannot be overwritten. - A
${{ github.run_id }}-only key would be unusable as the restore path, since the next run can never hit it exactly. - All state-writing workflows share one
concurrency: gh-ops-stategroup withcancel-in-progress: false, serializing the read-modify-write cycle against the single shared state file.
| Condition | Result |
|---|---|
| One repository fails to collect | Job succeeds; other repositories still collected, previous state preserved |
| Bad config, missing credentials, unreadable state | Job fails, exit 1, state not saved |
| Telegram delivery fails | Job still succeeds; state was already persisted |
| Unknown or missing job name | Exit 1 |
A job invocation never exits 0 without having executed a job.
- The state cache is evicted after 7 idle days of no runs, and lives in a shared 10 GB LRU pool that grows by one entry per successful run.
- GitHub runs at most one running plus one pending run per concurrency group; a third queued run replaces the pending one.
GITHUB_TOKENmay be refused by the Dependabot alerts endpoint. When that happens the collector is recorded as a partial failure and previous state is preserved — the system never reports "no alerts" for a check it could not run.
All tests use mocked HTTP responses — no live GitHub contact:
# Run full test suite
python -m pytest tests/ -v
# Run specific module tests
python -m pytest tests/github/ -v
python -m pytest tests/core/ -v
# Validate the workflow YAML, permissions, pins, and state strategy
python -m pytest tests/ci/ -vWorkflow validation is static: it parses .github/workflows/*.yml and asserts
the security and state invariants documented below. It never triggers a workflow
or contacts GitHub.
| Module | Tests | Coverage |
|---|---|---|
core/errors.py |
12 | Error types, collector, serialization |
core/config.py |
13 | YAML loading, deep merge, validation |
core/state.py |
60 | Load/save, diff, prune, merge, validation, atomic writes, apply_snapshot, ETags, history |
core/events.py |
49 | detect_events, diff_resources, field changes, determinism, normalization |
core/models.py |
24 | State models, validation, serialization |
core/rate_limit.py |
17 | Tracker, headers, backoff |
core/dispatcher.py |
7 | Job registration, execution |
core/dispatcher.py (CLI) |
24 | Exit codes, env-var job selection, module + run_local.py entry points |
core/lifecycle.py |
17 | Repository lifecycle tracking |
github/auth.py |
15 | Token resolution, AuthConfig |
github/client.py |
16 | GET-only, retry, pagination, ETags |
github/models.py |
19 | 8 dataclasses, from_api, to_dict |
github/collectors.py |
23 | 7 collectors, mocked responses |
monitors/repository.py |
12 | Repository event evaluation, collection failures |
monitors/ci.py |
12 | CI workflow event evaluation, failures, recovery |
monitors/release.py |
12 | Release event evaluation, prerelease/draft filtering |
monitors/endpoint.py |
26 | Security validation, config parsing, URL checks |
monitors/__init__.py |
16 | Registry, event dispatch, summary |
monitors/monitor.py |
14 | MonitorResult model, classification, properties, SECURITY category |
monitors/* (properties) |
5 | Property tests for MonitorResult invariants |
utils/text.py |
23 | MarkdownV2 escaping, split, truncate |
utils/time.py |
16 | Timestamps, relative time |
utils/env.py |
31 | .env parsing, precedence, secret safety |
intelligence/oss/models.py |
9 | Opportunity, ScoreBreakdown, from_search_result |
intelligence/oss/queries.py |
16 | Query generation, dedup, config, custom queries |
intelligence/oss/filters.py |
23 | 9 filter rules, bot detection, config parsing |
intelligence/oss/scorer.py |
26 | 7 scoring signals, normalization, tie-breaking |
intelligence/oss/dedup.py |
18 | Identity dedup, provenance merge, idempotency |
intelligence/oss/hunter.py |
14 | Orchestrator, metadata enrichment, partial failure |
developer/activity.py |
43 | Activity extraction, filtering, dedup, data quality |
developer/statistics.py |
21 | Counts, repo breakdown, periods, active days |
developer/reports.py |
16 | Report generation, periods, formatting |
notifications/telegram.py (config) |
48 | Token resolution, config validation, chat routing, summary toggles |
notifications/telegram.py (transport) |
40 | Send, 4xx/5xx, retries, Retry-After, token safety |
notifications/telegram.py (formatters) |
47 | Escaping, splitting, long messages, determinism |
notifications/telegram.py (notifier) |
33 | Routing, failure isolation, safe skipping |
notifications/telegram.py (summaries) |
11 | Run/oss summary formatting, toggles, delivery isolation |
intelligence/security/* |
24 | Severity ranks, findings, radar, report shape |
monitors/security.py |
17 | Alert evaluation, registry wiring, disabled/error paths |
notifications/security report |
15 | Security formatters, toggles, topic routing, isolation |
jobs/jobs.py |
53 | Six jobs, dispatch, partial failure, delivery isolation, OSS + security dedup |
jobs/* (architecture) |
18 | Dependency direction, no HTTP/LLM/eval in the job layer |
reporting/* |
68 | Conversion, priority, ledger, briefs (attention-first sections, optional LLM headline) |
notifications/telegram.py (contract) |
38 | Transport-boundary golden examples, silence, topic routing |
security/test_security_hardening.py |
40 | Phase 9 static verification: GET-only, no eval/shell, redaction |
intelligence/llm.py |
18 | Output validation, env config, provider failover, key safety |
intelligence/laya_adapter.py + interpret.py |
21 | System-1 gate, allowed-label validation, Laya → LLM orchestration |
intelligence/evidence.py |
15 | Evidence pack: dedup, ordering, 40-item bound, derived slug/URL/ID sets |
intelligence/temporal.py |
18 | State precedence, stale detection, recurring/quieted trends |
intelligence/correlate.py |
10 | One-per-repository priority chain, non-causal statements |
intelligence/attention.py |
35 | Report-mode allocation, focus, meaningfulness, reconcile, Laya triage |
intelligence/grounding.py |
14 | Claim validation: bounds, URL/slug/evidence-ID/item-number allow-lists |
intelligence/context.py |
9 | Bounded digest, focus derivation, determinism |
intelligence/interpret.py (context headline) |
10 | Quiet skip, reconcile, grounding of the LLM sentence |
reporting/narratives.py |
12 | Focus, repository signals, trends, OSS radar blocks |
jobs/* (intelligence wiring) |
5 | Brief focus/fail-soft/quiet day, OSS new → changed → silent |
.github/workflows/* |
220 | Static validation: YAML, permissions, SHA pins, schedules, secrets, state |
| Total | 1495 |
The post-Phase-8 hardening suites add Telegram transport-boundary contracts, unified reporting, security-intelligence delivery, cross-run deduplication, repository/event lifecycle hardening, and the final response-UX batch (attention-first briefs, gh-ops system-workflow classification, data-quality ledger gating). The deterministic intelligence batch adds the evidence-pack, temporal, correlation, attention, grounding, and narrative suites plus job-wiring tests.
The portfolio is actively contributing upstream alongside GH-OPS maintenance. The latest open PRs are:
- LlamaIndex #23260 — preserves originally retrieved node scores during overlapping Prev/Next expansion, with regression coverage.
- Pydantic AI #8823 — closed by the upstream issue-assignment guard before merge; Macroscope approved the implementation, but issue #8778 was not assigned.
These are tracked in the profile-level OSS ledger; GH-OPS itself remains FROZEN / maintenance mode.
gh-ops/
├── src/
│ ├── core/ # Config, state, events, errors, rate limiting, monitor model
│ ├── github/ # API client, auth, collectors, models
│ │ ├── auth.py # Token resolution (env vars only)
│ │ ├── client.py # Single HTTP exit point (GET-only)
│ │ ├── models.py # Frozen dataclasses for API responses
│ │ └── collectors/ # Thin data-fetching wrappers
│ ├── intelligence/ # OSS hunter, radars, evidence/temporal/attention layer
│ ├── developer/ # Personal activity, statistics, reports
│ ├── monitors/ # Repository, CI, release, security, endpoint monitors
│ │ ├── __init__.py # Monitor registry and evaluator
│ │ ├── repository.py # Repository metadata monitoring
│ │ ├── ci.py # CI/workflow failure/recovery detection
│ │ ├── release.py # Release event monitoring
│ │ ├── security.py # Security alert change evaluation
│ │ └── endpoint.py # HTTP endpoint health checks (SSRF-protected)
│ ├── notifications/ # Outbound Telegram delivery (Phase 7)
│ ├── reporting/ # Ledger, priority, briefs, narratives
│ ├── jobs/ # Thin job orchestration for Actions (Phase 8)
│ │ ├── __main__.py # python -m src.jobs <job_name>
│ │ ├── pipeline.py # Shared plumbing: config, collection, state, diff
│ │ └── jobs.py # The six jobs + registration
│ └── utils/ # Logging, text, time helpers
├── .github/workflows/ # Six Actions workflows (thin: they call python -m src.jobs)
├── config/ # YAML configuration files (monitoring.yml, oss_hunter.yml, telegram.yml)
├── .env.example # Tracked template; real .env is gitignored
├── data/ # Runtime state (gitignored, carried by Actions cache)
├── tests/ # Test suite (1495 tests)
└── scripts/ # Local development scripts
- Python 3.10+
requests— GitHub API HTTP client (client.py only)pyyaml— Configuration file parsing
MIT