@indigoai-us/hq-cloud 6.13.3 → 6.13.4

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.
Files changed (37) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +15 -0
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-rollup.d.ts +7 -0
  5. package/dist/bin/sync-runner-rollup.d.ts.map +1 -1
  6. package/dist/bin/sync-runner-rollup.js +15 -0
  7. package/dist/bin/sync-runner-rollup.js.map +1 -1
  8. package/dist/bin/sync-runner.d.ts +7 -0
  9. package/dist/bin/sync-runner.d.ts.map +1 -1
  10. package/dist/bin/sync-runner.js +27 -3
  11. package/dist/bin/sync-runner.js.map +1 -1
  12. package/dist/bin/sync-runner.test.js +73 -0
  13. package/dist/bin/sync-runner.test.js.map +1 -1
  14. package/dist/cli/share.js +15 -2
  15. package/dist/cli/share.js.map +1 -1
  16. package/dist/cli/share.test.js +39 -0
  17. package/dist/cli/share.test.js.map +1 -1
  18. package/dist/cli/sync.d.ts +22 -0
  19. package/dist/cli/sync.d.ts.map +1 -1
  20. package/dist/cli/sync.js +14 -0
  21. package/dist/cli/sync.js.map +1 -1
  22. package/dist/qmd-reindex.d.ts +14 -0
  23. package/dist/qmd-reindex.d.ts.map +1 -1
  24. package/dist/qmd-reindex.js +93 -19
  25. package/dist/qmd-reindex.js.map +1 -1
  26. package/dist/qmd-reindex.test.js +47 -0
  27. package/dist/qmd-reindex.test.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/bin/sync-runner-company.ts +21 -0
  30. package/src/bin/sync-runner-rollup.ts +23 -0
  31. package/src/bin/sync-runner.test.ts +90 -0
  32. package/src/bin/sync-runner.ts +37 -5
  33. package/src/cli/share.test.ts +46 -0
  34. package/src/cli/share.ts +14 -2
  35. package/src/cli/sync.ts +40 -0
  36. package/src/qmd-reindex.test.ts +50 -0
  37. package/src/qmd-reindex.ts +124 -18
@@ -45,9 +45,24 @@ const defaultExec: QmdExec = (args) => {
45
45
  return { status: res.status, stdout: res.stdout ?? "" };
46
46
  };
47
47
 
