coach

module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: BSD-2-Clause

README

coach

coach is an experimental AI coach designed to help software engineers and autonomous agents build better software. It parses source code syntactically and flags code smells, design issues, and structural metrics.

Packages

  • pkg/semantics — Deterministic structural analysis of Go, TypeScript, and TSX source bytes (validates syntax, extracts imports, computes branching metrics, and detects constructor-like patterns).
  • pkg/githubingest — Optional GitHub App-authenticated single-file reader via the GitHub Contents API.

Run locally (Docker)

Local Coach quickstart: Docker Compose API + worker, smoke test without GitHub, optional local Qwen (qwen3.5:4b / qwen3.5:4b-mlx), optional scan of a GitHub.com repo without cloning it. Local knobs: .env.example → .env.

For deterministic signals on a local checkout with no Docker, see coach codesignal.

Working in a Claude Code cloud session? See Claude Code cloud development for how the toolchain is bootstrapped and what to do when it fails.


Installation

Go Packages

To install the Go packages:

go get github.com/lousy-agents/coach/pkg/semantics

# Optional: for GitHub App content ingestion
go get github.com/lousy-agents/coach/pkg/githubingest
JavaScript / TypeScript Bindings (@lousy-agents/coach-semantics)

The JS/TS bindings are currently packaged for Node.js (ESM-only).

[!NOTE] Because coach is in an active experimental phase, the npm package is not yet published to the public npm registry. To consume it, you must clone the repository and build the library locally:

[!IMPORTANT] Build Prerequisites: Because the package compiles its underlying parser engine locally during installation, you must have:

  • Node.js (>= 20)
  • Go (>= 1.25.0)
  1. Clone and Build:

    git clone https://github.com/lousy-agents/coach.git
    cd coach/js/semantics
    npm install   # Compiles the underlying parser engine and packages TS code
    
  2. Link or Reference the Package: In your client application, add the local path as a dependency:

    cd ~/your-app
    npm install /path/to/coach/js/semantics
    

coach codesignal CLI Preview

cmd/coach provides a codesignal subcommand: a local, deterministic preview of pkg/codesignal you can run directly against a Git checkout, without any GitHub App, worker, or model/LLM configuration.

Download the latest coach binary for your platform from the GitHub Releases page. Each tagged release publishes archives for macOS (Apple silicon darwin_arm64 and Intel darwin_x86_64), Linux (linux_x86_64), and Windows (windows_x86_64), a checksums.txt file, and a cosign signature bundle.

ARCH=darwin_arm64  # or darwin_x86_64, linux_x86_64, windows_x86_64
EXT=tar.gz; [ "$ARCH" = windows_x86_64 ] && EXT=zip

curl -LO https://github.com/lousy-agents/coach/releases/latest/download/coach_${ARCH}.${EXT}
curl -LO https://github.com/lousy-agents/coach/releases/latest/download/checksums.txt
curl -LO https://github.com/lousy-agents/coach/releases/latest/download/checksums.txt.bundle

# Verify the checksums file was signed by this repository's release workflow
cosign verify-blob \
  --bundle checksums.txt.bundle \
  --certificate-identity-regexp '^https://github.com/lousy-agents/coach/.github/workflows/release.yml@refs/tags/v.*$' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  checksums.txt

# Verify the archive against the signed checksums
shasum -a 256 -c --ignore-missing checksums.txt

# Extract the binary
if [ "$EXT" = zip ]; then
  unzip coach_${ARCH}.${EXT}
else
  tar -xzf coach_${ARCH}.${EXT}
fi

Move the extracted coach binary somewhere on your PATH.

[!NOTE] From a local clone, you can still build/install it with:

go install ./cmd/coach

Prerequisites: a local Git checkout and the git executable in PATH. Building from source also requires Go 1.25+ (matching go.mod).

Usage

Run it from inside a Git worktree, pointing --base at the revision you want to diff against:

coach codesignal --base <ref>
  • --base is required; it can be any ref Git can resolve to a commit (a branch, tag, or SHA).
  • --format defaults to text; pass --format=json for machine-readable output.

Text example (coach codesignal --base <ref>), after a commit adds a function that mutates a caller-owned pointer:

scope: production, filtered: 0, files analyzed: 1, active signals: 1, diagnostics: 0
path: config.go
line: 10
lifecycle: introduced
source_scope: unknown
changed: true
evidence: cfg.Timeout
why it matters: Mutating a caller-owned input can create behavior that is not visible from the function signature, make outcomes dependent on call ordering, introduce temporal coupling, make tests and local reasoning more difficult, and surprise callers that expect an input to remain unchanged.
recommendation: Return a copy instead of mutating the caller's value, or document/rename this function to make the in-place mutation explicit.

The leading scope: production, filtered: 0, clause reflects --scope's default (production); pass --scope all to drop it. source_scope: unknown is expected here because no --build-target was given, so this run cannot tell whether config.go actually ships in a build — it is retained (not filtered), just labeled unknown.

JSON example (coach codesignal --base <ref> --format=json), for the same change — real output, captured from an actual coach binary built from this repository and run against a throwaway Git repository:

{
  "schema_version": "1",
  "scope": { "revision": "592147e385d...", "base": "ba6b55ffa1e...", "applied_scope": "production" },
  "summary": {
    "files_analyzed": 1,
    "files_with_diagnostics": 0,
    "active_signals": 1,
    "introduced_signals": 1,
    "existing_signals": 0,
    "resolved_signals": 0,
    "baseline_signals": 0
  },
  "signals": [
    {
      "id": "sig_8b52faea049d0bdc6eb96c8c2838803e05279ef40a0b94c797eb46f29f51f1d4",
      "fingerprint": "fp_dcf2afc2dfc5317dda2084b2de8eec306381b436ce887f8c5285904b0a9fdf50",
      "rule_id": "state.hidden_input_mutation",
      "rule_version": "1",
      "kind": "hidden_input_mutation",
      "category": "state_management",
      "severity": "medium",
      "confidence": "medium",
      "lifecycle": "introduced",
      "changed": true,
      "path": "config.go",
      "source_scope": "unknown",
      "subject": "ApplyDefaults:cfg",
      "location": { "start_byte": 113, "end_byte": 124, "start_row": 9, "start_col": 1, "end_row": 9, "end_col": 12 },
      "evidence": "cfg.Timeout",
      "why_it_matters": "Mutating a caller-owned input can create behavior that is not visible from the function signature, make outcomes dependent on call ordering, introduce temporal coupling, make tests and local reasoning more difficult, and surprise callers that expect an input to remain unchanged.",
      "recommendation": "Return a copy instead of mutating the caller's value, or document/rename this function to make the in-place mutation explicit.",
      "suggested_skill": "refactor-hidden-mutation",
      "provenance": { "producer": "semantics", "finding_kind": "mutates_input" },
      "machine_evidence": {},
      "related_locations": [],
      "path_steps": [],
      "coverage_refs": []
    }
  ],
  "diagnostics": [],
  "coverage": {
    "tracked_files_discovered": 0,
    "files_analyzed": 0,
    "files_unanalyzable": 0,
    "unsupported": [],
    "excluded": []
  }
}

