Skip to content

Commit 73d9f26

Browse files
feat(git): line-level AI provenance and per-commit model id from agent-trace (#866)
Extend the agent-trace channel from commit attribution to line granularity. AgentTraceIndex now builds a path-keyed line-share aggregate from each record's ranges[]: the count of distinct lines an AI/mixed contributor wrote (interval-union deduped across every record, so overlapping re-edits count once and a bogus end_line can't blow memory) plus a {model_id: line_count} breakdown. It is revision-independent and merged into git_metadata at the indexer's existing path-keyed merge point, so it also covers the rename-tracking walk. New columns: agent_line_count, agent_line_model_json. resolve() now also returns the winning record's dominant model_id (models.dev provider/model form), stored as git_commits.agent_model_id and set only when the agent_trace channel actually wins (a git-ai note or service identity can outrank it, and only the trace carries a model). The git indexer stores only the AI numerator; the "% AI-written" denominator is the file's current LOC, applied downstream, keeping ingestion LOC-decoupled. Migration 0038 adds the columns; a _WALK_FIELD_EMPTIES guard keeps a transient trace-read failure on an incremental update from zeroing a prior share.
1 parent 13035de commit 73d9f26

10 files changed

Lines changed: 546 additions & 68 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Line-level agent-trace provenance: model id + per-file AI line share.
2+
3+
Line-level extension of the agent-trace channel. Per-commit: the ``model_id``
4+
(models.dev ``provider/model`` form) resolved from the agent-trace record that
5+
attributed the commit — populated only for the ``agent_trace`` channel, NULL otherwise.
6+
Per-file: the count of distinct file lines an AI/mixed contributor wrote
7+
(interval-union deduped across all trace records for the path) plus a
8+
``{model_id: line_count}`` breakdown, so "N% of this file is AI-written" and an
9+
opus-vs-sonnet split can be derived downstream against the file's current LOC.
10+
11+
All columns are nullable / defaulted and back-populated on the next index. Data
12+
comes from ``.agent-trace/traces.jsonl`` only — repos without the standard pay
13+
nothing and keep NULL/zero throughout.
14+
15+
Revision ID: 0038
16+
Revises: 0037
17+
Create Date: 2026-07-16
18+
"""
19+
20+
from __future__ import annotations
21+
22+
from collections.abc import Sequence
23+
24+
import sqlalchemy as sa
25+
from alembic import op
26+
27+
# revision identifiers
28+
revision: str = "0038"
29+
down_revision: str | None = "0037"
30+
branch_labels: str | Sequence[str] | None = None
31+
depends_on: str | Sequence[str] | None = None
32+
33+
34+
def upgrade() -> None:
35+
op.add_column("git_commits", sa.Column("agent_model_id", sa.String(64), nullable=True))
36+
37+
op.add_column(
38+
"git_metadata",
39+
sa.Column("agent_line_count", sa.Integer, nullable=False, server_default="0"),
40+
)
41+
op.add_column(
42+
"git_metadata",
43+
sa.Column("agent_line_model_json", sa.Text, nullable=False, server_default="{}"),
44+
)
45+
46+
47+
def downgrade() -> None:
48+
op.drop_column("git_metadata", "agent_line_model_json")
49+
op.drop_column("git_metadata", "agent_line_count")
50+
op.drop_column("git_commits", "agent_model_id")

packages/core/src/repowise/core/ingestion/git_commit_index.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def load_commit_index(
8383
commit_sink: list[dict] | None = None,
8484
since_ts: int | None = None,
8585
provenance_classifier: object | None = None,
86+
trace_index: object | None = None,
8687
) -> dict[str, list[_CommitRec]]:
8788
"""Bucket every commit in the recent history by the files it touched.
8889
@@ -154,7 +155,10 @@ def load_commit_index(
154155
# git-ai authorship notes for this window (``{}`` unless the repo uses them).
155156
note_agents = load_git_ai_note_agents(repo, commit_limit)
156157
# Agent-trace records (empty after one stat call unless the repo has them).
157-
trace_index = AgentTraceIndex.load(repo)
158+
# The orchestrator may pass a shared instance so the trace file is read once
159+
# per index rather than once per walk.
160+
if trace_index is None:
161+
trace_index = AgentTraceIndex.load(repo)
158162

159163
bucket: dict[str, list[_CommitRec]] = {}
160164
commits_parsed = 0
@@ -262,6 +266,12 @@ def load_commit_index(
262266
"agent_autonomy_tier": prov.autonomy_tier,
263267
"agent_channel": prov.channel,
264268
"agent_confidence": prov.confidence,
269+
# Model id only when the trace channel actually won: a note
270+
# or service-identity match can outrank the trace above, and
271+
# only the trace record carries a model.
272+
"agent_model_id": (
273+
trace_hit[2] if (trace_hit and prov.channel == "agent_trace") else None
274+
),
265275
}
266276
)
267277

