@happyvertical/smrt-core 0.50.0 → 0.51.1
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/AGENTS.md +1 -0
- package/dist/index.js +2 -1
- package/dist/knowledge-graph.d.ts +95 -0
- package/dist/knowledge-graph.d.ts.map +1 -0
- package/dist/knowledge-graph.js +268 -0
- package/dist/knowledge-graph.js.map +1 -0
- package/dist/knowledge.d.ts +1 -0
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +57 -4
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +127 -21
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +379 -84
- package/dist/migrations/differ.js.map +1 -1
- package/dist/schema/column-data-probes.d.ts +38 -0
- package/dist/schema/column-data-probes.d.ts.map +1 -0
- package/dist/schema/column-data-probes.js +106 -0
- package/dist/schema/column-data-probes.js.map +1 -0
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +37 -46
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/smrt-knowledge.json +12 -7
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -19,6 +19,7 @@ and repository rules.
|
|
|
19
19
|
| `src/change-signals.ts` | Signal bus, replica fan-out, SSE | [agents/change-signals.md](agents/change-signals.md) |
|
|
20
20
|
| `src/generators/`, `src/vite-plugin/web-collections.ts` | REST/CLI/MCP generation, manifest hashes, ETags | [agents/generators.md](agents/generators.md) |
|
|
21
21
|
| `src/vite-plugin/`, `src/consumer-plugin/`, `src/knowledge.ts` | Decorator UI hints, knowledge projection, generation snapshots | [agents/build-knowledge.md](agents/build-knowledge.md) |
|
|
22
|
+
| `src/knowledge-graph.ts` | Cross-package knowledge graph merge and freshness check (#2863) — see [standards](../../docs/content/standards.md) | — |
|
|
22
23
|
| `src/object.ts`, `src/collection.ts`, `src/learning/memory.ts` | Context memory and semantic search | [agents/memory.md](agents/memory.md) |
|
|
23
24
|
| `src/system/diagnostics.ts` | SELECT-only `_smrt_*` diagnostics reader behind smrt-dev-mcp runtime tools (#1824) | [agents/system-diagnostics.md](agents/system-diagnostics.md) |
|
|
24
25
|
| `src/system/registry-snapshot.ts` | Sanitized plain-JSON projection of the booted `ObjectRegistry` for the smrt-dev-mcp runtime dev-plane (#1831); never constructors, validators, values, or absolute paths | [agents/registry-snapshot.md](agents/registry-snapshot.md) |
|
package/dist/index.js
CHANGED
|
@@ -69,6 +69,7 @@ import "./generators/index.js";
|
|
|
69
69
|
import { SmrtHierarchical } from "./hierarchical.js";
|
|
70
70
|
import { SmrtJunction } from "./junction.js";
|
|
71
71
|
import { discoverScopedPackageDirectories, readPackageAgentDoc } from "./knowledge-discovery.js";
|
|
72
|
+
import { buildKnowledgeGraph, checkKnowledgeGraphFreshness, discoverKnowledgeArtifactPaths, stableStringify } from "./knowledge-graph.js";
|
|
72
73
|
import { AGENT_SURFACE_HASH_PREFIX, MODULE_DOC_HASH_PREFIX, buildDomainKnowledgeManifest, readAgentModuleDocs, resolveAgentModuleDocPaths } from "./knowledge.js";
|
|
73
74
|
import { getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver } from "./lazy-config.js";
|
|
74
75
|
import { DEFAULT_LEARNING_CONFIG, LearningMemory } from "./learning/memory.js";
|
|
@@ -92,4 +93,4 @@ import "./system/index.js";
|
|
|
92
93
|
import { getTestDatabase } from "./testing/database.js";
|
|
93
94
|
import "./tools/index.js";
|
|
94
95
|
import { smrtPlugin } from "./vite-plugin/index.js";
|
|
95
|
-
export { AGENT_SURFACE_HASH_PREFIX, AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, BOOTED_PROVENANCE, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CRUD_OPERATIONS, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_DATA_QUERY_PAGE_LIMIT, DEFAULT_DATA_QUERY_RESULT_BYTES, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DEFAULT_FACET_LIMIT, DEFAULT_LEARNING_CONFIG, DEFAULT_LIST_LIMIT, DEFAULT_LIST_ORDER_BY, DEFAULT_POSTGRES_TIMEOUTS, DEFAULT_RETENTION_POLICY, DIAGNOSTICS_DEFAULT_LIMIT, DataQueryValidationError, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, EmbeddingUnavailableError, ErrorUtils, FilesystemError, GlobalInterceptors, LearningMemory, LiveSchemaParityError, MAX_CHANGES_LIMIT, MAX_DATA_QUERY_CURSOR_LENGTH, MAX_DATA_QUERY_FACETS, MAX_DATA_QUERY_FILTERS, MAX_DATA_QUERY_FILTER_DEPTH, MAX_DATA_QUERY_IN_VALUES, MAX_DATA_QUERY_OFFSET, MAX_DATA_QUERY_PAGE_LIMIT, MAX_DATA_QUERY_REQUEST_BYTES, MAX_DATA_QUERY_RESULT_BYTES, MAX_DATA_QUERY_WARNINGS, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_LIST_LIMIT, MAX_STI_READ_SCOPE_TYPES, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, MODULE_DOC_HASH_PREFIX, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, POSTGRES_REVISION_GUARD_EXPRESSION, POSTGRES_TIMEOUT_ENV_VARS, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, QueryBoundsError, QueryOrderByError, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SYSTEM_DIAGNOSTICS_TABLES, SYSTEM_TABLE_NAMES, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, appendChanges, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applyPostgresPermissions, applyPostgresRuntimeTimeouts, applySyncWritablePolicy, assertPlainJson, assertPostgresSystemTimestampsCurrent, broadcastCacheInvalidation, buildCascadePlan, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, buildDefaultListOrderBy, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, canonicalizeDataQuery, cascadeReferencesTo, changeEventSubscribersAtCapacity, checkLiveSchemaParity, childAccessorName, classifyDatabaseError, classifyDialectMessage, classifyMethodWireability, classifyParameterWireability, clearRetentionTasks, clone, coerceCustomActionArgument, collectForeignKeyOrphanCounts, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createClassNamePredicate, createDataQueryFingerprint, createDispatchBus, createFilesystemAdapter, createInterceptorContext, createMCPServer, createManifestClassNamePredicate, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, detectEngine, discoverManifestEntry, discoverScopedPackageDirectories, drainChangeFeed, ensureBootstrapSystemTableCompatibility, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeCollectionReadPlan, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getDatabaseEngine, getManifest, getPackageFromQualifiedName, getRetentionTasks, getSQLFromDiff, getSystemTableShapes, getTableVersion, getTenantScopedChangesSince, getTestDatabase, handlePlaybookPreflightRoute, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, importOptionalDependency, invalidateCollectionCache, isAbortedTransactionError, isAdvisoryOnlyChange, isApiActionEnabledForObject, isCrudOperation, isCrudToolAction, isDatabaseInterface, isDeterministicDatabaseError, isEmbeddedDatabase, isFrameworkBaseClass, isFromPackage, isLazyConfigSentinel, isManualOrAdvisoryChange, isNotNullViolationError, isPostgresDatabase, isQualifiedName, isRestActionRoutable, isRestRoutePublic, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isTransientDatabaseError, isType, isUniqueViolationError, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, method, migratePostgresSystemTimestamps, normalizeCustomActionFailure, normalizeDataQueryRequest, normalizeDataQueryResult, normalizeDataQuerySchema, normalizeEventsMaxSubscribers, normalizeOnDelete, normalizeTypedHttpError, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, planForeignKeyCreation, planPostgresPermissions, planPostgresSystemTimestampMigrations, postgresRevisionCandidates, postgresRevisionCondition, processSyncApplyBatch, pruneAiUsage, pruneChangeFeed, pruneExpiredContexts, qualifiedNamesEqual, queryStringDecoderFor, readAgentModuleDocs, readDispatchHealth, readJobHealth, readMethodDecoratorConfig, readMigrationStatus, readPackageAgentDoc, readRecentChanges, readRegistryDrift, readScheduleHealth, readSystemDiagnostics, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerCompatibleMethodDecorator, registerConfigResolver, registerFilesystemAdapterFactory, registerOptionalDependency, registerRetentionTask, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveAgentModuleDocPaths, resolveApiMethodExposure, resolveCustomActionMetadata, resolveDatabase, resolveDbCacheKey, resolveDeclaredScopeMismatch, resolveDispatchTenantId, resolveDispatchTenantScope, resolveEffectiveActionMetadata, resolveGetStringFilter, resolveLazyConfig, resolveListLimit, resolveListOffset, resolveMCPToolListCacheHint, resolvePostgresTimeouts, resolveReadCacheControl, resolveRegisteredObjectName, resolveTenantEtagDiscriminator, restFieldReadPermissions, restMethodForApiAction, runCascadeDelete, runRetentionSweep, runWithTenantGate, safeParse, safeStringify, sanitizeMessagePaths, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, snapshotRegistry, sortMCPTools, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, unregisterRetentionTask, usesEmbeddedRevisionFallback, validatePostgresPermissionContract, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized, withEmbeddedWriteQueue, withEmbeddedWriteTransaction };
|
|
96
|
+
export { AGENT_SURFACE_HASH_PREFIX, AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, BOOTED_PROVENANCE, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CRUD_OPERATIONS, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_DATA_QUERY_PAGE_LIMIT, DEFAULT_DATA_QUERY_RESULT_BYTES, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DEFAULT_FACET_LIMIT, DEFAULT_LEARNING_CONFIG, DEFAULT_LIST_LIMIT, DEFAULT_LIST_ORDER_BY, DEFAULT_POSTGRES_TIMEOUTS, DEFAULT_RETENTION_POLICY, DIAGNOSTICS_DEFAULT_LIMIT, DataQueryValidationError, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, EmbeddingUnavailableError, ErrorUtils, FilesystemError, GlobalInterceptors, LearningMemory, LiveSchemaParityError, MAX_CHANGES_LIMIT, MAX_DATA_QUERY_CURSOR_LENGTH, MAX_DATA_QUERY_FACETS, MAX_DATA_QUERY_FILTERS, MAX_DATA_QUERY_FILTER_DEPTH, MAX_DATA_QUERY_IN_VALUES, MAX_DATA_QUERY_OFFSET, MAX_DATA_QUERY_PAGE_LIMIT, MAX_DATA_QUERY_REQUEST_BYTES, MAX_DATA_QUERY_RESULT_BYTES, MAX_DATA_QUERY_WARNINGS, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_LIST_LIMIT, MAX_STI_READ_SCOPE_TYPES, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, MCP_STABLE_CATALOG_TTL_MS, MODULE_DOC_HASH_PREFIX, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PLAYBOOK_PREFLIGHT_CAPABILITY, PLAYBOOK_PREFLIGHT_ROUTE_SEGMENT, POSTGRES_REVISION_GUARD_EXPRESSION, POSTGRES_TIMEOUT_ENV_VARS, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, QueryBoundsError, QueryOrderByError, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SMRT_CUSTOM_ACTION_ERROR_METADATA_KEY, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SYSTEM_DIAGNOSTICS_TABLES, SYSTEM_TABLE_NAMES, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, appendChanges, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applyPostgresPermissions, applyPostgresRuntimeTimeouts, applySyncWritablePolicy, assertPlainJson, assertPostgresSystemTimestampsCurrent, broadcastCacheInvalidation, buildCascadePlan, buildChangeEventStream, buildCustomActionInputSchema, buildCustomActionInvocationArgs, buildDefaultListOrderBy, buildDomainKnowledgeManifest, buildKnowledgeGraph, bumpChangeFeed, canonicalReadRepresentation, canonicalizeDataQuery, cascadeReferencesTo, changeEventSubscribersAtCapacity, checkKnowledgeGraphFreshness, checkLiveSchemaParity, childAccessorName, classifyDatabaseError, classifyDialectMessage, classifyMethodWireability, classifyParameterWireability, clearRetentionTasks, clone, coerceCustomActionArgument, collectForeignKeyOrphanCounts, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createClassNamePredicate, createDataQueryFingerprint, createDispatchBus, createFilesystemAdapter, createInterceptorContext, createMCPServer, createManifestClassNamePredicate, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, customActionParameterInputName, declaredTypeAcceptsDate, declaresRuntimeRestRoute, declaresRuntimeRestRouteShape, detectEngine, discoverKnowledgeArtifactPaths, discoverManifestEntry, discoverScopedPackageDirectories, drainChangeFeed, ensureBootstrapSystemTableCompatibility, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeCollectionReadPlan, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getDatabaseEngine, getManifest, getPackageFromQualifiedName, getRetentionTasks, getSQLFromDiff, getSystemTableShapes, getTableVersion, getTenantScopedChangesSince, getTestDatabase, handlePlaybookPreflightRoute, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, importOptionalDependency, invalidateCollectionCache, isAbortedTransactionError, isAdvisoryOnlyChange, isApiActionEnabledForObject, isCrudOperation, isCrudToolAction, isDatabaseInterface, isDeterministicDatabaseError, isEmbeddedDatabase, isFrameworkBaseClass, isFromPackage, isLazyConfigSentinel, isManualOrAdvisoryChange, isNotNullViolationError, isPostgresDatabase, isQualifiedName, isRestActionRoutable, isRestRoutePublic, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isTransientDatabaseError, isType, isUniqueViolationError, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, method, migratePostgresSystemTimestamps, normalizeCustomActionFailure, normalizeDataQueryRequest, normalizeDataQueryResult, normalizeDataQuerySchema, normalizeEventsMaxSubscribers, normalizeOnDelete, normalizeTypedHttpError, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, planForeignKeyCreation, planPostgresPermissions, planPostgresSystemTimestampMigrations, postgresRevisionCandidates, postgresRevisionCondition, processSyncApplyBatch, pruneAiUsage, pruneChangeFeed, pruneExpiredContexts, qualifiedNamesEqual, queryStringDecoderFor, readAgentModuleDocs, readDispatchHealth, readJobHealth, readMethodDecoratorConfig, readMigrationStatus, readPackageAgentDoc, readRecentChanges, readRegistryDrift, readScheduleHealth, readSystemDiagnostics, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerCompatibleMethodDecorator, registerConfigResolver, registerFilesystemAdapterFactory, registerOptionalDependency, registerRetentionTask, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveAgentModuleDocPaths, resolveApiMethodExposure, resolveCustomActionMetadata, resolveDatabase, resolveDbCacheKey, resolveDeclaredScopeMismatch, resolveDispatchTenantId, resolveDispatchTenantScope, resolveEffectiveActionMetadata, resolveGetStringFilter, resolveLazyConfig, resolveListLimit, resolveListOffset, resolveMCPToolListCacheHint, resolvePostgresTimeouts, resolveReadCacheControl, resolveRegisteredObjectName, resolveTenantEtagDiscriminator, restFieldReadPermissions, restMethodForApiAction, runCascadeDelete, runRetentionSweep, runWithTenantGate, safeParse, safeStringify, sanitizeMessagePaths, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, snapshotRegistry, sortMCPTools, stableStringify, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, toCustomActionBoolean, toCustomActionDate, toCustomActionNumber, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, unregisterRetentionTask, usesEmbeddedRevisionFallback, validatePostgresPermissionContract, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized, withEmbeddedWriteQueue, withEmbeddedWriteTransaction };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { DomainKnowledgeManifest } from '@happyvertical/smrt-types';
|
|
2
|
+
/**
|
|
3
|
+
* Discovers every currently-buildable per-package artifact path under
|
|
4
|
+
* `<rootDir>/packages/*`, preferring the published `dist/smrt-knowledge.json`
|
|
5
|
+
* over the local dev `.smrt/smrt-knowledge.json` when both exist. Shared by
|
|
6
|
+
* the generator (which needs the manifest content) and the freshness check
|
|
7
|
+
* (which only needs to know whether the current SET of artifacts matches
|
|
8
|
+
* what the graph was built from — a newly built package, or a package that
|
|
9
|
+
* newly started exporting `./smrt-knowledge.json`, must mark the graph stale
|
|
10
|
+
* even though every artifact the graph already knows about is unchanged).
|
|
11
|
+
*/
|
|
12
|
+
export declare function discoverKnowledgeArtifactPaths(rootDir: string): string[];
|
|
13
|
+
/** One package's `smrt-knowledge.json`, located relative to the graph root. */
|
|
14
|
+
export interface KnowledgeGraphInput {
|
|
15
|
+
/** Path to the artifact, relative to the repo root, POSIX-separated. */
|
|
16
|
+
artifactPath: string;
|
|
17
|
+
manifest: DomainKnowledgeManifest;
|
|
18
|
+
}
|
|
19
|
+
export interface KnowledgeGraphPackageNode {
|
|
20
|
+
name: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
artifactPath: string;
|
|
23
|
+
tags: string[];
|
|
24
|
+
risks: string[];
|
|
25
|
+
summary?: string;
|
|
26
|
+
objectCount: number;
|
|
27
|
+
}
|
|
28
|
+
export interface KnowledgeGraphObjectNode {
|
|
29
|
+
/** `<packageName>#<qualifiedName-or-name>`, this graph's stable object id. */
|
|
30
|
+
id: string;
|
|
31
|
+
packageName: string;
|
|
32
|
+
name: string;
|
|
33
|
+
qualifiedName?: string;
|
|
34
|
+
collection: string;
|
|
35
|
+
tableName?: string;
|
|
36
|
+
tableStrategy?: 'cti' | 'sti';
|
|
37
|
+
extends?: string;
|
|
38
|
+
}
|
|
39
|
+
export type KnowledgeGraphEdgeType = 'crossPackageRef' | 'sti' | 'junction' | 'hierarchical' | 'polymorphic' | 'systemTable';
|
|
40
|
+
export interface KnowledgeGraphEdge {
|
|
41
|
+
type: KnowledgeGraphEdgeType;
|
|
42
|
+
/** Object id (or `<packageName>#<extends>`-style unresolved target) this edge starts from. */
|
|
43
|
+
from: string;
|
|
44
|
+
/**
|
|
45
|
+
* Object id the edge points to when resolvable against a scanned object;
|
|
46
|
+
* otherwise the raw declared target (e.g. a qualified name this run did
|
|
47
|
+
* not scan), so the edge is still readable rather than dropped.
|
|
48
|
+
*/
|
|
49
|
+
to?: string;
|
|
50
|
+
/** Declaring field name, for `crossPackageRef` edges. */
|
|
51
|
+
field?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface SmrtKnowledgeGraph {
|
|
54
|
+
schemaVersion: 1;
|
|
55
|
+
generatedAt: string;
|
|
56
|
+
/** `artifactPath -> sha256(content)`, sorted by key; the freshness input. */
|
|
57
|
+
sourceHashes: Record<string, string>;
|
|
58
|
+
packages: KnowledgeGraphPackageNode[];
|
|
59
|
+
objects: KnowledgeGraphObjectNode[];
|
|
60
|
+
edges: KnowledgeGraphEdge[];
|
|
61
|
+
}
|
|
62
|
+
export interface BuildKnowledgeGraphOptions {
|
|
63
|
+
/**
|
|
64
|
+
* The graph currently on disk, when regenerating. `generatedAt` is
|
|
65
|
+
* preserved from it rather than reset to the clock whenever the rebuilt
|
|
66
|
+
* graph is otherwise semantically identical — the same pattern
|
|
67
|
+
* `preserveKnowledgeGeneratedAt` uses for per-package artifacts
|
|
68
|
+
* (`packages/core/src/vite-plugin/index.ts`) — so re-running the generator
|
|
69
|
+
* with nothing merged actually changed does not churn the file (#2872
|
|
70
|
+
* review).
|
|
71
|
+
*/
|
|
72
|
+
previousGraph?: SmrtKnowledgeGraph;
|
|
73
|
+
}
|
|
74
|
+
/** Builds the merged graph. Deterministic: every array is sorted. */
|
|
75
|
+
export declare function buildKnowledgeGraph(inputs: KnowledgeGraphInput[], options?: BuildKnowledgeGraphOptions): SmrtKnowledgeGraph;
|
|
76
|
+
/** Deterministic `JSON.stringify` — object keys are sorted at every level. */
|
|
77
|
+
export declare function stableStringify(value: unknown): string;
|
|
78
|
+
export interface KnowledgeGraphFreshnessIssue {
|
|
79
|
+
severity: 'error' | 'warning';
|
|
80
|
+
code: 'missing-knowledge-graph' | 'stale-knowledge-graph' | 'knowledge-graph-source-missing';
|
|
81
|
+
message: string;
|
|
82
|
+
file?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Checks the merged graph's recorded `sourceHashes` against the current
|
|
86
|
+
* content of each per-package artifact it was built from — the same pattern
|
|
87
|
+
* `knowledge.ts`/`smrt-dev-mcp` use for a single package's `sourceHashes`.
|
|
88
|
+
*
|
|
89
|
+
* `graphPath` and each `artifactPath` in the graph are resolved relative to
|
|
90
|
+
* `rootDir` (the repo root).
|
|
91
|
+
*/
|
|
92
|
+
export declare function checkKnowledgeGraphFreshness(rootDir: string, graphPath: string, options?: {
|
|
93
|
+
requireArtifact?: boolean;
|
|
94
|
+
}): KnowledgeGraphFreshnessIssue[];
|
|
95
|
+
//# sourceMappingURL=knowledge-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-graph.d.ts","sourceRoot":"","sources":["../src/knowledge-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EACV,uBAAuB,EAExB,MAAM,2BAA2B,CAAC;AAEnC;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAexE;AAED,+EAA+E;AAC/E,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;CACnC;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,sBAAsB,GAC9B,iBAAiB,GACjB,KAAK,GACL,UAAU,GACV,cAAc,GACd,aAAa,GACb,aAAa,CAAC;AAElB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,8FAA8F;IAC9F,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,CAAC,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAUD,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,EAAE,EAC7B,OAAO,GAAE,0BAA+B,GACvC,kBAAkB,CAoKpB;AAOD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD;AAoBD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,EACA,yBAAyB,GACzB,uBAAuB,GACvB,gCAAgC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,4BAA4B,EAAE,CA0FhC"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
//#region src/knowledge-graph.ts
|
|
5
|
+
/**
|
|
6
|
+
* Cross-package knowledge graph (#2863).
|
|
7
|
+
*
|
|
8
|
+
* Each package's own `smrt-knowledge.json` (see `knowledge.ts`) is a
|
|
9
|
+
* per-package projection. Nothing merges them, so cross-package invariants
|
|
10
|
+
* that the scanner manifest already carries — `@crossPackageRef` targets, STI
|
|
11
|
+
* parent/child pairs, junction/hierarchical/polymorphic base usage, and
|
|
12
|
+
* `_smrt_` system-table ownership — live only as prose in the root
|
|
13
|
+
* `AGENTS.md`. This module builds the merged, deterministic root artifact
|
|
14
|
+
* (`.smrt/smrt-knowledge-graph.json`) from whichever per-package artifacts
|
|
15
|
+
* are present, and checks it for staleness the same way `knowledge.ts` checks
|
|
16
|
+
* a package artifact: by comparing recorded `sourceHashes` against the
|
|
17
|
+
* current file content.
|
|
18
|
+
*
|
|
19
|
+
* This module never invents an edge: every edge is a direct read of a field
|
|
20
|
+
* already emitted into a package's `DomainKnowledgeManifest` (`related`,
|
|
21
|
+
* `tableStrategy`, `extends`, `relationshipFeatures`, `tableName`).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Discovers every currently-buildable per-package artifact path under
|
|
25
|
+
* `<rootDir>/packages/*`, preferring the published `dist/smrt-knowledge.json`
|
|
26
|
+
* over the local dev `.smrt/smrt-knowledge.json` when both exist. Shared by
|
|
27
|
+
* the generator (which needs the manifest content) and the freshness check
|
|
28
|
+
* (which only needs to know whether the current SET of artifacts matches
|
|
29
|
+
* what the graph was built from — a newly built package, or a package that
|
|
30
|
+
* newly started exporting `./smrt-knowledge.json`, must mark the graph stale
|
|
31
|
+
* even though every artifact the graph already knows about is unchanged).
|
|
32
|
+
*/
|
|
33
|
+
function discoverKnowledgeArtifactPaths(rootDir) {
|
|
34
|
+
const packagesDir = join(rootDir, "packages");
|
|
35
|
+
if (!existsSync(packagesDir)) return [];
|
|
36
|
+
const paths = [];
|
|
37
|
+
for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {
|
|
38
|
+
if (!entry.isDirectory()) continue;
|
|
39
|
+
const packageDir = join(packagesDir, entry.name);
|
|
40
|
+
const found = [join(packageDir, "dist", "smrt-knowledge.json"), join(packageDir, ".smrt", "smrt-knowledge.json")].find((path) => existsSync(path));
|
|
41
|
+
if (found) paths.push(relative(rootDir, found).split("\\").join("/"));
|
|
42
|
+
}
|
|
43
|
+
return paths.sort();
|
|
44
|
+
}
|
|
45
|
+
function hashContent(content) {
|
|
46
|
+
return createHash("sha256").update(content).digest("hex");
|
|
47
|
+
}
|
|
48
|
+
function objectId(packageName, object) {
|
|
49
|
+
return `${packageName}#${object.qualifiedName ?? object.name}`;
|
|
50
|
+
}
|
|
51
|
+
/** Builds the merged graph. Deterministic: every array is sorted. */
|
|
52
|
+
function buildKnowledgeGraph(inputs, options = {}) {
|
|
53
|
+
const sortedInputs = [...inputs].sort((a, b) => a.artifactPath.localeCompare(b.artifactPath));
|
|
54
|
+
const sourceHashes = {};
|
|
55
|
+
const packages = [];
|
|
56
|
+
const objects = [];
|
|
57
|
+
const edges = [];
|
|
58
|
+
const idByQualifiedName = /* @__PURE__ */ new Map();
|
|
59
|
+
const idBySimpleName = /* @__PURE__ */ new Map();
|
|
60
|
+
const idBySimpleNamePerPackage = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const input of sortedInputs) {
|
|
62
|
+
const manifestPackageName = input.manifest.packageName ?? input.artifactPath;
|
|
63
|
+
for (const object of input.manifest.objects) {
|
|
64
|
+
const packageName = object.packageName ?? manifestPackageName;
|
|
65
|
+
const perPackage = idBySimpleNamePerPackage.get(packageName) ?? /* @__PURE__ */ new Map();
|
|
66
|
+
const id = objectId(packageName, object);
|
|
67
|
+
idByQualifiedName.set(object.qualifiedName ?? id, id);
|
|
68
|
+
const bySimple = idBySimpleName.get(object.name) ?? /* @__PURE__ */ new Set();
|
|
69
|
+
bySimple.add(id);
|
|
70
|
+
idBySimpleName.set(object.name, bySimple);
|
|
71
|
+
perPackage.set(object.name, id);
|
|
72
|
+
idBySimpleNamePerPackage.set(packageName, perPackage);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const resolveTarget = (raw, declaringPackageName) => {
|
|
76
|
+
if (!raw) return void 0;
|
|
77
|
+
if (idByQualifiedName.has(raw)) return idByQualifiedName.get(raw);
|
|
78
|
+
if (declaringPackageName) {
|
|
79
|
+
const sameId = idBySimpleNamePerPackage.get(declaringPackageName)?.get(raw);
|
|
80
|
+
if (sameId) return sameId;
|
|
81
|
+
}
|
|
82
|
+
const bySimple = idBySimpleName.get(raw);
|
|
83
|
+
if (bySimple && bySimple.size === 1) return [...bySimple][0];
|
|
84
|
+
};
|
|
85
|
+
const seenObjectIds = /* @__PURE__ */ new Set();
|
|
86
|
+
for (const input of sortedInputs) {
|
|
87
|
+
sourceHashes[input.artifactPath] = hashContent(stableStringify(input.manifest));
|
|
88
|
+
const packageName = input.manifest.packageName ?? input.artifactPath;
|
|
89
|
+
packages.push({
|
|
90
|
+
name: packageName,
|
|
91
|
+
version: input.manifest.packageVersion,
|
|
92
|
+
artifactPath: input.artifactPath,
|
|
93
|
+
tags: [...input.manifest.tags].sort(),
|
|
94
|
+
risks: [...input.manifest.risks].sort(),
|
|
95
|
+
summary: input.manifest.summary,
|
|
96
|
+
objectCount: input.manifest.objects.length
|
|
97
|
+
});
|
|
98
|
+
for (const object of input.manifest.objects) {
|
|
99
|
+
const objectPackageName = object.packageName ?? packageName;
|
|
100
|
+
const id = objectId(objectPackageName, object);
|
|
101
|
+
if (!seenObjectIds.has(id)) {
|
|
102
|
+
seenObjectIds.add(id);
|
|
103
|
+
objects.push({
|
|
104
|
+
id,
|
|
105
|
+
packageName: objectPackageName,
|
|
106
|
+
name: object.name,
|
|
107
|
+
qualifiedName: object.qualifiedName,
|
|
108
|
+
collection: object.collection,
|
|
109
|
+
tableName: object.tableName,
|
|
110
|
+
tableStrategy: object.tableStrategy,
|
|
111
|
+
extends: object.extends
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
for (const field of object.relationships) {
|
|
115
|
+
if (field.type !== "crossPackageRef") continue;
|
|
116
|
+
edges.push({
|
|
117
|
+
type: "crossPackageRef",
|
|
118
|
+
from: id,
|
|
119
|
+
to: resolveTarget(field.related) ?? field.related,
|
|
120
|
+
field: field.name
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (object.tableStrategy === "sti" && object.extends) edges.push({
|
|
124
|
+
type: "sti",
|
|
125
|
+
from: id,
|
|
126
|
+
to: resolveTarget(object.extends, objectPackageName) ?? `${objectPackageName}#${object.extends}`
|
|
127
|
+
});
|
|
128
|
+
for (const feature of object.relationshipFeatures) if (feature === "SmrtJunction") edges.push({
|
|
129
|
+
type: "junction",
|
|
130
|
+
from: id
|
|
131
|
+
});
|
|
132
|
+
else if (feature === "SmrtHierarchical") edges.push({
|
|
133
|
+
type: "hierarchical",
|
|
134
|
+
from: id
|
|
135
|
+
});
|
|
136
|
+
else if (feature === "SmrtPolymorphicAssociation") edges.push({
|
|
137
|
+
type: "polymorphic",
|
|
138
|
+
from: id
|
|
139
|
+
});
|
|
140
|
+
if (object.tableName?.startsWith("_smrt_")) edges.push({
|
|
141
|
+
type: "systemTable",
|
|
142
|
+
from: id,
|
|
143
|
+
to: object.tableName
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
148
|
+
objects.sort((a, b) => a.id.localeCompare(b.id));
|
|
149
|
+
edges.sort((a, b) => {
|
|
150
|
+
const key = (edge) => `${edge.type}|${edge.from}|${edge.to ?? ""}|${edge.field ?? ""}`;
|
|
151
|
+
return key(a).localeCompare(key(b));
|
|
152
|
+
});
|
|
153
|
+
const graph = {
|
|
154
|
+
schemaVersion: 1,
|
|
155
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
156
|
+
sourceHashes: sortRecord(sourceHashes),
|
|
157
|
+
packages,
|
|
158
|
+
objects,
|
|
159
|
+
edges
|
|
160
|
+
};
|
|
161
|
+
const { previousGraph } = options;
|
|
162
|
+
if (previousGraph && semanticGraphJson(previousGraph) === semanticGraphJson(graph)) return {
|
|
163
|
+
...graph,
|
|
164
|
+
generatedAt: previousGraph.generatedAt
|
|
165
|
+
};
|
|
166
|
+
return graph;
|
|
167
|
+
}
|
|
168
|
+
function semanticGraphJson(graph) {
|
|
169
|
+
const { generatedAt: _generatedAt, ...rest } = graph;
|
|
170
|
+
return stableStringify(rest);
|
|
171
|
+
}
|
|
172
|
+
/** Deterministic `JSON.stringify` — object keys are sorted at every level. */
|
|
173
|
+
function stableStringify(value) {
|
|
174
|
+
return JSON.stringify(sortValue(value));
|
|
175
|
+
}
|
|
176
|
+
function sortValue(value) {
|
|
177
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
178
|
+
if (value && typeof value === "object") {
|
|
179
|
+
const sorted = {};
|
|
180
|
+
for (const key of Object.keys(value).sort()) sorted[key] = sortValue(value[key]);
|
|
181
|
+
return sorted;
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function sortRecord(record) {
|
|
186
|
+
const sorted = {};
|
|
187
|
+
for (const key of Object.keys(record).sort()) sorted[key] = record[key];
|
|
188
|
+
return sorted;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Checks the merged graph's recorded `sourceHashes` against the current
|
|
192
|
+
* content of each per-package artifact it was built from — the same pattern
|
|
193
|
+
* `knowledge.ts`/`smrt-dev-mcp` use for a single package's `sourceHashes`.
|
|
194
|
+
*
|
|
195
|
+
* `graphPath` and each `artifactPath` in the graph are resolved relative to
|
|
196
|
+
* `rootDir` (the repo root).
|
|
197
|
+
*/
|
|
198
|
+
function checkKnowledgeGraphFreshness(rootDir, graphPath, options = {}) {
|
|
199
|
+
const issues = [];
|
|
200
|
+
const absoluteGraphPath = `${rootDir}/${graphPath}`;
|
|
201
|
+
if (!existsSync(absoluteGraphPath)) {
|
|
202
|
+
if (!options.requireArtifact) return issues;
|
|
203
|
+
issues.push({
|
|
204
|
+
severity: "error",
|
|
205
|
+
code: "missing-knowledge-graph",
|
|
206
|
+
message: `${graphPath} does not exist; run \`pnpm knowledge:graph\``,
|
|
207
|
+
file: graphPath
|
|
208
|
+
});
|
|
209
|
+
return issues;
|
|
210
|
+
}
|
|
211
|
+
let graph;
|
|
212
|
+
try {
|
|
213
|
+
graph = JSON.parse(readFileSync(absoluteGraphPath, "utf8"));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
issues.push({
|
|
216
|
+
severity: "error",
|
|
217
|
+
code: "stale-knowledge-graph",
|
|
218
|
+
message: `${graphPath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
219
|
+
file: graphPath
|
|
220
|
+
});
|
|
221
|
+
return issues;
|
|
222
|
+
}
|
|
223
|
+
const recordedPaths = Object.keys(graph.sourceHashes ?? {});
|
|
224
|
+
for (const [artifactPath, expectedHash] of Object.entries(graph.sourceHashes ?? {})) {
|
|
225
|
+
const absoluteArtifactPath = `${rootDir}/${artifactPath}`;
|
|
226
|
+
if (!existsSync(absoluteArtifactPath)) {
|
|
227
|
+
issues.push({
|
|
228
|
+
severity: "error",
|
|
229
|
+
code: "knowledge-graph-source-missing",
|
|
230
|
+
message: `${artifactPath} used to build ${graphPath} no longer exists`,
|
|
231
|
+
file: graphPath
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
let manifest;
|
|
236
|
+
try {
|
|
237
|
+
manifest = JSON.parse(readFileSync(absoluteArtifactPath, "utf8"));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
issues.push({
|
|
240
|
+
severity: "error",
|
|
241
|
+
code: "stale-knowledge-graph",
|
|
242
|
+
message: `${artifactPath} is not valid JSON (${error instanceof Error ? error.message : String(error)}); run \`pnpm knowledge:graph\``,
|
|
243
|
+
file: graphPath
|
|
244
|
+
});
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (hashContent(stableStringify(manifest)) !== expectedHash) issues.push({
|
|
248
|
+
severity: "error",
|
|
249
|
+
code: "stale-knowledge-graph",
|
|
250
|
+
message: `${artifactPath} changed since ${graphPath} was generated; run \`pnpm knowledge:graph\``,
|
|
251
|
+
file: graphPath
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const currentPaths = discoverKnowledgeArtifactPaths(rootDir);
|
|
255
|
+
const recordedSet = new Set(recordedPaths);
|
|
256
|
+
const newPaths = currentPaths.filter((path) => !recordedSet.has(path));
|
|
257
|
+
if (newPaths.length > 0) issues.push({
|
|
258
|
+
severity: "error",
|
|
259
|
+
code: "stale-knowledge-graph",
|
|
260
|
+
message: `${newPaths.join(", ")} ${newPaths.length === 1 ? "is a new package artifact" : "are new package artifacts"} not yet merged into ${graphPath}; run \`pnpm knowledge:graph\``,
|
|
261
|
+
file: graphPath
|
|
262
|
+
});
|
|
263
|
+
return issues;
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
export { buildKnowledgeGraph, checkKnowledgeGraphFreshness, discoverKnowledgeArtifactPaths, stableStringify };
|
|
267
|
+
|
|
268
|
+
//# sourceMappingURL=knowledge-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-graph.js","names":[],"sources":["../src/knowledge-graph.ts"],"sourcesContent":["/**\n * Cross-package knowledge graph (#2863).\n *\n * Each package's own `smrt-knowledge.json` (see `knowledge.ts`) is a\n * per-package projection. Nothing merges them, so cross-package invariants\n * that the scanner manifest already carries — `@crossPackageRef` targets, STI\n * parent/child pairs, junction/hierarchical/polymorphic base usage, and\n * `_smrt_` system-table ownership — live only as prose in the root\n * `AGENTS.md`. This module builds the merged, deterministic root artifact\n * (`.smrt/smrt-knowledge-graph.json`) from whichever per-package artifacts\n * are present, and checks it for staleness the same way `knowledge.ts` checks\n * a package artifact: by comparing recorded `sourceHashes` against the\n * current file content.\n *\n * This module never invents an edge: every edge is a direct read of a field\n * already emitted into a package's `DomainKnowledgeManifest` (`related`,\n * `tableStrategy`, `extends`, `relationshipFeatures`, `tableName`).\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport type {\n DomainKnowledgeManifest,\n DomainKnowledgeObject,\n} from '@happyvertical/smrt-types';\n\n/**\n * Discovers every currently-buildable per-package artifact path under\n * `<rootDir>/packages/*`, preferring the published `dist/smrt-knowledge.json`\n * over the local dev `.smrt/smrt-knowledge.json` when both exist. Shared by\n * the generator (which needs the manifest content) and the freshness check\n * (which only needs to know whether the current SET of artifacts matches\n * what the graph was built from — a newly built package, or a package that\n * newly started exporting `./smrt-knowledge.json`, must mark the graph stale\n * even though every artifact the graph already knows about is unchanged).\n */\nexport function discoverKnowledgeArtifactPaths(rootDir: string): string[] {\n const packagesDir = join(rootDir, 'packages');\n if (!existsSync(packagesDir)) return [];\n const paths: string[] = [];\n for (const entry of readdirSync(packagesDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const packageDir = join(packagesDir, entry.name);\n const candidates = [\n join(packageDir, 'dist', 'smrt-knowledge.json'),\n join(packageDir, '.smrt', 'smrt-knowledge.json'),\n ];\n const found = candidates.find((path) => existsSync(path));\n if (found) paths.push(relative(rootDir, found).split('\\\\').join('/'));\n }\n return paths.sort();\n}\n\n/** One package's `smrt-knowledge.json`, located relative to the graph root. */\nexport interface KnowledgeGraphInput {\n /** Path to the artifact, relative to the repo root, POSIX-separated. */\n artifactPath: string;\n manifest: DomainKnowledgeManifest;\n}\n\nexport interface KnowledgeGraphPackageNode {\n name: string;\n version?: string;\n artifactPath: string;\n tags: string[];\n risks: string[];\n summary?: string;\n objectCount: number;\n}\n\nexport interface KnowledgeGraphObjectNode {\n /** `<packageName>#<qualifiedName-or-name>`, this graph's stable object id. */\n id: string;\n packageName: string;\n name: string;\n qualifiedName?: string;\n collection: string;\n tableName?: string;\n tableStrategy?: 'cti' | 'sti';\n extends?: string;\n}\n\nexport type KnowledgeGraphEdgeType =\n | 'crossPackageRef'\n | 'sti'\n | 'junction'\n | 'hierarchical'\n | 'polymorphic'\n | 'systemTable';\n\nexport interface KnowledgeGraphEdge {\n type: KnowledgeGraphEdgeType;\n /** Object id (or `<packageName>#<extends>`-style unresolved target) this edge starts from. */\n from: string;\n /**\n * Object id the edge points to when resolvable against a scanned object;\n * otherwise the raw declared target (e.g. a qualified name this run did\n * not scan), so the edge is still readable rather than dropped.\n */\n to?: string;\n /** Declaring field name, for `crossPackageRef` edges. */\n field?: string;\n}\n\nexport interface SmrtKnowledgeGraph {\n schemaVersion: 1;\n generatedAt: string;\n /** `artifactPath -> sha256(content)`, sorted by key; the freshness input. */\n sourceHashes: Record<string, string>;\n packages: KnowledgeGraphPackageNode[];\n objects: KnowledgeGraphObjectNode[];\n edges: KnowledgeGraphEdge[];\n}\n\nfunction hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nfunction objectId(packageName: string, object: DomainKnowledgeObject): string {\n return `${packageName}#${object.qualifiedName ?? object.name}`;\n}\n\nexport interface BuildKnowledgeGraphOptions {\n /**\n * The graph currently on disk, when regenerating. `generatedAt` is\n * preserved from it rather than reset to the clock whenever the rebuilt\n * graph is otherwise semantically identical — the same pattern\n * `preserveKnowledgeGeneratedAt` uses for per-package artifacts\n * (`packages/core/src/vite-plugin/index.ts`) — so re-running the generator\n * with nothing merged actually changed does not churn the file (#2872\n * review).\n */\n previousGraph?: SmrtKnowledgeGraph;\n}\n\n/** Builds the merged graph. Deterministic: every array is sorted. */\nexport function buildKnowledgeGraph(\n inputs: KnowledgeGraphInput[],\n options: BuildKnowledgeGraphOptions = {},\n): SmrtKnowledgeGraph {\n const sortedInputs = [...inputs].sort((a, b) =>\n a.artifactPath.localeCompare(b.artifactPath),\n );\n\n const sourceHashes: Record<string, string> = {};\n const packages: KnowledgeGraphPackageNode[] = [];\n const objects: KnowledgeGraphObjectNode[] = [];\n const edges: KnowledgeGraphEdge[] = [];\n\n // Qualified-name -> object id, so a crossPackageRef/STI target declared in\n // one package can be resolved to the node emitted by whichever package\n // scanned it, regardless of scan order.\n const idByQualifiedName = new Map<string, string>();\n // Distinct ids per simple name, not a plain array: the same external\n // object can appear in more than one input (e.g. a package artifact and a\n // smrtConsumer aggregate that also scanned it), and counting the\n // duplicate would make resolveTarget's `.length === 1` uniqueness check\n // see a false ambiguity for an otherwise-unique name (#2872 review).\n const idBySimpleName = new Map<string, Set<string>>();\n // A `tableStrategy: 'sti'` object's `extends` is an unqualified simple name\n // (its own package's base class, never a cross-package reference — STI\n // shares one table within a package), so a same-package simple-name match\n // must win over an ambiguous or wrong-package GLOBAL simple-name match:\n // two packages both declaring `Account` must not point one package's\n // subclass at the other package's `Account` (#2863 review).\n const idBySimpleNamePerPackage = new Map<string, Map<string, string>>();\n\n for (const input of sortedInputs) {\n const manifestPackageName =\n input.manifest.packageName ?? input.artifactPath;\n for (const object of input.manifest.objects) {\n // A merged consumer manifest (`smrtConsumer`) aggregates objects\n // scanned from multiple external packages under one local artifact;\n // each object then carries its own `packageName` and that must own\n // the id, or every external object misattributes to the local\n // project and edges resolve to the wrong node (#2872 review).\n const packageName = object.packageName ?? manifestPackageName;\n const perPackage =\n idBySimpleNamePerPackage.get(packageName) ?? new Map<string, string>();\n const id = objectId(packageName, object);\n idByQualifiedName.set(object.qualifiedName ?? id, id);\n const bySimple = idBySimpleName.get(object.name) ?? new Set<string>();\n bySimple.add(id);\n idBySimpleName.set(object.name, bySimple);\n perPackage.set(object.name, id);\n idBySimpleNamePerPackage.set(packageName, perPackage);\n }\n }\n\n const resolveTarget = (\n raw: string | undefined,\n declaringPackageName?: string,\n ): string | undefined => {\n if (!raw) return undefined;\n if (idByQualifiedName.has(raw)) return idByQualifiedName.get(raw);\n if (declaringPackageName) {\n const sameId = idBySimpleNamePerPackage\n .get(declaringPackageName)\n ?.get(raw);\n if (sameId) return sameId;\n }\n const bySimple = idBySimpleName.get(raw);\n if (bySimple && bySimple.size === 1) return [...bySimple][0];\n return undefined;\n };\n\n const seenObjectIds = new Set<string>();\n for (const input of sortedInputs) {\n sourceHashes[input.artifactPath] = hashContent(\n stableStringify(input.manifest),\n );\n const packageName = input.manifest.packageName ?? input.artifactPath;\n\n packages.push({\n name: packageName,\n version: input.manifest.packageVersion,\n artifactPath: input.artifactPath,\n tags: [...input.manifest.tags].sort(),\n risks: [...input.manifest.risks].sort(),\n summary: input.manifest.summary,\n objectCount: input.manifest.objects.length,\n });\n\n for (const object of input.manifest.objects) {\n // See the id-map pass above: an aggregated consumer manifest attributes\n // each object to its own scanned package, not the local project.\n const objectPackageName = object.packageName ?? packageName;\n const id = objectId(objectPackageName, object);\n if (!seenObjectIds.has(id)) {\n seenObjectIds.add(id);\n objects.push({\n id,\n packageName: objectPackageName,\n name: object.name,\n qualifiedName: object.qualifiedName,\n collection: object.collection,\n tableName: object.tableName,\n tableStrategy: object.tableStrategy,\n extends: object.extends,\n });\n }\n\n for (const field of object.relationships) {\n if (field.type !== 'crossPackageRef') continue;\n edges.push({\n type: 'crossPackageRef',\n from: id,\n to: resolveTarget(field.related) ?? field.related,\n field: field.name,\n });\n }\n\n if (object.tableStrategy === 'sti' && object.extends) {\n edges.push({\n type: 'sti',\n from: id,\n to:\n resolveTarget(object.extends, objectPackageName) ??\n `${objectPackageName}#${object.extends}`,\n });\n }\n\n for (const feature of object.relationshipFeatures) {\n if (feature === 'SmrtJunction') {\n edges.push({ type: 'junction', from: id });\n } else if (feature === 'SmrtHierarchical') {\n edges.push({ type: 'hierarchical', from: id });\n } else if (feature === 'SmrtPolymorphicAssociation') {\n edges.push({ type: 'polymorphic', from: id });\n }\n }\n\n if (object.tableName?.startsWith('_smrt_')) {\n edges.push({ type: 'systemTable', from: id, to: object.tableName });\n }\n }\n }\n\n packages.sort((a, b) => a.name.localeCompare(b.name));\n objects.sort((a, b) => a.id.localeCompare(b.id));\n edges.sort((a, b) => {\n const key = (edge: KnowledgeGraphEdge) =>\n `${edge.type}|${edge.from}|${edge.to ?? ''}|${edge.field ?? ''}`;\n return key(a).localeCompare(key(b));\n });\n\n const graph: SmrtKnowledgeGraph = {\n schemaVersion: 1,\n generatedAt: new Date().toISOString(),\n sourceHashes: sortRecord(sourceHashes),\n packages,\n objects,\n edges,\n };\n\n const { previousGraph } = options;\n if (\n previousGraph &&\n semanticGraphJson(previousGraph) === semanticGraphJson(graph)\n ) {\n return { ...graph, generatedAt: previousGraph.generatedAt };\n }\n return graph;\n}\n\nfunction semanticGraphJson(graph: SmrtKnowledgeGraph): string {\n const { generatedAt: _generatedAt, ...rest } = graph;\n return stableStringify(rest);\n}\n\n/** Deterministic `JSON.stringify` — object keys are sorted at every level. */\nexport function stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortValue);\n if (value && typeof value === 'object') {\n const sorted: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[key] = sortValue((value as Record<string, unknown>)[key]);\n }\n return sorted;\n }\n return value;\n}\n\nfunction sortRecord(record: Record<string, string>): Record<string, string> {\n const sorted: Record<string, string> = {};\n for (const key of Object.keys(record).sort()) sorted[key] = record[key];\n return sorted;\n}\n\nexport interface KnowledgeGraphFreshnessIssue {\n severity: 'error' | 'warning';\n code:\n | 'missing-knowledge-graph'\n | 'stale-knowledge-graph'\n | 'knowledge-graph-source-missing';\n message: string;\n file?: string;\n}\n\n/**\n * Checks the merged graph's recorded `sourceHashes` against the current\n * content of each per-package artifact it was built from — the same pattern\n * `knowledge.ts`/`smrt-dev-mcp` use for a single package's `sourceHashes`.\n *\n * `graphPath` and each `artifactPath` in the graph are resolved relative to\n * `rootDir` (the repo root).\n */\nexport function checkKnowledgeGraphFreshness(\n rootDir: string,\n graphPath: string,\n options: { requireArtifact?: boolean } = {},\n): KnowledgeGraphFreshnessIssue[] {\n const issues: KnowledgeGraphFreshnessIssue[] = [];\n const absoluteGraphPath = `${rootDir}/${graphPath}`;\n if (!existsSync(absoluteGraphPath)) {\n if (!options.requireArtifact) return issues;\n issues.push({\n severity: 'error',\n code: 'missing-knowledge-graph',\n message: `${graphPath} does not exist; run \\`pnpm knowledge:graph\\``,\n file: graphPath,\n });\n return issues;\n }\n\n let graph: SmrtKnowledgeGraph;\n try {\n graph = JSON.parse(readFileSync(absoluteGraphPath, 'utf8'));\n } catch (error) {\n issues.push({\n severity: 'error',\n code: 'stale-knowledge-graph',\n message: `${graphPath} is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n file: graphPath,\n });\n return issues;\n }\n\n const recordedPaths = Object.keys(graph.sourceHashes ?? {});\n for (const [artifactPath, expectedHash] of Object.entries(\n graph.sourceHashes ?? {},\n )) {\n const absoluteArtifactPath = `${rootDir}/${artifactPath}`;\n if (!existsSync(absoluteArtifactPath)) {\n issues.push({\n severity: 'error',\n code: 'knowledge-graph-source-missing',\n message: `${artifactPath} used to build ${graphPath} no longer exists`,\n file: graphPath,\n });\n continue;\n }\n let manifest: unknown;\n try {\n manifest = JSON.parse(readFileSync(absoluteArtifactPath, 'utf8'));\n } catch (error) {\n issues.push({\n severity: 'error',\n code: 'stale-knowledge-graph',\n message: `${artifactPath} is not valid JSON (${\n error instanceof Error ? error.message : String(error)\n }); run \\`pnpm knowledge:graph\\``,\n file: graphPath,\n });\n continue;\n }\n const actualHash = hashContent(stableStringify(manifest));\n if (actualHash !== expectedHash) {\n issues.push({\n severity: 'error',\n code: 'stale-knowledge-graph',\n message: `${artifactPath} changed since ${graphPath} was generated; run \\`pnpm knowledge:graph\\``,\n file: graphPath,\n });\n }\n }\n\n // A per-key hash comparison alone cannot see an ADDED artifact: a package\n // that just finished its first build, or just started exporting\n // `./smrt-knowledge.json`, contributes a path the graph never recorded, so\n // every recorded hash still matches. Compare the current discoverable SET\n // against the recorded set to catch that (#2863 review).\n const currentPaths = discoverKnowledgeArtifactPaths(rootDir);\n const recordedSet = new Set(recordedPaths);\n const newPaths = currentPaths.filter((path) => !recordedSet.has(path));\n if (newPaths.length > 0) {\n issues.push({\n severity: 'error',\n code: 'stale-knowledge-graph',\n message: `${newPaths.join(', ')} ${\n newPaths.length === 1\n ? 'is a new package artifact'\n : 'are new package artifacts'\n } not yet merged into ${graphPath}; run \\`pnpm knowledge:graph\\``,\n file: graphPath,\n });\n }\n\n return issues;\n}\n"],"mappings":"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"}
|
package/dist/knowledge.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DomainKnowledgeAgentSurface, DomainKnowledgeConfig, DomainKnowledgeManifest, DomainKnowledgeModuleDoc } from '@happyvertical/smrt-types';
|
|
2
2
|
import { SmartObjectManifest } from './scanner/types.js';
|
|
3
3
|
export { discoverScopedPackageDirectories, readPackageAgentDoc, type ScopedPackageDirectory, } from './knowledge-discovery.js';
|
|
4
|
+
export { buildKnowledgeGraph, checkKnowledgeGraphFreshness, discoverKnowledgeArtifactPaths, type KnowledgeGraphEdge, type KnowledgeGraphEdgeType, type KnowledgeGraphFreshnessIssue, type KnowledgeGraphInput, type KnowledgeGraphObjectNode, type KnowledgeGraphPackageNode, type SmrtKnowledgeGraph, stableStringify, } from './knowledge-graph.js';
|
|
4
5
|
/**
|
|
5
6
|
* Minimal package.json shape consumed by the knowledge builder.
|
|
6
7
|
* `name`/`version` are typed concretely because they flow into the manifest's
|
package/dist/knowledge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../src/knowledge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,mBAAmB,EACnB,KAAK,sBAAsB,GAC5B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../src/knowledge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,mBAAmB,EACnB,KAAK,sBAAsB,GAC5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,8BAA8B,EAC9B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,KAAK,EACV,2BAA2B,EAC3B,qBAAqB,EAGrB,uBAAuB,EAEvB,wBAAwB,EAKzB,MAAM,2BAA2B,CAAC;AAanC,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,oBAAoB,CAAC;AAG5B;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,2BAA2B,CAAC;CAC5C;AAmDD,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,eAAe,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,EAAE,CAgBV;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,wBAAwB,EAAE,CAM5B;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,2BAA2B,GACnC,uBAAuB,CA0FzB"}
|
package/dist/knowledge.js
CHANGED
|
@@ -2,6 +2,7 @@ import { toSnakeCase } from "./utils/naming.js";
|
|
|
2
2
|
import { CRUD_OPERATIONS, createManifestClassNamePredicate, isCrudOperation, isCrudToolAction, isFrameworkLifecycleMethod, resolveApiMethodExposure, resolveCustomActionMetadata, resolveEffectiveActionMetadata } from "./generators/custom-action.js";
|
|
3
3
|
import { isFrameworkBaseClass } from "./registry/framework-base-classes.js";
|
|
4
4
|
import { discoverScopedPackageDirectories, readPackageAgentDoc } from "./knowledge-discovery.js";
|
|
5
|
+
import { buildKnowledgeGraph, checkKnowledgeGraphFreshness, discoverKnowledgeArtifactPaths, stableStringify } from "./knowledge-graph.js";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
7
8
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
@@ -113,6 +114,7 @@ function buildDomainKnowledgeManifest(options) {
|
|
|
113
114
|
const surfaces = objects.flatMap((object) => object.surfaces);
|
|
114
115
|
const manifestJson = stableJson(normalizeManifestForHash(options.manifest));
|
|
115
116
|
const agentSurface = normalizeAgentSurface(options.agentSurface);
|
|
117
|
+
const relationshipsV2 = summarizeRelationships(objects, manifestObjects);
|
|
116
118
|
return {
|
|
117
119
|
schemaVersion: 1,
|
|
118
120
|
sensitiveFieldsExcluded: true,
|
|
@@ -132,13 +134,17 @@ function buildDomainKnowledgeManifest(options) {
|
|
|
132
134
|
dependencies: allDependencies,
|
|
133
135
|
smrtDependencies: Object.keys(allDependencies).filter((dep) => dep.startsWith("@happyvertical/smrt-")).sort(),
|
|
134
136
|
sdkDependencies: Object.keys(allDependencies).filter((dep) => SDK_PACKAGE_NAMES.has(dep)).sort(),
|
|
135
|
-
tags: options.config?.tags ??
|
|
137
|
+
tags: options.config?.tags ?? deriveDefaultTags(packageJson, allDependencies),
|
|
136
138
|
summary: options.config?.summary,
|
|
137
|
-
risks: options.config?.risks ??
|
|
139
|
+
risks: options.config?.risks ?? deriveDefaultRisks({
|
|
140
|
+
sensitiveFieldsExcluded: true,
|
|
141
|
+
relationshipsV2,
|
|
142
|
+
objects
|
|
143
|
+
}),
|
|
138
144
|
objects,
|
|
139
145
|
surfaces,
|
|
140
146
|
prompts: options.config?.includePrompts === false ? [] : readPrompts(rootDir),
|
|
141
|
-
relationshipsV2
|
|
147
|
+
relationshipsV2,
|
|
142
148
|
agentDoc,
|
|
143
149
|
moduleDocs: includeDocs && moduleDocPaths.length > 0 ? readAgentModuleDocs(rootDir, agentDocContent) : void 0,
|
|
144
150
|
agentSurface
|
|
@@ -681,6 +687,53 @@ function summarizeRelationships(objects, manifestObjects) {
|
|
|
681
687
|
uuidColumns: manifestObjects.reduce((count, object) => count + Object.values(object.schema?.columns ?? {}).filter((column) => column.type === "UUID").length, 0)
|
|
682
688
|
};
|
|
683
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* Documented default `tags` derivation for a package's domain-knowledge
|
|
692
|
+
* artifact when `@smrt({ knowledge: { tags } })` is not set (#2863).
|
|
693
|
+
*
|
|
694
|
+
* Never invents taxonomy: tags come only from `package.json#keywords`
|
|
695
|
+
* (authored by the package owner) plus a `cross-package` tag when the
|
|
696
|
+
* package declares a `@happyvertical/smrt-*` dependency. This keeps the
|
|
697
|
+
* derivation deterministic and traceable to an authored source, matching
|
|
698
|
+
* the "populate tags/risks from scanner output where the manifest carries
|
|
699
|
+
* the data" scope — a package that wants richer tags still sets
|
|
700
|
+
* `knowledge.tags` explicitly.
|
|
701
|
+
*/
|
|
702
|
+
function deriveDefaultTags(packageJson, allDependencies) {
|
|
703
|
+
const tags = /* @__PURE__ */ new Set();
|
|
704
|
+
const keywords = packageJson.keywords;
|
|
705
|
+
if (Array.isArray(keywords)) {
|
|
706
|
+
for (const keyword of keywords) if (typeof keyword === "string" && keyword.trim()) tags.add(keyword.trim());
|
|
707
|
+
}
|
|
708
|
+
if (Object.keys(allDependencies).some((dep) => dep.startsWith("@happyvertical/smrt-"))) tags.add("cross-package");
|
|
709
|
+
return [...tags].sort();
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Documented default `risks` derivation (#2863). Each entry names a
|
|
713
|
+
* structural fact the manifest already carries — never freeform prose — so
|
|
714
|
+
* it stays deterministic and regenerable:
|
|
715
|
+
*
|
|
716
|
+
* - `sensitive-fields-excluded`: generation always strips sensitive fields
|
|
717
|
+
* before projecting objects (`sensitiveFieldsExcluded` is unconditionally
|
|
718
|
+
* `true`), so a reviewer relying on this artifact must not treat it as a
|
|
719
|
+
* complete field inventory.
|
|
720
|
+
* - `cross-package-refs:<n>`: the package reads or writes another package's
|
|
721
|
+
* rows through `@crossPackageRef`; a reviewer must check that package's
|
|
722
|
+
* tenancy and lifecycle guarantees too.
|
|
723
|
+
* - `sti-inheritance`: at least one object shares a table via
|
|
724
|
+
* `tableStrategy: 'sti'`; a schema change must consider every sibling.
|
|
725
|
+
* - `polymorphic-associations:<n>`: at least one
|
|
726
|
+
* `SmrtPolymorphicAssociation`, whose target type is resolved at read time
|
|
727
|
+
* rather than by a foreign key constraint.
|
|
728
|
+
*/
|
|
729
|
+
function deriveDefaultRisks(options) {
|
|
730
|
+
const risks = /* @__PURE__ */ new Set();
|
|
731
|
+
if (options.sensitiveFieldsExcluded) risks.add("sensitive-fields-excluded");
|
|
732
|
+
if (options.relationshipsV2.crossPackageRefFields > 0) risks.add(`cross-package-refs:${options.relationshipsV2.crossPackageRefFields}`);
|
|
733
|
+
if (options.objects.some((object) => object.tableStrategy === "sti")) risks.add("sti-inheritance");
|
|
734
|
+
if (options.relationshipsV2.polymorphicAssociations > 0) risks.add(`polymorphic-associations:${options.relationshipsV2.polymorphicAssociations}`);
|
|
735
|
+
return [...risks].sort();
|
|
736
|
+
}
|
|
684
737
|
function columnType(object, fieldName) {
|
|
685
738
|
const columnName = camelToSnake(fieldName);
|
|
686
739
|
return object.schema?.columns[columnName]?.type;
|
|
@@ -757,6 +810,6 @@ function sortJson(value) {
|
|
|
757
810
|
return value;
|
|
758
811
|
}
|
|
759
812
|
//#endregion
|
|
760
|
-
export { AGENT_SURFACE_HASH_PREFIX, MODULE_DOC_HASH_PREFIX, buildDomainKnowledgeManifest, discoverScopedPackageDirectories, readAgentModuleDocs, readPackageAgentDoc, resolveAgentModuleDocPaths };
|
|
813
|
+
export { AGENT_SURFACE_HASH_PREFIX, MODULE_DOC_HASH_PREFIX, buildDomainKnowledgeManifest, buildKnowledgeGraph, checkKnowledgeGraphFreshness, discoverKnowledgeArtifactPaths, discoverScopedPackageDirectories, readAgentModuleDocs, readPackageAgentDoc, resolveAgentModuleDocPaths, stableStringify };
|
|
761
814
|
|
|
762
815
|
//# sourceMappingURL=knowledge.js.map
|