@indigoai-us/hq-cli 5.77.14 → 5.78.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.
Files changed (308) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
  3. package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
  4. package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
  5. package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
  6. package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
  7. package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
  8. package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
  9. package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
  10. package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
  11. package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
  12. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
  13. package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
  14. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
  15. package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
  16. package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
  17. package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
  18. package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
  19. package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
  20. package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
  21. package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
  22. package/assets/scaffold/core/scripts/worktree.sh +136 -0
  23. package/dist/commands/agents.js +28 -12
  24. package/dist/commands/api-keys.js +100 -20
  25. package/dist/commands/core.d.ts +90 -0
  26. package/dist/commands/core.js +279 -0
  27. package/dist/commands/files-browse.d.ts +21 -0
  28. package/dist/commands/files-browse.js +145 -24
  29. package/dist/commands/files.js +33 -7
  30. package/dist/commands/members.js +16 -5
  31. package/dist/commands/pack-install.d.ts +14 -0
  32. package/dist/commands/pack-install.js +42 -2
  33. package/dist/commands/pkg-install.js +39 -2
  34. package/dist/commands/run.js +6 -2
  35. package/dist/commands/secrets.js +120 -65
  36. package/dist/main.js +6 -0
  37. package/dist/utils/api-key-command-gate.d.ts +45 -0
  38. package/dist/utils/api-key-command-gate.js +51 -0
  39. package/dist/utils/hq-roots.d.ts +91 -0
  40. package/dist/utils/hq-roots.js +212 -0
  41. package/dist/utils/resolve-vault-credential.d.ts +63 -3
  42. package/dist/utils/resolve-vault-credential.js +152 -6
  43. package/dist/utils/run-bundled-script.d.ts +66 -0
  44. package/dist/utils/run-bundled-script.js +80 -0
  45. package/dist/utils/vault-api.d.ts +21 -0
  46. package/dist/utils/vault-api.js +74 -3
  47. package/package.json +7 -1
  48. package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
  49. package/.github/workflows/ci.yml +0 -46
  50. package/.github/workflows/publish.yml +0 -36
  51. package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
  52. package/e2e/cli.test.ts +0 -128
  53. package/eslint.config.js +0 -37
  54. package/pnpm-workspace.yaml +0 -4
  55. package/scripts/chmod-bins.mjs +0 -49
  56. package/scripts/generate-dsn.mjs +0 -26
  57. package/scripts/generate-scan-packages-table.mjs +0 -113
  58. package/scripts/smoke-sources-signals.sh +0 -103
  59. package/src/bin/hq-auth-refresh.ts +0 -43
  60. package/src/cli-version.ts +0 -13
  61. package/src/commands/__fixtures__/make-tar.ts +0 -126
  62. package/src/commands/_patterns.ts +0 -10
  63. package/src/commands/add.ts +0 -75
  64. package/src/commands/agents.test.ts +0 -527
  65. package/src/commands/agents.ts +0 -923
  66. package/src/commands/api-keys.test.ts +0 -291
  67. package/src/commands/api-keys.ts +0 -382
  68. package/src/commands/artifact-verify.test.ts +0 -177
  69. package/src/commands/auth.test.ts +0 -82
  70. package/src/commands/auth.ts +0 -213
  71. package/src/commands/billing.test.ts +0 -158
  72. package/src/commands/billing.ts +0 -146
  73. package/src/commands/channels.test.ts +0 -74
  74. package/src/commands/channels.ts +0 -91
  75. package/src/commands/cloud-demote.test.ts +0 -401
  76. package/src/commands/cloud-demote.ts +0 -278
  77. package/src/commands/cloud-provision.test.ts +0 -1117
  78. package/src/commands/cloud-provision.ts +0 -942
  79. package/src/commands/cloud.pull-all.test.ts +0 -711
  80. package/src/commands/cloud.pull-per-company.test.ts +0 -188
  81. package/src/commands/cloud.push-all.test.ts +0 -485
  82. package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
  83. package/src/commands/cloud.selectors.test.ts +0 -208
  84. package/src/commands/cloud.status.test.ts +0 -160
  85. package/src/commands/cloud.test.ts +0 -41
  86. package/src/commands/cloud.ts +0 -2130
  87. package/src/commands/company.test.ts +0 -202
  88. package/src/commands/company.ts +0 -148
  89. package/src/commands/creators.test.ts +0 -60
  90. package/src/commands/creators.ts +0 -117
  91. package/src/commands/crm.test.ts +0 -179
  92. package/src/commands/crm.ts +0 -236
  93. package/src/commands/db-migrate.ts +0 -55
  94. package/src/commands/db-provision.ts +0 -114
  95. package/src/commands/db-sql.ts +0 -124
  96. package/src/commands/db-status.ts +0 -108
  97. package/src/commands/db.ts +0 -26
  98. package/src/commands/dm.test.ts +0 -748
  99. package/src/commands/dm.ts +0 -1226
  100. package/src/commands/feedback.test.ts +0 -413
  101. package/src/commands/feedback.ts +0 -169
  102. package/src/commands/files-browse.test.ts +0 -1249
  103. package/src/commands/files-browse.ts +0 -1506
  104. package/src/commands/files-delete.test.ts +0 -484
  105. package/src/commands/files-recovery.test.ts +0 -370
  106. package/src/commands/files.test.ts +0 -722
  107. package/src/commands/files.ts +0 -1446
  108. package/src/commands/group-grants.test.ts +0 -330
  109. package/src/commands/group-grants.ts +0 -455
  110. package/src/commands/groups.test.ts +0 -44
  111. package/src/commands/groups.ts +0 -368
  112. package/src/commands/integrations.test.ts +0 -633
  113. package/src/commands/integrations.ts +0 -507
  114. package/src/commands/list.ts +0 -85
  115. package/src/commands/login.ts +0 -52
  116. package/src/commands/logout.ts +0 -30
  117. package/src/commands/marketplace-install.test.ts +0 -543
  118. package/src/commands/marketplace-security.test.ts +0 -646
  119. package/src/commands/mcp-registration.test.ts +0 -2787
  120. package/src/commands/mcp-registration.ts +0 -2612
  121. package/src/commands/mcp-status.test.ts +0 -483
  122. package/src/commands/mcp-status.ts +0 -575
  123. package/src/commands/mcp-status.us011.test.ts +0 -243
  124. package/src/commands/meetings.test.ts +0 -522
  125. package/src/commands/meetings.ts +0 -789
  126. package/src/commands/members.test.ts +0 -1474
  127. package/src/commands/members.ts +0 -1181
  128. package/src/commands/onboard-identity-guard.test.ts +0 -99
  129. package/src/commands/onboard-identity-guard.ts +0 -103
  130. package/src/commands/onboard-join.test.ts +0 -96
  131. package/src/commands/onboard-join.ts +0 -101
  132. package/src/commands/onboard-warning.test.ts +0 -26
  133. package/src/commands/onboard-warning.ts +0 -12
  134. package/src/commands/onboard.ts +0 -267
  135. package/src/commands/outposts-heartbeat.test.ts +0 -299
  136. package/src/commands/outposts-heartbeat.ts +0 -310
  137. package/src/commands/outposts-replica-sync.test.ts +0 -272
  138. package/src/commands/outposts-self-deploy.test.ts +0 -243
  139. package/src/commands/outposts.test.ts +0 -1039
  140. package/src/commands/outposts.ts +0 -1935
  141. package/src/commands/pack-install-secret-authorization.test.ts +0 -115
  142. package/src/commands/pack-install.test.ts +0 -1469
  143. package/src/commands/pack-install.ts +0 -2256
  144. package/src/commands/pack-update-cache.test.ts +0 -149
  145. package/src/commands/packs-update-api-key.test.ts +0 -105
  146. package/src/commands/packs.test.ts +0 -88
  147. package/src/commands/packs.ts +0 -622
  148. package/src/commands/people.test.ts +0 -807
  149. package/src/commands/people.ts +0 -422
  150. package/src/commands/pkg-install.dispatch.test.ts +0 -103
  151. package/src/commands/pkg-install.ts +0 -221
  152. package/src/commands/pkg-list.test.ts +0 -79
  153. package/src/commands/pkg-list.ts +0 -13
  154. package/src/commands/pkg-remove.ts +0 -71
  155. package/src/commands/pkg-update.ts +0 -189
  156. package/src/commands/publish.test.ts +0 -650
  157. package/src/commands/publish.ts +0 -580
  158. package/src/commands/reindex.test.ts +0 -259
  159. package/src/commands/reindex.ts +0 -274
  160. package/src/commands/rescue.test.ts +0 -39
  161. package/src/commands/rescue.ts +0 -210
  162. package/src/commands/run.env-local.test.ts +0 -88
  163. package/src/commands/run.test.ts +0 -45
  164. package/src/commands/run.ts +0 -190
  165. package/src/commands/safe-extract.test.ts +0 -459
  166. package/src/commands/safe-extract.ts +0 -444
  167. package/src/commands/secrets-scope.test.ts +0 -56
  168. package/src/commands/secrets-scope.ts +0 -32
  169. package/src/commands/secrets.parse-destination.test.ts +0 -38
  170. package/src/commands/secrets.test.ts +0 -2050
  171. package/src/commands/secrets.ts +0 -2249
  172. package/src/commands/signals.ts +0 -351
  173. package/src/commands/skill.test.ts +0 -770
  174. package/src/commands/skill.ts +0 -796
  175. package/src/commands/sources.ts +0 -362
  176. package/src/commands/sync-mode.test.ts +0 -366
  177. package/src/commands/sync-mode.ts +0 -387
  178. package/src/commands/sync-narrow.test.ts +0 -633
  179. package/src/commands/sync-narrow.ts +0 -555
  180. package/src/commands/sync.ts +0 -165
  181. package/src/commands/team-sync.ts +0 -628
  182. package/src/commands/update.ts +0 -71
  183. package/src/commands/whoami.ts +0 -83
  184. package/src/commands/workers.test.ts +0 -158
  185. package/src/commands/workers.ts +0 -301
  186. package/src/index.test.ts +0 -32
  187. package/src/index.ts +0 -20
  188. package/src/lib/db/control-plane.test.ts +0 -59
  189. package/src/lib/db/control-plane.ts +0 -113
  190. package/src/lib/db/local.test.ts +0 -81
  191. package/src/lib/db/local.ts +0 -148
  192. package/src/lib/db/migrate.test.ts +0 -133
  193. package/src/lib/db/migrate.ts +0 -137
  194. package/src/lib/db/paths.test.ts +0 -112
  195. package/src/lib/db/paths.ts +0 -128
  196. package/src/lib/db/remote-engine.test.ts +0 -44
  197. package/src/lib/db/remote-engine.ts +0 -148
  198. package/src/lib/db/remote-sql.test.ts +0 -32
  199. package/src/lib/db/remote-sql.ts +0 -62
  200. package/src/lib/db/sql.test.ts +0 -106
  201. package/src/lib/db/sql.ts +0 -192
  202. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  203. package/src/lib/local-tree-diff.test.ts +0 -301
  204. package/src/lib/local-tree-diff.ts +0 -362
  205. package/src/lib/narrow-hint-banner.test.ts +0 -235
  206. package/src/lib/narrow-hint-banner.ts +0 -212
  207. package/src/lib/plan-limit-nag.test.ts +0 -317
  208. package/src/lib/plan-limit-nag.ts +0 -264
  209. package/src/main.ts +0 -371
  210. package/src/node-preflight.test.ts +0 -60
  211. package/src/node-preflight.ts +0 -67
  212. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  213. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  214. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  215. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  216. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  217. package/src/outpost/session-heartbeat.test.ts +0 -459
  218. package/src/outpost/session-heartbeat.ts +0 -877
  219. package/src/packaging.test.ts +0 -109
  220. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  221. package/src/run/discover-schemas.test.ts +0 -153
  222. package/src/run/discover-schemas.ts +0 -79
  223. package/src/run/hq-plugin.test.ts +0 -371
  224. package/src/run/hq-plugin.ts +0 -277
  225. package/src/run/varlock-shape.test.ts +0 -57
  226. package/src/schemas/hq-package.schema.json +0 -155
  227. package/src/sentry-before-send.test.ts +0 -359
  228. package/src/sentry-before-send.ts +0 -221
  229. package/src/sentry-epipe.test.ts +0 -37
  230. package/src/sentry-release.test.ts +0 -54
  231. package/src/sentry.test.ts +0 -24
  232. package/src/sentry.ts +0 -54
  233. package/src/strategies/link.ts +0 -62
  234. package/src/strategies/merge.ts +0 -142
  235. package/src/types.ts +0 -146
  236. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  237. package/src/utils/auth-error.test.ts +0 -40
  238. package/src/utils/auth-error.ts +0 -42
  239. package/src/utils/billing-gate.test.ts +0 -95
  240. package/src/utils/billing-gate.ts +0 -228
  241. package/src/utils/breadcrumb-buffer.ts +0 -18
  242. package/src/utils/cli-telemetry.test.ts +0 -153
  243. package/src/utils/cli-telemetry.ts +0 -61
  244. package/src/utils/cognito-session.machine.test.ts +0 -109
  245. package/src/utils/cognito-session.refresh.test.ts +0 -84
  246. package/src/utils/cognito-session.test.ts +0 -328
  247. package/src/utils/cognito-session.ts +0 -448
  248. package/src/utils/company-selection-error.test.ts +0 -42
  249. package/src/utils/company-selection-error.ts +0 -45
  250. package/src/utils/contribution-table.ts +0 -83
  251. package/src/utils/environmental-error.test.ts +0 -45
  252. package/src/utils/environmental-error.ts +0 -39
  253. package/src/utils/epipe.test.ts +0 -28
  254. package/src/utils/epipe.ts +0 -29
  255. package/src/utils/expected-cli-error.test.ts +0 -28
  256. package/src/utils/expected-cli-error.ts +0 -39
  257. package/src/utils/feedback-diagnostics.test.ts +0 -183
  258. package/src/utils/feedback-diagnostics.ts +0 -123
  259. package/src/utils/feedback-screenshots.test.ts +0 -134
  260. package/src/utils/feedback-screenshots.ts +0 -124
  261. package/src/utils/feedback-versions.test.ts +0 -98
  262. package/src/utils/feedback-versions.ts +0 -68
  263. package/src/utils/git.ts +0 -74
  264. package/src/utils/integrity.ts +0 -59
  265. package/src/utils/intercepted-process-exit.test.ts +0 -37
  266. package/src/utils/intercepted-process-exit.ts +0 -36
  267. package/src/utils/login-provider.test.ts +0 -42
  268. package/src/utils/login-provider.ts +0 -30
  269. package/src/utils/manifest.test.ts +0 -167
  270. package/src/utils/manifest.ts +0 -131
  271. package/src/utils/pack-contributions.test.ts +0 -603
  272. package/src/utils/pack-contributions.ts +0 -450
  273. package/src/utils/people.ts +0 -215
  274. package/src/utils/registry-client.ts +0 -204
  275. package/src/utils/registry.ts +0 -90
  276. package/src/utils/resolve-vault-credential.test.ts +0 -69
  277. package/src/utils/resolve-vault-credential.ts +0 -60
  278. package/src/utils/sandbox-runner-client.test.ts +0 -279
  279. package/src/utils/sandbox-runner-client.ts +0 -260
  280. package/src/utils/secrets-cache.ts +0 -183
  281. package/src/utils/sentry-identity.ts +0 -45
  282. package/src/utils/settle-with-timeout.test.ts +0 -21
  283. package/src/utils/settle-with-timeout.ts +0 -22
  284. package/src/utils/user-yaml-error.test.ts +0 -24
  285. package/src/utils/user-yaml-error.ts +0 -30
  286. package/src/utils/vault-api.test.ts +0 -536
  287. package/src/utils/vault-api.ts +0 -382
  288. package/src/utils/version-check.test.ts +0 -176
  289. package/src/utils/version-check.ts +0 -155
  290. package/src/utils/version-gate.test.ts +0 -1024
  291. package/src/utils/version-gate.ts +0 -702
  292. package/test/commands/db-tenant-isolation.test.ts +0 -94
  293. package/test/commands/db.test.ts +0 -85
  294. package/test/commands/signals.test.ts +0 -207
  295. package/test/commands/sources.test.ts +0 -231
  296. package/test/e2e/smoke-install-mcp.sh +0 -113
  297. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  298. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  299. package/test/fixtures/signals/action_item/sample.md +0 -16
  300. package/test/fixtures/signals/summary/sample.md +0 -12
  301. package/test/fixtures/sources/meetings/sample.md +0 -25
  302. package/test/helpers/cli-runner.ts +0 -150
  303. package/test/helpers/s3-list-mock.ts +0 -79
  304. package/test/helpers/vault-service-mock.ts +0 -295
  305. package/test/sources-signals/smoke.test.ts +0 -228
  306. package/tsconfig.json +0 -20
  307. package/vitest.config.ts +0 -11
  308. package/vitest.e2e.config.ts +0 -7
