@happyvertical/smrt-cli 0.40.21 → 0.40.23

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/README.md CHANGED
@@ -55,6 +55,27 @@ reconciles live schema drift. Global `--force` remains available by itself for
55
55
  backward compatibility but intentionally overrides guards for the whole pending
56
56
  batch.
57
57
 
58
+ ### Audited PostgreSQL timestamp conversion
59
+
60
+ `timestamp without time zone` values do not carry enough information for SMRT
61
+ to infer their original instant. After auditing every historical writer,
62
+ database default, trigger, and raw SQL path, an operator may confirm that the
63
+ legacy values are UTC wall times and include the exact opt-in:
64
+
65
+ ```bash
66
+ smrt db:migrate --postgres-timestamp-legacy-timezone UTC
67
+ ```
68
+
69
+ The option has no default and rejects every value other than `UTC`. On
70
+ PostgreSQL, it allows the manifest-owned timestamp columns to be converted to
71
+ `timestamptz` with `USING column AT TIME ZONE 'UTC'`, preserving the proven UTC
72
+ instants. It is not safe for a database with any local-time writer; use an
73
+ application-owned, provenance-aware migration in that case. Rehearse against a
74
+ restored clone and keep a verified backup because type upgrades have no
75
+ automatic down migration. `smrt db:diff --postgres-timestamp-legacy-timezone
76
+ UTC` and `smrt db:migrate --dry-run --postgres-timestamp-legacy-timezone UTC`
77
+ both preview the same conversion without writing it.
78
+
58
79
  ### Code Generation
59
80
 
60
81
  | Command | Description |
@@ -12,6 +12,7 @@ import { createLogger } from "@happyvertical/logger";
12
12
  import glob from "fast-glob";
13
13
  import { createHash } from "node:crypto";
14
14
  import { toSnakeCase } from "@happyvertical/smrt-core/utils";
15
+ import { readAgentModuleDocs } from "@happyvertical/smrt-core/knowledge";
15
16
  import { MCPGenerator } from "@happyvertical/smrt-core/generators";
16
17
  import { generateDeclarationsFromCLI } from "@happyvertical/smrt-core/prebuild";
17
18
  import { execSync, spawn, spawnSync } from "node:child_process";
@@ -408,6 +409,20 @@ async function autoDiscoverAndLoad(projectRoot = process.cwd()) {
408
409
  };
409
410
  }
410
411
  //#endregion
412
+ //#region src/commands/postgres-timestamp-migration.ts
413
+ /**
414
+ * Fail-closed confirmation shared by PostgreSQL schema preview and migration.
415
+ *
416
+ * A `timestamp without time zone` value cannot establish its own original
417
+ * instant. Callers must therefore make the same explicit UTC provenance
418
+ * confirmation whether they are previewing or applying the conversion.
419
+ */
420
+ function resolvePostgresTimestampMigration(legacyTimezone) {
421
+ if (legacyTimezone === void 0) return;
422
+ if (legacyTimezone !== "UTC") throw new Error("--postgres-timestamp-legacy-timezone must be exactly UTC; refusing to infer the offset of legacy PostgreSQL timestamps");
423
+ return { legacyTimezone: "UTC" };
424
+ }
425
+ //#endregion
411
426
  //#region src/commands/db-diff.ts