Every one of schema_version, scope, summary, signals, diagnostics, and coverage is a guaranteed top-level key — see schema_version: "1" vs "2" below for the full present/optional key contract, including every signals[] entry's guaranteed key set (machine_evidence/related_locations/path_steps/coverage_refs above are empty-typed, not project-origin, but still always present). coverage's tracked_files_discovered/files_analyzed/files_unanalyzable are baseline-only accounting fields and are always 0 on a diff-flow (--base) report; only coverage.excluded is meaningful there.

Exit status
  • 0 — the CLI completed its analysis, regardless of whether any signals or diagnostics were reported. A quiet report with only diagnostics and zero signals is still a normal, exit-0 outcome.
  • 1 — an operational failure: the working directory is not a Git worktree, --base cannot be resolved, git is not found in PATH, or an internal analysis step fails. One actionable message goes to stderr; nothing is written to stdout.
  • 2 — a usage error: --base is missing, --format is not text or json, or (with --project-config) the config document is missing, unreadable, or fails schema validation (project_config_invalid). Usage guidance goes to stderr; nothing is written to stdout.
  • 3 — reserved for a valid --project-config naming a --project-language with no registered project-analysis backend at all (project_backend_unavailable; see Configured layer violations below). It's currently unreachable through the real CLI: --project-language accepts only go and typescript, both of which have registered backends, and any other value is rejected as a usage error (exit 2) before backend dispatch ever happens. This exit code stays defined for a future language whose backend is registered but unavailable. A missing or failed TypeScript sidecar is a distinct, exit-0 condition; see below.

