Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
9e7a44f
fix(parser): require explicit provider factories
mariusvniekerk Jun 24, 2026
a9082ac
refactor(parser): remove dead legacy DiscoverFunc/FindSourceFunc hooks
mariusvniekerk Jun 25, 2026
2936cdb
fix(parser): fall back to hermes transcripts when state.db lookup fails
mariusvniekerk Jun 25, 2026
87fe273
test(provider): harden provider lookup and source-pinning coverage
mariusvniekerk Jun 25, 2026
b48fdb2
fix(parser): align visual studio copilot source selection and refresh…
mariusvniekerk Jun 25, 2026
aaf8518
docs(provider): mark shadow-compare migration sequence as historical
mariusvniekerk Jun 25, 2026
d42101f
revert(sync): restore deliberate Kiro TotalSessions parity guard
mariusvniekerk Jun 25, 2026
996ff9b
style(parser): gofmt antigravity CLI version test call sites
mariusvniekerk Jun 26, 2026
59508f5
fix(parser,sync): generalize S3 session discovery through the provide…
mariusvniekerk Jun 26, 2026
044e6b3
refactor(parser): remove the folded DiscoverCodexSessions seam
mariusvniekerk Jun 26, 2026
d536054
refactor(parser): extract a general s3PrefixScan for the /<machine>/r…
mariusvniekerk Jun 26, 2026
b95b2bb
refactor(sync): route S3 sessions through provider.Parse
mariusvniekerk Jun 26, 2026
6f39ba0
fix(sync): correct S3 provider exclusions and incremental mtime cutoff
mariusvniekerk Jun 26, 2026
dc43d5f
docs(parser): reconcile the SourceRef.Opaque contract with S3 threading
mariusvniekerk Jun 26, 2026
7ef39de
refactor(parser): remove the dead shadow-compare migration mode
mariusvniekerk Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 80 additions & 13 deletions cmd/agentsview/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,10 +593,49 @@ func TestCollectWatchRootsHermesSessionsWatchesStateDBParent(t *testing.T) {
assert.Equal(t, []string{sessionsDir}, roots[1].dirs)
}

