@mean-weasel/lineage 0.1.7 → 0.1.9

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.9
4
+
5
+ - Add runtime identity diagnostics in Settings so operators can see the active channel, version, Git SHA when available, SQLite path, and database counts.
6
+ - Add `lineage db info` for CLI and agent checks before touching a local Lineage database.
7
+ - Document the stable, preview, and dev channel data policy, keeping explicit database overrides available with `--db` and `LINEAGE_DB`.
8
+ - Add browser coverage to ensure Settings keeps surfacing the active runtime and SQLite identity.
9
+
10
+ ## 0.1.8
11
+
12
+ - Add durable selection packet export so agents can hand selected Lineage assets to GrowthOps without scraping UI state or copying local paths.
13
+ - Add Agent OS adoption guidance for Lineage agents and operators.
14
+ - Improve popover media previews, node actions, and image expansion controls for faster asset inspection.
15
+ - Clean up the Lineage shell navigation, toolbar, and side-panel layout for a more focused workspace.
16
+
3
17
  ## 0.1.7
4
18
 
5
19
  - Add a claim-aware lineage task queue for per-image iteration and re-roll work, including task instructions, comments, cancellation, and human override controls.
package/README.md CHANGED
@@ -4,19 +4,20 @@ Lineage is a local-first workspace for reviewing creative assets, branching vari
4
4
 
5
5
  ## Package Channels
6
6
 
7
- Lineage is packaged as `@mean-weasel/lineage`. Use `latest` for the stable dogfood or production install, and use `next` when you intentionally want the development or release-candidate channel:
7
+ Lineage is packaged as `@mean-weasel/lineage`. Use `latest` for the stable dogfood or production install, and use `next` when you intentionally want the published preview candidate:
8
8
 
9
9
  ```bash
10
10
  npm install -g @mean-weasel/lineage@latest
11
11
  npm install -g @mean-weasel/lineage@next
12
12
  ```
13
13
 
14
- The stable and development channels are intended to coexist conceptually:
14
+ The stable, preview, and dev channels are intended to coexist conceptually:
15
15
 
16
16
  - `latest` is the version you should trust for day-to-day dogfooding.
17
- - `next` is the version to test before promotion.
17
+ - `next` is the published preview version to test before promotion.
18
+ - `dev` is the local GitHub checkout or branch before publication.
18
19
  - `lineage` runs with stable runtime defaults.
19
- - `lineage-dev` runs with development runtime defaults.
20
+ - `lineage-dev` runs with dev runtime defaults.
20
21
 
21
22
  The package includes both CLI bridge bins for help and version checks:
22
23
 
@@ -40,6 +41,30 @@ By default, `lineage start` listens on `lineage.localhost:5197` and stores SQLit
40
41
  lineage start --port 6123 --db ~/.lineage/lineage.sqlite
41
42
  ```
42
43
 
44
+ ## Runtime Channels and SQLite
45
+
46
+ Lineage treats app code channels and local SQLite data as separate concerns:
47
+
48
+ - `stable` is npm `latest` and is the daily-use channel.
49
+ - `preview` is the published candidate channel, normally npm `next`.
50
+ - `dev` is a GitHub checkout or local branch before publication.
51
+
52
+ Each channel should use its own default runtime directory and SQLite database.
53
+ Do not point preview or dev code at the stable database unless you are doing an
54
+ intentional, explicit test. Prefer copying a stable snapshot forward when a
55
+ realistic preview/dev dataset is needed. `--db` and `LINEAGE_DB` remain the
56
+ escape hatches for intentionally choosing a database.
57
+
58
+ Check the active runtime before making changes:
59
+
60
+ ```bash
61
+ lineage db info --json
62
+ lineage-dev db info --json
63
+ ```
64
+
65
+ The app also shows channel, version, Git SHA when available, and SQLite path in
66
+ Settings.
67
+
43
68
  ## Agent Claims
44
69
 
45
70
  Mutating agent writes use target-scoped claim tokens. Read-only inspection stays
@@ -60,6 +85,20 @@ Agents can inspect the current lineage graph without a claim:
60
85
  lineage agent graph --project demo-project --root <root-asset-id> --json
61
86
  ```
62
87
 
88
+ Agents can also export the current active workspace selection as a durable JSON
89
+ packet for downstream tools such as GrowthOps:
90
+
91
+ ```bash
92
+ lineage selection packet --project demo-project --channel linkedin --campaign 2026-07-launch --out ./lineage-selection-packet.json --json
93
+ ```
94
+
95
+ The packet is schema-versioned as `lineage.selection_packet.v1` and includes the
96
+ workspace/root binding, selected asset IDs, local media paths when known, S3 key
97
+ metadata when known, context labels/notes, warnings, and a stable `packet_id`.
98
+ Lineage only exports the packet. GrowthOps or another downstream tool remains
99
+ responsible for importing it, creating posts, checking media readiness, preparing
100
+ public URLs, scheduling, and recording placement receipts.
101
+
63
102
  Keep the claim fresh and pass it to mutating commands:
