@indigoai-us/hq-cli 5.77.14 → 5.79.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 (309) hide show
  1. package/CHANGELOG.md +31 -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/meetings.js +16 -9
  31. package/dist/commands/members.js +16 -5
  32. package/dist/commands/pack-install.d.ts +14 -0
  33. package/dist/commands/pack-install.js +42 -2
  34. package/dist/commands/pkg-install.js +39 -2
  35. package/dist/commands/run.js +6 -2
  36. package/dist/commands/secrets.js +120 -65
  37. package/dist/main.js +6 -0
  38. package/dist/utils/api-key-command-gate.d.ts +45 -0
  39. package/dist/utils/api-key-command-gate.js +51 -0
  40. package/dist/utils/hq-roots.d.ts +91 -0
  41. package/dist/utils/hq-roots.js +212 -0
  42. package/dist/utils/resolve-vault-credential.d.ts +63 -3
  43. package/dist/utils/resolve-vault-credential.js +152 -6
  44. package/dist/utils/run-bundled-script.d.ts +66 -0
  45. package/dist/utils/run-bundled-script.js +80 -0
  46. package/dist/utils/vault-api.d.ts +21 -0
  47. package/dist/utils/vault-api.js +74 -3
  48. package/package.json +7 -1
  49. package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
  50. package/.github/workflows/ci.yml +0 -46
  51. package/.github/workflows/publish.yml +0 -36
  52. package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
  53. package/e2e/cli.test.ts +0 -128
  54. package/eslint.config.js +0 -37
  55. package/pnpm-workspace.yaml +0 -4
  56. package/scripts/chmod-bins.mjs +0 -49
  57. package/scripts/generate-dsn.mjs +0 -26
  58. package/scripts/generate-scan-packages-table.mjs +0 -113
  59. package/scripts/smoke-sources-signals.sh +0 -103
  60. package/src/bin/hq-auth-refresh.ts +0 -43
  61. package/src/cli-version.ts +0 -13
  62. package/src/commands/__fixtures__/make-tar.ts +0 -126
  63. package/src/commands/_patterns.ts +0 -10
  64. package/src/commands/add.ts +0 -75
  65. package/src/commands/agents.test.ts +0 -527
  66. package/src/commands/agents.ts +0 -923
  67. package/src/commands/api-keys.test.ts +0 -291
  68. package/src/commands/api-keys.ts +0 -382
  69. package/src/commands/artifact-verify.test.ts +0 -177
  70. package/src/commands/auth.test.ts +0 -82
  71. package/src/commands/auth.ts +0 -213
  72. package/src/commands/billing.test.ts +0 -158
  73. package/src/commands/billing.ts +0 -146
  74. package/src/commands/channels.test.ts +0 -74
  75. package/src/commands/channels.ts +0 -91
  76. package/src/commands/cloud-demote.test.ts +0 -401
  77. package/src/commands/cloud-demote.ts +0 -278
  78. package/src/commands/cloud-provision.test.ts +0 -1117
  79. package/src/commands/cloud-provision.ts +0 -942
  80. package/src/commands/cloud.pull-all.test.ts +0 -711
  81. package/src/commands/cloud.pull-per-company.test.ts +0 -188
  82. package/src/commands/cloud.push-all.test.ts +0 -485
  83. package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
  84. package/src/commands/cloud.selectors.test.ts +0 -208
  85. package/src/commands/cloud.status.test.ts +0 -160
  86. package/src/commands/cloud.test.ts +0 -41
  87. package/src/commands/cloud.ts +0 -2130
  88. package/src/commands/company.test.ts +0 -202
  89. package/src/commands/company.ts +0 -148
  90. package/src/commands/creators.test.ts +0 -60
  91. package/src/commands/creators.ts +0 -117
  92. package/src/commands/crm.test.ts +0 -179
  93. package/src/commands/crm.ts +0 -236
  94. package/src/commands/db-migrate.ts +0 -55
  95. package/src/commands/db-provision.ts +0 -114
  96. package/src/commands/db-sql.ts +0 -124
  97. package/src/commands/db-status.ts +0 -108
  98. package/src/commands/db.ts +0 -26
  99. package/src/commands/dm.test.ts +0 -748
  100. package/src/commands/dm.ts +0 -1226
  101. package/src/commands/feedback.test.ts +0 -413
  102. package/src/commands/feedback.ts +0 -169
  103. package/src/commands/files-browse.test.ts +0 -1249
  104. package/src/commands/files-browse.ts +0 -1506
  105. package/src/commands/files-delete.test.ts +0 -484
  106. package/src/commands/files-recovery.test.ts +0 -370
  107. package/src/commands/files.test.ts +0 -722
  108. package/src/commands/files.ts +0 -1446
  109. package/src/commands/group-grants.test.ts +0 -330
  110. package/src/commands/group-grants.ts +0 -455
  111. package/src/commands/groups.test.ts +0 -44
  112. package/src/commands/groups.ts +0 -368
  113. package/src/commands/integrations.test.ts +0 -633
  114. package/src/commands/integrations.ts +0 -507
  115. package/src/commands/list.ts +0 -85
  116. package/src/commands/login.ts +0 -52
  117. package/src/commands/logout.ts +0 -30
  118. package/src/commands/marketplace-install.test.ts +0 -543
  119. package/src/commands/marketplace-security.test.ts +0 -646
  120. package/src/commands/mcp-registration.test.ts +0 -2787
  121. package/src/commands/mcp-registration.ts +0 -2612
  122. package/src/commands/mcp-status.test.ts +0 -483
  123. package/src/commands/mcp-status.ts +0 -575
  124. package/src/commands/mcp-status.us011.test.ts +0 -243
  125. package/src/commands/meetings.test.ts +0 -522
  126. package/src/commands/meetings.ts +0 -789
  127. package/src/commands/members.test.ts +0 -1474
  128. package/src/commands/members.ts +0 -1181
  129. package/src/commands/onboard-identity-guard.test.ts +0 -99
  130. package/src/commands/onboard-identity-guard.ts +0 -103
  131. package/src/commands/onboard-join.test.ts +0 -96
  132. package/src/commands/onboard-join.ts +0 -101
  133. package/src/commands/onboard-warning.test.ts +0 -26
  134. package/src/commands/onboard-warning.ts +0 -12
  135. package/src/commands/onboard.ts +0 -267
  136. package/src/commands/outposts-heartbeat.test.ts +0 -299
  137. package/src/commands/outposts-heartbeat.ts +0 -310
  138. package/src/commands/outposts-replica-sync.test.ts +0 -272
  139. package/src/commands/outposts-self-deploy.test.ts +0 -243
  140. package/src/commands/outposts.test.ts +0 -1039
  141. package/src/commands/outposts.ts +0 -1935
  142. package/src/commands/pack-install-secret-authorization.test.ts +0 -115
  143. package/src/commands/pack-install.test.ts +0 -1469
  144. package/src/commands/pack-install.ts +0 -2256
  145. package/src/commands/pack-update-cache.test.ts +0 -149
  146. package/src/commands/packs-update-api-key.test.ts +0 -105
  147. package/src/commands/packs.test.ts +0 -88
  148. package/src/commands/packs.ts +0 -622
  149. package/src/commands/people.test.ts +0 -807
  150. package/src/commands/people.ts +0 -422
  151. package/src/commands/pkg-install.dispatch.test.ts +0 -103
  152. package/src/commands/pkg-install.ts +0 -221
  153. package/src/commands/pkg-list.test.ts +0 -79
  154. package/src/commands/pkg-list.ts +0 -13
  155. package/src/commands/pkg-remove.ts +0 -71
  156. package/src/commands/pkg-update.ts +0 -189
  157. package/src/commands/publish.test.ts +0 -650
  158. package/src/commands/publish.ts +0 -580
  159. package/src/commands/reindex.test.ts +0 -259
  160. package/src/commands/reindex.ts +0 -274
  161. package/src/commands/rescue.test.ts +0 -39
  162. package/src/commands/rescue.ts +0 -210
  163. package/src/commands/run.env-local.test.ts +0 -88
  164. package/src/commands/run.test.ts +0 -45
  165. package/src/commands/run.ts +0 -190
  166. package/src/commands/safe-extract.test.ts +0 -459
  167. package/src/commands/safe-extract.ts +0 -444
  168. package/src/commands/secrets-scope.test.ts +0 -56
  169. package/src/commands/secrets-scope.ts +0 -32
  170. package/src/commands/secrets.parse-destination.test.ts +0 -38
  171. package/src/commands/secrets.test.ts +0 -2050
  172. package/src/commands/secrets.ts +0 -2249
  173. package/src/commands/signals.ts +0 -351
  174. package/src/commands/skill.test.ts +0 -770
  175. package/src/commands/skill.ts +0 -796
  176. package/src/commands/sources.ts +0 -362
  177. package/src/commands/sync-mode.test.ts +0 -366
  178. package/src/commands/sync-mode.ts +0 -387
  179. package/src/commands/sync-narrow.test.ts +0 -633
  180. package/src/commands/sync-narrow.ts +0 -555
  181. package/src/commands/sync.ts +0 -165
  182. package/src/commands/team-sync.ts +0 -628
  183. package/src/commands/update.ts +0 -71
  184. package/src/commands/whoami.ts +0 -83
  185. package/src/commands/workers.test.ts +0 -158
  186. package/src/commands/workers.ts +0 -301
  187. package/src/index.test.ts +0 -32
  188. package/src/index.ts +0 -20
  189. package/src/lib/db/control-plane.test.ts +0 -59
  190. package/src/lib/db/control-plane.ts +0 -113
  191. package/src/lib/db/local.test.ts +0 -81
  192. package/src/lib/db/local.ts +0 -148
  193. package/src/lib/db/migrate.test.ts +0 -133
  194. package/src/lib/db/migrate.ts +0 -137
  195. package/src/lib/db/paths.test.ts +0 -112
  196. package/src/lib/db/paths.ts +0 -128
  197. package/src/lib/db/remote-engine.test.ts +0 -44
  198. package/src/lib/db/remote-engine.ts +0 -148
  199. package/src/lib/db/remote-sql.test.ts +0 -32
  200. package/src/lib/db/remote-sql.ts +0 -62
  201. package/src/lib/db/sql.test.ts +0 -106
  202. package/src/lib/db/sql.ts +0 -192
  203. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  204. package/src/lib/local-tree-diff.test.ts +0 -301
  205. package/src/lib/local-tree-diff.ts +0 -362
  206. package/src/lib/narrow-hint-banner.test.ts +0 -235
  207. package/src/lib/narrow-hint-banner.ts +0 -212
  208. package/src/lib/plan-limit-nag.test.ts +0 -317
  209. package/src/lib/plan-limit-nag.ts +0 -264
  210. package/src/main.ts +0 -371
  211. package/src/node-preflight.test.ts +0 -60
  212. package/src/node-preflight.ts +0 -67
  213. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  214. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  215. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  216. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  217. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  218. package/src/outpost/session-heartbeat.test.ts +0 -459
  219. package/src/outpost/session-heartbeat.ts +0 -877
  220. package/src/packaging.test.ts +0 -109
  221. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  222. package/src/run/discover-schemas.test.ts +0 -153
  223. package/src/run/discover-schemas.ts +0 -79
  224. package/src/run/hq-plugin.test.ts +0 -371
  225. package/src/run/hq-plugin.ts +0 -277
  226. package/src/run/varlock-shape.test.ts +0 -57
  227. package/src/schemas/hq-package.schema.json +0 -155
  228. package/src/sentry-before-send.test.ts +0 -359
  229. package/src/sentry-before-send.ts +0 -221
  230. package/src/sentry-epipe.test.ts +0 -37
  231. package/src/sentry-release.test.ts +0 -54
  232. package/src/sentry.test.ts +0 -24
  233. package/src/sentry.ts +0 -54
  234. package/src/strategies/link.ts +0 -62
  235. package/src/strategies/merge.ts +0 -142
  236. package/src/types.ts +0 -146
  237. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  238. package/src/utils/auth-error.test.ts +0 -40
  239. package/src/utils/auth-error.ts +0 -42
  240. package/src/utils/billing-gate.test.ts +0 -95
  241. package/src/utils/billing-gate.ts +0 -228
  242. package/src/utils/breadcrumb-buffer.ts +0 -18
  243. package/src/utils/cli-telemetry.test.ts +0 -153
  244. package/src/utils/cli-telemetry.ts +0 -61
  245. package/src/utils/cognito-session.machine.test.ts +0 -109
  246. package/src/utils/cognito-session.refresh.test.ts +0 -84
  247. package/src/utils/cognito-session.test.ts +0 -328
  248. package/src/utils/cognito-session.ts +0 -448
  249. package/src/utils/company-selection-error.test.ts +0 -42
  250. package/src/utils/company-selection-error.ts +0 -45
  251. package/src/utils/contribution-table.ts +0 -83
  252. package/src/utils/environmental-error.test.ts +0 -45
  253. package/src/utils/environmental-error.ts +0 -39
  254. package/src/utils/epipe.test.ts +0 -28
  255. package/src/utils/epipe.ts +0 -29
  256. package/src/utils/expected-cli-error.test.ts +0 -28
  257. package/src/utils/expected-cli-error.ts +0 -39
  258. package/src/utils/feedback-diagnostics.test.ts +0 -183
  259. package/src/utils/feedback-diagnostics.ts +0 -123
  260. package/src/utils/feedback-screenshots.test.ts +0 -134
  261. package/src/utils/feedback-screenshots.ts +0 -124
  262. package/src/utils/feedback-versions.test.ts +0 -98
  263. package/src/utils/feedback-versions.ts +0 -68
  264. package/src/utils/git.ts +0 -74
  265. package/src/utils/integrity.ts +0 -59
  266. package/src/utils/intercepted-process-exit.test.ts +0 -37
  267. package/src/utils/intercepted-process-exit.ts +0 -36
  268. package/src/utils/login-provider.test.ts +0 -42
  269. package/src/utils/login-provider.ts +0 -30
  270. package/src/utils/manifest.test.ts +0 -167
  271. package/src/utils/manifest.ts +0 -131
  272. package/src/utils/pack-contributions.test.ts +0 -603
  273. package/src/utils/pack-contributions.ts +0 -450
  274. package/src/utils/people.ts +0 -215
  275. package/src/utils/registry-client.ts +0 -204
  276. package/src/utils/registry.ts +0 -90
  277. package/src/utils/resolve-vault-credential.test.ts +0 -69
  278. package/src/utils/resolve-vault-credential.ts +0 -60
  279. package/src/utils/sandbox-runner-client.test.ts +0 -279
  280. package/src/utils/sandbox-runner-client.ts +0 -260
  281. package/src/utils/secrets-cache.ts +0 -183
  282. package/src/utils/sentry-identity.ts +0 -45
  283. package/src/utils/settle-with-timeout.test.ts +0 -21
  284. package/src/utils/settle-with-timeout.ts +0 -22
  285. package/src/utils/user-yaml-error.test.ts +0 -24
  286. package/src/utils/user-yaml-error.ts +0 -30
  287. package/src/utils/vault-api.test.ts +0 -536
  288. package/src/utils/vault-api.ts +0 -382
  289. package/src/utils/version-check.test.ts +0 -176
  290. package/src/utils/version-check.ts +0 -155
  291. package/src/utils/version-gate.test.ts +0 -1024
  292. package/src/utils/version-gate.ts +0 -702
  293. package/test/commands/db-tenant-isolation.test.ts +0 -94
  294. package/test/commands/db.test.ts +0 -85
  295. package/test/commands/signals.test.ts +0 -207
  296. package/test/commands/sources.test.ts +0 -231
  297. package/test/e2e/smoke-install-mcp.sh +0 -113
  298. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  299. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  300. package/test/fixtures/signals/action_item/sample.md +0 -16
  301. package/test/fixtures/signals/summary/sample.md +0 -12
  302. package/test/fixtures/sources/meetings/sample.md +0 -25
  303. package/test/helpers/cli-runner.ts +0 -150
  304. package/test/helpers/s3-list-mock.ts +0 -79
  305. package/test/helpers/vault-service-mock.ts +0 -295
  306. package/test/sources-signals/smoke.test.ts +0 -228
  307. package/tsconfig.json +0 -20
  308. package/vitest.config.ts +0 -11
  309. 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
- });