@@ -282,6 +292,7 @@ def load_deep_commit_index(
282292
skip: int,
283293
deep_limit: int,
284294
provenance_classifier: object | None = None,
295+
trace_index: object | None = None,
285296
) -> dict[str, list[_CommitRec]]:
286297
"""Bucket commits OLDER than the recent window for *wanted_files* only.
287298
@@ -343,7 +354,9 @@ def load_deep_commit_index(
343354
# git-ai notes across the deep region (``{}`` unless the repo uses them).
344355
note_agents = load_git_ai_note_agents(repo, skip + deep_limit)
345356
# Agent-trace records (empty after one stat call unless the repo has them).
346-
trace_index = AgentTraceIndex.load(repo)
357+
# Reuse the orchestrator's shared instance when supplied (single file read).
358+
if trace_index is None:
359+
trace_index = AgentTraceIndex.load(repo)
347360

348361
bucket: dict[str, list[_CommitRec]] = {}
349362
commits_parsed = 0

packages/core/src/repowise/core/ingestion/git_indexer/README.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,19 @@ Agent-trace records name a `vcs.revision` captured at edit or commit time, so
114114
`AgentTraceIndex` attributes a record to a commit when the revision matches
115115
the commit itself (confidence `high`) or one of its parents (the traced change
116116
landed in the child; confidence `medium`), and only when the record's file
117-
set overlaps the commit's changed paths.
117+
set overlaps the commit's changed paths. The winning record's dominant
118+
`model_id` (models.dev `provider/model` form) rides through to the commit for
119+
an opus-vs-sonnet split.
120+
121+
`AgentTraceIndex` also produces a **per-file line share** from each record's
122+
`ranges[]`: the count of distinct lines an AI/mixed contributor wrote
123+
(interval-union deduped across every record for the path) plus a
124+
`{model_id: line_count}` breakdown. This is revision-independent (a record
125+
without `vcs.revision` still counts lines) and path-keyed, so the orchestrator
126+
merges it into `git_metadata` alongside the co-change / prior-defect signals
127+
and it works on the rename-tracking walk too. The `"N% AI-written"` denominator
128+
is the file's current LOC, applied downstream — the indexer stores only the AI
129+
numerator so it stays LOC-decoupled.
118130

119131
Design rules:
120132

@@ -134,11 +146,12 @@ Design rules:
134146
(`service_emails`, `footer_patterns`, `coauthor_patterns`) — additive on top
135147
of the built-ins, malformed entries skipped with a warning.
136148

137-
Persisted as four nullable columns on `git_commits`
138-
(`agent_name`/`agent_autonomy_tier`/`agent_channel`/`agent_confidence`) and a
139-
per-file rollup on `git_metadata` (`agent_commit_count`, `agent_authored_pct`,
140-
`agent_tier_counts_json`), surfaced in the `/git-metadata` file view and the
141-
MCP `get_context` ownership block.
149+
Persisted as five nullable columns on `git_commits`
150+
(`agent_name`/`agent_autonomy_tier`/`agent_channel`/`agent_confidence`/`agent_model_id`)
151+
and a per-file rollup on `git_metadata` (`agent_commit_count`,
152+
`agent_authored_pct`, `agent_tier_counts_json` at the commit level;
153+
`agent_line_count`, `agent_line_model_json` at the line level), surfaced in the
154+
`/git-metadata` file view and the MCP `get_context` ownership block.
142155

143156
## Internal layout
144157

0 commit comments

Comments
 (0)