@jeffober/thatch 0.1.37 → 0.1.38
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/package.json +1 -1
- package/src/index.ts +18 -0
- package/src/watchers.ts +20 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffober/thatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "Persistent memory for AI coding agents — local embeddings, SQLite stores, zero config. Works with OpenCode (as a plugin), Claude Code (as an MCP server), and Cursor (as an MCP server).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -110,6 +110,24 @@ export const server: Plugin = async ({ client, worktree }) => {
|
|
|
110
110
|
parts: [{ type: "text", text: watcherNotificationNudge(events[0]?.target ?? "watched target", events), synthetic: true }],
|
|
111
111
|
},
|
|
112
112
|
});
|
|
113
|
+
// Toast: the notification part is TUI-hidden, so without this the
|
|
114
|
+
// user watching the session sees the model wake up with no visible
|
|
115
|
+
// cause. Announce what fired. Failed checks/workflows surface as a
|
|
116
|
+
// warning variant; routine events as info. Best-effort - the TUI may
|
|
117
|
+
// not be connected (headless mode).
|
|
118
|
+
const failed = events.some((e) => e.summary.includes("failure"));
|
|
119
|
+
const more = events.length > 1 ? ` +${events.length - 1} more` : "";
|
|
120
|
+
try {
|
|
121
|
+
await client.tui.showToast({
|
|
122
|
+
body: {
|
|
123
|
+
message: `\u23F0 ${events[0]?.target ?? "watched target"}: ${events[0]?.summary ?? ""}${more}`,
|
|
124
|
+
variant: failed ? "warning" : "info",
|
|
125
|
+
duration: 5000,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
} catch {
|
|
129
|
+
// TUI may not be connected. Best-effort.
|
|
130
|
+
}
|
|
113
131
|
},
|
|
114
132
|
canDeliver: (sessionID) =>
|
|
115
133
|
!compacting.has(sessionID) && sessionStatus.get(sessionID) === "idle",
|
package/src/watchers.ts
CHANGED
|
@@ -97,6 +97,9 @@ export interface WorkflowRunRef {
|
|
|
97
97
|
url: string;
|
|
98
98
|
/** What triggered the run: push, pull_request, schedule, workflow_run, ... */
|
|
99
99
|
event: string;
|
|
100
|
+
/** The branch the run's head commit is on (a tag-triggered run puts the TAG name here). */
|
|
101
|
+
headBranch: string;
|
|
102
|
+
headSha: string;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
// ---------------------------------------------------------------------------
|
|
@@ -303,6 +306,8 @@ function parseWorkflowRuns(raw: unknown): Record<string, WorkflowRunRef> {
|
|
|
303
306
|
conclusion?: string | null;
|
|
304
307
|
html_url?: string;
|
|
305
308
|
event?: string;
|
|
309
|
+
head_branch?: string;
|
|
310
|
+
head_sha?: string;
|
|
306
311
|
}> }).workflow_runs ?? [];
|
|
307
312
|
for (const run of rawRuns) {
|
|
308
313
|
if (run.id === undefined) continue;
|
|
@@ -312,6 +317,8 @@ function parseWorkflowRuns(raw: unknown): Record<string, WorkflowRunRef> {
|
|
|
312
317
|
conclusion: run.conclusion ?? null,
|
|
313
318
|
url: run.html_url ?? "",
|
|
314
319
|
event: run.event ?? "unknown",
|
|
320
|
+
headBranch: run.head_branch ?? "",
|
|
321
|
+
headSha: run.head_sha ?? "",
|
|
315
322
|
};
|
|
316
323
|
}
|
|
317
324
|
return runs;
|
|
@@ -375,20 +382,30 @@ export async function fetchPrState(gh: GhRunner, repo: string, pr: number): Prom
|
|
|
375
382
|
|
|
376
383
|
/**
|
|
377
384
|
* Fetches the current state of a branch: the head commit, check runs on that
|
|
378
|
-
* head, and
|
|
385
|
+
* head, and recent workflow runs relevant to the branch. The branch and
|
|
379
386
|
* workflow-run calls run in parallel; check runs need the head SHA first.
|
|
387
|
+
*
|
|
388
|
+
* Workflow runs are fetched WITHOUT the API's branch filter and filtered
|
|
389
|
+
* client-side: keep runs whose head_branch matches the branch (push runs)
|
|
390
|
+
* or whose head_sha is the branch's current head (tag-triggered runs like a
|
|
391
|
+
* release Publish run - their head_branch is the tag name, so the API's
|
|
392
|
+
* branch filter would hide exactly the runs a release watch exists for).
|
|
380
393
|
*/
|
|
381
394
|
export async function fetchBranchState(gh: GhRunner, repo: string, branch: string): Promise<BranchState> {
|
|
382
395
|
const [branchInfo, runs] = await Promise.all([
|
|
383
396
|
gh([`/repos/${repo}/branches/${branch}`]),
|
|
384
|
-
gh([`/repos/${repo}/actions/runs?
|
|
397
|
+
gh([`/repos/${repo}/actions/runs?per_page=30`]),
|
|
385
398
|
]);
|
|
386
399
|
const headSha = (branchInfo as { commit?: { sha?: string } }).commit?.sha ?? "";
|
|
400
|
+
const allRuns = parseWorkflowRuns(runs);
|
|
401
|
+
const relevant = Object.fromEntries(
|
|
402
|
+
Object.entries(allRuns).filter(([, run]) => run.headBranch === branch || run.headSha === headSha),
|
|
403
|
+
);
|
|
387
404
|
const checkRuns = await gh([`/repos/${repo}/commits/${headSha}/check-runs?per_page=100`]);
|
|
388
405
|
return {
|
|
389
406
|
headSha,
|
|
390
407
|
checkRuns: parseCheckRuns(checkRuns),
|
|
391
|
-
workflowRuns:
|
|
408
|
+
workflowRuns: relevant,
|
|
392
409
|
};
|
|
393
410
|
}
|
|
394
411
|
|