48
+ const DEFAULT_CHANGED_PATH_DEBOUNCE_MS = 60_000;
49
+
48
50
  export interface ReindexOptions {
49
51
  /** Rebuild embeddings too (slow). Default false — lexical-only. */
50
52
  embed?: boolean;
53
+ /**
54
+ * HQ-relative paths changed by sync. When omitted, preserves the historical
55
+ * behavior and runs QMD for any sync caller that invokes this function.
56
+ */
57
+ changedPaths?: string[];
58
+ /** Force a collection registration refresh even when content is not dirty. */
59
+ forceCollectionRefresh?: boolean;
60
+ /** Delay dirty updates by this many ms while keeping a persistent marker. */
61
+ debounceMs?: number;
62
+ /** Clock override for deterministic debounce tests. */
63
+ nowMs?: number;
64
+ /** State file override for tests. */
65
+ statePath?: string;
51
66
  /** Command runner override for tests. */
52
67
  exec?: QmdExec;
53
68
  /** `existsSync` override for tests. */
@@ -74,15 +89,42 @@ export interface ReindexOptions {
74
89
  export function reindexAfterSync(
75
90
  hqRoot: string,
76
91
  opts: ReindexOptions = {},
77
- ): { qmdAvailable: boolean; collectionsAdded: string[]; updated: boolean; embedded: boolean } {
92
+ ): {
93
+ qmdAvailable: boolean;
94
+ collectionsAdded: string[];
95
+ updated: boolean;
96
+ embedded: boolean;
97
+ pendingDirty: boolean;
98
+ } {
78
99
  const exec = opts.exec ?? defaultExec;
79
100
  const existsSync = opts.existsSync ?? fs.existsSync;
80
- const result = { qmdAvailable: false, collectionsAdded: [] as string[], updated: false, embedded: false };
101
+ const result = {
102
+ qmdAvailable: false,
103
+ collectionsAdded: [] as string[],
104
+ updated: false,
105
+ embedded: false,
106
+ pendingDirty: false,
107
+ };
81
108
 
82
109
  try {
83
110
  // Guard: only operate on a real HQ tree.
84
111
  if (!existsSync(path.join(hqRoot, "core", "core.yaml"))) return result;
85
112
 
113
+ const statePath = opts.statePath ?? path.join(hqRoot, ".hq", "qmd-reindex-state.json");
114
+ const state = readState(statePath);
115
+ const changedPaths = opts.changedPaths;
116
+ const dirtyFromChanges =
117
+ changedPaths === undefined ||
118
+ changedPaths.some(isKnowledgeMarkdownPath);
119
+ const registrationMayBeStale =
120
+ opts.forceCollectionRefresh === true ||
121
+ changedPaths === undefined ||
122
+ dirtyFromChanges;
123
+ const pendingDirty = state.pendingDirty === true;
124
+ if (!dirtyFromChanges && !registrationMayBeStale && !pendingDirty) {
125
+ return result;
126
+ }
127
+
86
128
  // Guard: qmd must be installed. `qmd collection list` doubles as the
87
129
  // availability probe AND the source for which collections already exist.
88
130
  const list = exec(["collection", "list"]);
@@ -91,26 +133,62 @@ export function reindexAfterSync(
91
133
  const existingCollections = list.stdout;
92
134
 
93
135
  // 1. Auto-register missing company knowledge collections.
94
- const companiesDir = path.join(hqRoot, "companies");
95
- const slugs = (opts.readCompanies ?? defaultReadCompanies)(companiesDir);
96
- for (const slug of slugs) {
97
- const knowledgeDir = path.join(companiesDir, slug, "knowledge");
98
- if (!existsSync(knowledgeDir)) continue;
99
- const hasMd = (opts.hasIndexableMarkdown ?? defaultHasIndexableMarkdown)(knowledgeDir);
100
- if (!hasMd) continue;
101
- // Already registered? qmd collection URIs look like `qmd://<slug>/`.
102
- if (existingCollections.includes(`qmd://${slug}/`)) continue;
103
-
104
- const add = exec(["collection", "add", knowledgeDir, "--name", slug, "--mask", "**/*.md"]);
105
- if (add.status === 0) {
106
- exec(["context", "add", `qmd://${slug}`, `Knowledge base for ${slug}.`]);
107
- result.collectionsAdded.push(slug);
136
+ if (registrationMayBeStale) {
137
+ const companiesDir = path.join(hqRoot, "companies");
138
+ const slugs = (opts.readCompanies ?? defaultReadCompanies)(companiesDir);
139
+ for (const slug of slugs) {
140
+ const knowledgeDir = path.join(companiesDir, slug, "knowledge");
141
+ if (!existsSync(knowledgeDir)) continue;
142
+ const hasMd = (opts.hasIndexableMarkdown ?? defaultHasIndexableMarkdown)(knowledgeDir);
143
+ if (!hasMd) continue;
144
+ // Already registered? qmd collection URIs look like `qmd://<slug>/`.
145
+ if (existingCollections.includes(`qmd://${slug}/`)) continue;
146
+
147
+ const add = exec(["collection", "add", knowledgeDir, "--name", slug, "--mask", "**/*.md"]);
148
+ if (add.status === 0) {
149
+ exec(["context", "add", `qmd://${slug}`, `Knowledge base for ${slug}.`]);
150
+ result.collectionsAdded.push(slug);
151
+ }
108
152
  }
109
153
  }
110
154
 
111
155
  // 2. Incremental lexical reindex.
112
- const update = exec(["update"]);
113
- result.updated = update.status === 0;
156
+ const shouldUpdate = dirtyFromChanges || pendingDirty;
157
+ if (shouldUpdate) {
158
+ const nowMs = opts.nowMs ?? Date.now();
159
+ const debounceMs =
160
+ opts.debounceMs ??
161
+ (changedPaths === undefined ? 0 : DEFAULT_CHANGED_PATH_DEBOUNCE_MS);
162
+ const pendingSinceMs =
163
+ pendingDirty && typeof state.pendingSinceMs === "number"
164
+ ? state.pendingSinceMs
165
+ : nowMs;
166
+ if (debounceMs > 0 && nowMs - pendingSinceMs < debounceMs) {
167
+ writeState(statePath, {
168
+ ...state,
169
+ pendingDirty: true,
170
+ pendingSinceMs,
171
+ });
172
+ result.pendingDirty = true;
173
+ return result;
174
+ }
175
+
176
+ const update = exec(["update"]);
177
+ result.updated = update.status === 0;
178
+ if (result.updated) {
179
+ writeState(statePath, {
180
+ pendingDirty: false,
181
+ lastSuccessMs: nowMs,
182
+ });
183
+ } else {
184
+ writeState(statePath, {
185
+ ...state,
186
+ pendingDirty: true,
187
+ pendingSinceMs,
188
+ });
189
+ result.pendingDirty = true;
190
+ }
191
+ }
114
192
 
115
193
  // 3. Embeddings only on explicit request.
116
194
  if (opts.embed) {
@@ -134,6 +212,34 @@ export function reindexAfterSync(
134
212
  return result;
135
213
  }
136
214
 
215
+ interface QmdReindexState {
216
+ pendingDirty?: boolean;
217
+ pendingSinceMs?: number;
218
+ lastSuccessMs?: number;
219
+ }
220
+
221
+ function readState(statePath: string): QmdReindexState {
222
+ try {
223
+ return JSON.parse(fs.readFileSync(statePath, "utf8")) as QmdReindexState;
224
+ } catch {
225
+ return {};
226
+ }
227
+ }
228
+
229
+ function writeState(statePath: string, state: QmdReindexState): void {
230
+ try {
231
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
232
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
233
+ } catch {
234
+ // State is an optimization/guarantee aid; QMD failures must not fail sync.
235
+ }
236
+ }
237
+
238
+ function isKnowledgeMarkdownPath(relPath: string): boolean {
239
+ const normalized = relPath.split(path.sep).join("/");
240
+ return /^companies\/[^/]+\/knowledge\/.+\.md$/i.test(normalized);
241
+ }
242
+
137
243
  function defaultReadCompanies(companiesDir: string): string[] {
138
244
  try {
139
245
  return fs