shraga 0.1.45 → 0.1.47
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/server/data-sync.ts +30 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/server/data-sync.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { hostname } from 'node:os';
|
|
3
4
|
import { DATA_DIR } from './paths.ts';
|
|
4
5
|
import { emitEvent } from './events/bus.ts';
|
|
5
6
|
import { runTextQuery } from './sdk-utils.ts';
|
|
@@ -18,6 +19,15 @@ export class DataSyncOptions {
|
|
|
18
19
|
* A real deployment must ALSO set DATA_SYNC_ENABLE=1. Dev/verify boots never sync/push.
|
|
19
20
|
*/
|
|
20
21
|
enabled = process.env.DATA_SYNC_ENABLE === '1' || process.env.DATA_SYNC_ENABLE === 'true';
|
|
22
|
+
/**
|
|
23
|
+
* Owner-facing alerts are a PROD side effect, so only the designated single-writer instance
|
|
24
|
+
* (the same one that fires schedules) may DM owners. A dev laptop legitimately syncs the shared
|
|
25
|
+
* data repo, but it also shares .env's APP_NAME, OWNERS and the Slack bot token — so its alerts
|
|
26
|
+
* are indistinguishable from the box's. Past incident 2026-08-20: a laptop pinned to an older
|
|
27
|
+
* shraga re-sent a merge-conflict alert for a bug already fixed AND deployed on the box, and the
|
|
28
|
+
* DM gave no way to tell which instance sent it. Suppressed alerts are still logged locally.
|
|
29
|
+
*/
|
|
30
|
+
notify = process.env.DATA_SYNC_SCHEDULER_ACTIVE === 'true';
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export class DataSync {
|
|
@@ -97,8 +107,8 @@ export class DataSync {
|
|
|
97
107
|
}
|
|
98
108
|
this.ensureGitignore();
|
|
99
109
|
if (!this.verifyDeploymentId()) return;
|
|
110
|
+
await this.configureGit(); // must precede untrackIgnored — it neutralises per-user excludes
|
|
100
111
|
await this.untrackIgnored();
|
|
101
|
-
await this.configureGit();
|
|
102
112
|
this.ready = true;
|
|
103
113
|
}
|
|
104
114
|
|
|
@@ -550,6 +560,12 @@ export class DataSync {
|
|
|
550
560
|
}
|
|
551
561
|
|
|
552
562
|
private async notifyOwners(text: string): Promise<void> {
|
|
563
|
+
if (!this.options.notify) {
|
|
564
|
+
// Not the authoritative instance — log it so a dev run still surfaces the problem locally,
|
|
565
|
+
// but never DM owners (see DataSyncOptions.notify).
|
|
566
|
+
console.warn(`${TAG} notification suppressed (not the authoritative instance):\n${text}`);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
553
569
|
// No Slack coupling here — resolve owner contacts and publish a deploy notice on the event bus.
|
|
554
570
|
// The Slack feature (slackFeature) subscribes and DMs each owner. Owner resolution uses the
|
|
555
571
|
// contacts store only, so data-sync stays transport-agnostic.
|
|
@@ -562,7 +578,10 @@ export class DataSync {
|
|
|
562
578
|
console.warn(`${TAG} No owners (OWNERS env) with Slack IDs found, skipping notification`);
|
|
563
579
|
return;
|
|
564
580
|
}
|
|
565
|
-
|
|
581
|
+
// Stamp the sender: every owner alert must name the instance it came from, so "is this back?"
|
|
582
|
+
// is answerable without log archaeology across hosts.
|
|
583
|
+
const from = `${this.options.deploymentId || 'shraga'}@${hostname()}`;
|
|
584
|
+
emitEvent('data-sync', { kind: 'deploy', owners, text: `${text}\n\n_from ${from}_` });
|
|
566
585
|
}
|
|
567
586
|
|
|
568
587
|
/**
|
|
@@ -605,7 +624,13 @@ export class DataSync {
|
|
|
605
624
|
/** Commit a refreshed .gitignore and untrack any committed files that now match it. */
|
|
606
625
|
private async untrackIgnored(): Promise<void> {
|
|
607
626
|
await this.git('add', '.gitignore').catch(() => {});
|
|
608
|
-
|
|
627
|
+
// core.excludesFile=/dev/null: --exclude-standard would otherwise fold in the USER'S global
|
|
628
|
+
// gitignore (~/.gitignore_global) and untrack files that are ignored on this host only. The
|
|
629
|
+
// data repo is shared, so a personal rule must never decide what leaves the remote. Real
|
|
630
|
+
// near-miss 2026-08-20: a laptop's global `.agent/` rule staged 50 files for deletion —
|
|
631
|
+
// including workspace/.agent/memory/MEMORY.md, the agent's own memory — and only
|
|
632
|
+
// guardMassDeletions() stopped it. Only the repo's OWN .gitignore may untrack anything.
|
|
633
|
+
const out = await this.git('-c', 'core.excludesFile=/dev/null', 'ls-files', '-i', '-c', '--exclude-standard').catch(() => '');
|
|
609
634
|
// Only untrack files that actually exist on disk AND match .gitignore.
|
|
610
635
|
// git ls-files -i can falsely report tracked files missing from the worktree
|
|
611
636
|
// (remote-only files restored during init). Removing those wipes remote data.
|
|
@@ -654,6 +679,8 @@ export class DataSync {
|
|
|
654
679
|
const name = process.env.APP_NAME || 'shraga';
|
|
655
680
|
await this.git('config', 'user.name', `${name} agent`).catch(() => {});
|
|
656
681
|
await this.git('config', 'user.email', `agent@${name}.local`).catch(() => {});
|
|
682
|
+
// The data repo must behave identically on every host: no per-user global gitignore.
|
|
683
|
+
await this.git('config', 'core.excludesFile', '/dev/null').catch(() => {});
|
|
657
684
|
}
|
|
658
685
|
|
|
659
686
|
private authedUrl(): string {
|