412
427
  /**
413
428
  * db:diff Command
@@ -463,11 +478,16 @@ var dbDiffCommand = {
463
478
  type: "boolean",
464
479
  description: "Include orphan-index drops in the diff (indexes in DB but not in the manifest, excluding *_pkey/*_key implicit-from-constraint indexes). Off by default for safety.",
465
480
  default: false
481
+ },
482
+ "postgres-timestamp-legacy-timezone": {
483
+ type: "string",
484
+ description: "Confirm that legacy PostgreSQL timestamp-without-time-zone values are UTC wall times before previewing their conversion to timestamptz. Exact value required: UTC; omitted by default."
466
485
  }
467
486
  },
468
487
  handler: async (_args, options) => {
469
488
  let db;
470
489
  try {
490
+ const postgresTimestampMigration = resolvePostgresTimestampMigration(options["postgres-timestamp-legacy-timezone"]);
471
491
  const unsupportedFileOptions = [
472
492
  "generate",
473
493
  "name",
@@ -516,7 +536,8 @@ var dbDiffCommand = {
516
536
  const diff = await new SchemaComparer(db, {
517
537
  includeDroppedTables: false,
518
538
  includeDroppedColumns: false,
519
- includeDroppedIndexes: Boolean(options["drop-indexes"])
539
+ includeDroppedIndexes: Boolean(options["drop-indexes"]),
540
+ postgresTimestampMigration
520
541
  }).compare(schemaDefinitions);
521
542
  if (options.json) {
522
543
  console.log(JSON.stringify({
@@ -2373,8 +2394,11 @@ function createDocsCommand(config) {
2373
2394
  console.log(` ${totalPackages} packages documented`);
2374
2395
  if (packages.length > 0) console.log(` ${packages.length} SMRT packages`);
2375
2396
  if (sdkPackages.length > 0) console.log(` ${sdkPackages.length} SDK packages`);
2376
- const withAgentDocs = [...packages, ...sdkPackages].filter((p) => p.agentMd).length;
2397
+ const allPackages = [...packages, ...sdkPackages];
2398
+ const withAgentDocs = allPackages.filter((p) => p.agentMd).length;
2377
2399
  if (withAgentDocs > 0) console.log(` ${withAgentDocs} with AGENTS.md`);
2400
+ const moduleDocCount = allPackages.reduce((total, p) => total + (p.moduleDocs?.length ?? 0), 0);
2401
+ if (moduleDocCount > 0) console.log(` ${moduleDocCount} linked module docs`);
2378
2402
  if (rootDocs.length > 0) console.log(` ${rootDocs.length} framework documents included`);
2379
2403
  console.log("");
2380
2404
  } catch (error) {
@@ -2474,7 +2498,8 @@ function loadPackageInfo(packagePath, dirName) {
2474
2498
  readme: existsSync(readmePath) ? readFileSync(readmePath, "utf-8") : null,
2475
2499
  agentMd: agentDoc.content,
2476
2500
  claudeMd: agentDoc.content,
2477
- docSource: agentDoc.source
2501
+ docSource: agentDoc.source,
2502
+ moduleDocs: agentDoc.source === "AGENTS.md" ? readAgentModuleDocs(packagePath, agentDoc.content ?? void 0) : []
2478
2503
  };
2479
2504
  } catch {
2480
2505
  console.warn(` ⚠️ Could not read package.json for ${dirName}`);
@@ -2553,6 +2578,27 @@ function renderAgentMd(content) {
2553
2578
  if (h1Match) content = content.slice(h1Match[0].length);
2554
2579
  return content.trim();
2555
2580
  }
2581
+ /**
2582
+ * The package's AGENTS.md followed by every module doc it links (#2108).
2583
+ * The links stay in the rendered text so a reader can still map a section back
2584
+ * to its source path; the bodies follow so nothing is lost downstream.
2585
+ */
2586
+ function renderPackageDoc(pkg, lines) {
2587
+ const content = pkg.agentMd ?? pkg.claudeMd;
2588
+ if (!content) {
2589
+ lines.push("*No AGENTS.md found for this package.*");
2590
+ lines.push("");
2591
+ return;
2592
+ }
2593
+ lines.push(renderAgentMd(content));
2594
+ lines.push("");
2595
+ for (const doc of pkg.moduleDocs ?? []) {
2596
+ lines.push(`#### ${doc.path}`);
2597
+ lines.push("");
2598
+ lines.push(renderAgentMd(doc.content));
2599
+ lines.push("");
2600
+ }
2601
+ }
2556
2602
  function generateMarkdown(packages, rootDocs, sdkPackages, generatedBy = "smrt docs:agents") {
2557
2603
  const lines = [];
2558
2604
  lines.push("# SMRT Framework Context");
@@ -2596,14 +2642,7 @@ function generateMarkdown(packages, rootDocs, sdkPackages, generatedBy = "smrt d
2596
2642
  lines.push("");
2597
2643
  lines.push(`## ${pkg.name}`);
2598
2644
  lines.push("");
2599
- const content = pkg.agentMd ?? pkg.claudeMd;
2600
- if (content) {
2601
- lines.push(renderAgentMd(content));
2602
- lines.push("");
2603
- } else {
2604
- lines.push("*No AGENTS.md found for this package.*");
2605
- lines.push("");
2606
- }
2645
+ renderPackageDoc(pkg, lines);
2607
2646
  }
2608
2647
  if (sdkPackages && sdkPackages.length > 0) {
2609
2648
  lines.push("---");
@@ -2615,14 +2654,7 @@ function generateMarkdown(packages, rootDocs, sdkPackages, generatedBy = "smrt d
2615
2654
  lines.push("");
2616
2655
  lines.push(`### ${pkg.name}`);
2617
2656
  lines.push("");
2618
- const content = pkg.agentMd ?? pkg.claudeMd;
2619
- if (content) {
2620
- lines.push(renderAgentMd(content));
2621
- lines.push("");
2622
- } else {
2623
- lines.push("*No AGENTS.md found for this package.*");
2624
- lines.push("");
2625
- }
2657
+ renderPackageDoc(pkg, lines);
2626
2658
  }
2627
2659
  }
2628
2660
  lines.push("---");
@@ -7074,6 +7106,10 @@ export default testManifest;
7074
7106
  description: "Use PostgreSQL-safe operations (CONCURRENTLY for indexes, lock_timeout)",
7075
7107
  default: false
7076
7108
  },