A --project-config load/validation failure (exit 2, project_config_invalid) is treated exactly like any other usage error: it never writes a report to stdout, even in --format json. The stderr message identifies the --project-config path, the analyzed revision, and a specific reason — not found at that revision, not valid JSON, or a schema validation failure. If the path exists in your worktree but not at the analyzed revision (e.g. a --suggest-project-config --output candidate that hasn't been committed yet, see below), the message says so explicitly and names committing the file as the fix. This is distinct from project_backend_unavailable (exit 3), which keeps its own report-on-stdout behavior unchanged.

schema_version: "1" vs "2"

schema_version is chosen per invocation by whether --project-config is supplied, not by the coach binary's release version:

  • No --project-config → schema_version: "1".
  • A valid --project-config that reaches a registered backend → schema_version: "2".

Because a --project-config failure now writes nothing to stdout, there is no schema-1 or schema-2 document to distinguish on that path — the failure is detectable from the exit status (2) alone.

Top-level keys

At schema_version: "1", the following six top-level keys are guaranteed present in every report, never absent, never null — empty-typed ([]/{}/zero) rather than omitted when there is nothing to report:

Key Type Guarantee
schema_version string always "1" or "2"
scope object always present; individual fields (repository, revision, base, applied_scope, baseline) are each optional/omitted when not applicable (e.g. base is absent on a --baseline run, applied_scope is absent only if scope resolution itself never ran)
summary object always present, all-zero when there is nothing to summarize
signals array always present; [] when there are no signals
diagnostics array always present; [] when there are no diagnostics
coverage object always present; its unsupported/excluded arrays are always [] rather than omitted, even though the object as a whole is empty on a fresh diff-flow report

At schema_version: "2", all six keys above are still guaranteed, plus these four additional top-level keys, likewise guaranteed present and empty-typed (never absent, never null) rather than omitted when the project phase produced nothing:

Key Type Guarantee
project_changes array [] when the configured layer policy produced no findings
project_facts array [] when no facts-only backend (e.g. reachability) is wired for the run
project_summary object all-zero when there are no project changes
project_coverage object present even if the backend never ran a real analysis (e.g. project_backend_unavailable)

At schema_version: "1", none of these four keys are present at all — not null, entirely absent from the JSON document.

scope.applied_scope records the resolved --scope value ("production" by default, or "all") on both --base <ref> and --baseline runs. For example, coach codesignal --baseline --scope all --format=json against a repository with one Go file:

{
  "schema_version": "1",
  "scope": { "revision": "3ff11bbc945...", "applied_scope": "all", "baseline": true },
  "summary": { "files_analyzed": 1, "files_with_diagnostics": 0, "active_signals": 0, "introduced_signals": 0, "existing_signals": 0, "resolved_signals": 0, "baseline_signals": 0 },
  "signals": [],
  "diagnostics": [],
  "coverage": {
    "tracked_files_discovered": 2,
    "files_analyzed": 1,
    "files_unanalyzable": 0,
    "unsupported": [{ "reason": "unsupported_language", "language": ".mod", "count": 1 }],
    "excluded": []
  }
}
signals[] entry keys

Every entry in signals[] — at schema_version "1" or "2" alike, and regardless of whether the signal originated from file-local analysis or a --project-config backend — carries the same key set below, with one exception: source_scope/subject/evidence are omitted entirely (not emitted as "") when empty, because those three fields alone are tagged omitempty in pkg/codesignal/report.go. Every other key listed is present on every signal, never omitted, with its JSON empty value used rather than leaving the key out:

Key Guarantee when not applicable
id, fingerprint, rule_id, rule_version, kind, category, severity, confidence, lifecycle, changed, path, location, why_it_matters, recommendation, provenance always populated
suggested_skill always present; "" when the rule suggests no skill
source_scope, subject, evidence omitted entirely when empty (omitempty) — a consumer must treat them as absent-or-present, not assume the key exists
machine_evidence always present; {} for a file-local signal (only project-origin signals populate it)
related_locations, path_steps, coverage_refs always present; [] for a file-local signal, or when the project-origin finding has none

machine_evidence/related_locations/path_steps/coverage_refs mirror the same-named project_changes[] fields for project-origin signals; see Configured layer violations below.

Scope and limitations
  • Advisory only. It surfaces deterministic structural signals; it does not judge correctness or block anything on its own.
  • Go, TypeScript, and TSX only. Changed files in other languages are skipped (with an unsupported_language diagnostic).
  • Does not execute code. All analysis is static, over source bytes read via git show.
  • No runtime proof, cross-file analysis only by explicit opt-in. By default it cannot prove a defect exists or trace causality across files; each file is analyzed independently. An explicit --project-config (see Configured layer violations below) opts into one narrow, advisory exception: checking configured Go or TypeScript import edges against configured architectural layers.
  • Local-only, zero external configuration. It never contacts GitHub, a model/LLM API, or any other network service — see internal/codesignalcli/dependencies_test.go's TestNoExternalDependencies for the enforced boundary (no net/http, no GitHub client, anywhere in its dependency graph).
  • Renames and copies are not analyzed for lifecycle continuity. A renamed or copied file produces an unsupported_change_type diagnostic instead of being diffed against its old path.
Signal lifecycle and changed

Every signal carries a lifecycle, computed by comparing HEAD against the resolved merge-base:

  • introduced — present at HEAD, not present at the merge-base.
  • existing — present at both HEAD and the merge-base.
  • resolved — present at the merge-base, not present at HEAD.
  • unknown — the merge-base side of the file could not be analyzed (e.g. it had a syntax error), so lifecycle can't be determined.

changed is a separate boolean: it's true when the signal's HEAD location overlaps a line the diff marks as changed, independent of lifecycle — an untouched, pre-existing signal in a file that had other lines changed is changed: false.

Locations: 0-based JSON vs 1-based text

JSON output reports Location fields (start_row, start_col, etc., see pkg/semantics/result.go) as 0-based, matching Tree-sitter's own convention. Text-mode output's line: field adds 1 to start_row for a human-friendly 1-based display line.

--suggest-project-config: opt-in project-config candidate

coach codesignal --baseline --suggest-project-config is a separate, opt-in mode that discovers Go module/workspace roots at HEAD and prints a minimal --project-config candidate for you to review and commit yourself — it never reads or writes an actual --project-config file, and it never runs any project-analysis backend. It resolves HEAD once, walks an immutable Git snapshot for go.mod/go.work files (never the worktree, so uncommitted or ignored files never affect the result), and emits strict schema-1 JSON containing only roots (never layers, forbidden_imports, or required_layer — those require a human decision this mode does not make; nor source_sink_pack, which is a reserved field with no effect on evaluation today, so there is nothing for this mode to suggest for it).

coach codesignal --baseline --suggest-project-config
  • Requires --baseline; it cannot be combined with --base, --project-config, --project-language, --format, --scope, --build-target, or a positional argument — any of those combinations is rejected outright rather than given a precedence.
  • The candidate JSON is written to stdout (2-space indent, one trailing newline); pass --output <path> to write it to a repository-relative file instead (create-only — it fails if the target already exists in any form). Either way, exactly one UTF-8 newline-delimited JSON (NDJSON) diagnostic/provenance object — compact, single-line, one trailing newline — is written to stderr describing the resolved revision, the roots considered, and root-discovery coverage.
  • This is a candidate only. Nothing consumes it automatically: review it, add any layers/forbidden_imports/required_layer policy you want (source_sink_pack is accepted but currently reserved and has no effect — see Configured layer violations below), and pass the result to --project-config yourself.
  • Commit before use. --project-config reads at the analyzed revision, not your worktree (see Configured layer violations below), so a candidate written with --output <path> must be committed before a subsequent --project-config <path> run can read it — an uncommitted candidate fails with exit 2 and a stderr message naming commit as the remedy.
  • roots alone yields facts only. A candidate straight from this mode has roots but no layers/forbidden_imports: fed into --project-config as-is, it produces a real schema-2 report with project_coverage, but zero layer findings, because there is no layer policy yet to violate. Add layers/forbidden_imports yourself (see below) once you're ready to enforce one.

Candidate example (stdout, for a repository with a root module and one nested module):

{
  "schema_version": "1",
  "roots": [
    ".",
    "services/payments"
  ]
}

Diagnostic/provenance example (stderr, one NDJSON line — shown compact, exactly as emitted):

{"diagnostic_version":"1","kind":"project_config_suggestion","revision":"3474e2c...","heuristic_version":"go-project-config-roots@1","roots_considered":[".","services/payments"],"coverage":{"phase":"project_config_suggestion","complete":true,"counts":{"files_seen":2,"files_skipped":0,"modules_seen":2,"modules_skipped":0,"roots_emitted":2},"budgets":{"graph_edges":0,"graph_nodes":0,"input_bytes":67108864,"input_files":500000,"stderr_bytes":0,"wall_time_ms":0,"working_set_bytes":0},"diagnostics":[]},"diagnostics":[{"code":"project_config_suggestion_ready","message":"coach codesignal --suggest-project-config: candidate generated successfully"}]}

Exit codes are distinct from the standard codesignal modes above: 0 on a successfully generated candidate, 2 for a usage error or a discovery problem (no Go modules found, ambiguous/duplicate roots, an invalid or already-existing --output path), and 3 if the immutable snapshot can't be read or candidate serialization unexpectedly fails.

Configured layer violations (--project-config)

Pass --project-config <path> (a repository-relative path to a JSON document, read at the analyzed revision — not your worktree) to opt into one narrow exception to the file-local default: an import-layer check. It never guesses your architecture — you declare which directories belong to which layer and which layer-to-layer imports are forbidden, and the CLI reports only edges that violate that explicit policy.

{
  "schema_version": "1",
  "roots": ["."],
  "layers": [
    { "name": "handlers", "prefixes": ["pkg/handlers"] },
    { "name": "db", "prefixes": ["pkg/db"] }
  ],
  "forbidden_imports": [
    { "from": "handlers", "to": "db" }
  ]
}
  • roots — repository-relative directories to analyze (for --project-language go, your Go module/workspace roots; for typescript, the directories the sidecar should collect); required, at least one. --suggest-project-config above generates a starting candidate for Go projects.
  • layers — named layers, each with one or more non-overlapping repository-relative directory prefixes; optional.
  • forbidden_imports — directed {"from": "<layer>", "to": "<layer>"} pairs naming layers declared above; optional. A from/to that doesn't match a declared layer name is a config error (exit 2), not a silently-ignored rule.
  • required_layer — the name of one layer declared above that a statically resolved call path from a handler-shaped source to a pinned database-access sink must pass through; optional, and only wired for --project-language go today (see architecture.layer_bypass below). A name that doesn't match a declared layer is a config error (exit 2), matching forbidden_imports' undefined-layer rule.
  • source_sink_pack — accepted and schema-validated (must be "" or "builtin-v1" if set; any other value is a config error, exit 2). It is currently a reserved field: no backend reads it, so setting it never changes which findings are produced or their content (kind, severity, confidence, lifecycle, evidence, anchors, etc.). It is still part of the raw config bytes hashed into config_digest, though, so setting or clearing it changes a project analysis's config_digest and therefore every ProjectChange's id/fingerprint — downstream consumers keyed on those IDs will see churn even though the findings themselves are unchanged. architecture.layer_bypass's source/sink set is pinned in code (pkg/projectmodel/go_reachability.go's ReachabilitySinkPatterns), not selected via this field.

