agentleFS
onelastcommitonelastcommit/noctra/.claude/skills/architecture/SKILL.md

architecture

Skill4 starsChanged 29 days agoMITView on GitHub

Use when modifying Noctra's own source code to understand non-obvious invariants, package boundaries, testability conventions, and the patterns an agent must respect to avoid breaking subtle runtime behaviour.

  • Reads credentials
  • Commits and pushes

136 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use intelligent-routing, bash-linux and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SKB3002Skill

    intelligent-routing

    Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.

    Also in 6 projects that use this

  • xenitV1Skill

    bash-linux

    Bash/Linux terminal patterns. Critical commands, piping, error handling, scripting. Use when working on macOS or Linux systems.

    Also in 8 projects that use this

  • xenitV1Skill

    red-team-tactics

    Red team tactics principles based on MITRE ATT&CK. Attack phases, detection evasion, reporting.

    Also in 8 projects that use this

  • SKB3002Skill

    nextjs-react-expert

    React and Next.js performance optimization from Vercel Engineering. Use when building React components, optimizing performance, eliminating waterfalls, reducing bundle size, reviewing code for performance issues, or implementing server/client-side optimizations.

    Also in 3 projects that use this

  • +8 more from xenitV1

The file

We split this skill into the rules it gives an agent

9 rules. 0 are common, 4 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 117 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use this playbook before changing Noctra's core code.

    line 8
  2. Distinctive:

    Rule: never scan the full log file to detect failures.

    line 48
  3. Very specific:

    Keep CreateWorktree and ResumeWorktree on the same prefix or the watcher silently never finds its own PRs.

    line 57
  4. Very specific:

    Never create a noctra/-prefixed branch for manual work or agent-assisted PRs in this repo.

    line 58
  5. Very specific:

    Use fix/, feat/, docs/, chore/, or eng-<n>-<slug> prefixes for non-Noctra branches.

    line 59
  6. Distinctive:

    Rule: don't change cursor types or comparison logic without understanding that timestamps work for comments (monotonically increasing) but …

    line 72
  7. Distinctive:

    Rule: when adding new logic, prefer extracting the decision/formatting into a pure function with table-driven tests, then call it from the …

    line 86
  8. Distinctive:

    Rule: if adding linter config, use v2 syntax.

    line 94
  9. Very specific:

    If golangci-lint is available:

    line 170

“Common” is measured: kept by at least 6 in 10 of the 117 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Architecture & Contributing

Use this playbook before changing Noctra's core code. The invariants below are easy to violate in a plausible-looking patch; each has caused a real bug or regression.

Package map

