GitContribute is a local-first research workbench. GitHub is an explicit input source; SQLite is the durable system of record. Commands that inspect the corpus must not silently fetch data, execute repository code, or mutate GitHub.
explicit network reads
|
v
CLI ---------+ GitHub adapter ------ GitHub API
MCP ---------+----> application service
TUI ---------+ |
+----> DeepWiki adapter -----> public DeepWiki MCP
|
+----> acquisition/workspace adapters ----> git
|
+----> validation runner -----------------> process
|
v
SQLite corpus
observations + projections
|
v
offline search, radar, health, dossiers, thread briefs,
investigations, evidence, readiness, and drafts
The dependency direction is toward product-owned contracts:
internal/appowns use cases and side-effect decisions.internal/corpusowns persistence, transactions, migrations, and local query behavior.internal/github,internal/deepwiki,internal/acquire,internal/workspace, and the evidence runner are adapters for external capabilities. DeepWiki prose is untrusted derived context, is not persisted, and never updates GitHub projections.internal/cli,internal/mcpserver, andinternal/tuitranslate user or protocol input into application calls. They do not own product rules. MCP prompts are static workflow guidance; they cannot grant new authority or turn repository content into instructions.
Third-party SDK and database types terminate inside their adapters. The application and domain packages expose product-owned values and interfaces. Inbound adapters import those contracts directly; they do not re-export product-owned types, errors, or constants.
| Capability | Examples | Network | Local write | Process execution | GitHub mutation |
|---|---|---|---|---|---|
| Corpus read | search, health, dossier show, research brief, readiness, MCP resources | no | no | no | no |
| Corpus write | investigations, start-thread, evidence, lenses, tracking, cluster governance | no | yes | no | no |
| Derived projection refresh | explicit clusters refresh OWNER/REPO |
no | yes | no | no |
| Private MCP runtime installation | explicit setup --mode mcp |
no | yes | no | no |
| Global CLI installation | explicit setup --mode cli or --mode both |
npm registry dependent | yes | npm only |
no |
| Setup verification | all applied setup modes | no | no | git --version |
no |
| GitHub read | sync, crawl, hydrate, bounded search/source acquisition | yes | yes | no | no |
| Coverage workflow | explicit corpus.ensure_coverage |
yes, bounded | yes | no | no |
| DeepWiki external read | public repository structure, contents, questions | yes | no | no | no |
| Git acquisition | acquire, workspace create | remote-dependent | yes | git only |
no |
| Local merge check | compare already-fetched revisions | no | no | git only |
no |
| Validation | validation run/repeat with explicit execution | no by default | yes | yes | no |
Version 1 has no GitHub mutation path. Adding one requires a separate application capability and protocol annotation; it must not be hidden behind a read operation.
Validation definitions remain shell-free and execution requires an explicit
authorization flag. Repeat groups bound attempts, concurrency, per-run and
overall timeouts, captured output, and sampler frequency. The generic process
runner records spawn/execution/shutdown boundaries and uses gopsutil for
PID-plus-start-time process-tree metrics. Declared mcp_stdio definitions use
the official MCP SDK for initialize and tools/list milestones; protocol state
is never inferred from arbitrary stdout text. Metric availability and cleanup
status are persisted separately from semantic pass/fail classification.
The corpus separates source history from convenient current state:
- Observations are append-only records of source payloads and provenance.
- Projections are normalized repository and thread rows used by local queries.
- Facet observations store paginated child data such as issue comments, reviews, and review comments.
- Facet coverage records whether a facet fetch completed and the source revision it represents.
GitHub actors are first-class projections rather than denormalized author strings. A stable provider node ID is the preferred identity; observed logins are aliases, so a rename does not split one contributor into two profiles. Profile fields remain nullable because absence may mean undisclosed, unauthorized, or not acquired. Social accounts, public organizations, pinned items, repository relationships, and contribution periods are independent facets with their own observation time, source time, authorization scope, and completeness. A child table is replaced only after its complete bounded fetch; an incomplete observation advances coverage without destroying the last complete projection.
Actor acquisition is deliberately atomic. github.search_users stores only
identity observations. Exact github.sync_users profile reads and the
github.sync_user_* facet tools perform no implicit fan-out into one another.
corpus.search_actors, corpus.get_actors, corpus.get_actor_facets, and
corpus.search_contributions are offline and snapshot-bound. See
docs/actor-corpus.md for the typed provider-to-SQLite mapping.
An explicit repository-context sync checks a fixed, bounded set of conventional
CONTRIBUTING.md and AI-policy paths. Found text is stored as an untrusted
repository-level contribution_guidance facet with exact file provenance.
Offline readers may classify only predefined policy statements; repository
text cannot introduce instructions or grant capabilities.
Thread-header sync never fetches pull-request detail facets per listed item.
Repository metadata and fixed policy paths share one explicit context budget.
Thread-header sync is a separate capability over an existing repository
projection: pageable selections consume as many list requests as fit, while
exact selections are admitted atomically. Batch allocation is stable by input
order. Each executor consumes the immutable plan produced before network or
corpus writes, and reports the same planned_requests and request_budget
values afterward.
The issue-list endpoint used by thread-header sync does not expose pull-request
merge state. A header-only closed pull request therefore stores merge state as
unknown, not false. Explicit pr_details hydration makes the value known, and
later header refreshes cannot erase that observation. Offline filters for
merged=true or merged=false, dossier outcome groups, precedent/seed
classification, portfolio attention, and health merge rates include only
explicitly observed outcomes. Unknown closed outcomes are surfaced separately;
MCP thread output omits the nullable merged field until it is known.
Health also consumes the repository-level threads coverage fact instead of
inferring completeness from the number of rows returned. External merge rates
are nullable: when no explicitly observed closed external outcome exists, JSON
reports null and the health coverage explains why the rate is unavailable. A
known zero merge rate remains distinct from an unknown rate.
Pull-request portfolios use the ordinary repository and thread projections.
github.sync_pull_request_portfolio is the only public portfolio producer. Its
discriminated selection is either authored discovery (optionally scoped to one
repository) or an explicit bounded set; identity lookup, authored discovery,
and scalar status refresh are
internal phases rather than separately advertised operations.
REST pr_details and pr_reviews facets are combined with typed GraphQL
facets for checks, unresolved review threads, detailed merge state, merge queue,
closing issues, and changed files. Each facet has independent coverage; an
incomplete refresh preserves the previous complete child snapshot but marks
the newer coverage incomplete. Offline portfolio reads therefore return
unknown instead of treating missing checks as passing or missing overlap
signals as no overlap.
Feedback and CI are separate producer capabilities. Feedback preserves issue comments, submitted reviews, inline comments, and GraphQL review-thread topology as independently covered channels. CI observations bind checks and statuses to the pull request's observed head SHA. An unsupported log request fails before submission rather than producing fabricated log coverage.
Repository-wide feedback indexing is a separate bounded workflow:
github.index_pull_request_feedback
-> jobs.get
-> corpus.search_pull_request_feedback(feedback_author=exact_login)
-> gitcontribute://pull-request-feedback/{owner}/{repo}/{number}
-> gitcontribute://pull-request-feedback/{owner}/{repo}/{number}/{channel}/{feedback_id}
The index job enumerates provider pull-request pages with state=all and
stores its next page, request count, item bound, and completeness in a
repository-scoped discovery checkpoint. Each discovered PR is then passed
through the exact feedback adapter, so open, closed, and merged PRs retain
their observed head SHA, merge state, channel coverage, and raw facet payloads.
The normalized pull_request_feedback_fts projection is rebuildable from
complete raw observations; a newer incomplete or stale observation can advance
coverage without replacing the previous complete child snapshot. Offline
search reports discovery and facet coverage separately from match count and
returns the exact index, feedback-sync, or PR-details recovery action needed
to resolve an unknown. Search never performs network access.
The live MCP server exposes workflow.get_catalog_contract as a read-only
catalog-parity contract. It reports the running version, whether the server is
in all or read_only mode, a deterministic fingerprint of the registered
tools/list definitions, and explicit availability of each feedback route.
This makes a stale registration distinguishable from an intentionally
restricted catalog. Clients must create a fresh MCP connection after setup,
upgrade, or registration changes before comparing the contract.
Thread resolution remains outside this read/index workflow. A future mutation must accept exact repository, PR, and thread identifiers plus the expected head SHA and must be separately authorized; indexing never auto-resolves a thread.
Portfolio relationships and derived resolution records are local product contracts. Their normalized snapshots carry rule versions and exact source observation references. Explicit timeline events may produce a resolution; closing-issue relationships remain relationship evidence until completion is independently observed. Lexical similarity alone never becomes a root-cause claim. Corpus portfolio and resolution reads perform no network access.
Repository and thread projections use this ordering:
(source_updated_at, observation_sequence)
A newer source timestamp wins. If timestamps are equal or unavailable, the local observation sequence breaks the tie. This prevents late or retried work from replacing a newer projection.
Duplicate clusters are a versioned derived projection over stored repository
threads. Listing a repository's clusters, inspecting one cluster, finding a
member's cluster, and MCP corpus.find_clusters are pure corpus reads: they do
not score candidates, write SQLite, fetch GitHub, or execute a process.
Refresh is a separate, explicit local-write capability:
read one repository snapshot
-> identify source + governance + rule versions
-> close the read transaction
-> generate a lossless bounded candidate-pair set
-> score candidate pairs exactly with cancellation
-> reconcile durable governance
-> begin write transaction and recheck revisions
-> atomically replace the visible projection
A projection identity consists of the repository, a full SHA-256 source revision, a monotonic governance revision, and a similarity rule version. The source revision covers every candidate field consumed by scoring or persisted output. An empty completed projection has an identity too, so unchanged empty repositories do not recompute on every refresh. If source or governance changes during computation, commit returns a stale-input error and leaves the previous complete projection visible.
duplicate-v1 scores pairs at a 0.30 threshold. Candidate generation is
lossless: every pair with an explicit thread reference or shared normalized
title token is scored exactly. A pair with neither signal can contribute at
most 0.15 from body, labels, and author, so it cannot reach the threshold.
The 10,000,000-comparison budget remains a worst-case population bound; sparse
repositories score fewer pairs. Refresh statistics distinguish all
possible_pairs from exact scored_pairs. Cancellation is checked during
preparation and candidate evaluation. Similarity preparation, candidate
selection, and scoring live in internal/similarity; internal/clustering
remains storage-free, while internal/corpus owns snapshots, governance
transactions, and atomic projection replacement.
Membership overrides are durable governance, not direct projection edits. Adding an override and advancing its repository governance revision happen in one transaction. The next explicit refresh applies the decision. A governed canonical choice may change the displayed canonical member, but the stable cluster identity remains anchored to the engine-selected canonical member so the next refresh can recover its history.
Hydration buffers every page before writing. ApplyFacetObservationSet then
replaces the previous facet and advances coverage in one transaction:
fetch page 1..N -> validate complete set -> begin transaction
-> compare source ordering
-> replace observations
-> advance coverage
-> commit
Cancellation, a page error, or a stale source revision leaves the previous complete set visible. An empty complete set is meaningful: it replaces old children and records complete coverage with zero items.
Long-running application operations return stable job IDs. Job state and events live in SQLite; active execution remains process-bound. A stale running job is marked failed after restart and is never silently replayed, because external reads and host operations cannot all promise safe automatic replay. The agent may inspect the stored request and explicitly resubmit an idempotent operation after reviewing the failure.
Each JobExecutor registers an opaque owner ID and periodically updates its
lease. A new executor reconciles only running jobs whose owner is absent or
stale. It must never fail jobs owned by another live process.
queued -> running -> succeeded
-> failed
-> cancelled
Terminal states do not transition again. Cancellation is first persisted, then delivered to an in-process worker directly or observed by one executor-wide poll that checks all active job IDs together. Executors cap both running and admitted jobs, so nested operation-specific worker pools cannot create unbounded process-wide concurrency. Reconciliation uses an immediate SQLite transaction so a heartbeat cannot interleave between the liveness read and stale-owner update. MCP job reads expose structured phase, completed-item, total-item, percentage, and retry-delay fields. Concise polling omits stored request and result payloads; detailed mode retrieves them after a finalist is terminal. Batch reads and cancellation preserve input order and isolate per-item failures; free-form job event text is not an MCP progress contract.
Batch MCP operations preserve input order and return an outcome per input key. Independent GitHub reads use fixed server-side concurrency ceilings; code acquisition uses a lower ceiling because it performs Git processes and local writes. A single unavailable or retryable item does not erase successful siblings. Callers should retry only items marked retryable and use the provided recovery hint for unavailable inputs. Duplicate batch keys are rejected before submission instead of being silently collapsed, because collapsing would make the returned outcome count differ from the requested input count. Index requests also reject two remotes for the same repository as ambiguous.
MCP exposes one canonical gitcontribute:// resource namespace. Historical
resource aliases are not advertised or routed. Tool names follow capability
boundaries (corpus, github, code, workspace, validation, workflow,
and research) rather than mirroring low-level API endpoints. Frequently
chained operations may be consolidated only when they share one authority and
one failure boundary; a read must never hide a refresh, write, or process run.
The CLI exposes the unified all catalog. Internal catalog projections are
used only by controlled tool-selection evaluations. The catalog keeps
code/workspace execution, external derived research, diagnostics, portfolio,
and advanced similarity as separately annotated capabilities rather than
separate user-configured profiles.
Feedback routing is intentional: use
github.index_pull_request_feedback -> jobs.get -> corpus.search_pull_request_feedback for repository-wide comment audits, and
github.sync_pull_request_feedback -> jobs.get when exact PR numbers are
already known. corpus.search_threads remains the generic offline text search
for issue or pull-request records; it is not the comment-level feedback
projection.
Tool inputs are strict and bounded, output distinguishes total population from returned/truncated items, and errors state how the caller can recover. MCP SDK annotations describe observable effects: pure external reads are read-only and idempotent with open-world access, while reads that also persist projections remain write operations. Catalog changes require realistic multi-call agent evaluations, including held-out queries, tool-call count, errors, latency, and context size; scripted schema checks alone do not establish good tool choice.
Recovery-plan actions are sealed variants. The concrete input type derives the
action discriminator, the output schema advertises the variants with oneOf,
and decoding rejects unknown, mismatched, or multiple payloads. Application
code therefore receives a typed action rather than validating a discriminator
against a nullable argument bag.
The canonical source-audit workflow is a machine-readable contract exposed by
workflow.get_source_audit_contract:
corpus.get_coverage -> typed exact/repository recovery -> jobs.get
-> corpus.get_threads/get_thread_facets -> duplicate checks
-> explicit github.sync_threads -> jobs.get
-> receipt attachment -> evidence/draft handoff
Each transition declares its required token, result type, allowed next
actions, retryability, side-effect authority, and incomplete semantics.
Post-job handoffs use a discriminated FollowUpAction; generic tool argument
bags and arguments reconstructed from prose are not part of the contract.
corpus.ensure_coverage is the explicit network-authorized recovery boundary.
It durably owns repository bootstrap, thread-header synchronization, selected
facet hydration, coverage verification, and immutable snapshot creation.
Ordinary coverage and corpus reads remain offline.
Reusable research reads use scoped CorpusSnapshotToken records rather than a
global revision alone. Tokens bind scope, observation watermark, source
manifest, derived versions, completeness, provenance, and an immutable payload
digest. Resolution fails closed with snapshot_unavailable or
snapshot_expired; current mutable projections are never substituted.
Read-only search, precedent, coverage, code-search, fix-pattern preview, and
research-brief responses also bind their query digest to an observation
watermark. Because these operations have no local-write capability, their
ephemeral: identities are explicitly non-durable and report completeness,
truncation, and unknown coverage inline. A caller that needs reuse across calls
must cross the explicit corpus.ensure_coverage write boundary and use the
resulting durable snapshot token.
Code indexes are separate immutable artifact records addressed as
gitcontribute://artifact/code-index/<artifact-digest>. Re-indexing an
unchanged source commit emits a new artifact identity and does not mutate old
resource payloads. See ADR 0005.
Explicit live acquisition follows one direction:
live GitHub request
-> adapter conversion with provider provenance
-> local observations and/or immutable digest-bound artifact
-> compact MCP result plus opaque resource URI
-> local resources/read
github.search_threads accepts a required nested repository reference and persists the returned issue or pull-request
observations and an exact github-thread-search.v1 result artifact. A search
page never advances repository-wide thread coverage and an empty page is not
proof that no matching live thread exists. github.read_source_files accepts
the same required nested repository reference, resolves
one named ref to a commit, reads bounded repository-relative files in input
order, and stores a source-bundle.v1 artifact. Commit SHA is the authoritative
revision; GitHub blob SHA remains a separate file identity. Source content is
untrusted text and is never merged into thread facets or code-index snapshots.
The two artifact resource families are
gitcontribute://artifact/github-thread-search/<digest> and
gitcontribute://artifact/source-bundle/<digest>. Their resource readers open
the local corpus only and verify the digest before decoding the typed payload.
The live operations have network-read/local-write annotations; the resource
reads are offline and immutable.
The GitHub adapter wraps go-github behind narrow read interfaces. For each
logical request, the retry transport invokes the rate-limited base transport:
go-github -> bounded retry loop -> rate limiter -> HTTP transport
Putting the limiter inside the retry loop means every actual attempt consumes rate capacity. Only replayable reads are retried. Backoff honors GitHub rate headers, is bounded, observes context cancellation, and redacts URL userinfo before retry metadata is persisted.
Repository identities are parsed at input, provider, and persistence
boundaries into a private, comparable domain.RepoRef. Interior code cannot
construct an owner without a repository name, carry whitespace, or bypass the
owner and repository grammar; it receives a parsed identity and uses explicit
accessors. The zero value is reserved for optional scope and must be tested
with IsValid. JSON decoding reparses the identity, and larger domain records
hold it in named fields so its codec cannot be promoted over the enclosing
record.
Pull-request merge knowledge is likewise a parsed domain.MergeStatus, not
independent merged, merged_known, and merged_at fields. Constructors make
unknown, observed-unmerged, and observed-merged outcomes explicit. SQLite and
GitHub adapter reads reject contradictions such as an unknown outcome marked
merged or an unmerged outcome with a merge timestamp; interior code cannot
create those combinations. The relational schema keeps scalar columns for
querying, but rows are reparsed before they enter application models.
Durable run and job lifecycles are read through private state values that bind statuses to their timestamps. Running work cannot be completed, queued jobs cannot already be started, terminal work requires a completion time, and only cancelled or cancellation-requested jobs carry a cancellation time. Terminal run transitions are conditional on the stored running state, while job transitions update the status and required timestamps atomically. Corrupt or unknown persisted combinations fail at the corpus boundary.
JSON inputs that express alternatives remain wire-compatible discriminated objects, but they are parsed before any durable job is submitted. Thread sync becomes either repository discovery with repository-only filters or an exact thread set. Portfolio sync becomes either authored discovery or an explicit pull-request set. Actor identity becomes either a canonical login or a node ID, and coverage becomes either a repository target or an exact typed thread. Workers receive these private variants rather than the original field bags, so mode-specific fields cannot be silently ignored and identity strings are canonicalized before duplicate detection. The normalized wire form, not the caller's mutable slices or pointers, is what the durable job records.
Acquisition and workspace packages invoke git directly with prompts, hooks,
global configuration, optional locks, and repository filesystem monitors
disabled where applicable. Crawling and indexing never run repository code.
Managed mirrors are keyed by repository identity and remote, and acquisition
uses both in-process and filesystem locks. Remote validation rejects embedded
credentials before metadata is written. Transient worktrees are checked for
cleanliness and removed after indexing. The indexer streams all selected blobs
through one long-lived git cat-file --batch process and prepares one SQLite
insert statement per snapshot transaction. A clean commit with a stored
current-format snapshot is reused without rereading blobs or replacing its
documents; the format version changes whenever indexing semantics change.
Validation is a different capability. It executes only after the caller passes the explicit execution flag and records the command, working directory, environment allowlist, timeout, output bound, and result. The MCP definition tool accepts managed workspace IDs rather than arbitrary host paths. The application resolves each ID and verifies that it belongs to the selected investigation before persisting executable state. The explicit CLI remains a local-user interface and may accept a directly supplied path.
Observation definitions cross command and MCP boundaries as untrusted specs. The application parses a complete base-and-candidate contract before it enters the evidence service. Parsed observations have a private representation: their source and artifact-path relationship is established once, default occurrence is normalized, and regular expressions are compiled once for execution. Persistence decodes through the same parser, so malformed stored contracts do not re-enter the trusted model. Execution therefore consumes parsed values and does not repeat structural validation or regular-expression compilation.
Durable workflow JSON is parsed again on read. Concern, investigation, hypothesis, opportunity, validation, and evidence discriminators cannot enter application logic as unchecked strings; legacy empty states are canonicalized only where their historical meaning is unambiguous. Telemetry metrics decode as either an available value or an unavailable reason and reject payloads claiming both. External validation receipts atomically store their synthetic definition and run, while external evidence manifests atomically store the complete claim set. A failed import therefore leaves no orphan definition or partial manifest.
Bulk local-metadata and collection inputs are fully parsed before writable corpus access. Collection references are stored in canonical repository, thread, or UUID form, and malformed later members cannot follow earlier writes. Thread projections similarly parse kind, lifecycle state, repository key, and number before a transaction begins and again when SQLite rows are read.
Search uses the local SQLite corpus and FTS5 indexes; agents query bounded
application tools rather than receiving raw database access. Repository ranking
weights owner/name at 10, topics at 5, and description at 2. Thread ranking
weights title at 10, labels at 5, body at 2, and complete hydrated facet evidence
at 0.5. Code ranking weights path at 5 and content at 1. README text remains
available through code search when indexed; it is not silently treated as
repository metadata because code coverage can be partial.
Scoped code search returns the selected snapshot manifest even when no document
matches, so absence can be separated from a missing or truncated index.
Snapshots created before manifests were introduced report
indexed_coverage_unknown; their zero skip counts are never presented as proof
of complete coverage.
Repository coverage uses collection membership to represent presence: a
returned domain.FacetCoverage is necessarily present, while a missing facet is
absent from the collection. Its private constructor binds the facet name,
observation time, completeness, and non-negative count. Immutable code-index
artifacts similarly use their digest-bound manifest as the sole in-memory
authority; duplicated query columns are checked against that manifest while
decoding and discarded rather than exposed as a second source of truth.
Title, labels, body, and hydrated evidence are materialized into one search document per thread and ranked by one BM25 invocation. Ranks from the legacy thread and facet indexes are never compared; the facet index is used only to identify the matching evidence source and excerpt. A thread page and its exact count share one read transaction. Counts use a lean FTS match set rather than recomputing ranking and excerpts, and the first-page no-overflow case derives its total directly from the returned rows.
Relevance is the default. Equal-ranked results use newest source revision as
the first tie-breaker. Repository and thread tools also expose sort=updated
for tasks that explicitly ask for the newest matching records. Search returns
bounded excerpts rather than complete thread bodies or files; exact-object
tools provide details after the agent narrows candidates. Thread search indexes
titles and bodies plus product-selected fields from complete hydrated issue
comments, pull-request reviews, review comments, and opt-in timeline events.
The searchable facet projection is replaced in the same transaction as its
observations; a failed, cancelled, incomplete, or stale refresh cannot expose a
partial search document. Transport pages are collapsed into one semantic facet
document, and matches report the source facet plus a bounded excerpt. Untrusted
discussion remains searchable data and cannot grant capabilities. Cursors
encode their query and scope so they cannot be reused for a different search.
Ordering always has a deterministic tie-breaker.
Hydrated search text is materialized once per complete facet replacement and
bounded to 262,144 characters per thread. Results expose
match_truncated=true when that bound omitted text; complete API coverage must
not be mistaken for complete search-text coverage.
FTS rank is retrieval evidence and must not be relabeled as a separately
hand-written score. Match explanations report the actual lower-is-better BM25
rank and the indexed document or hydrated facet that supplied the excerpt;
they do not guess token matches with a second string matcher. Freshness and
coverage are separate facts. Lens ranking
uses a bounded population and therefore does not support cursor pagination.
Contribution Radar similarly ranks a bounded open-issue population, separates
score from the explicit ready_to_code, needs_diagnosis,
needs_coordination, and blocked eligibility states, and reports positive
signals, risks, blockers, and unknown evidence. One repository run accepts and
can return up to the same 500-issue population that it evaluates. Eligibility
derives only from stored policy, labels, discussion, ownership, collision, and
coverage facts.
The cross-repository MCP form requires an explicit non-empty repository set
and reports the evaluated total and truncated. Per-repo
summaries distinguish considered, returned, per-repo truncation, and an
internally capped population. It deliberately does not expose a cursor: callers
raise the bounded limit or narrow the repository set instead of treating a
newly derived ranking as a stable result snapshot.
Radar normalizes PR text, authoritative closing-issue facets, issue/comment
references, opt-in timeline cross-references, and duplicate projections into
bounded related_work facts with exact source evidence. Quoted and code-fenced
text is excluded from lexical relationship classification. Only an open PR
with a closing relationship is an implementation blocker; open dependencies
and non-closing PR relationships require coordination.
Missing coverage is never silently converted into a negative score, but it
prevents a ready-to-code claim. Health metrics, dossier generation, and thread
research briefs also operate only on stored facts and report partial or missing
coverage when required facets are incomplete. A research-brief section must
carry a source reference or an explicit unknown reason; untrusted thread text
remains data and
cannot grant an adapter additional authority.
Seed extraction is also a strict offline read. It labels explicitly merged pull
requests as positive examples and explicitly closed-unmerged pull requests as
negative constraints. A closed issue is negative only when GitHub records
not_planned or it carries a predefined rejection/supersession label; all
other issues are context, not outcome evidence. The default seed view includes
positive and negative evidence, while contextual issues require an explicit
polarity selection. Repository-controlled titles and bodies may add excerpts
to evidence but cannot determine polarity.
Starting an investigation from a thread is an explicit corpus-write capability. The investigation and seed hypothesis are committed in one transaction and carry the exact thread observation ID, source timestamp, and observation sequence used as their baseline. A partial unique origin key returns the existing open pair on repeated or concurrent requests; later thread projections do not rewrite that baseline, and a closed investigation releases the origin for a deliberate new start.
Evidence freshness is a read-time assessment over stored observations, not
another persisted evidence relation. Source-backed evidence can record the
repository, thread, facet, or guidance revision it used; readers compare those
recorded revisions with the current winning local projections and return
fresh, stale, unknown, or not_applicable. Freshness reads must not
perform network access, execute processes, delete evidence, or silently treat
stale evidence as invalid. Tracking exports require schema version 2 so
evidence provenance is always explicit; unversioned and other schema versions
are rejected before import writes begin.
Repo-local concerns are durable intake records for findings that are not yet
contribution hypotheses. They are distinct from precedent Seed records,
which are read-only examples extracted from stored threads. Concerns use UUID
identities, explicit lifecycle transitions, bounded FTS5 search, typed
relationships, and source revisions evaluated by the existing evidence
freshness reader.
Creating, listing, searching, updating, or linking a concern is corpus-only:
it performs no GitHub access, worktree read, or process execution. Similarity
and duplicate candidates remain typed links and never become a root-cause
claim. Only the promotion operation may set promoted; it atomically creates
the investigation and seed hypothesis, optionally creates an opportunity, and
stores the downstream IDs on the concern. A status-only update cannot create a
partially promoted record.
Concern protocol results expose opaque workspace and evidence IDs but omit
absolute paths and source-reference URLs. Local provenance retains exact source
revisions so fresh, stale, and unknown are derived at read time rather
than persisted as mutable truth.
Contribution readiness is also a pure corpus-read capability. It re-evaluates a
versioned rule set for one opportunity and returns deterministic checks with
pass, warn, block, or unknown status, evidence references, and
remediation text. Only objectively unsubmitable local states, such as an
archived repository, closed target thread, failing candidate validation, or
unresolved contradicting evidence, should block. Missing guidance, missing
coverage, stale evidence, or incomplete validation usually remain warn or
unknown. Readiness must not fetch GitHub, execute validation, mutate
opportunities, or inspect repository-controlled code while evaluating a gate.
MCP exposes the same report as an offline tool/resource and adds workflow
prompts that point agents at local resources first. Those prompts must preserve
the same side-effect boundary: they may suggest explicit tools, but they cannot
authorize network reads, local writes, process execution, or GitHub mutation.
Contribution evidence manifests are explicit local-write exports. They use an in-toto Statement v1 envelope around a product-owned predicate and bind claims to repository, opportunity, and optional managed-workspace identities. The workspace identity covers HEAD, staged and unstaged patches, bounded untracked content, submodules, and commit metadata; every omitted resource becomes an explicit gap. Candidate validations are usable only when their pre/post snapshot is complete, unchanged, and equal to the exported candidate snapshot. Stored GitHub facets and evidence are evaluated independently, so missing, stale, or unknown data cannot become a passing claim.
Manifest generation never refreshes GitHub. Callers explicitly sync the exact repository or pull-request facets first, then export from SQLite and optional non-mutating Git reads. The content ID excludes generation/evaluation clocks, is recomputed before persistence, and rejects mismatched subjects or payloads. Drafts may store a manifest ID as structured metadata; renderers do not copy manifest claims into public prose.
Migrations are embedded from internal/corpus/migrations and applied by Goose,
but an ordinary corpus read never applies them. Read-only opens inspect the
schema and return a typed migration-required or newer-schema error. Existing
corpora advance only through the explicit corpus migrate capability, which
plans pending versions, creates a verified online backup by default, reports
step boundaries, and holds an exclusive cross-process corpus lease. Normal
readers hold shared leases; incompatible work fails fast instead of waiting on
SQLite until an opaque deadline.
Schema history starts from the canonical 001_initial.sql baseline. Every
supported corpus carries the canonical lineage identifier in SQLite's
application_id; numeric migration versions are never used to infer lineage.
Unmarked corpora and corpora from another lineage remain untouched and cannot
be migrated in place, even when their numeric version happens to match. Use a
matching release, or archive or move the incompatible corpus out of the
configured database path before setup creates a corpus on the canonical
baseline. Future schema changes may append migrations within this identified
lineage.
setup may create a missing empty corpus. It must not migrate an existing
corpus or rebuild derived indexes as a side effect of installing a launcher or
coding-agent integration. Versioned private runtimes are staged independently;
client registration is activated only after the configured corpus is usable.
Add migrations in numeric order; never edit an applied migration to change an existing database. Every migration needs a working Down section unless SQLite cannot represent the reversal safely, in which case the reason belongs in the migration and an ADR. Large data migrations need prerequisite indexes and a regression plan based on representative row counts.
Durable observations and derived projections have separate lifecycles. Search and clustering indexes carry product-owned rule versions and status. A read may report a stale or unavailable projection; rebuilding is an explicit local-write operation and never triggers GitHub access.
Corpus inventory is also read-only. The corpus-wide view performs grouped aggregation and returns at most one summary per repository or code-index scope; it does not load observation payloads or code documents into the application. Database and WAL bytes remain whole-file measurements because SQLite pages are shared. Logical observation-payload and code-content bytes are reported separately rather than attributed to individual database pages.
Semantic commit preparation is a two-step local read. First,
workspace.inspect_commit_changes asks Git for a binary/full-index patch and
untracked blob identities, then uses the maintained sourcegraph/go-diff
parser to expose stable file and hunk units. Second, the agent supplies semantic
grouping judgment to workspace.plan_semantic_commits; deterministic code
rejects stale inventories, unknown or duplicate unit assignments, and invalid
dependency graphs. Ambiguous units remain explicit. A verified reconstruction
record binds one-to-one unit coverage to the exact source patch and untracked
content identities. Neither operation stages files, applies patches, creates
commits, changes refs, executes repository code, or contacts GitHub. Applying a
plan is intentionally a separate future capability with an explicit mutation
boundary.
Storage changes should include tests for upgrade behavior, rollback when supported, stale-write rejection, transaction atomicity, and deterministic query ordering.