@zabaca/lattice 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -437,7 +437,8 @@ var SITE_TEMPLATE_FILES = [
437
437
  "src/content.config.ts",
438
438
  "src/collections/authors.ts",
439
439
  "src/collections/documents.ts",
440
- "src/collections/tags.ts"
440
+ "src/collections/tags.ts",
441
+ "src/plugins/rehype-strip-md-extension.ts"
441
442
  ];
442
443
 
443
444
  class InitCommand extends CommandRunner2 {
@@ -522,6 +523,9 @@ OBSIDIAN_VAULT_DIR=docs
522
523
  await fs.mkdir(path.join(latticeHome, "src", "collections"), {
523
524
  recursive: true
524
525
  });
526
+ await fs.mkdir(path.join(latticeHome, "src", "plugins"), {
527
+ recursive: true
528
+ });
525
529
  let copied = 0;
526
530
  let skipped = 0;
527
531
  for (const file of SITE_TEMPLATE_FILES) {
@@ -3456,15 +3460,328 @@ QuestionUnansweredCommand = __legacyDecorateClassTS([
3456
3460
  typeof GraphService === "undefined" ? Object : GraphService
3457
3461
  ])
3458
3462
  ], QuestionUnansweredCommand);
3459
- // src/commands/site.command.ts
3463
+ // src/commands/receive.command.ts
3460
3464
  import { spawn } from "child_process";
