@indigoai-us/hq-cloud 6.11.20 → 6.12.0
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/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +9 -11
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner.test.js +35 -64
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/sync.d.ts +11 -25
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +16 -17
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +40 -42
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/personal-vault.d.ts +23 -24
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +38 -30
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.d.ts +9 -6
- package/dist/personal-vault.test.d.ts.map +1 -1
- package/dist/personal-vault.test.js +90 -79
- package/dist/personal-vault.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +9 -12
- package/src/bin/sync-runner.test.ts +35 -74
- package/src/cli/sync.test.ts +40 -42
- package/src/cli/sync.ts +25 -42
- package/src/personal-vault.test.ts +91 -83
- package/src/personal-vault.ts +45 -41
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for the personal-vault path-discovery helpers.
|
|
3
3
|
*
|
|
4
|
-
* These cover the column-anchored `cloud:
|
|
5
|
-
* `companies/{slug}/` enumeration filter — both are pure functions over
|
|
4
|
+
* These cover the column-anchored `cloud: true` exclusion-marker regex and
|
|
5
|
+
* the `companies/{slug}/` enumeration filter — both are pure functions over
|
|
6
6
|
* disk + caller-supplied option sets, so we exercise them against tmp
|
|
7
|
-
* directories rather than mocking fs.
|
|
7
|
+
* directories rather than mocking fs. Local (non-cloud) companies sync to
|
|
8
|
+
* the personal vault by DEFAULT: a missing/markerless `company.yaml` is
|
|
9
|
+
* INCLUDED; only an explicit `cloud: true` marker (or membership via
|
|
10
|
+
* `teamSyncedSlugs`) opts a company out.
|
|
8
11
|
*
|
|
9
|
-
* The sync-runner-level integration (
|
|
10
|
-
*
|
|
11
|
-
* `bin/sync-runner.test.ts` — tests G, H, I.
|
|
12
|
+
* The sync-runner-level integration (team-synced slug derivation,
|
|
13
|
+
* decommission of cloud-promoted companies, end-to-end share() invocation)
|
|
14
|
+
* lives in `bin/sync-runner.test.ts` — tests G, H, I.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
@@ -63,108 +66,125 @@ describe("personal-vault helpers", () => {
|
|
|
63
66
|
expect(PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS).toContain("_template");
|
|
64
67
|
});
|
|
65
68
|
|
|
66
|
-
// ── computePersonalCompanySubdirs:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
// ── computePersonalCompanySubdirs: default inclusion ───────────────────
|
|
70
|
+
// Local companies sync to the personal vault by default. A missing or
|
|
71
|
+
// markerless company.yaml is INCLUDED — only an explicit `cloud: true`
|
|
72
|
+
// marker (or membership) opts a company OUT.
|
|
73
|
+
it("default: includes a company with NO company.yaml", () => {
|
|
74
|
+
writeCompany("foo", null);
|
|
69
75
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
70
76
|
path.join("companies", "foo"),
|
|
71
77
|
]);
|
|
72
78
|
});
|
|
73
79
|
|
|
74
|
-
it("
|
|
75
|
-
writeCompany("foo", "
|
|
80
|
+
it("default: includes a company with an empty company.yaml", () => {
|
|
81
|
+
writeCompany("foo", "");
|
|
76
82
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
77
83
|
path.join("companies", "foo"),
|
|
78
84
|
]);
|
|
79
85
|
});
|
|
80
86
|
|
|
81
|
-
it("
|
|
82
|
-
writeCompany("foo", "cloud: false
|
|
87
|
+
it("default: includes a company explicitly marked `cloud: false`", () => {
|
|
88
|
+
writeCompany("foo", "cloud: false\nname: Foo\n");
|
|
83
89
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
84
90
|
path.join("companies", "foo"),
|
|
85
91
|
]);
|
|
86
92
|
});
|
|
87
93
|
|
|
88
|
-
it("
|
|
89
|
-
writeCompany("foo", "
|
|
94
|
+
it("default: includes a company.yaml with no `cloud:` key at all", () => {
|
|
95
|
+
writeCompany("foo", "name: Foo\nslug: foo\n");
|
|
90
96
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
91
97
|
path.join("companies", "foo"),
|
|
92
98
|
]);
|
|
93
99
|
});
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
]);
|
|
101
|
+
// ── computePersonalCompanySubdirs: `cloud: true` exclusion-marker ──────
|
|
102
|
+
it("marker: excludes canonical `cloud: true` at column 0", () => {
|
|
103
|
+
writeCompany("foo", "cloud: true\nname: Foo\n");
|
|
104
|
+
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
100
105
|
});
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
writeCompany("foo", "cloud: true\n");
|
|
107
|
+
it("marker: excludes `cloud:true` (no space after colon)", () => {
|
|
108
|
+
writeCompany("foo", "cloud:true\n");
|
|
105
109
|
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
106
110
|
});
|
|
107
111
|
|
|
108
|
-
it("marker:
|
|
109
|
-
writeCompany("foo",
|
|
112
|
+
it("marker: excludes with a trailing comment after `cloud: true`", () => {
|
|
113
|
+
writeCompany("foo", "cloud: true # cloud-backed, synced to team bucket\n");
|
|
110
114
|
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
111
115
|
});
|
|
112
116
|
|
|
113
|
-
it("marker:
|
|
114
|
-
writeCompany("foo", "");
|
|
117
|
+
it("marker: excludes with CRLF line endings", () => {
|
|
118
|
+
writeCompany("foo", "cloud: true\r\nname: Foo\r\n");
|
|
115
119
|
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
116
120
|
});
|
|
117
121
|
|
|
118
|
-
it("marker:
|
|
119
|
-
|
|
120
|
-
// allowed arbitrary leading whitespace, which silently matched
|
|
121
|
-
// nested keys like `meta.cloud = false`. The tightened `^cloud:` regex
|
|
122
|
-
// rejects this, matching the canonical column-0 shape that
|
|
123
|
-
// `/designate-team`'s awk emits.
|
|
124
|
-
writeCompany("foo", "meta:\n cloud: false\n");
|
|
122
|
+
it("marker: excludes `cloud: true` even when it is NOT the first line", () => {
|
|
123
|
+
writeCompany("foo", "name: Foo\nslug: foo\ncloud: true\n");
|
|
125
124
|
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
126
125
|
});
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
// ── computePersonalCompanySubdirs: marker fails OPEN (still included) ───
|
|
128
|
+
// A marker that isn't the canonical column-0 lowercase `cloud: true`
|
|
129
|
+
// shape `/designate-team` emits does NOT exclude — the company stays in
|
|
130
|
+
// the personal vault, which is the safe default.
|
|
131
|
+
it("marker: nested `cloud: true` does NOT exclude (column-0 anchor)", () => {
|
|
132
|
+
writeCompany("foo", "meta:\n cloud: true\n");
|
|
133
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
134
|
+
path.join("companies", "foo"),
|
|
135
|
+
]);
|
|
131
136
|
});
|
|
132
137
|
|
|
133
|
-
it("marker:
|
|
134
|
-
writeCompany("foo", "
|
|
135
|
-
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([
|
|
138
|
+
it("marker: list-item style `- cloud: true` does NOT exclude", () => {
|
|
139
|
+
writeCompany("foo", "tags:\n- cloud: true\n");
|
|
140
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
141
|
+
path.join("companies", "foo"),
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("marker: flow-mapping `{ cloud: true }` does NOT exclude", () => {
|
|
146
|
+
writeCompany("foo", "{ cloud: true }\n");
|
|
147
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
148
|
+
path.join("companies", "foo"),
|
|
149
|
+
]);
|
|
136
150
|
});
|
|
137
151
|
|
|
138
|
-
it("marker:
|
|
139
|
-
for (const variant of ["cloud:
|
|
152
|
+
it("marker: YAML-alt boolean spellings (True, TRUE, yes, on) do NOT exclude", () => {
|
|
153
|
+
for (const variant of ["cloud: True\n", "cloud: TRUE\n", "cloud: yes\n", "cloud: on\n"]) {
|
|
140
154
|
writeCompany("foo", variant);
|
|
141
|
-
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([
|
|
155
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
156
|
+
path.join("companies", "foo"),
|
|
157
|
+
]);
|
|
142
158
|
fs.rmSync(path.join(hqRoot, "companies", "foo"), { recursive: true });
|
|
143
159
|
}
|
|
144
160
|
});
|
|
145
161
|
|
|
146
|
-
it("marker:
|
|
147
|
-
writeCompany("foo", 'cloud: "
|
|
148
|
-
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([
|
|
162
|
+
it("marker: quoted `cloud: \"true\"` does NOT exclude (quote breaks the literal-true match)", () => {
|
|
163
|
+
writeCompany("foo", 'cloud: "true"\n');
|
|
164
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
165
|
+
path.join("companies", "foo"),
|
|
166
|
+
]);
|
|
149
167
|
});
|
|
150
168
|
|
|
151
|
-
it("marker:
|
|
152
|
-
writeCompany("foo", "cloud:
|
|
153
|
-
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([
|
|
169
|
+
it("marker: `cloud: truthy` does NOT exclude (word-boundary guard)", () => {
|
|
170
|
+
writeCompany("foo", "cloud: truthy\n");
|
|
171
|
+
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
172
|
+
path.join("companies", "foo"),
|
|
173
|
+
]);
|
|
154
174
|
});
|
|
155
175
|
|
|
156
176
|
// ── Subdir-level filters ───────────────────────────────────────────────
|
|
157
|
-
it("filter: skips _template even
|
|
158
|
-
writeCompany("_template",
|
|
159
|
-
writeCompany("real",
|
|
177
|
+
it("filter: skips _template even when it would otherwise be included", () => {
|
|
178
|
+
writeCompany("_template", null);
|
|
179
|
+
writeCompany("real", null);
|
|
160
180
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
161
181
|
path.join("companies", "real"),
|
|
162
182
|
]);
|
|
163
183
|
});
|
|
164
184
|
|
|
165
|
-
it("filter: skips slugs in the teamSyncedSlugs set", () => {
|
|
166
|
-
writeCompany("free",
|
|
167
|
-
writeCompany("team-acme",
|
|
185
|
+
it("filter: skips slugs in the teamSyncedSlugs set (cloud-backed)", () => {
|
|
186
|
+
writeCompany("free", null);
|
|
187
|
+
writeCompany("team-acme", null);
|
|
168
188
|
expect(
|
|
169
189
|
rel(computePersonalCompanySubdirs(hqRoot, new Set(["team-acme"]))),
|
|
170
190
|
).toEqual([path.join("companies", "free")]);
|
|
@@ -175,7 +195,7 @@ describe("personal-vault helpers", () => {
|
|
|
175
195
|
// crash, should not be enumerated as a company subdir.
|
|
176
196
|
fs.mkdirSync(path.join(hqRoot, "companies"), { recursive: true });
|
|
177
197
|
fs.writeFileSync(path.join(hqRoot, "companies", "README.md"), "# README");
|
|
178
|
-
writeCompany("real",
|
|
198
|
+
writeCompany("real", null);
|
|
179
199
|
expect(rel(computePersonalCompanySubdirs(hqRoot))).toEqual([
|
|
180
200
|
path.join("companies", "real"),
|
|
181
201
|
]);
|
|
@@ -187,40 +207,31 @@ describe("personal-vault helpers", () => {
|
|
|
187
207
|
});
|
|
188
208
|
|
|
189
209
|
// ── computePersonalVaultPaths: top-level + company-subdir composition ──
|
|
190
|
-
it("composition:
|
|
210
|
+
it("composition: local company subdirs are included by default (no opt-in flag)", () => {
|
|
191
211
|
fs.mkdirSync(path.join(hqRoot, ".claude"));
|
|
192
212
|
fs.mkdirSync(path.join(hqRoot, "knowledge"));
|
|
193
|
-
writeCompany("foo",
|
|
213
|
+
writeCompany("foo", null);
|
|
214
|
+
writeCompany("bar", "cloud: true\n");
|
|
194
215
|
|
|
195
216
|
const out = rel(computePersonalVaultPaths(hqRoot));
|
|
196
|
-
// Top-level entries present
|
|
217
|
+
// Top-level entries present.
|
|
197
218
|
expect(out).toContain(".claude");
|
|
198
219
|
expect(out).toContain("knowledge");
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("composition: includeLocalCompanies=true adds opt-in company subdirs", () => {
|
|
203
|
-
fs.mkdirSync(path.join(hqRoot, "knowledge"));
|
|
204
|
-
writeCompany("foo", "cloud: false\n");
|
|
205
|
-
writeCompany("bar", "cloud: true\n");
|
|
206
|
-
|
|
207
|
-
const out = rel(computePersonalVaultPaths(hqRoot, { includeLocalCompanies: true }));
|
|
208
|
-
expect(out).toContain("knowledge");
|
|
220
|
+
// Local (non-cloud) company included; cloud-marked one excluded.
|
|
209
221
|
expect(out).toContain(path.join("companies", "foo"));
|
|
210
222
|
expect(out).not.toContain(path.join("companies", "bar"));
|
|
211
223
|
// The companies/ top-level entry itself is never present — only specific
|
|
212
|
-
//
|
|
224
|
+
// eligible subdirs. Preserves the invariant that share() never walks
|
|
213
225
|
// the whole companies/ tree under personalMode.
|
|
214
226
|
expect(out).not.toContain("companies");
|
|
215
227
|
});
|
|
216
228
|
|
|
217
|
-
it("composition:
|
|
218
|
-
writeCompany("foo",
|
|
219
|
-
writeCompany("synced",
|
|
229
|
+
it("composition: teamSyncedSlugs filter excludes cloud-backed companies", () => {
|
|
230
|
+
writeCompany("foo", null);
|
|
231
|
+
writeCompany("synced", null);
|
|
220
232
|
|
|
221
233
|
const out = rel(
|
|
222
234
|
computePersonalVaultPaths(hqRoot, {
|
|
223
|
-
includeLocalCompanies: true,
|
|
224
235
|
teamSyncedSlugs: new Set(["synced"]),
|
|
225
236
|
}),
|
|
226
237
|
);
|
|
@@ -230,7 +241,7 @@ describe("personal-vault helpers", () => {
|
|
|
230
241
|
|
|
231
242
|
it("composition: missing hqRoot returns []", () => {
|
|
232
243
|
fs.rmSync(hqRoot, { recursive: true });
|
|
233
|
-
expect(computePersonalVaultPaths(hqRoot
|
|
244
|
+
expect(computePersonalVaultPaths(hqRoot)).toEqual([]);
|
|
234
245
|
});
|
|
235
246
|
|
|
236
247
|
// ── companies/manifest.yaml inclusion ─────────────────────────────────
|
|
@@ -254,16 +265,14 @@ describe("personal-vault helpers", () => {
|
|
|
254
265
|
expect(out).toContain(".claude");
|
|
255
266
|
});
|
|
256
267
|
|
|
257
|
-
it("manifest: included even when
|
|
268
|
+
it("manifest: included even when there are no eligible company subdirs", () => {
|
|
258
269
|
fs.mkdirSync(path.join(hqRoot, "companies"));
|
|
259
270
|
fs.writeFileSync(
|
|
260
271
|
path.join(hqRoot, "companies", "manifest.yaml"),
|
|
261
272
|
"companies: {}\n",
|
|
262
273
|
);
|
|
263
274
|
|
|
264
|
-
const out = rel(
|
|
265
|
-
computePersonalVaultPaths(hqRoot, { includeLocalCompanies: false }),
|
|
266
|
-
);
|
|
275
|
+
const out = rel(computePersonalVaultPaths(hqRoot));
|
|
267
276
|
expect(out).toContain(path.join("companies", "manifest.yaml"));
|
|
268
277
|
});
|
|
269
278
|
|
|
@@ -303,18 +312,17 @@ describe("personal-vault helpers", () => {
|
|
|
303
312
|
expect(out).not.toContain(path.join("companies", "README.md"));
|
|
304
313
|
});
|
|
305
314
|
|
|
306
|
-
it("manifest: composes with
|
|
315
|
+
it("manifest: composes with local company subdirs", () => {
|
|
307
316
|
fs.mkdirSync(path.join(hqRoot, "companies"));
|
|
308
317
|
fs.writeFileSync(
|
|
309
318
|
path.join(hqRoot, "companies", "manifest.yaml"),
|
|
310
319
|
"companies:\n foo: {cloud_uid: null}\n team-acme: {cloud_uid: cmp_01XYZ}\n",
|
|
311
320
|
);
|
|
312
|
-
writeCompany("foo",
|
|
321
|
+
writeCompany("foo", null);
|
|
313
322
|
writeCompany("team-acme", "cloud: true\n");
|
|
314
323
|
|
|
315
324
|
const out = rel(
|
|
316
325
|
computePersonalVaultPaths(hqRoot, {
|
|
317
|
-
includeLocalCompanies: true,
|
|
318
326
|
teamSyncedSlugs: new Set(["team-acme"]),
|
|
319
327
|
}),
|
|
320
328
|
);
|
|
@@ -344,7 +352,7 @@ describe("personal-vault helpers", () => {
|
|
|
344
352
|
expect(computePersonalCompanySubdirs(hqRoot)).toEqual([]);
|
|
345
353
|
|
|
346
354
|
// Push set carries the real manifest, never the conflict litter.
|
|
347
|
-
const out = rel(computePersonalVaultPaths(hqRoot
|
|
355
|
+
const out = rel(computePersonalVaultPaths(hqRoot));
|
|
348
356
|
expect(out).toContain(path.join("companies", "manifest.yaml"));
|
|
349
357
|
expect(
|
|
350
358
|
out.some((p) => p.includes("manifest.yaml.conflict-")),
|
package/src/personal-vault.ts
CHANGED
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
* - `companies/`: the top-level `companies/` directory is never enumerated
|
|
18
18
|
* wholesale. Team-backed companies are synced by the runner's
|
|
19
19
|
* per-membership fanout (one bucket per company). Individual
|
|
20
|
-
* `companies/{slug}/` subdirs
|
|
21
|
-
* `computePersonalCompanySubdirs()` for
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `companies/{slug}/...` keys.
|
|
20
|
+
* `companies/{slug}/` subdirs are added back via
|
|
21
|
+
* `computePersonalCompanySubdirs()` for every LOCAL (non-cloud) company
|
|
22
|
+
* — i.e. any company that is not in the operator's team-synced membership
|
|
23
|
+
* set and is not explicitly marked `cloud: true` in `company.yaml`. Those
|
|
24
|
+
* land under the personal bucket as `companies/{slug}/...` keys. (The
|
|
25
|
+
* single `companies/manifest.yaml` is also carved in unconditionally as
|
|
26
|
+
* the routing source-of-truth.)
|
|
25
27
|
* - `repos/`, `workspace/`: per user directive — heavy local-only
|
|
26
28
|
* content (cloned remotes, session threads) that has no business in
|
|
27
29
|
* the personal vault.
|
|
@@ -63,19 +65,12 @@ export interface PersonalVaultOptions {
|
|
|
63
65
|
* Slugs of companies that already have their own team bucket (i.e. the
|
|
64
66
|
* operator has an active Membership row for them). These are excluded
|
|
65
67
|
* from the personal-bucket fallback so a single company's content never
|
|
66
|
-
* ends up in two buckets.
|
|
68
|
+
* ends up in two buckets. This membership-derived set is the single
|
|
69
|
+
* "cloud-backed" signal: when a company becomes cloud-true (gains an
|
|
70
|
+
* active membership), its slug lands here and the personal vault both
|
|
71
|
+
* stops pushing it and decommissions its stale copy.
|
|
67
72
|
*/
|
|
68
73
|
teamSyncedSlugs?: ReadonlySet<string>;
|
|
69
|
-
/**
|
|
70
|
-
* When true, walk `companies/` and include subdirs that explicitly opt in
|
|
71
|
-
* via `cloud: false` in their `company.yaml` (after applying the standard
|
|
72
|
-
* filter: exclude `_template`, exclude team-synced slugs). When false
|
|
73
|
-
* (the default), `companies/` is never enumerated — same as the legacy
|
|
74
|
-
* personal-vault scope. Gated behind a runtime flag so the new behavior
|
|
75
|
-
* stays opt-in until operators explicitly request it via
|
|
76
|
-
* `HQ_SYNC_LOCAL_COMPANIES_TO_PERSONAL=1`.
|
|
77
|
-
*/
|
|
78
|
-
includeLocalCompanies?: boolean;
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
/**
|
|
@@ -84,10 +79,12 @@ export interface PersonalVaultOptions {
|
|
|
84
79
|
* Two sources are concatenated:
|
|
85
80
|
* 1. Every top-level entry under `hqRoot` whose basename is NOT in
|
|
86
81
|
* `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL`.
|
|
87
|
-
* 2. Every `companies/{slug}/` subdir
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
82
|
+
* 2. Every local (non-cloud) `companies/{slug}/` subdir, after excluding
|
|
83
|
+
* (a) `_template`, (b) slugs already in `opts.teamSyncedSlugs`
|
|
84
|
+
* (cloud-backed — they sync to their own team bucket), and (c) any
|
|
85
|
+
* subdir explicitly marked `cloud: true` in its `company.yaml`.
|
|
86
|
+
* A company with no `company.yaml` (or one without a `cloud:` marker)
|
|
87
|
+
* is included — local companies sync to the personal vault by default.
|
|
91
88
|
*
|
|
92
89
|
* Order is whatever `fs.readdirSync` returns — share() doesn't care, and
|
|
93
90
|
* the per-file walk inside share() handles recursion uniformly. Missing
|
|
@@ -131,9 +128,10 @@ export function computePersonalVaultPaths(
|
|
|
131
128
|
} catch {
|
|
132
129
|
// Missing or unreadable — silently omit. Callers tolerate empty arrays.
|
|
133
130
|
}
|
|
134
|
-
const companySubdirs =
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const companySubdirs = computePersonalCompanySubdirs(
|
|
132
|
+
hqRoot,
|
|
133
|
+
opts.teamSyncedSlugs,
|
|
134
|
+
);
|
|
137
135
|
const continuity = computeContinuityPointerPaths(hqRoot);
|
|
138
136
|
const agency = computeAgencySyncPaths(hqRoot);
|
|
139
137
|
return [...topLevel, ...manifest, ...companySubdirs, ...continuity, ...agency];
|
|
@@ -273,10 +271,12 @@ export function computeAgencySyncPaths(hqRoot: string): string[] {
|
|
|
273
271
|
* (currently just `_template`).
|
|
274
272
|
* 3. The slug is NOT in `teamSyncedSlugs` (membership-backed slugs sync
|
|
275
273
|
* to their own bucket and must not be double-written).
|
|
276
|
-
* 4. `companies/{slug}/company.yaml`
|
|
277
|
-
* `cloud:
|
|
278
|
-
*
|
|
279
|
-
*
|
|
274
|
+
* 4. `companies/{slug}/company.yaml` does NOT contain an explicit
|
|
275
|
+
* `cloud: true` line. Local companies sync to the personal vault by
|
|
276
|
+
* default, so a missing/markerless `company.yaml` is INCLUDED; only an
|
|
277
|
+
* explicit `cloud: true` marker opts the directory OUT (belt-and-braces
|
|
278
|
+
* with `teamSyncedSlugs` for a company designated cloud-backed before
|
|
279
|
+
* its membership has resolved).
|
|
280
280
|
*
|
|
281
281
|
* Returns absolute paths.
|
|
282
282
|
*/
|
|
@@ -303,7 +303,7 @@ export function computePersonalCompanySubdirs(
|
|
|
303
303
|
continue;
|
|
304
304
|
}
|
|
305
305
|
if (!isDir) continue;
|
|
306
|
-
if (
|
|
306
|
+
if (companyHasCloudTrueMarker(subdir)) continue;
|
|
307
307
|
eligible.push(subdir);
|
|
308
308
|
}
|
|
309
309
|
return eligible;
|
|
@@ -311,25 +311,29 @@ export function computePersonalCompanySubdirs(
|
|
|
311
311
|
|
|
312
312
|
/**
|
|
313
313
|
* True iff `{subdir}/company.yaml` exists and contains an explicit
|
|
314
|
-
* top-level `cloud:
|
|
315
|
-
*
|
|
316
|
-
* this key, and it always emits `cloud: <bool>` at column zero
|
|
317
|
-
* indentation, no quoting, lowercase boolean, optional trailing
|
|
314
|
+
* top-level `cloud: true` line — the marker `/designate-team` writes when a
|
|
315
|
+
* company is promoted to cloud-backed. That command's awk is the only
|
|
316
|
+
* producer of this key, and it always emits `cloud: <bool>` at column zero
|
|
317
|
+
* with no indentation, no quoting, lowercase boolean, optional trailing
|
|
318
|
+
* comment. A `true` marker excludes the directory from the personal-vault
|
|
319
|
+
* fallback; everything else (missing file, no marker, `cloud: false`)
|
|
320
|
+
* leaves it eligible — local companies sync to the personal vault by default.
|
|
318
321
|
*
|
|
319
322
|
* Anchored at column 0 deliberately:
|
|
320
|
-
* - rejects `meta:\n cloud:
|
|
323
|
+
* - rejects `meta:\n cloud: true` (nested key, would be `meta.cloud`
|
|
321
324
|
* in a real YAML parser — false positive for us if we allowed
|
|
322
325
|
* arbitrary leading whitespace)
|
|
323
|
-
* - rejects `- cloud:
|
|
324
|
-
* - rejects flow mappings like `{ cloud:
|
|
326
|
+
* - rejects `- cloud: true` (list item, also a nested key)
|
|
327
|
+
* - rejects flow mappings like `{ cloud: true }`
|
|
325
328
|
*
|
|
326
|
-
* Other intentional rejections (lowercase `
|
|
327
|
-
* boolean spellings): `cloud:
|
|
328
|
-
* `cloud:
|
|
329
|
+
* Other intentional rejections (lowercase `true` only, no YAML-alt
|
|
330
|
+
* boolean spellings): `cloud: True`, `cloud: TRUE`, `cloud: yes`,
|
|
331
|
+
* `cloud: on`, `cloud: "true"`, `cloud: 'true'`. None of these are
|
|
329
332
|
* produced by `/designate-team`; matching them risks confusing
|
|
330
|
-
* round-trips with hand-edited YAML.
|
|
333
|
+
* round-trips with hand-edited YAML. (A misspelled marker fails OPEN —
|
|
334
|
+
* the company stays in the personal vault — which is the safe default.)
|
|
331
335
|
*/
|
|
332
|
-
function
|
|
336
|
+
function companyHasCloudTrueMarker(subdir: string): boolean {
|
|
333
337
|
const yamlPath = path.join(subdir, "company.yaml");
|
|
334
338
|
let text: string;
|
|
335
339
|
try {
|
|
@@ -337,5 +341,5 @@ function companyHasCloudFalseMarker(subdir: string): boolean {
|
|
|
337
341
|
} catch {
|
|
338
342
|
return false;
|
|
339
343
|
}
|
|
340
|
-
return /^cloud:\s*
|
|
344
|
+
return /^cloud:\s*true\b/m.test(text);
|
|
341
345
|
}
|