Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
claude/adoring-hopper-5x74bqclaude/affectionate-feynman-ykrf1hclaude/architecture-audit-design-wxprenclaude/build-status-update-3MXsfclaude/charming-meitner-mllb5rclaude/compare-gate-gluecron-s4mFQclaude/confident-faraday-tikcwbclaude/continue-work-XMTlIclaude/crontech-gluecron-deploy-7MIECclaude/crontech-platform-setup-SeKfwclaude/design-2026claude/ecstatic-ptolemy-jMdigclaude/enhance-github-integration-QNHdGclaude/fix-aa-loop-issue-PonMQclaude/fix-actions-and-processclaude/fix-desktop-errors-XqoW8claude/fix-red-workflowsclaude/fix-website-access-6FKJNclaude/gatetest-integration-hardeningclaude/github-audit-improvements-bDFr9claude/gluecron-launch-status-FoMRlclaude/hopeful-lamport-olfCTclaude/issue-to-pr-and-protectionsclaude/jolly-heisenberg-2sg1Qclaude/launch-preparation-QmTb6claude/new-session-xk1l7claude/plan-platform-architecture-kkN4yclaude/platform-analysis-roadmap-1nUGLclaude/platform-launch-assessment-8dWV8claude/polish-platform-release-AeDrUclaude/resume-previous-work-KzyLwclaude/review-crontech-handoff-qYEVqclaude/review-project-completeness-lHhS2claude/review-readme-docs-ulqPKclaude/serene-edison-rj87weclaude/setup-multi-repo-dev-BCwNQclaude/ship-fixes-and-tests-Jvz1cclaude/site-audit-competitive-pctlwgclaude/site-migration-vercel-XstpKclaude/standalone-product-repos-XHFTDcopilot/feat-smart-empty-states-keyboard-first-enhancementcopilot/feat-smart-morning-digest-review-context-restorecopilot/fix-and-process-workflowscopilot/update-ai-powered-code-reviewfeat/debt-mapfeat/push-policy-codeowners-hardeningfeat/smart-digest-contextfeat/stage-impactfeat/t1-secret-migrationfeat/u-polishfeat/w-self-hostfeat/w2-claude-configfix/agent-journey-orphan-sweepfix/audit-sweep-2026-07-26gatetest/auto-fix-1776586424172gatetest/auto-fix-1776586534814gatetest/auto-fix-1776590685143gatetest/auto-fix-1776590808199mainops/redeploy-retriggerstyle/dxt-cta-themeworktree-agent-a3377aad30d55da26worktree-agent-a7ef607b7ee1d6c74
index-existing-repo.test.ts6.2 KB · 161 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * Repositories that arrive without a push must still be indexed.
 *
 * indexChangedFiles() is called from exactly ONE place — the git post-receive
 * hook. That is correct for pushes and wrong for every other way a repository
 * comes into existence:
 *
 *   - bulk import CLONES (git clone fires no post-receive hook)
 *   - POST /new + first-repo-scaffold writes via the git plumbing API
 *   - forks copy the bare repo directly
 *
 * So both onboarding paths built in this session produced repositories where
 * gluecron_semantic_search, repo chat and "ask this repo" returned nothing.
 * Silently — an empty index is indistinguishable from "no matches", which is
 * why it survived. It was found while wiring the agent-first path, which
 * depends entirely on that index being populated.
 *
 * Deps are injected rather than mock.module()'d: ../git/repository is imported
 * by dozens of test files and a module mock leaks across the whole run.
 */

import { describe, expect, it } from "bun:test";
import { readFileSync } from "fs";

const SRC = readFileSync("src/lib/index-existing-repo.ts", "utf8");

type Entry = { name: string; type: string; path?: string };

function treeFrom(map: Record<string, Entry[]>) {
  return async (_o: string, _r: string, _ref: string, path?: string) =>
    map[path ?? ""] ?? [];
}

describe("tree walking", () => {
  it("collects blobs across nested directories", async () => {
    const captured: string[][] = [];
    const { indexExistingRepo } = await import("../lib/index-existing-repo");
    const r = await indexExistingRepo(
      { repositoryId: "r1", owner: "a", repoName: "b", ref: "main", commitSha: "sha" },
      {
        getTree: treeFrom({
          "": [
            { name: "index.ts", type: "blob", path: "index.ts" },
            { name: "src", type: "tree", path: "src" },
          ],
          src: [{ name: "app.ts", type: "blob", path: "src/app.ts" }],
        }),
        indexChangedFiles: async (a) => {
          captured.push(a.changedPaths);
          return { indexed: a.changedPaths.length, skipped: 0, model: "test" };
        },
      }
    );
    expect(r.indexed).toBe(2);
    expect(captured[0].sort()).toEqual(["index.ts", "src/app.ts"]);
  });

  it("skips vendor and build directories", async () => {
    let seen: string[] = [];
    const { indexExistingRepo } = await import("../lib/index-existing-repo");
    await indexExistingRepo(
      { repositoryId: "r1", owner: "a", repoName: "b", ref: "main", commitSha: "sha" },
      {
        getTree: treeFrom({
          "": [
            { name: "node_modules", type: "tree", path: "node_modules" },
            { name: "dist", type: "tree", path: "dist" },
            { name: "keep.ts", type: "blob", path: "keep.ts" },
          ],
          node_modules: [{ name: "junk.js", type: "blob", path: "node_modules/junk.js" }],
          dist: [{ name: "out.js", type: "blob", path: "dist/out.js" }],
        }),
        indexChangedFiles: async (a) => {
          seen = a.changedPaths;
          return { indexed: a.changedPaths.length, skipped: 0, model: "test" };
        },
      }
    );
    expect(seen).toEqual(["keep.ts"]);
  });

  it("delegates filtering to indexChangedFiles rather than duplicating it", () => {
    // isCodeFile, the per-push cap and the embedding model all live in
    // semantic-index.ts. Re-implementing them here would let scaffolded and
    // pushed repos drift apart in what they index.
    //
    // Comments stripped: the source explains this delegation in prose that
    // names the very symbol being asserted absent.
    const code = SRC.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
    expect(code).toContain("indexChangedFiles");
    expect(code).not.toContain("isCodeFile");
  });
});

describe("fault isolation", () => {
  it("never throws when the tree read fails", async () => {
    const { indexExistingRepo } = await import("../lib/index-existing-repo");
    const r = await indexExistingRepo(
      { repositoryId: "r1", owner: "a", repoName: "b", ref: "main", commitSha: "sha" },
      {
        getTree: async () => {
          throw new Error("disk on fire");
        },
        indexChangedFiles: async () => ({ indexed: 0, skipped: 0, model: "test" }),
      }
    );
    // A single unreadable directory must not abandon the index, and an
    // unindexed repo must never fail an import or a repo creation.
    expect(r.indexed).toBe(0);
    expect(r.error).toBeUndefined();
  });

  it("reports an embedding failure instead of throwing", async () => {
    const { indexExistingRepo } = await import("../lib/index-existing-repo");
    const r = await indexExistingRepo(
      { repositoryId: "r1", owner: "a", repoName: "b", ref: "main", commitSha: "sha" },
      {
        getTree: treeFrom({ "": [{ name: "a.ts", type: "blob", path: "a.ts" }] }),
        indexChangedFiles: async () => {
          throw new Error("embedder down");
        },
      }
    );
    expect(r.indexed).toBe(0);
    expect(r.error).toContain("embedder down");
  });

  it("returns zero for an empty repository without calling the embedder", async () => {
    let called = false;
    const { indexExistingRepo } = await import("../lib/index-existing-repo");
    const r = await indexExistingRepo(
      { repositoryId: "r1", owner: "a", repoName: "b", ref: "main", commitSha: "sha" },
      {
        getTree: treeFrom({}),
        indexChangedFiles: async () => {
          called = true;
          return { indexed: 0, skipped: 0, model: "test" };
        },
      }
    );
    expect(r.indexed).toBe(0);
    expect(called).toBe(false);
  });
});

describe("both onboarding paths index", () => {
  it("bulk import indexes each repo", () => {
    const mig = readFileSync("src/lib/migration-onboarding.ts", "utf8");
    expect(mig).toContain("indexExistingRepo");
    expect(mig).toContain("filesIndexed");
  });

  it("new-repo scaffold indexes after committing", () => {
    const scaffold = readFileSync("src/lib/first-repo-scaffold.ts", "utf8");
    expect(scaffold).toContain("indexExistingRepo");
    // Must come after the commits — there is nothing to index before them.
    expect(scaffold.indexOf("createOrUpdateFileOnBranch")).toBeLessThan(
      scaffold.lastIndexOf("indexExistingRepo")
    );
  });
});