@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 +21 -0
- package/dist/{commands-mHvAZYmi.js → commands-CR1RZCYu.js} +60 -20
- package/dist/index.js +8 -8
- package/package.json +7 -7
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
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
36
|
-
"@happyvertical/smrt-
|
|
37
|
-
"@happyvertical/smrt-
|
|
38
|
-
"@happyvertical/smrt-
|
|
39
|
-
"@happyvertical/smrt-
|
|
40
|
-
"@happyvertical/smrt-types": "0.40.
|
|
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",
|