64
103
 
65
104
  ```bash
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/lineageCli.ts
4
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3 } from "node:fs";
4
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
5
5
  import { homedir, platform } from "node:os";
6
- import { dirname as dirname3, join as join6, resolve as resolve4 } from "node:path";
6
+ import { dirname as dirname3, join as join7, resolve as resolve5 } from "node:path";
7
7
  import { spawn } from "node:child_process";
8
8
  import { fileURLToPath as fileURLToPath2 } from "node:url";
9
9
 
@@ -283,8 +283,8 @@ function isPackageRoot(path) {
283
283
  const packageJson = join3(path, "package.json");
284
284
  if (!existsSync3(packageJson)) return false;
285
285
  try {
286
- const packageInfo = JSON.parse(readFileSync2(packageJson, "utf8"));
287
- return packageInfo.name === "@mean-weasel/lineage";
286
+ const packageInfo2 = JSON.parse(readFileSync2(packageJson, "utf8"));
287
+ return packageInfo2.name === "@mean-weasel/lineage";
288
288
  } catch {
289
289
  return false;
290
290
  }
@@ -328,6 +328,13 @@ function catalogPath(project = defaultProject) {
328
328
  function fixtureCatalogPath(project = defaultProject) {
329
329
  return join3(repoRoot, "fixtures", cleanProject(project), "assets", "catalog.json");
330
330
  }
331
+ function resolvedCatalogPath(project = defaultProject) {
332
+ const path = catalogPath(project);
333
+ if (existsSync3(path)) return path;
334
+ const clean = cleanProject(project);
335
+ if (clean === defaultProject && existsSync3(fixtureCatalogPath(clean))) return fixtureCatalogPath(clean);
336
+ return path;
337
+ }
331
338
  function normalizeCatalog(catalog, fallbackProject = defaultProject) {
332
339
  const project = cleanProject(catalog.project || catalog.product || fallbackProject);
333
340
  const product = catalog.product || project;
@@ -570,6 +577,10 @@ function listAssets(project = defaultProject, options = {}) {
570
577
  error
571
578
  };
572
579
  }
580
+ function validateProject(project = defaultProject) {
581
+ const catalog = loadCatalog(project);
582
+ return { project: catalog.project, product: catalog.product, catalogPath: resolvedCatalogPath(project), default_bucket: catalog.default_bucket, default_region: catalog.default_region, asset_count: catalog.assets.length };
583
+ }
573
584
 
574
585
  // src/server/assetLineageDb.ts
575
586
  var require2 = createRequire(import.meta.url);
@@ -2223,6 +2234,17 @@ function seedLegacyWorkspaces(database, project) {
2223
2234
  );
2224
2235
  }
2225
2236
  }
2237
+ function listRows(database, project) {
2238
+ return database.prepare(`
2239
+ select * from lineage_workspaces
2240
+ where project_id = ?
2241
+ order by
2242
+ case status when 'active' then 0 when 'paused' then 1 else 2 end,
2243
+ active_at desc nulls last,
2244
+ updated_at desc,
2245
+ title
2246
+ `).all(project).map(rowToWorkspace);
2247
+ }
2226
2248
  function activeWorkspace(database, project) {
2227
2249
  const row = database.prepare(`
2228
2250
  select * from lineage_workspaces
