@hasna/instructions 0.4.36 → 0.4.42

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 (57) hide show
  1. package/README.md +9 -8
  2. package/assets/skills/inbox/SKILL.md +86 -0
  3. package/dashboard/README.md +34 -70
  4. package/dist/cli/index.js +1673 -516
  5. package/dist/cli/raw-store-root.test.d.ts +2 -0
  6. package/dist/cli/raw-store-root.test.d.ts.map +1 -0
  7. package/dist/data/config-store.d.ts +4 -4
  8. package/dist/data/config-store.d.ts.map +1 -1
  9. package/dist/db/configs.d.ts.map +1 -1
  10. package/dist/db/database.d.ts.map +1 -1
  11. package/dist/generated/storage-kit/backend.d.ts +20 -0
  12. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  13. package/dist/generated/storage-kit/index.d.ts +2 -2
  14. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  15. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  16. package/dist/generated/storage-kit/own.d.ts +11 -0
  17. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  18. package/dist/generated/storage-kit/pool.d.ts +7 -6
  19. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  20. package/dist/generated/storage-kit/tls.d.ts +30 -3
  21. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  22. package/dist/index.d.ts +4 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +945 -372
  25. package/dist/lib/instruction-graph.d.ts.map +1 -1
  26. package/dist/lib/managed-skill-runtimes.d.ts +80 -0
  27. package/dist/lib/managed-skill-runtimes.d.ts.map +1 -0
  28. package/dist/lib/managed-skill-runtimes.test.d.ts +2 -0
  29. package/dist/lib/managed-skill-runtimes.test.d.ts.map +1 -0
  30. package/dist/lib/raw-store-root.d.ts +17 -0
  31. package/dist/lib/raw-store-root.d.ts.map +1 -0
  32. package/dist/lib/retired-storage-mode.d.ts +8 -0
  33. package/dist/lib/retired-storage-mode.d.ts.map +1 -0
  34. package/dist/lib/session-apply.d.ts.map +1 -1
  35. package/dist/lib/session-authority.d.ts +27 -0
  36. package/dist/lib/session-authority.d.ts.map +1 -0
  37. package/dist/lib/session-authority.test.d.ts +2 -0
  38. package/dist/lib/session-authority.test.d.ts.map +1 -0
  39. package/dist/lib/session-render.d.ts +7 -3
  40. package/dist/lib/session-render.d.ts.map +1 -1
  41. package/dist/mcp/index.js +177 -115
  42. package/dist/server/cloud-auth.test.d.ts +2 -0
  43. package/dist/server/cloud-auth.test.d.ts.map +1 -0
  44. package/dist/server/cloud.d.ts +7 -4
  45. package/dist/server/cloud.d.ts.map +1 -1
  46. package/dist/server/cloud.test.d.ts +2 -0
  47. package/dist/server/cloud.test.d.ts.map +1 -0
  48. package/dist/server/index.d.ts +1 -1
  49. package/dist/server/index.js +781 -325
  50. package/dist/status.d.ts +11 -1
  51. package/dist/status.d.ts.map +1 -1
  52. package/dist/storage/cloud-store.d.ts.map +1 -1
  53. package/dist/storage/cloud-store.test.d.ts +2 -0
  54. package/dist/storage/cloud-store.test.d.ts.map +1 -0
  55. package/package.json +10 -7
  56. package/dist/generated/storage-kit/mode.d.ts +0 -48
  57. package/dist/generated/storage-kit/mode.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -47,7 +47,7 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
