@indigoai-us/hq-cloud 6.12.5 → 6.13.1
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.d.ts +2 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +35 -2
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +49 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +39 -4
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +55 -0
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/share.js +15 -0
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +100 -0
- 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 +87 -3
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +204 -0
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/journal.d.ts +8 -2
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +32 -1
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +34 -1
- package/dist/journal.test.js.map +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +7 -3
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +12 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner.test.ts +61 -0
- package/src/bin/sync-runner.ts +57 -14
- package/src/cli/reindex.test.ts +60 -0
- package/src/cli/reindex.ts +47 -8
- package/src/cli/share.test.ts +119 -0
- package/src/cli/share.ts +21 -1
- package/src/cli/sync.test.ts +256 -0
- package/src/cli/sync.ts +97 -3
- package/src/journal.test.ts +44 -0
- package/src/journal.ts +37 -2
- package/src/skill-telemetry.test.ts +24 -0
- package/src/skill-telemetry.ts +6 -3
- package/src/types.ts +11 -1
package/src/journal.ts
CHANGED
|
@@ -26,6 +26,13 @@ export const TOMBSTONE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
26
26
|
/** Current journal schema version written by all v2-aware writers. */
|
|
27
27
|
export const JOURNAL_VERSION_CURRENT = "2" as const;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Retain bounded pull history per company. Scope-shrink logic only needs the
|
|
31
|
+
* newest record, but a small tail keeps diagnostics useful without letting
|
|
32
|
+
* long-running sync loops grow journals forever.
|
|
33
|
+
*/
|
|
34
|
+
export const MAX_PULLS_PER_COMPANY = 50;
|
|
35
|
+
|
|
29
36
|
const JOURNAL_FILE_PREFIX = "sync-journal.";
|
|
30
37
|
const JOURNAL_FILE_SUFFIX = ".json";
|
|
31
38
|
const JOURNAL_LAST_GOOD_SUFFIX = ".last-good";
|
|
@@ -517,13 +524,41 @@ export function lastPullRecord(
|
|
|
517
524
|
return best;
|
|
518
525
|
}
|
|
519
526
|
|
|
520
|
-
|
|
527
|
+
function trimPullRecords(journal: SyncJournal): void {
|
|
528
|
+
const pulls = journal.pulls;
|
|
529
|
+
if (!pulls || pulls.length === 0) return;
|
|
530
|
+
|
|
531
|
+
const byCompany = new Map<string, Array<{ record: PullRecord; index: number }>>();
|
|
532
|
+
pulls.forEach((record, index) => {
|
|
533
|
+
const entries = byCompany.get(record.companyUid);
|
|
534
|
+
if (entries) entries.push({ record, index });
|
|
535
|
+
else byCompany.set(record.companyUid, [{ record, index }]);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const keep = new Set<number>();
|
|
539
|
+
for (const entries of byCompany.values()) {
|
|
540
|
+
entries
|
|
541
|
+
.sort((a, b) => {
|
|
542
|
+
const byCompletedAt = b.record.completedAt.localeCompare(
|
|
543
|
+
a.record.completedAt,
|
|
544
|
+
);
|
|
545
|
+
return byCompletedAt === 0 ? b.index - a.index : byCompletedAt;
|
|
546
|
+
})
|
|
547
|
+
.slice(0, MAX_PULLS_PER_COMPANY)
|
|
548
|
+
.forEach((entry) => keep.add(entry.index));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
journal.pulls = pulls.filter((_, index) => keep.has(index));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Append a `PullRecord` (mutates `journal.pulls`) and cap retained history. */
|
|
521
555
|
export function appendPullRecord(
|
|
522
556
|
journal: SyncJournal,
|
|
523
557
|
record: PullRecord,
|
|
524
558
|
): void {
|
|
525
559
|
migrateToV2(journal);
|
|
526
560
|
journal.pulls!.push(record);
|
|
561
|
+
trimPullRecords(journal);
|
|
527
562
|
}
|
|
528
563
|
|
|
529
564
|
/**
|
|
@@ -537,7 +572,7 @@ export function appendPullRecord(
|
|
|
537
572
|
export function tombstoneEntry(
|
|
538
573
|
journal: SyncJournal,
|
|
539
574
|
relativePath: string,
|
|
540
|
-
reason: "scope_shrink" | "narrow_apply" | "manual",
|
|
575
|
+
reason: "scope_shrink" | "narrow_apply" | "manual" | "local-delete",
|
|
541
576
|
now: string = new Date().toISOString(),
|
|
542
577
|
): void {
|
|
543
578
|
const entry = journal.files[relativePath];
|
|
@@ -226,6 +226,30 @@ describe("extractCodexSkillToolEvents (Codex model-driven SKILL.md reads)", () =
|
|
|
226
226
|
expect(ev.source).toBe("model");
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
+
it("matches normal Codex skill paths without backtracking ambiguity", () => {
|
|
230
|
+
expect(
|
|
231
|
+
extractCodexSkillToolEvents(
|
|
232
|
+
execEnd("cat /home/u/.claude/skills/hq/land/SKILL.md"),
|
|
233
|
+
ctx,
|
|
234
|
+
)[0].skill,
|
|
235
|
+
).toBe("land");
|
|
236
|
+
expect(
|
|
237
|
+
extractCodexSkillToolEvents(
|
|
238
|
+
execEnd("cat /home/u/.claude/skills/a/b/c/SKILL.md"),
|
|
239
|
+
ctx,
|
|
240
|
+
)[0].skill,
|
|
241
|
+
).toBe("c");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("returns quickly for pathological non-matching skills paths", () => {
|
|
245
|
+
const cmd = "/home/u/.claude/skills/" + "a/".repeat(2000) + "z z";
|
|
246
|
+
const started = Date.now();
|
|
247
|
+
const events = extractCodexSkillToolEvents(execEnd(cmd), ctx);
|
|
248
|
+
const elapsedMs = Date.now() - started;
|
|
249
|
+
expect(events).toEqual([]);
|
|
250
|
+
expect(elapsedMs).toBeLessThan(200);
|
|
251
|
+
});
|
|
252
|
+
|
|
229
253
|
it("absolute and relative SKILL.md paths both resolve", () => {
|
|
230
254
|
expect(
|
|
231
255
|
extractCodexSkillToolEvents(execEnd("cat /abs/path/.agents/skills/quiz/SKILL.md"), ctx)[0].skill,
|
package/src/skill-telemetry.ts
CHANGED
|
@@ -342,10 +342,11 @@ export function extractCodexSkillEvents(
|
|
|
342
342
|
* may be nested arbitrarily deep (`.agents/skills/…`, `.codex/skills/hq/…`),
|
|
343
343
|
* and `<name>` is always the directory immediately above SKILL.md — captured
|
|
344
344
|
* as the last segment so the bridge's `skills/hq/<name>/` layout resolves to
|
|
345
|
-
* `<name>`, not `hq`. The
|
|
346
|
-
*
|
|
345
|
+
* `<name>`, not `hq`. The segment classes deliberately exclude `/` so segment
|
|
346
|
+
* boundaries are unambiguous and non-matching `skills/...` paths cannot
|
|
347
|
+
* catastrophically backtrack. */
|
|
347
348
|
const CODEX_SKILL_FILE =
|
|
348
|
-
/(?:^|\/)skills\/(?:[
|
|
349
|
+
/(?:^|\/)skills\/(?:[^/\s'"]+\/)*?([^/\s'"]+)\/SKILL\.md\b/;
|
|
349
350
|
|
|
350
351
|
/** Pull the shell command string out of a Codex `exec_command_end` `command`,
|
|
351
352
|
* which is `["/bin/zsh", "-lc", "<cmd>"]` (array) on the runtimes we see, but
|
|
@@ -488,6 +489,8 @@ export function extractCodexSkillToolEvents(
|
|
|
488
489
|
const exec = codexExecParams(obj, payload, ctx);
|
|
489
490
|
if (!exec) return [];
|
|
490
491
|
const { cmd } = exec;
|
|
492
|
+
// Real exec commands are tiny; avoid running regexes over pathological rows.
|
|
493
|
+
if (cmd.length > 100_000) return [];
|
|
491
494
|
const m = CODEX_SKILL_FILE.exec(cmd);
|
|
492
495
|
if (!m) return [];
|
|
493
496
|
// Confirm the exec is a read of the skill file, not a write to it. Codex's own
|
package/src/types.ts
CHANGED
|
@@ -71,7 +71,7 @@ export interface JournalEntry {
|
|
|
71
71
|
* the same paths as orphans, then garbage-collected.
|
|
72
72
|
*/
|
|
73
73
|
removedAt?: string;
|
|
74
|
-
removedReason?: "scope_shrink" | "narrow_apply" | "manual";
|
|
74
|
+
removedReason?: "scope_shrink" | "narrow_apply" | "manual" | "local-delete";
|
|
75
75
|
/**
|
|
76
76
|
* Durable automatic-pull retention marker. Set when a scope shrink keeps an
|
|
77
77
|
* out-of-scope caller-authored or unknown-author entry on disk instead of
|
|
@@ -80,6 +80,16 @@ export interface JournalEntry {
|
|
|
80
80
|
* the entry becomes in-scope again.
|
|
81
81
|
*/
|
|
82
82
|
outOfScopeProtected?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Journal-honesty marker. Set when a conflict was resolved by KEEPing a local
|
|
85
|
+
* copy that diverges from the remote: the entry still records the remote
|
|
86
|
+
* `remoteEtag` (so the conflict can't re-fire — the #137 invariant), but the
|
|
87
|
+
* local bytes never matched that remote. The currency-gated delete planner
|
|
88
|
+
* refuses to propagate a delete for such an entry — its currency would
|
|
89
|
+
* falsely "match" on HEAD, and deleting would destroy the divergent remote
|
|
90
|
+
* version. Cleared by any genuine download (which makes local == remote).
|
|
91
|
+
*/
|
|
92
|
+
localDiverges?: boolean;
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
/**
|