package/src/lib/db/sql.ts DELETED
@@ -1,192 +0,0 @@
1
- /**
2
- * Local SQL execution helpers (vault-databases US-004).
3
- *
4
- * Company scope is path-bound via resolveLocalDbPath — callers must pass the
5
- * resolved company slug from HQ session/flags. Path overrides that open
6
- * another company's DB are denied unless an explicit dangerous flag is set.
7
- */
8
-
9
- import Database from "better-sqlite3";
10
-
11
- import type { LocalDb } from "./local.js";
12
- import { openLocalDb } from "./local.js";
13
- import {
14
- normalizeCompanySlugForLocalDb,
15
- resolveLocalDbDir,
16
- resolveLocalDbPath,
17
- type LocalDbPathEnv,
18
- } from "./paths.js";
19
-
20
- export interface SqlRunOptions extends LocalDbPathEnv {
21
- company: string;
22
- sql: string;
23
- /** Allow INSERT/UPDATE/DELETE/DDL. Default false (read-only). */
24
- write?: boolean;
25
- /**
26
- * Dangerous: open an absolute DB path instead of the company canonical path.
27
- * Denied unless allowCrossCompanyPath is true.
28
- */
29
- dbPathOverride?: string;
30
- /** Explicit dangerous flag to open a non-canonical local DB path. Default false. */
31
- allowCrossCompanyPath?: boolean;
32
- /** Output format */
33
- format?: "jsonl" | "table";
34
- }
35
-
36
- export interface SqlRunResult {
37
- columns: string[];
38
- rows: Record<string, unknown>[];
39
- changes: number;
40
- readonly: boolean;
41
- }
42
-
43
- const WRITE_PATTERN =
44
- /^\s*(INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|ALTER|TRUNCATE|REINDEX|VACUUM|BEGIN|COMMIT|ROLLBACK)\b/i;
45
-
46
- /** Always forbidden — opens other files/DBs and breaks company path isolation. */
47
- const FORBIDDEN_SQL_PATTERN =
48
- /\b(ATTACH|DETACH|LOAD_EXTENSION)\b/i;
49
-
50
- export function stripSqlNoise(sql: string): string {
51
- return sql
52
- .replace(/^\s*--[^\n]*\n/gm, "")
53
- .replace(/^\s*\/\*[\s\S]*?\*\//gm, "")
54
- .trim();
55
- }
56
-
57
- export function isWriteSql(sql: string): boolean {
58
- return WRITE_PATTERN.test(stripSqlNoise(sql));
59
- }
60
-
61
- /**
62
- * Statements that must never run via `hq db sql`, even with --write.
63
- * ATTACH/DETACH would let a company-scoped session open another company's
64
- * vault.db by absolute path (category-1 isolation).
65
- */
66
- export function assertSqlAllowed(sql: string): void {
67
- const stripped = stripSqlNoise(sql);
68
- if (FORBIDDEN_SQL_PATTERN.test(stripped)) {
69
- throw new Error(
70
- "ATTACH/DETACH/LOAD_EXTENSION are not allowed via hq db sql (tenant isolation); use the company-canonical local path only",
71
- );
72
- }
73
- }
74
-
75
- /**
76
- * Resolve which file path to open, enforcing company isolation by default.
77
- */
78
- export function resolveSqlDbPath(opts: SqlRunOptions): string {
79
- const company = normalizeCompanySlugForLocalDb(opts.company);
80
- const canonical = resolveLocalDbPath(company, opts);
81
-
82
- if (!opts.dbPathOverride) {
83
- return canonical;
84
- }
85
-
86
- if (opts.dbPathOverride === canonical) {
87
- return canonical;
88
- }
89
-
90
- if (!opts.allowCrossCompanyPath) {
91
- throw new Error(
92
- "cross-company or path override denied: refusing to open a non-canonical local DB path without --allow-cross-company-path (dangerous)",
93
- );
94
- }
95
-
96
- return opts.dbPathOverride;
97
- }
98
-
99
- function openByPath(dbPath: string): LocalDb {
100
- const db = new Database(dbPath);
101
- db.pragma("journal_mode = WAL");
102
- db.pragma("foreign_keys = ON");
103
- return db;
104
- }
105
-
106
- /**
107
- * Execute SQL against the company local vault DB.
108
- */
109
- export function runLocalSql(opts: SqlRunOptions): SqlRunResult {
110
- const sql = opts.sql?.trim();
111
- if (!sql) {
112
- throw new Error("SQL statement is required");
113
- }
114
-
115
- assertSqlAllowed(sql);
116
-
117
- const writeRequested = !!opts.write;
118
- if (isWriteSql(sql) && !writeRequested) {
119
- throw new Error(
120
- "write/DDL statement blocked in read-only mode; pass --write to allow (prefer hq db migrate for schema changes)",
121
- );
122
- }
123
-
124
- const dbPath = resolveSqlDbPath(opts);
125
- const usingCanonical =
126
- !opts.dbPathOverride || opts.dbPathOverride === resolveLocalDbPath(opts.company, opts);
127
-
128
- const db: LocalDb = usingCanonical
129
- ? openLocalDb(opts.company, opts)
130
- : openByPath(dbPath);
131
-
132
- try {
133
- if (!writeRequested) {
134
- db.pragma("query_only = ON");
135
- }
136
-
137
- const stmt = db.prepare(sql);
138
- if (stmt.reader) {
139
- const rows = stmt.all() as Record<string, unknown>[];
140
- const columns =
141
- rows.length > 0
142
- ? Object.keys(rows[0] as object)
143
- : stmt.columns().map((c) => c.name);
144
- return {
145
- columns,
146
- rows,
147
- changes: 0,
148
- readonly: !writeRequested,
149
- };
150
- }
151
-
152
- const info = stmt.run();
153
- return {
154
- columns: ["changes", "lastInsertRowid"],
155
- rows: [
156
- {
157
- changes: info.changes,
158
- lastInsertRowid: Number(info.lastInsertRowid),
159
- },
160
- ],
161
- changes: info.changes,
162
- readonly: !writeRequested,
163
- };
164
- } finally {
165
- db.close();
166
- }
167
- }
168
-
169
- export function formatSqlResult(
170
- result: SqlRunResult,
171
- format: "jsonl" | "table" = "jsonl",
172
- ): string {
173
- if (format === "jsonl") {
174
- if (result.rows.length === 0) {
175
- return JSON.stringify({ columns: result.columns, rows: 0 });
176
- }
177
- return result.rows.map((r) => JSON.stringify(r)).join("\n");
178
- }
179
-
180
- const cols = result.columns;
181
- if (cols.length === 0) return "(no columns)";
182
- const header = cols.join("\t");
183
- const body = result.rows
184
- .map((r) => cols.map((c) => String(r[c] ?? "")).join("\t"))
185
- .join("\n");
186
- return body ? `${header}\n${body}` : header;
187
- }
188
-
189
- /** Test helper: company dir for isolation assertions. */
190
- export function companyLocalDbDir(company: string, env?: LocalDbPathEnv): string {
191
- return resolveLocalDbDir(company, env);
192
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Dependency-bump smoke for @indigoai-us/hq-cloud.
3
- *
4
- * After moving from `file:../hq-cloud` to the published `^5.23.0`, this
5
- * test pins the contract: the SDK methods + types that hq-cli's
6
- * sync-mode / sync-narrow / files-browse / narrow-hint-banner commands
7
- * depend on must keep resolving from the npm-published package.
8
- *
9
- * If a future major bump on hq-cloud removes or renames any of these, the
10
- * matching hq-cli command would only fail at runtime (or at the call site's
11
- * own test). Locking it here catches the regression at install time in CI.
12
- */
13
-
14
- import { describe, it, expect } from "vitest";
15
- import * as cloud from "@indigoai-us/hq-cloud";
16
-
17
- describe("@indigoai-us/hq-cloud — pinned methods used by hq-cli", () => {
18
- it("US-004 + US-008 prep — VaultClient carries the sync-browse methods", () => {
19
- const proto = cloud.VaultClient.prototype as unknown as Record<
20
- string,
21
- unknown
22
- >;
23
- expect(typeof proto.listMyExplicitGrants).toBe("function");
24
- expect(typeof proto.getMembershipSyncConfig).toBe("function");
25
- expect(typeof proto.setMembershipSyncConfig).toBe("function");
26
- expect(typeof proto.vend).toBe("function");
27
- });
28
-
29
- it("US-004 type aliases used by sync-mode + sync-narrow + files-browse compile-link", () => {
30
- const _grant: cloud.ExplicitGrant = {
31
- companyUid: "cmp_x",
32
- path: "companies/x/",
33
- permission: "read",
34
- source: "person",
35
- };
36
- const _config: cloud.MembershipSyncConfig = {
37
- membershipId: "mbr_x",
38
- syncMode: "shared",
39
- isDefault: false,
40
- updatedAt: "2026-05-20T00:00:00Z",
41
- updatedBy: "prs_x",
42
- };
43
- const _input: cloud.SetMembershipSyncConfigInput = { syncMode: "all" };
44
- expect(_grant.source).toBe("person");
45
- expect(_config.syncMode).toBe("shared");
46
- expect(_input.syncMode).toBe("all");
47
- });
48
-
49
- it("US-008 prep — VendPurpose + VendInput keep purpose='browse' typecheckable", () => {
50
- const _vendInput: cloud.VendInput = {
51
- paths: ["companies/x/"],
52
- operations: "read-only",
53
- purpose: "browse",
54
- };
55
- expect(_vendInput.purpose).toBe("browse");
56
- });
57
-
58
- it("dep is the published @indigoai-us/hq-cloud, not a local file: link", async () => {
59
- // The package.json of the loaded module must be the npm-published one
60
- // (not file:../hq-cloud which was the dev wiring during US-006 onwards).
61
- // We resolve the package.json path via Node's require resolver and read
62
- // its `_resolved` (pnpm) or `version` field — both confirm origin.
63
- const pkgPath = require.resolve(
64
- "@indigoai-us/hq-cloud/package.json",
65
- );
66
- const pkg = await import(pkgPath, { with: { type: "json" } });
67
- expect(pkg.default.name).toBe("@indigoai-us/hq-cloud");
68
- // Must be ≥ 5.23.0 — the first published version carrying the sync-
69
- // browse SDK + raw vend with purpose. A lower version means the bump
70
- // never made it into the lockfile / pnpm-store.
71
- const [maj, min] = pkg.default.version.split(".").map(Number);
72
- expect(maj).toBeGreaterThanOrEqual(5);
73
- if (maj === 5) expect(min).toBeGreaterThanOrEqual(23);
74
- });
75
- });
@@ -1,301 +0,0 @@
1
- /**
2
- * Unit tests for `buildNarrowPlan` (local-tree-diff.ts).
3
- *
4
- * Uses a tmpdir fixture (same pattern as cloud-demote.test.ts) — vitest +
5
- * real fs — so the symmetric-diff + dirty-classification logic is exercised
6
- * end-to-end against an actual journal + filesystem.
7
- *
8
- * NAMESPACE CONTRACT (the bug these tests previously encoded): files are
9
- * written on disk at their hq-root-relative path (`companies/<slug>/...`),
10
- * but `buildNarrowPlan` emits — and the journal + grants endpoint use —
11
- * COMPANY-RELATIVE keys (`meetings/a.md`). The `key()` helper derives the
12
- * company-relative form from the on-disk path so journals, prefix sets, and
13
- * expectations all speak the same namespace the real server does.
14
- */
15
-
16
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
17
- import * as fs from "node:fs";
18
- import * as os from "node:os";
19
- import * as path from "node:path";
20
- import * as crypto from "node:crypto";
21
-
22
- import {
23
- buildNarrowPlan,
24
- toPosixKey,
25
- formatBytes,
26
- formatNarrowPlanSummary,
27
- } from "./local-tree-diff.js";
28
- import type { SyncJournal } from "@indigoai-us/hq-cloud";
29
-
30
- // ── Fixture helpers ─────────────────────────────────────────────────────────
31
-
32
- let tmpRoot: string;
33
-
34
- function writeFile(rel: string, contents: string): { abs: string; rel: string } {
35
- const abs = path.join(tmpRoot, rel);
36
- fs.mkdirSync(path.dirname(abs), { recursive: true });
37
- fs.writeFileSync(abs, contents);
38
- return { abs, rel };
39
- }
40
-
41
- /**
42
- * Strip the `companies/<slug>/` prefix from an on-disk hq-root-relative path
43
- * to get the COMPANY-RELATIVE key — the namespace `buildNarrowPlan` emits and
44
- * the journal + grants endpoint use.
45
- */
46
- function key(rel: string): string {
47
- return rel.replace(/^companies\/[^/]+\//, "");
48
- }
49
-
50
- function sha256(s: string): string {
51
- return crypto.createHash("sha256").update(s).digest("hex");
52
- }
53
-
54
- /**
55
- * Build a journal that "knows about" the given files at their current hash +
56
- * mtime. Journal keys are COMPANY-RELATIVE (via `key()`), matching the engine.
57
- */
58
- function journalFromFiles(
59
- files: Array<{ rel: string; contents: string; syncedAt?: string }>,
60
- ): SyncJournal {
61
- const j: SyncJournal = {
62
- version: "2",
63
- lastSync: new Date().toISOString(),
64
- files: {},
65
- pulls: [],
66
- };
67
- for (const f of files) {
68
- j.files[key(f.rel)] = {
69
- hash: sha256(f.contents),
70
- size: Buffer.byteLength(f.contents),
71
- // Stamp syncedAt slightly in the future so on-disk mtime <= syncedAt
72
- // (the clean path needs `mtime <= syncedAt + 1s` and mkdtemp file
73
- // mtimes are essentially "now").
74
- syncedAt: f.syncedAt ?? new Date(Date.now() + 5000).toISOString(),
75
- direction: "down",
76
- };
77
- }
78
- return j;
79
- }
80
-
81
- beforeEach(() => {
82
- tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "hq-narrow-plan-"));
83
- });
84
-
85
- afterEach(() => {
86
- fs.rmSync(tmpRoot, { recursive: true, force: true });
87
- });
88
-
89
- // ── Happy-path partitioning ─────────────────────────────────────────────────
90
-
91
- describe("toPosixKey — POSIX key normalization (Windows client regression)", () => {
92
- // Regression: on a non-POSIX (Windows) client, path.relative() yields
93
- // "\\"-separated paths. Emitted verbatim as a vault key, `knowledge\books-eoi.md`
94
- // has no "/", so the server's listing (which splits on "/") renders it flat at
95
- // the root. The narrow-plan must emit POSIX keys regardless of client OS.
96
- it("converts a Windows-style nested path to a POSIX key", () => {
97
- expect(toPosixKey("knowledge\\books-eoi.md")).toBe("knowledge/books-eoi.md");
98
- expect(toPosixKey("data\\sub\\boots-accounts.json")).toBe(
99
- "data/sub/boots-accounts.json",
100
- );
101
- });
102
-
103
- it("leaves an already-POSIX key unchanged (idempotent)", () => {
104
- expect(toPosixKey("meetings/2026/notes.md")).toBe("meetings/2026/notes.md");
105
- expect(toPosixKey("flat.md")).toBe("flat.md");
106
- });
107
- });
108
-
109
- describe("buildNarrowPlan — partition by prefix coverage", () => {
110
- it("separates files into staying / clean / dirty buckets", () => {
111
- // Staying — covered by prospective prefix.
112
- const staying = writeFile(
113
- "companies/acme/meetings/2026/notes.md",
114
- "stays here",
115
- );
116
- // Clean orphan — outside prospective, matches journal.
117
- const cleanOrphan = writeFile(
118
- "companies/acme/scratch/old.md",
119
- "clean content",
120
- );
121
- // Dirty orphan — outside prospective, hash mismatch.
122
- const dirtyOrphan = writeFile(
123
- "companies/acme/scratch/dirty.md",
124
- "current local content",
125
- );
126
-
127
- const journal = journalFromFiles([
128
- { rel: staying.rel, contents: "stays here" },
129
- { rel: cleanOrphan.rel, contents: "clean content" },
130
- // Journal saw an earlier version → hash mismatch with the on-disk
131
- // "current local content" → dirty.
132
- { rel: dirtyOrphan.rel, contents: "JOURNAL VERSION DIFFERENT" },
133
- ]);
134
-
135
- const plan = buildNarrowPlan({
136
- hqRoot: tmpRoot,
137
- companySlug: "acme",
138
- prospectivePrefixSet: ["meetings/"],
139
- journal,
140
- });
141
-
142
- expect(plan.staying.map((f) => f.relPath)).toEqual([key(staying.rel)]);
143
- expect(plan.clean.map((f) => f.relPath)).toEqual([key(cleanOrphan.rel)]);
144
- expect(plan.dirty.map((f) => f.relPath)).toEqual([key(dirtyOrphan.rel)]);
145
- expect(plan.dirty[0].reason).toBe("hash-mismatch");
146
-
147
- expect(plan.totalStayingCount).toBe(1);
148
- expect(plan.totalCleanCount).toBe(1);
149
- expect(plan.totalDirtyCount).toBe(1);
150
- expect(plan.totalCleanBytes).toBe(Buffer.byteLength("clean content"));
151
- });
152
-
153
- it("classifies files with no journal entry as dirty (not-in-journal)", () => {
154
- const orphan = writeFile("companies/acme/scratch/local-draft.md", "never synced");
155
- const plan = buildNarrowPlan({
156
- hqRoot: tmpRoot,
157
- companySlug: "acme",
158
- prospectivePrefixSet: ["meetings/"],
159
- journal: journalFromFiles([]),
160
- });
161
- expect(plan.dirty.map((f) => f.relPath)).toEqual([key(orphan.rel)]);
162
- expect(plan.dirty[0].reason).toBe("not-in-journal");
163
- });
164
-
165
- it("treats tombstoned journal entries as not-in-journal (dirty)", () => {
166
- const orphan = writeFile("companies/acme/scratch/tombstoned.md", "still here");
167
- const journal = journalFromFiles([
168
- { rel: orphan.rel, contents: "still here" },
169
- ]);
170
- // Mark the entry tombstoned post-hoc.
171
- journal.files[key(orphan.rel)]!.removedAt = new Date().toISOString();
172
- journal.files[key(orphan.rel)]!.removedReason = "scope_shrink";
173
-
174
- const plan = buildNarrowPlan({
175
- hqRoot: tmpRoot,
176
- companySlug: "acme",
177
- prospectivePrefixSet: ["meetings/"],
178
- journal,
179
- });
180
- expect(plan.dirty.map((f) => f.relPath)).toEqual([key(orphan.rel)]);
181
- expect(plan.dirty[0].reason).toBe("not-in-journal");
182
- });
183
-
184
- it("flags modified-after-sync via mtime past syncedAt", () => {
185
- const orphan = writeFile("companies/acme/scratch/touched.md", "v1");
186
- const journal = journalFromFiles([
187
- { rel: orphan.rel, contents: "v1", syncedAt: new Date(0).toISOString() },
188
- ]);
189
- const plan = buildNarrowPlan({
190
- hqRoot: tmpRoot,
191
- companySlug: "acme",
192
- prospectivePrefixSet: ["meetings/"],
193
- journal,
194
- });
195
- expect(plan.dirty.map((f) => f.relPath)).toEqual([key(orphan.rel)]);
196
- expect(plan.dirty[0].reason).toBe("modified-after-sync");
197
- });
198
-
199
- it("handles nested prefixes (broader covers narrower) by trusting the caller-coalesced set", () => {
200
- const a = writeFile("companies/acme/meetings/a.md", "a");
201
- const b = writeFile("companies/acme/meetings/2026/b.md", "b");
202
- const c = writeFile("companies/acme/scratch/c.md", "c");
203
-
204
- const plan = buildNarrowPlan({
205
- hqRoot: tmpRoot,
206
- companySlug: "acme",
207
- prospectivePrefixSet: ["meetings/"],
208
- journal: journalFromFiles([
209
- { rel: a.rel, contents: "a" },
210
- { rel: b.rel, contents: "b" },
211
- { rel: c.rel, contents: "c" },
212
- ]),
213
- });
214
-
215
- const stayingPaths = plan.staying.map((f) => f.relPath).sort();
216
- expect(stayingPaths).toEqual([key(a.rel), key(b.rel)].sort());
217
- expect(plan.clean.map((f) => f.relPath)).toEqual([key(c.rel)]);
218
- });
219
-
220
- it("returns an empty plan when the company folder is missing", () => {
221
- const plan = buildNarrowPlan({
222
- hqRoot: tmpRoot,
223
- companySlug: "ghost",
224
- prospectivePrefixSet: [""],
225
- journal: journalFromFiles([]),
226
- });
227
- expect(plan.totalStayingCount).toBe(0);
228
- expect(plan.totalCleanCount).toBe(0);
229
- expect(plan.totalDirtyCount).toBe(0);
230
- });
231
-
232
- it("everything is clean when the prefix set is empty (entire tree is orphan)", () => {
233
- const a = writeFile("companies/acme/a.md", "aa");
234
- const b = writeFile("companies/acme/sub/b.md", "bb");
235
- const plan = buildNarrowPlan({
236
- hqRoot: tmpRoot,
237
- companySlug: "acme",
238
- prospectivePrefixSet: [],
239
- journal: journalFromFiles([
240
- { rel: a.rel, contents: "aa" },
241
- { rel: b.rel, contents: "bb" },
242
- ]),
243
- });
244
- expect(plan.totalStayingCount).toBe(0);
245
- expect(plan.totalCleanCount).toBe(2);
246
- expect(plan.totalDirtyCount).toBe(0);
247
- });
248
-
249
- it("everything stays when the prospective prefix covers the whole company folder", () => {
250
- const a = writeFile("companies/acme/a.md", "aa");
251
- const b = writeFile("companies/acme/sub/b.md", "bb");
252
- const plan = buildNarrowPlan({
253
- hqRoot: tmpRoot,
254
- companySlug: "acme",
255
- // Empty-string prefix = "covers everything" per isCoveredByAny.
256
- prospectivePrefixSet: [""],
257
- journal: journalFromFiles([]),
258
- });
259
- expect(plan.totalStayingCount).toBe(2);
260
- expect(plan.totalCleanCount).toBe(0);
261
- expect(plan.totalDirtyCount).toBe(0);
262
- // both are staying, regardless of journal state
263
- expect(
264
- plan.staying.map((f) => f.relPath).sort(),
265
- ).toEqual([key(a.rel), key(b.rel)].sort());
266
- });
267
- });
268
-
269
- // ── Formatters ──────────────────────────────────────────────────────────────
270
-
271
- describe("formatBytes", () => {
272
- it("formats bytes in a fixed unit ladder", () => {
273
- expect(formatBytes(0)).toBe("0 B");
274
- expect(formatBytes(512)).toBe("512 B");
275
- expect(formatBytes(2048)).toBe("2.00 KB");
276
- expect(formatBytes(5 * 1024 * 1024)).toBe("5.00 MB");
277
- });
278
-
279
- it("clamps negative + non-finite to 0 B", () => {
280
- expect(formatBytes(-1)).toBe("0 B");
281
- expect(formatBytes(Number.NaN)).toBe("0 B");
282
- });
283
- });
284
-
285
- describe("formatNarrowPlanSummary", () => {
286
- it("renders staying / clean / dirty counts with byte totals", () => {
287
- const out = formatNarrowPlanSummary({
288
- staying: [],
289
- clean: [],
290
- dirty: [],
291
- totalStayingCount: 5,
292
- totalCleanCount: 3,
293
- totalCleanBytes: 2048,
294
- totalDirtyCount: 1,
295
- totalDirtyBytes: 1024,
296
- });
297
- expect(out).toContain("files staying: 5");
298
- expect(out).toContain("clean orphans: 3 (2.00 KB)");
299
- expect(out).toContain("dirty orphans: 1 (1.00 KB)");
300
- });
301
- });