- // node_modules/commander/lib/error.js
50
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
51
51
  var require_error = __commonJS((exports) => {
52
52
  class CommanderError extends Error {
53
53
  constructor(exitCode, code, message) {
@@ -71,7 +71,7 @@ var require_error = __commonJS((exports) => {
71
71
  exports.InvalidArgumentError = InvalidArgumentError;
72
72
  });
73
73
 
74
- // node_modules/commander/lib/argument.js
74
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/argument.js
75
75
  var require_argument = __commonJS((exports) => {
76
76
  var { InvalidArgumentError } = require_error();
77
77
 
@@ -150,7 +150,7 @@ var require_argument = __commonJS((exports) => {
150
150
  exports.humanReadableArgName = humanReadableArgName;
151
151
  });
152
152
 
153
- // node_modules/commander/lib/help.js
153
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/help.js
154
154
  var require_help = __commonJS((exports) => {
155
155
  var { humanReadableArgName } = require_argument();
156
156
 
@@ -500,7 +500,7 @@ ${itemIndentStr}`);
500
500
  exports.stripColor = stripColor;
501
501
  });
502
502
 
503
- // node_modules/commander/lib/option.js
503
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/option.js
504
504
  var require_option = __commonJS((exports) => {
505
505
  var { InvalidArgumentError } = require_error();
506
506
 
@@ -678,7 +678,7 @@ var require_option = __commonJS((exports) => {
678
678
  exports.DualOptions = DualOptions;
679
679
  });
680
680
 
681
- // node_modules/commander/lib/suggestSimilar.js
681
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/suggestSimilar.js
682
682
  var require_suggestSimilar = __commonJS((exports) => {
683
683
  var maxDistance = 3;
684
684
  function editDistance(a, b) {
@@ -751,7 +751,7 @@ var require_suggestSimilar = __commonJS((exports) => {
751
751
  exports.suggestSimilar = suggestSimilar;
752
752
  });
753
753
 
754
- // node_modules/commander/lib/command.js
754
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/lib/command.js
755
755
  var require_command = __commonJS((exports) => {
756
756
  var EventEmitter = __require("events").EventEmitter;
757
757
  var childProcess = __require("child_process");
@@ -2061,7 +2061,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2061
2061
  exports.useColor = useColor;
2062
2062
  });
2063
2063
 
2064
- // node_modules/commander/index.js
2064
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/index.js
2065
2065
  var require_commander = __commonJS((exports) => {
2066
2066
  var { Argument } = require_argument();
2067
2067
  var { Command } = require_command();
@@ -2136,19 +2136,51 @@ var init_types = __esm(() => {
2136
2136
  };
2137
2137
  });
2138
2138
 
2139
+ // src/lib/retired-storage-mode.ts
2140
+ function firstDefinedEnvKey(env, keys) {
2141
+ for (const key of keys) {
2142
+ if (Object.hasOwn(env, key) && env[key] !== undefined)
2143
+ return key;
2144
+ }
2145
+ return null;
2146
+ }
2147
+ function assertNoLegacyStorageMode(env = process.env) {
2148
+ const legacyKey = firstDefinedEnvKey(env, LEGACY_STORAGE_MODE_KEYS);
2149
+ if (!legacyKey)
2150
+ return;
2151
+ throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_INSTRUCTIONS_API_URL + HASNA_INSTRUCTIONS_API_KEY. ` + `On the server, set HASNA_INSTRUCTIONS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
2152
+ }
2153
+ var LEGACY_STORAGE_MODE_KEYS;
2154
+ var init_retired_storage_mode = __esm(() => {
2155
+ LEGACY_STORAGE_MODE_KEYS = [
2156
+ "HASNA_INSTRUCTIONS_STORAGE_MODE",
2157
+ "HASNA_INSTRUCTIONS_MODE",
2158
+ "INSTRUCTIONS_STORAGE_MODE",
2159
+ "INSTRUCTIONS_MODE"
2160
+ ];
2161
+ });
2162
+
2163
+ // src/lib/raw-store-root.ts
2164
+ import { homedir as homedir2 } from "os";
2165
+ import { join as join2, resolve } from "path";
2166
+ function getRawStoreRoot() {
2167
+ return resolve(process.env[RAW_STORE_ROOT_ENV] || join2(process.env["HOME"] || homedir2(), ".hasna", "instructions"));
2168
+ }
2169
+ var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME";
2170
+ var init_raw_store_root = () => {};
2171
+
2139
2172
  // src/db/database.ts
2140
2173
  import { Database } from "bun:sqlite";
2141
2174
  import { existsSync as existsSync2, mkdirSync, rmSync } from "fs";
2142
- import { join as join2 } from "path";
2175
+ import { join as join3 } from "path";
2143
2176
  import { randomUUID as randomUUID3 } from "crypto";
2144
2177
  function getDbPath() {
2145
2178
  if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
2146
2179
  return process.env["HASNA_INSTRUCTIONS_DB_PATH"];
2147
2180
  }
2148
- const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
2149
- const dir = join2(home, ".hasna", "instructions");
2181
+ const dir = getRawStoreRoot();
2150
2182
  mkdirSync(dir, { recursive: true });
2151
- return join2(dir, "instructions.db");
2183
+ return join3(dir, "instructions.db");
2152
2184
  }
2153
2185
  function uuid() {
2154
2186
  return randomUUID3();
@@ -2162,8 +2194,9 @@ function slugify(name) {
2162
2194
  function getDatabase(path) {
2163
2195
  if (_db)
2164
2196
  return _db;
2197
+ assertNoLegacyStorageMode();
2165
2198
  if (!path && process.env["HASNA_INSTRUCTIONS_API_URL"] && process.env["HASNA_INSTRUCTIONS_API_KEY"]) {
2166
- throw new Error("instructions is in self_hosted (cloud) mode: this command is not wired to the cloud API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
2199
+ throw new Error("instructions is using the HTTP API transport (HASNA_INSTRUCTIONS_API_URL set): this command is not wired to the API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
2167
2200
  }
2168
2201
  const dbPath = path || getDbPath();
2169
2202
  const db = new Database(dbPath);
@@ -2244,6 +2277,8 @@ function insertFeedback(input, db) {
2244
2277
  }
2245
2278
  var MIGRATIONS, _db = null;
2246
2279
  var init_database = __esm(() => {
2280
+ init_retired_storage_mode();
2281
+ init_raw_store_root();
2247
2282
  MIGRATIONS = [
2248
2283
  `
2249
2284
  CREATE TABLE IF NOT EXISTS configs (
@@ -2328,6 +2363,37 @@ var init_database = __esm(() => {
2328
2363
  ];
2329
2364
  });
2330
2365
 
2366
+ // src/db/snapshots.ts
2367
+ function createSnapshot(configId, content, version, db) {
2368
+ const d = db || getDatabase();
2369
+ const id = uuid();
2370
+ const ts = now2();
2371
+ d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
2372
+ return { id, config_id: configId, content, version, created_at: ts };
2373
+ }
2374
+ function listSnapshots(configId, db) {
2375
+ const d = db || getDatabase();
2376
+ return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
2377
+ }
2378
+ function getSnapshot(id, db) {
2379
+ const d = db || getDatabase();
2380
+ return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
2381
+ }
2382
+ function getSnapshotByVersion(configId, version, db) {
2383
+ const d = db || getDatabase();
2384
+ return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
2385
+ }
2386
+ function pruneSnapshots(configId, keep = 10, db) {
2387
+ const d = db || getDatabase();
2388
+ const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
2389
+ SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
2390
+ )`, [configId, configId, keep]);
2391
+ return result.changes;
2392
+ }
2393
+ var init_snapshots = __esm(() => {
2394
+ init_database();
2395
+ });
2396
+
2331
2397
  // src/db/configs.ts
2332
2398
  function rowToConfig(row) {
2333
2399
  let outputs = [];
@@ -2366,25 +2432,28 @@ function createConfig(input, db) {
2366
2432
  const slug = uniqueSlug(input.name, d);
2367
2433
  const tags = JSON.stringify(input.tags || []);
2368
2434
  const outputs = JSON.stringify(input.outputs || []);
2369
- d.run(`INSERT INTO configs (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at, synced_at)
2370
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
2371
- id,
2372
- input.name,
2373
- slug,
2374
- input.kind ?? "file",
2375
- input.category,
2376
- input.agent ?? "global",
2377
- input.target_path ?? null,
2378
- outputs,
2379
- input.format ?? "text",
2380
- input.content,
2381
- input.description ?? null,
2382
- tags,
2383
- input.is_template ? 1 : 0,
2384
- ts,
2385
- ts
2386
- ]);
2387
- return getConfig(id, d);
2435
+ return d.transaction(() => {
2436
+ d.run(`INSERT INTO configs (id, name, slug, kind, category, agent, target_path, outputs, format, content, description, tags, is_template, version, created_at, updated_at, synced_at)
2437
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, NULL)`, [
2438
+ id,
2439
+ input.name,
2440
+ slug,
2441
+ input.kind ?? "file",
2442
+ input.category,
2443
+ input.agent ?? "global",
2444
+ input.target_path ?? null,
2445
+ outputs,
2446
+ input.format ?? "text",
2447
+ input.content,
2448
+ input.description ?? null,
2449
+ tags,
2450
+ input.is_template ? 1 : 0,
2451
+ ts,
2452
+ ts
2453
+ ]);
2454
+ createSnapshot(id, input.content, 1, d);
2455
+ return getConfig(id, d);
2456
+ })();
2388
2457
  }
2389
2458
  function getConfig(idOrSlug, db) {
2390
2459
  const d = db || getDatabase();
@@ -2489,9 +2558,13 @@ function updateConfig(idOrSlug, input, db) {
2489
2558
  updates.push("synced_at = ?");
2490
2559
  params.push(input.synced_at);
2491
2560
  }
2492
- params.push(existing.id);
2493
- d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
2494
- return getConfigById(existing.id, d);
2561
+ return d.transaction(() => {
2562
+ params.push(existing.id);
2563
+ d.run(`UPDATE configs SET ${updates.join(", ")} WHERE id = ?`, params);
2564
+ const updated = getConfigById(existing.id, d);
2565
+ createSnapshot(updated.id, updated.content, updated.version, d);
2566
+ return updated;
2567
+ })();
2495
2568
  }
2496
2569
  function deleteConfig(idOrSlug, db) {
2497
2570
  const d = db || getDatabase();
@@ -2511,6 +2584,7 @@ function getConfigStats(db) {
2511
2584
  var init_configs = __esm(() => {
2512
2585
  init_types();
2513
2586
  init_database();
2587
+ init_snapshots();
2514
2588
  });
2515
2589
 
2516
2590
  // src/lib/template.ts
@@ -2584,9 +2658,9 @@ var init_template = __esm(() => {
2584
2658
  });
2585
2659
 
2586
2660
  // src/lib/machine.ts
2587
- import { arch as currentArch, homedir as homedir2, hostname as currentHostname, type as currentOsType } from "os";
2661
+ import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
2588
2662
  import { existsSync as existsSync3 } from "fs";
2589
- import { join as join3 } from "path";
2663
+ import { join as join4 } from "path";
2590
2664
  function normalizeOsFamily(os) {
2591
2665
  const value = (os ?? "").trim().toLowerCase();
2592
2666
  if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
@@ -2598,11 +2672,11 @@ function normalizeOsFamily(os) {
2598
2672
  return value || "unknown";
2599
2673
  }
2600
2674
  function detectMachineContext(overrides = {}) {
2601
- const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir2();
2675
+ const homeDir = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
2602
2676
  const os = overrides.os ?? currentOsType();
2603
2677
  const osFamily = normalizeOsFamily(os);
2604
- const bunBinDir = overrides.bun_bin_dir ?? join3(homeDir, ".bun", "bin");
2605
- const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join3(bunBinDir, "bun");
2678
+ const bunBinDir = overrides.bun_bin_dir ?? join4(homeDir, ".bun", "bin");
2679
+ const defaultBunPath = osFamily === "macos" && existsSync3(BREW_BUN_PATH) ? BREW_BUN_PATH : join4(bunBinDir, "bun");
2606
2680
  return {
2607
2681
  id: "current-machine",
2608
2682
  hostname: overrides.hostname ?? currentHostname(),
@@ -2612,10 +2686,10 @@ function detectMachineContext(overrides = {}) {
2612
2686
  created_at: "",
2613
2687
  os_family: osFamily,
2614
2688
  home_dir: homeDir,
2615
- workspace_root: overrides.workspace_root ?? join3(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
2689
+ workspace_root: overrides.workspace_root ?? join4(homeDir, osFamily === "macos" ? "Workspace" : "workspace"),
2616
2690
  bun_bin_dir: bunBinDir,
2617
2691
  bun_path: overrides.bun_path ?? defaultBunPath,
2618
- path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join3("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2692
+ path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join4("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
2619
2693
  };
2620
2694
  }
2621
2695
  function machineContextToVariables(machine) {
@@ -3037,7 +3111,7 @@ Policy reference: \`${CODEWITH_SHARED_TODOS_STORAGE_POLICY_REFERENCE}\`
3037
3111
  `;
3038
3112
  });
3039
3113
 
3040
- // node_modules/zod/v3/helpers/util.js
3114
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
3041
3115
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
3042
3116
  const t = typeof data;
3043
3117
  switch (t) {
@@ -3168,7 +3242,7 @@ var init_util = __esm(() => {
3168
3242
  ]);
3169
3243
  });
3170
3244
 
3171
- // node_modules/zod/v3/ZodError.js
3245
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
3172
3246
  var ZodIssueCode, quotelessJson = (obj) => {
3173
3247
  const json = JSON.stringify(obj, null, 2);
3174
3248
  return json.replace(/"([^"]+)":/g, "$1:");
@@ -3289,7 +3363,7 @@ var init_ZodError = __esm(() => {
3289
3363
  };
3290
3364
  });
3291
3365
 
3292
- // node_modules/zod/v3/locales/en.js
3366
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
3293
3367
  var errorMap = (issue, _ctx) => {
3294
3368
  let message;
3295
3369
  switch (issue.code) {
@@ -3396,7 +3470,7 @@ var init_en = __esm(() => {
3396
3470
  en_default = errorMap;
3397
3471
  });
3398
3472
 
3399
- // node_modules/zod/v3/errors.js
3473
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
3400
3474
  function setErrorMap(map) {
3401
3475
  overrideErrorMap = map;
3402
3476
  }
@@ -3409,7 +3483,7 @@ var init_errors = __esm(() => {
3409
3483
  overrideErrorMap = en_default;
3410
3484
  });
3411
3485
 
3412
- // node_modules/zod/v3/helpers/parseUtil.js
3486
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
3413
3487
  function addIssueToContext(ctx, issueData) {
3414
3488
  const overrideMap = getErrorMap();
3415
3489
  const issue = makeIssue({
@@ -3514,10 +3588,10 @@ var init_parseUtil = __esm(() => {
3514
3588
  });
3515
3589
  });
3516
3590
 
3517
- // node_modules/zod/v3/helpers/typeAliases.js
3591
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
3518
3592
  var init_typeAliases = () => {};
3519
3593
 
3520
- // node_modules/zod/v3/helpers/errorUtil.js
3594
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
3521
3595
  var errorUtil;
3522
3596
  var init_errorUtil = __esm(() => {
3523
3597
  (function(errorUtil2) {
@@ -3526,7 +3600,7 @@ var init_errorUtil = __esm(() => {
3526
3600
  })(errorUtil || (errorUtil = {}));
3527
3601
  });
3528
3602
 
3529
- // node_modules/zod/v3/types.js
3603
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
3530
3604
  class ParseInputLazyPath {
3531
3605
  constructor(parent, value, path, key) {
3532
3606
  this._cachedPath = [];
@@ -6877,7 +6951,7 @@ var init_types2 = __esm(() => {
6877
6951
  NEVER = INVALID;
6878
6952
  });
6879
6953
 
6880
- // node_modules/zod/v3/external.js
6954
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
6881
6955
  var exports_external = {};
6882
6956
  __export(exports_external, {
6883
6957
  void: () => voidType,
@@ -6997,7 +7071,7 @@ var init_external = __esm(() => {
6997
7071
  init_ZodError();
6998
7072
  });
6999
7073
 
7000
- // node_modules/zod/index.js
7074
+ // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
7001
7075
  var init_zod = __esm(() => {
7002
7076
  init_external();
7003
7077
  init_external();
@@ -7189,7 +7263,7 @@ var init_redact = __esm(() => {
7189
7263
  VALUE_PATTERNS = [
7190
7264
  { re: /npm_[A-Za-z0-9]{36,}/, reason: "npm token" },
7191
7265
  { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
7192
- { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
7266
+ { re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, reason: "Anthropic API key" },
7193
7267
  { re: /sk-[A-Za-z0-9]{48,}/, reason: "OpenAI API key" },
7194
7268
  { re: /xoxb-[0-9]+-[A-Za-z0-9\-]+/, reason: "Slack bot token" },
7195
7269
  { re: /AIza[0-9A-Za-z\-_]{35}/, reason: "Google API key" },
@@ -7239,7 +7313,7 @@ import {
7239
7313
  statSync,
7240
7314
  writeFileSync
7241
7315
  } from "fs";
7242
- import { basename, dirname, isAbsolute, join as join4, parse, relative, resolve } from "path";
7316
+ import { basename, dirname, isAbsolute, join as join5, parse, relative, resolve as resolve2 } from "path";
7243
7317
  function managedObservationMaxBytes(relativePath) {
7244
7318
  return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
7245
7319
  }
@@ -7410,7 +7484,7 @@ function composeProjectContextSessionRender(input) {
7410
7484
  if (currentMarkers.block.id !== cache.project_id || currentMarkers.block.revision !== cache.revision || currentMarkers.block.hash !== cache.hash) {
7411
7485
  throw new ProjectContextError("MANAGED_BLOCK_CONFLICT", "project-context provider markers differ from the durable cache");
7412
7486
  }
7413
- const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve(file.path) === paths.target);
7487
+ const plannedIndexes = input.files.filter((file) => file.role === "index" && resolve2(file.path) === paths.target);
7414
7488
  if (plannedIndexes.length !== 1) {
7415
7489
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "session renderer does not own the selected project-context provider target");
7416
7490
  }
@@ -7468,7 +7542,7 @@ function withProjectContextSessionGuard(guard, action, options = {}) {
7468
7542
  verify();
7469
7543
  return action(null);
7470
7544
  }
7471
- const lockPath = resolve(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7545
+ const lockPath = resolve2(validated.workspace_root, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7472
7546
  const lock = acquireWorkspaceLock(validated.workspace_root, lockPath);
7473
7547
  try {
7474
7548
  verify();
@@ -7494,7 +7568,7 @@ function validateProjectContextSessionGuard(guard) {
7494
7568
  if (!isRecord(observed) || typeof observed.path !== "string") {
7495
7569
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains malformed hash metadata");
7496
7570
  }
7497
- const path = resolve(observed.path);
7571
+ const path = resolve2(observed.path);
7498
7572
  if (!allowedPaths.has(path) || observedPaths.has(path)) {
7499
7573
  throw new ProjectContextError("PROJECT_CONTEXT_SESSION_STALE", "session project-context guard contains an unexpected or duplicate path");
7500
7574
  }
@@ -7516,7 +7590,7 @@ function validateProjectContextSessionGuard(guard) {
7516
7590
  function applyProjectContext(options) {
7517
7591
  const workspaceRoot = assertSafeWorkspaceRoot(options.workspace_root);
7518
7592
  const now3 = options.now ?? new Date;
7519
- const lockPath = resolve(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7593
+ const lockPath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_LOCK_PATH.split("/"));
7520
7594
  const lock = options.dry_run ? null : acquireWorkspaceLock(workspaceRoot, lockPath, options.test_hooks?.after_lock_open, options.test_hooks?.before_stale_lock_remove, options.test_hooks?.process_start_identity);
7521
7595
  try {
7522
7596
  const resolved = resolveBundleForApply(options, workspaceRoot, now3);
@@ -7663,7 +7737,7 @@ function resolveBundleForApply(options, workspaceRoot, now3) {
7663
7737
  if (!options.expected_project_id) {
7664
7738
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_ID_REQUIRED", "expected_project_id is required for stale-cache fallback");
7665
7739
  }
7666
- const cachePath = resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
7740
+ const cachePath = resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
7667
7741
  const cache = readProjectContextCache(cachePath, workspaceRoot);
7668
7742
  if (!cache)
7669
7743
  throw new ProjectContextError("PROJECT_CONTEXT_CACHE_MISSING", "no last-known-good project context cache exists");
@@ -8019,7 +8093,7 @@ function buildManifest(plan, now3) {
8019
8093
  function buildSessionCompatibilityManifest(plan, now3) {
8020
8094
  const paths = runtimePaths(plan.workspace_root, plan.runtime);
8021
8095
  const tool = manifestTool(plan.runtime);
8022
- const targetHome = plan.runtime === "codewith" ? resolve(plan.workspace_root, ".codewith") : plan.workspace_root;
8096
+ const targetHome = plan.runtime === "codewith" ? resolve2(plan.workspace_root, ".codewith") : plan.workspace_root;
8023
8097
  const targetRelativePath = sessionTargetRelativePath(plan.runtime);
8024
8098
  const existing = existsSync4(paths.sessionManifest) ? readSessionManifestRecord(paths.sessionManifest, plan.workspace_root) : {
8025
8099
  schema: SESSION_RENDER_SCHEMA,
@@ -8041,7 +8115,7 @@ function buildSessionCompatibilityManifest(plan, now3) {
8041
8115
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest is malformed or incompatible");
8042
8116
  }
8043
8117
  const existingTargetHome = safeLegacyMetadataString(existing["targetHome"], null);
8044
- if (existingTargetHome !== null && resolve(existingTargetHome) !== targetHome) {
8118
+ if (existingTargetHome !== null && resolve2(existingTargetHome) !== targetHome) {
8045
8119
  throw new ProjectContextError("PROJECT_CONTEXT_MANIFEST_INVALID", "provider session manifest targets a different workspace");
8046
8120
  }
8047
8121
  const sources = sanitizeLegacySources(existing["sources"]).filter((source) => source["id"] !== "project-context-bundle");
@@ -8327,9 +8401,9 @@ function writeMetadataSnapshot(plan, now3) {
8327
8401
  const previous = readProjectContextManifest(plan.manifest_path, plan.workspace_root);
8328
8402
  if (!previous || previous.projectContext.revision === plan.bundle.revision && previous.projectContext.hash === plan.bundle.hash)
8329
8403
  return null;
8330
- const snapshotDir = resolve(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8404
+ const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8331
8405
  ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8332
- const snapshotPath = resolve(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8406
+ const snapshotPath = resolve2(snapshotDir, `${safeFilename(previous.projectContext.revision)}-${previous.projectContext.hash.slice(-12)}.json`);
8333
8407
  const snapshot = {
8334
8408
  schema: "hasna.configs.session-render-snapshot/v1",
8335
8409
  kind: "project-context-metadata",
@@ -8345,8 +8419,8 @@ function writeMetadataSnapshot(plan, now3) {
8345
8419
  return snapshotPath;
8346
8420
  }
8347
8421
  function metadataSnapshotMatchesManifest(plan, manifest) {
8348
- const snapshotDir = resolve(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8349
- const snapshotPath = resolve(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8422
+ const snapshotDir = resolve2(plan.workspace_root, ...PROJECT_CONTEXT_SNAPSHOT_DIR.split("/"));
8423
+ const snapshotPath = resolve2(snapshotDir, `${safeFilename(manifest.projectContext.revision)}-${manifest.projectContext.hash.slice(-12)}.json`);
8350
8424
  if (!existsSync4(snapshotPath))
8351
8425
  return false;
8352
8426
  const record = readJsonRecord(snapshotPath, plan.workspace_root);
@@ -8395,10 +8469,10 @@ function writeProjectContextRollbackSnapshot(plan, now3, outputs) {
8395
8469
  sha256: nextHash
8396
8470
  };
8397
8471
  });
8398
- const snapshotDir = resolve(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
8472
+ const snapshotDir = resolve2(plan.workspace_root, ...SESSION_RENDER_SNAPSHOT_RELATIVE_DIR.split("/"));
8399
8473
  ensureSafeDirectory(snapshotDir, plan.workspace_root, 448);
8400
8474
  const timestamp = now3.toISOString().replace(/[:.]/g, "-");
8401
- const snapshotPath = resolve(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8475
+ const snapshotPath = resolve2(snapshotDir, `${timestamp}-${randomUUID5()}.json`);
8402
8476
  const snapshot = {
8403
8477
  schema: "hasna.configs.session-render-snapshot/v2",
8404
8478
  createdAt: now3.toISOString(),
@@ -8463,7 +8537,7 @@ function readSessionManifestRecord(path, workspaceRoot) {
8463
8537
  }
8464
8538
  }
8465
8539
  function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash, afterExchange, atomicExchangeUnavailable = false, beforeInstall, portableCreateOnly = false, maxObservedBytes, allowPortableReplacement = false) {
8466
- const dir = resolve(path, "..");
8540
+ const dir = resolve2(path, "..");
8467
8541
  ensureSafeDirectory(dir, workspaceRoot, 448);
8468
8542
  assertNoSymlinkSegments(workspaceRoot, path);
8469
8543
  const anchoredOps = portableCreateOnly ? null : resolveAnchoredFsOps();
@@ -8480,7 +8554,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
8480
8554
  const previous = anchoredFileObservation(directory, targetName);
8481
8555
  const previousMode = previous?.mode ?? defaultMode;
8482
8556
  const tempName = `.project-context-${randomUUID5()}.tmp`;
8483
- const tempPath = join4(dir, tempName);
8557
+ const tempPath = join5(dir, tempName);
8484
8558
  let fd = null;
8485
8559
  let preserveTemp = false;
8486
8560
  let directoryChanged = false;
@@ -8611,7 +8685,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
8611
8685
  }
8612
8686
  const dir = dirname(path);
8613
8687
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8614
- const tempPath = join4(dir, `.project-context-${randomUUID5()}.tmp`);
8688
+ const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
8615
8689
  let fd = null;
8616
8690
  let tempIdentity = null;
8617
8691
  try {
@@ -8668,7 +8742,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
8668
8742
  }
8669
8743
  const dir = dirname(path);
8670
8744
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8671
- const tempPath = join4(dir, `.project-context-${randomUUID5()}.tmp`);
8745
+ const tempPath = join5(dir, `.project-context-${randomUUID5()}.tmp`);
8672
8746
  const desiredHash = sha2562(content);
8673
8747
  let fd = null;
8674
8748
  let tempIdentity = null;
@@ -8737,11 +8811,11 @@ function portableFileHash(path, workspaceRoot, maxObservedBytes) {
8737
8811
  return createHash2("sha256").update(readFileSync(path)).digest("hex");
8738
8812
  }
8739
8813
  function writeProjectContextCoordinatedFile(input) {
8740
- atomicWriteFile(resolve(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
8814
+ atomicWriteFile(resolve2(input.path), input.content, assertSafeWorkspaceRoot(input.workspace_root), input.default_mode ?? 420, input.expected_hash, undefined, false, input.test_hooks?.before_install, input.force_portable_file_ops ?? false, input.max_observed_bytes, input.allow_portable_replacement ?? false);
8741
8815
  }
8742
8816
  function removeProjectContextCoordinatedFile(input) {
8743
8817
  const workspaceRoot = assertSafeWorkspaceRoot(input.workspace_root);
8744
- const path = resolve(input.path);
8818
+ const path = resolve2(input.path);
8745
8819
  assertNoSymlinkSegments(workspaceRoot, path);
8746
8820
  const dir = dirname(path);
8747
8821
  const anchoredOps = input.force_portable_file_ops ? null : resolveAnchoredFsOps();
@@ -8767,7 +8841,7 @@ function removeProjectContextCoordinatedFile(input) {
8767
8841
  throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
8768
8842
  }
8769
8843
  displaced = true;
8770
- input.test_hooks?.after_displace?.(join4(dir, displacedName));
8844
+ input.test_hooks?.after_displace?.(join5(dir, displacedName));
8771
8845
  const moved = anchoredFileObservation(directory, displacedName);
8772
8846
  if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
8773
8847
  throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
@@ -8813,7 +8887,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
8813
8887
  }
8814
8888
  const dir = dirname(path);
8815
8889
  const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
8816
- const displacedPath = join4(dir, `.project-context-delete-${randomUUID5()}.tmp`);
8890
+ const displacedPath = join5(dir, `.project-context-delete-${randomUUID5()}.tmp`);
8817
8891
  let displaced = false;
8818
8892
  try {
8819
8893
  assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
@@ -8884,7 +8958,7 @@ function anchoredOpenExclusive(directory, name, mode) {
8884
8958
  const requestedMode = mode & 4095;
8885
8959
  let fd;
8886
8960
  try {
8887
- fd = openSync(join4(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
8961
+ fd = openSync(join5(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
8888
8962
  } catch {
8889
8963
  throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
8890
8964
  }
@@ -8927,7 +9001,7 @@ function anchoredFileObservation(directory, name) {
8927
9001
  const stat = fstatSync(fd);
8928
9002
  if (!stat.isFile())
8929
9003
  throw new ProjectContextHashRace("managed output is not a regular file");
8930
- const relativePath = relativePosix(directory.workspaceRoot, join4(directory.path, name));
9004
+ const relativePath = relativePosix(directory.workspaceRoot, join5(directory.path, name));
8931
9005
  const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
8932
9006
  if (maxBytes !== null && stat.size > maxBytes) {
8933
9007
  throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
@@ -8953,7 +9027,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
8953
9027
  return observed;
8954
9028
  }
8955
9029
  function captureManagedDirectoryIdentity(path, workspaceRoot) {
8956
- assertNoSymlinkSegments(workspaceRoot, join4(path, ".project-context-directory-guard"));
9030
+ assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
8957
9031
  let stat;
8958
9032
  try {
8959
9033
  stat = lstatSync(path);
@@ -8966,7 +9040,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
8966
9040
  return { dev: stat.dev, ino: stat.ino };
8967
9041
  }
8968
9042
  function assertManagedDirectoryStable(path, workspaceRoot, expected) {
8969
- assertNoSymlinkSegments(workspaceRoot, join4(path, ".project-context-directory-guard"));
9043
+ assertNoSymlinkSegments(workspaceRoot, join5(path, ".project-context-directory-guard"));
8970
9044
  let current;
8971
9045
  try {
8972
9046
  current = lstatSync(path);
@@ -9099,10 +9173,10 @@ function resolveAnchoredFsOps() {
9099
9173
  return null;
9100
9174
  }
9101
9175
  function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRemove, processStartIdentityLookup = processStartIdentity) {
9102
- const lockDirectory = resolve(lockPath, "..");
9176
+ const lockDirectory = resolve2(lockPath, "..");
9103
9177
  ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
9104
9178
  assertNoSymlinkSegments(workspaceRoot, lockPath);
9105
- const tempPath = join4(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9179
+ const tempPath = join5(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
9106
9180
  let fd = null;
9107
9181
  let openedIdentity = null;
9108
9182
  let openedContentHash = null;
@@ -9174,7 +9248,7 @@ function removeOwnedLockByInode(lockPath, identity, expectedHash) {
9174
9248
  if (expectedHash !== undefined && sha2562(readFileSync(lockPath, "utf8")) !== expectedHash)
9175
9249
  return;
9176
9250
  rmSync2(lockPath);
9177
- fsyncDirectory(resolve(lockPath, ".."));
9251
+ fsyncDirectory(resolve2(lockPath, ".."));
9178
9252
  } catch {}
9179
9253
  }
9180
9254
  function observeStaleWorkspaceLock(lockPath, workspaceRoot, processStartIdentityLookup = processStartIdentity) {
@@ -9253,7 +9327,7 @@ function tryTakeoverStaleWorkspaceLock(candidatePath, lockPath, workspaceRoot, c
9253
9327
  throw new ProjectContextError("PROJECT_CONTEXT_LOCK_LOST", "workspace lock changed during stale-lock takeover and could not be restored safely");
9254
9328
  }
9255
9329
  rmSync2(candidatePath);
9256
- fsyncDirectory(resolve(lockPath, ".."));
9330
+ fsyncDirectory(resolve2(lockPath, ".."));
9257
9331
  exchanged = false;
9258
9332
  return true;
9259
9333
  } catch (error) {
@@ -9332,8 +9406,8 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
9332
9406
  }
9333
9407
  return;
9334
9408
  }
9335
- const lockDirectory = resolve(lockPath, "..");
9336
- const releasePath = join4(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9409
+ const lockDirectory = resolve2(lockPath, "..");
9410
+ const releasePath = join5(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
9337
9411
  let releaseFd = null;
9338
9412
  let releaseIdentity = null;
9339
9413
  let releaseHash = null;
@@ -9413,7 +9487,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9413
9487
  const segments = rel.split(/[\\/]+/).filter(Boolean);
9414
9488
  let current = workspaceRoot;
9415
9489
  for (const segment of segments) {
9416
- current = join4(current, segment);
9490
+ current = join5(current, segment);
9417
9491
  if (existsSync4(current)) {
9418
9492
  if (lstatSync(current).isSymbolicLink())
9419
9493
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
@@ -9421,7 +9495,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
9421
9495
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", `managed path is not a directory: ${current}`);
9422
9496
  } else {
9423
9497
  mkdirSync2(current, { mode });
9424
- fsyncDirectory(resolve(current, ".."));
9498
+ fsyncDirectory(resolve2(current, ".."));
9425
9499
  }
9426
9500
  }
9427
9501
  }
@@ -9477,11 +9551,11 @@ function scanGeneratedContent(content) {
9477
9551
  function runtimePaths(workspaceRoot, runtime) {
9478
9552
  const relativeTarget = runtime === "claude" ? "CLAUDE.md" : runtime === "codewith" ? ".codewith/CODEWITH.md" : "AGENTS.md";
9479
9553
  return {
9480
- target: resolve(workspaceRoot, ...relativeTarget.split("/")),
9481
- fragment: resolve(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9482
- manifest: resolve(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9483
- cache: resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9484
- sessionManifest: runtime === "codewith" ? resolve(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve(workspaceRoot, ".hasna", "session-render-manifest.json")
9554
+ target: resolve2(workspaceRoot, ...relativeTarget.split("/")),
9555
+ fragment: resolve2(workspaceRoot, ...PROJECT_CONTEXT_FRAGMENT_PATH.split("/")),
9556
+ manifest: resolve2(workspaceRoot, ...PROJECT_CONTEXT_MANIFEST_PATH.split("/")),
9557
+ cache: resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/")),
9558
+ sessionManifest: runtime === "codewith" ? resolve2(workspaceRoot, ".codewith", ".hasna", "session-render-manifest.json") : resolve2(workspaceRoot, ".hasna", "session-render-manifest.json")
9485
9559
  };
9486
9560
  }
9487
9561
  function projectContextSessionGuardPaths(paths, runtime) {
@@ -9491,7 +9565,7 @@ function projectContextSessionGuardPaths(paths, runtime) {
9491
9565
  paths.fragment,
9492
9566
  paths.target,
9493
9567
  paths.sessionManifest,
9494
- ...runtime === "codewith" ? [resolve(paths.target, "..", "CODEWITH.override.md")] : []
9568
+ ...runtime === "codewith" ? [resolve2(paths.target, "..", "CODEWITH.override.md")] : []
9495
9569
  ];
9496
9570
  }
9497
9571
  function sessionTargetRelativePath(runtime) {
@@ -9511,12 +9585,12 @@ function projectContextRuntimeForSessionTool(tool) {
9511
9585
  return null;
9512
9586
  }
9513
9587
  function projectContextWorkspaceForSession(input, runtime) {
9514
- const targetHome = resolve(input.target_home);
9588
+ const targetHome = resolve2(input.target_home);
9515
9589
  if (runtime === "codewith") {
9516
9590
  const workspaceRoot = basename(targetHome) === ".codewith" ? dirname(targetHome) : null;
9517
9591
  if (!workspaceRoot)
9518
9592
  return null;
9519
- if (input.project_root && resolve(input.project_root) !== workspaceRoot) {
9593
+ if (input.project_root && resolve2(input.project_root) !== workspaceRoot) {
9520
9594
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "Codewith project_root must be the parent workspace of target_home");
9521
9595
  }
9522
9596
  if (!existsSync4(workspaceRoot) || !lstatSync(workspaceRoot).isDirectory())
@@ -9530,7 +9604,7 @@ function projectContextWorkspaceForSession(input, runtime) {
9530
9604
  function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9531
9605
  if (runtime !== "codewith")
9532
9606
  return;
9533
- const override = resolve(workspaceRoot, ".codewith", "CODEWITH.override.md");
9607
+ const override = resolve2(workspaceRoot, ".codewith", "CODEWITH.override.md");
9534
9608
  if (!existsSync4(override))
9535
9609
  return;
9536
9610
  assertNoSymlinkSegments(workspaceRoot, override);
@@ -9541,7 +9615,7 @@ function assertCodewithTargetIsConsumed(workspaceRoot, runtime) {
9541
9615
  function assertSafeWorkspaceRoot(path) {
9542
9616
  if (!isAbsolute(path))
9543
9617
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root must be absolute");
9544
- const normalized = resolve(path);
9618
+ const normalized = resolve2(path);
9545
9619
  if (normalized === parse(normalized).root)
9546
9620
  throw new ProjectContextError("PROJECT_CONTEXT_PATH_INVALID", "workspace root cannot be the filesystem root");
9547
9621
  if (!existsSync4(normalized) || !lstatSync(normalized).isDirectory())
@@ -9558,17 +9632,17 @@ function assertNoSymlinkSegments(root, target) {
9558
9632
  }
9559
9633
  let current = root;
9560
9634
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
9561
- current = join4(current, segment);
9635
+ current = join5(current, segment);
9562
9636
  if (existsSync4(current) && lstatSync(current).isSymbolicLink()) {
9563
9637
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
9564
9638
  }
9565
9639
  }
9566
9640
  }
9567
9641
  function assertNoSymlinkAncestors(path) {
9568
- const normalized = resolve(path);
9642
+ const normalized = resolve2(path);
9569
9643
  let current = parse(normalized).root;
9570
9644
  for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
9571
- current = join4(current, segment);
9645
+ current = join5(current, segment);
9572
9646
  if (!existsSync4(current))
9573
9647
  return;
9574
9648
  if (lstatSync(current).isSymbolicLink())
@@ -9604,10 +9678,10 @@ function fragmentMatchesBundle(path, bundle, workspaceRoot) {
9604
9678
  }
9605
9679
  function durableSourcePath(path, workspaceRoot) {
9606
9680
  if (!path || path.startsWith("/dev/fd/"))
9607
- return resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9608
- const normalized = isAbsolute(path) ? resolve(path) : resolve(workspaceRoot, path);
9681
+ return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9682
+ const normalized = isAbsolute(path) ? resolve2(path) : resolve2(workspaceRoot, path);
9609
9683
  if (normalized.startsWith("/dev/fd/"))
9610
- return resolve(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9684
+ return resolve2(workspaceRoot, ...PROJECT_CONTEXT_CACHE_PATH.split("/"));
9611
9685
  return normalized;
9612
9686
  }
9613
9687
  function compareRevisions(incoming, previous) {
@@ -10167,7 +10241,7 @@ function compareProviderVersions(left, right) {
10167
10241
 
10168
10242
  // src/lib/asset-plan.ts
10169
10243
  import { createHash as createHash3 } from "crypto";
10170
- import { isAbsolute as isAbsolute2, posix, resolve as resolve2 } from "path";
10244
+ import { isAbsolute as isAbsolute2, posix, resolve as resolve3 } from "path";
10171
10245
  function assetCapability(provider, surface, kind, support, strategies, note, providerVersionRange = "*") {
10172
10246
  return Object.freeze({
10173
10247
  schema: ASSET_CAPABILITY_SCHEMA,
@@ -10374,8 +10448,8 @@ function resolveAssetDestination(item, roots) {
10374
10448
  if (!isAbsolute2(root))
10375
10449
  throw new Error(`Asset ${item.assetKey} destination root must be absolute.`);
10376
10450
  const relativePath = safeRelativePath(item.destination.relativePath);
10377
- const target = resolve2(root, ...relativePath.split("/"));
10378
- const normalizedRoot = resolve2(root);
10451
+ const target = resolve3(root, ...relativePath.split("/"));
10452
+ const normalizedRoot = resolve3(root);
10379
10453
  if (target === normalizedRoot)
10380
10454
  throw new Error(`Asset ${item.assetKey} destination cannot replace its root.`);
10381
10455
  if (!target.startsWith(`${normalizedRoot}/`))
@@ -10555,13 +10629,13 @@ var init_asset_plan = __esm(() => {
10555
10629
  // src/lib/cursor-authority.ts
10556
10630
  import { createHash as createHash4 } from "crypto";
10557
10631
  import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
10558
- import { homedir as homedir3 } from "os";
10559
- import { join as join5, resolve as resolve3 } from "path";
10632
+ import { homedir as homedir4 } from "os";
10633
+ import { join as join6, resolve as resolve4 } from "path";
10560
10634
  function sha2564(content) {
10561
10635
  return createHash4("sha256").update(content).digest("hex");
10562
10636
  }
10563
10637
  function homeDir() {
10564
- return process.env["HOME"] || homedir3();
10638
+ return process.env["HOME"] || homedir4();
10565
10639
  }
10566
10640
  function markerPayload(content, markerLine, markerIndex) {
10567
10641
  const index = markerIndex ?? content.indexOf(markerLine);
@@ -10577,12 +10651,12 @@ function baseObservation(path) {
10577
10651
  };
10578
10652
  }
10579
10653
  function observeCursorGlobalAuthority(options = {}) {
10580
- const authorityPath = resolve3(join5(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10654
+ const authorityPath = resolve4(join6(options.home ?? homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10581
10655
  const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
10582
10656
  return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
10583
10657
  }
10584
10658
  function observeCursorGlobalAuthorityAtPath(authorityPath) {
10585
- return observeCursorGlobalAuthorityPath(resolve3(authorityPath), (path) => readFileSync2(path, "utf8"));
10659
+ return observeCursorGlobalAuthorityPath(resolve4(authorityPath), (path) => readFileSync2(path, "utf8"));
10586
10660
  }
10587
10661
  function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10588
10662
  const base = baseObservation(authorityPath);
@@ -10727,7 +10801,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
10727
10801
  };
10728
10802
  }
10729
10803
  function isCursorGlobalAuthorityPath(path) {
10730
- return resolve3(path) === resolve3(join5(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10804
+ return resolve4(path) === resolve4(join6(homeDir(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
10731
10805
  }
10732
10806
  function stampCursorGlobalAuthorityMarker(content) {
10733
10807
  if (CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN.test(content))
@@ -10770,11 +10844,88 @@ var init_cursor_authority = __esm(() => {
10770
10844
  CURSOR_GLOBAL_AUTHORITY_FRONTMATTER_PATTERN = /^---\n[\s\S]*?\n---(?:\n|$)/;
10771
10845
  });
10772
10846
 
10773
- // src/lib/session-render.ts
10847
+ // src/lib/session-authority.ts
10774
10848
  import { createHash as createHash5 } from "crypto";
10775
- import { existsSync as existsSync5, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
10776
- import { homedir as homedir4 } from "os";
10777
- import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute3, join as join6, parse as parse2, posix as posix2, relative as relative2, resolve as resolve4 } from "path";
10849
+ import { lstatSync as lstatSync3, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
10850
+ import { join as join7, resolve as resolve5 } from "path";
10851
+ function sha2565(content) {
10852
+ return createHash5("sha256").update(content).digest("hex");
10853
+ }
10854
+ function detectClaudeAuthorityConflicts(targetHome) {
10855
+ const authorityPath = resolve5(join7(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
10856
+ let stat;
10857
+ try {
10858
+ stat = lstatSync3(authorityPath);
10859
+ } catch {
10860
+ return [];
10861
+ }
10862
+ const provenanceBase = {
10863
+ tool: "claude",
10864
+ relativePath: CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH,
10865
+ path: authorityPath
10866
+ };
10867
+ if (stat.isSymbolicLink() || !stat.isFile()) {
10868
+ return [{
10869
+ ...provenanceBase,
10870
+ kind: "invalid-unmanaged-authority",
10871
+ sha256: null,
10872
+ markers: [],
10873
+ provenance: {
10874
+ source: "filesystem",
10875
+ authority: "unmanaged",
10876
+ observedPath: authorityPath,
10877
+ detection: "non-regular-file"
10878
+ },
10879
+ reason: "Claude target contains unmanaged AGENTS.md that is not a regular file; authority cannot be verified safely."
10880
+ }];
10881
+ }
10882
+ if (statSync2(authorityPath).size > CLAUDE_LEGACY_AUTHORITY_MAX_BYTES) {
10883
+ return [{
10884
+ ...provenanceBase,
10885
+ kind: "invalid-unmanaged-authority",
10886
+ sha256: null,
10887
+ markers: [],
10888
+ provenance: {
10889
+ source: "filesystem",
10890
+ authority: "unmanaged",
10891
+ observedPath: authorityPath,
10892
+ detection: "oversized-file"
10893
+ },
10894
+ reason: `Claude target contains unmanaged AGENTS.md larger than ${CLAUDE_LEGACY_AUTHORITY_MAX_BYTES} bytes; authority cannot be classified safely.`
10895
+ }];
10896
+ }
10897
+ const content = readFileSync3(authorityPath, "utf8");
10898
+ const markers = CLAUDE_LEGACY_MARKERS.filter((marker) => marker.pattern.test(content)).map((marker) => marker.id);
10899
+ const knownLegacy = markers.includes("no-worktrees-heading") && markers.includes("no-worktrees-directive");
10900
+ return [{
10901
+ ...provenanceBase,
10902
+ kind: knownLegacy ? "known-legacy-no-worktree" : "unknown-unmanaged-authority",
10903
+ sha256: sha2565(content),
10904
+ markers,
10905
+ provenance: {
10906
+ source: "filesystem",
10907
+ authority: "unmanaged",
10908
+ observedPath: authorityPath,
10909
+ detection: knownLegacy ? "known-legacy-markers" : "unknown-content"
10910
+ },
10911
+ reason: knownLegacy ? "Claude target contains unmanaged legacy AGENTS.md with no-worktree directives; migrate or remove it through an owned authority path before applying." : "Claude target contains unmanaged AGENTS.md with unknown authority content; refusing to guess whether it conflicts with the managed Claude render."
10912
+ }];
10913
+ }
10914
+ var CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH = "AGENTS.md", CLAUDE_LEGACY_AUTHORITY_MAX_BYTES, CLAUDE_LEGACY_MARKERS;
10915
+ var init_session_authority = __esm(() => {
10916
+ CLAUDE_LEGACY_AUTHORITY_MAX_BYTES = 256 * 1024;
10917
+ CLAUDE_LEGACY_MARKERS = [
10918
+ { id: "claude-agent-rules-heading", pattern: /^# Agent Rules \(Claude\)/m },
10919
+ { id: "no-worktrees-heading", pattern: /^## No Worktrees/m },
10920
+ { id: "no-worktrees-directive", pattern: /\bNever use git worktrees\b/m }
10921
+ ];
10922
+ });
10923
+
10924
+ // src/lib/session-render.ts
10925
+ import { createHash as createHash6 } from "crypto";
10926
+ import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "fs";
10927
+ import { homedir as homedir5 } from "os";
10928
+ import { basename as basename3, dirname as dirname2, extname as extname2, isAbsolute as isAbsolute3, join as join8, parse as parse2, posix as posix2, relative as relative2, resolve as resolve6 } from "path";
10778
10929
  function normalizeSessionInstructionLayer(value) {
10779
10930
  if (value === "provider")
10780
10931
  return "tool";
@@ -10791,11 +10942,11 @@ function ensureTrailingNewline3(content) {
10791
10942
  `) ? content : `${content}
10792
10943
  `;
10793
10944
  }
10794
- function sha2565(content) {
10795
- return createHash5("sha256").update(content).digest("hex");
10945
+ function sha2566(content) {
10946
+ return createHash6("sha256").update(content).digest("hex");
10796
10947
  }
10797
10948
  function fingerprint(value) {
10798
- return sha2565(JSON.stringify(value));
10949
+ return sha2566(JSON.stringify(value));
10799
10950
  }
10800
10951
  function canonicalFingerprintValue(value) {
10801
10952
  if (Array.isArray(value))
@@ -10813,7 +10964,7 @@ function ruleAttestation(rule) {
10813
10964
  };
10814
10965
  const applied = metadata["payloadFloorApplied"];
10815
10966
  return {
10816
- contentSha256: sha2565(rule.content ?? ""),
10967
+ contentSha256: sha2566(rule.content ?? ""),
10817
10968
  payloadFloorApplied: typeof applied === "boolean" ? applied : null,
10818
10969
  flooredFromRulesVersion: read("flooredFromRulesVersion"),
10819
10970
  flooredFromPayloadSha256: read("flooredFromPayloadSha256"),
@@ -10859,16 +11010,14 @@ function slug(value) {
10859
11010
  function yamlQuote2(value) {
10860
11011
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
10861
11012
  }
10862
- function getRawStoreRoot() {
10863
- return resolve4(process.env[RAW_STORE_ROOT_ENV] || join6(process.env["HOME"] || homedir4(), ".hasna", "configs"));
10864
- }
10865
11013
  function defaultTargetHome(tool, profile, sessionId) {
10866
- return join6(getRawStoreRoot(), "sessions", tool, slug(profile), slug(sessionId || "latest"));
11014
+ const home = process.env["HOME"] || homedir5();
11015
+ return join8(home, ".hasna", "accounts", "profiles", tool, slug(profile));
10867
11016
  }
10868
11017
  function joinTarget(targetHome, relativePath) {
10869
11018
  const safeTargetHome = assertSafeTargetRoot(targetHome);
10870
11019
  const safeRelativePath2 = assertSafeRelativePath(relativePath);
10871
- return join6(safeTargetHome, ...safeRelativePath2.split("/"));
11020
+ return join8(safeTargetHome, ...safeRelativePath2.split("/"));
10872
11021
  }
10873
11022
  function makeFile(targetHome, relativePath, role, content, sourceIds) {
10874
11023
  const safeTargetHome = assertSafeTargetRoot(targetHome);
@@ -10879,7 +11028,7 @@ function makeFile(targetHome, relativePath, role, content, sourceIds) {
10879
11028
  relativePath: safeRelativePath2,
10880
11029
  role,
10881
11030
  content: normalizedContent,
10882
- sha256: sha2565(normalizedContent),
11031
+ sha256: sha2566(normalizedContent),
10883
11032
  sourceIds
10884
11033
  };
10885
11034
  }
@@ -10913,7 +11062,7 @@ function applyAgentOperatingRulesFloor(source, content) {
10913
11062
  const floored = {
10914
11063
  payloadFloorApplied: true,
10915
11064
  flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
10916
- flooredFromPayloadSha256: sha2565(content)
11065
+ flooredFromPayloadSha256: sha2566(content)
10917
11066
  };
10918
11067
  return {
10919
11068
  content: payload.content,
@@ -10935,7 +11084,7 @@ function applyAgentOperatingRulesFloorToRule(source, rule, content) {
10935
11084
  const floored = {
10936
11085
  payloadFloorApplied: true,
10937
11086
  flooredFromRulesVersion: parseAgentOperatingRulesVersion(content),
10938
- flooredFromPayloadSha256: sha2565(content)
11087
+ flooredFromPayloadSha256: sha2566(content)
10939
11088
  };
10940
11089
  return {
10941
11090
  content: payload.content,
@@ -10954,7 +11103,7 @@ function skippedSource(source, reason) {
10954
11103
  order: source.resolvedOrder,
10955
11104
  path: source.path ?? null,
10956
11105
  hash: source.hash ?? null,
10957
- renderedPayloadSha256: sha2565(source.content),
11106
+ renderedPayloadSha256: sha2566(source.content),
10958
11107
  nonOverridable: source.nonOverridable === true,
10959
11108
  provenance: source.provenance ?? null
10960
11109
  }
@@ -10996,7 +11145,7 @@ function compareSessionInstructionSources(a, b) {
10996
11145
  return SESSION_LAYER_RANK[a.resolvedLayer] - SESSION_LAYER_RANK[b.resolvedLayer] || a.resolvedOrder - b.resolvedOrder || a.id.localeCompare(b.id);
10997
11146
  }
10998
11147
  function semanticPolicyIntegrity(body) {
10999
- return sha2565(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
11148
+ return sha2566(body) === AGENT_OPERATING_RULES_PAYLOAD_SHA256 ? "pinned-digest" : "unverified-self-declared";
11000
11149
  }
11001
11150
  function semanticPolicyDeclaration(source) {
11002
11151
  const normalize = (value) => value.replace(/\r\n/g, `
@@ -11221,7 +11370,7 @@ function composeSources(sources, tool) {
11221
11370
  targetSourceId: target.id,
11222
11371
  targetNormalizedSourceId: target.normalizedId,
11223
11372
  targetHash: target.hash ?? null,
11224
- targetRenderedPayloadSha256: sha2565(target.content),
11373
+ targetRenderedPayloadSha256: sha2566(target.content),
11225
11374
  targetNonOverridable: protectedReplacement,
11226
11375
  authority: protectedReplacement ? "canonical-identity-export/codewith-provider/v1" : "overridable-source/v1"
11227
11376
  }
@@ -11312,16 +11461,50 @@ function indexHeader(tool, profile) {
11312
11461
  ].join(`
11313
11462
  `);
11314
11463
  }
11315
- function buildNativeImportFiles(targetHome, adapter, profile, sources) {
11464
+ function isCompiledClaudeGlobSource(adapter, source, providerVersion) {
11465
+ if (adapter.tool !== "claude")
11466
+ return false;
11467
+ if (providerVersion && !providerVersionSatisfies(providerVersion, CLAUDE_PATHS_RULES_VERSION_RANGE))
11468
+ return false;
11469
+ const compiledBinding = source.provenance?.["profileBinding"];
11470
+ if (!compiledBinding || typeof compiledBinding !== "object")
11471
+ return false;
11472
+ const activation = source.metadata?.["activation"];
11473
+ return activation?.mode === "glob";
11474
+ }
11475
+ function claudeGlobRuleFile(targetHome, index, source) {
11476
+ const globs = source.globs ?? [];
11477
+ if (globs.length === 0)
11478
+ throw new Error(`Claude glob source ${source.id} has no paths.`);
11479
+ const n = String(index + 1).padStart(2, "0");
11480
+ const relativePath = posix2.join("rules", `${n}-${source.normalizedId}.md`);
11481
+ const content = [
11482
+ "---",
11483
+ "paths:",
11484
+ ...globs.map((glob) => ` - ${yamlQuote2(glob)}`),
11485
+ "---",
11486
+ "",
11487
+ sectionForSource(source),
11488
+ ...source.resolvedRules.flatMap((rule) => ["", sectionForRule(source, rule)])
11489
+ ].join(`
11490
+ `);
11491
+ return makeFile(targetHome, relativePath, "rule", content, [source.id, ...source.resolvedRules.map((rule) => rule.id)]);
11492
+ }
11493
+ function buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion) {
11316
11494
  const indexFile = adapter.indexFile;
11317
- const fragments = sources.flatMap((source, index2) => [
11318
- makeFile(targetHome, fragmentPath(adapter, index2, source), "fragment", sectionForSource(source), [source.id]),
11319
- ...source.resolvedRules.map((rule) => makeFile(targetHome, ruleFragmentPath(adapter, source, rule), "rule", sectionForRule(source, rule), [source.id, rule.id]))
11320
- ]);
11495
+ const fragments = sources.flatMap((source, index2) => {
11496
+ if (isCompiledClaudeGlobSource(adapter, source, providerVersion))
11497
+ return [];
11498
+ return [
11499
+ makeFile(targetHome, fragmentPath(adapter, index2, source), "fragment", sectionForSource(source), [source.id]),
11500
+ ...source.resolvedRules.map((rule) => makeFile(targetHome, ruleFragmentPath(adapter, source, rule), "rule", sectionForRule(source, rule), [source.id, rule.id]))
11501
+ ];
11502
+ });
11503
+ const conditionalRules = sources.flatMap((source, index2) => isCompiledClaudeGlobSource(adapter, source, providerVersion) ? [claudeGlobRuleFile(targetHome, index2, source)] : []);
11321
11504
  const imports = fragments.map((file) => `@${importPath(indexFile, file.relativePath)}`);
11322
11505
  const index = makeFile(targetHome, indexFile, "index", [indexHeader(adapter.tool, profile), ...imports].join(`
11323
11506
  `), sources.map((source) => source.id));
11324
- return [index, ...fragments];
11507
+ return [index, ...fragments, ...conditionalRules];
11325
11508
  }
11326
11509
  function buildFlattenedMarkdownFiles(targetHome, adapter, profile, sources) {
11327
11510
  const content = [
@@ -11403,7 +11586,7 @@ function buildOpenCodeFiles(targetHome, adapter, profile, sources, providerConfi
11403
11586
  ...sources.flatMap((source) => source.resolvedRules.map((rule) => rule.id))
11404
11587
  ]);
11405
11588
  const existingConfigPath = joinTarget(targetHome, adapter.configFile);
11406
- const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync3(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11589
+ const selectedConfig = existsSync5(existingConfigPath) ? readOpenCodeConfig(readFileSync4(existingConfigPath, "utf8"), existingConfigPath) : providerConfig ? readOpenCodeConfig(providerConfig.content, providerConfig.sourceId) : {};
11407
11590
  const preservedInstructions = normalizeOpenCodeInstructions(selectedConfig["instructions"]).filter((path) => !pathIsManagedOpenCodeInstruction(path, adapter.managedDir));
11408
11591
  const config = {
11409
11592
  ...selectedConfig,
@@ -11509,10 +11692,10 @@ function makeAntigravityRuleFile(targetHome, relativePath, content, sourceIds) {
11509
11692
  }
11510
11693
  return file;
11511
11694
  }
11512
- function buildFiles(targetHome, adapter, profile, sources, providerConfig) {
11695
+ function buildFiles(targetHome, adapter, profile, sources, providerConfig, providerVersion) {
11513
11696
  switch (adapter.mode) {
11514
11697
  case "native-imports":
11515
- return buildNativeImportFiles(targetHome, adapter, profile, sources);
11698
+ return buildNativeImportFiles(targetHome, adapter, profile, sources, providerVersion);
11516
11699
  case "flattened-markdown":
11517
11700
  return buildFlattenedMarkdownFiles(targetHome, adapter, profile, sources);
11518
11701
  case "cursor-mdc":
@@ -11552,7 +11735,7 @@ function buildAssetFiles(input, targetHome, blocked) {
11552
11735
  relativePath: assertSafeRelativePath(relativePath),
11553
11736
  role: "asset",
11554
11737
  content,
11555
- sha256: sha2565(content),
11738
+ sha256: sha2566(content),
11556
11739
  sourceIds: [item.sourceConfigId, item.assetId]
11557
11740
  };
11558
11741
  });
@@ -11592,7 +11775,7 @@ function adapterFor(input) {
11592
11775
  return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
11593
11776
  }
11594
11777
  function getHomeDir() {
11595
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir4();
11778
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
11596
11779
  }
11597
11780
  function cleanSessionPathInput(path) {
11598
11781
  const trimmed = path.trim();
@@ -11607,16 +11790,16 @@ function resolveSessionPath(path) {
11607
11790
  throw new Error("Session render path cannot be empty.");
11608
11791
  const home = getHomeDir();
11609
11792
  if (cleaned === "~")
11610
- return resolve4(home);
11793
+ return resolve6(home);
11611
11794
  if (cleaned.startsWith("~/"))
11612
- return resolve4(home, cleaned.slice(2));
11795
+ return resolve6(home, cleaned.slice(2));
11613
11796
  if (cleaned === "{{HOME}}" || cleaned === "${HOME}")
11614
- return resolve4(home);
11797
+ return resolve6(home);
11615
11798
  if (cleaned.startsWith("{{HOME}}/"))
11616
- return resolve4(home, cleaned.slice("{{HOME}}/".length));
11799
+ return resolve6(home, cleaned.slice("{{HOME}}/".length));
11617
11800
  if (cleaned.startsWith("${HOME}/"))
11618
- return resolve4(home, cleaned.slice("${HOME}/".length));
11619
- return resolve4(cleaned);
11801
+ return resolve6(home, cleaned.slice("${HOME}/".length));
11802
+ return resolve6(cleaned);
11620
11803
  }
11621
11804
  function assertSafeRelativePath(relativePath) {
11622
11805
  if (!relativePath.trim())
@@ -11632,7 +11815,7 @@ function assertSafeRelativePath(relativePath) {
11632
11815
  function assertSafeTargetRoot(targetHome) {
11633
11816
  if (!isAbsolute3(targetHome))
11634
11817
  throw new Error(`Session render target must be an absolute path: ${targetHome}`);
11635
- const normalized = resolve4(targetHome);
11818
+ const normalized = resolve6(targetHome);
11636
11819
  if (normalized === parse2(normalized).root) {
11637
11820
  throw new Error(`Session render target cannot be the filesystem root: ${targetHome}`);
11638
11821
  }
@@ -11730,7 +11913,7 @@ function planSessionRender(input) {
11730
11913
  blockers: targetBlockers
11731
11914
  } = resolveRenderTarget(input);
11732
11915
  const authorityObservations = input.tool === "cursor" && targetKind !== "blocked" ? [observeCursorGlobalAuthority({ home: input.cursorAuthorityHome })] : [];
11733
- const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : [];
11916
+ const authorityConflicts = input.tool === "cursor" && targetKind !== "blocked" ? detectCursorAuthorityConflicts(authorityObservations[0]) : input.tool === "claude" && targetKind !== "blocked" ? detectClaudeAuthorityConflicts(targetHome) : [];
11734
11917
  const blockers = [
11735
11918
  ...targetBlockers,
11736
11919
  ...authorityConflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`)
@@ -11759,7 +11942,7 @@ function planSessionRender(input) {
11759
11942
  if (input.providerConfig && input.tool !== "opencode") {
11760
11943
  throw new Error("Provider base config is supported only for OpenCode session renders.");
11761
11944
  }
11762
- const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig);
11945
+ const baseFiles = blocked ? [] : buildFiles(targetHome, adapter, input.profile, orderedSources, input.providerConfig, input.provider_version);
11763
11946
  const projectContext = blocked ? null : composeProjectContextSessionRender({
11764
11947
  tool: input.tool,
11765
11948
  adapter_mode: adapter.mode,
@@ -11828,7 +12011,7 @@ function planSessionRender(input) {
11828
12011
  hash: rule.hash ?? null,
11829
12012
  ...ruleAttestation(rule)
11830
12013
  })),
11831
- renderedPayloadSha256: sha2565(source.content),
12014
+ renderedPayloadSha256: sha2566(source.content),
11832
12015
  provenance: source.provenance ?? null,
11833
12016
  metadata: source.metadata ?? null
11834
12017
  })),
@@ -11866,8 +12049,8 @@ function planSessionRender(input) {
11866
12049
  ...input.providerConfig ? {
11867
12050
  providerConfig: {
11868
12051
  sourceId: input.providerConfig.sourceId,
11869
- selectedPayloadSha256: sha2565(input.providerConfig.content),
11870
- renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2565(input.providerConfig.content),
12052
+ selectedPayloadSha256: sha2566(input.providerConfig.content),
12053
+ renderedPayloadSha256: files.find((file) => file.relativePath === adapter.configFile)?.sha256 ?? sha2566(input.providerConfig.content),
11871
12054
  selected: !existsSync5(joinTarget(targetHome, adapter.configFile))
11872
12055
  }
11873
12056
  } : {},
@@ -12153,7 +12336,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12153
12336
  }
12154
12337
  return;
12155
12338
  }
12156
- const stat = statSync2(resolvedPath);
12339
+ const stat = statSync3(resolvedPath);
12157
12340
  if (!stat.isFile()) {
12158
12341
  throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
12159
12342
  }
@@ -12162,7 +12345,7 @@ function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
12162
12345
  if (!pathIsInside(realPath, realBase)) {
12163
12346
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
12164
12347
  }
12165
- return readFileSync3(realPath, "utf-8");
12348
+ return readFileSync4(realPath, "utf-8");
12166
12349
  }
12167
12350
  function resolveIdentitySourcePath(path, baseDir, sourceId) {
12168
12351
  const cleaned = cleanSessionPathInput(path);
@@ -12170,8 +12353,8 @@ function resolveIdentitySourcePath(path, baseDir, sourceId) {
12170
12353
  throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
12171
12354
  if (cleaned.includes("\\"))
12172
12355
  throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
12173
- const resolvedPath = isAbsolute3(cleaned) ? resolve4(cleaned) : resolve4(baseDir, cleaned);
12174
- if (!pathIsInside(resolvedPath, resolve4(baseDir))) {
12356
+ const resolvedPath = isAbsolute3(cleaned) ? resolve6(cleaned) : resolve6(baseDir, cleaned);
12357
+ if (!pathIsInside(resolvedPath, resolve6(baseDir))) {
12175
12358
  throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
12176
12359
  }
12177
12360
  return resolvedPath;
@@ -12259,7 +12442,7 @@ function asStringArray(value) {
12259
12442
  return [];
12260
12443
  return value.filter((item) => typeof item === "string");
12261
12444
  }
12262
- var RAW_STORE_ROOT_ENV = "HASNA_CONFIGS_HOME", ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, IDENTITY_EXPORT_AUTHORITY, CODEWITH_PROTECTED_REPLACEMENT_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CANONICAL_IDENTITY_EXPORT_PACKAGES;
12445
+ var ANTIGRAVITY_RULE_FILE_CHAR_LIMIT = 12000, SESSION_RENDERER_OWNER_ID = "instructions-session-renderer", SESSION_RENDER_TOOLS, SESSION_RENDER_PROFILE_ENTRYPOINTS, SESSION_RENDER_OWNED_CONFIG_TARGETS, IDENTITY_EXPORT_AUTHORITY, CODEWITH_PROTECTED_REPLACEMENT_TARGETS, CODEWITH_FLATTENED_ADAPTER, CODEWITH_NATIVE_ADAPTER, SESSION_TOOL_ADAPTERS, SESSION_RENDER_MANAGED_DIRS, SESSION_RENDER_SHARED_MANAGED_DIRS, SESSION_RENDER_EXCLUSIVE_MANAGED_PATHS, SESSION_LAYER_RANK, CLAUDE_PATHS_RULES_VERSION_RANGE = ">=2.1.84", CANONICAL_IDENTITY_EXPORT_PACKAGES;
12263
12446
  var init_session_render = __esm(() => {
12264
12447
  init_global_agent_rules_standard();
12265
12448
  init_codewith_shared_todos_storage_standard();
@@ -12268,8 +12451,11 @@ var init_session_render = __esm(() => {
12268
12451
  init_transforms();
12269
12452
  init_asset_plan();
12270
12453
  init_cursor_authority();
12454
+ init_session_authority();
12271
12455
  init_session_render_contract();
12272
12456
  init_session_render_contract();
12457
+ init_raw_store_root();
12458
+ init_raw_store_root();
12273
12459
  SESSION_RENDER_TOOLS = [
12274
12460
  "claude",
12275
12461
  "codex",
@@ -12470,7 +12656,7 @@ var init_session_render = __esm(() => {
12470
12656
  });
12471
12657
 
12472
12658
  // src/lib/instruction-graph.ts
12473
- import { createHash as createHash6 } from "crypto";
12659
+ import { createHash as createHash7 } from "crypto";
12474
12660
  function capability(provider, providerVersionRange, selectedRepresentation, loadingPath, options = {}) {
12475
12661
  return Object.freeze({
12476
12662
  schema: PROVIDER_CAPABILITY_SCHEMA,
@@ -12717,7 +12903,7 @@ function compileInstructionGraph(input) {
12717
12903
  effective_activation: effective.get(configId),
12718
12904
  fallback: row.binding.fallback,
12719
12905
  required: row.binding.required,
12720
- content_sha256: sha2566(config.content),
12906
+ content_sha256: sha2567(config.content),
12721
12907
  dependencies: dependencies.get(configId) ?? []
12722
12908
  };
12723
12909
  });
@@ -12754,7 +12940,7 @@ function compileInstructionGraph(input) {
12754
12940
  diagnostics
12755
12941
  };
12756
12942
  return {
12757
- plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2566(stableJson2(planWithoutHash)) }),
12943
+ plan: deepFreeze2({ ...planWithoutHash, source_hash: sha2567(stableJson2(planWithoutHash)) }),
12758
12944
  sources,
12759
12945
  capability: capability2
12760
12946
  };
@@ -12779,7 +12965,6 @@ function planProfileSessionRender(input) {
12779
12965
  });
12780
12966
  const {
12781
12967
  profile_id: _profileId,
12782
- provider_version: _providerVersion,
12783
12968
  configs: _configs,
12784
12969
  bindings: _bindings,
12785
12970
  asset_configs: _assetConfigs,
@@ -12928,8 +13113,8 @@ function assertExactOnce(units, artifacts) {
12928
13113
  function errorDiagnostic(configId, code, message) {
12929
13114
  return { severity: "error", code, config_id: configId, message };
12930
13115
  }
12931
- function sha2566(value) {
12932
- return createHash6("sha256").update(value).digest("hex");
13116
+ function sha2567(value) {
13117
+ return createHash7("sha256").update(value).digest("hex");
12933
13118
  }
12934
13119
  function stableJson2(value) {
12935
13120
  const canonical = (entry) => Array.isArray(entry) ? entry.map(canonical) : entry && typeof entry === "object" ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, canonical(child)])) : entry;
@@ -12949,7 +13134,12 @@ var init_instruction_graph = __esm(() => {
12949
13134
  init_asset_plan();
12950
13135
  COMMON_FALLBACKS = ["fail", "flatten", "promote-always", "omit"];
12951
13136
  DEFAULT_PROVIDER_CAPABILITIES = Object.freeze({
12952
- claude: capability("claude", ">=1.0.0", "native-import", "CLAUDE.md @ imports", { native_imports: true, asset_surface: "code" }),
13137
+ claude: capability("claude", ">=1.0.0", "native-import", "CLAUDE.md @ imports plus path-gated rules", {
13138
+ activation_modes: ["always", "glob"],
13139
+ native_imports: true,
13140
+ conditional_artifacts: true,
13141
+ asset_surface: "code"
13142
+ }),
12953
13143
  codex: capability("codex", ">=0.1.0", "flattened", "AGENTS.md", { asset_surface: "cli" }),
12954
13144
  cursor: capability("cursor", ">=1.0.0", "cursor-rule", ".cursor/rules/*.mdc", { activation_modes: ["always", "glob"], conditional_artifacts: true, asset_surface: "ide" }),
12955
13145
  opencode: capability("opencode", ">=1.0.0", "managed-fragment", "opencode.json instructions", { provider_variant: "v1-instructions", session_surface: "opencode-config-instructions" }),
@@ -13228,37 +13418,6 @@ var init_profiles = __esm(() => {
13228
13418
  init_asset_plan();
13229
13419
  });
13230
13420
 
13231
- // src/db/snapshots.ts
13232
- function createSnapshot(configId, content, version, db) {
13233
- const d = db || getDatabase();
13234
- const id = uuid();
13235
- const ts = now2();
13236
- d.run("INSERT INTO config_snapshots (id, config_id, content, version, created_at) VALUES (?, ?, ?, ?, ?)", [id, configId, content, version, ts]);
13237
- return { id, config_id: configId, content, version, created_at: ts };
13238
- }
13239
- function listSnapshots(configId, db) {
13240
- const d = db || getDatabase();
13241
- return d.query("SELECT * FROM config_snapshots WHERE config_id = ? ORDER BY version DESC").all(configId);
13242
- }
13243
- function getSnapshot(id, db) {
13244
- const d = db || getDatabase();
13245
- return d.query("SELECT * FROM config_snapshots WHERE id = ?").get(id);
13246
- }
13247
- function getSnapshotByVersion(configId, version, db) {
13248
- const d = db || getDatabase();
13249
- return d.query("SELECT * FROM config_snapshots WHERE config_id = ? AND version = ?").get(configId, version);
13250
- }
13251
- function pruneSnapshots(configId, keep = 10, db) {
13252
- const d = db || getDatabase();
13253
- const result = d.run(`DELETE FROM config_snapshots WHERE config_id = ? AND id NOT IN (
13254
- SELECT id FROM config_snapshots WHERE config_id = ? ORDER BY version DESC LIMIT ?
13255
- )`, [configId, configId, keep]);
13256
- return result.changes;
13257
- }
13258
- var init_snapshots = __esm(() => {
13259
- init_database();
13260
- });
13261
-
13262
13421
  // src/db/machines.ts
13263
13422
  import { arch, hostname, type } from "os";
13264
13423
  function currentHostname2() {
@@ -13350,16 +13509,17 @@ function formatCliError(err, env = process.env) {
13350
13509
  return err instanceof Error ? err.message : String(err);
13351
13510
  }
13352
13511
  function resolveCloudConfig(env = process.env) {
13512
+ assertNoLegacyStorageMode(env);
13353
13513
  const apiUrl = env[API_URL_ENV]?.trim();
13354
13514
  const apiKey = env[API_KEY_ENV]?.trim();
13355
13515
  if (!apiUrl && !apiKey)
13356
13516
  return null;
13357
13517
  if (!apiUrl || !apiKey) {
13358
- throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the cloud API, ` + `or unset both to use the local store.`);
13518
+ throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the HTTP API, ` + `or unset both to use the local store.`);
13359
13519
  }
13360
13520
  return { apiUrl, apiKey };
13361
13521
  }
13362
- function isCloudMode(env = process.env) {
13522
+ function isApiTransport(env = process.env) {
13363
13523
  return resolveCloudConfig(env) !== null;
13364
13524
  }
13365
13525
 
@@ -13845,6 +14005,7 @@ var init_config_store = __esm(() => {
13845
14005
  init_types();
13846
14006
  init_bounded_read();
13847
14007
  init_instruction_graph();
14008
+ init_retired_storage_mode();
13848
14009
  CloudHttpError = class CloudHttpError extends Error {
13849
14010
  status;
13850
14011
  body;
@@ -13858,8 +14019,8 @@ var init_config_store = __esm(() => {
13858
14019
  });
13859
14020
 
13860
14021
  // src/lib/session-render-ownership.ts
13861
- import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
13862
- import { dirname as dirname3, join as join7, parse as parse3, relative as relative3, sep } from "path";
14022
+ import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
14023
+ import { dirname as dirname3, join as join9, parse as parse3, relative as relative3, sep } from "path";
13863
14024
  function toSegments(absolutePath2) {
13864
14025
  return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
13865
14026
  }
@@ -13880,7 +14041,7 @@ function readManifestRelativePaths(manifestPath) {
13880
14041
  try {
13881
14042
  if (!existsSync6(manifestPath))
13882
14043
  return null;
13883
- stats = statSync3(manifestPath);
14044
+ stats = statSync4(manifestPath);
13884
14045
  } catch {
13885
14046
  return null;
13886
14047
  }
@@ -13890,7 +14051,7 @@ function readManifestRelativePaths(manifestPath) {
13890
14051
  }
13891
14052
  let manifest;
13892
14053
  try {
13893
- manifest = JSON.parse(readFileSync4(manifestPath, "utf-8"));
14054
+ manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
13894
14055
  } catch {
13895
14056
  return null;
13896
14057
  }
@@ -13907,7 +14068,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
13907
14068
  const root = parse3(absolutePath2).root;
13908
14069
  let home = dirname3(absolutePath2);
13909
14070
  for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
13910
- const manifestPath = join7(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
14071
+ const manifestPath = join9(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
13911
14072
  const relativePaths = readManifestRelativePaths(manifestPath);
13912
14073
  if (relativePaths) {
13913
14074
  const claimed = relative3(home, absolutePath2).split(sep).join("/");
@@ -13942,17 +14103,17 @@ __export(exports_apply, {
13942
14103
  applyConfigs: () => applyConfigs,
13943
14104
  applyConfig: () => applyConfig
13944
14105
  });
13945
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
13946
- import { basename as basename4, dirname as dirname4, join as join8, resolve as resolve5 } from "path";
13947
- import { homedir as homedir5 } from "os";
14106
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2, writeFileSync as writeFileSync2 } from "fs";
14107
+ import { basename as basename4, dirname as dirname4, join as join10, resolve as resolve7 } from "path";
14108
+ import { homedir as homedir6 } from "os";
13948
14109
  function getConfigHome() {
13949
- return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir5();
14110
+ return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
13950
14111
  }
13951
14112
  function expandPath(p) {
13952
14113
  if (p.startsWith("~/")) {
13953
- return resolve5(getConfigHome(), p.slice(2));
14114
+ return resolve7(getConfigHome(), p.slice(2));
13954
14115
  }
13955
- return resolve5(p);
14116
+ return resolve7(p);
13956
14117
  }
13957
14118
  function normalizeTargetPath(p) {
13958
14119
  const expanded = expandPath(p);
@@ -13964,7 +14125,7 @@ function normalizeTargetPath(p) {
13964
14125
  while (true) {
13965
14126
  if (existsSync7(current)) {
13966
14127
  try {
13967
- return resolve5(realpathSync2(current), ...missingSegments);
14128
+ return resolve7(realpathSync2(current), ...missingSegments);
13968
14129
  } catch {
13969
14130
  return expanded;
13970
14131
  }
@@ -13993,7 +14154,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
13993
14154
  }
13994
14155
  const path = expandPath(renderedTargetPath);
13995
14156
  const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
13996
- const previousContent = existsSync7(path) ? readFileSync5(path, "utf-8") : null;
14157
+ const previousContent = existsSync7(path) ? readFileSync6(path, "utf-8") : null;
13997
14158
  const changed = previousContent !== renderedForTarget;
13998
14159
  if (!opts.dryRun) {
13999
14160
  const dir = dirname4(path);
@@ -14033,7 +14194,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
14033
14194
  const path = expandPath(targetPath);
14034
14195
  if (!existsSync7(path))
14035
14196
  return [];
14036
- current = readFileSync5(path, "utf-8");
14197
+ current = readFileSync6(path, "utf-8");
14037
14198
  } catch {
14038
14199
  return secretTokens;
14039
14200
  }
@@ -14357,7 +14518,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
14357
14518
  getConfigHome(),
14358
14519
  opts.vars?.["HOME_DIR"]
14359
14520
  ].filter((home) => typeof home === "string" && home.length > 0));
14360
- if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join8(home, ...relativePath.split("/"))))))
14521
+ if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join10(home, ...relativePath.split("/"))))))
14361
14522
  return true;
14362
14523
  return sessionRenderOwnsPath(normalized);
14363
14524
  }
@@ -14375,9 +14536,9 @@ var init_apply = __esm(() => {
14375
14536
  });
14376
14537
 
14377
14538
  // src/lib/sync-dir.ts
14378
- import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
14379
- import { join as join9, relative as relative4 } from "path";
14380
- import { homedir as homedir6 } from "os";
14539
+ import { existsSync as existsSync8, readdirSync, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
14540
+ import { join as join11, relative as relative4 } from "path";
14541
+ import { homedir as homedir7 } from "os";
14381
14542
  function shouldSkip(p) {
14382
14543
  return SKIP.some((s) => p.includes(s));
14383
14544
  }
@@ -14386,9 +14547,9 @@ async function syncFromDir(dir, opts = {}) {
14386
14547
  const absDir = expandPath(dir);
14387
14548
  if (!existsSync8(absDir))
14388
14549
  return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
14389
- const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join9(absDir, f)).filter((f) => statSync4(f).isFile());
14550
+ const files = opts.recursive !== false ? walkDir(absDir) : readdirSync(absDir).map((f) => join11(absDir, f)).filter((f) => statSync5(f).isFile());
14390
14551
  const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
14391
- const home = homedir6();
14552
+ const home = homedir7();
14392
14553
  const allConfigs = await store.listConfigs();
14393
14554
  for (const file of files) {
14394
14555
  if (shouldSkip(file)) {
@@ -14396,7 +14557,7 @@ async function syncFromDir(dir, opts = {}) {
14396
14557
  continue;
14397
14558
  }
14398
14559
  try {
14399
- const content = readFileSync6(file, "utf-8");
14560
+ const content = readFileSync7(file, "utf-8");
14400
14561
  if (content.length > 500000) {
14401
14562
  result.skipped.push(file + " (too large)");
14402
14563
  continue;
@@ -14422,7 +14583,7 @@ async function syncFromDir(dir, opts = {}) {
14422
14583
  }
14423
14584
  async function syncToDir(dir, opts = {}) {
14424
14585
  const store = opts.store ?? resolveConfigStore();
14425
- const home = homedir6();
14586
+ const home = homedir7();
14426
14587
  const absDir = expandPath(dir);
14427
14588
  const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
14428
14589
  const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
@@ -14446,7 +14607,7 @@ async function syncToDir(dir, opts = {}) {
14446
14607
  }
14447
14608
  function walkDir(dir, files = []) {
14448
14609
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
14449
- const full = join9(dir, entry.name);
14610
+ const full = join11(dir, entry.name);
14450
14611
  if (shouldSkip(full))
14451
14612
  continue;
14452
14613
  if (entry.isDirectory())
@@ -14480,8 +14641,8 @@ __export(exports_sync, {
14480
14641
  KNOWN_CONFIGS: () => KNOWN_CONFIGS,
14481
14642
  CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
14482
14643
  });
14483
- import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
14484
- import { basename as basename5, extname as extname3, join as join10 } from "path";
14644
+ import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
14645
+ import { basename as basename5, extname as extname3, join as join12 } from "path";
14485
14646
  function claudeRuleOutputs(fileName) {
14486
14647
  const stem = basename5(fileName, extname3(fileName));
14487
14648
  return [
@@ -14545,11 +14706,11 @@ async function syncProject(opts) {
14545
14706
  const allConfigs = await store.listConfigs();
14546
14707
  const machine = detectMachineContext();
14547
14708
  for (const pf of PROJECT_CONFIG_FILES) {
14548
- const abs = join10(absDir, pf.file);
14709
+ const abs = join12(absDir, pf.file);
14549
14710
  if (!existsSync9(abs))
14550
14711
  continue;
14551
14712
  try {
14552
- const rawContent = readFileSync7(abs, "utf-8");
14713
+ const rawContent = readFileSync8(abs, "utf-8");
14553
14714
  if (rawContent.length > 500000) {
14554
14715
  result.skipped.push(pf.file);
14555
14716
  continue;
@@ -14578,20 +14739,20 @@ async function syncProject(opts) {
14578
14739
  }
14579
14740
  }
14580
14741
  for (const ruleDir of [
14581
- { dir: join10(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
14582
- { dir: join10(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
14583
- { dir: join10(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
14584
- { dir: join10(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
14585
- { dir: join10(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
14586
- { dir: join10(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14587
- { dir: join10(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14742
+ { dir: join12(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
14743
+ { dir: join12(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
14744
+ { dir: join12(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
14745
+ { dir: join12(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
14746
+ { dir: join12(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
14747
+ { dir: join12(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
14748
+ { dir: join12(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
14588
14749
  ]) {
14589
14750
  if (!existsSync9(ruleDir.dir))
14590
14751
  continue;
14591
14752
  const mdFiles = readdirSync2(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
14592
14753
  for (const f of mdFiles) {
14593
- const abs = join10(ruleDir.dir, f);
14594
- const raw = readFileSync7(abs, "utf-8");
14754
+ const abs = join12(ruleDir.dir, f);
14755
+ const raw = readFileSync8(abs, "utf-8");
14595
14756
  const redacted = redactContent(raw, "markdown");
14596
14757
  const machineAware = templateizeMachineContent(redacted.content, machine);
14597
14758
  const content = machineAware.content;
@@ -14637,13 +14798,13 @@ async function syncKnown(opts = {}) {
14637
14798
  const extensions = known.rulesExtensions ?? [".md", ".mdc"];
14638
14799
  const ruleFiles = readdirSync2(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
14639
14800
  for (const f of ruleFiles) {
14640
- const abs2 = join10(absDir, f);
14801
+ const abs2 = join12(absDir, f);
14641
14802
  const targetPath = abs2.replace(home, "~");
14642
14803
  if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
14643
14804
  result.skipped.push(`${targetPath} (generated output)`);
14644
14805
  continue;
14645
14806
  }
14646
- const raw = readFileSync7(abs2, "utf-8");
14807
+ const raw = readFileSync8(abs2, "utf-8");
14647
14808
  const redacted = redactContent(raw, "markdown");
14648
14809
  const machineAware = templateizeMachineContent(redacted.content, machine);
14649
14810
  const content = machineAware.content;
@@ -14676,7 +14837,7 @@ async function syncKnown(opts = {}) {
14676
14837
  continue;
14677
14838
  }
14678
14839
  try {
14679
- const rawContent = normalizeKnownConfigSource(known, readFileSync7(abs, "utf-8"));
14840
+ const rawContent = normalizeKnownConfigSource(known, readFileSync8(abs, "utf-8"));
14680
14841
  if (rawContent.length > 500000) {
14681
14842
  result.skipped.push(known.path + " (too large)");
14682
14843
  continue;
@@ -14779,7 +14940,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
14779
14940
  const path = expandPath(targetPath);
14780
14941
  if (!existsSync9(path))
14781
14942
  return `(file not found on disk: ${path})`;
14782
- const diskContent = readFileSync7(path, "utf-8");
14943
+ const diskContent = readFileSync8(path, "utf-8");
14783
14944
  if (diskContent === expectedContent)
14784
14945
  return "(no diff \u2014 identical)";
14785
14946
  const format = redactFormatForTarget(targetPath, storedFormat);
@@ -15012,18 +15173,18 @@ __export(exports_package_manager_guard, {
15012
15173
  scanPackageManagerSecrets: () => scanPackageManagerSecrets
15013
15174
  });
15014
15175
  import { execFileSync as execFileSync2 } from "child_process";
15015
- import { existsSync as existsSync15, lstatSync as lstatSync4, readdirSync as readdirSync4, readFileSync as readFileSync12 } from "fs";
15016
- import { homedir as homedir7 } from "os";
15017
- import { basename as basename6, dirname as dirname7, isAbsolute as isAbsolute5, join as join15, relative as relative6, resolve as resolve9 } from "path";
15176
+ import { existsSync as existsSync16, lstatSync as lstatSync6, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "fs";
15177
+ import { homedir as homedir9 } from "os";
15178
+ import { basename as basename6, dirname as dirname8, isAbsolute as isAbsolute5, join as join18, relative as relative7, resolve as resolve12 } from "path";
15018
15179
  function scanPackageManagerSecrets(options = {}) {
15019
- const cwd = options.cwd ? resolve9(options.cwd) : process.cwd();
15020
- const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve9(cwd, root));
15180
+ const cwd = options.cwd ? resolve12(options.cwd) : process.cwd();
15181
+ const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve12(cwd, root));
15021
15182
  const findings = [];
15022
15183
  let scannedFiles = 0;
15023
15184
  for (const root of roots) {
15024
- if (!existsSync15(root))
15185
+ if (!existsSync16(root))
15025
15186
  continue;
15026
- const stat = lstatSync4(root);
15187
+ const stat = lstatSync6(root);
15027
15188
  if (stat.isFile()) {
15028
15189
  if (!shouldScanRepoFile(root))
15029
15190
  continue;
@@ -15031,14 +15192,14 @@ function scanPackageManagerSecrets(options = {}) {
15031
15192
  if (text === null)
15032
15193
  continue;
15033
15194
  scannedFiles++;
15034
- findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname7(root)));
15195
+ findings.push(...scanFile(root, text, classifyRepoFile(root), isTrackedFile(root), dirname8(root)));
15035
15196
  continue;
15036
15197
  }
15037
15198
  if (!stat.isDirectory())
15038
15199
  continue;
15039
15200
  const tracked = trackedFiles(root);
15040
15201
  for (const file of collectRepoFiles(root)) {
15041
- const rel = toPosix(relative6(root, file));
15202
+ const rel = toPosix(relative7(root, file));
15042
15203
  const isTracked = tracked.has(rel);
15043
15204
  const text = readTextFile(file);
15044
15205
  if (text === null)
@@ -15048,10 +15209,10 @@ function scanPackageManagerSecrets(options = {}) {
15048
15209
  }
15049
15210
  }
15050
15211
  if (options.includeHome) {
15051
- const home = homedir7();
15212
+ const home = homedir9();
15052
15213
  for (const name of HOME_FILES) {
15053
- const file = join15(home, name);
15054
- if (!existsSync15(file))
15214
+ const file = join18(home, name);
15215
+ if (!existsSync16(file))
15055
15216
  continue;
15056
15217
  const text = readTextFile(file);
15057
15218
  if (text === null)
@@ -15075,12 +15236,12 @@ function collectRepoFiles(root) {
15075
15236
  if (entry.isDirectory()) {
15076
15237
  if (SKIP_DIRS.has(entry.name))
15077
15238
  continue;
15078
- visit(join15(dir, entry.name));
15239
+ visit(join18(dir, entry.name));
15079
15240
  continue;
15080
15241
  }
15081
15242
  if (!entry.isFile())
15082
15243
  continue;
15083
- const file = join15(dir, entry.name);
15244
+ const file = join18(dir, entry.name);
15084
15245
  if (shouldScanRepoFile(file))
15085
15246
  out.push(file);
15086
15247
  }
@@ -15115,10 +15276,10 @@ function isNpmrcName(name) {
15115
15276
  }
15116
15277
  function readTextFile(file) {
15117
15278
  try {
15118
- const stat = lstatSync4(file);
15279
+ const stat = lstatSync6(file);
15119
15280
  if (!stat.isFile() || stat.size > 5000000)
15120
15281
  return null;
15121
- const buf = readFileSync12(file);
15282
+ const buf = readFileSync14(file);
15122
15283
  if (buf.includes(0))
15123
15284
  return null;
15124
15285
  return buf.toString("utf-8");
@@ -15318,11 +15479,11 @@ function trackedFiles(root) {
15318
15479
  }
15319
15480
  function isTrackedFile(file) {
15320
15481
  try {
15321
- const repoRoot = execFileSync2("git", ["-C", dirname7(file), "rev-parse", "--show-toplevel"], {
15482
+ const repoRoot = execFileSync2("git", ["-C", dirname8(file), "rev-parse", "--show-toplevel"], {
15322
15483
  encoding: "utf-8",
15323
15484
  stdio: ["ignore", "pipe", "ignore"]
15324
15485
  }).trim();
15325
- const rel = toPosix(relative6(repoRoot, file));
15486
+ const rel = toPosix(relative7(repoRoot, file));
15326
15487
  execFileSync2("git", ["-C", repoRoot, "ls-files", "--error-unmatch", "--", rel], {
15327
15488
  stdio: ["ignore", "ignore", "ignore"]
15328
15489
  });
@@ -15348,13 +15509,13 @@ function stripInlineComment(value) {
15348
15509
  return value.replace(/\s[#;].*$/, "").trim();
15349
15510
  }
15350
15511
  function displayPath(file, root) {
15351
- const home = homedir7();
15512
+ const home = homedir9();
15352
15513
  if (root === home && (file === home || file.startsWith(home + "/")))
15353
- return "~/" + toPosix(relative6(home, file));
15514
+ return "~/" + toPosix(relative7(home, file));
15354
15515
  if (isAbsolute5(root) && file.startsWith(root + "/"))
15355
- return toPosix(relative6(root, file));
15516
+ return toPosix(relative7(root, file));
15356
15517
  if (file === home || file.startsWith(home + "/"))
15357
- return "~/" + toPosix(relative6(home, file));
15518
+ return "~/" + toPosix(relative7(home, file));
15358
15519
  return file;
15359
15520
  }
15360
15521
  function toPosix(path) {
@@ -15392,15 +15553,16 @@ var init_package_manager_guard = __esm(() => {
15392
15553
  TOKEN_VALUE_PATTERNS = [
15393
15554
  { re: /npm_[A-Za-z0-9]{36,}/, rule: "literal-npm-token", detail: "literal npm token-like value" },
15394
15555
  { re: /gh[pousr]_[A-Za-z0-9_]{36,}/, rule: "literal-github-token", detail: "literal GitHub token-like value" },
15395
- { re: /sk-ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
15556
+ { re: /sk[-]ant-[A-Za-z0-9\-_]{40,}/, rule: "literal-anthropic-key", detail: "literal Anthropic key-like value" },
15396
15557
  { re: /sk-[A-Za-z0-9]{48,}/, rule: "literal-openai-key", detail: "literal OpenAI key-like value" },
15397
15558
  { re: /AKIA[0-9A-Z]{16}/, rule: "literal-aws-access-key", detail: "literal AWS access-key-like value" },
15398
15559
  { re: /xoxb-[0-9]+-[A-Za-z0-9-]+/, rule: "literal-slack-token", detail: "literal Slack token-like value" }
15399
15560
  ];
15400
15561
  });
15401
15562
 
15402
- // node_modules/@hasna/events/dist/commander.js
15563
+ // ../events/dist/commander.js
15403
15564
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
15565
+ import { Buffer as Buffer2 } from "buffer";
15404
15566
  import { existsSync } from "fs";
15405
15567
  import { homedir } from "os";
15406
15568
  import { join } from "path";
@@ -15416,29 +15578,83 @@ function getPathValue(input, path) {
15416
15578
  return;
15417
15579
  }, input);
15418
15580
  }
15419
- function wildcardToRegExp(pattern) {
15420
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
15421
- return new RegExp(`^${escaped}$`);
15581
+ function getFieldValues(input, path) {
15582
+ const values = [];
15583
+ const push = (value) => {
15584
+ if (!values.some((item) => Object.is(item, value)))
15585
+ values.push(value);
15586
+ };
15587
+ if (path.includes(".") && path in input)
15588
+ push(input[path]);
15589
+ const nestedValue = getPathValue(input, path);
15590
+ if (nestedValue !== undefined || !path.includes("."))
15591
+ push(nestedValue);
15592
+ return values;
15593
+ }
15594
+ function wildcardToRegExp(pattern, options = {}) {
15595
+ let body = "";
15596
+ for (let index = 0;index < pattern.length; index += 1) {
15597
+ const char = pattern[index];
15598
+ if (char === "*") {
15599
+ if (pattern[index + 1] === "*") {
15600
+ body += ".*";
15601
+ index += 1;
15602
+ } else {
15603
+ body += options.segmentSafe ? "[^/]*" : ".*";
15604
+ }
15605
+ } else {
15606
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
15607
+ }
15608
+ }
15609
+ return new RegExp(`^${body}$`);
15422
15610
  }
15423
- function matchString(value, matcher) {
15611
+ function matchString(value, matcher, options = {}) {
15424
15612
  if (matcher === undefined)
15425
15613
  return true;
15426
15614
  if (value === undefined)
15427
15615
  return false;
15428
15616
  const matchers = Array.isArray(matcher) ? matcher : [matcher];
15429
- return matchers.some((item) => wildcardToRegExp(item).test(value));
15617
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
15430
15618
  }
15431
15619
  function matchRecord(input, matcher) {
15432
15620
  if (!matcher)
15433
15621
  return true;
15434
15622
  return Object.entries(matcher).every(([path, expected]) => {
15435
- const actual = getPathValue(input, path);
15436
- if (typeof expected === "string" || Array.isArray(expected)) {
15437
- return matchString(actual === undefined ? undefined : String(actual), expected);
15438
- }
15439
- return actual === expected;
15623
+ const actualValues = getFieldValues(input, path);
15624
+ return matchField(actualValues, expected, path);
15440
15625
  });
15441
15626
  }
15627
+ function matchField(actualValues, expected, path) {
15628
+ if (isNegativeMatcher(expected)) {
15629
+ return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
15630
+ }
15631
+ return actualValues.some((actual) => matchPositiveField(actual, expected, path));
15632
+ }
15633
+ function matchPositiveField(actual, expected, path) {
15634
+ if (typeof expected === "string" || Array.isArray(expected)) {
15635
+ return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
15636
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
15637
+ }));
15638
+ }
15639
+ if (Array.isArray(actual)) {
15640
+ return actual.some((item) => item === expected);
15641
+ }
15642
+ return actual === expected;
15643
+ }
15644
+ function stringCandidates(actual) {
15645
+ if (actual === undefined)
15646
+ return [];
15647
+ if (Array.isArray(actual)) {
15648
+ return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
15649
+ }
15650
+ return [String(actual)];
15651
+ }
15652
+ function isPrimitiveFieldValue(value) {
15653
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
15654
+ }
15655
+ function isNegativeMatcher(value) {
15656
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
15657
+ }
15442
15658
  function eventMatchesFilter(event, filter) {
15443
15659
  return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
15444
15660
  }
@@ -15451,16 +15667,29 @@ function channelMatchesEvent(channel, event) {
15451
15667
  }
15452
15668
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
15453
15669
  var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
15670
+ var LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:";
15671
+ var DEFAULT_EVENT_PAGE_LIMIT = 100;
15672
+ var MAX_EVENT_PAGE_LIMIT = 1000;
15454
15673
  function getEventsDataDir(override) {
15455
15674
  return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
15456
15675
  }
15676
+ function getActiveEventsDirEnv() {
15677
+ if (process.env[HASNA_EVENTS_DIR_ENV])
15678
+ return HASNA_EVENTS_DIR_ENV;
15679
+ if (process.env[HASNA_EVENTS_HOME_ENV])
15680
+ return HASNA_EVENTS_HOME_ENV;
15681
+ return null;
15682
+ }
15683
+
15457
15684
  class JsonEventsStore {
15458
15685
  dataDir;
15686
+ runtime;
15459
15687
  channelsPath;
15460
15688
  eventsPath;
15461
15689
  deliveriesPath;
15462
15690
  constructor(dataDir = getEventsDataDir()) {
15463
15691
  this.dataDir = dataDir;
15692
+ this.runtime = localJsonRuntime(dataDir);
15464
15693
  this.channelsPath = join(dataDir, "channels.json");
15465
15694
  this.eventsPath = join(dataDir, "events.json");
15466
15695
  this.deliveriesPath = join(dataDir, "deliveries.json");
@@ -15508,13 +15737,58 @@ class JsonEventsStore {
15508
15737
  await this.writeJson(this.eventsPath, events);
15509
15738
  return event;
15510
15739
  }
15511
- async listEvents() {
15740
+ async appendEventOnce(event, options = {}) {
15512
15741
  await this.init();
15513
- return this.readJson(this.eventsPath, []);
15742
+ const events = await this.readJson(this.eventsPath, []);
15743
+ const dedupe = options.dedupe !== false;
15744
+ if (dedupe) {
15745
+ const existing = findEventByIdentity(events, { id: event.id, dedupeKey: event.dedupeKey });
15746
+ if (existing) {
15747
+ return {
15748
+ event: existing,
15749
+ stored: false,
15750
+ deduped: true,
15751
+ identity: { id: existing.id, dedupeKey: existing.dedupeKey }
15752
+ };
15753
+ }
15754
+ }
15755
+ events.push(event);
15756
+ await this.writeJson(this.eventsPath, events);
15757
+ return {
15758
+ event,
15759
+ stored: true,
15760
+ deduped: false,
15761
+ identity: { id: event.id, dedupeKey: event.dedupeKey }
15762
+ };
15763
+ }
15764
+ async listEvents(options = {}) {
15765
+ await this.init();
15766
+ const events = await this.readJson(this.eventsPath, []);
15767
+ return queryEvents(events, options);
15768
+ }
15769
+ async listEventsPage(options = {}) {
15770
+ await this.init();
15771
+ const events = await this.readJson(this.eventsPath, []);
15772
+ const queried = queryEvents(events, {
15773
+ eventId: options.eventId,
15774
+ source: options.source,
15775
+ type: options.type
15776
+ });
15777
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
15778
+ const limit = normalizeEventPageLimit(options.limit);
15779
+ const pageEvents = queried.slice(offset, offset + limit);
15780
+ const nextOffset = offset + pageEvents.length;
15781
+ const hasMore = nextOffset < queried.length;
15782
+ return {
15783
+ events: pageEvents,
15784
+ cursor: options.cursor,
15785
+ nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
15786
+ hasMore
15787
+ };
15514
15788
  }
15515
15789
  async findEventByIdentity(identity) {
15516
15790
  const events = await this.listEvents();
15517
- return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
15791
+ return findEventByIdentity(events, identity);
15518
15792
  }
15519
15793
  async appendDelivery(result) {
15520
15794
  await this.init();
@@ -15565,6 +15839,130 @@ class JsonEventsStore {
15565
15839
  });
15566
15840
  }
15567
15841
  }
15842
+ function localJsonRuntime(dataDir = getEventsDataDir()) {
15843
+ return {
15844
+ mode: "local-files",
15845
+ name: "json-events-store",
15846
+ remote: false,
15847
+ localFiles: true,
15848
+ localSqlite: false,
15849
+ postgres: false,
15850
+ s3: false,
15851
+ aws: false,
15852
+ durable: true,
15853
+ idempotency: "best-effort-local",
15854
+ replayCursors: true,
15855
+ description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
15856
+ };
15857
+ }
15858
+ function encodeLocalJsonEventCursor(offset, options = {}) {
15859
+ if (!Number.isInteger(offset) || offset < 0)
15860
+ throw new Error(`Invalid event cursor offset: ${offset}`);
15861
+ const payload = {
15862
+ offset,
15863
+ eventId: options.eventId,
15864
+ source: options.source,
15865
+ type: options.type
15866
+ };
15867
+ return `${LOCAL_JSON_EVENT_CURSOR_PREFIX}${Buffer2.from(JSON.stringify(payload), "utf-8").toString("base64url")}`;
15868
+ }
15869
+ function decodeLocalJsonEventCursor(cursor, options = {}) {
15870
+ if (!cursor)
15871
+ return 0;
15872
+ if (!cursor.startsWith(LOCAL_JSON_EVENT_CURSOR_PREFIX))
15873
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15874
+ const rawPayload = cursor.slice(LOCAL_JSON_EVENT_CURSOR_PREFIX.length);
15875
+ let payload;
15876
+ try {
15877
+ payload = JSON.parse(Buffer2.from(rawPayload, "base64url").toString("utf-8"));
15878
+ } catch {
15879
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15880
+ }
15881
+ const offset = payload.offset;
15882
+ if (!Number.isInteger(offset) || offset < 0)
15883
+ throw new Error(`Invalid local JSON event cursor: ${cursor}`);
15884
+ assertCursorFilter("eventId", payload.eventId, options.eventId);
15885
+ assertCursorFilter("source", payload.source, options.source);
15886
+ assertCursorFilter("type", payload.type, options.type);
15887
+ return offset;
15888
+ }
15889
+ function normalizeEventPageLimit(limit) {
15890
+ if (limit === undefined)
15891
+ return DEFAULT_EVENT_PAGE_LIMIT;
15892
+ if (!Number.isInteger(limit) || limit < 1)
15893
+ throw new Error(`Event page limit must be a positive integer, got ${limit}`);
15894
+ return Math.min(limit, MAX_EVENT_PAGE_LIMIT);
15895
+ }
15896
+ function queryEvents(events, options) {
15897
+ let rows = events;
15898
+ if (options.eventId)
15899
+ rows = rows.filter((event) => event.id === options.eventId);
15900
+ if (options.source)
15901
+ rows = rows.filter((event) => event.source === options.source);
15902
+ if (options.type)
15903
+ rows = rows.filter((event) => event.type === options.type);
15904
+ if (options.cursor) {
15905
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
15906
+ rows = rows.slice(offset);
15907
+ }
15908
+ if (options.limit !== undefined)
15909
+ rows = rows.slice(0, normalizeEventPageLimit(options.limit));
15910
+ return rows;
15911
+ }
15912
+ function assertCursorFilter(name, cursorValue, optionValue) {
15913
+ if (cursorValue !== optionValue)
15914
+ throw new Error(`Local JSON event cursor ${name} filter mismatch`);
15915
+ }
15916
+ function findEventByIdentity(events, identity) {
15917
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
15918
+ }
15919
+ async function getEventsStatus(dataDir) {
15920
+ const store = new JsonEventsStore(dataDir);
15921
+ await store.init();
15922
+ const [channels, events, deliveries] = await Promise.all([
15923
+ store.listChannels(),
15924
+ store.listEvents(),
15925
+ store.listDeliveries()
15926
+ ]);
15927
+ const transports = channels.reduce((counts, channel) => {
15928
+ counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
15929
+ return counts;
15930
+ }, {});
15931
+ return {
15932
+ service: "events",
15933
+ schemaVersion: "1.0",
15934
+ dataDir: store.dataDir,
15935
+ storage: store.runtime,
15936
+ env: {
15937
+ primary: HASNA_EVENTS_DIR_ENV,
15938
+ fallback: HASNA_EVENTS_HOME_ENV,
15939
+ active: getActiveEventsDirEnv()
15940
+ },
15941
+ files: {
15942
+ channels: statusFile(store.dataDir, "channels.json", channels.length),
15943
+ events: statusFile(store.dataDir, "events.json", events.length),
15944
+ deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
15945
+ },
15946
+ counts: {
15947
+ channels: channels.length,
15948
+ enabledChannels: channels.filter((channel) => channel.enabled).length,
15949
+ disabledChannels: channels.filter((channel) => !channel.enabled).length,
15950
+ events: events.length,
15951
+ deliveries: deliveries.length
15952
+ },
15953
+ transports,
15954
+ safety: {
15955
+ includesEventPayloads: false,
15956
+ includesWebhookSecrets: false,
15957
+ listOutputsRedactSecrets: true,
15958
+ statusOutputIsMetadataOnly: true
15959
+ }
15960
+ };
15961
+ }
15962
+ function statusFile(dataDir, fileName, records) {
15963
+ const path = join(dataDir, fileName);
15964
+ return { path, exists: existsSync(path), records };
15965
+ }
15568
15966
  var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
15569
15967
  function buildSignatureBase(timestamp, body) {
15570
15968
  return `${timestamp}.${body}`;
@@ -15579,21 +15977,27 @@ function now() {
15579
15977
  function truncate(value, max = 4096) {
15580
15978
  return value.length > max ? `${value.slice(0, max)}...` : value;
15581
15979
  }
15582
- function buildWebhookRequest(event, channel) {
15980
+ function buildWebhookRequest(event, channel, options = {}) {
15583
15981
  if (!channel.webhook)
15584
15982
  throw new Error(`Channel ${channel.id} has no webhook config`);
15983
+ for (const name of Object.keys(channel.webhook.headers ?? {})) {
15984
+ if (/^x-hasna-/i.test(name)) {
15985
+ throw new Error(`Webhook header ${name} is reserved for signed delivery metadata`);
15986
+ }
15987
+ }
15585
15988
  const body = JSON.stringify(event);
15586
- const timestamp = event.time;
15989
+ const timestamp = options.timestamp ?? new Date().toISOString();
15587
15990
  const headers = {
15588
15991
  "Content-Type": "application/json",
15589
15992
  "User-Agent": "@hasna/events",
15590
15993
  "X-Hasna-Event-Id": event.id,
15591
15994
  "X-Hasna-Event-Type": event.type,
15592
- "X-Hasna-Timestamp": timestamp,
15593
- ...channel.webhook.headers
15995
+ ...channel.webhook.headers,
15996
+ "X-Hasna-Timestamp": timestamp
15594
15997
  };
15595
- if (channel.webhook.secret) {
15596
- headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
15998
+ const secret = options.secret ?? channel.webhook.secret;
15999
+ if (secret) {
16000
+ headers["X-Hasna-Signature"] = signPayload(secret, timestamp, body);
15597
16001
  }
15598
16002
  return { body, headers };
15599
16003
  }
@@ -15601,7 +16005,21 @@ async function dispatchWebhook(event, channel, options = {}) {
15601
16005
  if (!channel.webhook)
15602
16006
  throw new Error(`Channel ${channel.id} has no webhook config`);
15603
16007
  const startedAt = now();
15604
- const { body, headers } = buildWebhookRequest(event, channel);
16008
+ let secret = channel.webhook.secret;
16009
+ if (channel.webhook.secretRef) {
16010
+ if (!options.secretResolver) {
16011
+ return failedAttempt(startedAt, "Webhook secret reference has no runtime resolver");
16012
+ }
16013
+ try {
16014
+ secret = await options.secretResolver(channel.webhook.secretRef);
16015
+ } catch {
16016
+ return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
16017
+ }
16018
+ if (!secret)
16019
+ return failedAttempt(startedAt, "Webhook secret reference could not be resolved");
16020
+ }
16021
+ const timestamp = (options.now?.() ?? new Date).toISOString();
16022
+ const { body, headers } = buildWebhookRequest(event, channel, { secret, timestamp });
15605
16023
  const controller = new AbortController;
15606
16024
  const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
15607
16025
  try {
@@ -15633,6 +16051,15 @@ async function dispatchWebhook(event, channel, options = {}) {
15633
16051
  clearTimeout(timeout);
15634
16052
  }
15635
16053
  }
16054
+ function failedAttempt(startedAt, error) {
16055
+ return {
16056
+ attempt: 1,
16057
+ status: "failed",
16058
+ startedAt,
16059
+ completedAt: now(),
16060
+ error
16061
+ };
16062
+ }
15636
16063
  async function dispatchCommand(event, channel) {
15637
16064
  if (!channel.command)
15638
16065
  throw new Error(`Channel ${channel.id} has no command config`);
@@ -15721,14 +16148,98 @@ function createDeliveryResult(event, channel, attempts) {
15721
16148
  completedAt: attempts.at(-1)?.completedAt ?? now()
15722
16149
  };
15723
16150
  }
15724
- function createEvent(input) {
15725
- return {
15726
- id: input.id ?? randomUUID2(),
15727
- source: input.source,
15728
- type: input.type,
15729
- time: normalizeTime(input.time),
15730
- subject: input.subject,
15731
- severity: input.severity ?? "info",
16151
+
16152
+ class EventValidationError extends Error {
16153
+ eventType;
16154
+ issues;
16155
+ constructor(eventType, issues) {
16156
+ const detail = issues.map((issue) => `${issue.path || "<root>"}: ${issue.message}`).join("; ");
16157
+ super(`Event validation failed for type "${eventType}": ${detail}`);
16158
+ this.name = "EventValidationError";
16159
+ this.eventType = eventType;
16160
+ this.issues = issues;
16161
+ }
16162
+ }
16163
+
16164
+ class EventTypeCatalog {
16165
+ definitions = new Map;
16166
+ register(definition) {
16167
+ this.definitions.set(definition.type, definition);
16168
+ return this;
16169
+ }
16170
+ unregister(type) {
16171
+ return this.definitions.delete(type);
16172
+ }
16173
+ has(type) {
16174
+ return this.definitions.has(type);
16175
+ }
16176
+ get(type) {
16177
+ return this.definitions.get(type);
16178
+ }
16179
+ list() {
16180
+ return [...this.definitions.values()];
16181
+ }
16182
+ validateEvent(event) {
16183
+ const definition = this.definitions.get(event.type);
16184
+ if (!definition)
16185
+ return { ok: true };
16186
+ return definition.validate(event.data, event);
16187
+ }
16188
+ assertEventValid(event) {
16189
+ const result = this.validateEvent(event);
16190
+ if (!result.ok) {
16191
+ throw new EventValidationError(event.type, result.issues);
16192
+ }
16193
+ }
16194
+ }
16195
+ var defaultEventTypeCatalog = new EventTypeCatalog;
16196
+ var APP_EVENT_V1_MAX_DATA_BYTES = 32 * 1024;
16197
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
16198
+ if (paths.length === 0)
16199
+ return event;
16200
+ const copy = structuredClone(event);
16201
+ for (const path of paths) {
16202
+ setPath(copy, path, replacement);
16203
+ }
16204
+ return copy;
16205
+ }
16206
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
16207
+ return redactValue(event, replacement);
16208
+ }
16209
+ function shouldRedactKey(key) {
16210
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
16211
+ }
16212
+ function redactValue(value, replacement) {
16213
+ if (Array.isArray(value))
16214
+ return value.map((item) => redactValue(item, replacement));
16215
+ if (!value || typeof value !== "object")
16216
+ return value;
16217
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
16218
+ key,
16219
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
16220
+ ]));
16221
+ }
16222
+ function setPath(input, path, replacement) {
16223
+ const parts = path.split(".");
16224
+ let cursor = input;
16225
+ for (const part of parts.slice(0, -1)) {
16226
+ const next = cursor[part];
16227
+ if (!next || typeof next !== "object")
16228
+ return;
16229
+ cursor = next;
16230
+ }
16231
+ const last = parts.at(-1);
16232
+ if (last && last in cursor)
16233
+ cursor[last] = replacement;
16234
+ }
16235
+ function createEvent(input) {
16236
+ return {
16237
+ id: input.id ?? randomUUID2(),
16238
+ source: input.source,
16239
+ type: input.type,
16240
+ time: normalizeTime(input.time),
16241
+ subject: input.subject,
16242
+ severity: input.severity ?? "info",
15732
16243
  data: input.data ?? {},
15733
16244
  message: input.message,
15734
16245
  dedupeKey: input.dedupeKey,
@@ -15741,10 +16252,18 @@ class EventsClient {
15741
16252
  store;
15742
16253
  redactors;
15743
16254
  transportOptions;
16255
+ catalog;
16256
+ validateCatalogTypes;
15744
16257
  constructor(options = {}) {
15745
16258
  this.store = options.store ?? new JsonEventsStore(options.dataDir);
15746
16259
  this.redactors = options.redactors ?? [];
15747
- this.transportOptions = { fetchImpl: options.fetchImpl };
16260
+ this.transportOptions = {
16261
+ fetchImpl: options.fetchImpl,
16262
+ secretResolver: options.secretResolver,
16263
+ now: options.now
16264
+ };
16265
+ this.catalog = options.catalog ?? defaultEventTypeCatalog;
16266
+ this.validateCatalogTypes = options.validateCatalogTypes ?? false;
15748
16267
  }
15749
16268
  async addChannel(input) {
15750
16269
  const timestamp = new Date().toISOString();
@@ -15762,18 +16281,40 @@ class EventsClient {
15762
16281
  }
15763
16282
  async emit(input, options = {}) {
15764
16283
  const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
15765
- if (options.dedupe !== false) {
15766
- const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
15767
- if (existing) {
15768
- return { event: existing, deliveries: [], deduped: true };
15769
- }
15770
- }
15771
- await this.store.appendEvent(event);
15772
- const deliveries = options.deliver === false ? [] : await this.deliver(event);
15773
- return { event, deliveries, deduped: false };
15774
- }
15775
- async listEvents() {
15776
- return this.store.listEvents();
16284
+ if (options.validate ?? this.validateCatalogTypes) {
16285
+ this.catalog.assertEventValid(event);
16286
+ }
16287
+ const append = await this.appendEvent(event, { dedupe: options.dedupe !== false });
16288
+ if (append.deduped) {
16289
+ return { event: append.event, deliveries: [], deduped: true };
16290
+ }
16291
+ const deliveries = options.deliver === false ? [] : await this.deliver(append.event);
16292
+ return { event: append.event, deliveries, deduped: false };
16293
+ }
16294
+ async listEvents(options = {}) {
16295
+ if (Object.keys(options).length === 0)
16296
+ return this.store.listEvents();
16297
+ return queryClientEvents(await this.store.listEvents(), options);
16298
+ }
16299
+ async listEventsPage(options = {}) {
16300
+ if (this.store.listEventsPage)
16301
+ return this.store.listEventsPage(options);
16302
+ const events = queryClientEvents(await this.store.listEvents(), {
16303
+ eventId: options.eventId,
16304
+ source: options.source,
16305
+ type: options.type
16306
+ });
16307
+ const offset = decodeLocalJsonEventCursor(options.cursor, options);
16308
+ const limit = normalizeEventPageLimit(options.limit);
16309
+ const pageEvents = events.slice(offset, offset + limit);
16310
+ const nextOffset = offset + pageEvents.length;
16311
+ const hasMore = nextOffset < events.length;
16312
+ return {
16313
+ events: pageEvents,
16314
+ cursor: options.cursor,
16315
+ nextCursor: hasMore ? encodeLocalJsonEventCursor(nextOffset, options) : undefined,
16316
+ hasMore
16317
+ };
15777
16318
  }
15778
16319
  async listDeliveries() {
15779
16320
  return this.store.listDeliveries();
@@ -15790,7 +16331,7 @@ class EventsClient {
15790
16331
  }
15791
16332
  return deliveries;
15792
16333
  }
15793
- async testChannel(id, input = {}) {
16334
+ async matchChannel(id, input = {}) {
15794
16335
  const channel = await this.store.getChannel(id);
15795
16336
  if (!channel)
15796
16337
  throw new Error(`Channel not found: ${id}`);
@@ -15807,28 +16348,71 @@ class EventsClient {
15807
16348
  time: input.time,
15808
16349
  id: input.id
15809
16350
  });
16351
+ const matched = channelMatchesEvent(channel, event);
16352
+ return {
16353
+ channelId: channel.id,
16354
+ matched,
16355
+ event,
16356
+ filters: channel.filters,
16357
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
16358
+ };
16359
+ }
16360
+ async testChannel(id, input = {}, options = {}) {
16361
+ const channel = await this.store.getChannel(id);
16362
+ if (!channel)
16363
+ throw new Error(`Channel not found: ${id}`);
16364
+ const match = await this.matchChannel(id, input);
16365
+ const event = match.event;
16366
+ if (options.honorFilters && !match.matched) {
16367
+ const timestamp = new Date().toISOString();
16368
+ const result2 = createDeliveryResult(event, channel, [{
16369
+ attempt: 1,
16370
+ status: "skipped",
16371
+ startedAt: timestamp,
16372
+ completedAt: timestamp,
16373
+ error: match.reason
16374
+ }]);
16375
+ result2.metadata = { reason: "filter_mismatch" };
16376
+ await this.store.appendDelivery(result2);
16377
+ return result2;
16378
+ }
15810
16379
  const eventForChannel = await this.applyRedaction(event, channel);
15811
16380
  const result = await this.deliverWithRetry(eventForChannel, channel);
15812
16381
  await this.store.appendDelivery(result);
15813
16382
  return result;
15814
16383
  }
15815
16384
  async replay(options = {}) {
15816
- const events = (await this.store.listEvents()).filter((event) => {
15817
- if (options.eventId && event.id !== options.eventId)
15818
- return false;
15819
- if (options.source && event.source !== options.source)
15820
- return false;
15821
- if (options.type && event.type !== options.type)
15822
- return false;
15823
- return true;
15824
- });
16385
+ const page = options.cursor || options.limit !== undefined ? await this.listEventsPage(options) : { events: await this.listEvents(options), hasMore: false };
15825
16386
  if (options.dryRun)
15826
- return { events, deliveries: [] };
16387
+ return { events: page.events, deliveries: [], cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
15827
16388
  const deliveries = [];
15828
- for (const event of events) {
16389
+ for (const event of page.events) {
15829
16390
  deliveries.push(...await this.deliver(event));
15830
16391
  }
15831
- return { events, deliveries };
16392
+ return { events: page.events, deliveries, cursor: page.cursor, nextCursor: page.nextCursor, hasMore: page.hasMore };
16393
+ }
16394
+ async appendEvent(event, options) {
16395
+ if (this.store.appendEventOnce) {
16396
+ return this.store.appendEventOnce(event, { dedupe: options.dedupe });
16397
+ }
16398
+ if (options.dedupe) {
16399
+ const existing = await this.store.findEventByIdentity({ id: event.id, dedupeKey: event.dedupeKey });
16400
+ if (existing) {
16401
+ return {
16402
+ event: existing,
16403
+ stored: false,
16404
+ deduped: true,
16405
+ identity: { id: existing.id, dedupeKey: existing.dedupeKey }
16406
+ };
16407
+ }
16408
+ }
16409
+ const stored = await this.store.appendEvent(event);
16410
+ return {
16411
+ event: stored,
16412
+ stored: true,
16413
+ deduped: false,
16414
+ identity: { id: stored.id, dedupeKey: stored.dedupeKey }
16415
+ };
15832
16416
  }
15833
16417
  async applyRedaction(event, channel) {
15834
16418
  let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
@@ -15855,15 +16439,6 @@ class EventsClient {
15855
16439
  return createDeliveryResult(event, channel, attempts);
15856
16440
  }
15857
16441
  }
15858
- function redactPaths(event, paths, replacement = "[REDACTED]") {
15859
- if (paths.length === 0)
15860
- return event;
15861
- const copy = structuredClone(event);
15862
- for (const path of paths) {
15863
- setPath(copy, path, replacement);
15864
- }
15865
- return copy;
15866
- }
15867
16442
  function sanitizeChannelForOutput(channel) {
15868
16443
  const copy = structuredClone(channel);
15869
16444
  if (copy.webhook?.secret)
@@ -15876,34 +16451,19 @@ function sanitizeChannelForOutput(channel) {
15876
16451
  function sanitizeChannelsForOutput(channels) {
15877
16452
  return channels.map(sanitizeChannelForOutput);
15878
16453
  }
15879
- function redactSensitiveKeys(event, replacement = "[REDACTED]") {
15880
- return redactValue(event, replacement);
15881
- }
15882
- function shouldRedactKey(key) {
15883
- return /secret|token|password|api[_-]?key|authorization/i.test(key);
15884
- }
15885
- function redactValue(value, replacement) {
15886
- if (Array.isArray(value))
15887
- return value.map((item) => redactValue(item, replacement));
15888
- if (!value || typeof value !== "object")
15889
- return value;
15890
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
15891
- key,
15892
- shouldRedactKey(key) ? replacement : redactValue(item, replacement)
15893
- ]));
15894
- }
15895
- function setPath(input, path, replacement) {
15896
- const parts = path.split(".");
15897
- let cursor = input;
15898
- for (const part of parts.slice(0, -1)) {
15899
- const next = cursor[part];
15900
- if (!next || typeof next !== "object")
15901
- return;
15902
- cursor = next;
15903
- }
15904
- const last = parts.at(-1);
15905
- if (last && last in cursor)
15906
- cursor[last] = replacement;
16454
+ function queryClientEvents(events, options) {
16455
+ let rows = events;
16456
+ if (options.eventId)
16457
+ rows = rows.filter((event) => event.id === options.eventId);
16458
+ if (options.source)
16459
+ rows = rows.filter((event) => event.source === options.source);
16460
+ if (options.type)
16461
+ rows = rows.filter((event) => event.type === options.type);
16462
+ if (options.cursor)
16463
+ rows = rows.slice(decodeLocalJsonEventCursor(options.cursor, options));
16464
+ if (options.limit !== undefined)
16465
+ rows = rows.slice(0, normalizeEventPageLimit(options.limit));
16466
+ return rows;
15907
16467
  }
15908
16468
  function normalizeTime(value) {
15909
16469
  if (!value)
@@ -15917,6 +16477,77 @@ function normalizeRetryPolicy(policy) {
15917
16477
  multiplier: Math.max(1, policy?.multiplier ?? 2)
15918
16478
  };
15919
16479
  }
16480
+ function parseFieldMatchers(values, label, typed = false) {
16481
+ if (!values?.length)
16482
+ return;
16483
+ const result = {};
16484
+ for (const value of values) {
16485
+ const parsed = parseMatcherExpression(value, label);
16486
+ const path = parsed.path;
16487
+ if (path in result)
16488
+ throw new Error(`Duplicate ${label} filter path: ${path}`);
16489
+ const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
16490
+ result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
16491
+ }
16492
+ return result;
16493
+ }
16494
+ function parseFilterOptions(options) {
16495
+ const filter2 = {};
16496
+ if (options.source)
16497
+ filter2.source = options.source;
16498
+ if (options.type)
16499
+ filter2.type = options.type;
16500
+ if (options.subject)
16501
+ filter2.subject = options.subject;
16502
+ if (options.severity)
16503
+ filter2.severity = options.severity;
16504
+ const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
16505
+ const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
16506
+ if (Object.keys(data).length > 0)
16507
+ filter2.data = data;
16508
+ if (Object.keys(metadata).length > 0)
16509
+ filter2.metadata = metadata;
16510
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
16511
+ }
16512
+ function mergeMatchers(...records) {
16513
+ const result = {};
16514
+ for (const record of records) {
16515
+ if (!record)
16516
+ continue;
16517
+ for (const [path, value] of Object.entries(record)) {
16518
+ if (path in result)
16519
+ throw new Error(`Duplicate filter path: ${path}`);
16520
+ result[path] = value;
16521
+ }
16522
+ }
16523
+ return result;
16524
+ }
16525
+ function parseTypedMatcherValue(value, label) {
16526
+ const parsed = JSON.parse(value);
16527
+ if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
16528
+ return parsed;
16529
+ }
16530
+ throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
16531
+ }
16532
+ function parseMatcherExpression(value, label) {
16533
+ const negativeSeparator = value.indexOf("!=");
16534
+ if (negativeSeparator > 0) {
16535
+ return {
16536
+ path: value.slice(0, negativeSeparator),
16537
+ rawValue: value.slice(negativeSeparator + 2),
16538
+ negated: true
16539
+ };
16540
+ }
16541
+ const separator = value.indexOf("=");
16542
+ if (separator <= 0)
16543
+ throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
16544
+ return {
16545
+ path: value.slice(0, separator),
16546
+ rawValue: value.slice(separator + 1),
16547
+ negated: false
16548
+ };
16549
+ }
16550
+ var DEFAULT_EVENT_LIST_LIMIT = 100;
15920
16551
  function parseJsonObject(value, fallback) {
15921
16552
  if (!value)
15922
16553
  return fallback;
@@ -15938,18 +16569,6 @@ function parseHeaders(values) {
15938
16569
  }
15939
16570
  return headers;
15940
16571
  }
15941
- function parseFilter(options) {
15942
- const filter2 = {};
15943
- if (options.source)
15944
- filter2.source = options.source;
15945
- if (options.type)
15946
- filter2.type = options.type;
15947
- if (options.subject)
15948
- filter2.subject = options.subject;
15949
- if (options.severity)
15950
- filter2.severity = options.severity;
15951
- return Object.keys(filter2).length > 0 ? [filter2] : undefined;
15952
- }
15953
16572
  function createClient(options) {
15954
16573
  if (options.createClient)
15955
16574
  return options.createClient();
@@ -15961,22 +16580,30 @@ function print(value, json, text) {
15961
16580
  else
15962
16581
  console.log(text);
15963
16582
  }
16583
+ function fail(error, json) {
16584
+ const message = error instanceof Error ? error.message : String(error);
16585
+ if (json)
16586
+ console.log(JSON.stringify({ error: message }, null, 2));
16587
+ else
16588
+ console.error(message);
16589
+ process.exitCode = 1;
16590
+ }
15964
16591
  function hasJsonOption(options) {
15965
16592
  return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
15966
16593
  }
15967
16594
  function wantsJson(actionOptions, command) {
15968
16595
  return hasJsonOption(actionOptions) || hasJsonOption(command);
15969
16596
  }
15970
- function registerWebhookCommands(program, options) {
15971
- const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
15972
- webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
16597
+ function registerChannelCommands(program, options) {
16598
+ const channels = program.command(options.channelsCommandName ?? "channels").description("Manage Hasna event channels");
16599
+ channels.command("add").description("Add or replace a channel").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--data <path=value...>", "Event data field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
15973
16600
  const timestamp = new Date().toISOString();
15974
16601
  const channel = {
15975
16602
  id: actionOptions.id,
15976
16603
  name: actionOptions.name,
15977
16604
  enabled: !actionOptions.disabled,
15978
16605
  transport: actionOptions.transport,
15979
- filters: parseFilter(actionOptions),
16606
+ filters: parseFilterOptions(actionOptions),
15980
16607
  retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
15981
16608
  redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
15982
16609
  createdAt: timestamp,
@@ -15992,35 +16619,61 @@ function registerWebhookCommands(program, options) {
15992
16619
  const saved = await createClient(options).addChannel(channel);
15993
16620
  print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
15994
16621
  });
15995
- webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
15996
- const channels = await createClient(options).listChannels();
16622
+ channels.command("list").description("List configured channels").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16623
+ const channels2 = await createClient(options).listChannels();
15997
16624
  if (wantsJson(actionOptions, command)) {
15998
- console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
16625
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
15999
16626
  return;
16000
16627
  }
16001
- if (!channels.length) {
16628
+ if (!channels2.length) {
16002
16629
  console.log("No channels configured.");
16003
16630
  return;
16004
16631
  }
16005
- for (const channel of channels) {
16632
+ for (const channel of channels2) {
16006
16633
  console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
16007
16634
  }
16008
16635
  });
16009
- webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16636
+ channels.command("status").description("Show events channel storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16637
+ const status = await getEventsStatus(options.dataDir);
16638
+ print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
16639
+ });
16640
+ channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16010
16641
  const removed = await createClient(options).removeChannel(id);
16011
16642
  print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
16012
16643
  });
16013
- webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16014
- const result = await createClient(options).testChannel(id, {
16015
- source: options.source,
16016
- type: actionOptions.type,
16017
- subject: actionOptions.subject ?? id,
16018
- message: actionOptions.message,
16019
- data: parseJsonObject(actionOptions.data, { test: true })
16020
- });
16021
- print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
16644
+ channels.command("test").description("Send a test event to one channel").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--honor-filters", "Skip delivery when the sample event does not match channel filters", false).option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16645
+ const json = wantsJson(actionOptions, command);
16646
+ try {
16647
+ const result = await createClient(options).testChannel(id, {
16648
+ source: actionOptions.source ?? options.source,
16649
+ type: actionOptions.type,
16650
+ subject: actionOptions.subject ?? id,
16651
+ message: actionOptions.message,
16652
+ data: parseJsonObject(actionOptions.data, { test: true }),
16653
+ metadata: parseJsonObject(actionOptions.metadata, {})
16654
+ }, { honorFilters: actionOptions.honorFilters });
16655
+ print(result, json, `${result.status}: ${result.channelId}`);
16656
+ } catch (error) {
16657
+ fail(error, json);
16658
+ }
16659
+ });
16660
+ channels.command("match").description("Check whether a sample event matches one channel without delivering").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events match preview").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
16661
+ const json = wantsJson(actionOptions, command);
16662
+ try {
16663
+ const result = await createClient(options).matchChannel(id, {
16664
+ source: actionOptions.source ?? options.source,
16665
+ type: actionOptions.type,
16666
+ subject: actionOptions.subject ?? id,
16667
+ message: actionOptions.message,
16668
+ data: parseJsonObject(actionOptions.data, { test: true }),
16669
+ metadata: parseJsonObject(actionOptions.metadata, {})
16670
+ });
16671
+ print(result, json, `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
16672
+ } catch (error) {
16673
+ fail(error, json);
16674
+ }
16022
16675
  });
16023
- return webhooks;
16676
+ return channels;
16024
16677
  }
16025
16678
  function registerEventCommands(program, options) {
16026
16679
  const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
@@ -16037,7 +16690,8 @@ function registerEventCommands(program, options) {
16037
16690
  }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
16038
16691
  print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
16039
16692
  });
16040
- events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16693
+ const defaultListLimit = options.defaultEventListLimit ?? DEFAULT_EVENT_LIST_LIMIT;
16694
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", `Limit to the most recent <n> events (default ${defaultListLimit}; use 0 for all)`, parseNumber, defaultListLimit).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16041
16695
  let rows = await createClient(options).listEvents();
16042
16696
  if (actionOptions.source)
16043
16697
  rows = rows.filter((event) => event.source === actionOptions.source);
@@ -16056,19 +16710,21 @@ function registerEventCommands(program, options) {
16056
16710
  for (const event of rows)
16057
16711
  console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
16058
16712
  });
16059
- events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16713
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--cursor <cursor>", "Opaque replay cursor from a previous page").option("--limit <n>", "Maximum events to replay", parseNumber).option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
16060
16714
  const result = await createClient(options).replay({
16061
16715
  eventId: actionOptions.id,
16062
16716
  source: actionOptions.source,
16063
16717
  type: actionOptions.type,
16718
+ cursor: actionOptions.cursor,
16719
+ limit: actionOptions.limit,
16064
16720
  dryRun: actionOptions.dryRun
16065
16721
  });
16066
- print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
16722
+ print(result, wantsJson(actionOptions, command), replaySummary(result.events.length, result.deliveries.length, result.nextCursor));
16067
16723
  });
16068
16724
  return events;
16069
16725
  }
16070
16726
  function registerEventsCommands(program, options) {
16071
- registerWebhookCommands(program, options);
16727
+ registerChannelCommands(program, options);
16072
16728
  registerEventCommands(program, options);
16073
16729
  }
16074
16730
  function parseNumber(value) {
@@ -16081,8 +16737,12 @@ function collectValues(value, previous) {
16081
16737
  previous.push(value);
16082
16738
  return previous;
16083
16739
  }
16740
+ function replaySummary(events, deliveries, nextCursor) {
16741
+ const suffix = nextCursor ? `, next cursor: ${nextCursor}` : "";
16742
+ return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
16743
+ }
16084
16744
 
16085
- // node_modules/commander/esm.mjs
16745
+ // ../../node_modules/.bun/commander@13.1.0/node_modules/commander/esm.mjs
16086
16746
  var import__ = __toESM(require_commander(), 1);
16087
16747
  var {
16088
16748
  program,
@@ -16101,9 +16761,9 @@ var {
16101
16761
  // src/cli/index.tsx
16102
16762
  init_apply();
16103
16763
  import chalk from "chalk";
16104
- import { existsSync as existsSync16, lstatSync as lstatSync5, readFileSync as readFileSync13, readSync, writeSync } from "fs";
16105
- import { homedir as homedir8 } from "os";
16106
- import { basename as basename7, join as join16, resolve as resolve10 } from "path";
16764
+ import { existsSync as existsSync17, lstatSync as lstatSync7, readFileSync as readFileSync15, readSync, writeSync } from "fs";
16765
+ import { homedir as homedir10 } from "os";
16766
+ import { basename as basename7, join as join19, resolve as resolve13 } from "path";
16107
16767
 
16108
16768
  // src/lib/config-target-identity.ts
16109
16769
  init_apply();
@@ -16150,14 +16810,14 @@ init_redact();
16150
16810
  // src/lib/export.ts
16151
16811
  init_config_store();
16152
16812
  import { existsSync as existsSync10, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
16153
- import { join as join11, resolve as resolve6 } from "path";
16813
+ import { join as join13, resolve as resolve8 } from "path";
16154
16814
  import { tmpdir } from "os";
16155
16815
  async function exportConfigs(outputPath, opts = {}) {
16156
16816
  const store = opts.store ?? resolveConfigStore();
16157
16817
  const configs = await store.listConfigs(opts.filter);
16158
- const absOutput = resolve6(outputPath);
16159
- const tmpDir = join11(tmpdir(), `configs-export-${Date.now()}`);
16160
- const contentsDir = join11(tmpDir, "contents");
16818
+ const absOutput = resolve8(outputPath);
16819
+ const tmpDir = join13(tmpdir(), `configs-export-${Date.now()}`);
16820
+ const contentsDir = join13(tmpDir, "contents");
16161
16821
  try {
16162
16822
  mkdirSync4(contentsDir, { recursive: true });
16163
16823
  const manifest = {
@@ -16165,10 +16825,10 @@ async function exportConfigs(outputPath, opts = {}) {
16165
16825
  exported_at: new Date().toISOString(),
16166
16826
  configs: configs.map(({ content: _content, ...meta }) => meta)
16167
16827
  };
16168
- writeFileSync3(join11(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
16828
+ writeFileSync3(join13(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
16169
16829
  for (const config of configs) {
16170
16830
  const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
16171
- writeFileSync3(join11(contentsDir, fileName), config.content, "utf-8");
16831
+ writeFileSync3(join13(contentsDir, fileName), config.content, "utf-8");
16172
16832
  }
16173
16833
  const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
16174
16834
  stdout: "pipe",
@@ -16189,14 +16849,14 @@ async function exportConfigs(outputPath, opts = {}) {
16189
16849
 
16190
16850
  // src/lib/import.ts
16191
16851
  init_config_store();
16192
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
16193
- import { join as join12, resolve as resolve7 } from "path";
16852
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync4 } from "fs";
16853
+ import { join as join14, resolve as resolve9 } from "path";
16194
16854
  import { tmpdir as tmpdir2 } from "os";
16195
16855
  async function importConfigs(bundlePath, opts = {}) {
16196
16856
  const store = opts.store ?? resolveConfigStore();
16197
16857
  const conflict = opts.conflict ?? "skip";
16198
- const absPath = resolve7(bundlePath);
16199
- const tmpDir = join12(tmpdir2(), `configs-import-${Date.now()}`);
16858
+ const absPath = resolve9(bundlePath);
16859
+ const tmpDir = join14(tmpdir2(), `configs-import-${Date.now()}`);
16200
16860
  const result = { created: 0, updated: 0, skipped: 0, errors: [] };
16201
16861
  try {
16202
16862
  mkdirSync5(tmpDir, { recursive: true });
@@ -16209,15 +16869,15 @@ async function importConfigs(bundlePath, opts = {}) {
16209
16869
  const stderr = await new Response(proc.stderr).text();
16210
16870
  throw new Error(`tar extraction failed: ${stderr}`);
16211
16871
  }
16212
- const manifestPath = join12(tmpDir, "manifest.json");
16872
+ const manifestPath = join14(tmpDir, "manifest.json");
16213
16873
  if (!existsSync11(manifestPath))
16214
16874
  throw new Error("Invalid bundle: missing manifest.json");
16215
- const manifest = JSON.parse(readFileSync8(manifestPath, "utf-8"));
16875
+ const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
16216
16876
  for (const meta of manifest.configs) {
16217
16877
  try {
16218
16878
  const ext = meta.format === "text" ? "txt" : meta.format;
16219
- const contentFile = join12(tmpDir, "contents", `${meta.slug}.${ext}`);
16220
- const content = existsSync11(contentFile) ? readFileSync8(contentFile, "utf-8") : "";
16879
+ const contentFile = join14(tmpDir, "contents", `${meta.slug}.${ext}`);
16880
+ const content = existsSync11(contentFile) ? readFileSync9(contentFile, "utf-8") : "";
16221
16881
  let existing = null;
16222
16882
  try {
16223
16883
  existing = await store.getConfig(meta.slug);
@@ -16265,16 +16925,17 @@ init_machine();
16265
16925
  init_project_context();
16266
16926
  init_session_render();
16267
16927
  init_cursor_authority();
16268
- import { createHash as createHash7, randomUUID as randomUUID7 } from "crypto";
16928
+ init_session_authority();
16929
+ import { createHash as createHash8, randomUUID as randomUUID7 } from "crypto";
16269
16930
  import {
16270
16931
  existsSync as existsSync12,
16271
- lstatSync as lstatSync3,
16932
+ lstatSync as lstatSync4,
16272
16933
  mkdirSync as mkdirSync6,
16273
- readFileSync as readFileSync9,
16934
+ readFileSync as readFileSync10,
16274
16935
  readdirSync as readdirSync3,
16275
- statSync as statSync5
16936
+ statSync as statSync6
16276
16937
  } from "fs";
16277
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join13, parse as parse4, relative as relative5, resolve as resolve8 } from "path";
16938
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join15, parse as parse4, relative as relative5, resolve as resolve10 } from "path";
16278
16939
 
16279
16940
  class SessionApplyError extends Error {
16280
16941
  constructor(message) {
@@ -16295,6 +16956,7 @@ function applySessionRenderUnlocked(plan, options, coordination) {
16295
16956
  }
16296
16957
  assertCursorAuthorityUnchanged(plan);
16297
16958
  const targetHome = assertSafeTargetHome(plan.targetHome);
16959
+ assertClaudeAuthorityStillClear(plan, targetHome);
16298
16960
  const payloadFiles = [...plan.files, ...plan.assetFiles ?? []];
16299
16961
  const files = [...payloadFiles, plan.manifestFile];
16300
16962
  const manifestPath = resolvePlannedFilePath(plan, plan.manifestFile, targetHome);
@@ -16395,6 +17057,15 @@ function assertCursorAuthorityUnchanged(plan) {
16395
17057
  throw new SessionApplyError("Cursor fixed global authority changed after planning; refusing to apply a stale render plan.");
16396
17058
  }
16397
17059
  }
17060
+ function assertClaudeAuthorityStillClear(plan, targetHome) {
17061
+ if (plan.tool !== "claude" || plan.targetKind === "blocked")
17062
+ return;
17063
+ const conflicts = detectClaudeAuthorityConflicts(targetHome);
17064
+ if (conflicts.length === 0)
17065
+ return;
17066
+ const summary = conflicts.map((conflict) => `${conflict.relativePath}: ${conflict.reason}`).join("; ");
17067
+ throw new SessionApplyError(`Claude authority changed after planning; refusing to apply: ${summary}`);
17068
+ }
16398
17069
  function ensureSessionTargetHome(targetHome) {
16399
17070
  if (!existsSync12(targetHome))
16400
17071
  mkdirSync6(targetHome, { recursive: true, mode: 448 });
@@ -16402,7 +17073,7 @@ function ensureSessionTargetHome(targetHome) {
16402
17073
  }
16403
17074
  function checkSessionRenderDrift(targetHome, manifestPath) {
16404
17075
  const safeTargetHome = assertSafeTargetHome(targetHome);
16405
- const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve8(manifestPath)), safeTargetHome) : resolve8(safeTargetHome, ".hasna", "session-render-manifest.json");
17076
+ const resolvedManifestPath = manifestPath ? resolveManifestRelativePath(relative5(safeTargetHome, resolve10(manifestPath)), safeTargetHome) : resolve10(safeTargetHome, ".hasna", "session-render-manifest.json");
16406
17077
  const checkedAt = new Date().toISOString();
16407
17078
  const previousManifest = readPreviousManifest(resolvedManifestPath);
16408
17079
  if (!previousManifest) {
@@ -16429,7 +17100,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16429
17100
  });
16430
17101
  continue;
16431
17102
  }
16432
- const actualSha256 = sha2567(readFileSync9(target, "utf-8"));
17103
+ const actualSha256 = sha2568(readFileSync10(target, "utf-8"));
16433
17104
  if (actualSha256 !== file.sha256) {
16434
17105
  drifted.push({
16435
17106
  path: target,
@@ -16452,7 +17123,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
16452
17123
  function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
16453
17124
  const snapshot = readSessionRenderSnapshot(snapshotPath);
16454
17125
  const targetHome = assertSafeTargetHome(snapshot.targetHome);
16455
- const resolvedSnapshotPath = resolve8(snapshotPath);
17126
+ const resolvedSnapshotPath = resolve10(snapshotPath);
16456
17127
  const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
16457
17128
  if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute4(snapshotRelativePath)) {
16458
17129
  throw new SessionApplyError("Session snapshot must be stored inside its target home.");
@@ -16570,19 +17241,19 @@ function requiredRestoreHash(file) {
16570
17241
  return file.previousSha256;
16571
17242
  }
16572
17243
  function readSessionRenderSnapshot(snapshotPath) {
16573
- const resolved = resolve8(snapshotPath);
17244
+ const resolved = resolve10(snapshotPath);
16574
17245
  if (!existsSync12(resolved))
16575
17246
  throw new SessionApplyError(`Session snapshot not found: ${snapshotPath}`);
16576
- const stat = lstatSync3(resolved);
17247
+ const stat = lstatSync4(resolved);
16577
17248
  if (stat.isSymbolicLink() || !stat.isFile()) {
16578
17249
  throw new SessionApplyError(`Session snapshot is not a regular file: ${snapshotPath}`);
16579
17250
  }
16580
- if (statSync5(resolved).size > 32 * 1024 * 1024) {
17251
+ if (statSync6(resolved).size > 32 * 1024 * 1024) {
16581
17252
  throw new SessionApplyError(`Session snapshot exceeds the 32 MiB restore limit: ${snapshotPath}`);
16582
17253
  }
16583
17254
  let parsed;
16584
17255
  try {
16585
- parsed = JSON.parse(readFileSync9(resolved, "utf8"));
17256
+ parsed = JSON.parse(readFileSync10(resolved, "utf8"));
16586
17257
  } catch {
16587
17258
  throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
16588
17259
  }
@@ -16604,7 +17275,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16604
17275
  const previousManifest = snapshot.previousManifest;
16605
17276
  const previousFiles = new Map;
16606
17277
  for (const file of snapshot.files) {
16607
- if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2567(file.content) !== file.sha256) {
17278
+ if (!file || typeof file.relativePath !== "string" || typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.content !== "string" || sha2568(file.content) !== file.sha256) {
16608
17279
  throw new SessionApplyError(`Session snapshot previous file metadata is invalid: ${snapshotPath}`);
16609
17280
  }
16610
17281
  resolveSnapshotFilePath(file.relativePath, file.path, targetHome);
@@ -16651,7 +17322,7 @@ function readSessionRenderSnapshot(snapshotPath) {
16651
17322
  }
16652
17323
  function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previousManifestFiles, targetHome, snapshotPath) {
16653
17324
  assertNoNewerSessionSnapshot(snapshotPath, snapshot.createdAt, targetHome);
16654
- const manifestPath = resolve8(snapshot.manifestPath);
17325
+ const manifestPath = resolve10(snapshot.manifestPath);
16655
17326
  const manifestRelativePath = relative5(targetHome, manifestPath).replaceAll("\\", "/");
16656
17327
  resolveSnapshotFilePath(manifestRelativePath, snapshot.manifestPath, targetHome);
16657
17328
  const manifestSha256 = currentSessionFileHash(manifestPath, targetHome);
@@ -16660,7 +17331,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
16660
17331
  }
16661
17332
  let parsedManifest;
16662
17333
  try {
16663
- parsedManifest = JSON.parse(readFileSync9(manifestPath, "utf8"));
17334
+ parsedManifest = JSON.parse(readFileSync10(manifestPath, "utf8"));
16664
17335
  } catch {
16665
17336
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
16666
17337
  }
@@ -16668,7 +17339,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
16668
17339
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is invalid: ${snapshotPath}`);
16669
17340
  }
16670
17341
  const appliedManifest = parsedManifest;
16671
- if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve8(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
17342
+ if (appliedManifest.schema !== SESSION_RENDER_SCHEMA || appliedManifest.tool !== snapshot.tool || appliedManifest.profile !== snapshot.profile || typeof appliedManifest.targetHome !== "string" || resolve10(appliedManifest.targetHome) !== targetHome || appliedManifest.targetKind !== "session-home" && appliedManifest.targetKind !== "project-root" || !Array.isArray(appliedManifest.files)) {
16672
17343
  throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest does not match its snapshot: ${snapshotPath}`);
16673
17344
  }
16674
17345
  const afterFiles = [];
@@ -16753,16 +17424,16 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
16753
17424
  throw new SessionApplyError(`Pre-rollback legacy v1 snapshot has an invalid creation time: ${snapshotPath}`);
16754
17425
  }
16755
17426
  for (const entry of readdirSync3(dirname5(snapshotPath))) {
16756
- const candidatePath = resolve8(dirname5(snapshotPath), entry);
16757
- if (candidatePath === resolve8(snapshotPath) || !entry.endsWith(".json"))
17427
+ const candidatePath = resolve10(dirname5(snapshotPath), entry);
17428
+ if (candidatePath === resolve10(snapshotPath) || !entry.endsWith(".json"))
16758
17429
  continue;
16759
- const candidateStat = lstatSync3(candidatePath);
17430
+ const candidateStat = lstatSync4(candidatePath);
16760
17431
  if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
16761
17432
  continue;
16762
17433
  try {
16763
- const candidate = JSON.parse(readFileSync9(candidatePath, "utf8"));
17434
+ const candidate = JSON.parse(readFileSync10(candidatePath, "utf8"));
16764
17435
  const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
16765
- if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve8(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
17436
+ if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve10(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
16766
17437
  throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
16767
17438
  }
16768
17439
  } catch (error) {
@@ -16820,7 +17491,7 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
16820
17491
  return "create";
16821
17492
  }
16822
17493
  if (file.role === "manifest" && previousManifest) {
16823
- const previousManifestSha256 = sha2567(`${JSON.stringify(previousManifest, null, 2)}
17494
+ const previousManifestSha256 = sha2568(`${JSON.stringify(previousManifest, null, 2)}
16824
17495
  `);
16825
17496
  if (previousManifestSha256 !== file.sha256) {
16826
17497
  throw new SessionApplyError(`Cannot infer legacy v1 manifest action without a before-image: ${file.relativePath}`);
@@ -16831,15 +17502,15 @@ function inferLegacySnapshotAction(file, previousFiles, previousManifestFiles, p
16831
17502
  }
16832
17503
  function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
16833
17504
  const path = resolveManifestRelativePath(relativePath, targetHome);
16834
- if (resolve8(recordedPath) !== path) {
17505
+ if (resolve10(recordedPath) !== path) {
16835
17506
  throw new SessionApplyError(`Session snapshot file path mismatch for ${relativePath}`);
16836
17507
  }
16837
17508
  return path;
16838
17509
  }
16839
17510
  function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
16840
17511
  const target = resolvePlannedFilePath(plan, file, targetHome);
16841
- const previousContent = existsSync12(target) ? readFileSync9(target, "utf-8") : null;
16842
- const previousSha256 = previousContent === null ? null : sha2567(previousContent);
17512
+ const previousContent = existsSync12(target) ? readFileSync10(target, "utf-8") : null;
17513
+ const previousSha256 = previousContent === null ? null : sha2568(previousContent);
16843
17514
  const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
16844
17515
  const changed = previousContent !== file.content;
16845
17516
  if (previousContent !== null && !options.force && !previouslyManaged) {
@@ -16916,28 +17587,31 @@ function planFileResult(plan, file, targetHome, previousHashes, previousManifest
16916
17587
  function assertNotSilentManagedWipeout(plan, results) {
16917
17588
  if (plan.allowEmptySources)
16918
17589
  return;
16919
- const managedDir = plan.adapter.managedDir;
16920
- const underManagedDir = (relativePath) => relativePath === managedDir || relativePath.startsWith(`${managedDir}/`);
16921
- const staleDeletions = results.filter((result) => result.action === "delete" && underManagedDir(result.relativePath));
17590
+ const staleDeletions = results.filter((result) => result.action === "delete" && isPlanManagedFile(plan, result.relativePath, result.role));
16922
17591
  if (staleDeletions.length === 0)
16923
17592
  return;
16924
- const managedRetained = results.some((result) => result.action !== "delete" && underManagedDir(result.relativePath));
17593
+ const managedRetained = results.some((result) => result.action !== "delete" && isPlanManagedFile(plan, result.relativePath, result.role));
16925
17594
  if (managedRetained)
16926
17595
  return;
16927
- throw new SessionApplyError(`Session render plan for ${plan.tool} (${plan.adapter.mode}) would delete ${staleDeletions.length} ` + `previously managed file(s) under "${managedDir}" and create or update none there: ` + `${staleDeletions.map((result) => result.relativePath).join(", ")}. ` + "Pass --allow-empty-sources only for explicit empty renders.");
17596
+ throw new SessionApplyError(`Session render plan for ${plan.tool} (${plan.adapter.mode}) would delete ${staleDeletions.length} ` + "previously managed file(s) and create or update none: " + `${staleDeletions.map((result) => result.relativePath).join(", ")}. ` + "Pass --allow-empty-sources only for explicit empty renders.");
17597
+ }
17598
+ function isPlanManagedFile(plan, relativePath, role) {
17599
+ const managedDir = plan.adapter.managedDir;
17600
+ if (relativePath === managedDir || relativePath.startsWith(`${managedDir}/`))
17601
+ return true;
17602
+ return plan.tool === "claude" && role === "rule" && relativePath.startsWith("rules/");
16928
17603
  }
16929
17604
  function planStaleFileResults(plan, targetHome, previousManifest, currentRelativePaths, options) {
16930
17605
  if (!previousManifest)
16931
17606
  return [];
16932
- const managedPrefix = `${plan.adapter.managedDir}/`;
16933
- return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => file.relativePath === plan.adapter.managedDir || file.relativePath.startsWith(managedPrefix)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
17607
+ return previousManifest.files.filter((file) => !currentRelativePaths.has(file.relativePath)).filter((file) => isPlanManagedFile(plan, file.relativePath, file.role)).map((file) => planStaleFileResult(file, targetHome, options)).filter((result) => result !== null);
16934
17608
  }
16935
17609
  function planStaleFileResult(file, targetHome, options) {
16936
17610
  const target = resolveManifestRelativePath(file.relativePath, targetHome);
16937
17611
  if (!existsSync12(target))
16938
17612
  return null;
16939
- const previousContent = readFileSync9(target, "utf-8");
16940
- const previousSha256 = sha2567(previousContent);
17613
+ const previousContent = readFileSync10(target, "utf-8");
17614
+ const previousSha256 = sha2568(previousContent);
16941
17615
  if (!options.force && previousSha256 !== file.sha256) {
16942
17616
  return {
16943
17617
  path: target,
@@ -16981,19 +17655,19 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
16981
17655
  return previousHashes.get(file.relativePath) === previousSha256;
16982
17656
  }
16983
17657
  function resolvePlannedFilePath(plan, file, targetHome) {
16984
- const target = resolve8(targetHome, ...file.relativePath.split("/"));
17658
+ const target = resolve10(targetHome, ...file.relativePath.split("/"));
16985
17659
  const rel = relative5(targetHome, target);
16986
17660
  if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16987
17661
  throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
16988
17662
  }
16989
- if (resolve8(file.path) !== target) {
17663
+ if (resolve10(file.path) !== target) {
16990
17664
  throw new SessionApplyError(`Session file path mismatch for ${file.relativePath}: ${file.path}`);
16991
17665
  }
16992
17666
  assertNoSymlinkSegments2(targetHome, target);
16993
17667
  return target;
16994
17668
  }
16995
17669
  function resolveManifestRelativePath(relativePath, targetHome) {
16996
- const target = resolve8(targetHome, ...relativePath.split(/[\\/]+/));
17670
+ const target = resolve10(targetHome, ...relativePath.split(/[\\/]+/));
16997
17671
  const rel = relative5(targetHome, target);
16998
17672
  if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute4(rel)) {
16999
17673
  throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
@@ -17005,7 +17679,7 @@ function readPreviousManifest(path) {
17005
17679
  if (!existsSync12(path))
17006
17680
  return null;
17007
17681
  try {
17008
- const parsed = JSON.parse(readFileSync9(path, "utf-8"));
17682
+ const parsed = JSON.parse(readFileSync10(path, "utf-8"));
17009
17683
  if (parsed.schema !== SESSION_RENDER_SCHEMA)
17010
17684
  return null;
17011
17685
  if (!Array.isArray(parsed.files))
@@ -17046,11 +17720,11 @@ function currentSessionFileHash(path, targetHome) {
17046
17720
  assertNoSymlinkSegments2(targetHome, path);
17047
17721
  if (!existsSync12(path))
17048
17722
  return null;
17049
- const stat = lstatSync3(path);
17723
+ const stat = lstatSync4(path);
17050
17724
  if (stat.isSymbolicLink() || !stat.isFile()) {
17051
17725
  throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
17052
17726
  }
17053
- return sha2567(readFileSync9(path, "utf-8"));
17727
+ return sha2568(readFileSync10(path, "utf-8"));
17054
17728
  }
17055
17729
  function requiredPreviousHash(result) {
17056
17730
  if (result.previousSha256 === null) {
@@ -17060,12 +17734,12 @@ function requiredPreviousHash(result) {
17060
17734
  }
17061
17735
  function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
17062
17736
  const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync12(result.path)).map((result) => {
17063
- const content = readFileSync9(result.path, "utf-8");
17737
+ const content = readFileSync10(result.path, "utf-8");
17064
17738
  return {
17065
17739
  path: result.path,
17066
17740
  relativePath: result.relativePath,
17067
17741
  role: result.role,
17068
- sha256: sha2567(content),
17742
+ sha256: sha2568(content),
17069
17743
  content
17070
17744
  };
17071
17745
  });
@@ -17078,7 +17752,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17078
17752
  };
17079
17753
  }
17080
17754
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
17081
- const snapshotPath = resolve8(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
17755
+ const snapshotPath = resolve10(targetHome, ".hasna", "session-render-snapshots", `${timestamp}-${randomUUID7()}.json`);
17082
17756
  const afterFiles = results.map((result) => {
17083
17757
  if (result.action === "conflict") {
17084
17758
  throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
@@ -17126,12 +17800,12 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
17126
17800
  function assertSafeTargetHome(targetHome) {
17127
17801
  if (!isAbsolute4(targetHome))
17128
17802
  throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
17129
- const normalized = resolve8(targetHome);
17803
+ const normalized = resolve10(targetHome);
17130
17804
  if (normalized === parse4(normalized).root) {
17131
17805
  throw new SessionApplyError(`Session target home cannot be the filesystem root: ${targetHome}`);
17132
17806
  }
17133
17807
  assertNoSymlinkAncestors2(normalized);
17134
- if (existsSync12(normalized) && lstatSync3(normalized).isSymbolicLink()) {
17808
+ if (existsSync12(normalized) && lstatSync4(normalized).isSymbolicLink()) {
17135
17809
  throw new SessionApplyError(`Session target home cannot be a symlink: ${normalized}`);
17136
17810
  }
17137
17811
  return normalized;
@@ -17141,32 +17815,33 @@ function assertNoSymlinkSegments2(root, target) {
17141
17815
  const rel = relative5(root, target);
17142
17816
  let current = root;
17143
17817
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17144
- current = join13(current, segment);
17145
- if (existsSync12(current) && lstatSync3(current).isSymbolicLink()) {
17818
+ current = join15(current, segment);
17819
+ if (existsSync12(current) && lstatSync4(current).isSymbolicLink()) {
17146
17820
  throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
17147
17821
  }
17148
17822
  }
17149
17823
  }
17150
17824
  function assertNoSymlinkAncestors2(path) {
17151
- const normalized = resolve8(path);
17825
+ const normalized = resolve10(path);
17152
17826
  const parsed = parse4(normalized);
17153
17827
  let current = parsed.root;
17154
17828
  const rel = relative5(parsed.root, normalized);
17155
17829
  for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
17156
- current = join13(current, segment);
17830
+ current = join15(current, segment);
17157
17831
  if (!existsSync12(current))
17158
17832
  return;
17159
- if (lstatSync3(current).isSymbolicLink()) {
17833
+ if (lstatSync4(current).isSymbolicLink()) {
17160
17834
  throw new SessionApplyError(`Session apply path uses a symlink ancestor: ${current}`);
17161
17835
  }
17162
17836
  }
17163
17837
  }
17164
- function sha2567(content) {
17165
- return createHash7("sha256").update(content).digest("hex");
17838
+ function sha2568(content) {
17839
+ return createHash8("sha256").update(content).digest("hex");
17166
17840
  }
17167
17841
 
17168
17842
  // src/cli/index.tsx
17169
17843
  init_session_render();
17844
+ init_raw_store_root();
17170
17845
  init_asset_plan();
17171
17846
  init_instruction_graph();
17172
17847
 
@@ -17513,34 +18188,433 @@ async function ensureDangerousOperationGuardStandardConfig(store = resolveConfig
17513
18188
 
17514
18189
  // src/cli/index.tsx
17515
18190
  init_codewith_shared_todos_storage_standard();
18191
+
18192
+ // src/lib/managed-skill-runtimes.ts
18193
+ import { createHash as createHash9 } from "crypto";
18194
+ import { spawnSync } from "child_process";
18195
+ import {
18196
+ existsSync as existsSync13,
18197
+ lstatSync as lstatSync5,
18198
+ mkdirSync as mkdirSync7,
18199
+ readFileSync as readFileSync11,
18200
+ renameSync as renameSync2,
18201
+ rmSync as rmSync5,
18202
+ writeFileSync as writeFileSync4
18203
+ } from "fs";
18204
+ import { homedir as homedir8 } from "os";
18205
+ import { dirname as dirname6, join as join16, parse as parse5, relative as relative6, resolve as resolve11 } from "path";
18206
+ var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
18207
+ var INBOX_SKILL_MARKERS = [
18208
+ [".claude", "skills", "inbox", "SKILL.md"],
18209
+ [".codex", "skills", "inbox", "SKILL.md"],
18210
+ [".codewith", "skills", "inbox", "SKILL.md"],
18211
+ [".config", "opencode", "skills", "inbox", "SKILL.md"],
18212
+ [".cursor", "skills", "inbox", "SKILL.md"]
18213
+ ];
18214
+ var REQUIRED_WATCH_FLAGS = ["--from <agent>", "--all", "--full-content"];
18215
+ function sha2569(content) {
18216
+ return createHash9("sha256").update(content).digest("hex");
18217
+ }
18218
+ function lstatOrNull(path) {
18219
+ try {
18220
+ return lstatSync5(path);
18221
+ } catch {
18222
+ return null;
18223
+ }
18224
+ }
18225
+ function findSymlinkedAncestor(path) {
18226
+ const normalized = resolve11(path);
18227
+ const parsed = parse5(normalized);
18228
+ let current = parsed.root;
18229
+ const rel = relative6(parsed.root, normalized);
18230
+ for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
18231
+ current = join16(current, segment);
18232
+ if (!existsSync13(current))
18233
+ return null;
18234
+ if (lstatSync5(current).isSymbolicLink())
18235
+ return current;
18236
+ }
18237
+ return null;
18238
+ }
18239
+ function assertNoSymlinkAncestors3(path) {
18240
+ const found = findSymlinkedAncestor(path);
18241
+ if (found !== null) {
18242
+ throw new Error(`managed skill path uses a symlink ancestor: ${found}`);
18243
+ }
18244
+ }
18245
+ function packagedInboxSkillPath(explicitPath) {
18246
+ if (explicitPath)
18247
+ return explicitPath;
18248
+ const candidates = [
18249
+ join16(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
18250
+ join16(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
18251
+ join16(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
18252
+ ];
18253
+ const found = candidates.find((candidate) => existsSync13(candidate));
18254
+ if (!found) {
18255
+ throw new Error(`packaged inbox skill contract is missing (checked ${candidates.length} package-relative locations)`);
18256
+ }
18257
+ return found;
18258
+ }
18259
+ function readCanonicalSkill(explicitPath) {
18260
+ const assetPath = packagedInboxSkillPath(explicitPath);
18261
+ const stat = lstatOrNull(assetPath);
18262
+ if (!stat?.isFile()) {
18263
+ throw new Error("packaged inbox skill contract is not a regular file");
18264
+ }
18265
+ const content = readFileSync11(assetPath, "utf8");
18266
+ if (!content.includes("conversations watch --from <agent> --all")) {
18267
+ throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
18268
+ }
18269
+ if (!content.includes("There is no separate")) {
18270
+ throw new Error("packaged inbox skill contract does not retire the legacy executable");
18271
+ }
18272
+ return { content, sha256: sha2569(content) };
18273
+ }
18274
+ function runProbe(command, args) {
18275
+ const result = spawnSync(command, args, {
18276
+ encoding: "utf8",
18277
+ timeout: 5000,
18278
+ stdio: ["ignore", "pipe", "pipe"]
18279
+ });
18280
+ if (result.error || result.status !== 0) {
18281
+ return { ok: false, output: "" };
18282
+ }
18283
+ return {
18284
+ ok: true,
18285
+ output: `${result.stdout ?? ""}
18286
+ ${result.stderr ?? ""}`.trim()
18287
+ };
18288
+ }
18289
+ function parseVersion(output) {
18290
+ return output.match(/\b(\d+\.\d+\.\d+)\b/)?.[1] ?? null;
18291
+ }
18292
+ function compareVersions(left, right) {
18293
+ const a = left.split(".").map(Number);
18294
+ const b = right.split(".").map(Number);
18295
+ for (let i = 0;i < Math.max(a.length, b.length); i++) {
18296
+ const delta = (a[i] ?? 0) - (b[i] ?? 0);
18297
+ if (delta !== 0)
18298
+ return delta;
18299
+ }
18300
+ return 0;
18301
+ }
18302
+ function inspectSkillMarkers(homeDir2) {
18303
+ return INBOX_SKILL_MARKERS.map((parts) => join16(homeDir2, ...parts)).map((path) => {
18304
+ const stat = lstatOrNull(path);
18305
+ if (!stat)
18306
+ return null;
18307
+ if (!stat.isFile()) {
18308
+ return { path, content: null, mode: null, regular: false };
18309
+ }
18310
+ return {
18311
+ path,
18312
+ content: readFileSync11(path, "utf8"),
18313
+ mode: stat.mode & 511,
18314
+ regular: true
18315
+ };
18316
+ }).filter((snapshot) => snapshot !== null);
18317
+ }
18318
+ function inspectInbox(options) {
18319
+ const homeDir2 = options.homeDir ?? homedir8();
18320
+ const runtimeCommand = options.conversationsCommand ?? "conversations";
18321
+ const snapshots = inspectSkillMarkers(homeDir2);
18322
+ const skillPresent = snapshots.length > 0;
18323
+ let canonicalContent = null;
18324
+ let canonicalSha256 = null;
18325
+ let assetError = null;
18326
+ try {
18327
+ const canonical = readCanonicalSkill(options.assetPath);
18328
+ canonicalContent = canonical.content;
18329
+ canonicalSha256 = canonical.sha256;
18330
+ } catch (error) {
18331
+ assetError = error instanceof Error ? error.message : String(error);
18332
+ }
18333
+ const versionProbe = skillPresent ? runProbe(runtimeCommand, ["--version"]) : { ok: false, output: "" };
18334
+ const helpProbe = versionProbe.ok ? runProbe(runtimeCommand, ["watch", "--help"]) : { ok: false, output: "" };
18335
+ const runtimeVersion = versionProbe.ok ? parseVersion(versionProbe.output) : null;
18336
+ const supportsFrom = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[0]);
18337
+ const supportsAll = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[1]);
18338
+ const supportsFullContent = helpProbe.ok && helpProbe.output.includes(REQUIRED_WATCH_FLAGS[2]);
18339
+ const packageReady = versionProbe.ok && runtimeVersion !== null && compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && helpProbe.ok && supportsFrom && supportsAll && supportsFullContent;
18340
+ const heartbeatProbe = skillPresent && packageReady && options.agent ? runProbe(runtimeCommand, ["agents", "heartbeat", "--from", options.agent, "--json"]) : null;
18341
+ const hostedHeartbeat = heartbeatProbe === null ? "unverified" : heartbeatProbe.ok ? "passed" : "failed";
18342
+ const deliveryVerified = hostedHeartbeat === "passed" && options.deliveryVerified === true;
18343
+ const staleMarkers = canonicalContent === null ? snapshots.map((snapshot) => snapshot.path) : snapshots.filter((snapshot) => !snapshot.regular || snapshot.content !== canonicalContent).map((snapshot) => snapshot.path);
18344
+ let reason = "skill not installed";
18345
+ if (skillPresent) {
18346
+ const nonRegular = snapshots.some((snapshot) => !snapshot.regular);
18347
+ const symlinkAncestor = snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
18348
+ if (nonRegular)
18349
+ reason = "managed skill target is not a regular file";
18350
+ else if (symlinkAncestor)
18351
+ reason = `managed skill path uses a symlink ancestor: ${symlinkAncestor}`;
18352
+ else if (assetError)
18353
+ reason = assetError;
18354
+ else if (!versionProbe.ok)
18355
+ reason = "conversations command unavailable";
18356
+ else if (!runtimeVersion)
18357
+ reason = "conversations version is unreadable";
18358
+ else if (compareVersions(runtimeVersion, INBOX_CONVERSATIONS_MINIMUM_VERSION) < 0) {
18359
+ reason = `conversations ${runtimeVersion} is older than ${INBOX_CONVERSATIONS_MINIMUM_VERSION}`;
18360
+ } else if (!helpProbe.ok)
18361
+ reason = "conversations watch help is unavailable";
18362
+ else if (!supportsFrom || !supportsAll || !supportsFullContent) {
18363
+ const missing = [
18364
+ !supportsFrom ? "--from" : null,
18365
+ !supportsAll ? "--all" : null,
18366
+ !supportsFullContent ? "--full-content" : null
18367
+ ].filter((flag) => flag !== null);
18368
+ reason = `conversations watch is missing required flags: ${missing.join(", ")}`;
18369
+ } else if (staleMarkers.length > 0)
18370
+ reason = "skill contract stale";
18371
+ else if (hostedHeartbeat === "failed")
18372
+ reason = "hosted heartbeat failed; manual fallback required";
18373
+ else if (hostedHeartbeat === "unverified")
18374
+ reason = "hosted heartbeat unverified; manual fallback required";
18375
+ else if (!deliveryVerified)
18376
+ reason = "hosted heartbeat passed; channel and DM delivery verification required";
18377
+ else
18378
+ reason = "ready";
18379
+ }
18380
+ return {
18381
+ status: {
18382
+ skill: "inbox",
18383
+ runtime: "conversations watch",
18384
+ minimum_version: INBOX_CONVERSATIONS_MINIMUM_VERSION,
18385
+ skill_present: skillPresent,
18386
+ skill_markers: snapshots.map((snapshot) => snapshot.path),
18387
+ skill_contracts_current: snapshots.length - staleMarkers.length,
18388
+ stale_skill_markers: staleMarkers,
18389
+ expected_skill_sha256: canonicalSha256,
18390
+ runtime_command: runtimeCommand,
18391
+ runtime_present: versionProbe.ok,
18392
+ runtime_version: runtimeVersion,
18393
+ watch_supports_from: supportsFrom,
18394
+ watch_supports_all: supportsAll,
18395
+ watch_supports_full_content: supportsFullContent,
18396
+ hosted_heartbeat: hostedHeartbeat,
18397
+ delivery_verified: deliveryVerified,
18398
+ manual_fallback_ready: skillPresent && staleMarkers.length === 0 && packageReady,
18399
+ healthy: !skillPresent || reason === "ready",
18400
+ reason
18401
+ },
18402
+ canonicalContent,
18403
+ snapshots
18404
+ };
18405
+ }
18406
+ function inspectManagedSkillRuntimes(options = {}) {
18407
+ const runtime = inspectInbox(options).status;
18408
+ const installed = runtime.skill_present ? [runtime] : [];
18409
+ return {
18410
+ runtimes: [runtime],
18411
+ skills_present: installed.length,
18412
+ healthy: installed.filter((item) => item.healthy).length,
18413
+ missing: installed.filter((item) => !item.healthy).length
18414
+ };
18415
+ }
18416
+ function runtimeReadyForWrite(status) {
18417
+ return status.runtime_present && status.runtime_version !== null && compareVersions(status.runtime_version, INBOX_CONVERSATIONS_MINIMUM_VERSION) >= 0 && status.watch_supports_from && status.watch_supports_all && status.watch_supports_full_content;
18418
+ }
18419
+ function projectUpdatedStatus(status, contractCount) {
18420
+ const healthy = status.hosted_heartbeat === "passed" && status.delivery_verified;
18421
+ return {
18422
+ ...status,
18423
+ skill_contracts_current: contractCount,
18424
+ stale_skill_markers: [],
18425
+ manual_fallback_ready: true,
18426
+ healthy,
18427
+ reason: healthy ? "ready" : status.hosted_heartbeat === "failed" ? "hosted heartbeat failed; manual fallback required" : status.hosted_heartbeat === "unverified" ? "hosted heartbeat unverified; manual fallback required" : "hosted heartbeat passed; channel and DM delivery verification required"
18428
+ };
18429
+ }
18430
+ function cleanup(path) {
18431
+ rmSync5(path, { force: true });
18432
+ }
18433
+ function writeAtomic(path, content, mode) {
18434
+ assertNoSymlinkAncestors3(dirname6(path));
18435
+ const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
18436
+ try {
18437
+ mkdirSync7(dirname6(path), { recursive: true, mode: 493 });
18438
+ writeFileSync4(tempPath, content, { mode, flag: "wx" });
18439
+ renameSync2(tempPath, path);
18440
+ } finally {
18441
+ cleanup(tempPath);
18442
+ }
18443
+ }
18444
+ var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
18445
+ lstat: lstatOrNull,
18446
+ read: (path) => readFileSync11(path, "utf8"),
18447
+ write: writeAtomic
18448
+ };
18449
+ function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
18450
+ const written = [];
18451
+ try {
18452
+ for (const snapshot of snapshots) {
18453
+ const currentStat = fileOperations.lstat(snapshot.path);
18454
+ if (!currentStat?.isFile() || fileOperations.read(snapshot.path) !== snapshot.content) {
18455
+ throw new Error("managed skill changed after inspection; refusing a stale write");
18456
+ }
18457
+ fileOperations.write(snapshot.path, canonicalContent, snapshot.mode);
18458
+ written.push(snapshot);
18459
+ }
18460
+ return { ok: true, error: null, rollback_conflicts: [] };
18461
+ } catch (error) {
18462
+ const rollbackConflicts = [];
18463
+ for (const snapshot of written.reverse()) {
18464
+ const currentStat = fileOperations.lstat(snapshot.path);
18465
+ if (!currentStat?.isFile()) {
18466
+ rollbackConflicts.push(`${snapshot.path}: no longer a regular file`);
18467
+ continue;
18468
+ }
18469
+ let currentContent;
18470
+ try {
18471
+ currentContent = fileOperations.read(snapshot.path);
18472
+ } catch {
18473
+ rollbackConflicts.push(`${snapshot.path}: could not read the current file`);
18474
+ continue;
18475
+ }
18476
+ if (currentContent !== canonicalContent) {
18477
+ rollbackConflicts.push(`${snapshot.path}: changed after this reconciliation wrote it`);
18478
+ continue;
18479
+ }
18480
+ try {
18481
+ fileOperations.write(snapshot.path, snapshot.content, snapshot.mode);
18482
+ } catch {
18483
+ rollbackConflicts.push(`${snapshot.path}: still owned but could not be restored`);
18484
+ }
18485
+ }
18486
+ return {
18487
+ ok: false,
18488
+ error: error instanceof Error ? error.message : String(error),
18489
+ rollback_conflicts: rollbackConflicts
18490
+ };
18491
+ }
18492
+ }
18493
+ async function reconcileManagedSkillRuntimes(options = {}) {
18494
+ const dryRun = options.dryRun ?? false;
18495
+ const before = inspectInbox(options);
18496
+ const status = before.status;
18497
+ if (!status.skill_present) {
18498
+ return {
18499
+ runtimes: [{ ...status, action: "skipped", dry_run: dryRun, skill_contracts_changed: 0 }],
18500
+ changed: 0,
18501
+ failed: 0,
18502
+ dry_run: dryRun
18503
+ };
18504
+ }
18505
+ if (before.snapshots.some((snapshot) => !snapshot.regular)) {
18506
+ return {
18507
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18508
+ changed: 0,
18509
+ failed: 1,
18510
+ dry_run: dryRun
18511
+ };
18512
+ }
18513
+ const symlinkedAncestor = before.snapshots.map((snapshot) => snapshot.path).map((path) => findSymlinkedAncestor(dirname6(path))).find((found) => found !== null);
18514
+ if (symlinkedAncestor) {
18515
+ return {
18516
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18517
+ changed: 0,
18518
+ failed: 1,
18519
+ dry_run: dryRun
18520
+ };
18521
+ }
18522
+ if (!before.canonicalContent || !runtimeReadyForWrite(status)) {
18523
+ return {
18524
+ runtimes: [{ ...status, action: "failed", dry_run: dryRun, skill_contracts_changed: 0 }],
18525
+ changed: 0,
18526
+ failed: 1,
18527
+ dry_run: dryRun
18528
+ };
18529
+ }
18530
+ const staleSnapshots = before.snapshots.filter((snapshot) => snapshot.content !== before.canonicalContent);
18531
+ if (staleSnapshots.length === 0) {
18532
+ return {
18533
+ runtimes: [{ ...status, action: "unchanged", dry_run: dryRun, skill_contracts_changed: 0 }],
18534
+ changed: 0,
18535
+ failed: 0,
18536
+ dry_run: dryRun
18537
+ };
18538
+ }
18539
+ if (dryRun) {
18540
+ const projected = projectUpdatedStatus(status, before.snapshots.length);
18541
+ return {
18542
+ runtimes: [{
18543
+ ...projected,
18544
+ action: "update",
18545
+ dry_run: true,
18546
+ skill_contracts_changed: staleSnapshots.length
18547
+ }],
18548
+ changed: 1,
18549
+ failed: 0,
18550
+ dry_run: true
18551
+ };
18552
+ }
18553
+ const transaction = writeSkillContractsTransactional(staleSnapshots.map((snapshot) => ({
18554
+ path: snapshot.path,
18555
+ content: snapshot.content,
18556
+ mode: snapshot.mode ?? 420
18557
+ })), before.canonicalContent);
18558
+ if (!transaction.ok) {
18559
+ const rollbackConflictReason = transaction.rollback_conflicts.length > 0 ? `; rollback conflicts: ${transaction.rollback_conflicts.join("; ")}` : "";
18560
+ const reason = `${transaction.error ?? "managed skill reconciliation failed"}${rollbackConflictReason}`;
18561
+ return {
18562
+ runtimes: [{
18563
+ ...status,
18564
+ action: "failed",
18565
+ dry_run: false,
18566
+ skill_contracts_changed: 0,
18567
+ reason
18568
+ }],
18569
+ changed: 0,
18570
+ failed: 1,
18571
+ dry_run: false
18572
+ };
18573
+ }
18574
+ const after = inspectInbox(options).status;
18575
+ const accepted = after.healthy || after.manual_fallback_ready;
18576
+ return {
18577
+ runtimes: [{
18578
+ ...after,
18579
+ action: accepted ? "update" : "failed",
18580
+ dry_run: false,
18581
+ skill_contracts_changed: accepted ? staleSnapshots.length : 0
18582
+ }],
18583
+ changed: accepted ? 1 : 0,
18584
+ failed: accepted ? 0 : 1,
18585
+ dry_run: false
18586
+ };
18587
+ }
18588
+
18589
+ // src/cli/index.tsx
17516
18590
  init_project_context();
17517
18591
 
17518
18592
  // src/status.ts
17519
18593
  init_config_store();
17520
18594
  init_apply();
17521
18595
  init_config_agents();
17522
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
18596
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
17523
18597
 
17524
18598
  // src/lib/package-version.ts
17525
- import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
17526
- import { dirname as dirname6, join as join14 } from "path";
18599
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
18600
+ import { dirname as dirname7, join as join17 } from "path";
17527
18601
  import { fileURLToPath } from "url";
17528
18602
  var cached = null;
17529
18603
  function getPackageVersion() {
17530
18604
  if (cached)
17531
18605
  return cached;
17532
18606
  try {
17533
- let dir = dirname6(fileURLToPath(import.meta.url));
18607
+ let dir = dirname7(fileURLToPath(import.meta.url));
17534
18608
  for (let i = 0;i < 8; i++) {
17535
- const pkgPath = join14(dir, "package.json");
17536
- if (existsSync13(pkgPath)) {
17537
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
18609
+ const pkgPath = join17(dir, "package.json");
18610
+ if (existsSync14(pkgPath)) {
18611
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
17538
18612
  if (pkg.name === "@hasna/instructions" && pkg.version) {
17539
18613
  cached = pkg.version;
17540
18614
  return cached;
17541
18615
  }
17542
18616
  }
17543
- const parent = dirname6(dir);
18617
+ const parent = dirname7(dir);
17544
18618
  if (parent === dir)
17545
18619
  break;
17546
18620
  dir = parent;
@@ -17573,7 +18647,7 @@ function countBy(items, getValue) {
17573
18647
  }
17574
18648
  return counts;
17575
18649
  }
17576
- async function getConfigsStatus(store = resolveConfigStore()) {
18650
+ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
17577
18651
  let databaseReachable = true;
17578
18652
  let configs = [];
17579
18653
  let categoryStats = { total: 0 };
@@ -17597,11 +18671,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17597
18671
  continue;
17598
18672
  knownTargets += 1;
17599
18673
  const targetPath = expandPath(config.target_path);
17600
- if (!existsSync14(targetPath)) {
18674
+ if (!existsSync15(targetPath)) {
17601
18675
  missingTargets += 1;
17602
18676
  continue;
17603
18677
  }
17604
- const disk = readFileSync11(targetPath, "utf-8");
18678
+ const disk = readFileSync13(targetPath, "utf-8");
17605
18679
  const { content: redactedDisk } = redactContent(disk, config.format);
17606
18680
  if (redactedDisk !== config.content) {
17607
18681
  driftedTargets += 1;
@@ -17627,7 +18701,11 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17627
18701
  }
17628
18702
  }
17629
18703
  const byCategory = Object.fromEntries(Object.entries(categoryStats).filter(([key]) => key !== "total"));
17630
- const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 ? "ok" : "warn";
18704
+ const managedSkillRuntimes = inspectManagedSkillRuntimes({
18705
+ homeDir: options.homeDir,
18706
+ conversationsCommand: options.conversationsCommand
18707
+ });
18708
+ const status = databaseReachable && driftedTargets === 0 && missingTargets === 0 && unredactedSecretFindings === 0 && retiredAgentRows === 0 && managedSkillRuntimes.missing === 0 ? "ok" : "warn";
17631
18709
  return {
17632
18710
  service: "configs",
17633
18711
  schemaVersion: "1.0",
@@ -17657,7 +18735,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17657
18735
  profileLinks,
17658
18736
  machines,
17659
18737
  snapshots,
17660
- knownTargets
18738
+ knownTargets,
18739
+ managedSkillRuntimes: {
18740
+ skillsPresent: managedSkillRuntimes.skills_present,
18741
+ healthy: managedSkillRuntimes.healthy,
18742
+ missing: managedSkillRuntimes.missing
18743
+ }
17661
18744
  },
17662
18745
  health: {
17663
18746
  status,
@@ -17666,10 +18749,12 @@ async function getConfigsStatus(store = resolveConfigStore()) {
17666
18749
  missingTargets,
17667
18750
  unredactedSecretFindings,
17668
18751
  retiredAgentRows,
18752
+ missingManagedSkillRuntimes: managedSkillRuntimes.missing,
17669
18753
  hasDrift: driftedTargets > 0,
17670
18754
  hasMissingTargets: missingTargets > 0,
17671
18755
  hasUnredactedSecrets: unredactedSecretFindings > 0,
17672
- hasRetiredAgentRows: retiredAgentRows > 0
18756
+ hasRetiredAgentRows: retiredAgentRows > 0,
18757
+ hasMissingManagedSkillRuntimes: managedSkillRuntimes.missing > 0
17673
18758
  },
17674
18759
  safety: {
17675
18760
  includesConfigValues: false,
@@ -17711,6 +18796,17 @@ function printLine(text = "") {
17711
18796
  function printJson(value) {
17712
18797
  printLine(JSON.stringify(value, null, 2));
17713
18798
  }
18799
+ function printManagedSkillRuntimeReport(report) {
18800
+ for (const runtime of report.runtimes) {
18801
+ if (!runtime.skill_present)
18802
+ continue;
18803
+ const prefix = runtime.action === "failed" ? chalk.red("[failed]") : runtime.dry_run ? chalk.yellow("[dry-run]") : runtime.manual_fallback_ready && !runtime.healthy ? chalk.yellow("[degraded]") : runtime.action === "unchanged" ? chalk.dim("=") : chalk.green("\u2713");
18804
+ console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
18805
+ if (runtime.action === "update") {
18806
+ console.log(chalk.dim(` skill contracts: ${runtime.skill_contracts_changed}`));
18807
+ }
18808
+ }
18809
+ }
17714
18810
  function fmtConfig(c, format) {
17715
18811
  if (format === "json")
17716
18812
  return JSON.stringify(c, null, 2);
@@ -17777,7 +18873,7 @@ function parseSessionSource(value, order) {
17777
18873
  if (!path)
17778
18874
  throw new Error(`Invalid --source "${value}" (expected path or id=path)`);
17779
18875
  const absPath = resolveSessionPath(path);
17780
- if (!existsSync16(absPath))
18876
+ if (!existsSync17(absPath))
17781
18877
  throw new Error(`Instruction source file not found: ${absPath}`);
17782
18878
  const content = readSessionInstructionSourceFile(absPath);
17783
18879
  const source = sourceFromFilePath(absPath, content, order);
@@ -17821,7 +18917,7 @@ function sessionSourceReplacements(values) {
17821
18917
  return replacements;
17822
18918
  }
17823
18919
  function readSessionInstructionSourceFile(path) {
17824
- const stat = lstatSync5(path);
18920
+ const stat = lstatSync7(path);
17825
18921
  if (stat.isSymbolicLink()) {
17826
18922
  throw new Error("SESSION_SOURCE_SYMLINK_REJECTED: instruction source file must be a regular non-symlink file");
17827
18923
  }
@@ -17831,7 +18927,7 @@ function readSessionInstructionSourceFile(path) {
17831
18927
  if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
17832
18928
  throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
17833
18929
  }
17834
- return readFileSync13(path, "utf-8");
18930
+ return readFileSync15(path, "utf-8");
17835
18931
  }
17836
18932
  function parseLayeredReference(value) {
17837
18933
  const trimmed = value.trim();
@@ -17858,9 +18954,9 @@ async function collectSessionSources(opts, tool, store) {
17858
18954
  }
17859
18955
  for (const value of opts.identityExport ?? []) {
17860
18956
  const path = resolveSessionPath(value);
17861
- if (!existsSync16(path))
18957
+ if (!existsSync17(path))
17862
18958
  throw new Error(`Identity instruction export not found: ${path}`);
17863
- const parsed = JSON.parse(readFileSync13(path, "utf-8"));
18959
+ const parsed = JSON.parse(readFileSync15(path, "utf-8"));
17864
18960
  sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
17865
18961
  }
17866
18962
  return sources.map((source) => {
@@ -17981,19 +19077,19 @@ function readProjectContextBundleOption(value, allowMissing = false) {
17981
19077
  if (value === "-")
17982
19078
  return { json: readBoundedProjectContextStdin() };
17983
19079
  const path = resolveSessionPath(value);
17984
- if (!existsSync16(path)) {
19080
+ if (!existsSync17(path)) {
17985
19081
  if (allowMissing)
17986
19082
  return {};
17987
19083
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_MISSING", `bundle file not found: ${path}`);
17988
19084
  }
17989
- const stat = lstatSync5(path);
19085
+ const stat = lstatSync7(path);
17990
19086
  if (stat.isSymbolicLink() || !stat.isFile()) {
17991
19087
  throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", "bundle input must be a regular non-symlink file");
17992
19088
  }
17993
19089
  if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
17994
19090
  throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
17995
19091
  }
17996
- return { json: readFileSync13(path, "utf8"), sourcePath: path };
19092
+ return { json: readFileSync15(path, "utf8"), sourcePath: path };
17997
19093
  }
17998
19094
  function readBoundedProjectContextStdin() {
17999
19095
  const chunks = [];
@@ -18152,15 +19248,15 @@ program.command("tag <id>").description("Add or remove tags on a stored config (
18152
19248
  console.log(chalk.green("\u2713") + ` Tags on ${chalk.bold(updated.name)} ${chalk.dim(`(${updated.slug})`)}: ${nextTags.join(", ") || chalk.dim("(none)")}`);
18153
19249
  });
18154
19250
  program.command("add <path>").description("Ingest a file into the config DB").option("-n, --name <name>", "config name (defaults to filename)").option("-c, --category <cat>", "category override").option("-a, --agent <agent>", "agent override").option("-k, --kind <kind>", "kind: file|reference", "file").option("--template", "mark as template (has {{VAR}} placeholders)").option("--update", "if a config already owns this path, update that row in place instead of refusing").action(async (filePath, opts) => {
18155
- const abs = resolve10(filePath);
18156
- if (!existsSync16(abs)) {
19251
+ const abs = resolve13(filePath);
19252
+ if (!existsSync17(abs)) {
18157
19253
  console.error(chalk.red(`File not found: ${abs}`));
18158
19254
  process.exit(1);
18159
19255
  }
18160
- const rawContent = readFileSync13(abs, "utf-8");
19256
+ const rawContent = readFileSync15(abs, "utf-8");
18161
19257
  const fmt = detectFormat(abs);
18162
19258
  const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
18163
- const targetPath = abs.startsWith(homedir8()) ? abs.replace(homedir8(), "~") : abs;
19259
+ const targetPath = abs.startsWith(homedir10()) ? abs.replace(homedir10(), "~") : abs;
18164
19260
  const name = opts.name || filePath.split("/").pop();
18165
19261
  const store = resolveConfigStore();
18166
19262
  const allConfigs = await store.listConfigs();
@@ -18335,7 +19431,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
18335
19431
  for (const entry of entries) {
18336
19432
  if (!entry.isDirectory())
18337
19433
  continue;
18338
- const projDir = join16(absDir, entry.name);
19434
+ const projDir = join19(absDir, entry.name);
18339
19435
  const hasAgentConfig = [
18340
19436
  "CLAUDE.md",
18341
19437
  ".mcp.json",
@@ -18348,7 +19444,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
18348
19444
  ".aicopilot",
18349
19445
  ".cursor",
18350
19446
  ".agents"
18351
- ].some((marker) => existsSync16(join16(projDir, marker)));
19447
+ ].some((marker) => existsSync17(join19(projDir, marker)));
18352
19448
  if (!hasAgentConfig)
18353
19449
  continue;
18354
19450
  const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
@@ -18399,10 +19495,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
18399
19495
  });
18400
19496
  program.command("whoami").description("Show setup summary").action(async () => {
18401
19497
  const store = resolveConfigStore();
18402
- const dbPath = isCloudMode() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1 (self_hosted)` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join16(homedir8(), ".hasna", "instructions", "instructions.db");
19498
+ const dbPath = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
18403
19499
  const stats = await store.getConfigStats();
18404
19500
  console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
18405
- console.log(chalk.cyan(isCloudMode() ? "API:" : "DB:") + " " + dbPath);
19501
+ console.log(chalk.cyan(isApiTransport() ? "API:" : "DB:") + " " + dbPath);
18406
19502
  console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
18407
19503
  console.log();
18408
19504
  console.log(chalk.bold("By category:"));
@@ -18622,7 +19718,7 @@ profileCmd.command("binding <profile> <config>").description("Set a config's sch
18622
19718
  process.exit(1);
18623
19719
  }
18624
19720
  });
18625
- profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
19721
+ profileCmd.command("apply [id]").description("Apply all configs in a profile to disk").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--auto", "resolve the matching profile for the current machine").option("--hostname <hostname>", "override detected hostname for auto resolution").option("--os <os>", "override detected OS for auto resolution").option("--arch <arch>", "override detected arch for auto resolution").action(async (id, opts) => {
18626
19722
  try {
18627
19723
  const store = resolveConfigStore();
18628
19724
  const { machine, profile } = await getMachineProfileContext(opts, store);
@@ -18659,6 +19755,14 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
18659
19755
  }
18660
19756
  if (report.failures.length > 0)
18661
19757
  process.exitCode = 1;
19758
+ const runtimeReport = await reconcileManagedSkillRuntimes({
19759
+ dryRun: opts.dryRun,
19760
+ agent: opts.from,
19761
+ deliveryVerified: opts.deliveryVerified
19762
+ });
19763
+ printManagedSkillRuntimeReport(runtimeReport);
19764
+ if (runtimeReport.failed > 0)
19765
+ process.exitCode = 1;
18662
19766
  console.log(chalk.dim(`
18663
19767
  ${changed}/${results.length} changed (${selected.slug} on ${machine.hostname} ${machine.os_family}/${machine.arch})`));
18664
19768
  } catch (e) {
@@ -19146,14 +20250,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
19146
20250
  } else if (target === "codex") {
19147
20251
  const { appendFileSync, existsSync: ex } = await import("fs");
19148
20252
  const { join: j } = await import("path");
19149
- const configPath = j(homedir8(), ".codex", "config.toml");
20253
+ const configPath = j(homedir10(), ".codex", "config.toml");
19150
20254
  const block = `
19151
20255
  [mcp_servers.configs]
19152
20256
  command = "${mcpBinary}"
19153
20257
  args = []
19154
20258
  `;
19155
20259
  if (ex(configPath)) {
19156
- const content = readFileSync13(configPath, "utf-8");
20260
+ const content = readFileSync15(configPath, "utf-8");
19157
20261
  if (content.includes("[mcp_servers.configs]")) {
19158
20262
  console.log(chalk.dim("= Already installed in Codex"));
19159
20263
  continue;
@@ -19164,7 +20268,7 @@ args = []
19164
20268
  } else if (target === "antigravity") {
19165
20269
  const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
19166
20270
  const { dirname: dn, join: j } = await import("path");
19167
- const configPath = j(homedir8(), ".gemini", "config", "mcp_config.json");
20271
+ const configPath = j(homedir10(), ".gemini", "config", "mcp_config.json");
19168
20272
  let settings = {};
19169
20273
  if (ex(configPath)) {
19170
20274
  try {
@@ -19248,9 +20352,9 @@ DB stats:`));
19248
20352
  if (count > 0)
19249
20353
  console.log(` ${key.padEnd(18)} ${count}`);
19250
20354
  }
19251
- const location = isCloudMode() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1 (self_hosted)` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join16(homedir8(), ".hasna", "instructions", "instructions.db");
20355
+ const location = isApiTransport() ? `${process.env["HASNA_INSTRUCTIONS_API_URL"]}/v1` : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join19(getRawStoreRoot(), "instructions.db");
19252
20356
  console.log(chalk.dim(`
19253
- ${isCloudMode() ? "API" : "DB"}: ${location}`));
20357
+ ${isApiTransport() ? "API" : "DB"}: ${location}`));
19254
20358
  });
19255
20359
  program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
19256
20360
  const status = await getConfigsStatus(resolveConfigStore());
@@ -19266,14 +20370,53 @@ program.command("status").description("Health check: total configs, drift from d
19266
20370
  console.log(chalk.cyan("Missing:") + ` ${status.health.missingTargets === 0 ? chalk.green("0") : chalk.yellow(String(status.health.missingTargets))} (file not on disk)`);
19267
20371
  console.log(chalk.cyan("Secrets:") + ` ${status.health.unredactedSecretFindings === 0 ? chalk.green("0 \u2713") : chalk.red(String(status.health.unredactedSecretFindings) + " \u26A0")} unredacted`);
19268
20372
  console.log(chalk.cyan("Retired agents:") + ` ${status.health.retiredAgentRows === 0 ? chalk.green("0") : chalk.yellow(String(status.health.retiredAgentRows))} row(s)`);
20373
+ console.log(chalk.cyan("Skill runtimes:") + ` ${status.health.missingManagedSkillRuntimes === 0 ? chalk.green(`${status.counts.managedSkillRuntimes.healthy} ready`) : chalk.yellow(`${status.health.missingManagedSkillRuntimes} missing`)} (${status.counts.managedSkillRuntimes.skillsPresent} managed skill(s) present)`);
19269
20374
  console.log(chalk.cyan("Templates:") + ` ${status.counts.configs.templates} (with {{VAR}} placeholders)`);
19270
20375
  });
20376
+ var managedSkillsCmd = program.command("managed-skills").description("Inspect or reconcile package-owned runtime contracts for installed managed skills");
20377
+ managedSkillsCmd.command("status").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the full local runtime status as JSON").action((opts) => {
20378
+ const report = inspectManagedSkillRuntimes({
20379
+ agent: opts.from,
20380
+ deliveryVerified: opts.deliveryVerified
20381
+ });
20382
+ if (opts.json) {
20383
+ printJson(report);
20384
+ if (report.missing > 0)
20385
+ process.exitCode = 1;
20386
+ return;
20387
+ }
20388
+ if (report.skills_present === 0) {
20389
+ console.log(chalk.dim("No managed skills with package-owned runtime contracts are installed."));
20390
+ return;
20391
+ }
20392
+ for (const runtime of report.runtimes) {
20393
+ if (!runtime.skill_present)
20394
+ continue;
20395
+ const prefix = runtime.healthy ? chalk.green("\u2713") : chalk.yellow("!");
20396
+ console.log(`${prefix} ${runtime.skill} via ${runtime.runtime} \u2014 ${runtime.reason}`);
20397
+ }
20398
+ if (report.missing > 0)
20399
+ process.exitCode = 1;
20400
+ });
20401
+ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--json", "output the reconcile report as JSON").action(async (opts) => {
20402
+ const report = await reconcileManagedSkillRuntimes({
20403
+ dryRun: opts.dryRun,
20404
+ agent: opts.from,
20405
+ deliveryVerified: opts.deliveryVerified
20406
+ });
20407
+ if (opts.json)
20408
+ printJson(report);
20409
+ else
20410
+ printManagedSkillRuntimeReport(report);
20411
+ if (report.failed > 0)
20412
+ process.exitCode = 1;
20413
+ });
19271
20414
  program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
19272
20415
  const { mkdirSync: mk } = await import("fs");
19273
- const backupDir = join16(homedir8(), ".hasna", "instructions", "backups");
20416
+ const backupDir = join19(getRawStoreRoot(), "backups");
19274
20417
  mk(backupDir, { recursive: true });
19275
20418
  const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
19276
- const outPath = join16(backupDir, `configs-${ts}.tar.gz`);
20419
+ const outPath = join19(backupDir, `configs-${ts}.tar.gz`);
19277
20420
  const result = await exportConfigs(outPath, { store: resolveConfigStore() });
19278
20421
  const { statSync: st } = await import("fs");
19279
20422
  const size = st(outPath).size;
@@ -19291,7 +20434,7 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
19291
20434
  const store = resolveConfigStore();
19292
20435
  let issues = 0;
19293
20436
  const pass = (msg) => console.log(chalk.green(" \u2713 ") + msg);
19294
- const fail = (msg) => {
20437
+ const fail2 = (msg) => {
19295
20438
  issues++;
19296
20439
  console.log(chalk.red(" \u2717 ") + msg);
19297
20440
  };
@@ -19301,9 +20444,9 @@ program.command("doctor").description("Validate configs: syntax, permissions, mi
19301
20444
  console.log(chalk.cyan("Known files on disk:"));
19302
20445
  for (const k of KNOWN_CONFIGS) {
19303
20446
  if (k.rulesDir) {
19304
- existsSync16(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail(`${k.rulesDir}/ not found`);
20447
+ existsSync17(expandPath(k.rulesDir)) ? pass(`${k.rulesDir}/ exists`) : k.optional ? skip(`${k.rulesDir}/ (optional)`) : fail2(`${k.rulesDir}/ not found`);
19305
20448
  } else {
19306
- existsSync16(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail(`${k.path} not found`);
20449
+ existsSync17(expandPath(k.path)) ? pass(k.path) : k.optional ? skip(`${k.path} (optional)`) : fail2(`${k.path} not found`);
19307
20450
  }
19308
20451
  }
19309
20452
  const allConfigs = await store.listConfigs();
@@ -19316,7 +20459,7 @@ Stored configs (${allConfigs.length}):`));
19316
20459
  JSON.parse(c.content);
19317
20460
  validCount++;
19318
20461
  } catch {
19319
- fail(`${c.slug}: invalid JSON`);
20462
+ fail2(`${c.slug}: invalid JSON`);
19320
20463
  }
19321
20464
  } else {
19322
20465
  validCount++;
@@ -19328,13 +20471,13 @@ Stored configs (${allConfigs.length}):`));
19328
20471
  const found = scanSecrets(c.content, c.format);
19329
20472
  secretCount += found.length;
19330
20473
  }
19331
- secretCount === 0 ? pass("No unredacted secrets") : fail(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
20474
+ secretCount === 0 ? pass("No unredacted secrets") : fail2(`${secretCount} unredacted secret(s) \u2014 run \`configs scan --fix\``);
19332
20475
  const duplicateTargets = findDuplicateTargetPathGroups(allConfigs);
19333
20476
  if (duplicateTargets.length === 0) {
19334
20477
  pass("No target path is claimed by more than one config");
19335
20478
  } else {
19336
20479
  const rowCount = duplicateTargets.reduce((total, group) => total + group.configs.length, 0);
19337
- fail(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
20480
+ fail2(`${duplicateTargets.length} target path(s) claimed by more than one config (${rowCount} rows) \u2014 apply order between them is undefined`);
19338
20481
  for (const group of duplicateTargets) {
19339
20482
  console.log(chalk.yellow(` ${group.target_path}`));
19340
20483
  for (const c of group.configs) {
@@ -19348,7 +20491,7 @@ Stored configs (${allConfigs.length}):`));
19348
20491
  pass("No reference config name is claimed by more than one row");
19349
20492
  } else {
19350
20493
  const rowCount = duplicateReferenceNames.reduce((total, group) => total + group.configs.length, 0);
19351
- fail(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
20494
+ fail2(`${duplicateReferenceNames.length} reference name(s) claimed by more than one row (${rowCount} rows) \u2014 only one is live in the next render`);
19352
20495
  for (const group of duplicateReferenceNames) {
19353
20496
  console.log(chalk.yellow(` ${group.name}`));
19354
20497
  for (const c of group.configs) {
@@ -19456,16 +20599,16 @@ program.command("watch").description("Watch known config files for changes and a
19456
20599
  for (const k of KNOWN_CONFIGS) {
19457
20600
  if (k.rulesDir) {
19458
20601
  const absDir = expandPath2(k.rulesDir);
19459
- if (!existsSync16(absDir))
20602
+ if (!existsSync17(absDir))
19460
20603
  continue;
19461
20604
  const { readdirSync: readdirSync5 } = await import("fs");
19462
20605
  for (const f of readdirSync5(absDir).filter((f2) => f2.endsWith(".md"))) {
19463
- const abs = join16(absDir, f);
20606
+ const abs = join19(absDir, f);
19464
20607
  mtimes.set(abs, st(abs).mtimeMs);
19465
20608
  }
19466
20609
  } else {
19467
20610
  const abs = expandPath2(k.path);
19468
- if (existsSync16(abs))
20611
+ if (existsSync17(abs))
19469
20612
  mtimes.set(abs, st(abs).mtimeMs);
19470
20613
  }
19471
20614
  }
@@ -19473,7 +20616,7 @@ program.command("watch").description("Watch known config files for changes and a
19473
20616
  const tick = async () => {
19474
20617
  let changed = 0;
19475
20618
  for (const [abs, oldMtime] of mtimes) {
19476
- if (!existsSync16(abs))
20619
+ if (!existsSync17(abs))
19477
20620
  continue;
19478
20621
  const newMtime = st(abs).mtimeMs;
19479
20622
  if (newMtime !== oldMtime) {
@@ -19485,10 +20628,10 @@ program.command("watch").description("Watch known config files for changes and a
19485
20628
  for (const k of KNOWN_CONFIGS) {
19486
20629
  if (k.rulesDir) {
19487
20630
  const absDir = expandPath2(k.rulesDir);
19488
- if (!existsSync16(absDir))
20631
+ if (!existsSync17(absDir))
19489
20632
  continue;
19490
20633
  for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
19491
- const abs = join16(absDir, f);
20634
+ const abs = join19(absDir, f);
19492
20635
  if (!mtimes.has(abs)) {
19493
20636
  mtimes.set(abs, st(abs).mtimeMs);
19494
20637
  changed++;
@@ -19496,7 +20639,7 @@ program.command("watch").description("Watch known config files for changes and a
19496
20639
  }
19497
20640
  } else {
19498
20641
  const abs = expandPath2(k.path);
19499
- if (existsSync16(abs) && !mtimes.has(abs)) {
20642
+ if (existsSync17(abs) && !mtimes.has(abs)) {
19500
20643
  mtimes.set(abs, st(abs).mtimeMs);
19501
20644
  changed++;
19502
20645
  }
@@ -19524,11 +20667,11 @@ program.command("report").description("Summary of stored configs, drift, and eco
19524
20667
  if (!c.target_path)
19525
20668
  continue;
19526
20669
  const abs = expandPath(c.target_path);
19527
- if (!existsSync16(abs)) {
20670
+ if (!existsSync17(abs)) {
19528
20671
  missing++;
19529
20672
  continue;
19530
20673
  }
19531
- const disk = readFileSync13(abs, "utf-8");
20674
+ const disk = readFileSync15(abs, "utf-8");
19532
20675
  const { content: redactedDisk } = redactContent(disk, c.format);
19533
20676
  if (redactedDisk !== c.content)
19534
20677
  drifted++;
@@ -19595,7 +20738,7 @@ program.command("clean").description("Remove configs from DB whose target files
19595
20738
  if (!c.target_path)
19596
20739
  continue;
19597
20740
  const abs = expandPath(c.target_path);
19598
- if (!existsSync16(abs)) {
20741
+ if (!existsSync17(abs)) {
19599
20742
  if (printed < maxPrinted) {
19600
20743
  if (opts.dryRun) {
19601
20744
  console.log(chalk.yellow(" would remove:") + ` ${c.slug} ${chalk.dim(`(${truncateMiddle(c.target_path, 88)})`)}`);
@@ -19619,7 +20762,7 @@ ${removed} orphaned config(s) ${opts.dryRun ? "found" : "removed"}${omitted > 0
19619
20762
  console.log(chalk.dim(`Use --limit ${removed} to print every orphan row.`));
19620
20763
  }
19621
20764
  });
19622
- program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
20765
+ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI tools + MCP servers + configs").option("--dry-run", "show what would be installed without doing it").option("--from <agent>", "verify the hosted Conversations heartbeat for this agent").option("--delivery-verified", "assert channel and direct-message canaries were observed in this acceptance pass").option("--skip-mcp", "skip MCP server registration").action(async (opts) => {
19623
20766
  const store = resolveConfigStore();
19624
20767
  const packages = [
19625
20768
  { name: "@hasna/todos", bin: "todos", mcp: "todos-mcp" },
@@ -19682,8 +20825,22 @@ Initializing configs:`));
19682
20825
  } else {
19683
20826
  console.log(chalk.dim(" would run: configs init"));
19684
20827
  }
20828
+ console.log(chalk.cyan(`
20829
+ Reconciling managed skill runtimes:`));
20830
+ const runtimeReport = await reconcileManagedSkillRuntimes({
20831
+ dryRun: opts.dryRun,
20832
+ agent: opts.from,
20833
+ deliveryVerified: opts.deliveryVerified
20834
+ });
20835
+ printManagedSkillRuntimeReport(runtimeReport);
20836
+ if (runtimeReport.failed > 0) {
20837
+ console.error(chalk.red(`
20838
+ Bootstrap incomplete: a managed skill runtime could not be reconciled.`));
20839
+ process.exitCode = 1;
20840
+ return;
20841
+ }
19685
20842
  console.log(chalk.bold(`
19686
- \u2713 Bootstrap complete.`) + chalk.dim(" Restart Claude Code for MCP servers to activate."));
20843
+ \u2713 Bootstrap complete.`) + chalk.dim(" Restart agent sessions to load updated integrations."));
19687
20844
  });
19688
20845
  program.command("pull").description("Alias for sync (read from disk into DB)").option("-a, --agent <agent>", "only sync this agent").option("--dry-run", "preview without writing").action(async (opts) => {
19689
20846
  const result = await syncKnown({ dryRun: opts.dryRun, agent: opts.agent, store: resolveConfigStore() });