openlore 2.0.7 → 2.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -25
- package/dist/api/init.d.ts.map +1 -1
- package/dist/api/init.js +9 -2
- package/dist/api/init.js.map +1 -1
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +5 -3
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/decisions.d.ts +0 -1
- package/dist/cli/commands/decisions.d.ts.map +1 -1
- package/dist/cli/commands/decisions.js +19 -31
- package/dist/cli/commands/decisions.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +85 -23
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +766 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +444 -244
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/orient.d.ts.map +1 -1
- package/dist/cli/commands/orient.js +14 -2
- package/dist/cli/commands/orient.js.map +1 -1
- package/dist/cli/commands/prove.d.ts +29 -0
- package/dist/cli/commands/prove.d.ts.map +1 -0
- package/dist/cli/commands/prove.js +160 -0
- package/dist/cli/commands/prove.js.map +1 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +5 -3
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install/adapters/claude-code.d.ts +8 -2
- package/dist/cli/install/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/install/adapters/claude-code.js +99 -35
- package/dist/cli/install/adapters/claude-code.js.map +1 -1
- package/dist/cli/install/detect.d.ts.map +1 -1
- package/dist/cli/install/detect.js +14 -0
- package/dist/cli/install/detect.js.map +1 -1
- package/dist/cli/install/templates/agent-instructions.md +4 -2
- package/dist/constants.d.ts +16 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +24 -3
- package/dist/constants.js.map +1 -1
- package/dist/core/agent-eval/measure.d.ts +57 -0
- package/dist/core/agent-eval/measure.d.ts.map +1 -0
- package/dist/core/agent-eval/measure.js +85 -0
- package/dist/core/agent-eval/measure.js.map +1 -0
- package/dist/core/agent-eval/scorecard.d.ts +37 -0
- package/dist/core/agent-eval/scorecard.d.ts.map +1 -0
- package/dist/core/agent-eval/scorecard.js +70 -0
- package/dist/core/agent-eval/scorecard.js.map +1 -0
- package/dist/core/agent-eval/tasks.d.ts +37 -0
- package/dist/core/agent-eval/tasks.d.ts.map +1 -0
- package/dist/core/agent-eval/tasks.js +76 -0
- package/dist/core/agent-eval/tasks.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +5 -3
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -1
- package/dist/core/analyzer/architecture-writer.js +6 -4
- package/dist/core/analyzer/architecture-writer.js.map +1 -1
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +59 -4
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/call-graph.d.ts +19 -1
- package/dist/core/analyzer/call-graph.d.ts.map +1 -1
- package/dist/core/analyzer/call-graph.js +130 -28
- package/dist/core/analyzer/call-graph.js.map +1 -1
- package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -1
- package/dist/core/analyzer/spec-vector-index.js +6 -2
- package/dist/core/analyzer/spec-vector-index.js.map +1 -1
- package/dist/core/architecture/check.d.ts +63 -0
- package/dist/core/architecture/check.d.ts.map +1 -0
- package/dist/core/architecture/check.js +192 -0
- package/dist/core/architecture/check.js.map +1 -0
- package/dist/core/architecture/rules.d.ts +73 -0
- package/dist/core/architecture/rules.d.ts.map +1 -0
- package/dist/core/architecture/rules.js +201 -0
- package/dist/core/architecture/rules.js.map +1 -0
- package/dist/core/decisions/project.d.ts +59 -0
- package/dist/core/decisions/project.d.ts.map +1 -0
- package/dist/core/decisions/project.js +68 -0
- package/dist/core/decisions/project.js.map +1 -0
- package/dist/core/decisions/verifier.d.ts +10 -0
- package/dist/core/decisions/verifier.d.ts.map +1 -1
- package/dist/core/decisions/verifier.js +48 -5
- package/dist/core/decisions/verifier.js.map +1 -1
- package/dist/core/provenance/change-coupling.d.ts +68 -0
- package/dist/core/provenance/change-coupling.d.ts.map +1 -0
- package/dist/core/provenance/change-coupling.js +134 -0
- package/dist/core/provenance/change-coupling.js.map +1 -0
- package/dist/core/provenance/git-provenance.d.ts +67 -0
- package/dist/core/provenance/git-provenance.d.ts.map +1 -0
- package/dist/core/provenance/git-provenance.js +177 -0
- package/dist/core/provenance/git-provenance.js.map +1 -0
- package/dist/core/provenance/project.d.ts +37 -0
- package/dist/core/provenance/project.d.ts.map +1 -0
- package/dist/core/provenance/project.js +46 -0
- package/dist/core/provenance/project.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +17 -0
- package/dist/core/services/config-manager.d.ts.map +1 -1
- package/dist/core/services/config-manager.js +35 -1
- package/dist/core/services/config-manager.js.map +1 -1
- package/dist/core/services/edge-store.d.ts +41 -0
- package/dist/core/services/edge-store.d.ts.map +1 -1
- package/dist/core/services/edge-store.js +251 -3
- package/dist/core/services/edge-store.js.map +1 -1
- package/dist/core/services/llm-service.d.ts +9 -0
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +15 -4
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/architecture.d.ts +19 -0
- package/dist/core/services/mcp-handlers/architecture.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/architecture.js +104 -0
- package/dist/core/services/mcp-handlers/architecture.js.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts +16 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.js +57 -0
- package/dist/core/services/mcp-handlers/change-coupling.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +27 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/graph.js +98 -16
- package/dist/core/services/mcp-handlers/graph.js.map +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/orient.js +196 -39
- package/dist/core/services/mcp-handlers/orient.js.map +1 -1
- package/dist/core/services/mcp-handlers/progressive.d.ts +46 -0
- package/dist/core/services/mcp-handlers/progressive.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/progressive.js +0 -0
- package/dist/core/services/mcp-handlers/progressive.js.map +1 -0
- package/dist/core/services/mcp-handlers/reachability.d.ts +30 -0
- package/dist/core/services/mcp-handlers/reachability.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/reachability.js +222 -0
- package/dist/core/services/mcp-handlers/reachability.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +1 -1
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/semantic.js +39 -26
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
- package/dist/core/services/mcp-handlers/structural-diff.d.ts +31 -0
- package/dist/core/services/mcp-handlers/structural-diff.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/structural-diff.js +268 -0
- package/dist/core/services/mcp-handlers/structural-diff.js.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts +34 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.js +221 -0
- package/dist/core/services/mcp-handlers/test-impact.js.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts +45 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.js +81 -0
- package/dist/core/services/mcp-handlers/tool-guard.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/utils.js +15 -1
- package/dist/core/services/mcp-handlers/utils.js.map +1 -1
- package/dist/core/services/mcp-watcher.d.ts +7 -0
- package/dist/core/services/mcp-watcher.d.ts.map +1 -1
- package/dist/core/services/mcp-watcher.js +46 -0
- package/dist/core/services/mcp-watcher.js.map +1 -1
- package/dist/core/services/project-detector.d.ts.map +1 -1
- package/dist/core/services/project-detector.js +45 -2
- package/dist/core/services/project-detector.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture invariant checker (spec-23).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic, offline passes over the file-level dependency graph. Two entry
|
|
5
|
+
* points:
|
|
6
|
+
* - `scanViolations` — the full current-violations report (continuous reporting).
|
|
7
|
+
* - `canImport` — the pre-edit query: "may a file under A import B?", answered
|
|
8
|
+
* BEFORE the edge is written. Pure; writes nothing.
|
|
9
|
+
*
|
|
10
|
+
* The `layers` kind reuses `classifyLayerEdge` from the call-graph analyzer so the
|
|
11
|
+
* layering convention has exactly one source of truth.
|
|
12
|
+
*/
|
|
13
|
+
import type { DependencyGraphResult } from '../analyzer/dependency-graph.js';
|
|
14
|
+
import type { ArchitectureRule, ArchitectureRules, RuleSource } from './rules.js';
|
|
15
|
+
/** A concrete dependency that breaks a declared rule. Paths are repo-relative. */
|
|
16
|
+
export interface Violation {
|
|
17
|
+
kind: ArchitectureRule['kind'];
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
source: RuleSource;
|
|
22
|
+
}
|
|
23
|
+
/** Result of a full scan. */
|
|
24
|
+
export interface ScanResult {
|
|
25
|
+
violations: Violation[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
checkedEdges: number;
|
|
28
|
+
rulesApplied: number;
|
|
29
|
+
}
|
|
30
|
+
/** Verdict for a hypothetical (pre-edit) import. */
|
|
31
|
+
export interface ImportVerdict {
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
/** The governing rule when disallowed (or the unresolved-target note). */
|
|
34
|
+
rule?: {
|
|
35
|
+
kind: ArchitectureRule['kind'] | 'unresolved';
|
|
36
|
+
source?: RuleSource;
|
|
37
|
+
reason: string;
|
|
38
|
+
};
|
|
39
|
+
/** The resolved target file (relative) when a symbol was resolved to one. */
|
|
40
|
+
resolvedTo?: string;
|
|
41
|
+
reason: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Prefix/dir match: `pattern` is treated as a path prefix (a directory or an exact
|
|
45
|
+
* file). Trailing `/`, `/*`, `/**`, or `*` are tolerated and stripped. Deterministic;
|
|
46
|
+
* no full glob engine (kept to the well-understood dir-prefix vocabulary).
|
|
47
|
+
*/
|
|
48
|
+
export declare function pathMatches(rel: string, pattern: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Full violation scan over the dependency graph. Reports every edge that breaks a
|
|
51
|
+
* declared rule, plus warnings for rule prefixes that match no file in the repo
|
|
52
|
+
* (likely typos) — never a throw.
|
|
53
|
+
*/
|
|
54
|
+
export declare function scanViolations(depGraph: DependencyGraphResult, rules: ArchitectureRules): ScanResult;
|
|
55
|
+
/**
|
|
56
|
+
* Pre-edit query: would importing `to` from `fromFile` be allowed under the rules?
|
|
57
|
+
* `to` may be a file path (relative or absolute) or a bare exported symbol — in the
|
|
58
|
+
* latter case it is resolved to its declaring file via the dependency graph. When
|
|
59
|
+
* the target cannot be resolved to a file, the verdict is permissive (`allowed:
|
|
60
|
+
* true`) with an `unresolved` note: the checker only decides what it can ground.
|
|
61
|
+
*/
|
|
62
|
+
export declare function canImport(fromFile: string, to: string, rules: ArchitectureRules, depGraph?: DependencyGraphResult): ImportVerdict;
|
|
63
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/core/architecture/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAE7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAElF,kFAAkF;AAClF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,6BAA6B;AAC7B,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9F,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAKjE;AAqDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,qBAAqB,EAAE,KAAK,EAAE,iBAAiB,GAAG,UAAU,CA6CpG;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,EACxB,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,aAAa,CAuDf"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture invariant checker (spec-23).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic, offline passes over the file-level dependency graph. Two entry
|
|
5
|
+
* points:
|
|
6
|
+
* - `scanViolations` — the full current-violations report (continuous reporting).
|
|
7
|
+
* - `canImport` — the pre-edit query: "may a file under A import B?", answered
|
|
8
|
+
* BEFORE the edge is written. Pure; writes nothing.
|
|
9
|
+
*
|
|
10
|
+
* The `layers` kind reuses `classifyLayerEdge` from the call-graph analyzer so the
|
|
11
|
+
* layering convention has exactly one source of truth.
|
|
12
|
+
*/
|
|
13
|
+
import { classifyLayerEdge } from '../analyzer/call-graph.js';
|
|
14
|
+
/** Normalize a path to forward slashes with no trailing slash. */
|
|
15
|
+
function norm(p) {
|
|
16
|
+
return p.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Prefix/dir match: `pattern` is treated as a path prefix (a directory or an exact
|
|
20
|
+
* file). Trailing `/`, `/*`, `/**`, or `*` are tolerated and stripped. Deterministic;
|
|
21
|
+
* no full glob engine (kept to the well-understood dir-prefix vocabulary).
|
|
22
|
+
*/
|
|
23
|
+
export function pathMatches(rel, pattern) {
|
|
24
|
+
const r = norm(rel);
|
|
25
|
+
const p = norm(pattern.replace(/\/\*\*$/, '').replace(/\/\*$/, '').replace(/\*+$/, ''));
|
|
26
|
+
if (!p)
|
|
27
|
+
return false;
|
|
28
|
+
return r === p || r.startsWith(p + '/');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Evaluate one directed edge (relative paths) against one rule. Returns a reason
|
|
32
|
+
* string when the edge violates the rule, or null when it's legal under that rule.
|
|
33
|
+
*/
|
|
34
|
+
function edgeViolation(fromRel, toRel, rule) {
|
|
35
|
+
switch (rule.kind) {
|
|
36
|
+
case 'layers': {
|
|
37
|
+
const cls = classifyLayerEdge(fromRel, toRel, rule.layers);
|
|
38
|
+
if (!cls)
|
|
39
|
+
return null;
|
|
40
|
+
return `layer "${cls.fromLayer}" must not depend on upper layer "${cls.toLayer}"`;
|
|
41
|
+
}
|
|
42
|
+
case 'forbidden': {
|
|
43
|
+
if (pathMatches(fromRel, rule.from) && pathMatches(toRel, rule.to)) {
|
|
44
|
+
return rule.reason ?? `"${rule.from}" must not depend on "${rule.to}"`;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
case 'allowedOnly': {
|
|
49
|
+
if (!pathMatches(fromRel, rule.module))
|
|
50
|
+
return null;
|
|
51
|
+
// Intra-module dependencies are always allowed.
|
|
52
|
+
if (pathMatches(toRel, rule.module))
|
|
53
|
+
return null;
|
|
54
|
+
if (rule.mayDependOn.some(allowed => pathMatches(toRel, allowed)))
|
|
55
|
+
return null;
|
|
56
|
+
const why = rule.reason ? ` — ${rule.reason}` : '';
|
|
57
|
+
return `"${rule.module}" may depend only on [${rule.mayDependOn.join(', ')}]${why}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Build an absolute→relative path map from dependency-graph nodes. */
|
|
62
|
+
function relMap(depGraph) {
|
|
63
|
+
const m = new Map();
|
|
64
|
+
for (const n of depGraph.nodes) {
|
|
65
|
+
if (n.file?.absolutePath && n.file?.path)
|
|
66
|
+
m.set(n.file.absolutePath, norm(n.file.path));
|
|
67
|
+
}
|
|
68
|
+
return m;
|
|
69
|
+
}
|
|
70
|
+
/** Resolve an edge endpoint (absolute id or already-relative) to a relative path. */
|
|
71
|
+
function toRel(id, rels) {
|
|
72
|
+
return rels.get(id) ?? norm(id);
|
|
73
|
+
}
|
|
74
|
+
/** Every rule prefix, for the non-existent-path warning pass. */
|
|
75
|
+
function rulePrefixes(rule) {
|
|
76
|
+
switch (rule.kind) {
|
|
77
|
+
case 'layers': return Object.values(rule.layers).flat();
|
|
78
|
+
case 'forbidden': return [rule.from, rule.to];
|
|
79
|
+
case 'allowedOnly': return [rule.module, ...rule.mayDependOn];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Full violation scan over the dependency graph. Reports every edge that breaks a
|
|
84
|
+
* declared rule, plus warnings for rule prefixes that match no file in the repo
|
|
85
|
+
* (likely typos) — never a throw.
|
|
86
|
+
*/
|
|
87
|
+
export function scanViolations(depGraph, rules) {
|
|
88
|
+
const warnings = [...rules.warnings];
|
|
89
|
+
if (rules.rules.length === 0) {
|
|
90
|
+
return { violations: [], warnings, checkedEdges: 0, rulesApplied: 0 };
|
|
91
|
+
}
|
|
92
|
+
const rels = relMap(depGraph);
|
|
93
|
+
const allRel = [...rels.values()];
|
|
94
|
+
// Warn on prefixes that match nothing (typos / stale rules).
|
|
95
|
+
const seenPrefix = new Set();
|
|
96
|
+
for (const rule of rules.rules) {
|
|
97
|
+
for (const prefix of rulePrefixes(rule)) {
|
|
98
|
+
if (seenPrefix.has(prefix))
|
|
99
|
+
continue;
|
|
100
|
+
seenPrefix.add(prefix);
|
|
101
|
+
if (!allRel.some(f => pathMatches(f, prefix))) {
|
|
102
|
+
warnings.push(`rule path "${prefix}" matches no file in the repository — check for a typo`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const violations = [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const edge of depGraph.edges) {
|
|
109
|
+
const fromRel = toRel(edge.source, rels);
|
|
110
|
+
const toRelPath = toRel(edge.target, rels);
|
|
111
|
+
if (fromRel === toRelPath)
|
|
112
|
+
continue;
|
|
113
|
+
for (const rule of rules.rules) {
|
|
114
|
+
const reason = edgeViolation(fromRel, toRelPath, rule);
|
|
115
|
+
if (reason) {
|
|
116
|
+
const key = `${rule.kind}|${fromRel}|${toRelPath}|${reason}`;
|
|
117
|
+
if (seen.has(key))
|
|
118
|
+
continue;
|
|
119
|
+
seen.add(key);
|
|
120
|
+
violations.push({ kind: rule.kind, from: fromRel, to: toRelPath, reason, source: rule.source });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Deterministic ordering.
|
|
125
|
+
violations.sort((a, b) => a.from !== b.from ? (a.from < b.from ? -1 : 1)
|
|
126
|
+
: a.to !== b.to ? (a.to < b.to ? -1 : 1)
|
|
127
|
+
: a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0);
|
|
128
|
+
return { violations, warnings, checkedEdges: depGraph.edges.length, rulesApplied: rules.rules.length };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Pre-edit query: would importing `to` from `fromFile` be allowed under the rules?
|
|
132
|
+
* `to` may be a file path (relative or absolute) or a bare exported symbol — in the
|
|
133
|
+
* latter case it is resolved to its declaring file via the dependency graph. When
|
|
134
|
+
* the target cannot be resolved to a file, the verdict is permissive (`allowed:
|
|
135
|
+
* true`) with an `unresolved` note: the checker only decides what it can ground.
|
|
136
|
+
*/
|
|
137
|
+
export function canImport(fromFile, to, rules, depGraph) {
|
|
138
|
+
if (rules.rules.length === 0) {
|
|
139
|
+
return { allowed: true, reason: 'no architecture rules declared — inert' };
|
|
140
|
+
}
|
|
141
|
+
const rels = depGraph ? relMap(depGraph) : new Map();
|
|
142
|
+
const fromRel = toRel(fromFile, rels);
|
|
143
|
+
// Resolve the target to a relative file path.
|
|
144
|
+
const looksLikePath = to.includes('/') || /\.[a-z]{1,5}$/i.test(to);
|
|
145
|
+
let targets;
|
|
146
|
+
if (looksLikePath) {
|
|
147
|
+
targets = [toRel(to, rels)];
|
|
148
|
+
}
|
|
149
|
+
else if (depGraph) {
|
|
150
|
+
// Bare symbol → declaring file(s).
|
|
151
|
+
targets = depGraph.nodes
|
|
152
|
+
.filter(n => (n.exports ?? []).some(e => e.name === to))
|
|
153
|
+
.map(n => norm(n.file.path))
|
|
154
|
+
.sort();
|
|
155
|
+
if (targets.length === 0) {
|
|
156
|
+
return {
|
|
157
|
+
allowed: true,
|
|
158
|
+
rule: { kind: 'unresolved', reason: `symbol "${to}" not found among exports` },
|
|
159
|
+
reason: `could not resolve "${to}" to a file; no rule could be evaluated`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
return {
|
|
165
|
+
allowed: true,
|
|
166
|
+
rule: { kind: 'unresolved', reason: 'no dependency graph available to resolve symbol' },
|
|
167
|
+
reason: `could not resolve "${to}" without a dependency graph; no rule could be evaluated`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Disallow if ANY candidate target breaks ANY rule (conservative pre-edit guard).
|
|
171
|
+
for (const target of targets) {
|
|
172
|
+
if (fromRel === target)
|
|
173
|
+
continue;
|
|
174
|
+
for (const rule of rules.rules) {
|
|
175
|
+
const reason = edgeViolation(fromRel, target, rule);
|
|
176
|
+
if (reason) {
|
|
177
|
+
return {
|
|
178
|
+
allowed: false,
|
|
179
|
+
rule: { kind: rule.kind, source: rule.source, reason },
|
|
180
|
+
resolvedTo: target,
|
|
181
|
+
reason: `importing "${target}" from "${fromRel}" violates a ${rule.kind} rule: ${reason}`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
allowed: true,
|
|
188
|
+
resolvedTo: looksLikePath ? undefined : targets[0],
|
|
189
|
+
reason: `no rule forbids importing ${looksLikePath ? `"${targets[0]}"` : `"${to}"`} from "${fromRel}"`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/core/architecture/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture invariant rules (spec-23).
|
|
3
|
+
*
|
|
4
|
+
* A small, opt-in, fully declarative rule format for dependency / layer /
|
|
5
|
+
* module-boundary constraints. Rules are author-declared in
|
|
6
|
+
* `.openlore/architecture.json` (and optionally sourced from synced ADR files),
|
|
7
|
+
* NEVER inferred by an LLM. Parsing is total: malformed entries become warnings
|
|
8
|
+
* and are skipped — loading rules never throws.
|
|
9
|
+
*
|
|
10
|
+
* The checker ([check.ts](./check.ts)) compiles these down to deterministic
|
|
11
|
+
* passes over the file-level dependency graph, reusing the canonical
|
|
12
|
+
* `classifyLayerEdge` primitive from the call-graph analyzer for the `layers` kind.
|
|
13
|
+
*/
|
|
14
|
+
/** Where a rule came from — an author's config file, or a recorded decision (spec-16). */
|
|
15
|
+
export type RuleSource = 'config' | 'decision';
|
|
16
|
+
/**
|
|
17
|
+
* Ordered layering: key order is top → bottom, so a lower layer depending on an
|
|
18
|
+
* upper layer is a violation. Each layer maps to one or more path prefixes.
|
|
19
|
+
*/
|
|
20
|
+
export interface LayersRule {
|
|
21
|
+
kind: 'layers';
|
|
22
|
+
layers: Record<string, string[]>;
|
|
23
|
+
source: RuleSource;
|
|
24
|
+
}
|
|
25
|
+
/** "Files under `from` must not depend on files under `to`." */
|
|
26
|
+
export interface ForbiddenRule {
|
|
27
|
+
kind: 'forbidden';
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
source: RuleSource;
|
|
32
|
+
}
|
|
33
|
+
/** Module boundary: "files under `module` may depend ONLY on `mayDependOn` (plus themselves)." */
|
|
34
|
+
export interface AllowedOnlyRule {
|
|
35
|
+
kind: 'allowedOnly';
|
|
36
|
+
module: string;
|
|
37
|
+
mayDependOn: string[];
|
|
38
|
+
reason?: string;
|
|
39
|
+
source: RuleSource;
|
|
40
|
+
}
|
|
41
|
+
export type ArchitectureRule = LayersRule | ForbiddenRule | AllowedOnlyRule;
|
|
42
|
+
/** The parsed rule set plus any non-fatal warnings collected while loading. */
|
|
43
|
+
export interface ArchitectureRules {
|
|
44
|
+
rules: ArchitectureRule[];
|
|
45
|
+
warnings: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a raw config object into validated rules. Total: every malformed entry is
|
|
49
|
+
* recorded as a warning and skipped; this never throws. `source` tags provenance.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseArchitectureRules(raw: unknown, source: RuleSource): ArchitectureRules;
|
|
52
|
+
/**
|
|
53
|
+
* Parse `Invariant:` markers out of synced ADR files. We read SYNCED files only —
|
|
54
|
+
* never `pending.json` fields, which are purged on sync (spec-16 edge case).
|
|
55
|
+
* Supported single-line grammar (deterministic, no LLM):
|
|
56
|
+
*
|
|
57
|
+
* Invariant: forbidden <fromPrefix> -> <toPrefix> [(reason)]
|
|
58
|
+
* Invariant: allowedOnly <modulePrefix> -> <prefixA>, <prefixB> [(reason)]
|
|
59
|
+
*
|
|
60
|
+
* Anything else is ignored. Returns rules tagged `source: 'decision'`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseInvariantMarkers(adrText: string): ArchitectureRule[];
|
|
63
|
+
/**
|
|
64
|
+
* Load the effective architecture rules for a project: the opt-in config file
|
|
65
|
+
* merged with any decision-sourced invariants. Absent config is NOT an error —
|
|
66
|
+
* returns an empty, inert rule set. Never throws.
|
|
67
|
+
*/
|
|
68
|
+
export declare function loadArchitectureRules(absDir: string, opts?: {
|
|
69
|
+
includeDecisions?: boolean;
|
|
70
|
+
}): Promise<ArchitectureRules>;
|
|
71
|
+
/** True when no rules are declared — the instrument is fully inert. */
|
|
72
|
+
export declare function rulesAreInert(rules: ArchitectureRules): boolean;
|
|
73
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/core/architecture/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,0FAA0F;AAC1F,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,aAAa,GAAG,eAAe,CAAC;AAE5E,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAeD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,iBAAiB,CAwE1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CA0BzE;AAyBD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,iBAAiB,CAAC,CA4B5B;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAE/D"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture invariant rules (spec-23).
|
|
3
|
+
*
|
|
4
|
+
* A small, opt-in, fully declarative rule format for dependency / layer /
|
|
5
|
+
* module-boundary constraints. Rules are author-declared in
|
|
6
|
+
* `.openlore/architecture.json` (and optionally sourced from synced ADR files),
|
|
7
|
+
* NEVER inferred by an LLM. Parsing is total: malformed entries become warnings
|
|
8
|
+
* and are skipped — loading rules never throws.
|
|
9
|
+
*
|
|
10
|
+
* The checker ([check.ts](./check.ts)) compiles these down to deterministic
|
|
11
|
+
* passes over the file-level dependency graph, reusing the canonical
|
|
12
|
+
* `classifyLayerEdge` primitive from the call-graph analyzer for the `layers` kind.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { OPENLORE_DIR } from '../../constants.js';
|
|
17
|
+
const ARCHITECTURE_CONFIG_FILE = 'architecture.json';
|
|
18
|
+
function isStringArray(v) {
|
|
19
|
+
return Array.isArray(v) && v.every(x => typeof x === 'string');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a raw config object into validated rules. Total: every malformed entry is
|
|
23
|
+
* recorded as a warning and skipped; this never throws. `source` tags provenance.
|
|
24
|
+
*/
|
|
25
|
+
export function parseArchitectureRules(raw, source) {
|
|
26
|
+
const rules = [];
|
|
27
|
+
const warnings = [];
|
|
28
|
+
if (!raw || typeof raw !== 'object') {
|
|
29
|
+
return { rules, warnings: ['architecture rules: expected a JSON object'] };
|
|
30
|
+
}
|
|
31
|
+
const cfg = raw;
|
|
32
|
+
// layers
|
|
33
|
+
if (cfg.layers !== undefined) {
|
|
34
|
+
if (cfg.layers && typeof cfg.layers === 'object' && !Array.isArray(cfg.layers)) {
|
|
35
|
+
const layers = {};
|
|
36
|
+
for (const [name, prefixes] of Object.entries(cfg.layers)) {
|
|
37
|
+
if (isStringArray(prefixes) && prefixes.length > 0) {
|
|
38
|
+
layers[name] = prefixes;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
warnings.push(`layers.${name}: expected a non-empty array of path prefixes — skipped`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (Object.keys(layers).length >= 2) {
|
|
45
|
+
rules.push({ kind: 'layers', layers, source });
|
|
46
|
+
}
|
|
47
|
+
else if (Object.keys(layers).length > 0) {
|
|
48
|
+
warnings.push('layers: need at least 2 layers to define a direction — skipped');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
warnings.push('layers: expected an object mapping layer name → path prefixes — skipped');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// forbidden
|
|
56
|
+
if (cfg.forbidden !== undefined) {
|
|
57
|
+
if (Array.isArray(cfg.forbidden)) {
|
|
58
|
+
cfg.forbidden.forEach((r, i) => {
|
|
59
|
+
if (r && typeof r.from === 'string' && typeof r.to === 'string') {
|
|
60
|
+
rules.push({
|
|
61
|
+
kind: 'forbidden',
|
|
62
|
+
from: r.from,
|
|
63
|
+
to: r.to,
|
|
64
|
+
reason: typeof r.reason === 'string' ? r.reason : undefined,
|
|
65
|
+
source,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
warnings.push(`forbidden[${i}]: requires string "from" and "to" — skipped`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
warnings.push('forbidden: expected an array — skipped');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// allowedOnly
|
|
78
|
+
if (cfg.allowedOnly !== undefined) {
|
|
79
|
+
if (Array.isArray(cfg.allowedOnly)) {
|
|
80
|
+
cfg.allowedOnly.forEach((r, i) => {
|
|
81
|
+
if (r && typeof r.module === 'string' && isStringArray(r.mayDependOn)) {
|
|
82
|
+
rules.push({
|
|
83
|
+
kind: 'allowedOnly',
|
|
84
|
+
module: r.module,
|
|
85
|
+
mayDependOn: r.mayDependOn,
|
|
86
|
+
reason: typeof r.reason === 'string' ? r.reason : undefined,
|
|
87
|
+
source,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
warnings.push(`allowedOnly[${i}]: requires string "module" and string[] "mayDependOn" — skipped`);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
warnings.push('allowedOnly: expected an array — skipped');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { rules, warnings };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse `Invariant:` markers out of synced ADR files. We read SYNCED files only —
|
|
103
|
+
* never `pending.json` fields, which are purged on sync (spec-16 edge case).
|
|
104
|
+
* Supported single-line grammar (deterministic, no LLM):
|
|
105
|
+
*
|
|
106
|
+
* Invariant: forbidden <fromPrefix> -> <toPrefix> [(reason)]
|
|
107
|
+
* Invariant: allowedOnly <modulePrefix> -> <prefixA>, <prefixB> [(reason)]
|
|
108
|
+
*
|
|
109
|
+
* Anything else is ignored. Returns rules tagged `source: 'decision'`.
|
|
110
|
+
*/
|
|
111
|
+
export function parseInvariantMarkers(adrText) {
|
|
112
|
+
const rules = [];
|
|
113
|
+
for (const line of adrText.split(/\r?\n/)) {
|
|
114
|
+
const m = line.match(/^\s*(?:[-*>]\s*)*Invariant:\s*(.+)$/i);
|
|
115
|
+
if (!m)
|
|
116
|
+
continue;
|
|
117
|
+
let body = m[1].trim();
|
|
118
|
+
let reason;
|
|
119
|
+
const reasonMatch = body.match(/\(([^)]*)\)\s*$/);
|
|
120
|
+
if (reasonMatch) {
|
|
121
|
+
reason = reasonMatch[1].trim() || undefined;
|
|
122
|
+
body = body.slice(0, reasonMatch.index).trim();
|
|
123
|
+
}
|
|
124
|
+
const forbidden = body.match(/^forbidden\s+(\S+)\s*->\s*(\S+)$/i);
|
|
125
|
+
if (forbidden) {
|
|
126
|
+
rules.push({ kind: 'forbidden', from: forbidden[1], to: forbidden[2], reason, source: 'decision' });
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const allowed = body.match(/^allowedOnly\s+(\S+)\s*->\s*(.+)$/i);
|
|
130
|
+
if (allowed) {
|
|
131
|
+
const mayDependOn = allowed[2].split(',').map(s => s.trim()).filter(Boolean);
|
|
132
|
+
if (mayDependOn.length > 0) {
|
|
133
|
+
rules.push({ kind: 'allowedOnly', module: allowed[1], mayDependOn, reason, source: 'decision' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return rules;
|
|
138
|
+
}
|
|
139
|
+
/** Read invariants from synced ADR files under `openspec/decisions/adr-*.md`. */
|
|
140
|
+
async function loadDecisionInvariants(absDir) {
|
|
141
|
+
const rules = [];
|
|
142
|
+
const warnings = [];
|
|
143
|
+
const decisionsDir = join(absDir, 'openspec', 'decisions');
|
|
144
|
+
let entries;
|
|
145
|
+
try {
|
|
146
|
+
entries = await readdir(decisionsDir);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return { rules, warnings }; // no decisions dir — fine
|
|
150
|
+
}
|
|
151
|
+
for (const name of entries.sort()) {
|
|
152
|
+
if (!/^adr-.*\.md$/i.test(name))
|
|
153
|
+
continue;
|
|
154
|
+
try {
|
|
155
|
+
const text = await readFile(join(decisionsDir, name), 'utf-8');
|
|
156
|
+
rules.push(...parseInvariantMarkers(text));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
warnings.push(`could not read decision file ${name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { rules, warnings };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Load the effective architecture rules for a project: the opt-in config file
|
|
166
|
+
* merged with any decision-sourced invariants. Absent config is NOT an error —
|
|
167
|
+
* returns an empty, inert rule set. Never throws.
|
|
168
|
+
*/
|
|
169
|
+
export async function loadArchitectureRules(absDir, opts = {}) {
|
|
170
|
+
const rules = [];
|
|
171
|
+
const warnings = [];
|
|
172
|
+
// Config file (opt-in).
|
|
173
|
+
try {
|
|
174
|
+
const raw = await readFile(join(absDir, OPENLORE_DIR, ARCHITECTURE_CONFIG_FILE), 'utf-8');
|
|
175
|
+
let parsed;
|
|
176
|
+
try {
|
|
177
|
+
parsed = JSON.parse(raw);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return { rules, warnings: [`${OPENLORE_DIR}/${ARCHITECTURE_CONFIG_FILE}: invalid JSON — ignored`] };
|
|
181
|
+
}
|
|
182
|
+
const fromConfig = parseArchitectureRules(parsed, 'config');
|
|
183
|
+
rules.push(...fromConfig.rules);
|
|
184
|
+
warnings.push(...fromConfig.warnings);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
/* no config file — inert */
|
|
188
|
+
}
|
|
189
|
+
// Decision-sourced invariants (spec-16 tie), opt-in via flag.
|
|
190
|
+
if (opts.includeDecisions !== false) {
|
|
191
|
+
const fromDecisions = await loadDecisionInvariants(absDir);
|
|
192
|
+
rules.push(...fromDecisions.rules);
|
|
193
|
+
warnings.push(...fromDecisions.warnings);
|
|
194
|
+
}
|
|
195
|
+
return { rules, warnings };
|
|
196
|
+
}
|
|
197
|
+
/** True when no rules are declared — the instrument is fully inert. */
|
|
198
|
+
export function rulesAreInert(rules) {
|
|
199
|
+
return rules.rules.length === 0;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/core/architecture/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAgDlD,MAAM,wBAAwB,GAAG,mBAAmB,CAAC;AAErD,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAY,EAAE,MAAkB;IACrE,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,4CAA4C,CAAC,EAAE,CAAC;IAC7E,CAAC;IACD,MAAM,GAAG,GAAG,GAA4B,CAAC;IAEzC,SAAS;IACT,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/E,MAAM,MAAM,GAA6B,EAAE,CAAC;YAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1D,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACnD,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,IAAI,CAAC,UAAU,IAAI,yDAAyD,CAAC,CAAC;gBACzF,CAAC;YACH,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACpC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YACjD,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,QAAQ,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,YAAY;IACZ,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC7B,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;oBAChE,KAAK,CAAC,IAAI,CAAC;wBACT,IAAI,EAAE,WAAW;wBACjB,IAAI,EAAE,CAAC,CAAC,IAAI;wBACZ,EAAE,EAAE,CAAC,CAAC,EAAE;wBACR,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;wBAC3D,MAAM;qBACP,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,8CAA8C,CAAC,CAAC;gBAC9E,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,cAAc;IACd,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC/B,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;oBACtE,KAAK,CAAC,IAAI,CAAC;wBACT,IAAI,EAAE,aAAa;wBACnB,MAAM,EAAE,CAAC,CAAC,MAAM;wBAChB,WAAW,EAAE,CAAC,CAAC,WAAW;wBAC1B,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;wBAC3D,MAAM;qBACP,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,kEAAkE,CAAC,CAAC;gBACpG,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC7D,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvB,IAAI,MAA0B,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAClD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;YAC5C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAClE,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YACpG,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACjE,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC7E,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YACnG,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,sBAAsB,CAAC,MAAc;IAClD,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3D,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,0BAA0B;IACxD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,IAAI,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,OAAuC,EAAE;IAEzC,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,wBAAwB,CAAC,EAAE,OAAO,CAAC,CAAC;QAC1F,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,GAAG,YAAY,IAAI,wBAAwB,0BAA0B,CAAC,EAAE,CAAC;QACtG,CAAC;QACD,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5D,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;IAED,8DAA8D;IAC9D,IAAI,IAAI,CAAC,gBAAgB,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,MAAM,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,KAAwB;IACpD,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection: decision store → first-class graph nodes + `affects` edges (spec-16).
|
|
3
|
+
*
|
|
4
|
+
* This is the decisions analogue of src/core/analyzer/iac/project.ts. The JSON
|
|
5
|
+
* store (.openlore/decisions/pending.json) remains the authored source of truth;
|
|
6
|
+
* this projection is derived and regenerable. Promoting a Decision to a graph node
|
|
7
|
+
* with `affects` edges to the files it governs turns orient's runtime
|
|
8
|
+
* set-membership filter into a deterministic graph join, and lets
|
|
9
|
+
* analyze_impact / get_subgraph answer "what decisions govern this code?" with the
|
|
10
|
+
* same machinery they use for code edges.
|
|
11
|
+
*
|
|
12
|
+
* Edge direction is decision → governed file, mirroring the IaC convention
|
|
13
|
+
* (dependent/owner → dependency): a decision "affects" the files it governs.
|
|
14
|
+
*/
|
|
15
|
+
import type { DecisionStore, DecisionStatus } from '../../types/index.js';
|
|
16
|
+
/** Graph node-id namespace for projected decisions (keeps them distinct from code ids). */
|
|
17
|
+
export declare const DECISION_NODE_PREFIX = "decision::";
|
|
18
|
+
/** Stable graph node id for a decision's 8-char store id. */
|
|
19
|
+
export declare function decisionNodeId(decisionId: string): string;
|
|
20
|
+
/** A projected decision — a first-class, clearly-typed graph node (not a FunctionNode). */
|
|
21
|
+
export interface DecisionNode {
|
|
22
|
+
/** Graph node id, e.g. "decision::c6d1ad07". */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Original 8-char store id. */
|
|
25
|
+
decisionId: string;
|
|
26
|
+
/** Discriminator so callers never confuse this with a code node. */
|
|
27
|
+
kind: 'decision';
|
|
28
|
+
title: string;
|
|
29
|
+
status: DecisionStatus;
|
|
30
|
+
rationale: string;
|
|
31
|
+
consequences: string;
|
|
32
|
+
affectedDomains: string[];
|
|
33
|
+
affectedFiles: string[];
|
|
34
|
+
confidence: 'high' | 'medium' | 'low';
|
|
35
|
+
/** 8-char id of a prior decision this one reverses, if any. */
|
|
36
|
+
supersedes?: string;
|
|
37
|
+
}
|
|
38
|
+
/** An `affects` edge: decision node → a governed file path. */
|
|
39
|
+
export interface DecisionAffectsEdge {
|
|
40
|
+
/** Graph node id of the decision ("decision::<id>"). */
|
|
41
|
+
decisionNodeId: string;
|
|
42
|
+
/** Repo-relative, POSIX path of the governed file. */
|
|
43
|
+
filePath: string;
|
|
44
|
+
kind: 'affects';
|
|
45
|
+
}
|
|
46
|
+
export interface ProjectedDecisions {
|
|
47
|
+
nodes: DecisionNode[];
|
|
48
|
+
edges: DecisionAffectsEdge[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Project the active decisions in a store onto decision nodes + `affects` edges.
|
|
52
|
+
*
|
|
53
|
+
* - Inactive decisions (synced / rejected / phantom) are excluded, matching
|
|
54
|
+
* orient's INACTIVE_STATUSES — their content already lives in ADRs / spec.md.
|
|
55
|
+
* - An empty or legacy store projects to zero nodes/edges.
|
|
56
|
+
* - Output is fully sorted for deterministic, regenerable persistence.
|
|
57
|
+
*/
|
|
58
|
+
export declare function projectDecisions(store: DecisionStore): ProjectedDecisions;
|
|
59
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../src/core/decisions/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAmB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3F,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAOD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,kBAAkB,CAmCzE"}
|