@hasna/instructions 0.6.0 → 0.7.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 (265) hide show
  1. package/README.md +149 -8
  2. package/dist/chunks/apply-7svah2qm.js +25 -0
  3. package/dist/chunks/apply-jp13sa1e.js +25 -0
  4. package/dist/chunks/database-wstmr56z.js +23 -0
  5. package/dist/chunks/index-1cnyjnd7.js +623 -0
  6. package/dist/chunks/index-35z65jsa.js +198 -0
  7. package/dist/chunks/index-3djthcm6.js +117 -0
  8. package/dist/chunks/index-4pfq42t5.js +7300 -0
  9. package/dist/chunks/index-5d7zdftc.js +186 -0
  10. package/dist/chunks/index-7m4j8a33.js +12578 -0
  11. package/dist/chunks/index-8rb7ng2y.js +148 -0
  12. package/dist/chunks/index-arrrx0ed.js +344 -0
  13. package/dist/chunks/index-bsan4c16.js +48 -0
  14. package/dist/chunks/index-bzyxvwcd.js +625 -0
  15. package/dist/chunks/index-fpeyzn3n.js +186 -0
  16. package/dist/chunks/index-fwbs6tma.js +534 -0
  17. package/dist/chunks/index-ma2xmp6c.js +14 -0
  18. package/dist/chunks/index-n3fjspr0.js +119 -0
  19. package/dist/chunks/index-nnwp92tk.js +19 -0
  20. package/dist/chunks/index-z3ma4aqk.js +532 -0
  21. package/dist/chunks/legacy-store-migration-z2m5yzyk.js +322 -0
  22. package/dist/chunks/local-5wvv5xv3.js +694 -0
  23. package/dist/chunks/local-zxa5dpf4.js +518 -0
  24. package/dist/chunks/package-manager-guard-x70xrwk2.js +391 -0
  25. package/dist/chunks/redact-hnq4sc4r.js +16 -0
  26. package/dist/chunks/s3-backup-n2z4fwyh.js +240 -0
  27. package/dist/chunks/s3-config-hhght91k.js +14 -0
  28. package/dist/chunks/s3-object-store-90tgbh6k.js +14 -0
  29. package/dist/chunks/sync-2075deve.js +34 -0
  30. package/dist/chunks/sync-r8t7zsp3.js +34 -0
  31. package/dist/chunks/template-fsfxpe4p.js +16 -0
  32. package/dist/chunks/template-wgnzdn7h.js +16 -0
  33. package/dist/cli/index.js +1681 -15643
  34. package/dist/data/config-store.d.ts +2 -1
  35. package/dist/data/config-store.d.ts.map +1 -1
  36. package/dist/db/database.d.ts +3 -5
  37. package/dist/db/database.d.ts.map +1 -1
  38. package/dist/db/local.d.ts +38 -0
  39. package/dist/db/local.d.ts.map +1 -0
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +13809 -12998
  43. package/dist/lib/apply.d.ts +7 -0
  44. package/dist/lib/apply.d.ts.map +1 -1
  45. package/dist/lib/compact-output.d.ts +1 -0
  46. package/dist/lib/compact-output.d.ts.map +1 -1
  47. package/dist/lib/export.d.ts +22 -3
  48. package/dist/lib/export.d.ts.map +1 -1
  49. package/dist/lib/ids.d.ts +4 -0
  50. package/dist/lib/ids.d.ts.map +1 -0
  51. package/dist/lib/import.d.ts +18 -0
  52. package/dist/lib/import.d.ts.map +1 -1
  53. package/dist/lib/instruction-graph.d.ts.map +1 -1
  54. package/dist/lib/legacy-store-migration.d.ts +39 -0
  55. package/dist/lib/legacy-store-migration.d.ts.map +1 -0
  56. package/dist/lib/managed-skill-runtimes.d.ts +1 -0
  57. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -1
  58. package/dist/lib/session-render.d.ts +3 -0
  59. package/dist/lib/session-render.d.ts.map +1 -1
  60. package/dist/lib/sync-dir.d.ts.map +1 -1
  61. package/dist/mcp/index.js +73 -9462
  62. package/dist/sdk/index.d.ts +2 -0
  63. package/dist/sdk/index.d.ts.map +1 -1
  64. package/dist/sdk/index.js +205 -10
  65. package/dist/sdk/resolve.d.ts +1 -1
  66. package/dist/sdk/resolve.d.ts.map +1 -1
  67. package/dist/sdk/v1-client.d.ts +57 -0
  68. package/dist/sdk/v1-client.d.ts.map +1 -0
  69. package/dist/sdk/v1.generated.d.ts +212 -21
  70. package/dist/sdk/v1.generated.d.ts.map +1 -1
  71. package/dist/server/cloud.d.ts.map +1 -1
  72. package/dist/server/index.d.ts +13 -5
  73. package/dist/server/index.d.ts.map +1 -1
  74. package/dist/server/index.js +8337 -7495
  75. package/dist/server/openapi.d.ts +1264 -92
  76. package/dist/server/openapi.d.ts.map +1 -1
  77. package/dist/server/v1.d.ts +25 -1
  78. package/dist/server/v1.d.ts.map +1 -1
  79. package/dist/status.d.ts +12 -2
  80. package/dist/status.d.ts.map +1 -1
  81. package/dist/storage/cloud-store.d.ts +57 -1
  82. package/dist/storage/cloud-store.d.ts.map +1 -1
  83. package/dist/storage/index.d.ts +7 -0
  84. package/dist/storage/index.d.ts.map +1 -0
  85. package/dist/storage/index.js +725 -0
  86. package/dist/storage/s3-backup.d.ts +76 -0
  87. package/dist/storage/s3-backup.d.ts.map +1 -0
  88. package/dist/storage/s3-config.d.ts +46 -0
  89. package/dist/storage/s3-config.d.ts.map +1 -0
  90. package/dist/storage/s3-object-store.d.ts +70 -0
  91. package/dist/storage/s3-object-store.d.ts.map +1 -0
  92. package/dist/types/index.d.ts +65 -3
  93. package/dist/types/index.d.ts.map +1 -1
  94. package/hasna.contract.json +110 -0
  95. package/migrations/0001_instructions.sql +63 -0
  96. package/migrations/0002_api_keys.sql +20 -0
  97. package/migrations/0003_idempotency_receipts.sql +24 -0
  98. package/package.json +29 -14
  99. package/assets/skills/inbox/SKILL.md +0 -86
  100. package/dist/cli/add-duplicate-target.test.d.ts +0 -2
  101. package/dist/cli/add-duplicate-target.test.d.ts.map +0 -1
  102. package/dist/cli/add-reference-update.test.d.ts +0 -2
  103. package/dist/cli/add-reference-update.test.d.ts.map +0 -1
  104. package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts +0 -2
  105. package/dist/cli/apply-unchanged-snapshot-bloat.test.d.ts.map +0 -1
  106. package/dist/cli/doctor-reference-duplicates.test.d.ts +0 -2
  107. package/dist/cli/doctor-reference-duplicates.test.d.ts.map +0 -1
  108. package/dist/cli/fail-closed-no-env.test.d.ts +0 -2
  109. package/dist/cli/fail-closed-no-env.test.d.ts.map +0 -1
  110. package/dist/cli/output.test.d.ts +0 -2
  111. package/dist/cli/output.test.d.ts.map +0 -1
  112. package/dist/cli/profile-binding.test.d.ts +0 -2
  113. package/dist/cli/profile-binding.test.d.ts.map +0 -1
  114. package/dist/cli/profile-reads.test.d.ts +0 -2
  115. package/dist/cli/profile-reads.test.d.ts.map +0 -1
  116. package/dist/cli/profile-update.test.d.ts +0 -2
  117. package/dist/cli/profile-update.test.d.ts.map +0 -1
  118. package/dist/cli/project-context.test.d.ts +0 -2
  119. package/dist/cli/project-context.test.d.ts.map +0 -1
  120. package/dist/cli/raw-store-root.test.d.ts +0 -2
  121. package/dist/cli/raw-store-root.test.d.ts.map +0 -1
  122. package/dist/cli/session-apply-managed-input-bound.test.d.ts +0 -2
  123. package/dist/cli/session-apply-managed-input-bound.test.d.ts.map +0 -1
  124. package/dist/cli/session.test.d.ts +0 -2
  125. package/dist/cli/session.test.d.ts.map +0 -1
  126. package/dist/cli/station-profile.test.d.ts +0 -2
  127. package/dist/cli/station-profile.test.d.ts.map +0 -1
  128. package/dist/data/config-store.test.d.ts +0 -2
  129. package/dist/data/config-store.test.d.ts.map +0 -1
  130. package/dist/db/configs.test.d.ts +0 -2
  131. package/dist/db/configs.test.d.ts.map +0 -1
  132. package/dist/db/database.test.d.ts +0 -2
  133. package/dist/db/database.test.d.ts.map +0 -1
  134. package/dist/db/machines.test.d.ts +0 -2
  135. package/dist/db/machines.test.d.ts.map +0 -1
  136. package/dist/db/profile-assets.test.d.ts +0 -2
  137. package/dist/db/profile-assets.test.d.ts.map +0 -1
  138. package/dist/db/profile-config-bindings.test.d.ts +0 -2
  139. package/dist/db/profile-config-bindings.test.d.ts.map +0 -1
  140. package/dist/db/profiles.test.d.ts +0 -2
  141. package/dist/db/profiles.test.d.ts.map +0 -1
  142. package/dist/db/snapshots.test.d.ts +0 -2
  143. package/dist/db/snapshots.test.d.ts.map +0 -1
  144. package/dist/lib/app-home.test.d.ts +0 -2
  145. package/dist/lib/app-home.test.d.ts.map +0 -1
  146. package/dist/lib/apply-batch.test.d.ts +0 -2
  147. package/dist/lib/apply-batch.test.d.ts.map +0 -1
  148. package/dist/lib/apply-dry-run-verdict.test.d.ts +0 -2
  149. package/dist/lib/apply-dry-run-verdict.test.d.ts.map +0 -1
  150. package/dist/lib/apply-secret-placeholder-guard.test.d.ts +0 -2
  151. package/dist/lib/apply-secret-placeholder-guard.test.d.ts.map +0 -1
  152. package/dist/lib/apply-text-format-credential-guard.test.d.ts +0 -2
  153. package/dist/lib/apply-text-format-credential-guard.test.d.ts.map +0 -1
  154. package/dist/lib/apply.test.d.ts +0 -2
  155. package/dist/lib/apply.test.d.ts.map +0 -1
  156. package/dist/lib/asset-plan.test.d.ts +0 -2
  157. package/dist/lib/asset-plan.test.d.ts.map +0 -1
  158. package/dist/lib/client-types.test.d.ts +0 -2
  159. package/dist/lib/client-types.test.d.ts.map +0 -1
  160. package/dist/lib/codewith-shared-todos-storage-standard.test.d.ts +0 -2
  161. package/dist/lib/codewith-shared-todos-storage-standard.test.d.ts.map +0 -1
  162. package/dist/lib/compact-output.test.d.ts +0 -2
  163. package/dist/lib/compact-output.test.d.ts.map +0 -1
  164. package/dist/lib/config-target-identity.test.d.ts +0 -2
  165. package/dist/lib/config-target-identity.test.d.ts.map +0 -1
  166. package/dist/lib/cursor-authority.test.d.ts +0 -2
  167. package/dist/lib/cursor-authority.test.d.ts.map +0 -1
  168. package/dist/lib/dangerous-operation-guard-standard.test.d.ts +0 -2
  169. package/dist/lib/dangerous-operation-guard-standard.test.d.ts.map +0 -1
  170. package/dist/lib/diff-redaction.test.d.ts +0 -2
  171. package/dist/lib/diff-redaction.test.d.ts.map +0 -1
  172. package/dist/lib/export-import.test.d.ts +0 -2
  173. package/dist/lib/export-import.test.d.ts.map +0 -1
  174. package/dist/lib/global-agent-rules-render-integration.test.d.ts +0 -2
  175. package/dist/lib/global-agent-rules-render-integration.test.d.ts.map +0 -1
  176. package/dist/lib/global-agent-rules-standard.test.d.ts +0 -2
  177. package/dist/lib/global-agent-rules-standard.test.d.ts.map +0 -1
  178. package/dist/lib/global-agent-rules-stored-content.test.d.ts +0 -2
  179. package/dist/lib/global-agent-rules-stored-content.test.d.ts.map +0 -1
  180. package/dist/lib/global-source-coverage.test.d.ts +0 -2
  181. package/dist/lib/global-source-coverage.test.d.ts.map +0 -1
  182. package/dist/lib/instruction-graph-apply.test.d.ts +0 -2
  183. package/dist/lib/instruction-graph-apply.test.d.ts.map +0 -1
  184. package/dist/lib/instruction-graph.test.d.ts +0 -2
  185. package/dist/lib/instruction-graph.test.d.ts.map +0 -1
  186. package/dist/lib/machine.test.d.ts +0 -2
  187. package/dist/lib/machine.test.d.ts.map +0 -1
  188. package/dist/lib/managed-skill-runtimes.test.d.ts +0 -2
  189. package/dist/lib/managed-skill-runtimes.test.d.ts.map +0 -1
  190. package/dist/lib/package-manager-guard.test.d.ts +0 -2
  191. package/dist/lib/package-manager-guard.test.d.ts.map +0 -1
  192. package/dist/lib/project-context-prepared-file.test.d.ts +0 -2
  193. package/dist/lib/project-context-prepared-file.test.d.ts.map +0 -1
  194. package/dist/lib/project-context.test.d.ts +0 -2
  195. package/dist/lib/project-context.test.d.ts.map +0 -1
  196. package/dist/lib/project-dashboard-standard.test.d.ts +0 -2
  197. package/dist/lib/project-dashboard-standard.test.d.ts.map +0 -1
  198. package/dist/lib/provider-assets-adapters.test.d.ts +0 -2
  199. package/dist/lib/provider-assets-adapters.test.d.ts.map +0 -1
  200. package/dist/lib/provider-context.test.d.ts +0 -2
  201. package/dist/lib/provider-context.test.d.ts.map +0 -1
  202. package/dist/lib/provider-version-adapters.test.d.ts +0 -2
  203. package/dist/lib/provider-version-adapters.test.d.ts.map +0 -1
  204. package/dist/lib/redact.test.d.ts +0 -2
  205. package/dist/lib/redact.test.d.ts.map +0 -1
  206. package/dist/lib/session-apply.test.d.ts +0 -2
  207. package/dist/lib/session-apply.test.d.ts.map +0 -1
  208. package/dist/lib/session-authority.test.d.ts +0 -2
  209. package/dist/lib/session-authority.test.d.ts.map +0 -1
  210. package/dist/lib/session-render-managed-output-bound.test.d.ts +0 -2
  211. package/dist/lib/session-render-managed-output-bound.test.d.ts.map +0 -1
  212. package/dist/lib/session-render-ownership.test.d.ts +0 -2
  213. package/dist/lib/session-render-ownership.test.d.ts.map +0 -1
  214. package/dist/lib/session-render-rule-currency-floor.test.d.ts +0 -2
  215. package/dist/lib/session-render-rule-currency-floor.test.d.ts.map +0 -1
  216. package/dist/lib/session-render-silent-source-drop.test.d.ts +0 -2
  217. package/dist/lib/session-render-silent-source-drop.test.d.ts.map +0 -1
  218. package/dist/lib/session-render-state-hermeticity.test.d.ts +0 -2
  219. package/dist/lib/session-render-state-hermeticity.test.d.ts.map +0 -1
  220. package/dist/lib/session-render-state.test.d.ts +0 -2
  221. package/dist/lib/session-render-state.test.d.ts.map +0 -1
  222. package/dist/lib/session-render.test.d.ts +0 -2
  223. package/dist/lib/session-render.test.d.ts.map +0 -1
  224. package/dist/lib/station-profile.test.d.ts +0 -2
  225. package/dist/lib/station-profile.test.d.ts.map +0 -1
  226. package/dist/lib/sync-dir.test.d.ts +0 -2
  227. package/dist/lib/sync-dir.test.d.ts.map +0 -1
  228. package/dist/lib/sync-dry-run-unchanged.test.d.ts +0 -2
  229. package/dist/lib/sync-dry-run-unchanged.test.d.ts.map +0 -1
  230. package/dist/lib/sync-known.test.d.ts +0 -2
  231. package/dist/lib/sync-known.test.d.ts.map +0 -1
  232. package/dist/lib/sync.test.d.ts +0 -2
  233. package/dist/lib/sync.test.d.ts.map +0 -1
  234. package/dist/lib/template.test.d.ts +0 -2
  235. package/dist/lib/template.test.d.ts.map +0 -1
  236. package/dist/lib/transforms.test.d.ts +0 -2
  237. package/dist/lib/transforms.test.d.ts.map +0 -1
  238. package/dist/lib/transport-resolver.test.d.ts +0 -2
  239. package/dist/lib/transport-resolver.test.d.ts.map +0 -1
  240. package/dist/mcp/create-config-target-guard.test.d.ts +0 -2
  241. package/dist/mcp/create-config-target-guard.test.d.ts.map +0 -1
  242. package/dist/mcp/early-args.test.d.ts +0 -2
  243. package/dist/mcp/early-args.test.d.ts.map +0 -1
  244. package/dist/mcp/http.test.d.ts +0 -2
  245. package/dist/mcp/http.test.d.ts.map +0 -1
  246. package/dist/mcp/mcp.test.d.ts +0 -2
  247. package/dist/mcp/mcp.test.d.ts.map +0 -1
  248. package/dist/sdk/resolve.test.d.ts +0 -2
  249. package/dist/sdk/resolve.test.d.ts.map +0 -1
  250. package/dist/sdk/sdk-bundle-self-contained.test.d.ts +0 -2
  251. package/dist/sdk/sdk-bundle-self-contained.test.d.ts.map +0 -1
  252. package/dist/server/cloud-auth.test.d.ts +0 -2
  253. package/dist/server/cloud-auth.test.d.ts.map +0 -1
  254. package/dist/server/cloud.test.d.ts +0 -2
  255. package/dist/server/cloud.test.d.ts.map +0 -1
  256. package/dist/server/early-args.test.d.ts +0 -2
  257. package/dist/server/early-args.test.d.ts.map +0 -1
  258. package/dist/server/profile-contract.test.d.ts +0 -2
  259. package/dist/server/profile-contract.test.d.ts.map +0 -1
  260. package/dist/status.test.d.ts +0 -2
  261. package/dist/status.test.d.ts.map +0 -1
  262. package/dist/storage/cloud-store.test.d.ts +0 -2
  263. package/dist/storage/cloud-store.test.d.ts.map +0 -1
  264. package/dist/test-support/preload-state-home.d.ts +0 -2
  265. package/dist/test-support/preload-state-home.d.ts.map +0 -1