3461
- import { existsSync as existsSync6, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3462
- import * as path2 from "path";
3463
3465
  import { Injectable as Injectable17 } from "@nestjs/common";
3464
3466
  import { Command as Command7, CommandRunner as CommandRunner7, Option as Option5 } from "nest-commander";
3465
- class SiteCommand extends CommandRunner7 {
3467
+
3468
+ // src/utils/croc-binary.ts
3469
+ import { execSync } from "child_process";
3470
+ import {
3471
+ chmodSync,
3472
+ createWriteStream,
3473
+ existsSync as existsSync6,
3474
+ mkdirSync as mkdirSync2,
3475
+ unlinkSync
3476
+ } from "fs";
3477
+ import { join as join3 } from "path";
3478
+ var CROC_VERSION = "10.3.1";
3479
+ function getBinPath() {
3480
+ return join3(getLatticeHome(), "bin");
3481
+ }
3482
+ function getCrocPath() {
3483
+ const binName = process.platform === "win32" ? "croc.exe" : "croc";
3484
+ return join3(getBinPath(), binName);
3485
+ }
3486
+ function getAssetName() {
3487
+ const platform = process.platform;
3488
+ const arch = process.arch;
3489
+ const assetMap = {
3490
+ "darwin-arm64": "macOS-ARM64",
3491
+ "darwin-x64": "macOS-64bit",
3492
+ "linux-x64": "Linux-64bit",
3493
+ "linux-arm64": "Linux-ARM64",
3494
+ "win32-x64": "Windows-64bit"
3495
+ };
3496
+ const key = `${platform}-${arch}`;
3497
+ return assetMap[key] || null;
3498
+ }
3499
+ async function downloadCroc() {
3500
+ const asset = getAssetName();
3501
+ if (!asset) {
3502
+ const key = `${process.platform}-${process.arch}`;
3503
+ console.error(`
3504
+ \u274C Unsupported platform: ${key}`);
3505
+ console.error(`
3506
+ Install croc manually: https://github.com/schollz/croc#install`);
3507
+ console.error(`Then place the binary at: ~/.lattice/bin/croc
3508
+ `);
3509
+ process.exit(1);
3510
+ }
3511
+ const binDir = getBinPath();
3512
+ if (!existsSync6(binDir)) {
3513
+ mkdirSync2(binDir, { recursive: true });
3514
+ }
3515
+ const ext = process.platform === "win32" ? "zip" : "tar.gz";
3516
+ const url = `https://github.com/schollz/croc/releases/download/v${CROC_VERSION}/croc_v${CROC_VERSION}_${asset}.${ext}`;
3517
+ console.log(`\uD83D\uDCE5 Downloading croc v${CROC_VERSION}...`);
3518
+ try {
3519
+ const response = await fetch(url);
3520
+ if (!response.ok) {
3521
+ throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
3522
+ }
3523
+ const crocPath = getCrocPath();
3524
+ if (ext === "tar.gz") {
3525
+ const tempTarGz = join3(binDir, "croc.tar.gz");
3526
+ const fileStream = createWriteStream(tempTarGz);
3527
+ const arrayBuffer = await response.arrayBuffer();
3528
+ const buffer = Buffer.from(arrayBuffer);
3529
+ fileStream.write(buffer);
3530
+ fileStream.end();
3531
+ await new Promise((resolve4, reject) => {
3532
+ fileStream.on("finish", resolve4);
3533
+ fileStream.on("error", reject);
3534
+ });
3535
+ execSync(`tar -xzf croc.tar.gz`, { cwd: binDir, stdio: "pipe" });
3536
+ unlinkSync(tempTarGz);
3537
+ chmodSync(crocPath, 493);
3538
+ } else {
3539
+ console.error(`
3540
+ \u274C Windows auto-download not yet implemented.`);
3541
+ console.error(`
3542
+ Install croc manually: https://github.com/schollz/croc#install`);
3543
+ console.error(`Then place the binary at: ~/.lattice/bin/croc.exe
3544
+ `);
3545
+ process.exit(1);
3546
+ }
3547
+ console.log(`\u2705 Installed croc to ${crocPath}
3548
+ `);
3549
+ } catch (error) {
3550
+ console.error(`
3551
+ \u274C Failed to download croc: ${error instanceof Error ? error.message : String(error)}`);
3552
+ console.error(`
3553
+ Install croc manually: https://github.com/schollz/croc#install`);
3554
+ console.error(`Then place the binary at: ${getCrocPath()}
3555
+ `);
3556
+ process.exit(1);
3557
+ }
3558
+ }
3559
+ async function ensureCroc() {
3560
+ const crocPath = getCrocPath();
3561
+ if (existsSync6(crocPath)) {
3562
+ return crocPath;
3563
+ }
3564
+ await downloadCroc();
3565
+ return crocPath;
3566
+ }
3567
+
3568
+ // src/commands/receive.command.ts
3569
+ class ReceiveCommand extends CommandRunner7 {
3570
+ syncService;
3571
+ constructor(syncService) {
3572
+ super();
3573
+ this.syncService = syncService;
3574
+ }
3575
+ async run([code], options) {
3576
+ if (!code) {
3577
+ console.error(`
3578
+ \u274C Please specify a share code
3579
+ `);
3580
+ console.error(`Usage: lattice receive <code>
3581
+ `);
3582
+ console.error("Example:");
3583
+ console.error(` lattice receive 7-actress-plural-pilgrim
3584
+ `);
3585
+ process.exit(1);
3586
+ }
3587
+ const docsDir = getDocsPath();
3588
+ console.log(`
3589
+ \uD83D\uDCE5 Receiving files to: ${docsDir}
3590
+ `);
3591
+ const crocPath = await ensureCroc();
3592
+ const receiveCode = await new Promise((resolve4, reject) => {
3593
+ const child = spawn(crocPath, ["--yes", "--out", docsDir, code], {
3594
+ stdio: "inherit"
3595
+ });
3596
+ child.on("close", (code2) => {
3597
+ resolve4(code2 ?? 0);
3598
+ });
3599
+ child.on("error", (err) => {
3600
+ reject(err);
3601
+ });
3602
+ });
3603
+ if (receiveCode !== 0) {
3604
+ console.error(`
3605
+ \u274C Transfer failed
3606
+ `);
3607
+ process.exit(receiveCode);
3608
+ }
3609
+ if (!options.noSync) {
3610
+ console.log(`
3611
+ \uD83D\uDD04 Syncing received documents to knowledge graph...
3612
+ `);
3613
+ try {
3614
+ const result = await this.syncService.sync({
3615
+ verbose: false
3616
+ });
3617
+ console.log(`\uD83D\uDCCA Sync Results:
3618
+ `);
3619
+ console.log(` \u2705 Added: ${result.added}`);
3620
+ console.log(` \uD83D\uDD04 Updated: ${result.updated}`);
3621
+ console.log(` \u23ED\uFE0F Unchanged: ${result.unchanged}`);
3622
+ if (result.embeddingsGenerated > 0) {
3623
+ console.log(` \uD83E\uDDE0 Embeddings: ${result.embeddingsGenerated}`);
3624
+ }
3625
+ if (result.errors.length > 0) {
3626
+ console.log(`
3627
+ \u274C Errors (${result.errors.length}):
3628
+ `);
3629
+ result.errors.forEach((e) => {
3630
+ console.log(` ${e.path}: ${e.error}`);
3631
+ });
3632
+ }
3633
+ } catch (error) {
3634
+ console.error(`
3635
+ \u26A0\uFE0F Sync failed: ${error instanceof Error ? error.message : String(error)}`);
3636
+ console.error("Files were received but not synced to the knowledge graph.");
3637
+ console.error(`Run 'lattice sync' manually to complete sync.
3638
+ `);
3639
+ }
3640
+ } else {
3641
+ console.log(`
3642
+ \u23ED\uFE0F Skipping sync (--no-sync specified)`);
3643
+ console.log(`Run 'lattice sync' to add documents to the knowledge graph.
3644
+ `);
3645
+ }
3646
+ console.log(`\u2705 Done!
3647
+ `);
3648
+ }
3649
+ parseNoSync() {
3650
+ return true;
3651
+ }
3652
+ }
3653
+ __legacyDecorateClassTS([
3654
+ Option5({
3655
+ flags: "--no-sync",
3656
+ description: "Skip running lattice sync after receiving"
3657
+ }),
3658
+ __legacyMetadataTS("design:type", Function),
3659
+ __legacyMetadataTS("design:paramtypes", []),
3660
+ __legacyMetadataTS("design:returntype", Boolean)
3661
+ ], ReceiveCommand.prototype, "parseNoSync", null);
3662
+ ReceiveCommand = __legacyDecorateClassTS([
3663
+ Injectable17(),
3664
+ Command7({
3665
+ name: "receive",
3666
+ arguments: "<code>",
3667
+ description: "Receive shared documents via P2P transfer"
3668
+ }),
3669
+ __legacyMetadataTS("design:paramtypes", [
3670
+ typeof SyncService === "undefined" ? Object : SyncService
3671
+ ])
3672
+ ], ReceiveCommand);
3673
+ // src/commands/share.command.ts
3674
+ import { spawn as spawn2 } from "child_process";
3675
+ import { existsSync as existsSync7, statSync } from "fs";
3676
+ import * as path2 from "path";
3677
+ import { Injectable as Injectable18 } from "@nestjs/common";
3678
+ import { Command as Command8, CommandRunner as CommandRunner8 } from "nest-commander";
3679
+ class ShareCommand extends CommandRunner8 {
3680
+ async run([docPath]) {
3681
+ if (!docPath) {
3682
+ console.error(`
3683
+ \u274C Please specify a path to share
3684
+ `);
3685
+ console.error(`Usage: lattice share <path>
3686
+ `);
3687
+ console.error("Examples:");
3688
+ console.error(" lattice share duckdb # Share ~/.lattice/docs/duckdb/");
3689
+ console.error(` lattice share duckdb/README.md # Share single file
3690
+ `);
3691
+ process.exit(1);
3692
+ }
3693
+ const fullPath = this.resolvePath(docPath);
3694
+ if (!existsSync7(fullPath)) {
3695
+ console.error(`
3696
+ \u274C Path not found: ${fullPath}
3697
+ `);
3698
+ process.exit(1);
3699
+ }
3700
+ const stat = statSync(fullPath);
3701
+ const isDir = stat.isDirectory();
3702
+ console.log(`
3703
+ \uD83D\uDCE4 Sharing ${isDir ? "directory" : "file"}: ${fullPath}
3704
+ `);
3705
+ const crocPath = await ensureCroc();
3706
+ const child = spawn2(crocPath, ["--disable-clipboard", "send", fullPath], {
3707
+ stdio: ["inherit", "pipe", "pipe"]
3708
+ });
3709
+ let codeExtracted = false;
3710
+ let outputBuffer = "";
3711
+ const handleOutput = (data) => {
3712
+ const text = data.toString();
3713
+ outputBuffer += text;
3714
+ if (!codeExtracted) {
3715
+ const codeMatch = outputBuffer.match(/Code is:\s*(\S+)/);
3716
+ if (codeMatch) {
3717
+ codeExtracted = true;
3718
+ const code = codeMatch[1];
3719
+ const sendingMatches = outputBuffer.match(/Sending \d+ files? (?:and \d+ folders? )?\([^)]+\)/g);
3720
+ if (sendingMatches && sendingMatches.length > 0) {
3721
+ const lastMatch = sendingMatches[sendingMatches.length - 1];
3722
+ if (!lastMatch.includes("0 files")) {
3723
+ console.log(lastMatch);
3724
+ }
3725
+ }
3726
+ console.log(`
3727
+ \uD83D\uDD17 Share code: ${code}
3728
+ `);
3729
+ console.log("On the receiving machine, run:");
3730
+ console.log(` lattice receive ${code}
3731
+ `);
3732
+ console.log(`Waiting for recipient...
3733
+ `);
3734
+ outputBuffer = "";
3735
+ }
3736
+ return;
3737
+ }
3738
+ if (text.includes("On the other computer") || text.includes("(For Windows)") || text.includes("(For Linux/macOS)") || text.includes("CROC_SECRET=") || text.includes("Code copied to clipboard") || text.includes("croc ")) {
3739
+ return;
3740
+ }
3741
+ process.stdout.write(text);
3742
+ };
3743
+ child.stdout?.on("data", handleOutput);
3744
+ child.stderr?.on("data", handleOutput);
3745
+ child.on("close", (code) => {
3746
+ if (code === 0) {
3747
+ console.log(`
3748
+ \u2705 Transfer complete!
3749
+ `);
3750
+ }
3751
+ process.exit(code ?? 0);
3752
+ });
3753
+ child.on("error", (err) => {
3754
+ console.error(`
3755
+ \u274C Failed to run croc: ${err.message}
3756
+ `);
3757
+ process.exit(1);
3758
+ });
3759
+ }
3760
+ resolvePath(input) {
3761
+ if (path2.isAbsolute(input)) {
3762
+ return input;
3763
+ }
3764
+ const docsDir = getDocsPath();
3765
+ return path2.join(docsDir, input);
3766
+ }
3767
+ }
3768
+ ShareCommand = __legacyDecorateClassTS([
3769
+ Injectable18(),
3770
+ Command8({
3771
+ name: "share",
3772
+ arguments: "<path>",
3773
+ description: "Share a document or topic directory via P2P transfer"
3774
+ })
3775
+ ], ShareCommand);
3776
+ // src/commands/site.command.ts
3777
+ import { spawn as spawn3 } from "child_process";
3778
+ import { existsSync as existsSync8, readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
3779
+ import * as path3 from "path";
3780
+ import { Injectable as Injectable19 } from "@nestjs/common";
3781
+ import { Command as Command9, CommandRunner as CommandRunner9, Option as Option6 } from "nest-commander";
3782
+ class SiteCommand extends CommandRunner9 {
3466
3783
  getPidFile() {
3467
- return path2.join(getLatticeHome(), "site.pid");
3784
+ return path3.join(getLatticeHome(), "site.pid");
3468
3785
  }
3469
3786
  async run(_inputs, options) {
3470
3787
  if (options.kill) {
@@ -3472,9 +3789,9 @@ class SiteCommand extends CommandRunner7 {
3472
3789
  process.exit(0);
3473
3790
  }
3474
3791
  const latticeHome = getLatticeHome();
3475
- const packageJsonPath = path2.join(latticeHome, "package.json");
3476
- const nodeModulesPath = path2.join(latticeHome, "node_modules");
3477
- if (!existsSync6(packageJsonPath)) {
3792
+ const packageJsonPath = path3.join(latticeHome, "package.json");
3793
+ const nodeModulesPath = path3.join(latticeHome, "node_modules");
3794
+ if (!existsSync8(packageJsonPath)) {
3478
3795
  console.error("Error: Site not initialized. Run 'lattice init' first.");
3479
3796
  process.exit(1);
3480
3797
  }
@@ -3483,7 +3800,7 @@ class SiteCommand extends CommandRunner7 {
3483
3800
  console.error("Use 'lattice site --kill' to stop it first.");
3484
3801
  process.exit(1);
3485
3802
  }
3486
- if (!existsSync6(nodeModulesPath)) {
3803
+ if (!existsSync8(nodeModulesPath)) {
3487
3804
  console.log("\uD83D\uDCE6 Installing dependencies...");
3488
3805
  await this.runCommand("bun", ["install"], latticeHome);
3489
3806
  console.log();
@@ -3510,7 +3827,7 @@ class SiteCommand extends CommandRunner7 {
3510
3827
  }
3511
3828
  runCommand(cmd, args, cwd) {
3512
3829
  return new Promise((resolve4, reject) => {
3513
- const child = spawn(cmd, args, {
3830
+ const child = spawn3(cmd, args, {
3514
3831
  cwd,
3515
3832
  stdio: "pipe",
3516
3833
  env: { ...process.env, FORCE_COLOR: "1" }
@@ -3535,7 +3852,7 @@ class SiteCommand extends CommandRunner7 {
3535
3852
  }
3536
3853
  runServerCommand(cmd, args, cwd) {
3537
3854
  return new Promise((resolve4, reject) => {
3538
- const child = spawn(cmd, args, {
3855
+ const child = spawn3(cmd, args, {
3539
3856
  cwd,
3540
3857
  stdio: "inherit",
3541
3858
  env: { ...process.env, FORCE_COLOR: "1" }
@@ -3545,7 +3862,7 @@ class SiteCommand extends CommandRunner7 {
3545
3862
  }
3546
3863
  const cleanup = () => {
3547
3864
  try {
3548
- unlinkSync(this.getPidFile());
3865
+ unlinkSync2(this.getPidFile());
3549
3866
  } catch {}
3550
3867
  };
3551
3868
  child.on("close", (code) => {
@@ -3566,7 +3883,7 @@ class SiteCommand extends CommandRunner7 {
3566
3883
  }
3567
3884
  isRunning() {
3568
3885
  const pidFile = this.getPidFile();
3569
- if (!existsSync6(pidFile)) {
3886
+ if (!existsSync8(pidFile)) {
3570
3887
  return false;
3571
3888
  }
3572
3889
  try {
@@ -3575,14 +3892,14 @@ class SiteCommand extends CommandRunner7 {
3575
3892
  return true;
3576
3893
  } catch {
3577
3894
  try {
3578
- unlinkSync(pidFile);
3895
+ unlinkSync2(pidFile);
3579
3896
  } catch {}
3580
3897
  return false;
3581
3898
  }
3582
3899
  }
3583
3900
  killSiteProcess() {
3584
3901
  const pidFile = this.getPidFile();
3585
- if (!existsSync6(pidFile)) {
3902
+ if (!existsSync8(pidFile)) {
3586
3903
  console.log("No Lattice site process running");
3587
3904
  return;
3588
3905
  }
@@ -3593,11 +3910,11 @@ class SiteCommand extends CommandRunner7 {
3593
3910
  } catch {
3594
3911
  process.kill(pid, "SIGTERM");
3595
3912
  }
3596
- unlinkSync(pidFile);
3913
+ unlinkSync2(pidFile);
3597
3914
  console.log(`\u2705 Killed Lattice site process (PID: ${pid})`);
3598
3915
  } catch (_err) {
3599
3916
  try {
3600
- unlinkSync(pidFile);
3917
+ unlinkSync2(pidFile);
3601
3918
  } catch {}
3602
3919
  console.log("No Lattice site process running");
3603
3920
  }
@@ -3616,7 +3933,7 @@ class SiteCommand extends CommandRunner7 {
3616
3933
  }
3617
3934
  }
3618
3935
  __legacyDecorateClassTS([
3619
- Option5({
3936
+ Option6({
3620
3937
  flags: "-b, --build",
3621
3938
  description: "Build the site without starting the server"
3622
3939
  }),
@@ -3625,7 +3942,7 @@ __legacyDecorateClassTS([
3625
3942
  __legacyMetadataTS("design:returntype", Boolean)
3626
3943
  ], SiteCommand.prototype, "parseBuild", null);
3627
3944
  __legacyDecorateClassTS([
3628
- Option5({
3945
+ Option6({
3629
3946
  flags: "-d, --dev",
3630
3947
  description: "Run in development mode (hot reload, no search)"
3631
3948
  }),
@@ -3634,7 +3951,7 @@ __legacyDecorateClassTS([
3634
3951
  __legacyMetadataTS("design:returntype", Boolean)
3635
3952
  ], SiteCommand.prototype, "parseDev", null);
3636
3953
  __legacyDecorateClassTS([
3637
- Option5({
3954
+ Option6({
3638
3955
  flags: "-p, --port <port>",
3639
3956
  description: "Port to run the server on (default: 4321)"
3640
3957
  }),
@@ -3645,7 +3962,7 @@ __legacyDecorateClassTS([
3645
3962
  __legacyMetadataTS("design:returntype", String)
3646
3963
  ], SiteCommand.prototype, "parsePort", null);
3647
3964
  __legacyDecorateClassTS([
3648
- Option5({
3965
+ Option6({
3649
3966
  flags: "-k, --kill",
3650
3967
  description: "Kill the running Lattice site process"
3651
3968
  }),
@@ -3654,16 +3971,16 @@ __legacyDecorateClassTS([
3654
3971
  __legacyMetadataTS("design:returntype", Boolean)
3655
3972
  ], SiteCommand.prototype, "parseKill", null);
3656
3973
  SiteCommand = __legacyDecorateClassTS([
3657
- Injectable17(),
3658
- Command7({
3974
+ Injectable19(),
3975
+ Command9({
3659
3976
  name: "site",
3660
3977
  description: "Build and run the Lattice documentation site"
3661
3978
  })
3662
3979
  ], SiteCommand);
3663
3980
  // src/commands/status.command.ts
3664
- import { Injectable as Injectable18 } from "@nestjs/common";
3665
- import { Command as Command8, CommandRunner as CommandRunner8, Option as Option6 } from "nest-commander";
3666
- class StatusCommand extends CommandRunner8 {
3981
+ import { Injectable as Injectable20 } from "@nestjs/common";
3982
+ import { Command as Command10, CommandRunner as CommandRunner10, Option as Option7 } from "nest-commander";
3983
+ class StatusCommand extends CommandRunner10 {
3667
3984
  syncService;
3668
3985
  dbChangeDetector;
3669
3986
  constructor(syncService, dbChangeDetector) {
@@ -3729,7 +4046,7 @@ class StatusCommand extends CommandRunner8 {
3729
4046
  }
3730
4047
  }
3731
4048
  __legacyDecorateClassTS([
3732
- Option6({
4049
+ Option7({
3733
4050
  flags: "-v, --verbose",
3734
4051
  description: "Show all documents including unchanged"
3735
4052
  }),
@@ -3738,8 +4055,8 @@ __legacyDecorateClassTS([
3738
4055
  __legacyMetadataTS("design:returntype", Boolean)
3739
4056
  ], StatusCommand.prototype, "parseVerbose", null);
3740
4057
  StatusCommand = __legacyDecorateClassTS([
3741
- Injectable18(),
3742
- Command8({
4058
+ Injectable20(),
4059
+ Command10({
3743
4060
  name: "status",
3744
4061
  description: "Show documents that need syncing (new or updated)"
3745
4062
  }),
@@ -3750,12 +4067,12 @@ StatusCommand = __legacyDecorateClassTS([
3750
4067
  ], StatusCommand);
3751
4068
  // src/commands/sync.command.ts
3752
4069
  import { watch } from "fs";
3753
- import { join as join4 } from "path";
3754
- import { Injectable as Injectable20 } from "@nestjs/common";
3755
- import { Command as Command9, CommandRunner as CommandRunner9, Option as Option7 } from "nest-commander";
4070
+ import { join as join6 } from "path";
4071
+ import { Injectable as Injectable22 } from "@nestjs/common";
4072
+ import { Command as Command11, CommandRunner as CommandRunner11, Option as Option8 } from "nest-commander";
3756
4073
 
3757
4074
  // src/sync/graph-validator.service.ts
3758
- import { Injectable as Injectable19, Logger as Logger9 } from "@nestjs/common";
4075
+ import { Injectable as Injectable21, Logger as Logger9 } from "@nestjs/common";
3759
4076
  class GraphValidatorService {
3760
4077
  graph;
3761
4078
  logger = new Logger9(GraphValidatorService.name);
@@ -3874,15 +4191,15 @@ class GraphValidatorService {
3874
4191
  });
3875
4192
  }
3876
4193
  }
3877
- async validateDocument(path3) {
4194
+ async validateDocument(path4) {
3878
4195
  const issues = [];
3879
4196
  try {
3880
- const result = await this.graph.query(`SELECT label, name, properties FROM nodes WHERE label = 'Document' AND name = '${this.escape(path3)}'`);
4197
+ const result = await this.graph.query(`SELECT label, name, properties FROM nodes WHERE label = 'Document' AND name = '${this.escape(path4)}'`);
3881
4198
  if (result.resultSet.length === 0) {
3882
4199
  issues.push({
3883
4200
  type: "error",
3884
4201
  nodeLabel: "Document",
3885
- nodeName: path3,
4202
+ nodeName: path4,
3886
4203
  field: "node",
3887
4204
  message: "Document not found in graph",
3888
4205
  suggestion: "Run 'lattice sync' to add this document"
@@ -3892,9 +4209,9 @@ class GraphValidatorService {
3892
4209
  const row = result.resultSet[0];
3893
4210
  const propertiesJson = row[2];
3894
4211
  const properties = typeof propertiesJson === "string" ? JSON.parse(propertiesJson) : propertiesJson;
3895
- this.validateDocumentNode(path3, properties, issues);
4212
+ this.validateDocumentNode(path4, properties, issues);
3896
4213
  } catch (error) {
3897
- this.logger.error(`Failed to validate document ${path3}: ${error instanceof Error ? error.message : String(error)}`);
4214
+ this.logger.error(`Failed to validate document ${path4}: ${error instanceof Error ? error.message : String(error)}`);
3898
4215
  throw error;
3899
4216
  }
3900
4217
  return issues;
@@ -3904,14 +4221,14 @@ class GraphValidatorService {
3904
4221
  }
3905
4222
  }
3906
4223
  GraphValidatorService = __legacyDecorateClassTS([
3907
- Injectable19(),
4224
+ Injectable21(),
3908
4225
  __legacyMetadataTS("design:paramtypes", [
3909
4226
  typeof GraphService === "undefined" ? Object : GraphService
3910
4227
  ])
3911
4228
  ], GraphValidatorService);
3912
4229
 
3913
4230
  // src/commands/sync.command.ts
3914
- class SyncCommand extends CommandRunner9 {
4231
+ class SyncCommand extends CommandRunner11 {
3915
4232
  syncService;
3916
4233
  graphService;
3917
4234
  _graphValidator;
@@ -4054,7 +4371,7 @@ class SyncCommand extends CommandRunner9 {
4054
4371
  `);
4055
4372
  this.watcher = watch(docsPath, { recursive: true }, (event, filename) => {
4056
4373
  if (filename?.endsWith(".md")) {
4057
- const fullPath = join4(docsPath, filename);
4374
+ const fullPath = join6(docsPath, filename);
4058
4375
  trackedFiles.add(fullPath);
4059
4376
  debouncedSync();
4060
4377
  }
@@ -4184,7 +4501,7 @@ class SyncCommand extends CommandRunner9 {
4184
4501
  }
4185
4502
  }
4186
4503
  __legacyDecorateClassTS([
4187
- Option7({
4504
+ Option8({
4188
4505
  flags: "-f, --force",
4189
4506
  description: "Force re-sync specified documents (requires paths to be specified)"
4190
4507
  }),
@@ -4193,7 +4510,7 @@ __legacyDecorateClassTS([
4193
4510
  __legacyMetadataTS("design:returntype", Boolean)
4194
4511
  ], SyncCommand.prototype, "parseForce", null);
4195
4512
  __legacyDecorateClassTS([
4196
- Option7({
4513
+ Option8({
4197
4514
  flags: "-d, --dry-run",
4198
4515
  description: "Show what would change without applying"
4199
4516
  }),
@@ -4202,7 +4519,7 @@ __legacyDecorateClassTS([
4202
4519
  __legacyMetadataTS("design:returntype", Boolean)
4203
4520
  ], SyncCommand.prototype, "parseDryRun", null);
4204
4521
  __legacyDecorateClassTS([
4205
- Option7({
4522
+ Option8({
4206
4523
  flags: "-v, --verbose",
4207
4524
  description: "Show detailed output"
4208
4525
  }),
@@ -4211,7 +4528,7 @@ __legacyDecorateClassTS([
4211
4528
  __legacyMetadataTS("design:returntype", Boolean)
4212
4529
  ], SyncCommand.prototype, "parseVerbose", null);
4213
4530
  __legacyDecorateClassTS([
4214
- Option7({
4531
+ Option8({
4215
4532
  flags: "-w, --watch",
4216
4533
  description: "Watch for file changes and sync automatically"
4217
4534
  }),
@@ -4220,7 +4537,7 @@ __legacyDecorateClassTS([
4220
4537
  __legacyMetadataTS("design:returntype", Boolean)
4221
4538
  ], SyncCommand.prototype, "parseWatch", null);
4222
4539
  __legacyDecorateClassTS([
4223
- Option7({
4540
+ Option8({
4224
4541
  flags: "--diff",
4225
4542
  description: "Show only changed documents (alias for --dry-run)"
4226
4543
  }),
@@ -4229,7 +4546,7 @@ __legacyDecorateClassTS([
4229
4546
  __legacyMetadataTS("design:returntype", Boolean)
4230
4547
  ], SyncCommand.prototype, "parseDiff", null);
4231
4548
  __legacyDecorateClassTS([
4232
- Option7({
4549
+ Option8({
4233
4550
  flags: "--skip-cascade",
4234
4551
  description: "Skip cascade analysis (faster for large repos)"
4235
4552
  }),
@@ -4238,7 +4555,7 @@ __legacyDecorateClassTS([
4238
4555
  __legacyMetadataTS("design:returntype", Boolean)
4239
4556
  ], SyncCommand.prototype, "parseSkipCascade", null);
4240
4557
  __legacyDecorateClassTS([
4241
- Option7({
4558
+ Option8({
4242
4559
  flags: "--no-embeddings",
4243
4560
  description: "Disable embedding generation during sync"
4244
4561
  }),
@@ -4247,7 +4564,7 @@ __legacyDecorateClassTS([
4247
4564
  __legacyMetadataTS("design:returntype", Boolean)
4248
4565
  ], SyncCommand.prototype, "parseNoEmbeddings", null);
4249
4566
  __legacyDecorateClassTS([
4250
- Option7({
4567
+ Option8({
4251
4568
  flags: "--skip-extraction",
4252
4569
  description: "Skip AI entity extraction (sync without re-extracting entities)"
4253
4570
  }),
@@ -4256,8 +4573,8 @@ __legacyDecorateClassTS([
4256
4573
  __legacyMetadataTS("design:returntype", Boolean)
4257
4574
  ], SyncCommand.prototype, "parseSkipExtraction", null);
4258
4575
  SyncCommand = __legacyDecorateClassTS([
4259
- Injectable20(),
4260
- Command9({
4576
+ Injectable22(),
4577
+ Command11({
4261
4578
  name: "sync",
4262
4579
  arguments: "[paths...]",
4263
4580
  description: "Synchronize documents to the knowledge graph"
@@ -4296,7 +4613,7 @@ GraphModule = __legacyDecorateClassTS([
4296
4613
  import { Module as Module3 } from "@nestjs/common";
4297
4614
 
4298
4615
  // src/query/query.service.ts
4299
- import { Injectable as Injectable21, Logger as Logger10 } from "@nestjs/common";
4616
+ import { Injectable as Injectable23, Logger as Logger10 } from "@nestjs/common";
4300
4617
  class QueryService {
4301
4618
  graphService;
4302
4619
  logger = new Logger10(QueryService.name);
@@ -4309,7 +4626,7 @@ class QueryService {
4309
4626
  }
4310
4627
  }
4311
4628
  QueryService = __legacyDecorateClassTS([
4312
- Injectable21(),
4629
+ Injectable23(),
4313
4630
  __legacyMetadataTS("design:paramtypes", [
4314
4631
  typeof GraphService === "undefined" ? Object : GraphService
4315
4632
  ])
@@ -4384,6 +4701,8 @@ AppModule = __legacyDecorateClassTS([
4384
4701
  InitCommand,
4385
4702
  MigrateCommand,
4386
4703
  SiteCommand,
4704
+ ShareCommand,
4705
+ ReceiveCommand,
4387
4706
  QuestionAddCommand,
4388
4707
  QuestionLinkCommand,
4389
4708
  QuestionUnansweredCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zabaca/lattice",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Human-initiated, AI-powered knowledge graph for markdown documentation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,9 +1,13 @@
1
1
  import { defineConfig } from 'astro/config';
2
2
  import { astroSpaceship } from 'astro-spaceship';
3
+ import rehypeStripMdExtension from './src/plugins/rehype-strip-md-extension';
3
4
 
4
5
  import websiteConfig from 'astro-spaceship/config';
5
6
 
6
7
  export default defineConfig({
8
+ markdown: {
9
+ rehypePlugins: [rehypeStripMdExtension],
10
+ },
7
11
  integrations: [
8
12
  astroSpaceship(websiteConfig)
9
13
  ]
@@ -0,0 +1,29 @@
1
+ import { visit } from 'unist-util-visit';
2
+ import type { Root, Element } from 'hast';
3
+
4
+ /**
5
+ * Rehype plugin that strips .md extensions from relative links.
6
+ * This allows standard markdown links like [text](./file.md) to work
7
+ * correctly in Astro where routes don't include the .md extension.
8
+ */
9
+ export default function rehypeStripMdExtension() {
10
+ return (tree: Root) => {
11
+ visit(tree, 'element', (node: Element) => {
12
+ if (
13
+ node.tagName === 'a' &&
14
+ typeof node.properties?.href === 'string'
15
+ ) {
16
+ const href = node.properties.href;
17
+ // Only process relative links ending in .md
18
+ if (
19
+ !href.startsWith('http://') &&
20
+ !href.startsWith('https://') &&
21
+ !href.startsWith('//') &&
22
+ href.endsWith('.md')
23
+ ) {
24
+ node.properties.href = href.slice(0, -3);
25
+ }
26
+ }
27
+ });
28
+ };
29
+ }