7109
+ "postgres-timestamp-legacy-timezone": {
7110
+ type: "string",
7111
+ description: "Confirm that legacy PostgreSQL timestamp-without-time-zone values are UTC wall times before converting them to timestamptz. Exact value required: UTC; omitted by default."
7112
+ },
7077
7113
  force: {
7078
7114
  type: "boolean",
7079
7115
  description: "Force re-apply even if already applied (skip checksum validation)",
@@ -7111,6 +7147,7 @@ export default testManifest;
7111
7147
  let db;
7112
7148
  try {
7113
7149
  const forceSelection = resolveForceMigrationSelection(options.force, options["force-migration"]);
7150
+ const postgresTimestampMigration = resolvePostgresTimestampMigration(options["postgres-timestamp-legacy-timezone"]);
7114
7151
  const { getPackageConfig } = await import("@happyvertical/smrt-config");
7115
7152
  const { DEFAULT_CLI_CONFIG } = await import("./config-BwrFRL8L.js");
7116
7153
  const config = getPackageConfig("cli", DEFAULT_CLI_CONFIG);
@@ -7211,7 +7248,10 @@ export default testManifest;
7211
7248
  console.warn(" Use --repair-data instead.\n");
7212
7249
  }
7213
7250
  console.log("🔍 Comparing schemas...\n");
7214
- const diff = await new SchemaComparer(db, { includeDroppedIndexes: Boolean(options["drop-indexes"]) }).compare(manifestSchemas);
7251
+ const diff = await new SchemaComparer(db, {
7252
+ includeDroppedIndexes: Boolean(options["drop-indexes"]),
7253
+ postgresTimestampMigration
7254
+ }).compare(manifestSchemas);
7215
7255
  const getClassForTable = (tableName) => {
7216
7256
  for (const className of initOrder) if (ObjectRegistry.getTableName(className) === tableName) return className;
7217
7257
  return tableName;
package/dist/index.js CHANGED
@@ -48,56 +48,56 @@ var _docsCommands = null;
48
48
  var _playgroundCommands = null;
49
49
  async function getGnodeCommands() {
50
50
  if (!_gnodeCommands) {
51
- const { gnodeCommands } = await import("./commands-mHvAZYmi.js");
51
+ const { gnodeCommands } = await import("./commands-CR1RZCYu.js");
52
52
  _gnodeCommands = gnodeCommands;
53
53
  }
54
54
  return _gnodeCommands;
55
55
  }
56
56
  async function getGitCommands() {
57
57
  if (!_gitCommands) {
58
- const { gitCommands } = await import("./commands-mHvAZYmi.js");
58
+ const { gitCommands } = await import("./commands-CR1RZCYu.js");
59
59
  _gitCommands = gitCommands;
60
60
  }
61
61
  return _gitCommands;
62
62
  }
63
63
  async function getGenerateCommands() {
64
64
  if (!_generateCommands) {
65
- const { generateCommands } = await import("./commands-mHvAZYmi.js");
65
+ const { generateCommands } = await import("./commands-CR1RZCYu.js");
66
66
  _generateCommands = generateCommands;
67
67
  }
68
68
  return _generateCommands;
69
69
  }
70
70
  async function getInitCommands() {
71
71
  if (!_initCommands) {
72
- const { initCommands } = await import("./commands-mHvAZYmi.js");
72
+ const { initCommands } = await import("./commands-CR1RZCYu.js");
73
73
  _initCommands = initCommands;
74
74
  }
75
75
  return _initCommands;
76
76
  }
77
77
  async function getUtilityCommands() {
78
78
  if (!_utilityCommands) {
79
- const { utilityCommands } = await import("./commands-mHvAZYmi.js");
79
+ const { utilityCommands } = await import("./commands-CR1RZCYu.js");
80
80
  _utilityCommands = utilityCommands;
81
81
  }
82
82
  return _utilityCommands;
83
83
  }
84
84
  async function getDispatchCommands() {
85
85
  if (!_dispatchCommands) {
86
- const { dispatchCommands } = await import("./commands-mHvAZYmi.js");
86
+ const { dispatchCommands } = await import("./commands-CR1RZCYu.js");
87
87
  _dispatchCommands = dispatchCommands;
88
88
  }
89
89
  return _dispatchCommands;
90
90
  }
91
91
  async function getDocsCommands() {
92
92
  if (!_docsCommands) {
93
- const { docsCommands } = await import("./commands-mHvAZYmi.js");
93
+ const { docsCommands } = await import("./commands-CR1RZCYu.js");
94
94
  _docsCommands = docsCommands;
95
95
  }
96
96
  return _docsCommands;
97
97
  }
98
98
  async function getPlaygroundCommands() {
99
99
  if (!_playgroundCommands) {
100
- const { playgroundCommands } = await import("./commands-mHvAZYmi.js");
100
+ const { playgroundCommands } = await import("./commands-CR1RZCYu.js");
101
101
  _playgroundCommands = playgroundCommands;
102
102
  }
103
103
  return _playgroundCommands;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-cli",
3
- "version": "0.40.21",
3
+ "version": "0.40.23",
4
4
  "description": "Developer CLI for SMRT framework - introspection, testing, and project management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,12 +32,12 @@
32
32
  "acorn": "^8.17.0",
33
33
  "fast-glob": "3.3.3",
34
34
  "tar": "^7.5.19",
35
- "@happyvertical/smrt-core": "0.40.21",
36
- "@happyvertical/smrt-agents": "0.40.21",
37
- "@happyvertical/smrt-config": "0.40.21",
38
- "@happyvertical/smrt-playground": "0.40.21",
39
- "@happyvertical/smrt-dev-mcp": "0.40.21",
40
- "@happyvertical/smrt-types": "0.40.21"
35
+ "@happyvertical/smrt-agents": "0.40.23",
36
+ "@happyvertical/smrt-config": "0.40.23",
37
+ "@happyvertical/smrt-dev-mcp": "0.40.23",
38
+ "@happyvertical/smrt-core": "0.40.23",
39
+ "@happyvertical/smrt-playground": "0.40.23",
40
+ "@happyvertical/smrt-types": "0.40.23"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "24.13.2",