@@ -0,0 +1,322 @@
1
+ // @bun
2
+ import"./index-bsan4c16.js";
3
+
4
+ // src/lib/legacy-store-migration.ts
5
+ import { Database } from "bun:sqlite";
6
+ import { chmodSync, constants, copyFileSync, existsSync, mkdirSync, statSync } from "fs";
7
+ import { createHash } from "crypto";
8
+ import { dirname, join, resolve } from "path";
9
+ import { homedir } from "os";
10
+
11
+ class LegacyStoreMigrationError extends Error {
12
+ code;
13
+ constructor(code, message) {
14
+ super(message);
15
+ this.name = "LegacyStoreMigrationError";
16
+ this.code = code;
17
+ }
18
+ }
19
+ function emptyCounts() {
20
+ return { configs: 0, snapshots: 0, profiles: 0, profileConfigs: 0, machines: 0 };
21
+ }
22
+ function legacyInstructionsStorePaths(home = process.env.HOME || homedir()) {
23
+ return {
24
+ sourcePath: join(home, ".hasna", "configs", "configs.db"),
25
+ destinationPath: join(home, ".hasna", "instructions", "instructions.db")
26
+ };
27
+ }
28
+ function tableExists(db, table) {
29
+ return db.query("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = ?").get(table)?.present === 1;
30
+ }
31
+ function columns(db, table) {
32
+ if (!tableExists(db, table))
33
+ return new Set;
34
+ return new Set(db.query(`PRAGMA table_info(${table})`).all().map((row) => row.name));
35
+ }
36
+ function rows(db, table) {
37
+ if (!tableExists(db, table))
38
+ return [];
39
+ return db.query(`SELECT * FROM ${table} ORDER BY rowid`).all();
40
+ }
41
+ function value(row, name, fallback) {
42
+ return Object.prototype.hasOwnProperty.call(row, name) && row[name] !== undefined ? row[name] : fallback;
43
+ }
44
+ function normalizeJson(valueToNormalize, fallback) {
45
+ if (typeof valueToNormalize !== "string")
46
+ return fallback;
47
+ try {
48
+ JSON.parse(valueToNormalize);
49
+ return valueToNormalize;
50
+ } catch {
51
+ return fallback;
52
+ }
53
+ }
54
+ function compatibleConfig(row) {
55
+ return {
56
+ id: value(row, "id", ""),
57
+ name: value(row, "name", ""),
58
+ slug: value(row, "slug", ""),
59
+ kind: value(row, "kind", "file"),
60
+ category: value(row, "category", "rules"),
61
+ agent: value(row, "agent", "global"),
62
+ target_path: value(row, "target_path", null),
63
+ outputs: normalizeJson(value(row, "outputs", "[]"), "[]"),
64
+ format: value(row, "format", "text"),
65
+ content: value(row, "content", ""),
66
+ description: value(row, "description", null),
67
+ tags: normalizeJson(value(row, "tags", "[]"), "[]"),
68
+ is_template: value(row, "is_template", 0),
69
+ version: value(row, "version", 1),
70
+ created_at: value(row, "created_at", "1970-01-01T00:00:00.000Z"),
71
+ updated_at: value(row, "updated_at", value(row, "created_at", "1970-01-01T00:00:00.000Z")),
72
+ synced_at: value(row, "synced_at", null)
73
+ };
74
+ }
75
+ function compatibleProfile(row) {
76
+ return {
77
+ id: value(row, "id", ""),
78
+ name: value(row, "name", ""),
79
+ slug: value(row, "slug", ""),
80
+ description: value(row, "description", null),
81
+ selectors: normalizeJson(value(row, "selectors", "{}"), "{}"),
82
+ variables: normalizeJson(value(row, "variables", "{}"), "{}"),
83
+ created_at: value(row, "created_at", "1970-01-01T00:00:00.000Z"),
84
+ updated_at: value(row, "updated_at", value(row, "created_at", "1970-01-01T00:00:00.000Z"))
85
+ };
86
+ }
87
+ function compatibleMachine(row) {
88
+ return {
89
+ id: value(row, "id", ""),
90
+ hostname: value(row, "hostname", ""),
91
+ os: value(row, "os", null),
92
+ arch: value(row, "arch", null),
93
+ last_applied_at: value(row, "last_applied_at", null),
94
+ created_at: value(row, "created_at", "1970-01-01T00:00:00.000Z")
95
+ };
96
+ }
97
+ function stableSnapshotId(config) {
98
+ const digest = createHash("sha256").update(String(config.id)).update("\x00").update(String(config.version)).update("\x00").update(String(config.content)).digest("hex").slice(0, 32);
99
+ return `legacy-current-${digest}`;
100
+ }
101
+ function countDestinationRows(db) {
102
+ return ["configs", "config_snapshots", "profiles", "profile_configs", "machines"].reduce((total, table) => {
103
+ if (!tableExists(db, table))
104
+ return total;
105
+ return total + (db.query(`SELECT COUNT(*) AS count FROM ${table}`).get()?.count ?? 0);
106
+ }, 0);
107
+ }
108
+ function requireCurrentSchema(db) {
109
+ const required = {
110
+ configs: ["id", "slug", "kind", "outputs", "content", "version"],
111
+ config_snapshots: ["id", "config_id", "content", "version"],
112
+ profiles: ["id", "slug", "selectors", "variables"],
113
+ profile_configs: ["profile_id", "config_id", "binding"],
114
+ machines: ["id", "hostname", "arch"]
115
+ };
116
+ for (const [table, names] of Object.entries(required)) {
117
+ const actual = columns(db, table);
118
+ if (names.some((name) => !actual.has(name))) {
119
+ throw new LegacyStoreMigrationError("DESTINATION_SCHEMA_INCOMPATIBLE", `instructions legacy migration: destination is not a current Instructions SQLite database (incompatible ${table} table)`);
120
+ }
121
+ }
122
+ }
123
+ function sameConfig(a, b) {
124
+ const keys = ["id", "name", "slug", "kind", "category", "agent", "target_path", "outputs", "format", "content", "description", "tags", "is_template", "version", "created_at", "updated_at", "synced_at"];
125
+ return keys.every((key) => String(a[key] ?? "") === String(b[key] ?? ""));
126
+ }
127
+ function buildPlan(source, destination, mergePolicy) {
128
+ const sourceCounts = emptyCounts();
129
+ const migrated = emptyCounts();
130
+ const skipped = emptyCounts();
131
+ const planned = [];
132
+ let conflicts = 0;
133
+ let snapshotsCreated = 0;
134
+ const sourceConfigs = rows(source, "configs").map(compatibleConfig);
135
+ const sourceSnapshots = rows(source, "config_snapshots");
136
+ const sourceProfiles = rows(source, "profiles").map(compatibleProfile);
137
+ const sourceProfileConfigs = rows(source, "profile_configs");
138
+ const sourceMachines = rows(source, "machines").map(compatibleMachine);
139
+ sourceCounts.configs = sourceConfigs.length;
140
+ sourceCounts.snapshots = sourceSnapshots.length;
141
+ sourceCounts.profiles = sourceProfiles.length;
142
+ sourceCounts.profileConfigs = sourceProfileConfigs.length;
143
+ sourceCounts.machines = sourceMachines.length;
144
+ const acceptedConfigIds = new Set;
145
+ for (const config of sourceConfigs) {
146
+ const id = String(config.id);
147
+ const existing = destination.query("SELECT * FROM configs WHERE id = ? OR slug = ? LIMIT 1").get(id, String(config.slug));
148
+ if (existing) {
149
+ skipped.configs++;
150
+ if (!sameConfig(existing, config))
151
+ conflicts++;
152
+ if (mergePolicy === "preserve-destination" && String(existing.id) === id && sameConfig(existing, config)) {
153
+ acceptedConfigIds.add(id);
154
+ }
155
+ continue;
156
+ }
157
+ acceptedConfigIds.add(id);
158
+ planned.push({ table: "configs", values: config });
159
+ migrated.configs++;
160
+ }
161
+ const snapshotIdentity = new Set;
162
+ for (const snapshot of sourceSnapshots) {
163
+ const configId = String(value(snapshot, "config_id", ""));
164
+ if (!acceptedConfigIds.has(configId)) {
165
+ skipped.snapshots++;
166
+ continue;
167
+ }
168
+ const normalized = {
169
+ id: value(snapshot, "id", ""),
170
+ config_id: configId,
171
+ content: value(snapshot, "content", ""),
172
+ version: value(snapshot, "version", 1),
173
+ created_at: value(snapshot, "created_at", "1970-01-01T00:00:00.000Z")
174
+ };
175
+ const identity = `${configId}\x00${normalized.version}\x00${normalized.content}`;
176
+ snapshotIdentity.add(identity);
177
+ const existing = destination.query("SELECT id FROM config_snapshots WHERE id = ?").get(String(normalized.id));
178
+ if (existing)
179
+ skipped.snapshots++;
180
+ else {
181
+ planned.push({ table: "snapshots", values: normalized });
182
+ migrated.snapshots++;
183
+ }
184
+ }
185
+ for (const config of sourceConfigs) {
186
+ const configId = String(config.id);
187
+ if (!acceptedConfigIds.has(configId))
188
+ continue;
189
+ const identity = `${configId}\x00${config.version}\x00${config.content}`;
190
+ const destinationHas = destination.query("SELECT 1 AS present FROM config_snapshots WHERE config_id = ? AND version = ? AND content = ? LIMIT 1").get(configId, Number(config.version), String(config.content));
191
+ if (!snapshotIdentity.has(identity) && !destinationHas) {
192
+ planned.push({ table: "snapshots", values: {
193
+ id: stableSnapshotId(config),
194
+ config_id: configId,
195
+ content: config.content,
196
+ version: config.version,
197
+ created_at: config.updated_at
198
+ } });
199
+ migrated.snapshots++;
200
+ snapshotsCreated++;
201
+ }
202
+ }
203
+ const acceptedProfileIds = new Set;
204
+ for (const profile of sourceProfiles) {
205
+ const existing = destination.query("SELECT id FROM profiles WHERE id = ? OR slug = ? LIMIT 1").get(String(profile.id), String(profile.slug));
206
+ if (existing) {
207
+ skipped.profiles++;
208
+ if (String(existing.id) !== String(profile.id))
209
+ conflicts++;
210
+ } else {
211
+ acceptedProfileIds.add(String(profile.id));
212
+ planned.push({ table: "profiles", values: profile });
213
+ migrated.profiles++;
214
+ }
215
+ }
216
+ for (const link of sourceProfileConfigs) {
217
+ const profileId = String(value(link, "profile_id", ""));
218
+ const configId = String(value(link, "config_id", ""));
219
+ if (!acceptedProfileIds.has(profileId) || !acceptedConfigIds.has(configId)) {
220
+ skipped.profileConfigs++;
221
+ continue;
222
+ }
223
+ planned.push({ table: "profileConfigs", values: {
224
+ profile_id: profileId,
225
+ config_id: configId,
226
+ sort_order: value(link, "sort_order", 0),
227
+ binding: normalizeJson(value(link, "binding", '{"schema":"hasna.instructions.profile-config-binding/v1","activation":{"mode":"always"},"required":true,"fallback":"fail"}'), "{}")
228
+ } });
229
+ migrated.profileConfigs++;
230
+ }
231
+ for (const machine of sourceMachines) {
232
+ const existing = destination.query("SELECT id FROM machines WHERE id = ? OR hostname = ? LIMIT 1").get(String(machine.id), String(machine.hostname));
233
+ if (existing) {
234
+ skipped.machines++;
235
+ if (String(existing.id) !== String(machine.id))
236
+ conflicts++;
237
+ } else {
238
+ planned.push({ table: "machines", values: machine });
239
+ migrated.machines++;
240
+ }
241
+ }
242
+ return { source: sourceCounts, migrated, skipped, conflicts, snapshotsCreated, rows: planned };
243
+ }
244
+ function insertRow(db, row) {
245
+ const table = row.table === "snapshots" ? "config_snapshots" : row.table === "profileConfigs" ? "profile_configs" : row.table;
246
+ const names = Object.keys(row.values);
247
+ const placeholders = names.map(() => "?").join(", ");
248
+ db.run(`INSERT INTO ${table} (${names.join(", ")}) VALUES (${placeholders})`, names.map((name) => row.values[name]));
249
+ }
250
+ function migrateLegacyInstructionsStore(options) {
251
+ if (!options.operatorConfirmed) {
252
+ throw new LegacyStoreMigrationError("LOCAL_OPERATOR_CONFIRMATION_REQUIRED", "instructions legacy migration requires explicit local operator confirmation");
253
+ }
254
+ const defaults = legacyInstructionsStorePaths();
255
+ const sourcePath = resolve(options.sourcePath ?? defaults.sourcePath);
256
+ const destinationPath = resolve(options.destinationPath ?? defaults.destinationPath);
257
+ const dryRun = options.dryRun ?? true;
258
+ if (sourcePath === destinationPath) {
259
+ throw new LegacyStoreMigrationError("SOURCE_DESTINATION_SAME", "instructions legacy migration source and destination must differ");
260
+ }
261
+ if (!existsSync(sourcePath) || !statSync(sourcePath).isFile()) {
262
+ throw new LegacyStoreMigrationError("SOURCE_NOT_FOUND", "instructions legacy migration source database was not found");
263
+ }
264
+ if (!existsSync(destinationPath) || !statSync(destinationPath).isFile()) {
265
+ throw new LegacyStoreMigrationError("DESTINATION_NOT_FOUND", "instructions legacy migration destination must be an initialized current Instructions SQLite database");
266
+ }
267
+ const source = new Database(sourcePath, { readonly: true, strict: true });
268
+ const destination = new Database(destinationPath, { strict: true });
269
+ let backupPath = null;
270
+ try {
271
+ source.run("PRAGMA query_only = ON");
272
+ destination.run("PRAGMA foreign_keys = ON");
273
+ requireCurrentSchema(destination);
274
+ const destinationRows = countDestinationRows(destination);
275
+ if (destinationRows > 0 && options.mergePolicy === undefined) {
276
+ throw new LegacyStoreMigrationError("DESTINATION_NOT_EMPTY", "instructions legacy migration refuses a non-empty destination without an explicit merge policy");
277
+ }
278
+ const plan = buildPlan(source, destination, options.mergePolicy);
279
+ if (!dryRun) {
280
+ backupPath = resolve(options.backupPath ?? `${destinationPath}.before-legacy-migration.bak`);
281
+ if (backupPath === sourcePath || backupPath === destinationPath) {
282
+ throw new LegacyStoreMigrationError("INVALID_BACKUP_PATH", "instructions legacy migration backup path must be separate");
283
+ }
284
+ if (existsSync(backupPath)) {
285
+ throw new LegacyStoreMigrationError("BACKUP_ALREADY_EXISTS", "instructions legacy migration refuses to overwrite an existing destination backup");
286
+ }
287
+ mkdirSync(dirname(backupPath), { recursive: true, mode: 448 });
288
+ destination.run("PRAGMA wal_checkpoint(FULL)");
289
+ copyFileSync(destinationPath, backupPath, constants.COPYFILE_EXCL);
290
+ chmodSync(backupPath, 384);
291
+ const apply = destination.transaction(() => {
292
+ for (const row of plan.rows)
293
+ insertRow(destination, row);
294
+ });
295
+ apply();
296
+ }
297
+ return {
298
+ dryRun,
299
+ source: plan.source,
300
+ migrated: plan.migrated,
301
+ skipped: plan.skipped,
302
+ conflicts: plan.conflicts,
303
+ snapshotsCreated: plan.snapshotsCreated,
304
+ backupCreated: !dryRun,
305
+ sourcePath,
306
+ destinationPath,
307
+ backupPath
308
+ };
309
+ } catch (error) {
310
+ if (error instanceof LegacyStoreMigrationError)
311
+ throw error;
312
+ throw new LegacyStoreMigrationError("MIGRATION_FAILED", `instructions legacy migration failed safely: ${error instanceof Error ? error.name : "unknown error"}`);
313
+ } finally {
314
+ source.close();
315
+ destination.close();
316
+ }
317
+ }
318
+ export {
319
+ migrateLegacyInstructionsStore,
320
+ legacyInstructionsStorePaths,
321
+ LegacyStoreMigrationError
322
+ };