Works with both --baseline and --base <ref>:

coach codesignal --baseline --project-config project.json
Repository Baseline for revision 9022f72... (not a diff comparison)
tracked files discovered: 4, analyzed: 2, unsupported: 2, excluded: 0, unanalyzable: 0, active signals: 1, diagnostics: 0
Project findings:
semantic_key: architecture.layer_violation:pkg/handlers->pkg/db
rule_id: architecture.layer_violation
path: pkg/handlers/handlers.go
line: 3
lifecycle: baseline
changed: false
evidence: pkg/handlers imports pkg/db (layer handlers -> db is forbidden)
machine_evidence.importee: pkg/db
machine_evidence.importer: pkg/handlers
machine_evidence.layer_from: handlers
machine_evidence.layer_to: db
machine_evidence.rule: handlers->db
Project summary: active=1, introduced=0, existing=0, resolved=0, baseline=1

Coverage:
  unsupported: 1 .json files
  unsupported: 1 .mod files

Project coverage: phase=go_model_build, complete=true
  ...

Required intermediate layer (architecture.layer_bypass, --project-language go only). Add required_layer naming one of the declared layers to additionally check that every statically resolved call path from a net/http.HandlerFunc-shaped source to a pinned database-access sink (*database/sql.DB's Exec/ExecContext/Query/QueryContext) passes through that layer:

{
  "schema_version": "1",
  "roots": ["."],
  "layers": [
    { "name": "handlers", "prefixes": ["pkg/handlers"] },
    { "name": "service", "prefixes": ["pkg/service"] }
  ],
  "required_layer": "service"
}
Project findings:
semantic_key: architecture.layer_bypass:service:example.com/app/pkg/handlers.Handler->(*database/sql.DB).Query
rule_id: architecture.layer_bypass
path: pkg/handlers/handlers.go
line: 10
lifecycle: baseline
changed: false
evidence: example.com/app/pkg/handlers.Handler reaches (*database/sql.DB).Query via a statically resolved path that never passes through required layer "service"
machine_evidence.path: example.com/app/pkg/handlers.Handler->example.com/app/pkg/handlers.directQuery->example.com/app/pkg/handlers.rawQuery->(*database/sql.DB).Query
machine_evidence.required_layer: service
machine_evidence.sink: (*database/sql.DB).Query
machine_evidence.source: example.com/app/pkg/handlers.Handler
why it matters: A statically resolved call path from a handler-shaped source to a pinned sink that structurally never passes through a required intermediate layer means that layer's invariants (validation, authorization, caching, ...) can be silently skipped by this route, even if a separate compliant path also exists.
recommendation: Route the call through the required layer, or add an explicit exception/allowlist entry if the bypass is intentional so it stops surfacing as drift.
path step: example.com/app/pkg/handlers.Handler, confidence: high
  source: pkg/handlers/handlers.go:10
path step: example.com/app/pkg/handlers.directQuery, confidence: high
  source: pkg/handlers/handlers.go:15
path step: example.com/app/pkg/handlers.rawQuery, confidence: high
  source: pkg/handlers/handlers.go:19
path step: (*database/sql.DB).Query, confidence: high
Project summary: active=1, introduced=0, existing=0, resolved=0, baseline=1

This Handler also calls service.LoadUser — a compliant route that does pass through the required service layer — but architecture.layer_bypass still reports the direct directQuery/rawQuery route: a coexisting compliant route never suppresses a real bypass witness (see pkg/projectmodel.LayerBypassWitness's doc comment). Only exact, statically resolved, confidence: high witnesses are ever reported — an unresolved, dynamic, ambiguous-layer, or budget-truncated path is silently dropped rather than guessed. --format=json carries the same shape as architecture.layer_violation's project_changes[]/signals[] entries, with path_steps[] giving each hop's node_id, source location, and confidence, and machine_evidence carrying source/sink/required_layer/path instead of importer/importee/layer_from/layer_to/rule.

A clean architecture.layer_bypass report is not a compliance verdict. The source and sink registries are narrow and pinned in code, not configurable: a source must match net/http.HandlerFunc's exact signature (a custom router, a gRPC handler, or a framework that wraps the request differently is invisible to this check), and a sink must be a direct call to one of *database/sql.DB's four methods — Exec/ExecContext/Query/QueryContext — not a call through *sql.Tx, *sql.Stmt, *sql.Conn, an ORM like gorm/sqlx, or a repository interface the driver call sits behind, none of which are in the registry. If your codebase's handlers or data access don't match those exact shapes, zero findings means "nothing matched the registry," not "no bypass exists" — the same caveat architecture.layer_violation states for its own coverage below applies here with an even narrower net.

When the underlying search's own coverage is incomplete, a project_layer_bypass_coverage_incomplete diagnostic appears in the report, a witness the search already found is still reported rather than hard-suppressed, and every project finding's lifecycle in that report degrades to unknown instead of a definitive resolved/introduced/existing/baseline claim. Each degraded finding's own evidence says "see the project_lifecycle_indeterminate diagnostic for why" — so you don't have to separately cross-reference the report's Diagnostics: section to find out.

TypeScript example. The same layers/forbidden_imports schema shown above works unmodified with --project-language typescript (required_layer is Go-only — see above) — only the CLI flag changes. A TypeScript project additionally needs a discoverable tsconfig.json (the sidecar uses it to resolve relative import specifiers into file-addressed edges) and the vendored sidecar binary described below.

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "bundler",
    "target": "es2022"
  },
  "include": ["pkg/**/*.ts"]
}
coach codesignal --baseline --project-config project.json --project-language typescript
Repository Baseline for revision c67e28c... (not a diff comparison)
tracked files discovered: 4, analyzed: 2, unsupported: 2, excluded: 0, unanalyzable: 0, active signals: 1, diagnostics: 0
Project findings:
semantic_key: architecture.layer_violation:pkg/handlers/handlers.ts->pkg/db/db.ts
rule_id: architecture.layer_violation
path: pkg/handlers/handlers.ts
line: 1
lifecycle: baseline
changed: false
evidence: pkg/handlers/handlers.ts imports pkg/db/db.ts (layer handlers -> db is forbidden)
machine_evidence.importee: pkg/db/db.ts
machine_evidence.importer: pkg/handlers/handlers.ts
machine_evidence.language: typescript
machine_evidence.layer_from: handlers
machine_evidence.layer_to: db
machine_evidence.rule: handlers->db
why it matters: Import edges that cross a configured layer boundary erode the architecture the team claims to follow, so drift compounds across packages that look fine in isolation.
recommendation: Move the dependency behind an allowed boundary (interface in the lower layer, invert the import, or relocate the shared type), or update the explicit layer policy if the edge is intentional.
Project summary: active=1, introduced=0, existing=0, resolved=0, baseline=1

