{"id":50687736,"url":"https://github.com/esaldgut/lessongate","last_synced_at":"2026-06-09T00:04:19.760Z","repository":{"id":362814280,"uuid":"1260904821","full_name":"esaldgut/lessongate","owner":"esaldgut","description":"Local Go runtime agent that watches a private repo's merged PRs, detects generalizable engineering lessons with Claude, sanitizes them through an NDA gate, and opens draft PRs to a public skills library. The agent that keeps ai-native-engineering-workspace dynamic.","archived":false,"fork":false,"pushed_at":"2026-06-06T03:15:06.000Z","size":73,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T04:15:14.597Z","etag":null,"topics":["agentic-ai","ai-agents","anthropic","claude","devsecops","github-automation","golang","llm","nda-safe","test-driven-development"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esaldgut.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T02:26:16.000Z","updated_at":"2026-06-06T03:15:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/esaldgut/lessongate","commit_stats":null,"previous_names":["esaldgut/lessongate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/esaldgut/lessongate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esaldgut%2Flessongate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esaldgut%2Flessongate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esaldgut%2Flessongate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esaldgut%2Flessongate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esaldgut","download_url":"https://codeload.github.com/esaldgut/lessongate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esaldgut%2Flessongate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34085335,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agentic-ai","ai-agents","anthropic","claude","devsecops","github-automation","golang","llm","nda-safe","test-driven-development"],"created_at":"2026-06-09T00:04:17.377Z","updated_at":"2026-06-09T00:04:19.755Z","avatar_url":"https://github.com/esaldgut.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lessongate\n\nA local Go runtime agent that keeps a public engineering-knowledge repository\n**dynamic**: it watches a private project's merged pull requests, asks Claude\nwhich of the lessons captured from them are *generalizable* (reusable patterns,\nnot business-specific), sanitizes them through an NDA gate, and opens **draft**\npull requests to a public skills repo for human review.\n\nIt automates an upstream that would otherwise be manual, and it is built to a\nproduction bar: every stage is behind an interface, the security-critical gate is\ndeterministic and tested against a golden corpus, and the whole pipeline runs\ndeterministically offline in CI.\n\n```\n            private repo (merged PRs + a curated lesson registry)\n                                    │\n   ┌────────────────────────────────┼────────────────────────────────────────┐\n   │ lessongate (single-process Go binary, single-run flock)                 │\n   │                                                                         │\n   │  WATCH ─▶ PREFILTER ─▶ REDACT ─▶ EXTRACT ─▶ GATE ─▶ RECONCILE ─▶ EMIT   │\n   │  go-github  (no API)   (det.,    (Claude:   (det.   (novel/     (draft  │\n   │  mergedAt              before    is it      regex + overlaps/   PR,     │\n   │  cursor               any API)   general-   corpus  duplicate)  idempo- │\n   │                                  izable?)   + canary           tent)    │\n   │                                  + Claude                               │\n   │                                  verify                                 │\n   │  ledger (JSONL, atomic, fail-closed) · slog (redacted) · core dumps off │\n   └──────────────────────────────────────────────────────────────────────┬──┘\n                                                                          │\n                                            DRAFT PR (fingerprinted) ─────┘\n                                                      │\n                            public repo (branch-protected) ─▶ human review\n```\n\n## Why it's interesting\n\n- **The trust boundary is on the right side of the wire.** An early design sent\n  the raw PR diff to the Claude API *before* sanitizing. That ships every private\n  identifier to a third party on every PR. The fix: consume the *human-curated\n  lesson text* (already distilled to a pattern, low NDA density) instead of the\n  raw diff, and run a deterministic redaction pass **before** anything leaves the\n  process. The diff never leaves the machine.\n- **The confidentiality control is a deterministic gate, not an LLM.** A blacklist\n  of literals is guaranteed-incomplete. The primary control is structural regex\n  (12-digit account IDs, ARNs, reverse-DNS bundle IDs, secret-shaped tokens) plus\n  a template allow-list, tested against a golden corpus of leaky/clean fixtures\n  with a seeded canary that fails the build if it ever leaks. The Claude verify\n  pass is *extra recall* — it can only downgrade a verdict to unsafe, never the\n  reverse.\n- **Determinism where it counts.** The non-deterministic Claude stages run against\n  recorded cassettes in CI, so the security-critical path is regression-tested\n  offline. The deterministic gate, the merge-cursor logic, the ledger's\n  crash-resume, and the idempotency check are all covered by fast unit tests.\n- **It reuses, rather than reinvents.** Skill validation shells out to the official\n  Claude Code `skill-creator` plugin's `quick_validate.py`, discovered by glob (no\n  hardcoded version hash) with a fail-closed startup assert.\n\n## Design decisions, verified not assumed\n\nThe dependency stack was checked against live sources, which overturned several\nassumptions:\n\n| Decision | Why |\n|---|---|\n| `anthropic-sdk-go` v1.46.0 | Current. Opus 4.8 is **adaptive-thinking-only** — `temperature`/`top_p`/`budget_tokens` all 400, so determinism comes from forced structured output (tool-choice), not a temperature knob. |\n| `go-github` v88 | Current. `NewClient` returns `(*Client, error)` and auth is a `ClientOptionsFunc` (`WithAuthToken`) — a breaking change from older majors. Draft PRs via `NewPullRequest{Draft: true}`. |\n| **No** MCP go-sdk | Real, but speculative here — the agent calls the API and GitHub directly; there is no MCP server in the data flow. |\n| **No** OpenTelemetry | Over-engineering for a single-process local binary. `slog` + counters instead. |\n| Go 1.26 idioms | `errors.AsType[T]`, `wg.Go`, `strings.SplitSeq`, `omitzero` — used where they fit. |\n\nThe design was put through an adversarial audit before implementation. The two\nfindings tagged CRITICAL — *raw diffs crossing the wire before the gate* and *a\nblacklist as the primary confidentiality control* — changed the architecture\nabove, not a footnote.\n\n## Threat model (summary)\n\n| Boundary | What crosses | Control |\n|---|---|---|\n| process → Claude API | redacted lesson text (not raw diff) | deterministic redaction before send; ZDR key required |\n| process → public repo | SKILL.md + PR body | full gate (det + structural + allow-list + Claude verify + canary) before; draft-only; human review; server-side secret scan |\n| process → disk | quarantine (if any) | outside synced paths, `chmod 700`, TTL-shred, no core dumps |\n| process → logs | telemetry | `slog` redactor — IDs/hashes/counts only, never payloads |\n| GitHub token | credential | fine-grained PAT, 2 repos, Keychain, not inherited by subprocesses |\n\nSee [`docs/RUNBOOK.md`](docs/RUNBOOK.md) for the pre-flight checklist and the\nleak-response procedure.\n\n## Layout\n\n```\ncmd/lessongate/        CLI: run / backfill / dry-run / status\ninternal/\n  watch/               go-github merged-PR cursor (mergedAt, not PR number)\n  prefilter/           drop docs-only / dep-bump / own capture commits, no API\n  lessons/             parse the curated lesson registry (1:N, fenced-code safe)\n  redact/              deterministic pre-flight: deny-list + structural + allow-list\n  extract/             Claude: is this lesson generalizable?\n  gate/                deterministic gate + golden corpus + canary + Claude verify\n  reconcile/           novel / overlaps-existing / duplicate\n  skillcreator/        shell-out to the skill-creator plugin (glob discovery, fail-closed)\n  emit/                idempotent draft PR via go-github (fingerprint in body)\n  claude/              anthropic-sdk-go wrapper + cassette client for offline CI\n  ledger/              JSONL state, atomic writes, flock, fail-closed\n  obs/                 redacting slog handler + counters\n  pipeline/            the orchestrator wiring the six stages\n```\n\n## Running it\n\n```bash\ngo build ./... \u0026\u0026 go test ./...          # 68 tests, all offline\n\nexport ANTHROPIC_API_KEY=...             # must be a ZDR-enabled key\nexport LESSONGATE_GITHUB_TOKEN=...        # fine-grained PAT, 2 repos\n\n./bin/lessongate dry-run --repo owner/name   # list candidates, open NO PRs\n./bin/lessongate run --repo owner/name --max 1   # open one draft PR\n./bin/lessongate run --repo owner/name --max 1   # re-run: idempotent, no duplicate\n```\n\nWithout credentials the agent **fails closed** with a clear message — it never\ndegrades to a silent no-op that would skip the gate.\n\n## Status\n\nv0.1 — the full pipeline is wired and tested offline (73 tests across 13\npackages, deterministic CI). The agent is complete; what gates the first\nproduction run is a **deliberate precondition, not missing work**.\n\nProcessing real lessons from a private project means sending redacted lesson text\nto a third-party API. The threat model requires **Zero-Data-Retention** on the\nAnthropic organization before that happens. ZDR is not a free-tier toggle — it is\ngranted under a commercial agreement (see [`docs/RUNBOOK.md`](docs/RUNBOOK.md)).\nSo lessongate **waits for ZDR** rather than processing private data without it.\nThe agent enforces its own threat model; it doesn't get bypassed because the\nauthor owns the data.\n\nUntil then the **synthetic smoke** path exercises the entire pipeline — real\nClaude API calls, a real draft PR — on lesson text that carries no private\nproject data:\n\n```bash\n./bin/lessongate dry-run --once --lessons-file testdata/smoke/synthetic_lessons.md\n```\n\nNon-goals for v0.1: MCP, OpenTelemetry, auto-merge, and watching anything but the\nsingle private vertical slice.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesaldgut%2Flessongate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesaldgut%2Flessongate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesaldgut%2Flessongate/lists"}