@indigoai-us/hq-cloud 6.14.9 → 6.14.11
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/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +89 -14
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +131 -3
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +138 -41
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +271 -25
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/journal.d.ts +7 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +22 -1
- package/dist/journal.js.map +1 -1
- package/dist/personal-vault-exclusions.d.ts +20 -4
- package/dist/personal-vault-exclusions.d.ts.map +1 -1
- package/dist/personal-vault-exclusions.js +37 -3
- package/dist/personal-vault-exclusions.js.map +1 -1
- package/dist/personal-vault-exclusions.test.js +19 -0
- package/dist/personal-vault-exclusions.test.js.map +1 -1
- package/dist/personal-vault.d.ts +6 -0
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +12 -0
- package/dist/personal-vault.js.map +1 -1
- package/dist/s3.d.ts +23 -1
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +74 -3
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +71 -1
- package/dist/s3.test.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/share.test.ts +176 -3
- package/src/cli/share.ts +114 -15
- package/src/cli/sync.test.ts +330 -24
- package/src/cli/sync.ts +184 -50
- package/src/journal.ts +28 -0
- package/src/personal-vault-exclusions.test.ts +23 -0
- package/src/personal-vault-exclusions.ts +39 -3
- package/src/personal-vault.ts +15 -0
- package/src/s3.test.ts +91 -0
- package/src/s3.ts +88 -2
- package/src/types.ts +13 -0
package/dist/cli/share.test.js
CHANGED
|
@@ -62,6 +62,14 @@ function makeEntityContext(overrides = {}) {
|
|
|
62
62
|
...overrides,
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
function deleteIntent(remoteEtag, localHash, localKind = "file") {
|
|
66
|
+
return {
|
|
67
|
+
version: 1,
|
|
68
|
+
remoteEtag,
|
|
69
|
+
localHash,
|
|
70
|
+
localKind,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
65
73
|
describe("wrapFilterWithIgnoreVisibility", () => {
|
|
66
74
|
it("records noteworthy base-ignore exclusions while counting all rejections", () => {
|
|
67
75
|
const hqRoot = path.join("/tmp", "hqroot");
|
|
@@ -1613,6 +1621,8 @@ describe("share", () => {
|
|
|
1613
1621
|
syncedAt: new Date().toISOString(),
|
|
1614
1622
|
direction: "up",
|
|
1615
1623
|
remoteEtag: "knowledge-etag",
|
|
1624
|
+
kind: "symlink",
|
|
1625
|
+
localDeleteIntent: deleteIntent("knowledge-etag", "irrelevant-not-checked", "symlink"),
|
|
1616
1626
|
},
|
|
1617
1627
|
},
|
|
1618
1628
|
}));
|
|
@@ -1744,6 +1754,32 @@ describe("share", () => {
|
|
|
1744
1754
|
delete process.env.HQ_STATE_DIR;
|
|
1745
1755
|
}
|
|
1746
1756
|
});
|
|
1757
|
+
it("personalMode excludes generated namespaced skill wrappers while preserving local skills", async () => {
|
|
1758
|
+
const generatedSource = path.join(tmpDir, "companies", "acme", "skills", "demo", "scripts");
|
|
1759
|
+
const generatedWrapper = path.join(tmpDir, ".claude", "skills", "acme:demo");
|
|
1760
|
+
const localSkill = path.join(tmpDir, ".claude", "skills", "local-skill");
|
|
1761
|
+
fs.mkdirSync(generatedSource, { recursive: true });
|
|
1762
|
+
fs.writeFileSync(path.join(generatedSource, "run.sh"), "#!/bin/sh\n");
|
|
1763
|
+
fs.mkdirSync(generatedWrapper, { recursive: true });
|
|
1764
|
+
fs.symlinkSync(generatedSource, path.join(generatedWrapper, "scripts"), "dir");
|
|
1765
|
+
fs.mkdirSync(localSkill, { recursive: true });
|
|
1766
|
+
fs.writeFileSync(path.join(localSkill, "SKILL.md"), "# local\n");
|
|
1767
|
+
const result = await share({
|
|
1768
|
+
paths: [path.join(tmpDir, ".claude")],
|
|
1769
|
+
entityContext: makeEntityContext({
|
|
1770
|
+
uid: "prs_01PERSONALTEST",
|
|
1771
|
+
slug: "__hq_personal_vault__",
|
|
1772
|
+
bucketName: "hq-vault-prs-01PERSONALTEST",
|
|
1773
|
+
}),
|
|
1774
|
+
hqRoot: tmpDir,
|
|
1775
|
+
personalMode: true,
|
|
1776
|
+
journalSlug: "personal-generated-wrapper",
|
|
1777
|
+
});
|
|
1778
|
+
expect(result.filesUploaded).toBe(1);
|
|
1779
|
+
expect(result.filesExcludedByPolicy).toBeGreaterThanOrEqual(1);
|
|
1780
|
+
expect(uploadSymlink).not.toHaveBeenCalled();
|
|
1781
|
+
expect(uploadFile).toHaveBeenCalledWith(expect.anything(), path.join(localSkill, "SKILL.md"), ".claude/skills/local-skill/SKILL.md", undefined, expect.anything());
|
|
1782
|
+
});
|
|
1747
1783
|
it("personal-vault override does NOT apply to company vaults: `owned-only` on cmp_ entity still " +
|
|
1748
1784
|
"refuses to tombstone a direction:'down' entry (multi-user curation intact)", async () => {
|
|
1749
1785
|
// Sibling guard for the previous test: the override is scoped strictly
|
|
@@ -1808,6 +1844,8 @@ describe("share", () => {
|
|
|
1808
1844
|
syncedAt: new Date().toISOString(),
|
|
1809
1845
|
direction: "up",
|
|
1810
1846
|
remoteEtag: "gone-etag",
|
|
1847
|
+
kind: "file",
|
|
1848
|
+
localDeleteIntent: deleteIntent("gone-etag", "irrelevant-not-checked-here"),
|
|
1811
1849
|
},
|
|
1812
1850
|
},
|
|
1813
1851
|
}));
|
|
@@ -1842,6 +1880,9 @@ describe("share", () => {
|
|
|
1842
1880
|
size: 42,
|
|
1843
1881
|
syncedAt: new Date().toISOString(),
|
|
1844
1882
|
direction: "up",
|
|
1883
|
+
remoteEtag: "removed-etag",
|
|
1884
|
+
kind: "file",
|
|
1885
|
+
localDeleteIntent: deleteIntent("removed-etag", "h"),
|
|
1845
1886
|
},
|
|
1846
1887
|
},
|
|
1847
1888
|
}));
|
|
@@ -1912,6 +1953,9 @@ describe("share", () => {
|
|
|
1912
1953
|
size: 1,
|
|
1913
1954
|
syncedAt: new Date().toISOString(),
|
|
1914
1955
|
direction: "up",
|
|
1956
|
+
remoteEtag: "in-scope-etag",
|
|
1957
|
+
kind: "file",
|
|
1958
|
+
localDeleteIntent: deleteIntent("in-scope-etag", "h"),
|
|
1915
1959
|
},
|
|
1916
1960
|
"other/also-gone.md": {
|
|
1917
1961
|
hash: "h",
|
|
@@ -1954,6 +1998,9 @@ describe("share", () => {
|
|
|
1954
1998
|
size: 5,
|
|
1955
1999
|
syncedAt: new Date().toISOString(),
|
|
1956
2000
|
direction: "up",
|
|
2001
|
+
remoteEtag: "flaky-etag",
|
|
2002
|
+
kind: "file",
|
|
2003
|
+
localDeleteIntent: deleteIntent("flaky-etag", "h"),
|
|
1957
2004
|
},
|
|
1958
2005
|
},
|
|
1959
2006
|
}));
|
|
@@ -2020,6 +2067,9 @@ describe("share", () => {
|
|
|
2020
2067
|
"i-uploaded.md": {
|
|
2021
2068
|
hash: "h", size: 1, syncedAt: new Date().toISOString(),
|
|
2022
2069
|
direction: "up",
|
|
2070
|
+
remoteEtag: "uploaded-etag",
|
|
2071
|
+
kind: "file",
|
|
2072
|
+
localDeleteIntent: deleteIntent("uploaded-etag", "h"),
|
|
2023
2073
|
},
|
|
2024
2074
|
},
|
|
2025
2075
|
}));
|
|
@@ -2050,6 +2100,9 @@ describe("share", () => {
|
|
|
2050
2100
|
"pulled.md": {
|
|
2051
2101
|
hash: "h", size: 1, syncedAt: new Date().toISOString(),
|
|
2052
2102
|
direction: "down",
|
|
2103
|
+
remoteEtag: "pulled-etag",
|
|
2104
|
+
kind: "file",
|
|
2105
|
+
localDeleteIntent: deleteIntent("pulled-etag", "h"),
|
|
2053
2106
|
},
|
|
2054
2107
|
},
|
|
2055
2108
|
}));
|
|
@@ -2084,6 +2137,9 @@ describe("share", () => {
|
|
|
2084
2137
|
"active/current.md": {
|
|
2085
2138
|
hash: "h", size: 1, syncedAt: new Date().toISOString(),
|
|
2086
2139
|
direction: "up",
|
|
2140
|
+
remoteEtag: "active-etag",
|
|
2141
|
+
kind: "file",
|
|
2142
|
+
localDeleteIntent: deleteIntent("active-etag", "h"),
|
|
2087
2143
|
},
|
|
2088
2144
|
},
|
|
2089
2145
|
}));
|
|
@@ -2137,6 +2193,8 @@ describe("share", () => {
|
|
|
2137
2193
|
hash: "h", size: 100, syncedAt: new Date().toISOString(),
|
|
2138
2194
|
direction: "down",
|
|
2139
2195
|
remoteEtag: "abc123",
|
|
2196
|
+
kind: "file",
|
|
2197
|
+
localDeleteIntent: deleteIntent("abc123", "h"),
|
|
2140
2198
|
},
|
|
2141
2199
|
},
|
|
2142
2200
|
}));
|
|
@@ -2178,6 +2236,8 @@ describe("share", () => {
|
|
|
2178
2236
|
syncedAt: new Date().toISOString(),
|
|
2179
2237
|
direction: "down",
|
|
2180
2238
|
remoteEtag: "abc123",
|
|
2239
|
+
kind: "file",
|
|
2240
|
+
localDeleteIntent: deleteIntent("abc123", "h"),
|
|
2181
2241
|
removedAt: new Date().toISOString(),
|
|
2182
2242
|
removedReason: "local-delete",
|
|
2183
2243
|
},
|
|
@@ -2219,6 +2279,8 @@ describe("share", () => {
|
|
|
2219
2279
|
hash: "h", size: 50, syncedAt: new Date().toISOString(),
|
|
2220
2280
|
direction: "down",
|
|
2221
2281
|
remoteEtag: "stale-etag",
|
|
2282
|
+
kind: "file",
|
|
2283
|
+
localDeleteIntent: deleteIntent("stale-etag", "h"),
|
|
2222
2284
|
},
|
|
2223
2285
|
},
|
|
2224
2286
|
}));
|
|
@@ -2258,6 +2320,55 @@ describe("share", () => {
|
|
|
2258
2320
|
expect(result.filesRefusedStale).toBe(1);
|
|
2259
2321
|
expect(result.filesTombstoned).toBe(0);
|
|
2260
2322
|
});
|
|
2323
|
+
it("currency-gated: preserves a file recreated after the delete plan", async () => {
|
|
2324
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
2325
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
2326
|
+
const recreatedPath = path.join(companyRoot, "racy.md");
|
|
2327
|
+
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
2328
|
+
fs.writeFileSync(journalPath, JSON.stringify({
|
|
2329
|
+
version: "2",
|
|
2330
|
+
lastSync: new Date().toISOString(),
|
|
2331
|
+
files: {
|
|
2332
|
+
"racy.md": {
|
|
2333
|
+
hash: "synced-hash",
|
|
2334
|
+
size: 10,
|
|
2335
|
+
syncedAt: new Date().toISOString(),
|
|
2336
|
+
direction: "up",
|
|
2337
|
+
remoteEtag: "racy-etag",
|
|
2338
|
+
kind: "file",
|
|
2339
|
+
localDeleteIntent: deleteIntent("racy-etag", "synced-hash"),
|
|
2340
|
+
},
|
|
2341
|
+
},
|
|
2342
|
+
pulls: [],
|
|
2343
|
+
}));
|
|
2344
|
+
// The currency HEAD establishes the remote revision, then a local writer
|
|
2345
|
+
// recreates the file before the planned DeleteObject can run.
|
|
2346
|
+
vi.mocked(headRemoteFile).mockImplementationOnce(async () => {
|
|
2347
|
+
fs.writeFileSync(recreatedPath, "recreated after planning");
|
|
2348
|
+
return { lastModified: new Date(), etag: '"racy-etag"', size: 10 };
|
|
2349
|
+
});
|
|
2350
|
+
const events = [];
|
|
2351
|
+
const result = await share({
|
|
2352
|
+
paths: [companyRoot],
|
|
2353
|
+
company: "acme",
|
|
2354
|
+
vaultConfig: mockConfig,
|
|
2355
|
+
hqRoot: tmpDir,
|
|
2356
|
+
skipUnchanged: true,
|
|
2357
|
+
propagateDeletes: true,
|
|
2358
|
+
propagateDeletePolicy: "currency-gated",
|
|
2359
|
+
onEvent: (e) => events.push(e),
|
|
2360
|
+
});
|
|
2361
|
+
expect(result.filesDeleted).toBe(0);
|
|
2362
|
+
expect(deleteRemoteFile).not.toHaveBeenCalled();
|
|
2363
|
+
expect(fs.readFileSync(recreatedPath, "utf-8")).toBe("recreated after planning");
|
|
2364
|
+
const journal = JSON.parse(fs.readFileSync(journalPath, "utf-8"));
|
|
2365
|
+
expect(journal.files["racy.md"]).toBeDefined();
|
|
2366
|
+
expect(events).toContainEqual(expect.objectContaining({
|
|
2367
|
+
type: "delete-refused-stale-etag",
|
|
2368
|
+
path: "racy.md",
|
|
2369
|
+
reason: "intent-changed",
|
|
2370
|
+
}));
|
|
2371
|
+
});
|
|
2261
2372
|
it("currency-gated: refuses delete for a divergent-local entry (journal-honesty — never delete a remote the local never genuinely matched)", async () => {
|
|
2262
2373
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
2263
2374
|
fs.mkdirSync(companyRoot, { recursive: true });
|
|
@@ -2328,6 +2439,8 @@ describe("share", () => {
|
|
|
2328
2439
|
hash: "h", size: 25, syncedAt: new Date().toISOString(),
|
|
2329
2440
|
direction: "down",
|
|
2330
2441
|
remoteEtag: "doesnt-matter",
|
|
2442
|
+
kind: "file",
|
|
2443
|
+
localDeleteIntent: deleteIntent("doesnt-matter", "h"),
|
|
2331
2444
|
},
|
|
2332
2445
|
},
|
|
2333
2446
|
}));
|
|
@@ -2396,12 +2509,13 @@ describe("share", () => {
|
|
|
2396
2509
|
});
|
|
2397
2510
|
expect(result.filesDeleted).toBe(0);
|
|
2398
2511
|
expect(deleteRemoteFile).not.toHaveBeenCalled();
|
|
2399
|
-
// HEAD must NOT be called —
|
|
2512
|
+
// HEAD must NOT be called — the explicit-intent gate rejects legacy
|
|
2513
|
+
// entries before any remote operation.
|
|
2400
2514
|
expect(headRemoteFile).not.toHaveBeenCalled();
|
|
2401
2515
|
const refused = events.find((e) => e.type === "delete-refused-stale-etag");
|
|
2402
2516
|
expect(refused).toMatchObject({
|
|
2403
|
-
journalEtag: "<
|
|
2404
|
-
reason: "
|
|
2517
|
+
journalEtag: "<missing-delete-intent>",
|
|
2518
|
+
reason: "missing-delete-intent",
|
|
2405
2519
|
});
|
|
2406
2520
|
expect(result.filesRefusedStale).toBe(1);
|
|
2407
2521
|
});
|
|
@@ -2424,6 +2538,8 @@ describe("share", () => {
|
|
|
2424
2538
|
hash: "h", size: 200, syncedAt: new Date().toISOString(),
|
|
2425
2539
|
direction: "down",
|
|
2426
2540
|
remoteEtag: "upstream-etag",
|
|
2541
|
+
kind: "file",
|
|
2542
|
+
localDeleteIntent: deleteIntent("upstream-etag", "h"),
|
|
2427
2543
|
},
|
|
2428
2544
|
},
|
|
2429
2545
|
}));
|
|
@@ -2525,6 +2641,8 @@ describe("share", () => {
|
|
|
2525
2641
|
hash: "h", size: 5, syncedAt: new Date().toISOString(),
|
|
2526
2642
|
direction: "up",
|
|
2527
2643
|
remoteEtag: "regular-etag",
|
|
2644
|
+
kind: "file",
|
|
2645
|
+
localDeleteIntent: deleteIntent("regular-etag", "h"),
|
|
2528
2646
|
},
|
|
2529
2647
|
},
|
|
2530
2648
|
}));
|
|
@@ -2796,6 +2914,8 @@ describe("share", () => {
|
|
|
2796
2914
|
hash: "h", size: 1, syncedAt: new Date().toISOString(),
|
|
2797
2915
|
direction: "up",
|
|
2798
2916
|
remoteEtag: "abc",
|
|
2917
|
+
kind: "file",
|
|
2918
|
+
localDeleteIntent: deleteIntent("abc", "h"),
|
|
2799
2919
|
},
|
|
2800
2920
|
},
|
|
2801
2921
|
}));
|
|
@@ -3190,6 +3310,8 @@ describe("share", () => {
|
|
|
3190
3310
|
syncedAt: new Date().toISOString(),
|
|
3191
3311
|
direction,
|
|
3192
3312
|
remoteEtag: `etag-${i}`,
|
|
3313
|
+
kind: "file",
|
|
3314
|
+
localDeleteIntent: deleteIntent(`etag-${i}`, "h"),
|
|
3193
3315
|
};
|
|
3194
3316
|
if (i >= missing) {
|
|
3195
3317
|
fs.writeFileSync(path.join(companyRoot, key), "hi");
|
|
@@ -3394,11 +3516,15 @@ describe("share", () => {
|
|
|
3394
3516
|
files[`d-${i.toString().padStart(4, "0")}.md`] = {
|
|
3395
3517
|
hash: "h", size: 5, syncedAt: now,
|
|
3396
3518
|
direction: "down", remoteEtag: `down-etag-${i}`,
|
|
3519
|
+
kind: "file",
|
|
3520
|
+
localDeleteIntent: deleteIntent(`down-etag-${i}`, "h"),
|
|
3397
3521
|
};
|
|
3398
3522
|
}
|
|
3399
3523
|
files["only-up.md"] = {
|
|
3400
3524
|
hash: "h", size: 5, syncedAt: now,
|
|
3401
3525
|
direction: "up", remoteEtag: "up-etag",
|
|
3526
|
+
kind: "file",
|
|
3527
|
+
localDeleteIntent: deleteIntent("up-etag", "h"),
|
|
3402
3528
|
};
|
|
3403
3529
|
const journalPath = path.join(stateDir, "sync-journal.acme.json");
|
|
3404
3530
|
fs.writeFileSync(journalPath, JSON.stringify({ version: "1", lastSync: now, files }));
|
|
@@ -3534,6 +3660,8 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
3534
3660
|
syncedAt: new Date().toISOString(),
|
|
3535
3661
|
direction: "down",
|
|
3536
3662
|
remoteEtag: "v2-etag",
|
|
3663
|
+
kind: "file",
|
|
3664
|
+
localDeleteIntent: deleteIntent("v2-etag", "h"),
|
|
3537
3665
|
},
|
|
3538
3666
|
},
|
|
3539
3667
|
pulls: [],
|