Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .grafema/guarantees.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -595,3 +595,28 @@ guarantees:
check: datalog
rule: 'violation(F) :- node(F, "STATE_FIELD"), edge(_, F, "READS_STATE_TRUTHY"), edge(_, F, "WRITES_STATE_TRUTHY"), \+ edge(_, F, "WRITES_STATE_FALSY"), \+ edge(_, F, "WRITES_STATE_DYNAMIC").'
severity: warning

# ============================================================
# CONNASCENCE OF VALUE (RFD-75)
# ============================================================

- name: value-lockstep
description: >
Any two version:ref nodes that refer to the same version:coord must carry
the same value. Two refs to one coord with different values is
connascence-of-value drift (e.g. a cli package's optionalDeps pinned to a
stale @grafema/x version while another package pins the current one, or a
package.version that has drifted from the git tag / CHANGELOG top entry).
value/locus/coord ride the version:ref metadata JSON, so the rule reads
`value` via the derive-engine `node_attr` builtin (NOT `attr`, which only
sees top-level node columns and would yield zero rows). R and R2 are bound
by the REFERS_TO generator legs before node_attr (a point probe with no
generator mode) runs. Generalizes verbatim to feature-flag / enum-case /
API-version DRY connascence — coord/ref/REFERS_TO is value-agnostic.
check: datalog
severity: error
rule: >-
violation(R) :- edge(R, C, "REFERS_TO"), edge(R2, C, "REFERS_TO"),
neq(R, R2),
node_attr(R, "value", V1), node_attr(R2, "value", V2),
neq(V1, V2).
18 changes: 18 additions & 0 deletions packages/cli/src/commands/analyzeAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { enrichBehaviors } from '@grafema/util/enrichers/behaviorEnricher';
import { enrichContracts } from '@grafema/util/enrichers/contractEnricher';
import { enrichLibraryCallbacks } from '@grafema/util/enrichers/libraryCallbackEnricher';
import { enrichPackageApis } from '@grafema/util/enrichers/packageApiEnricher';
import { enrichVersionRefs } from '@grafema/util/enrichers/versionRefEnricher';
import { enrichMcpToolDefinitions } from '@grafema/util/enrichers/mcpToolDefinitionEnricher';
import { enrichSpecedContracts } from '@grafema/util/enrichers/specedContractEnricher';
import { commanderExtractor } from '@grafema/util/enrichers/extractors/commanderExtractor';
Expand Down Expand Up @@ -527,6 +528,23 @@ export async function analyzeAction(path: string, options: { service?: string; e
debug(`Package API enricher skipped: ${err instanceof Error ? err.message : String(err)}`);
}

// Version-ref enricher (RFD-75) — emit a `version:ref` node per repo-
// static version locus (package.json version, @grafema/* dependency pins,
// git tag, CHANGELOG top entry). The version_coords_nodes /
// version_refs_edges derive packs then mint `version:coord` + REFERS_TO,
// and the `value-lockstep` guarantee trips on connascence-of-value drift.
try {
const client = (backend as unknown as { client: Parameters<typeof enrichVersionRefs>[0] }).client;
if (client) {
const result = await enrichVersionRefs(client, projectPath);
if (result.nodesCreated > 0) {
info(` Version refs: ${result.nodesCreated} version:ref nodes across ${result.lociScanned} loci (pkg.version=${result.packageVersionRefs}, deps=${result.dependencyRefs}, gitTag=${result.gitTagRefs}, changelog=${result.changelogRefs})`);
}
}
} catch (err) {
debug(`Version-ref enricher skipped: ${err instanceof Error ? err.message : String(err)}`);
}

// Generate manifest after successful analysis
try {
const manifestPath = await generateManifest(backend, projectPath, grafemaDir, debug);
Expand Down
9 changes: 9 additions & 0 deletions packages/grafema-orchestrator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ const STDLIB_RULE_PACKS: &[&str] = &[
// consume analyzer EDB only.
"@stdlib/js_entrypoint_features_nodes",
"@stdlib/js_entrypoint_features_edges",
// RFD-75 connascence-of-value vertical: version_coords_nodes MINTS the
// version:coord nodes (one per distinct coord) the version_refs_edges pack
// joins as committed EDB to derive REFERS_TO (strict nodes→edges order — the
// js_builtins two-pack split). Both consume the enricher-minted version:ref
// EDB only.
"@stdlib/version_coords_nodes",
"@stdlib/version_refs_edges",
];


Expand Down Expand Up @@ -618,6 +625,8 @@ fn pack_owned_slice(pack: &str) -> &'static str {
"@stdlib/js_http_routes_edges" => "js http:route EXPOSES + HANDLES",
"@stdlib/js_entrypoint_features_nodes" => "js cli:command/mcp:tool/vscode:command FEATURE nodes (commander/mcp-sdk/vscode)",
"@stdlib/js_entrypoint_features_edges" => "js cli:command/mcp:tool/vscode:command EXPOSES + HANDLES",
"@stdlib/version_coords_nodes" => "version:coord nodes (RFD-75 connascence)",
"@stdlib/version_refs_edges" => "version:ref → version:coord REFERS_TO (RFD-75 connascence)",
_ => "(unregistered pack)",
}
}
Expand Down
Loading
Loading