@@ -2232,6 +2254,20 @@ function activeWorkspace(database, project) {
2232
2254
  `).get(project);
2233
2255
  return row ? rowToWorkspace(row) : null;
2234
2256
  }
2257
+ function listLineageWorkspaces(project) {
2258
+ const database = lineageDb();
2259
+ try {
2260
+ seedLegacyWorkspaces(database, project);
2261
+ return {
2262
+ project,
2263
+ active_workspace: activeWorkspace(database, project),
2264
+ workspaces: listRows(database, project),
2265
+ fetchedAt: nowIso()
2266
+ };
2267
+ } finally {
2268
+ database.close();
2269
+ }
2270
+ }
2235
2271
  function activeLineageWorkspaceRoot(project) {
2236
2272
  const database = lineageDb();
2237
2273
  try {
@@ -3336,27 +3372,316 @@ function importImageRerollOutput(project = defaultProject, fields) {
3336
3372
  }
3337
3373
  }
3338
3374
 
3375
+ // src/server/lineageSelectionPacket.ts
3376
+ import { createHash as createHash3 } from "node:crypto";
3377
+ import { existsSync as existsSync5, statSync as statSync4 } from "node:fs";
3378
+ import { isAbsolute, resolve as resolve4 } from "node:path";
3379
+ var LineageSelectionPacketError = class extends Error {
3380
+ constructor(message, warnings = [], errors = []) {
3381
+ super(message);
3382
+ this.warnings = warnings;
3383
+ this.errors = errors;
3384
+ }
3385
+ warnings;
3386
+ errors;
3387
+ };
3388
+ function listAllAssets(project) {
3389
+ const pageSize = 100;
3390
+ const first = listAssets(project, { page: 1, pageSize, source: "all" });
3391
+ const assets = [...first.assets];
3392
+ for (let page = 2; page <= first.pagination.totalPages; page += 1) {
3393
+ assets.push(...listAssets(project, { page, pageSize, source: "all" }).assets);
3394
+ }
3395
+ return assets;
3396
+ }
3397
+ function resolveWorkspace(project, options) {
3398
+ if (options.rootAssetId && options.workspaceId) {
3399
+ throw new LineageSelectionPacketError("Use either --root or --workspace, not both.", [], ["ambiguous_workspace"]);
3400
+ }
3401
+ const snapshot = listLineageWorkspaces(project);
3402
+ const target = options.workspaceId || options.rootAssetId;
3403
+ const workspace = target ? snapshot.workspaces.find((item) => item.id === target || item.root_asset_id === target) : snapshot.active_workspace;
3404
+ if (workspace) return workspace;
3405
+ if (options.rootAssetId) {
3406
+ return {
3407
+ id: lineageWorkspaceId(project, options.rootAssetId),
3408
+ project,
3409
+ root_asset_id: options.rootAssetId,
3410
+ title: `${options.rootAssetId} lineage`,
3411
+ status: "active",
3412
+ created_by: "system",
3413
+ created_at: "",
3414
+ updated_at: ""
3415
+ };
3416
+ }
3417
+ const message = options.workspaceId ? `Unknown lineage workspace: ${options.workspaceId}` : "No active lineage workspace. Pass --root or activate a workspace first.";
3418
+ throw new LineageSelectionPacketError(message, [], [options.workspaceId ? "unknown_workspace" : "missing_active_workspace"]);
3419
+ }
3420
+ function resolveLocalReference(reference) {
3421
+ if (!reference) return void 0;
3422
+ if (isAbsolute(reference)) return reference;
3423
+ const repoRelative = resolve4(repoRoot, reference);
3424
+ if (existsSync5(repoRelative)) return repoRelative;
3425
+ return resolve4(repoRoot, ".asset-scratch", reference);
3426
+ }
3427
+ function fileSize(path) {
3428
+ if (!path || !existsSync5(path)) return void 0;
3429
+ try {
3430
+ return statSync4(path).size;
3431
+ } catch {
3432
+ return void 0;
3433
+ }
3434
+ }
3435
+ function storageState(hasLocal, hasS3) {
3436
+ if (hasLocal && hasS3) return "local_and_s3";
3437
+ if (hasLocal) return "local_only";
3438
+ if (hasS3) return "s3_backed";
3439
+ return "unresolved";
3440
+ }
3441
+ function currentAttemptFor(node) {
3442
+ if (!node.current_attempt) return void 0;
3443
+ return {
3444
+ asset_id: node.current_attempt.asset_id,
3445
+ checksum_sha256: node.current_attempt.checksum_sha256,
3446
+ file_path: node.current_attempt.file_path,
3447
+ generation_job_id: node.current_attempt.generation_job_id,
3448
+ id: node.current_attempt.id,
3449
+ is_current: node.current_attempt.is_current,
3450
+ source: node.current_attempt.source
3451
+ };
3452
+ }
3453
+ function assetForNode(node, catalogAsset, warnings) {
3454
+ const localReference = catalogAsset?.local?.absolute_path || node.current_attempt?.file_path || node.local_path || catalogAsset?.local?.relative_path;
3455
+ const absolutePath = catalogAsset?.local?.absolute_path || resolveLocalReference(localReference);
3456
+ const localExists = absolutePath ? existsSync5(absolutePath) : false;
3457
+ const hasLocalClaim = Boolean(absolutePath || node.local_path || catalogAsset?.local?.relative_path);
3458
+ const s3Key = catalogAsset?.s3?.key || node.s3_key;
3459
+ const hasS3 = Boolean(s3Key);
3460
+ const checksum = catalogAsset?.local?.checksum_sha256 || node.current_attempt?.checksum_sha256 || node.checksum_sha256 || catalogAsset?.s3?.checksum_sha256;
3461
+ const mimeType = catalogAsset?.local?.content_type || catalogAsset?.s3?.content_type;
3462
+ const mediaType = catalogAsset?.content_type || node.media_type;
3463
+ if (hasLocalClaim && !localExists) warnings.push(`Selected asset ${node.asset_id} has a local path but the file is missing: ${absolutePath || node.local_path}`);
3464
+ if (!hasLocalClaim && !hasS3) warnings.push(`Selected asset ${node.asset_id} has neither a local file nor S3 key.`);
3465
+ if (mediaType && mediaType !== "image" && mediaType !== "gif") warnings.push(`Selected asset ${node.asset_id} is ${mediaType}, not an image/gif asset.`);
3466
+ return {
3467
+ asset_id: node.asset_id,
3468
+ campaign: catalogAsset?.campaign || node.campaign,
3469
+ channel: catalogAsset?.channel || node.channel,
3470
+ checksum_sha256: checksum,
3471
+ current_attempt: currentAttemptFor(node),
3472
+ local: {
3473
+ absolute_path: absolutePath,
3474
+ content_type: catalogAsset?.local?.content_type,
3475
+ exists: localExists,
3476
+ relative_path: catalogAsset?.local?.relative_path || node.local_path || node.current_attempt?.file_path,
3477
+ size_bytes: catalogAsset?.local?.size_bytes || fileSize(absolutePath)
3478
+ },
3479
+ media_type: mediaType,
3480
+ mime_type: mimeType,
3481
+ review_notes: node.review_notes,
3482
+ review_state: node.review_state,
3483
+ s3: {
3484
+ bucket: catalogAsset?.s3?.bucket,
3485
+ checksum_sha256: catalogAsset?.s3?.checksum_sha256,
3486
+ content_type: catalogAsset?.s3?.content_type,
3487
+ etag: catalogAsset?.s3?.etag,
3488
+ key: s3Key,
3489
+ region: catalogAsset?.s3?.region,
3490
+ size_bytes: catalogAsset?.s3?.size_bytes,
3491
+ version_id: catalogAsset?.s3?.version_id
3492
+ },
3493
+ selection_note: node.selection_note,
3494
+ source: catalogAsset?.source || node.source,
3495
+ status: catalogAsset?.status || node.status,
3496
+ storage_state: storageState(hasLocalClaim, hasS3),
3497
+ title: catalogAsset?.title || node.title || node.asset_id
3498
+ };
3499
+ }
3500
+ function packetId(packetIdentity) {
3501
+ const digest = createHash3("sha256").update(JSON.stringify(packetIdentity)).digest("hex").slice(0, 24);
3502
+ return `lineage_packet_${digest}`;
3503
+ }
3504
+ function getLineageSelectionPacket(project, options = {}) {
3505
+ const workspace = resolveWorkspace(project, options);
3506
+ const snapshot = getLineageSnapshot(project, workspace.root_asset_id);
3507
+ const catalogSummary = validateProject(project);
3508
+ const catalogAssets = listAllAssets(project);
3509
+ const catalogById = new Map(catalogAssets.map((asset) => [asset.asset_id, asset]));
3510
+ const warnings = [];
3511
+ const errors = [];
3512
+ const nodeById = new Map(snapshot.nodes.map((node) => [node.asset_id, node]));
3513
+ const selectedItems = snapshot.selections.map((selection) => ({
3514
+ asset_id: selection.asset_id,
3515
+ position: selection.position,
3516
+ selected_at: selection.selected_at,
3517
+ selection_note: selection.notes
3518
+ }));
3519
+ if (selectedItems.length === 0) warnings.push(`Lineage workspace ${workspace.id} has no selected assets.`);
3520
+ const assets = selectedItems.flatMap((item) => {
3521
+ const node = nodeById.get(item.asset_id);
3522
+ if (!node) {
3523
+ const message = `Selected asset ${item.asset_id} is not present in the resolved workspace snapshot.`;
3524
+ warnings.push(message);
3525
+ errors.push("selected_asset_outside_workspace");
3526
+ return [];
3527
+ }
3528
+ return [assetForNode(node, catalogById.get(item.asset_id), warnings)];
3529
+ });
3530
+ if (assets.some((asset) => !asset.dimensions)) warnings.push("Image dimensions are unavailable for one or more selected assets.");
3531
+ if (options.strict) {
3532
+ const strictErrors = [
3533
+ ...selectedItems.length === 0 ? ["empty_selection"] : [],
3534
+ ...assets.filter((asset) => asset.local.absolute_path && !asset.local.exists).map((asset) => `missing_local_file:${asset.asset_id}`),
3535
+ ...errors
3536
+ ];
3537
+ if (strictErrors.length > 0) {
3538
+ throw new LineageSelectionPacketError(`Lineage selection packet strict mode failed: ${strictErrors.join(", ")}`, warnings, strictErrors);
3539
+ }
3540
+ }
3541
+ const context = {
3542
+ campaign: options.campaign,
3543
+ channel: options.channel,
3544
+ labels: options.labels || [],
3545
+ notes: options.contextNotes
3546
+ };
3547
+ const identity = {
3548
+ assets: assets.map((asset) => ({
3549
+ asset_id: asset.asset_id,
3550
+ checksum_sha256: asset.checksum_sha256,
3551
+ local_path: asset.local.absolute_path,
3552
+ s3_key: asset.s3.key,
3553
+ storage_state: asset.storage_state
3554
+ })),
3555
+ context,
3556
+ project,
3557
+ root_asset_id: workspace.root_asset_id,
3558
+ schema_version: "lineage.selection_packet.v1",
3559
+ selection: selectedItems,
3560
+ workspace_id: workspace.id
3561
+ };
3562
+ return {
3563
+ assets,
3564
+ context,
3565
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
3566
+ errors,
3567
+ kind: "lineage.selection_packet",
3568
+ packet_id: packetId(identity),
3569
+ product: catalogSummary.product,
3570
+ project,
3571
+ schema_version: "lineage.selection_packet.v1",
3572
+ selection: {
3573
+ asset_ids: selectedItems.map((item) => item.asset_id),
3574
+ count: selectedItems.length,
3575
+ items: selectedItems,
3576
+ root_asset_id: workspace.root_asset_id
3577
+ },
3578
+ source: {
3579
+ app: "lineage",
3580
+ command: options.command,
3581
+ db_path: options.dbPath || process.env.LINEAGE_DB || lineageDbPath(),
3582
+ package: options.packageVersion
3583
+ },
3584
+ warnings: [...new Set(warnings)],
3585
+ workspace: {
3586
+ active_at: workspace.active_at,
3587
+ id: workspace.id,
3588
+ notes: workspace.notes,
3589
+ root_asset_id: workspace.root_asset_id,
3590
+ status: workspace.status,
3591
+ title: workspace.title
3592
+ }
3593
+ };
3594
+ }
3595
+
3596
+ // src/server/runtimeInfo.ts
3597
+ import { spawnSync as spawnSync2 } from "node:child_process";
3598
+ import { createRequire as createRequire2 } from "node:module";
3599
+ import { existsSync as existsSync6, readFileSync as readFileSync3, statSync as statSync5 } from "node:fs";
3600
+ import { join as join6 } from "node:path";
3601
+ var require3 = createRequire2(import.meta.url);
3602
+ function packageInfo() {
3603
+ try {
3604
+ const info = JSON.parse(readFileSync3(join6(repoRoot, "package.json"), "utf8"));
3605
+ return { name: info.name || "@mean-weasel/lineage", version: info.version || "0.0.0" };
3606
+ } catch {
3607
+ return { name: "@mean-weasel/lineage", version: "0.0.0" };
3608
+ }
3609
+ }
3610
+ function normalizeRuntimeChannel(value) {
3611
+ if (value === "stable" || value === "preview" || value === "dev") return value;
3612
+ if (value === "production") return "stable";
3613
+ if (value === "next") return "preview";
3614
+ if (value === "development") return "dev";
3615
+ return process.env.NODE_ENV === "production" ? "stable" : "dev";
3616
+ }
3617
+ function gitSha() {
3618
+ const envSha = process.env.LINEAGE_GIT_SHA || process.env.GITHUB_SHA;
3619
+ if (envSha) return envSha.slice(0, 40);
3620
+ if (!existsSync6(join6(repoRoot, ".git"))) return void 0;
3621
+ const result = spawnSync2("git", ["rev-parse", "--short=12", "HEAD"], { cwd: repoRoot, encoding: "utf8" });
3622
+ return result.status === 0 ? result.stdout.trim() || void 0 : void 0;
3623
+ }
3624
+ function tableExists(database, table) {
3625
+ return Boolean(database.prepare("select name from sqlite_master where type = 'table' and name = ?").get(table));
3626
+ }
3627
+ function tableCount(database, table) {
3628
+ if (!tableExists(database, table)) return void 0;
3629
+ const row = database.prepare(`select count(*) count from ${table}`).get();
3630
+ return typeof row?.count === "number" ? row.count : void 0;
3631
+ }
3632
+ function getLineageRuntimeInfo(options = {}) {
3633
+ const info = packageInfo();
3634
+ const dbPath = options.dbPath || lineageDbPath();
3635
+ const databaseInfo = { exists: existsSync6(dbPath), path: dbPath };
3636
+ if (databaseInfo.exists) {
3637
+ try {
3638
+ const stat = statSync5(dbPath);
3639
+ databaseInfo.modified_at = stat.mtime.toISOString();
3640
+ databaseInfo.size_bytes = stat.size;
3641
+ const { DatabaseSync } = require3("node:sqlite");
3642
+ const database = new DatabaseSync(dbPath, { readOnly: true });
3643
+ try {
3644
+ databaseInfo.projects = tableCount(database, "projects");
3645
+ databaseInfo.workspaces = tableCount(database, "lineage_workspaces");
3646
+ } finally {
3647
+ database.close();
3648
+ }
3649
+ } catch (error) {
3650
+ databaseInfo.error = error instanceof Error ? error.message : String(error);
3651
+ }
3652
+ }
3653
+ return {
3654
+ channel: normalizeRuntimeChannel(options.channel || process.env.LINEAGE_CHANNEL || process.env.LINEAGE_RELEASE_CHANNEL),
3655
+ database: databaseInfo,
3656
+ fetchedAt: nowIso(),
3657
+ git_sha: gitSha(),
3658
+ node_env: process.env.NODE_ENV,
3659
+ package_name: info.name,
3660
+ version: info.version
3661
+ };
3662
+ }
3663
+
3339
3664
  // src/cli/lineageCli.ts
3340
3665
  var signalExitCodes = {
3341
3666
  SIGINT: 130,
3342
3667
  SIGTERM: 143
3343
3668
  };
3344
3669
  function packageRoot() {
3345
- return resolve4(dirname3(fileURLToPath2(import.meta.url)), "..", "..");
3670
+ return resolve5(dirname3(fileURLToPath2(import.meta.url)), "..", "..");
3346
3671
  }
3347
3672
  function packageVersion() {
3348
3673
  try {
3349
- const packageInfo = JSON.parse(readFileSync3(join6(packageRoot(), "package.json"), "utf8"));
3350
- return packageInfo.version || "0.0.0";
3674
+ const packageInfo2 = JSON.parse(readFileSync4(join7(packageRoot(), "package.json"), "utf8"));
3675
+ return packageInfo2.version || "0.0.0";
3351
3676
  } catch {
3352
3677
  return "0.0.0";
3353
3678
  }
3354
3679
  }
3355
3680
  function dataRoot(displayName) {
3356
3681
  if (process.env.LINEAGE_HOME) return process.env.LINEAGE_HOME;
3357
- if (platform() === "darwin") return join6(homedir(), "Library", "Application Support", displayName);
3358
- if (platform() === "win32") return join6(process.env.APPDATA || join6(homedir(), "AppData", "Roaming"), displayName);
3359
- return join6(process.env.XDG_DATA_HOME || join6(homedir(), ".local", "share"), displayName.toLowerCase().replace(/\s+/g, "-"));
3682
+ if (platform() === "darwin") return join7(homedir(), "Library", "Application Support", displayName);
3683
+ if (platform() === "win32") return join7(process.env.APPDATA || join7(homedir(), "AppData", "Roaming"), displayName);
3684
+ return join7(process.env.XDG_DATA_HOME || join7(homedir(), ".local", "share"), displayName.toLowerCase().replace(/\s+/g, "-"));
3360
3685
  }
3361
3686
  function readOption(args, name) {
3362
3687
  const prefix = `${name}=`;
@@ -3366,6 +3691,19 @@ function readOption(args, name) {
3366
3691
  if (index >= 0) return args[index + 1];
3367
3692
  return void 0;
3368
3693
  }
3694
+ function readOptions(args, name) {
3695
+ const values = [];
3696
+ const prefix = `${name}=`;
3697
+ for (let index = 0; index < args.length; index += 1) {
3698
+ const arg = args[index];
3699
+ if (arg.startsWith(prefix)) values.push(arg.slice(prefix.length));
3700
+ else if (arg === name && args[index + 1] && !args[index + 1].startsWith("--")) {
3701
+ values.push(args[index + 1]);
3702
+ index += 1;
3703
+ }
3704
+ }
3705
+ return values;
3706
+ }
3369
3707
  function resolveStartOptions(config, args) {
3370
3708
  const runtimeDir = dataRoot(config.displayName);
3371
3709
  const rawPort = readOption(args, "--port") || process.env.PORT || String(config.defaultPort);
@@ -3374,7 +3712,7 @@ function resolveStartOptions(config, args) {
3374
3712
  throw new Error(`Invalid port: ${rawPort}`);
3375
3713
  }
3376
3714
  return {
3377
- dbPath: readOption(args, "--db") || process.env.LINEAGE_DB || join6(runtimeDir, `${config.binName}.sqlite`),
3715
+ dbPath: readOption(args, "--db") || process.env.LINEAGE_DB || join7(runtimeDir, `${config.binName}.sqlite`),
3378
3716
  host: readOption(args, "--host") || process.env.HOST || config.defaultHost,
3379
3717
  json: args.includes("--json"),
3380
3718
  open: args.includes("--open"),
@@ -3389,6 +3727,7 @@ Usage:
3389
3727
  ${config.binName} next [--project <project>] [--root <asset-id>] [--db <path>] [--json]
3390
3728
  ${config.binName} brief [--project <project>] [--root <asset-id>] [--db <path>] [--json]
3391
3729
  ${config.binName} inspect --asset-id <asset-id> [--project <project>] [--db <path>] [--json]
3730
+ ${config.binName} selection packet [--project <project>] [--workspace <id-or-root>|--root <asset-id>] [--channel <channel>] [--campaign <campaign>] [--context-notes <text>] [--label <label>] [--out <path>] [--strict] [--db <path>] [--json]
3392
3731
  ${config.binName} link-child --root <asset-id> --child <asset-id> [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
3393
3732
  ${config.binName} reroll list --root <asset-id> [--project <project>] [--db <path>] [--json]
3394
3733
  ${config.binName} reroll mark --root <asset-id> --target <asset-id> [--notes <text>] [--requested-by agent|human|system] [--project <project>] [--confirm-write] [--db <path>] [--json]
@@ -3403,6 +3742,7 @@ Usage:
3403
3742
  ${config.binName} tasks cancel --task <task-id> [--confirm-write] [--override] [--project <project>] [--db <path>] [--json]
3404
3743
  ${config.binName} tasks override --task <task-id> --reason <text> [--instructions <text>] [--project <project>] [--db <path>] [--json]
3405
3744
  ${config.binName} tasks instructions --task <task-id> --instructions <text> [--project <project>] [--db <path>] [--json]
3745
+ ${config.binName} db info [--db <path>] [--json]
3406
3746
  ${config.binName} agent claim --project <project> --scope <scope> --target <target-id> --agent-name <name> [--channel <channel>] [--ttl 20m] [--json]
3407
3747
  ${config.binName} agent graph --root <asset-id> [--project <project>] [--db <path>] [--json]
3408
3748
  ${config.binName} agent status [--project <project>] [--json]
@@ -3464,6 +3804,31 @@ function runLineageDataCommand(command, args) {
3464
3804
  if (!options.assetId) throw new Error("lineage inspect requires --asset-id");
3465
3805
  return getLineageSnapshot(options.project, options.assetId);
3466
3806
  }
3807
+ if (command === "selection") {
3808
+ const subcommand = positionalArgs(args)[0] || "";
3809
+ if (subcommand !== "packet") throw new Error(`Unknown selection command: ${subcommand}`);
3810
+ const labels = readOptions(args, "--label").flatMap((label) => label.split(",")).map((label) => label.trim()).filter(Boolean);
3811
+ const packet = getLineageSelectionPacket(options.project, {
3812
+ campaign: readOption(args, "--campaign"),
3813
+ channel: readOption(args, "--channel"),
3814
+ command: "lineage selection packet",
3815
+ contextNotes: readOption(args, "--context-notes") || readOption(args, "--notes"),
3816
+ dbPath: options.dbPath,
3817
+ labels,
3818
+ packageVersion: packageVersion(),
3819
+ rootAssetId: options.rootAssetId,
3820
+ strict: args.includes("--strict"),
3821
+ workspaceId: readOption(args, "--workspace") || readOption(args, "--workspace-id")
3822
+ });
3823
+ const out = readOption(args, "--out");
3824
+ if (out) {
3825
+ const outPath = resolve5(out);
3826
+ mkdirSync4(dirname3(outPath), { recursive: true });
3827
+ writeFileSync2(outPath, `${JSON.stringify(packet, null, 2)}
3828
+ `);
3829
+ }
3830
+ return packet;
3831
+ }
3467
3832
  if (command === "link-child") {
3468
3833
  if (!options.childAssetId) throw new Error("lineage link-child requires --child");
3469
3834
  return linkSelectedLineageChild(options.project, {
@@ -3613,6 +3978,13 @@ function printDataResult(command, result, json) {
3613
3978
  console.log(`Active: ${snapshot.active_asset_id}`);
3614
3979
  return;
3615
3980
  }
3981
+ if (command === "selection" && result && typeof result === "object" && "packet_id" in result) {
3982
+ const packet = result;
3983
+ console.log(`${packet.packet_id}: ${packet.selection?.count || packet.assets?.length || 0} selected asset(s)`);
3984
+ if (packet.workspace?.root_asset_id) console.log(`Root: ${packet.workspace.root_asset_id}`);
3985
+ for (const warning of packet.warnings || []) console.log(`Warning: ${warning}`);
3986
+ return;
3987
+ }
3616
3988
  if (command === "link-child" && result && typeof result === "object") {
3617
3989
  const link = result;
3618
3990
  console.log(link.message || `${link.dryRun ? "Dry run: " : ""}Link ${link.edge?.child_asset_id || "child"} from ${link.edge?.parent_asset_id || "parent"}`);
@@ -3655,6 +4027,34 @@ function printDataResult(command, result, json) {
3655
4027
  }
3656
4028
  console.log(String(result));
3657
4029
  }
4030
+ function runLineageDbCommand(config, command, args) {
4031
+ const dbPath = readOption(args, "--db");
4032
+ if (dbPath) process.env.LINEAGE_DB = dbPath;
4033
+ if (command === "info") return getLineageRuntimeInfo({ channel: config.channel, dbPath });
4034
+ throw new Error(`Unknown db command: ${command}`);
4035
+ }
4036
+ function printDbResult(command, result, json) {
4037
+ if (json) {
4038
+ console.log(JSON.stringify(result, null, 2));
4039
+ return;
4040
+ }
4041
+ if (command === "info" && result && typeof result === "object" && "database" in result) {
4042
+ const runtime = result;
4043
+ console.log(`Channel: ${runtime.channel}`);
4044
+ console.log(`Version: ${runtime.version}`);
4045
+ if (runtime.git_sha) console.log(`Git: ${runtime.git_sha}`);
4046
+ if (runtime.node_env) console.log(`Node env: ${runtime.node_env}`);
4047
+ console.log(`SQLite: ${runtime.database.path}`);
4048
+ console.log(`Exists: ${runtime.database.exists ? "yes" : "no"}`);
4049
+ if (runtime.database.size_bytes !== void 0) console.log(`Size: ${runtime.database.size_bytes} bytes`);
4050
+ if (runtime.database.modified_at) console.log(`Modified: ${runtime.database.modified_at}`);
4051
+ if (runtime.database.projects !== void 0) console.log(`Projects: ${runtime.database.projects}`);
4052
+ if (runtime.database.workspaces !== void 0) console.log(`Workspaces: ${runtime.database.workspaces}`);
4053
+ if (runtime.database.error) console.log(`Warning: ${runtime.database.error}`);
4054
+ return;
4055
+ }
4056
+ console.log(String(result));
4057
+ }
3658
4058
  function runLineageAgentCommand(command, args) {
3659
4059
  const dbPath = readOption(args, "--db");
3660
4060
  if (dbPath) process.env.LINEAGE_DB = dbPath;
@@ -3781,8 +4181,8 @@ function start(config, args) {
3781
4181
  else console.error(`${config.binName}: ${message}`);
3782
4182
  process.exit(1);
3783
4183
  }
3784
- const serverPath = join6(packageRoot(), "dist", "server.js");
3785
- if (!existsSync5(serverPath)) {
4184
+ const serverPath = join7(packageRoot(), "dist", "server.js");
4185
+ if (!existsSync7(serverPath)) {
3786
4186
  const message = `Missing bundled server at ${serverPath}. Run npm run build before using ${config.binName} start from a source checkout.`;
3787
4187
  if (options.json) console.error(JSON.stringify({ ok: false, error: message }, null, 2));
3788
4188
  else console.error(`${config.binName}: ${message}`);
@@ -3836,7 +4236,7 @@ function runLineageCli(config, args = process.argv.slice(2)) {
3836
4236
  start(config, normalizedArgs.slice(1));
3837
4237
  return;
3838
4238
  }
3839
- if (command === "next" || command === "brief" || command === "inspect" || command === "link-child" || command === "reroll" || command === "tasks") {
4239
+ if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "reroll" || command === "tasks") {
3840
4240
  const commandArgs = normalizedArgs.slice(1);
3841
4241
  const json2 = commandArgs.includes("--json");
3842
4242
  try {
@@ -3851,6 +4251,20 @@ function runLineageCli(config, args = process.argv.slice(2)) {
3851
4251
  }
3852
4252
  process.exit(0);
3853
4253
  }
4254
+ if (command === "db") {
4255
+ const commandArgs = normalizedArgs.slice(2);
4256
+ const dbCommand = normalizedArgs[1] || "";
4257
+ const json2 = commandArgs.includes("--json");
4258
+ try {
4259
+ printDbResult(dbCommand, runLineageDbCommand(config, dbCommand, commandArgs), json2);
4260
+ } catch (error) {
4261
+ const message2 = error instanceof Error ? error.message : String(error);
4262
+ if (json2) console.error(JSON.stringify({ ok: false, command: `db ${dbCommand}`, error: message2 }, null, 2));
4263
+ else console.error(`${config.binName}: ${message2}`);
4264
+ process.exit(1);
4265
+ }
4266
+ process.exit(0);
4267
+ }
3854
4268
  if (command === "agent") {
3855
4269
  const commandArgs = normalizedArgs.slice(2);
3856
4270
  const agentCommand = normalizedArgs[1] || "";
@@ -3877,5 +4291,5 @@ function runLineageCli(config, args = process.argv.slice(2)) {
3877
4291
  }
3878
4292
 
3879
4293
  // src/cli/lineage-dev.ts
3880
- runLineageCli({ binName: "lineage-dev", channel: "development", defaultHost: "lineage-dev.localhost", defaultPort: 5198, displayName: "Lineage Dev" });
4294
+ runLineageCli({ binName: "lineage-dev", channel: "dev", defaultHost: "lineage-dev.localhost", defaultPort: 5198, displayName: "Lineage Dev" });
3881
4295
  //# sourceMappingURL=lineage-dev.js.map