diff --git a/bin/gstack-memory-ingest.ts b/bin/gstack-memory-ingest.ts index 653d4069ae..1e4e4e4cfa 100644 --- a/bin/gstack-memory-ingest.ts +++ b/bin/gstack-memory-ingest.ts @@ -744,7 +744,10 @@ function buildTranscriptPage(path: string, session: ParsedSession): PageRecord { source_path: path, session_id: session.session_id, cwd: session.cwd, - git_remote: remote, + // Apply the same "_unattributed" fallback the frontmatter and repoSlug use. + // The unattributed skip guard reads this field, so leaving the raw "" here + // makes `--include-unattributed` inert (`"" === "_unattributed"` is never true). + git_remote: remote || "_unattributed", start_time: session.start_time, end_time: session.end_time, partial: session.partial, diff --git a/test/gstack-memory-ingest.test.ts b/test/gstack-memory-ingest.test.ts index fef9070c44..7709671ba0 100644 --- a/test/gstack-memory-ingest.test.ts +++ b/test/gstack-memory-ingest.test.ts @@ -296,6 +296,64 @@ describe("internal: parseTranscriptJsonl + buildTranscriptPage shape", () => { }); }); +// ── unattributed skip policy ─────────────────────────────────────────────── + +describe("gstack-memory-ingest unattributed skip", () => { + function sessionIn(cwd: string): string { + return ( + `{"type":"user","message":{"role":"user","content":"hi"},"timestamp":"2026-05-01T00:00:00Z","cwd":"${cwd}"}\n` + + `{"type":"assistant","message":{"role":"assistant","content":"hello"},"timestamp":"2026-05-01T00:00:01Z"}\n` + ); + } + + it("skips a session whose cwd is not a git repo", () => { + const home = makeTestHome(); + const work = join(home, "work"); + mkdirSync(work, { recursive: true }); + writeClaudeCodeSession(home, "not-a-repo", "sess1", sessionIn(work)); + + const r = runScript(["--bulk", "--no-write"], { HOME: home, GSTACK_HOME: join(home, ".gstack") }); + expect(r.exitCode).toBe(0); + expect(r.stdout).toContain("written: 0"); + expect(r.stdout).toContain("skipped (unattrib): 1"); + + rmSync(home, { recursive: true, force: true }); + }); + + it("imports that same session when --include-unattributed is passed", () => { + const home = makeTestHome(); + const work = join(home, "work"); + mkdirSync(work, { recursive: true }); + writeClaudeCodeSession(home, "not-a-repo", "sess1", sessionIn(work)); + + const r = runScript(["--bulk", "--no-write", "--include-unattributed"], { + HOME: home, + GSTACK_HOME: join(home, ".gstack"), + }); + expect(r.exitCode).toBe(0); + expect(r.stdout).toContain("written: 1"); + expect(r.stdout).toContain("skipped (unattrib): 0"); + + rmSync(home, { recursive: true, force: true }); + }); + + it("still imports a session whose cwd does resolve to a git remote", () => { + const home = makeTestHome(); + const repo = join(home, "repo"); + mkdirSync(repo, { recursive: true }); + spawnSync("git", ["-C", repo, "init", "-q"]); + spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/foo/bar.git"]); + writeClaudeCodeSession(home, "real", "sess2", sessionIn(repo)); + + const r = runScript(["--bulk", "--no-write"], { HOME: home, GSTACK_HOME: join(home, ".gstack") }); + expect(r.exitCode).toBe(0); + expect(r.stdout).toContain("written: 1"); + expect(r.stdout).toContain("skipped (unattrib): 0"); + + rmSync(home, { recursive: true, force: true }); + }); +}); + // ── --limit shortcut for smoke tests ─────────────────────────────────────── describe("gstack-memory-ingest --limit", () => {