Coverage:
  unsupported: 2 .json files

Project coverage: phase=ts_sidecar_build, complete=true
  count: files_analyzed=2
  count: files_seen=3
  count: projects_analyzed=1
  count: tsconfig_count=1
  budget: timeout_ms=60000

Three differences from the Go example above are structural, not incidental. machine_evidence carries an extra language: typescript entry (Go's output has no such key). The project coverage phase is ts_sidecar_build rather than go_model_build, and each backend emits its own disjoint set of count:/budget: keys under that phase (only count: files_seen is shared) — the Go example's ... above stands in for Go's own set, not TypeScript's. And addressing granularity differs: Go's evaluator groups findings per (importer package directory, importee package directory) pair, so semantic_key, evidence, and machine_evidence.importer/machine_evidence.importee use package-directory addressing (pkg/handlers, pkg/db), while the TypeScript evaluator is file-addressed instead, so those same fields carry full file paths (pkg/handlers/handlers.ts, pkg/db/db.ts) and findings group per violating file pair rather than per package pair — forced by file-level vs. package-level import-edge identity in each backend, not an artifact of the filenames chosen for this example.

The why it matters:/recommendation: lines are true abridgement, not a fourth structural difference: both evaluators reuse the same underlying constants, and the Go example above simply leaves those two lines out rather than marking them with ... (its one ... is under Project coverage:, standing in for Go's coverage keys, not for these lines).

--format=json adds project_changes (each with a machine_evidence map, primary_anchor, and — when the same violating layer pair has more than one import site — related_locations), project_facts, project_summary, and project_coverage alongside the existing signals array; --base <ref> classifies each finding's lifecycle as introduced, existing, or resolved instead of baseline, following the same semantics as file-local signals. Omitting --project-config leaves every existing example above completely unchanged: byte-identical schema_version: "1" output, with the project_changes/project_facts/project_summary/project_coverage keys entirely absent (not merely empty) from the document — confirmed above, where the diff-mode JSON example run with no --project-config carries no project_* key at all.

Each signals[] entry carries the same machine_evidence, related_locations, path_steps, and coverage_refs fields as its project_changes[] counterpart, for a project-origin finding and a file-local one alike — see schema_version: "1" vs "2" above. A file-local finding's copies are simply empty ({}/[]/[]/[]) rather than omitted. This lets automation that reads only signals[] get the same structured evidence as the text report, without also reading project_changes[]. project_changes[] still exposes the same fields for consumers that want the project-specific raw view.

This check is advisory, coverage-honest, and approximate: it reports zero findings (never a guess) for any import edge it cannot resolve or that doesn't map to a configured layer, retains findings at lifecycle: unknown rather than a false introduced/existing/resolved claim when project coverage is incomplete, and matches layers by repository-relative directory prefix over Go import facts (the go backend) or TypeScript import facts (the typescript backend) — not a full type or build-graph analysis. --project-language typescript is a real, registered backend: layer violations are evaluated from file-addressed import edges produced by a pinned sidecar binary at js/semantics/bin/coach-ts-project-sidecar, resolved relative to the analyzed repository's own root (not this coach binary's install location — the analyzed repository must vendor the sidecar at that exact path). If the sidecar is missing, crashes, or times out, that is reported as a project_backend_unavailable entry in project_coverage.diagnostics with complete: false and exit 0 — never exit 3. Exit 3 is reserved: currently unreachable, because --project-language accepts only go and typescript (both registered) and any other value is a usage error (exit 2); it remains defined for a future language whose backend is registered but unavailable.

Absence is not a compliance verdict. A report with no architecture.layer_violation finding is never proof the codebase follows any particular architecture: it can mean the edge doesn't violate your configured policy, the edge isn't covered by any layer you declared, or project coverage was incomplete for that revision — check project_coverage/Project coverage: before treating a clean report as an architectural guarantee.

pkg/codesignal.EvaluateGoLayerBypass maps a pkg/projectmodel.BuildGoLayerBypass run's high-confidence witnesses — a statically resolved call path from a source to a sink that structurally never passes through a required layer — onto architecture.layer_bypass findings, following the same suppression rules as architecture.layer_violation: only exact high-confidence witnesses are emitted, path search is shortest-first, and an incomplete search still emits any witnesses it did find alongside a coverage-incomplete diagnostic rather than a silent "no bypass" claim. Both are wired into coach codesignal --project-config via the required_layer field documented above — but for --project-language go only; see the worked example above. pkg/projectmodel separately has library-only support for bounded Go possible-call-reachability facts (a pinned static call graph plus deterministic source-to-sink path search, both bounded by budgets and approximate — direct, statically resolved calls only, not full points-to) via BuildGoReachability, which stays unwired. BuildTypeScriptLayerBypass/EvaluateTypeScriptLayerBypass are the TypeScript analogs of the now-wired Go pair — built from the same sidecar round trip as --project-language typescript's import-edge analysis and sharing the architecture.layer_bypass rule ID and every suppression rule above, with a machine_evidence.language: typescript entry and a distinct algorithm_version (ts-layer-bypass-registry@1 vs. go-layer-bypass-registry@1) as the only externally visible differences, matching architecture.layer_violation's existing Go/TypeScript split — but they remain library-only: CLI wiring is scoped to the Go backend, so required_layer has no effect under --project-language typescript today.

BuildTypeScriptReachability is the TypeScript analog of BuildGoReachability, producing possible_call_reachability facts over the same sidecar round trip — but not the same facts: the TS walk follows a route handler's body only one level deep, so a handler → service → sink chain that isn't a direct call yields no fact, only a ts_reachability_local_call_not_followed_gap coverage diagnostic, where Go's BFS is unbounded. Sources is correspondingly narrower too, listing only the sources that actually produced a resolved fact rather than every registry source function identified in the snapshot. Unlike architecture.layer_bypass, these facts carry no ProjectChange/machine_evidence shape at all — they map onto ProjectFact (facts-only: a reachability fact never becomes a Signal or ProjectChange) via pkg/codesignal.ReachabilityProjectFacts, which the caller invokes once per backend with a language argument ("go" or "typescript") that lands in the resulting facts' provenance.language field. That field is ProjectFact's only cross-language provenance signal, since ProjectFact has neither a rule_id nor a machine_evidence map to carry an equivalent key.

None of BuildGoReachability, BuildTypeScriptReachability, BuildTypeScriptLayerBypass, EvaluateTypeScriptLayerBypass, or ReachabilityProjectFacts are wired into coach codesignal today: no flag builds them, and no report field surfaces call-graph, reachability, or TypeScript layer-bypass facts through the CLI yet. BuildGoLayerBypass/EvaluateGoLayerBypass are the one exception, wired as described above.


pkg/semantics Quickstart

pkg/semantics operates purely on raw bytes, meaning you don't need a file system to analyze code.

package main

import (
	"context"
	"fmt"

	"github.com/lousy-agents/coach/pkg/semantics"
)

func main() {
	// Initialize the analyzer
	analyzer, err := semantics.NewAnalyzer(semantics.AnalyzerOptions{})
	if err != nil {
		panic(err)
	}

	sourceBytes := []byte(`
		package main
		func Hello() string {
			return "world"
		}
	`)

	result, err := analyzer.AnalyzeBytes(context.Background(), semantics.FileInput{
		Path:     "hello.go",
		Language: semantics.LanguageGo,
		Content:  sourceBytes,
	})
	if err != nil {
		panic(err)
	}

	fmt.Println("Status:", result.ParseStatus) // "ok"
	for _, f := range result.Findings {
		fmt.Printf("Finding: %s (Location: %v)\n", f.Kind, f.Location)
	}
}
Error Handling

If a file has syntax errors, AnalyzeBytes returns a partial *Result along with an error wrapped as ErrSyntax. You can extract detailed syntax issues using errors.As:

import "errors"

// ...

result, err := analyzer.AnalyzeBytes(ctx, input)
if errors.Is(err, semantics.ErrSyntax) {
	var syntaxErr *semantics.SyntaxError
	if errors.As(err, &syntaxErr) {
		for _, issue := range syntaxErr.Issues {
			fmt.Printf("Syntax issue: %s at %v\n", issue.Kind, issue.Location)
		}
	}
}

Other sentinel errors:

  • ErrEmptyContent — The provided input content is empty.
  • ErrUnsupportedLanguage — The file extension or language is not supported.
  • ErrFileTooLarge — The source file exceeds limits.
  • ErrBinaryContent — The file appears to be binary.
  • ErrParseFailure — General Tree-sitter parsing failure.

Coaching Findings

Beyond parsing metrics, coach analyzes code constructs to flag specific design issues. The most common is mutates_input.

mutates_input Finding

mutates_input flags functions or methods that mutate a parameter in place. This can lead to hard-to-debug "spooky action at a distance" bugs.

For example, this Go function mutates its input in place:

func ApplyDefaults(cfg *Config) {
    cfg.Timeout = 30 * time.Second // Mutates the caller's Config
}

This generates a finding detailing the mutation:

{
  "kind": "mutates_input",
  "name": "ApplyDefaults:cfg",
  "location": { "start_byte": 26, "end_byte": 36, "start_row": 1, "start_col": 4, "end_row": 1, "end_col": 14 },
  "confidence": "medium",
  "evidence": "cfg.Timeout",
  "recommendation": "Return a copy instead of mutating the caller's value, or document/rename this function to make the in-place mutation explicit.",
  "suggested_skill": "refactor-hidden-mutation"
}

A safer alternative returns the updated value:

func WithDefaults(cfg Config) Config {
    cfg.Timeout = 30 * time.Second
    return cfg
}

[!TIP] Syntactic Constraint: The mutates_input checker is purely syntactic and conservative. It does not perform interprocedural analysis or follow reference aliases across scopes.

Other Supported Findings
  • constructor_func (Go) — Flags factory or constructor functions.
  • pointer_return (Go) — Flags functions returning a pointer to a struct.
  • tight_coupling (TS/TSX) — Flags class/object patterns with high coupling.
  • toctou_check_then_act (Go, TS/TSX) — Flags a filesystem existence/state check whose success path later acts on the identical path (CWE-367).

JavaScript / TypeScript Quickstart

Here is how you can perform syntactic analysis in a Node.js project:

import { readFile } from "node:fs/promises";
import { createAnalyzer, SemanticsSyntaxError } from "@lousy-agents/coach-semantics";

// Initialize the analyzer child process
const analyzer = await createAnalyzer();

try {
  const content = await readFile("widget.ts");
  const result = await analyzer.analyzeBytes({
    path: "widget.ts",
    language: "typescript", // "go", "typescript", or "tsx"
    content: content,
  });

  console.log("Status:", result.parse_status);
  console.log("Metrics:", result.metrics);
  console.log("Findings:", result.findings);
} catch (err) {
  if (err instanceof SemanticsSyntaxError) {
    // Access the partial Result containing syntax errors
    console.log("Syntax Errors:", err.partialResult.syntax_errors);
  } else {
    console.error("Analysis failed:", err);
    throw err;
  }
} finally {
  // Always clean up the analyzer process when finished
  analyzer.dispose();
}

Thrown errors inherit from SemanticsError and carry a kind string:

  • "syntax", "empty_content", "unsupported_language", "file_too_large", "binary_content", "parse_failure", "invalid_options", "canceled", "internal", "backend_unavailable".

Stability Guarantees

  • JSON Stability: The output structure and its snake_case JSON field names are completely frozen.
  • API Stability: Because coach is currently pre-1.0, the core JSON structure and sentinel errors are stable, but other parts of the API surface may evolve.

Directories

Path Synopsis
cmd
acceptance-guard-preflight command
Command acceptance-guard-preflight is the activation entry point for internal/acceptanceharness's ambient-credential guard: run it before any acceptance suite starts (see mise.toml's test-acceptance-fast task).
Command acceptance-guard-preflight is the activation entry point for internal/acceptanceharness's ambient-credential guard: run it before any acceptance suite starts (see mise.toml's test-acceptance-fast task).
acceptance-style-guard command
Command acceptance-style-guard fails if any acceptance_test.go or *_acceptance_test.go under the module root does not import github.com/onsi/ginkgo/v2 and reference a Ginkgo suite/spec API, except an explicit allowlist (see internal/acceptancestyle).
Command acceptance-style-guard fails if any acceptance_test.go or *_acceptance_test.go under the module root does not import github.com/onsi/ginkgo/v2 and reference a Ginkgo suite/spec API, except an explicit allowlist (see internal/acceptancestyle).
coach command
Command coach is the composition-root CLI for the coach project.
Command coach is the composition-root CLI for the coach project.
coach-api command
Command coach-api is the composition-root HTTP service for the coach platform (Task 2 / GitHub issue #103, epic #97): it wires internal/authn (Coach JWT auth, optional GitHub OAuth login, config-gated test mint) and internal/coachapi (POST /v1/jobs, GET /v1/jobs/{id}, GET /v1/jobs/{id}/report) behind a live authz.RepoAuthorizer and a Redis Streams queue.TaskQueue into one runnable server.
Command coach-api is the composition-root HTTP service for the coach platform (Task 2 / GitHub issue #103, epic #97): it wires internal/authn (Coach JWT auth, optional GitHub OAuth login, config-gated test mint) and internal/coachapi (POST /v1/jobs, GET /v1/jobs/{id}, GET /v1/jobs/{id}/report) behind a live authz.RepoAuthorizer and a Redis Streams queue.TaskQueue into one runnable server.
coach-worker command
Command coach-worker claims work through queue.TaskQueue, persists claim/heartbeat/fenced writes via coachapi.WorkerJobStore, and runs the repo_baseline_scan handler.
Command coach-worker claims work through queue.TaskQueue, persists claim/heartbeat/fenced writes via coachapi.WorkerJobStore, and runs the repo_baseline_scan handler.
codesignal-report command
Command codesignal-report reads a batch of NDJSON lines from stdin, accumulates a single codesignal.Input, and writes exactly one JSON codesignal.Report to stdout.
Command codesignal-report reads a batch of NDJSON lines from stdin, accumulates a single codesignal.Input, and writes exactly one JSON codesignal.Report to stdout.
fakegithub-server command
Command fakegithub-server runs internal/fakegithub as a standalone HTTP process, packaging the issue #79 Task 0.3 thin-offline-proof fixture (internal/acceptanceharness/thinproof) as an internal-network Compose service rather than an in-process httptest.Server.
Command fakegithub-server runs internal/fakegithub as a standalone HTTP process, packaging the issue #79 Task 0.3 thin-offline-proof fixture (internal/acceptanceharness/thinproof) as an internal-network Compose service rather than an in-process httptest.Server.
model-stub command
Command model-stub serves the compose OpenAI-compatible HTTP stub from internal/modelgateway so core smoke can exercise worker → Envoy AI Gateway → backend without model weights.
Command model-stub serves the compose OpenAI-compatible HTTP stub from internal/modelgateway so core smoke can exercise worker → Envoy AI Gateway → backend without model weights.
platform-smoke command
Command platform-smoke is the end-to-end credential-free smoke for the local platform compose stack (Baseline Scan Story 4 / Task 10): mint → submit repo_baseline_scan → poll → assert provenance-tagged report.
Command platform-smoke is the end-to-end credential-free smoke for the local platform compose stack (Baseline Scan Story 4 / Task 10): mint → submit repo_baseline_scan → poll → assert provenance-tagged report.
semantics-json command
Command semantics-json exposes internal/jsbridge as a long-running newline-delimited-JSON server on stdin/stdout, so non-Go hosts (the js/semantics package) can call pkg/semantics without linking CGO themselves.
Command semantics-json exposes internal/jsbridge as a long-running newline-delimited-JSON server on stdin/stdout, so non-Go hosts (the js/semantics package) can call pkg/semantics without linking CGO themselves.
semantics-wasm-smoke command
Stub entry point so `go build ./...` / `go vet ./...` on native platforms don't fail with "build constraints exclude all Go files".
Stub entry point so `go build ./...` / `go vet ./...` on native platforms don't fail with "build constraints exclude all Go files".
thinproof-preflight command
Command thinproof-preflight is the no-pull/offline Compose preflight for issue #79's Task 0.3 thin offline proof (see docs/architecture/acceptance-harness.md section 2's binding contract): it checks, via `docker image inspect`, that the two local images deploy/compose/thinproof's docker-compose.yml expects already exist, before any `docker compose up` runs.
Command thinproof-preflight is the no-pull/offline Compose preflight for issue #79's Task 0.3 thin offline proof (see docs/architecture/acceptance-harness.md section 2's binding contract): it checks, via `docker image inspect`, that the two local images deploy/compose/thinproof's docker-compose.yml expects already exist, before any `docker compose up` runs.
thinproof-runner command
Command thinproof-runner is the external test-runner container for issue #79's Task 0.3 thin offline proof: it exercises the fake-GitHub -> pkg/githubingest -> pkg/semantics -> pkg/codesignal path against a fake GitHub service reachable only at FAKE_GITHUB_BASE_URL (e.g.
Command thinproof-runner is the external test-runner container for issue #79's Task 0.3 thin offline proof: it exercises the fake-GitHub -> pkg/githubingest -> pkg/semantics -> pkg/codesignal path against a fake GitHub service reachable only at FAKE_GITHUB_BASE_URL (e.g.
deploy
internal
acceptanceharness
Package acceptanceharness provides the offline acceptance-test guard rails for Feature Zero (GitHub issue #76, epic #73): an ambient-credential scanner that flags leaked-in, CI-style GitHub/AWS credentials before an acceptance suite runs, and a no-egress http.RoundTripper that makes an accidental public network call (e.g.
Package acceptanceharness provides the offline acceptance-test guard rails for Feature Zero (GitHub issue #76, epic #73): an ambient-credential scanner that flags leaked-in, CI-style GitHub/AWS credentials before an acceptance suite runs, and a no-egress http.RoundTripper that makes an accidental public network call (e.g.
acceptanceharness/agentloopharness
Package agentloopharness defines test/harness seams that a later epic's internal/agentloop package will import from its own tests: a scripted model-gateway stand-in (ScriptedGateway) and a recording tool-call broker (RecordingToolRegistry).
Package agentloopharness defines test/harness seams that a later epic's internal/agentloop package will import from its own tests: a scripted model-gateway stand-in (ScriptedGateway) and a recording tool-call broker (RecordingToolRegistry).
acceptanceharness/queueconformance
Package queueconformance defines a black-box behavioral contract for the eventual TaskQueue port described by ADR-006, and a reusable conformance suite (Run) that exercises any Queue implementation against it.
Package queueconformance defines a black-box behavioral contract for the eventual TaskQueue port described by ADR-006, and a reusable conformance suite (Run) that exercises any Queue implementation against it.
acceptanceharness/thinproof
Package thinproof is the shared, data-as-code fixture for GitHub issue #79's Task 0.3 thin offline proof: a single small file, at a fixed owner/repo/ref/path, served by internal/fakegithub and read back through pkg/githubingest's public GitHubFileReader API.
Package thinproof is the shared, data-as-code fixture for GitHub issue #79's Task 0.3 thin offline proof: a single small file, at a fixed owner/repo/ref/path, served by internal/fakegithub and read back through pkg/githubingest's public GitHubFileReader API.
acceptancestyle
Package acceptancestyle mechanically checks that every acceptance_test.go and *_acceptance_test.go imports github.com/onsi/ginkgo/v2 and references a Ginkgo suite/spec API (RunSpecs, Describe, It, …), except an explicit allowlist.
Package acceptancestyle mechanically checks that every acceptance_test.go and *_acceptance_test.go imports github.com/onsi/ginkgo/v2 and references a Ginkgo suite/spec API (RunSpecs, Describe, It, …), except an explicit allowlist.
agentloop
Package agentloop is a bounded tool-call broker over a typed registry (ADR-005).
Package agentloop is a bounded tool-call broker over a typed registry (ADR-005).
authn
Package authn implements Coach-signed JWT issue, validation, jti denylist revocation, HTTP auth middleware, optional GitHub OAuth App login (no scope), and a config-gated test-mint path for protected /v1 routes (ADR-001).
Package authn implements Coach-signed JWT issue, validation, jti denylist revocation, HTTP auth middleware, optional GitHub OAuth App login (no scope), and a config-gated test-mint path for protected /v1 routes (ADR-001).
authz
Package authz implements ADR-003's synchronous, submit-time repository authorization check: a principal may submit a scan of owner/repo only if the Coach GitHub App installation can read the repository and the principal has a GitHub-recognized role in it.
Package authz implements ADR-003's synchronous, submit-time repository authorization check: a principal may submit a scan of owner/repo only if the Coach GitHub App installation can read the repository and the principal has a GitHub-recognized role in it.
coachapi/queue
Package queue defines the application-owned TaskQueue and EventBus ports required by ADR-006 (docs/architecture/ADR-006-watermill-queue-abstraction.md): coach-api and coach-worker depend only on these interfaces, never on Watermill or a specific broker's types, so the backend (Redis Streams, SQS, ...) can be swapped without touching domain code.
Package queue defines the application-owned TaskQueue and EventBus ports required by ADR-006 (docs/architecture/ADR-006-watermill-queue-abstraction.md): coach-api and coach-worker depend only on these interfaces, never on Watermill or a specific broker's types, so the backend (Redis Streams, SQS, ...) can be swapped without touching domain code.
coachapi/queue/redisstream
Package redisstream implements the internal/coachapi/queue.TaskQueue port (ADR-006, docs/architecture/ADR-006-watermill-queue-abstraction.md) on top of Watermill's Redis Streams pub/sub (github.com/ThreeDotsLabs/watermill-redisstream), for coach's non-AWS / self-hosted deployment target.
Package redisstream implements the internal/coachapi/queue.TaskQueue port (ADR-006, docs/architecture/ADR-006-watermill-queue-abstraction.md) on top of Watermill's Redis Streams pub/sub (github.com/ThreeDotsLabs/watermill-redisstream), for coach's non-AWS / self-hosted deployment target.
coachapi/queue/sqs
Package sqs implements the internal/coachapi/queue.TaskQueue port (ADR-006, "Provider configuration" -> aws-sqs) on top of Amazon SQS.
Package sqs implements the internal/coachapi/queue.TaskQueue port (ADR-006, "Provider configuration" -> aws-sqs) on top of Amazon SQS.
codesignalcli
Package codesignalcli implements the Git-facing plumbing behind the `coach codesignal` subcommand: resolving revisions and selecting the files a CodeSignal report should analyze.
Package codesignalcli implements the Git-facing plumbing behind the `coach codesignal` subcommand: resolving revisions and selecting the files a CodeSignal report should analyze.
fakegithub
Package fakegithub is a Coach-owned, fixture-driven fake GitHub HTTP service for offline acceptance testing.
Package fakegithub is a Coach-owned, fixture-driven fake GitHub HTTP service for offline acceptance testing.
jsbridge
Package jsbridge defines the JSON protocol that exposes pkg/semantics to JavaScript/TypeScript consumers, plus the single handler both transport backends (WASM and stdio CLI) call.
Package jsbridge defines the JSON protocol that exposes pkg/semantics to JavaScript/TypeScript consumers, plus the single handler both transport backends (WASM and stdio CLI) call.
modelgateway
Package modelgateway is the sole inference seam for structured judgment: request in → schema-validated response out.
Package modelgateway is the sole inference seam for structured judgment: request in → schema-validated response out.
projectbridge
Package projectbridge defines the JSON protocol between pkg/projectmodel's TypeScript sidecar client and a pinned local Node/TypeScript sidecar subprocess that produces raw TypeScript/TSX import facts from an immutable snapshot (issue #214).
Package projectbridge defines the JSON protocol between pkg/projectmodel's TypeScript sidecar client and a pinned local Node/TypeScript sidecar subprocess that produces raw TypeScript/TSX import facts from an immutable snapshot (issue #214).
rubrics
Package rubrics holds versioned LLM-as-judge definitions, prompt assembly, and agent-loop tool adapters for seed rubrics (ADR-005).
Package rubrics holds versioned LLM-as-judge definitions, prompt assembly, and agent-loop tool adapters for seed rubrics (ADR-005).
pkg
codesignal
Package codesignal turns pkg/semantics analysis results into prioritized, change-aware coaching signals.
Package codesignal turns pkg/semantics analysis results into prioritized, change-aware coaching signals.
githubingest
Package githubingest reads individual file contents from a GitHub repository via the GitHub Contents API, authenticated as a GitHub App installation.
Package githubingest reads individual file contents from a GitHub repository via the GitHub Contents API, authenticated as a GitHub App installation.
projectmodel
Package projectmodel defines deterministic, offline, whole-repository structural facts about a codebase: workspace/module/package/file inventory and import edges.
Package projectmodel defines deterministic, offline, whole-repository structural facts about a codebase: workspace/module/package/file inventory and import edges.
semantics
Package semantics extracts deterministic structural facts from raw source bytes (syntax validity, imports, branching metrics, constructor-like patterns, mutates_input) using Tree-sitter grammars.
Package semantics extracts deterministic structural facts from raw source bytes (syntax validity, imports, branching metrics, constructor-like patterns, mutates_input) using Tree-sitter grammars.
semantics/internal/engine
Package engine defines the narrow tree-sitter surface pkg/semantics relies on.
Package engine defines the narrow tree-sitter surface pkg/semantics relies on.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL