@thallylabs/mcp 0.10.21 → 0.10.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/track.d.ts CHANGED
@@ -1,10 +1,16 @@
1
+ /** The legacy docs-agent branch namespace. Retained for API compatibility. */
2
+ declare const AGENT_BRANCH_PREFIX = "thally/agent-";
1
3
  /**
2
- * The branch prefix the docs agent stamps on its own PRs (`run.ts` creates
3
- * `thally/agent-<base36>`). Track's loop guard ignores PRs from these branches so a
4
- * self-tracking repo never chases its own agent PRs. Single source of truth for
5
- * the webhook relay, the scaffolded Actions workflow, and the producer.
4
+ * Every branch namespace authored by a Track-controlled writer.
5
+ *
6
+ * Intake must ignore all of these namespaces. Otherwise a self-tracking docs
7
+ * repository can treat Track's own documentation pull request as a new product
8
+ * change and recursively create another run. Keep this list shared by Cloud,
9
+ * generated sender workflows, and any future Track producer.
6
10
  */
7
- declare const AGENT_BRANCH_PREFIX = "thally/agent-";
11
+ declare const TRACK_OWNED_BRANCH_PREFIXES: readonly ["thally/agent-", "thally/track-vnext/"];
12
+ /** Return whether a branch or full `refs/heads/` ref belongs to Track. */
13
+ declare function isTrackOwnedBranch(ref: string | null | undefined): boolean;
8
14
  /** Label that turns an OPEN PR into a preview-docs request (shared by the
9
15
  * webhook relay and the scaffolded sender workflow). */
10
16
  declare const DOCS_PREVIEW_LABEL = "docs-preview";
@@ -123,11 +129,11 @@ declare const TRACK_CONTEXT_CHAR_CAP = 20000;
123
129
  * The instruction frames the agent's actual job: judge what user-facing
124
130
  * behavior the merged PR changed and update the docs that describe it.
125
131
  */
126
- declare function buildTrackInstruction(repo: TrackedRepoLike, pr: Pick<PullRequestInfo, 'number'>, options?: {
132
+ declare function buildTrackInstruction(repo: TrackedRepoLike, pr: Pick<PullRequestInfo, "number">, options?: {
127
133
  preview?: boolean;
128
134
  }): string;
129
135
  /** The capped markdown context for a merged tracked PR (title, body, file diffs). */
130
- declare function buildTrackContext(repo: Pick<TrackedRepoLike, 'owner' | 'repo'>, pr: PullRequestInfo, files: Array<PrFile>): string;
136
+ declare function buildTrackContext(repo: Pick<TrackedRepoLike, "owner" | "repo">, pr: PullRequestInfo, files: Array<PrFile>): string;
131
137
  /**
132
138
  * Distill a merged PR into the docs task the agent runs: a one-line instruction
133
139
  * (output routing) and a capped markdown context (PR description + diff).
@@ -137,4 +143,4 @@ declare function buildTrackTask(repo: TrackedRepoLike, pr: PullRequestInfo, file
137
143
  context: string;
138
144
  };
139
145
 
140
- export { AGENT_BRANCH_PREFIX, DOCS_PREVIEW_LABEL, type GithubAppCreds, type GithubFetchOptions, type OwnerRepoRef, type PrFile, type PullRequestInfo, TRACK_CONTEXT_CHAR_CAP, type TrackedRepoLike, buildTrackContext, buildTrackInstruction, buildTrackTask, createAppJwt, fetchLatestMergedPr, fetchPullRequest, fetchPullRequestFiles, filterFilesByGlobs, matchesGlob, mintInstallationToken, parseOwnerRepo, resolveGithubToken, verifyInstallationBelongsToApp };
146
+ export { AGENT_BRANCH_PREFIX, DOCS_PREVIEW_LABEL, type GithubAppCreds, type GithubFetchOptions, type OwnerRepoRef, type PrFile, type PullRequestInfo, TRACK_CONTEXT_CHAR_CAP, TRACK_OWNED_BRANCH_PREFIXES, type TrackedRepoLike, buildTrackContext, buildTrackInstruction, buildTrackTask, createAppJwt, fetchLatestMergedPr, fetchPullRequest, fetchPullRequestFiles, filterFilesByGlobs, isTrackOwnedBranch, matchesGlob, mintInstallationToken, parseOwnerRepo, resolveGithubToken, verifyInstallationBelongsToApp };
package/dist/track.js CHANGED
@@ -1,16 +1,38 @@
1
1
  // src/lib/track.ts
2
2
  import { createSign, createHash } from "crypto";
3
3
  var AGENT_BRANCH_PREFIX = "thally/agent-";
4
+ var TRACK_OWNED_BRANCH_PREFIXES = [
5
+ AGENT_BRANCH_PREFIX,
6
+ "thally/track-vnext/"
7
+ ];
8
+ function isTrackOwnedBranch(ref) {
9
+ if (typeof ref !== "string") return false;
10
+ const branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref;
11
+ return TRACK_OWNED_BRANCH_PREFIXES.some(
12
+ (prefix) => branch.startsWith(prefix)
13
+ );
14
+ }
4
15
  var DOCS_PREVIEW_LABEL = "docs-preview";
5
16
  function parseOwnerRepo(spec) {
6
17
  const trimmed = spec.trim();
7
18
  const url = trimmed.match(
8
19
  /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s#?]+?)(?:\.git)?(?:\/pull\/(\d+))?(?:[/#?].*)?$/i
9
20
  );
10
- if (url) return { owner: url[1], repo: url[2], ...url[3] ? { pr: Number(url[3]) } : {} };
11
- const plain = trimmed.match(/^([A-Za-z0-9-_.]+)\/([A-Za-z0-9-_.]+?)(?:#(\d+))?$/);
21
+ if (url)
22
+ return {
23
+ owner: url[1],
24
+ repo: url[2],
25
+ ...url[3] ? { pr: Number(url[3]) } : {}
26
+ };
27
+ const plain = trimmed.match(
28
+ /^([A-Za-z0-9-_.]+)\/([A-Za-z0-9-_.]+?)(?:#(\d+))?$/
29
+ );
12
30
  if (!plain) return null;
13
- return { owner: plain[1], repo: plain[2], ...plain[3] ? { pr: Number(plain[3]) } : {} };
31
+ return {
32
+ owner: plain[1],
33
+ repo: plain[2],
34
+ ...plain[3] ? { pr: Number(plain[3]) } : {}
35
+ };
14
36
  }
15
37
  function base64url(input) {
16
38
  return Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -19,8 +41,12 @@ var installationTokenCache = /* @__PURE__ */ new Map();
19
41
  function createAppJwt(appId, privateKey) {
20
42
  const now = Math.floor(Date.now() / 1e3);
21
43
  const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
22
- const payload = base64url(JSON.stringify({ iat: now - 60, exp: now + 9 * 60, iss: String(appId) }));
23
- const signature = base64url(createSign("RSA-SHA256").update(`${header}.${payload}`).sign(privateKey));
44
+ const payload = base64url(
45
+ JSON.stringify({ iat: now - 60, exp: now + 9 * 60, iss: String(appId) })
46
+ );
47
+ const signature = base64url(
48
+ createSign("RSA-SHA256").update(`${header}.${payload}`).sign(privateKey)
49
+ );
24
50
  return `${header}.${payload}.${signature}`;
25
51
  }
26
52
  async function mintInstallationToken(creds, fetchImpl = fetch) {
@@ -29,12 +55,20 @@ async function mintInstallationToken(creds, fetchImpl = fetch) {
29
55
  const cached = installationTokenCache.get(cacheKey);
30
56
  if (cached && cached.expiresAtMs - 6e4 > Date.now()) return cached.token;
31
57
  const jwt = createAppJwt(creds.appId, creds.privateKey);
32
- const res = await fetchImpl(`https://api.github.com/app/installations/${creds.installationId}/access_tokens`, {
33
- method: "POST",
34
- headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${jwt}` }
35
- });
58
+ const res = await fetchImpl(
59
+ `https://api.github.com/app/installations/${creds.installationId}/access_tokens`,
60
+ {
61
+ method: "POST",
62
+ headers: {
63
+ Accept: "application/vnd.github+json",
64
+ Authorization: `Bearer ${jwt}`
65
+ }
66
+ }
67
+ );
36
68
  if (!res.ok) {
37
- throw new Error(`GitHub App token exchange failed (${res.status}) \u2014 check the app id, installation id, and private key.`);
69
+ throw new Error(
70
+ `GitHub App token exchange failed (${res.status}) \u2014 check the app id, installation id, and private key.`
71
+ );
38
72
  }
39
73
  const body = await res.json();
40
74
  const parsed = Date.parse(body.expires_at);
@@ -44,9 +78,15 @@ async function mintInstallationToken(creds, fetchImpl = fetch) {
44
78
  }
45
79
  async function verifyInstallationBelongsToApp(appId, privateKey, installationId, fetchImpl = fetch) {
46
80
  const jwt = createAppJwt(appId, privateKey);
47
- const res = await fetchImpl(`https://api.github.com/app/installations/${installationId}`, {
48
- headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${jwt}` }
49
- });
81
+ const res = await fetchImpl(
82
+ `https://api.github.com/app/installations/${installationId}`,
83
+ {
84
+ headers: {
85
+ Accept: "application/vnd.github+json",
86
+ Authorization: `Bearer ${jwt}`
87
+ }
88
+ }
89
+ );
50
90
  if (!res.ok) return false;
51
91
  const body = await res.json();
52
92
  return String(body.app_id) === String(appId);
@@ -55,7 +95,8 @@ function envAppCreds() {
55
95
  const appId = (process.env.THALLY_GITHUB_APP_ID ?? process.env.DOX_GITHUB_APP_ID)?.trim();
56
96
  const installationId = (process.env.THALLY_GITHUB_APP_INSTALLATION_ID ?? process.env.DOX_GITHUB_APP_INSTALLATION_ID)?.trim();
57
97
  const privateKey = process.env.THALLY_GITHUB_APP_PRIVATE_KEY ?? process.env.DOX_GITHUB_APP_PRIVATE_KEY;
58
- if (appId && installationId && privateKey) return { appId, installationId, privateKey };
98
+ if (appId && installationId && privateKey)
99
+ return { appId, installationId, privateKey };
59
100
  return void 0;
60
101
  }
61
102
  async function resolveGithubToken(options) {
@@ -66,7 +107,9 @@ async function resolveGithubToken(options) {
66
107
  try {
67
108
  return await mintInstallationToken(appCreds, options?.fetchImpl ?? fetch);
68
109
  } catch (err) {
69
- console.warn(`[thally-track] GitHub App token mint failed, falling back to PAT: ${err instanceof Error ? err.message : String(err)}`);
110
+ console.warn(
111
+ `[thally-track] GitHub App token mint failed, falling back to PAT: ${err instanceof Error ? err.message : String(err)}`
112
+ );
70
113
  return pat;
71
114
  }
72
115
  }
@@ -75,9 +118,13 @@ async function resolveGithubToken(options) {
75
118
  async function githubJson(path, options) {
76
119
  const fetchImpl = options?.fetchImpl ?? fetch;
77
120
  const token = await resolveGithubToken(options);
78
- const headers = { Accept: "application/vnd.github+json" };
121
+ const headers = {
122
+ Accept: "application/vnd.github+json"
123
+ };
79
124
  if (token) headers.Authorization = `Bearer ${token}`;
80
- const response = await fetchImpl(`https://api.github.com${path}`, { headers });
125
+ const response = await fetchImpl(`https://api.github.com${path}`, {
126
+ headers
127
+ });
81
128
  if (!response.ok) {
82
129
  const hint = response.status === 404 || response.status === 403 ? " (private repo or rate limit? set THALLY_GITHUB_TOKEN or connect a GitHub App)" : "";
83
130
  throw new Error(`GitHub API ${response.status} for ${path}${hint}`);
@@ -96,7 +143,10 @@ function toPullRequestInfo(raw) {
96
143
  };
97
144
  }
98
145
  async function fetchPullRequest(owner, repo, number, options) {
99
- const raw = await githubJson(`/repos/${owner}/${repo}/pulls/${number}`, options);
146
+ const raw = await githubJson(
147
+ `/repos/${owner}/${repo}/pulls/${number}`,
148
+ options
149
+ );
100
150
  return toPullRequestInfo(raw);
101
151
  }
102
152
  async function fetchPullRequestFiles(owner, repo, number, options) {
@@ -104,7 +154,10 @@ async function fetchPullRequestFiles(owner, repo, number, options) {
104
154
  const maxPages = 30;
105
155
  const raw = [];
106
156
  for (let page = 1; page <= maxPages; page++) {
107
- const chunk = await githubJson(`/repos/${owner}/${repo}/pulls/${number}/files?per_page=${perPage}&page=${page}`, options);
157
+ const chunk = await githubJson(
158
+ `/repos/${owner}/${repo}/pulls/${number}/files?per_page=${perPage}&page=${page}`,
159
+ options
160
+ );
108
161
  raw.push(...chunk);
109
162
  if (chunk.length < perPage) break;
110
163
  }
@@ -121,7 +174,9 @@ async function fetchLatestMergedPr(owner, repo, base, options) {
121
174
  `/repos/${owner}/${repo}/pulls?state=closed&base=${encodeURIComponent(base)}&sort=updated&direction=desc&per_page=30`,
122
175
  options
123
176
  );
124
- const merged = raw.filter((pr) => Boolean(pr.merged_at)).sort((a, b) => Date.parse(b.merged_at ?? "") - Date.parse(a.merged_at ?? ""))[0];
177
+ const merged = raw.filter((pr) => Boolean(pr.merged_at)).sort(
178
+ (a, b) => Date.parse(b.merged_at ?? "") - Date.parse(a.merged_at ?? "")
179
+ )[0];
125
180
  return merged ? toPullRequestInfo(merged) : null;
126
181
  }
127
182
  function compileGlob(pattern) {
@@ -197,12 +252,16 @@ _(diff truncated \u2014 ${files.length} file(s) total)_
197
252
  return context.slice(0, TRACK_CONTEXT_CHAR_CAP);
198
253
  }
199
254
  function buildTrackTask(repo, pr, files) {
200
- return { instruction: buildTrackInstruction(repo, pr), context: buildTrackContext(repo, pr, files) };
255
+ return {
256
+ instruction: buildTrackInstruction(repo, pr),
257
+ context: buildTrackContext(repo, pr, files)
258
+ };
201
259
  }
202
260
  export {
203
261
  AGENT_BRANCH_PREFIX,
204
262
  DOCS_PREVIEW_LABEL,
205
263
  TRACK_CONTEXT_CHAR_CAP,
264
+ TRACK_OWNED_BRANCH_PREFIXES,
206
265
  buildTrackContext,
207
266
  buildTrackInstruction,
208
267
  buildTrackTask,
@@ -211,6 +270,7 @@ export {
211
270
  fetchPullRequest,
212
271
  fetchPullRequestFiles,
213
272
  filterFilesByGlobs,
273
+ isTrackOwnedBranch,
214
274
  matchesGlob,
215
275
  mintInstallationToken,
216
276
  parseOwnerRepo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thallylabs/mcp",
3
- "version": "0.10.21",
3
+ "version": "0.10.24",
4
4
  "description": "MCP server for managing Thally knowledge surfaces and tracing product changes into documentation work.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@anthropic-ai/sdk": "^0.36.0",
40
40
  "@modelcontextprotocol/sdk": "^1.15.0",
41
- "create-thally-docs": "0.10.20",
41
+ "create-thally-docs": "0.10.22",
42
42
  "gray-matter": "^4.0.3",
43
43
  "p-limit": "^6.1.0",
44
44
  "yaml": "^2.8.2",