func TestCollectWatchRootsUsesProviderWatchPlan(t *testing.T) {
func TestCollectWatchRootsUsesCoworkProviderRecursiveRoot(t *testing.T) {
root := t.TempDir()
for _, dir := range []string{"brain", "conversations", "implicit"} {
require.NoError(t, os.Mkdir(filepath.Join(root, dir), 0o755), "mkdir %s", dir)
cfg := config.Config{
AgentDirs: map[parser.AgentType][]string{
parser.AgentCowork: {root},
},
}

roots, unwatchedDirs := collectWatchRoots(cfg)

require.Empty(t, unwatchedDirs, "cowork root should be watched directly")
got, ok := findCollectedWatchRoot(roots, root)
require.True(t, ok, "cowork provider WatchPlan root not collected")
assert.False(t, got.shallow,
"cowork provider recursive WatchPlan must override legacy ShallowWatch")
assert.Equal(t, []string{root}, got.dirs)
}

func TestCollectWatchRootsUsesGeminiProviderMetadataRoot(t *testing.T) {
root := t.TempDir()
tmpRoot := filepath.Join(root, "tmp")
require.NoError(t, os.Mkdir(tmpRoot, 0o755), "mkdir tmp")
cfg := config.Config{
AgentDirs: map[parser.AgentType][]string{
parser.AgentGemini: {root},
},
}

roots, unwatchedDirs := collectWatchRoots(cfg)

require.Empty(t, unwatchedDirs, "all gemini provider roots exist")
metadataRoot, ok := findCollectedWatchRoot(roots, root)
require.True(t, ok, "gemini provider metadata root not collected")
assert.True(t, metadataRoot.shallow)
tmp, ok := findCollectedWatchRoot(roots, tmpRoot)
require.True(t, ok, "gemini provider recursive tmp root not collected")
assert.False(t, tmp.shallow)
}

func TestCollectWatchRootsUsesAntigravityCLIHistoryRoot(t *testing.T) {
root := t.TempDir()
for _, subdir := range []string{"brain", "conversations", "implicit"} {
require.NoError(t, os.Mkdir(filepath.Join(root, subdir), 0o755))
}
cfg := config.Config{
AgentDirs: map[parser.AgentType][]string{
Expand All @@ -606,16 +645,44 @@ func TestCollectWatchRootsUsesProviderWatchPlan(t *testing.T) {

roots, unwatchedDirs := collectWatchRoots(cfg)

require.Empty(t, unwatchedDirs, "unwatched dirs before watcher setup")
require.Len(t, roots, 4)
assert.Equal(t, filepath.Join(root, "brain"), roots[0].root)
assert.False(t, roots[0].shallow)
assert.Equal(t, filepath.Join(root, "conversations"), roots[1].root)
assert.True(t, roots[1].shallow)
assert.Equal(t, root, roots[2].root)
assert.True(t, roots[2].shallow, "history.jsonl root should be watched shallowly")
assert.Equal(t, filepath.Join(root, "implicit"), roots[3].root)
assert.True(t, roots[3].shallow)
require.Empty(t, unwatchedDirs, "all antigravity cli provider roots exist")
historyRoot, ok := findCollectedWatchRoot(roots, root)
require.True(t, ok, "antigravity cli history.jsonl root not collected")
assert.True(t, historyRoot.shallow)
conversations, ok := findCollectedWatchRoot(
roots, filepath.Join(root, "conversations"),
)
require.True(t, ok, "antigravity cli conversations root not collected")
assert.True(t, conversations.shallow)
brain, ok := findCollectedWatchRoot(roots, filepath.Join(root, "brain"))
require.True(t, ok, "antigravity cli brain root not collected")
assert.False(t, brain.shallow)
}

func TestMissingWatchRootCoverageDoesNotTreatShallowAncestorAsRecursive(t *testing.T) {
root := filepath.Clean(filepath.Join(t.TempDir(), "state"))
shallowRoots := []watchRoot{{root: root, shallow: true}}
recursiveRoots := []watchRoot{{root: root, shallow: false}}

assert.True(t,
pathCoveredByAnyWatchRootCreation(filepath.Join(root, "sessions"), shallowRoots),
"shallow roots can observe immediate child creation")
assert.False(t,
pathCoveredByAnyWatchRootCreation(filepath.Join(root, "nested", "sessions"), shallowRoots),
"shallow ancestors must not be treated like recursive watches")
assert.True(t,
pathCoveredByAnyWatchRootCreation(filepath.Join(root, "nested", "sessions"), recursiveRoots),
"recursive roots cover nested missing roots")
}

func findCollectedWatchRoot(roots []watchRoot, path string) (watchRoot, bool) {
path = filepath.Clean(path)
for _, root := range roots {
if filepath.Clean(root.root) == path {
return root, true
}
}
return watchRoot{}, false
}

func TestResyncCoversSignals(t *testing.T) {
Expand Down
3 changes: 0 additions & 3 deletions cmd/agentsview/parse_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,6 @@ func parseDiffAgentSupported(def parser.AgentDef) bool {
if !def.FileBased {
return false
}
if def.DiscoverFunc != nil {
return true
}
switch parser.ProviderMigrationModes()[def.Type] {
case parser.ProviderMigrationProviderAuthoritative:
_, ok := parser.ProviderFactoryByType(def.Type)
Expand Down
22 changes: 22 additions & 0 deletions cmd/agentsview/parse_diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ func TestParseDiffAgentTypes(t *testing.T) {
}
}

func TestParseDiffSupportedAgentsIncludesProviderAuthoritativeAgents(t *testing.T) {
supported := parseDiffSupportedAgents()
modes := parser.ProviderMigrationModes()
// Build the expected set from the registry so the contract covers every
// current file-based, provider-authoritative agent and stays correct as
// the migration manifest changes, rather than a hand-maintained subset.
checked := 0
for _, def := range parser.Registry {
if !def.FileBased ||
modes[def.Type] != parser.ProviderMigrationProviderAuthoritative {
continue
}
checked++
assert.True(t, parseDiffAgentSupported(def),
"parse-diff support must include provider-authoritative %s", def.Type)
assert.Contains(t, supported, string(def.Type),
"parse-diff supported list must include %s", def.Type)
}
require.Positive(t, checked,
"expected at least one file-based provider-authoritative agent")
}

func TestParseDiff_EmptyArchiveRunsClean(t *testing.T) {
isolateParseDiffEnv(t)

Expand Down
23 changes: 7 additions & 16 deletions cmd/agentsview/token_use.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ const (
// multiple suffix matches exist without an exact row, the
// most recent wins and an ambiguity warning is emitted.
// 3. Canonical disk probe: when input begins with a registered
// agent prefix, strip the prefix and call that agent's
// FindSourceFunc so a truly canonical-but-unsynced ID on disk
// still resolves.
// 4. Raw disk probe: call every file-based agent's FindSourceFunc
// agent prefix, strip the prefix and ask that agent's disk source
// lookup so a truly canonical-but-unsynced ID on disk still resolves.
// 4. Raw disk probe: ask every file-based agent's disk source lookup
// with the raw input; the first hit yields "<prefix><input>".
// 5. No match anywhere: returned unchanged with known=false.
//
Expand Down Expand Up @@ -124,13 +123,9 @@ func resolveRawSessionID(
}

// agentHasDiskSourceLookup reports whether a session source can be located on
// disk by raw ID for the agent: via the legacy AgentDef FindSourceFunc hook, or
// via a provider-authoritative provider's FindSource for agents whose lookup was
// folded onto the provider (e.g. Codex).
// disk by raw ID for the agent, via its provider-authoritative provider's
// FindSource.
func agentHasDiskSourceLookup(def parser.AgentDef) bool {
if def.FindSourceFunc != nil {
return true
}
if parser.ProviderMigrationModes()[def.Type] !=
parser.ProviderMigrationProviderAuthoritative {
return false
Expand All @@ -140,13 +135,9 @@ func agentHasDiskSourceLookup(def parser.AgentDef) bool {
}

// findAgentSourceFile resolves a raw agent session ID to an on-disk source path
// under dir, using the legacy FindSourceFunc when present and otherwise the
// provider's FindSource (RawSessionID lookup). Returns "" when no source
// resolves or the agent has no on-disk lookup.
// under dir via the provider's FindSource (RawSessionID lookup). Returns ""
// when no source resolves or the agent has no on-disk lookup.
func findAgentSourceFile(def parser.AgentDef, dir, rawID string) string {
if def.FindSourceFunc != nil {
return def.FindSourceFunc(dir, rawID)
}
factory, ok := parser.ProviderFactoryByType(def.Type)
if !ok {
return ""
Expand Down
61 changes: 58 additions & 3 deletions cmd/agentsview/token_use_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,8 @@ func TestResolveSessionID_CanonicalCodexID_OnDiskNotInDB(t *testing.T) {
ctx := context.Background()

// Canonical "codex:<uuid>" not yet synced but present on
// disk must resolve via the canonical disk probe — which
// strips the prefix before calling FindSourceFunc (the
// underlying finder rejects colon-bearing IDs).
// disk must resolve via the canonical disk probe, which strips
// the prefix before asking the agent source lookup.
codexDir := filepath.Join(t.TempDir(), "codex-sessions")
uuid := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
dayDir := filepath.Join(codexDir, "2026", "04", "17")
Expand All @@ -249,6 +248,39 @@ func TestResolveSessionID_CanonicalCodexID_OnDiskNotInDB(t *testing.T) {
assert.True(t, known, "canonical disk probe")
}

func TestResolveSessionID_ProviderAuthoritativeCursorOnDiskNotInDB(t *testing.T) {
d := newTestDB(t)
ctx := context.Background()

cursorDir := t.TempDir()
rawID := "provider-cursor"
transcriptPath := filepath.Join(
cursorDir,
"Users-fiona-Documents-demo",
"agent-transcripts",
rawID+".jsonl",
)
require.NoError(t, os.MkdirAll(filepath.Dir(transcriptPath), 0o755))
require.NoError(t, os.WriteFile(
transcriptPath,
[]byte(`{"role":"user","content":"hi"}`+"\n"),
0o644,
))

agentDirs := map[parser.AgentType][]string{
parser.AgentCursor: {cursorDir},
}
got, known := resolveRawSessionID(ctx, d, agentDirs, rawID)
assert.Equal(t, "cursor:"+rawID, got,
"provider FindSource should resolve unsynced raw cursor IDs")
assert.True(t, known, "provider disk probe")

got, known = resolveRawSessionID(ctx, d, agentDirs, "cursor:"+rawID)
assert.Equal(t, "cursor:"+rawID, got,
"canonical provider ID should resolve via provider FindSource")
assert.True(t, known, "canonical provider disk probe")
}

func TestResolveSessionID_RawOpenClawCollidesWithCodexPrefix(t *testing.T) {
d := newTestDB(t)
ctx := context.Background()
Expand Down Expand Up @@ -291,6 +323,29 @@ func TestResolveSessionID_UnderscoreID_NoFalseMatch(t *testing.T) {
assert.True(t, known)
}

func TestAgentHasDiskSourceLookupIncludesProviderAuthoritativeAgents(t *testing.T) {
for _, agent := range []parser.AgentType{
parser.AgentGptme,
parser.AgentPi,
parser.AgentOMP,
parser.AgentWorkBuddy,
parser.AgentCortex,
parser.AgentKimi,
parser.AgentQwenPaw,
parser.AgentOpenHands,
parser.AgentCursor,
parser.AgentVibe,
parser.AgentClaude,
parser.AgentCowork,
parser.AgentHermes,
} {
def, ok := parser.AgentByType(agent)
require.True(t, ok, "agent %s", agent)
assert.True(t, agentHasDiskSourceLookup(def),
"token-use disk probe must include provider-authoritative %s", agent)
}
}

func TestUsageExitCode_TokenData(t *testing.T) {
u := &db.SessionUsage{HasTokenData: true}
assert.Equal(t, tokenUseExitOK, usageExitCode(u))
Expand Down
17 changes: 17 additions & 0 deletions docs/superpowers/plans/2026-06-20-provider-dual-run-harness.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Provider Dual-Run Harness Implementation Plan

> **Status:** Superseded for the `provider-explicit-registry` stack tip. This
> plan records the historical root-harness slice that was implemented on the
> lower `provider-facade-core` branch. Do not execute these steps against the
> final stack tip: both `ProviderMigrationLegacyOnly` and the transitional
> `ProviderMigrationShadowCompare` modes have been removed there (the
> `provider_shadow` runtime was deleted once every provider became
> authoritative, so a lingering `shadow-compare` mode would validate but run no
> comparison), concrete parse-capable providers are expected to be
> `provider-authoritative`, and Claude.ai / ChatGPT are `import-only`. The
> stack-tip legacy cleanup in kata issue `n489` is complete; remaining
> provider-facade tracking lives in the caller and provider-group tasks listed
> in the provider facade design spec.
>
> The `legacy-only` and `shadow-compare` modes described below are historical:
> the current `internal/parser/provider_migration.go` contract accepts only
> `provider-authoritative` and `import-only`, and rejects every other value.

> **For agentic workers:** REQUIRED SUB-SKILL: Use
> superpowers:subagent-driven-development (recommended) or
> superpowers:executing-plans to implement this plan task-by-task. Steps use
Expand Down
10 changes: 10 additions & 0 deletions docs/superpowers/specs/2026-06-19-provider-facade-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,16 @@ consumer uses providers.

## Migration Plan

> **Historical (lower-branch) sequence.** The dual-run / shadow-compare steps
> below describe how providers were migrated on the lower branches of the
> original stack. They are kept as a record of how the migration was carried
> out. They are **not** current guidance: `ProviderMigrationShadowCompare` and
> `legacy-only` no longer exist at the stack tip (see the note near the top of
> this document), so a future staged migration must add its own explicit,
> temporary comparison mode on the branch that needs it rather than opting a
> provider into `shadow-compare`. Read every "opt the provider into
> shadow-compare" instruction below as belonging to that retired flow.

The implementation should migrate all providers through a stacked dual-run
sequence:

Expand Down
4 changes: 4 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,13 @@ func TestDefault_IncludesCodexArchivedSessionsDir(t *testing.T) {
}

func TestDefault_SkipsAiderUntilConfigured(t *testing.T) {
t.Setenv("AIDER_DIR", "")
cfg, err := Default()
require.NoError(t, err)

// Aider has no safe default root: a passive viewer must not enumerate
// $HOME (macOS privacy prompts), so it stays unresolved until the user
// opts in via AIDER_DIR or aider_dirs.
assert.Empty(t, cfg.ResolveDirs(parser.AgentAider))
assert.False(t, cfg.IsUserConfigured(parser.AgentAider))
}
Expand Down
30 changes: 28 additions & 2 deletions internal/importer/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,20 @@ func ImportClaudeAI(
}
}()

err := parser.ParseClaudeAIExport(r, func(
provider, ok := parser.NewProvider(
parser.AgentClaudeAI, parser.ProviderConfig{},
)
if !ok {
return stats, fmt.Errorf("claude.ai provider unavailable")
}
exporter, ok := provider.(parser.ClaudeAIExportParser)
if !ok {
return stats, fmt.Errorf(
"claude.ai provider does not support exports",
)
}

err := exporter.ParseClaudeAIExport(r, func(
result parser.ParseResult,
) error {
if ctx.Err() != nil {
Expand Down Expand Up @@ -279,7 +292,20 @@ func ImportChatGPT(
assetsDir: assetsDir,
}

err := parser.ParseChatGPTExport(dir, resolver,
provider, ok := parser.NewProvider(
parser.AgentChatGPT, parser.ProviderConfig{},
)
if !ok {
return stats, fmt.Errorf("chatgpt provider unavailable")
}
exporter, ok := provider.(parser.ChatGPTExportParser)
if !ok {
return stats, fmt.Errorf(
"chatgpt provider does not support exports",
)
}

err := exporter.ParseChatGPTExport(dir, resolver,
func(result parser.ParseResult) error {
if ctx.Err() != nil {
return ctx.Err()
Expand Down
Loading