loopgraph 0.1.0 → 0.2.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/dist/adapters/agenthub/extract.d.ts +2 -13
- package/dist/adapters/agenthub/facts.d.ts +1 -1
- package/dist/adapters/registry.js +5 -2
- package/dist/adapters/rush/extract.d.ts +36 -0
- package/dist/adapters/rush/extract.js +125 -0
- package/dist/adapters/rush/index.d.ts +10 -0
- package/dist/adapters/rush/index.js +15 -0
- package/dist/adapters/types.d.ts +26 -1
- package/dist/cli/commands/snapshot.d.ts +3 -1
- package/dist/cli/commands/snapshot.js +0 -0
- package/dist/cli/run.js +42 -3
- package/dist/validate/unregistered.d.ts +1 -1
- package/dist/validate/unregistered.js +9 -3
- package/package.json +1 -1
|
@@ -22,19 +22,8 @@
|
|
|
22
22
|
* didn't change still needs re-extraction if the extractor did).
|
|
23
23
|
*/
|
|
24
24
|
export declare const ADAPTER_VERSION = "agenthub-facts-1";
|
|
25
|
-
export type SignalKind
|
|
26
|
-
|
|
27
|
-
signal: SignalKind;
|
|
28
|
-
/** The queue name, or the poller's enclosing function/file marker. */
|
|
29
|
-
name: string;
|
|
30
|
-
filePath: string;
|
|
31
|
-
/** 1-based line for a human reading the report — not a stable anchor. */
|
|
32
|
-
line: number;
|
|
33
|
-
/** Extra context: e.g. "env-suffixed" for a QUEUE_SUFFIX template, or the interval. */
|
|
34
|
-
detail?: string;
|
|
35
|
-
/** true when the fact's key part (queue name / interval) couldn't be read statically. */
|
|
36
|
-
unanalyzable?: boolean;
|
|
37
|
-
}
|
|
25
|
+
export type { ImplementationFact, SignalKind } from "../types.js";
|
|
26
|
+
import type { ImplementationFact } from "../types.js";
|
|
38
27
|
/**
|
|
39
28
|
* pg-boss queue names. In this target stack the authoritative names live in
|
|
40
29
|
* `const *_QUEUE = <string | template>` declarations (e.g.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Adapter } from "../types.js";
|
|
2
|
-
import type { ImplementationFact } from "
|
|
2
|
+
import type { ImplementationFact } from "../types.js";
|
|
3
3
|
export interface FactsOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Machine-level content cache dir (B3). Defaults to `~/.cache/loopgraph`;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { agenthubAdapter } from "./agenthub/index.js";
|
|
2
|
+
import { rushAdapter } from "./rush/index.js";
|
|
2
3
|
/**
|
|
3
4
|
* The adapter registry (E1): the single place the engine resolves an adapter by
|
|
4
5
|
* name. A new target stack registers here and everything else — the fact
|
|
5
|
-
* runner, the cache key
|
|
6
|
-
*
|
|
6
|
+
* runner, the cache key — flows through the Adapter interface without further
|
|
7
|
+
* engine changes. `rush` (GATE-3) is the proof: registering it here is the ONLY
|
|
8
|
+
* engine-side change a second target required.
|
|
7
9
|
*/
|
|
8
10
|
const ADAPTERS = {
|
|
9
11
|
[agenthubAdapter.name]: agenthubAdapter,
|
|
12
|
+
[rushAdapter.name]: rushAdapter,
|
|
10
13
|
};
|
|
11
14
|
export function getAdapter(name) {
|
|
12
15
|
return ADAPTERS[name];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ImplementationFact } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* rush adapter — implementation-fact extraction (Proposal 006 E2 / GATE-3, the
|
|
4
|
+
* second target stack). PURE functions of `(filePath, content)`, an in-memory
|
|
5
|
+
* `ts.createSourceFile` parse, no I/O — same cacheable posture as the agenthub
|
|
6
|
+
* adapter.
|
|
7
|
+
*
|
|
8
|
+
* rush's background-signal profile differs from agenthub's: it has no pg-boss
|
|
9
|
+
* queue backend; async fan-out goes through Redis pub/sub channels, plus the
|
|
10
|
+
* usual setInterval pollers. So this adapter extracts (1) Redis channels and
|
|
11
|
+
* (2) setInterval pollers. It is fully independent of the agenthub adapter — no
|
|
12
|
+
* shared extractor logic (they're structurally similar but semantically distinct
|
|
13
|
+
* per-target extension points; extract a common core only once a 3rd adapter
|
|
14
|
+
* exists and the real invariants are known, not from n=2).
|
|
15
|
+
*/
|
|
16
|
+
/** Bumped when THIS adapter's extractor logic changes — part of the B3 cache key. */
|
|
17
|
+
export declare const RUSH_ADAPTER_VERSION = "rush-facts-1";
|
|
18
|
+
/**
|
|
19
|
+
* Redis pub/sub channels. rush's convention is `const *channel* = <string |
|
|
20
|
+
* template>` referenced by identifier at `.publish(channel, ...)` /
|
|
21
|
+
* `.subscribe(channel)` call sites; a direct string literal AT the call site is
|
|
22
|
+
* also captured. The `*channel*` const-name convention is rush-adapter
|
|
23
|
+
* knowledge — exactly what an adapter is allowed to encode. Deduped by name.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractRedisChannels(filePath: string, content: string): ImplementationFact[];
|
|
26
|
+
/**
|
|
27
|
+
* setInterval poller registrations — bare (`setInterval(...)`) or off an object
|
|
28
|
+
* (`globalThis.setInterval(...)`). Numeric-literal interval → detail; a
|
|
29
|
+
* variable/expression interval → unanalyzable. (setInterval is a Node global,
|
|
30
|
+
* not rush-specific — but kept local to this adapter rather than shared with
|
|
31
|
+
* agenthub until a 3rd adapter justifies extraction.)
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractPollers(filePath: string, content: string): ImplementationFact[];
|
|
34
|
+
/** Runs every rush extractor over one file. */
|
|
35
|
+
export declare function extractFacts(filePath: string, content: string): ImplementationFact[];
|
|
36
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* rush adapter — implementation-fact extraction (Proposal 006 E2 / GATE-3, the
|
|
4
|
+
* second target stack). PURE functions of `(filePath, content)`, an in-memory
|
|
5
|
+
* `ts.createSourceFile` parse, no I/O — same cacheable posture as the agenthub
|
|
6
|
+
* adapter.
|
|
7
|
+
*
|
|
8
|
+
* rush's background-signal profile differs from agenthub's: it has no pg-boss
|
|
9
|
+
* queue backend; async fan-out goes through Redis pub/sub channels, plus the
|
|
10
|
+
* usual setInterval pollers. So this adapter extracts (1) Redis channels and
|
|
11
|
+
* (2) setInterval pollers. It is fully independent of the agenthub adapter — no
|
|
12
|
+
* shared extractor logic (they're structurally similar but semantically distinct
|
|
13
|
+
* per-target extension points; extract a common core only once a 3rd adapter
|
|
14
|
+
* exists and the real invariants are known, not from n=2).
|
|
15
|
+
*/
|
|
16
|
+
/** Bumped when THIS adapter's extractor logic changes — part of the B3 cache key. */
|
|
17
|
+
export const RUSH_ADAPTER_VERSION = "rush-facts-1";
|
|
18
|
+
function lineOf(src, node) {
|
|
19
|
+
return src.getLineAndCharacterOfPosition(node.getStart(src)).line + 1;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The static name of a string/template expression, or null if not statically
|
|
23
|
+
* known. Template statics are joined with "…" across the dynamic `${}` gaps —
|
|
24
|
+
* e.g. `` `events:${id}` `` → "events:"; a fully dynamic template → "<dynamic>".
|
|
25
|
+
*/
|
|
26
|
+
function channelNameOf(expr) {
|
|
27
|
+
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr))
|
|
28
|
+
return { name: expr.text };
|
|
29
|
+
if (ts.isTemplateExpression(expr)) {
|
|
30
|
+
const staticParts = [expr.head.text, ...expr.templateSpans.map((s) => s.literal.text)].filter(Boolean);
|
|
31
|
+
if (staticParts.length === 0)
|
|
32
|
+
return { name: "<dynamic>", detail: "interpolated" };
|
|
33
|
+
return { name: staticParts.join("…"), detail: "interpolated" };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Redis pub/sub channels. rush's convention is `const *channel* = <string |
|
|
39
|
+
* template>` referenced by identifier at `.publish(channel, ...)` /
|
|
40
|
+
* `.subscribe(channel)` call sites; a direct string literal AT the call site is
|
|
41
|
+
* also captured. The `*channel*` const-name convention is rush-adapter
|
|
42
|
+
* knowledge — exactly what an adapter is allowed to encode. Deduped by name.
|
|
43
|
+
*/
|
|
44
|
+
export function extractRedisChannels(filePath, content) {
|
|
45
|
+
const src = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
46
|
+
const facts = [];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
const PUBSUB_METHODS = new Set(["publish", "subscribe"]);
|
|
49
|
+
const push = (name, node, detail) => {
|
|
50
|
+
if (seen.has(name))
|
|
51
|
+
return;
|
|
52
|
+
seen.add(name);
|
|
53
|
+
facts.push({
|
|
54
|
+
signal: "redis_channel",
|
|
55
|
+
name,
|
|
56
|
+
filePath,
|
|
57
|
+
line: lineOf(src, node),
|
|
58
|
+
...(detail ? { detail } : {}),
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
function visit(node) {
|
|
62
|
+
// `const *channel* = <string | template>` — the authoritative channel-name decl.
|
|
63
|
+
// Case-INSENSITIVE "channel" (rush uses lowercase `channel` locals and
|
|
64
|
+
// SCREAMING_SNAKE `*_CHANNEL` consts alike).
|
|
65
|
+
if (ts.isVariableDeclaration(node) &&
|
|
66
|
+
ts.isIdentifier(node.name) &&
|
|
67
|
+
node.name.text.toLowerCase().includes("channel") &&
|
|
68
|
+
node.initializer) {
|
|
69
|
+
const c = channelNameOf(node.initializer);
|
|
70
|
+
if (c)
|
|
71
|
+
push(c.name, node, c.detail);
|
|
72
|
+
}
|
|
73
|
+
// Direct string-literal `.publish/.subscribe("channel", ...)` call sites.
|
|
74
|
+
if (ts.isCallExpression(node) &&
|
|
75
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
76
|
+
PUBSUB_METHODS.has(node.expression.name.text)) {
|
|
77
|
+
const arg0 = node.arguments[0];
|
|
78
|
+
const c = arg0 ? channelNameOf(arg0) : null;
|
|
79
|
+
if (c)
|
|
80
|
+
push(c.name, node, c.detail);
|
|
81
|
+
}
|
|
82
|
+
ts.forEachChild(node, visit);
|
|
83
|
+
}
|
|
84
|
+
visit(src);
|
|
85
|
+
return facts;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* setInterval poller registrations — bare (`setInterval(...)`) or off an object
|
|
89
|
+
* (`globalThis.setInterval(...)`). Numeric-literal interval → detail; a
|
|
90
|
+
* variable/expression interval → unanalyzable. (setInterval is a Node global,
|
|
91
|
+
* not rush-specific — but kept local to this adapter rather than shared with
|
|
92
|
+
* agenthub until a 3rd adapter justifies extraction.)
|
|
93
|
+
*/
|
|
94
|
+
export function extractPollers(filePath, content) {
|
|
95
|
+
const src = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
96
|
+
const facts = [];
|
|
97
|
+
const isSetInterval = (expr) => (ts.isIdentifier(expr) && expr.text === "setInterval") ||
|
|
98
|
+
(ts.isPropertyAccessExpression(expr) && expr.name.text === "setInterval") ||
|
|
99
|
+
(ts.isElementAccessExpression(expr) &&
|
|
100
|
+
ts.isStringLiteral(expr.argumentExpression) &&
|
|
101
|
+
expr.argumentExpression.text === "setInterval");
|
|
102
|
+
function visit(node) {
|
|
103
|
+
if (ts.isCallExpression(node) && isSetInterval(node.expression)) {
|
|
104
|
+
const intervalArg = node.arguments[1];
|
|
105
|
+
const literalMs = intervalArg && ts.isNumericLiteral(intervalArg) ? intervalArg.text : undefined;
|
|
106
|
+
facts.push({
|
|
107
|
+
signal: "setinterval_poller",
|
|
108
|
+
name: `setInterval@${filePath}:${lineOf(src, node)}`,
|
|
109
|
+
filePath,
|
|
110
|
+
line: lineOf(src, node),
|
|
111
|
+
...(literalMs
|
|
112
|
+
? { detail: `${literalMs}ms` }
|
|
113
|
+
: { detail: "dynamic interval", unanalyzable: true }),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
ts.forEachChild(node, visit);
|
|
117
|
+
}
|
|
118
|
+
visit(src);
|
|
119
|
+
return facts;
|
|
120
|
+
}
|
|
121
|
+
/** Runs every rush extractor over one file. */
|
|
122
|
+
export function extractFacts(filePath, content) {
|
|
123
|
+
return [...extractRedisChannels(filePath, content), ...extractPollers(filePath, content)];
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Adapter } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The rush (rush-app) adapter behind the engine's Adapter interface (E2 /
|
|
4
|
+
* GATE-3). Proves the engine↔adapter seam generalizes to a second, differently
|
|
5
|
+
* shaped stack (Redis pub/sub + setInterval, no pg-boss) without touching the
|
|
6
|
+
* engine or the agenthub adapter. Ships no defaultInv1Config — a target's
|
|
7
|
+
* guarded-writer config lives in its own loopgraph.config.json.
|
|
8
|
+
*/
|
|
9
|
+
export declare const rushAdapter: Adapter;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RUSH_ADAPTER_VERSION, extractFacts } from "./extract.js";
|
|
2
|
+
/**
|
|
3
|
+
* The rush (rush-app) adapter behind the engine's Adapter interface (E2 /
|
|
4
|
+
* GATE-3). Proves the engine↔adapter seam generalizes to a second, differently
|
|
5
|
+
* shaped stack (Redis pub/sub + setInterval, no pg-boss) without touching the
|
|
6
|
+
* engine or the agenthub adapter. Ships no defaultInv1Config — a target's
|
|
7
|
+
* guarded-writer config lives in its own loopgraph.config.json.
|
|
8
|
+
*/
|
|
9
|
+
export const rushAdapter = {
|
|
10
|
+
name: "rush",
|
|
11
|
+
version: RUSH_ADAPTER_VERSION,
|
|
12
|
+
candidatePattern: "setInterval\\(|\\.(publish|subscribe)\\(",
|
|
13
|
+
extractFacts,
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import type { Inv1Config } from "../validate/inv1/config.js";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The kind of implementation signal a fact represents. An OPEN string, not a
|
|
4
|
+
* closed union: each adapter names its own signal kinds (agenthub:
|
|
5
|
+
* `pg_boss_queue` / `setinterval_poller`; rush: `redis_channel` / …). The
|
|
6
|
+
* engine treats it opaquely — it never enumerates every adapter's kinds — so a
|
|
7
|
+
* new adapter adds signals without the engine changing.
|
|
8
|
+
*/
|
|
9
|
+
export type SignalKind = string;
|
|
10
|
+
/**
|
|
11
|
+
* One extracted implementation signal (a "registerable" background unit the
|
|
12
|
+
* behavioral model is supposed to account for). Lives here on the engine↔adapter
|
|
13
|
+
* boundary — NOT inside any one adapter — so every adapter and the engine share
|
|
14
|
+
* one contract instead of importing it from a sibling adapter.
|
|
15
|
+
*/
|
|
16
|
+
export interface ImplementationFact {
|
|
17
|
+
signal: SignalKind;
|
|
18
|
+
/** The queue/channel/… name, or the poller's enclosing function/file marker. */
|
|
19
|
+
name: string;
|
|
20
|
+
filePath: string;
|
|
21
|
+
/** 1-based line for a human reading the report — not a stable anchor. */
|
|
22
|
+
line: number;
|
|
23
|
+
/** Extra context: e.g. "env-suffixed" for a templated name, or the interval. */
|
|
24
|
+
detail?: string;
|
|
25
|
+
/** true when the fact's key part (name / interval) couldn't be read statically. */
|
|
26
|
+
unanalyzable?: boolean;
|
|
27
|
+
}
|
|
3
28
|
/**
|
|
4
29
|
* The engine↔adapter boundary (Proposal 006 E1 / 001 §2 三分离, Phase 5
|
|
5
30
|
* generalization). The ENGINE (loader, T0, INV-1 scan primitive, query, cache,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImplementationFact, SignalKind } from "../../adapters/
|
|
1
|
+
import type { Adapter, ImplementationFact, SignalKind } from "../../adapters/types.js";
|
|
2
2
|
import { type Inv1CheckResult } from "../../validate/inv1/check.js";
|
|
3
3
|
import type { T0Result } from "../../validate/types.js";
|
|
4
4
|
export declare const SNAPSHOT_SCHEMA_VERSION: 1;
|
|
@@ -98,6 +98,8 @@ export interface SnapshotOptions {
|
|
|
98
98
|
repoRoot?: string | undefined;
|
|
99
99
|
/** Machine cache dir for facts (B3); null disables. Default: the shared cache. */
|
|
100
100
|
cacheDir?: string | null | undefined;
|
|
101
|
+
/** The adapter supplying the facts (default: agenthub). Threaded to runFacts. */
|
|
102
|
+
adapter?: Adapter | undefined;
|
|
101
103
|
/** Injected for reproducibility; defaults to now. */
|
|
102
104
|
generatedAt?: string | undefined;
|
|
103
105
|
/** Injected for tests; defaults to `git rev-parse HEAD` of repoRoot. */
|
|
Binary file
|
package/dist/cli/run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runFacts } from "../adapters/agenthub/facts.js";
|
|
2
|
+
import { adapterNames, getAdapter } from "../adapters/registry.js";
|
|
2
3
|
import { loadModel } from "../loader/load-model.js";
|
|
3
4
|
import { startMcpServer } from "../mcp/server.js";
|
|
4
5
|
import { runQuery } from "../query/run-query.js";
|
|
@@ -28,7 +29,7 @@ const USAGE = "usage: loopgraph <init|import|check> [dir] [--adapter name] [--re
|
|
|
28
29
|
" loopgraph inspect <node-id> [dir] [--depth n]\n" +
|
|
29
30
|
" loopgraph <impact|plan|scenario|evidence|matrix> <id> [dir]\n" +
|
|
30
31
|
" loopgraph mcp [dir] # start the stdio MCP server\n" +
|
|
31
|
-
" loopgraph facts [repo] # extract implementation facts (
|
|
32
|
+
" loopgraph facts [repo] [--adapter name] # extract implementation facts (default adapter: agenthub)\n" +
|
|
32
33
|
" loopgraph snapshot [dir] --repo-root path [--out file] [--drift prior.json] # T2 nightly scan artifact";
|
|
33
34
|
/**
|
|
34
35
|
* Reads a `--flag value` string option. Returns `{ error }` when the flag
|
|
@@ -47,6 +48,27 @@ function readStringFlag(flags, name) {
|
|
|
47
48
|
return { value: raw };
|
|
48
49
|
return { value: undefined, error: `--${name} requires a value` };
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve `--adapter <name>` for the fact-extracting commands (facts /
|
|
53
|
+
* reconcile / snapshot). Returns `{}` (no `adapter` key) when the flag is absent
|
|
54
|
+
* — the caller then falls back to the default adapter — or `{ error: true }`
|
|
55
|
+
* after reporting a malformed flag / unknown adapter name.
|
|
56
|
+
*/
|
|
57
|
+
function resolveAdapterFlag(flags, io) {
|
|
58
|
+
const f = readStringFlag(flags, "adapter");
|
|
59
|
+
if (f.error) {
|
|
60
|
+
io.error(`${f.error}. ${USAGE}`);
|
|
61
|
+
return { error: true };
|
|
62
|
+
}
|
|
63
|
+
if (f.value === undefined)
|
|
64
|
+
return {}; // no --adapter → caller uses the default
|
|
65
|
+
const adapter = getAdapter(f.value);
|
|
66
|
+
if (!adapter) {
|
|
67
|
+
io.error(`unknown adapter "${f.value}" (available: ${adapterNames().join(", ")})`);
|
|
68
|
+
return { error: true };
|
|
69
|
+
}
|
|
70
|
+
return { adapter };
|
|
71
|
+
}
|
|
50
72
|
/**
|
|
51
73
|
* Core dispatch, factored out of the process entry point so tests can
|
|
52
74
|
* drive it in-process (real filesystem via temp dirs, no subprocess
|
|
@@ -214,8 +236,15 @@ export async function run(argv, io) {
|
|
|
214
236
|
case "facts": {
|
|
215
237
|
// Extract implementation facts from a target repo (positionals[0] = repo
|
|
216
238
|
// root, defaulting to cwd). `--no-cache` disables the B3 content cache.
|
|
239
|
+
// `--adapter <name>` selects the target stack's adapter (default agenthub).
|
|
217
240
|
const repo = positionals[0] ?? process.cwd();
|
|
218
|
-
const
|
|
241
|
+
const factsAdapter = resolveAdapterFlag(flags, io);
|
|
242
|
+
if ("error" in factsAdapter)
|
|
243
|
+
return 1;
|
|
244
|
+
const result = await runFacts(repo, {
|
|
245
|
+
...(flags["no-cache"] === true ? { cacheDir: null } : {}),
|
|
246
|
+
...(factsAdapter.adapter ? { adapter: factsAdapter.adapter } : {}),
|
|
247
|
+
});
|
|
219
248
|
if (!result.ran) {
|
|
220
249
|
io.log(`⚠ facts extraction skipped: ${result.skippedReason}`);
|
|
221
250
|
return 0;
|
|
@@ -246,8 +275,14 @@ export async function run(argv, io) {
|
|
|
246
275
|
io.error(`reconcile needs --repo-root <repo>. ${USAGE}`);
|
|
247
276
|
return 1;
|
|
248
277
|
}
|
|
278
|
+
const reconcileAdapter = resolveAdapterFlag(flags, io);
|
|
279
|
+
if ("error" in reconcileAdapter)
|
|
280
|
+
return 1;
|
|
249
281
|
const model = await loadModel(`${targetDir}/model`);
|
|
250
|
-
const factsResult = await runFacts(repoRootFlag.value,
|
|
282
|
+
const factsResult = await runFacts(repoRootFlag.value, {
|
|
283
|
+
...(flags["no-cache"] === true ? { cacheDir: null } : {}),
|
|
284
|
+
...(reconcileAdapter.adapter ? { adapter: reconcileAdapter.adapter } : {}),
|
|
285
|
+
});
|
|
251
286
|
if (!factsResult.ran) {
|
|
252
287
|
io.log(`⚠ reconcile skipped: ${factsResult.skippedReason}`);
|
|
253
288
|
return 0;
|
|
@@ -279,9 +314,13 @@ export async function run(argv, io) {
|
|
|
279
314
|
return 1;
|
|
280
315
|
}
|
|
281
316
|
}
|
|
317
|
+
const snapshotAdapter = resolveAdapterFlag(flags, io);
|
|
318
|
+
if ("error" in snapshotAdapter)
|
|
319
|
+
return 1;
|
|
282
320
|
const snapshot = await runSnapshot(targetDir, {
|
|
283
321
|
repoRoot: repoRootFlag.value,
|
|
284
322
|
...(flags["no-cache"] === true ? { cacheDir: null } : {}),
|
|
323
|
+
...(snapshotAdapter.adapter ? { adapter: snapshotAdapter.adapter } : {}),
|
|
285
324
|
});
|
|
286
325
|
io.log(renderSnapshotSummary(snapshot));
|
|
287
326
|
if (driftFlag.value) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImplementationFact } from "../adapters/
|
|
1
|
+
import type { ImplementationFact } from "../adapters/types.js";
|
|
2
2
|
import type { ModelGraph } from "../loader/model-graph.js";
|
|
3
3
|
/**
|
|
4
4
|
* C2 T1 reconciliation (Proposal 006 Phase 3): which extracted implementation
|
|
@@ -81,9 +81,15 @@ export function reconcileFacts(facts, graph) {
|
|
|
81
81
|
const unregistered = [];
|
|
82
82
|
// Three DISJOINT buckets (active wins over dormant wins over nothing), so
|
|
83
83
|
// `registered`, `dormantSuppressed`, and `unregistered` partition the facts.
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
84
|
+
// EVERY fact — any adapter, any signal kind — reconciles by FILE (the base
|
|
85
|
+
// mechanism): file anchored → registered. The extra `consumes_queues`
|
|
86
|
+
// name-match is an OPT-IN, currently only for `pg_boss_queue` (agenthub's
|
|
87
|
+
// producer/consumer split: a queue defined in an un-anchored registry file
|
|
88
|
+
// whose loop is modeled elsewhere). Other signals (e.g. rush's
|
|
89
|
+
// `redis_channel`) are NOT ignored — they just reconcile by file only. If a
|
|
90
|
+
// second adapter needs the same name-match, generalize this to an
|
|
91
|
+
// adapter-declared "name-matchable signal kinds" set rather than hardcoding
|
|
92
|
+
// more kinds here (GATE-3 doesn't need it; file-level reconcile suffices).
|
|
87
93
|
const isActive = (f) => active.has(f.filePath) || (f.signal === "pg_boss_queue" && consumedQueues.has(f.name));
|
|
88
94
|
for (const f of facts) {
|
|
89
95
|
if (isActive(f)) {
|
package/package.json
CHANGED