PackagePurpose
cmd/noctraEntry point + subcommand dispatch (run / setup / dashboard / config / repos / sweep / github / git-credential / cleanup / doctor [--json] / update / install-service [--start/--force] / uninstall [--purge] [--force] / logs / tail / start / stop / restart / status / completion / version). start…status are thin systemctl --user <verb> noctra.service wrappers; completion bash|zsh is the pure completionScript; startup banner; --help
internal/config.env parser, validated Config, DefaultConfigDir (~/.noctra/)
internal/configcmdnoctra config path|edit|get|set — atomic, comment-preserving .env edits
internal/sourceTicket sources behind one interface: Linear, GitHub Issues (TICKET_SOURCES), Jira
internal/linearLinear GraphQL client: ResolveStateIDs, FetchTriggerIssues, FetchLabeledIssues (both fetch issue comments → Issue.ClarificationComments, which filters Noctra's own notices by the "**Noctra" body prefix, and the project description/content → Project.RepoDirective), ResolveLabelID, RemoveLabel, SetState, Comment; Telegram read queries (ProjectIssueCounts, ListProjectIssues, SearchIssues, GetIssueByIdentifier); ListProjects (+ id/slugId/url so MatchProjects resolves a pasted link) and UpdateProjectContent/UpsertRepoDirective. Auth: personal API key (New, sent verbatim), static app-actor OAuth token (NewOAuth, Bearer, LINEAR_OAUTH_TOKEN), or — preferred — the self-renewing actor=app TokenManager (oauth.go: client_credentials&actor=app from LINEAR_OAUTH_CLIENT_ID/SECRET, or refresh_token when LINEAR_OAUTH_REFRESH_TOKEN is set, rotations persisted via state.Store). OAuth paths set Client.FallbackAPIKey, so an expired app token degrades to the personal key (with OnDegrade) instead of crash-looping; a partial actor=app config warns and falls back
internal/linearclientBuilds the authenticated linear.Client from config; one place for credential precedence
internal/repoResolveDirect (Repo: directive or a PR's own repo, origin/HEAD default-branch detection) + Resolve (REPO_PATH fallback); AllRepoPaths/AllRepoRemotes (scan ReposBase); clone-on-demand (mkdir(2) lock); CreateWorktree / ResumeWorktree / CreateOrResumeWorktree (picks on RemoteBranchExists); lockRepo; BranchName
internal/repoaddShared "add a repository" core: clone via ResolveDirect first, then write the project's Repo: directive, so a directive never points at a repo the host can't reach. A Result with a Path plus an error means only the directive failed
internal/reposcmdnoctra repos add / list — CLI channel over repoadd
internal/agentBackends behind Backend (agent-backends); shared prompt builders, BuildFixPrompt, BlockedLine, log_offset, ExtractSummary, ExtractFindingReplies, pricing
internal/pluginsCurated, commit-pinned agent skills (agent-backends): Catalog/Resolve (packs from the embedded catalog.json + AGENT_PLUGINS_EXTRA), Install (fetch at the pinned SHA, verify, build a trimmed plugin dir), Stage (copy skills into a worktree for non-Claude backends), ExcludeStaged
internal/reviewOptional Gemini review gate. API mode requests JSON (verdict + summary + line-anchored findings); process.go posts findings as inline PR comments (github.PostInlineComments, each with NoctraReplyMarker) and leaves a concise verdict in the PR body. CLI mode / unparseable JSON fall back to the prose verdict
internal/budgetDaily token/USD caps (MAX_DAILY_TOKENS / MAX_DAILY_USD), reset at UTC midnight
internal/notifyFire-and-forget Notifier (Send/SendSync): Telegram, Slack, Discord; Multi fans out (buildNotifier in pipeline). Slack/Discord are on when their webhook URL is non-empty; Telegram keeps TELEGRAM_ENABLED. Messages use single-* mrkdwn; Discord rewrites to **x** and sends allowed_mentions:{parse:[]}
internal/telegramInbound listener: long-poll getUpdates, sender auth, dispatcher. Register for one-shot commands; RegisterConversation for guided flows — while live, plain messages route to it; it ends on completion, /cancel, a 5-minute sessionTTL, or any other command (which interrupts it and still runs)
internal/githubgh wrapper: ListNoctraPRs, GetPR (comments + reviews + inline comments via REST + statusCheckRollup), CheckLogs, thread replies/resolution; Command/CommandInDir (github-identity)
internal/ghauth / internal/ghauthcmdGitHub App client side and noctra github login|logout|status, git-credential, Activate
internal/stateSQLite store (STATE_DB, default ~/.noctra/state.db; modernc.org/sqlite, single conn): PR cursors + CI SHA + iteration count (pr_states), sweep cooldowns (sweep_states), plan/run-history/usage, the rotating Linear OAuth token. OpenMigrating imports legacy JSON (STATE_FILE) only when the DB is new; the JSON is never deleted
internal/watchSide-effect-free PR classifier (auto-iterate)
internal/lessonsLearns repo conventions from human commits on merged Noctra PRs (ProcessMergedPRs)
internal/pipelinePoll loop + worker pool + per-ticket lifecycle (process.go); PR-watch (iterate.go); sweeps (sweep.go); Telegram handlers /status /tickets /ticket /search-tickets (/find) /start /move /pause /resume /kill /requeue /sweep (commands.go) and guided /addrepo (addrepo.go)
internal/sweepSweep task catalog + scheduler (sweeps)
internal/sweepcmdnoctra sweep [--task] [--repo] [--force] — thin client over POST /api/sweep
internal/dashboardOperations dashboard server + SSE hub; UI in web/ (dashboard)
internal/setupInteractive wizard (noctra setup); writes .env only, merging into an existing one
internal/cleanupBranches, worktrees, old logs
internal/serviceinstall-service: renders the systemd --user unit (pure unitFile(exePath, pathEnv)), daemon-reload; --start enables/starts + loginctl enable-linger; refuses without --force if the unit exists. Pairs with scripts/install.sh
internal/doctorPreflight checks; gather is side-effect-free, Run renders the report, RunJSON emits {name, ok, detail, hint, note} + non-zero on failure
internal/selfupdateLatest/IsNewer/assetName (pure) + Update (download the GoReleaser archive via gh, verify SHA-256 against checksums.txt, atomic swap). noctra run fires a best-effort checkForUpdate at startup

Invariant 1: the log_offset pattern

Agent logs append across attempts. agent.OffsetBefore records the file size before the agent runs; agent.ReadAfter reads only the new tail. BlockedLine and HasRateLimit operate on that tail.

Rule: never scan the full log file to detect failures. That re-detects failures from previous attempts and causes false positives (e.g. a ticket that was rate-limited on attempt 1 would be falsely detected as rate-limited on attempt 2 even when the agent succeeded).

Where it lives: internal/agent/log.go (OffsetBefore, ReadAfter), consumed in internal/pipeline/process.go and internal/pipeline/iterate.go.

Invariant 2: the noctra/ branch-prefix guardrail

The auto-iterate watcher identifies its own PRs by the noctra/<id> branch prefix. repo.BranchName generates it; github.ListNoctraPRs filters on it; watch and iterate depend on it.

Rules:

  • Keep CreateWorktree and ResumeWorktree on the same prefix or the watcher silently never finds its own PRs.
  • Never create a noctra/-prefixed branch for manual work or agent-assisted PRs in this repo. The watcher will claim it and push conflicting follow-up commits.
  • Use fix/, feat/, docs/, chore/, or eng-<n>-<slug> prefixes for non-Noctra branches.

Invariant 3: backend-agnostic internal/agent split

Shared agent logic goes in the common code (backend.go, prompt.go); a backend file holds only its invocation args and rate-limit regex. Details and per-backend quirks: agent-backends.

Invariant 4: cursor semantics

The PR-watch system tracks what feedback has been acted on via cursors in internal/state:

  • Comment/review cursors are timestamps (naturally ordered). Conversation comments and inline review-thread comments share the comment cursor.
  • CI cursor is keyed by head commit SHA (LastCISHA) — acted on once per commit. A fix push changes the SHA, making a fresh failure on the new commit eligible again.

Rule: don't change cursor types or comparison logic without understanding that timestamps work for comments (monotonically increasing) but not for CI (where the same SHA can have multiple check runs; what matters is "have we already acted on this SHA?").

Invariant 5: pure functions for testability

Noctra follows a convention of extracting logic into pure, side-effect-free functions that are unit-tested independently from the I/O that calls them:

Pure functionPackageWhat it does
claudeArgs / codexArgs / copilotArgsinternal/agentBuild CLI argument slices
unitFile(exePath, pathEnv)internal/serviceRender the systemd unit file
completionScript(shell)cmd/noctraGenerate shell-completion scripts
watch.diff / watch.actionableinternal/watchClassify PR changes, apply trusted-reviewer rules
selfupdate.IsNewer / assetNameinternal/selfupdateVersion comparison, archive name selection

Rule: when adding new logic, prefer extracting the decision/formatting into a pure function with table-driven tests, then call it from the I/O layer. This keeps tests fast and deterministic.

Invariant 6: golangci-lint v2 syntax

The repo uses .golangci.yml with version: "2" syntax. This means:

  • Linters are configured under linters.default and linters.settings, not the v1 linters.enable list.
  • Formatters are under formatters.enable (e.g. gofmt), not mixed with linters.

Rule: if adding linter config, use v2 syntax. v1 keys will cause a parse error.

Invariant 7: the codebase carries no comments

Source files hold zero comments — the only exception is a compiler/tooling directive (//go:embed, //go:build, //nolint, // Code generated). Do not reintroduce explanatory comments, doc comments on exported symbols included. Names and structure carry the what; this file and CLAUDE.md carry the why.

The non-obvious facts that previously lived in comments, kept here so removing them lost nothing:

WhereInvariant
repo/worktree.goWorktree mutations serialize per clone via lockRepo — concurrent git worktree add on one clone collides on the .git/config lock. Remove the worktree before the branch: git won't delete a branch still checked out, and a leftover branch fails worktree add -b.
pipeline/iterate.goiteratePR resumes on the PR's own HeadRefName via ResumeWorktreeWithBranch, never ResumeWorktree. A sweep's identifier (SWEEP-<REPO-SLUG>-<TASK>) doesn't round-trip through BranchName to its branch (noctra/sweep-<task>), so every sweep PR acked feedback with 👀 and then failed to fetch a non-existent branch (PR #305).
repo/worktree.goTicket dispatch uses CreateOrResumeWorktree, never CreateWorktree directly: a run that pushed its branch but died before gh pr create leaves an orphaned remote branch, and re-branching from main produces a non-descendant origin rejects as non-fast-forward.
repo/worktree.goclearWorktreePath falls back to os.RemoveAll + git worktree prune when worktree remove fails. A directory git no longer tracks as a worktree (e.g. a half-deleted node_modules left by an interrupted cleanup) makes worktree remove a no-op and worktree add fail with "already exists" — forever, since nothing else deletes it.
pipeline/sweep_branch.goSweep branch names are fixed per repo+task, so they are reused across runs. A sweep skips while its previous PR is still open (a fresh run would collide with it), and otherwise pushes with --force-with-lease pinned to the SHA ls-remote just reported: the only remote branch left at that point belongs to a merged or closed PR, and a fresh branch from main is never its descendant.
pipeline/iterate.goEvery failure path must record the iteration before returning, or the cursor never advances and the same feedback loops forever. Two deliberate exceptions: infra failures (timeout / rate-limit) don't increment — they weren't real attempts — and a shutdown cancellation isn't recorded at all, since it would bump the count and could fire the cap warning.
lessons/lessons.goLessons come only from the PR branch's own (--first-parent, --no-merges) commits after LastPushedSHA that are neither [bot]-authored nor Noctra's. Noctra commits under the host's git identity, so it is recognised by the commit-body lines Implemented by Noctra / Follow-up commit by Noctra / Autonomous maintenance by Noctra (noctraCommitRe) — reword those in pipeline and Noctra starts learning from itself. A plain git diff also swept in everything an "Update branch" merge pulled from main, which is how feature descriptions ended up stored as conventions.
pipeline/iterate.goPush whenever the branch is ahead, not just when the worktree is dirty: the agent sometimes self-commits, and gating on dirtiness alone silently drops those commits (ENG-182).
pipeline/plan.gohasPendingPlan takes p.mu itself — callers must not already hold it.
pipeline/sweep.goA manual sweep deliberately skips MarkSwept so an ad-hoc run never shifts the scheduled cadence.
pipeline/sweep.goBoth abort paths (timeout, token cap) do record the cooldown. A task that aborts will abort again identically, so skipping it would re-burn the full ceiling every cycle; /sweep --force is the escape hatch. Their run_history status is aborted, not failed — the run was healthy, we killed it.
pipeline/sweep.goOutcome notifications send on context.WithoutCancel(ctx): markDone cancels the task context the instant processSweepTask returns, and notifier.Send is fire-and-forget, so a plain ctx races with its own cancellation.
agent/claude.gorunStream learns the real cost only from the terminal result event, which never arrives when the cap fires cancel(). It accumulates the per-turn usage breakdown plus the model name while streaming and estimates from PricesForModel whenever result is missing — otherwise aborted runs record $0.00 and the daily cost cap never sees them.
sweep/task_*.goA task that hits the token ceiling is non-converging, not under-provisioned — every aborted run consumed exactly its ceiling at 2M, 5M and 8M, while every productive one finished under 1M. Any task that verifies in a loop must cap both its work and its full-suite runs in the prompt, and be told to ship what is already green.
notify/discord.goallowed_mentions.parse must be a non-nil empty slice so it marshals to [] and not null; null re-enables @everyone/@here parsing on untrusted ticket text.
notify/telegram.goEscapeMarkdown covers Telegram's strict legacy-Markdown chars. Apply it to dynamic values only, leaving the template's own *bold* alone — an unescaped snake_case title returns 400 and the notification vanishes (PR #52).
linear/types.goThe self-comment filter still matches the pre-rename **Nightshift prefix (ENG-204 tickets carry it). Classify by the first non-empty line only: a leading > quote is a human quoting our notice, never a system comment.
github/types.goRepoURL is the discovering clone's remote URL, not gh JSON, and preserves its scheme — synthesizing HTTPS from owner/name breaks auto-iterate on SSH-only private repos.
github/unavailable.goWarnRepoUnavailable warns once per repository, with a moved-repository hint, when the token service answers not_installed (ghauthcmd.appTokenSource maps that code to ErrAppNotInstalled), and logs repeats at debug level. A transferred private repository can't be followed automatically, since the old name gets no token and GitHub hides the redirect from callers without access, so the fix is manual (troubleshooting). Any other mint failure still warns every time, because it may be transient.
authcheck/repos.goThe daily Repositories check asks GitHub for each clone's canonical name through github.Command, so one call catches a move (the name differs, because gh follows the redirect), a deletion (ErrRepoNotFound) and a missing installation (ErrAppNotInstalled). In app mode a moved private repository shows up as "not installed", not "moved", since the old name gets no token. A probe cut short by the 30-second check timeout is Skipped, not failed, so a slow network never raises a false alert. GitHubApp skips repositories without the app for the same reason: it tests the link, and a moved first clone used to make it tell you to re-run noctra github login.
github/client.goTruncation skips UTF-8 continuation bytes (top bits 10) so a log slice never cuts mid-rune.
github/client.goEvery Repo: directive ref goes through NormalizeRepoRef before parsing: Linear rewrites a bare URL in project content into [url](<url>) on save, and the raw markdown reaches ExtractOwnerRepo as the ref. Unwrapped, a GitHub link errors as "no repo mapping" and a non-GitHub link silently resolves to github.com/<owner>/<name>.
telegram/listener.goThe HTTP client timeout must exceed pollTimeout, or every long-poll getUpdates aborts client-side.
state/state.goUpdate's callback runs under the store lock — never call back into the Store from it. OpenMigrating imports the legacy JSON only when the DB is newly created; an existing DB is never clobbered.
agent/log.gousageFooterRe is anchored to end-of-string so it never eats a mid-summary mention of token usage.
agent/copilot.goBridge only tokens Copilot accepts (gho_ / github_pat_). It rejects classic ghp_ PATs and reads the env token before its own store, so injecting one breaks an otherwise-valid copilot /login.
agent/antigravity.goagy's --print is a string flag whose value is the prompt: the auto-approve flag must precede it and the prompt must be the final token, or --print swallows the next flag.
dashboard/dashboard.go/fonts/ stays unauthenticated: @font-face url() subrequests don't carry the page's ?token=, so gating them silently breaks the brand fonts.
selfupdateDev/snapshot builds never advertise an update — they can't be compared to a release tag.
config/config.goMigrateLegacyPaths renames ~/.nightshift* → ~/.noctra* only when the old exists and the new doesn't; best-effort, never clobbers.
cmd/noctrauninstall --help must never trigger the destructive action, and an unrecognized flag errors rather than falling through.
ghauthcmd/credential.gogit-credential never fails git outright: when it cannot help (not linked, not installed, non-GitHub host) it prints the reason to stderr and returns no credentials, so public repositories still clone anonymously and private ones fail with git's own auth error. It reads --dir instead of resolving the config dir, because git runs helpers with the worktree as cwd and the cwd-checkout override would pick the wrong directory.
ghauth/session.goNoctra's own gh calls reuse a cached write token until 10 minutes before expiry; the git helper and agent runs always mint fresh (FreshToken), so a long run never inherits a token that is about to lapse. An agent's GH_TOKEN is fixed at spawn and lives an hour, which is why Activate warns when AGENT_TIMEOUT_MINUTES exceeds 50.
ghauth/sign.goCanonicalString is a wire format shared with noctra-auth (src/signing.ts); TestCanonicalString_MatchesTokenServiceFormat pins a vector checked against the TypeScript implementation. Change both sides together or every signed request is refused.
namingBranch names, identifiers, commit and PR wording, the model label and the watcher's hidden markers are all read back by other code. Their rules live in the naming skill rather than here.
agent/auth.goAuthFailureLine only scans the last 40 non-empty log lines and is only consulted after a non-zero exit, so an agent that merely edited auth code mid-run is not misread as logged out. It deliberately does not match a bare 401 (that matches file.go:401).
agent/backend.goRunOptions.Env is merged over the backend's own env (or os.Environ()) by key, so the agent's GH_TOKEN and GIT_CONFIG_* replace the process-wide app-mode values rather than appearing twice.
doctor/agentauth.goThe agent auth check is informational and always ok. It reports unknown rather than guess: Codex's precedence between OPENAI_API_KEY and a stored login is undocumented, so only codex login status decides; GEMINI_API_KEY alone means nothing to agy (it also feeds the review gate), so API key needs modelProvider: gemini in ~/.gemini/antigravity-cli/settings.json as well. Copilot tokens are never an "API key": every route bills the Copilot plan.
plugins/install.goInstall builds a fresh plugin directory instead of reusing the upstream one: a generated plugin.json (named noctra-<plugin> so it never collides with a plugin the user installed) plus only the listed skills. Upstream hooks, commands and MCP servers never load, symlinks are skipped (they could point outside the skill), and the commit is checked after fetch so a moved ref can't slip in. A skill whose SKILL.md sits at the repo root (Path: ".") must set Name and Only, so it copies its own files rather than the whole repository; Only refuses anything that isn't a regular file. Install directories are immutable: the name is <plugin>@<first 12 chars of commit>-<variant>, where the variant hashes the selected skills (paths, names, Only, Exclude), because one pinned plugin can resolve to different skill sets (agent-skills sits in two packs). An existing directory is never rebuilt, so noctra setup choosing other packs can't change skills under a running daemon; a new build is renamed into place atomically, and a lost rename race reuses the winner. Old variants stay on disk (small) until removed by hand.
plugins/install.go (git)Every plugin git call sets cmd.WaitDelay and http.lowSpeedLimit/lowSpeedTime. Cancelling the context kills git but not its git-remote-http child, which keeps the output pipe open, so without WaitDelay a stalled fetch hangs CombinedOutput forever, deadline or not (TestInstall_GivesUpWhenTheFetchStalls). Startup and setup both bound plugin work with plugins.SetupTimeout, because Pipeline.Run installs plugins before the first poll.
plugins/requirements.goA skill that drives a runtime (webapp-testing needs Python Playwright and its Chromium) declares Requires. CheckRequirements runs each check command once per process and drops unmet skills before Install, so the built plugin never offers a skill the host can't run; otherwise the agent tries to install the runtime mid-run, a slow download on a Pi. A missing dependency is a warning in setup, the banner and doctor, never a failure, and once the dependency appears the next start builds the larger skill set as a new variant directory.
plugins/stage.goCodex, Copilot and Antigravity have no per-run plugin flag, but all three read project skills from .agents/skills (verified for Codex/Antigravity by probing the CLIs; Copilot per GitHub's docs). Stage copies skills there as noctra-<plugin>-<skill> for the run and the backend's Run removes them on return, before Noctra commits. Copies, not symlinks: an agent editing a staged file must not corrupt the shared install. Stage never removes a directory it didn't create: each staged copy carries a .noctra-staged marker, so a crashed run's leftover is replaced but a project's own skill at the same path is skipped. info/exclude hides untracked files, not deletions of tracked ones, so deleting a project's skill would land in the PR. info/exclude is also the lowest-precedence ignore source: a repo .gitignore negation such as !.agents/skills/** re-includes staged skills, so after staging each one Stage asks git ls-files --others --exclude-standard whether git add -A would pick it up, and removes it if so (or if the check fails).
repo/worktree.goEvery worktree creator calls plugins.ExcludeStaged inside lockRepo, adding /.agents/skills/noctra-*/ to the clone's shared info/exclude. Removal after the run isn't enough on its own: agents sometimes commit mid-run, and git add -A would sweep the staged skills into the PR.

Invariant 8: where types live

A package's data model lives in its types.go: plain structs, interfaces, func types and enums (the named type plus its const block), together with trivial methods such as Error, String or Unwrap. A large set of wire types for one external API gets its own <source>_types.go (source/jira_types.go).

Three kinds stay beside their logic instead:

  • a type that owns behaviour: a constructor plus real methods (Pipeline, Store, Server, claudeBackend, review.Result, agent.ModelPrices);
  • an unexported enum with a single consumer (agentRunStatus next to classifyAgentRun);
  • a small decode struct used by one function.

Go has no barrel or index files. Every file in a directory is already one package, so a re-exporting file adds nothing.

Quality gates

Before submitting changes:

go test ./...       # all tests must pass
go vet ./...        # must be clean

If golangci-lint is available:

golangci-lint run

These are the same checks CI runs. A PR that fails either will not be merged.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.