@chrisdudek/yg 5.3.0 → 5.4.0
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/bin.js +1860 -373
- package/dist/structure.d.ts +7 -0
- package/dist/structure.js +10 -5
- package/dist/templates/portal/js/bootstrap.js +206 -0
- package/dist/templates/portal/js/consumer.js +128 -0
- package/dist/templates/portal/js/dispatch.js +181 -0
- package/dist/templates/portal/js/export.js +151 -0
- package/dist/templates/portal/js/glossary.js +72 -0
- package/dist/templates/portal/js/namespace.js +60 -0
- package/dist/templates/portal/js/palette-overlay.js +176 -0
- package/dist/templates/portal/js/palette.js +133 -0
- package/dist/templates/portal/js/router.js +169 -0
- package/dist/templates/portal/js/shell.js +264 -0
- package/dist/templates/portal/js/state-model.js +142 -0
- package/dist/templates/portal/js/tree.js +190 -0
- package/dist/templates/portal/js/views/coverage-view.js +247 -0
- package/dist/templates/portal/js/views/flows-view.js +204 -0
- package/dist/templates/portal/js/views/overview-view.js +167 -0
- package/dist/templates/portal/js/views/panel-aspect.js +141 -0
- package/dist/templates/portal/js/views/panel-view.js +314 -0
- package/dist/templates/portal/js/views/relations-matrix.js +185 -0
- package/dist/templates/portal/js/views/relations-view.js +185 -0
- package/dist/templates/portal/js/views/rulebook-view.js +174 -0
- package/dist/templates/portal/js/views/start-view.js +287 -0
- package/dist/templates/portal/js/views/suppressions-view.js +187 -0
- package/dist/templates/portal/js/views/tree-view.js +68 -0
- package/dist/templates/portal/js/views/types-view.js +154 -0
- package/dist/templates/portal/vendor/d3-hierarchy.js +350 -0
- package/package.json +5 -2
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/bin.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command3 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/cli/init.ts
|
|
7
7
|
import chalk2 from "chalk";
|
|
@@ -128,7 +128,7 @@ Verification runs per \`(aspect, unit)\` pair. A **unit** is the subject of one
|
|
|
128
128
|
|
|
129
129
|
Verification is all-or-nothing and repo-wide: \`yg check --approve\` fills EVERY unverified pair (deterministic first, free; then LLM). The only scoping flag is \`--only-deterministic\` (the free, keyless CI gate); \`yg impact\` is the pre-edit cost predictor. Deterministic checks run first; a node with an enforced deterministic refusal has its LLM pairs skipped that run, so a known-broken node never bills the reviewer. Refusals are cached like approvals.
|
|
130
130
|
|
|
131
|
-
Interrupting \`yg check --approve\` is safe: finished verdicts are already committed to the lock; only in-flight pairs are lost and the next run resumes them. Read the raw output \u2014 never pipe it through \`| grep\`, \`| head\`, or \`| tail\`: those silently drop lines and the count you act on stops matching the count the build enforces. The default \`yg check\` output is **grouped**: issues with the same rule are collapsed into one block (shared why+fix shown once, affected nodes listed beneath). When the output is still large, the SANCTIONED way to narrow it further is \`yg check --summary\` (per-node counts only) or \`yg check --top [N]\` (the N highest-priority GROUPS; bare \`--top\` shows just the single suggested-next group) \u2014 both always print the TRUE aggregate \`Errors (N)\`/\`Warnings (N)\` header and preserve the real exit code, so a narrowed view can never read as a clean build. To drill into one rule: \`yg check --aspect <id>\`. For the old ungrouped per-pair view: \`yg check --details\`. Orient with \`--summary\`/\`--top\`, then drill with \`--aspect\` or plain \`yg check\`. The reviewer already ran; the output is the return on that cost.
|
|
131
|
+
Interrupting \`yg check --approve\` is safe: finished verdicts are already committed to the lock; only in-flight pairs are lost and the next run resumes them. Read the raw output \u2014 never pipe it through \`| grep\`, \`| head\`, or \`| tail\`: those silently drop lines and the count you act on stops matching the count the build enforces. The default \`yg check\` output is **grouped**: issues with the same rule are collapsed into one block (shared why+fix shown once, affected nodes listed beneath). When a rule's fix is NODE-SPECIFIC (the next command names the node \u2014 e.g. a per-node log entry, or declaring a dependency in one node's file), the block instead prints EACH affected node's own fix beneath its line, so following the block clears every node, not just the first. The triage and narrowing flags are READ-ONLY views over that same result and never combine with a fill flag (\`--approve\` / \`--only-deterministic\`). When the output is still large, the SANCTIONED way to narrow it further is \`yg check --summary\` (per-node counts only) or \`yg check --top [N]\` (the N highest-priority GROUPS; bare \`--top\` shows just the single suggested-next group) \u2014 both always print the TRUE aggregate \`Errors (N)\`/\`Warnings (N)\` header and preserve the real exit code, so a narrowed view can never read as a clean build. To drill into one rule: \`yg check --aspect <id>\`. For the old ungrouped per-pair view: \`yg check --details\`. Orient with \`--summary\`/\`--top\`, then drill with \`--aspect\` or plain \`yg check\`. The reviewer already ran; the output is the return on that cost.
|
|
132
132
|
|
|
133
133
|
A draft aspect produces no expected pairs \u2014 nothing is verified, nothing recorded. Advisory refusals render as warnings (never block \`yg check\`); enforced refusals render as errors (block \`yg check\`). Verdicts survive status flips, including a \`draft\` round-trip: an entry for unchanged inputs stays valid when the aspect returns to enforced.
|
|
134
134
|
|
|
@@ -1440,7 +1440,7 @@ var PROVIDER_DEFAULTS = {
|
|
|
1440
1440
|
"codex": { model: "o4-mini" },
|
|
1441
1441
|
"gemini-cli": { model: "gemini-2.5-flash" }
|
|
1442
1442
|
};
|
|
1443
|
-
async function parseConfig(filePath) {
|
|
1443
|
+
async function parseConfig(filePath, opts) {
|
|
1444
1444
|
const filename = path4.basename(filePath);
|
|
1445
1445
|
const content20 = await readFile4(filePath, "utf-8");
|
|
1446
1446
|
const baseRaw = parseYaml3(content20);
|
|
@@ -1451,7 +1451,7 @@ async function parseConfig(filePath) {
|
|
|
1451
1451
|
next: "restore the file from version control, or regenerate it via `yg init`"
|
|
1452
1452
|
}, "config-invalid");
|
|
1453
1453
|
}
|
|
1454
|
-
const overlay = await loadConfigOverlay(path4.dirname(filePath));
|
|
1454
|
+
const overlay = opts?.skipSecretsOverlay ? void 0 : await loadConfigOverlay(path4.dirname(filePath));
|
|
1455
1455
|
const raw = overlay ? deepMerge(baseRaw, overlay) : baseRaw;
|
|
1456
1456
|
const version = typeof raw.version === "string" ? raw.version.trim() : void 0;
|
|
1457
1457
|
const qualityRaw = raw.quality;
|
|
@@ -2889,6 +2889,11 @@ async function parseArchitecture(filePath) {
|
|
|
2889
2889
|
const nodeTypesObj = nodeTypesRaw ?? {};
|
|
2890
2890
|
const nodeTypes = {};
|
|
2891
2891
|
for (const [typeName, val] of Object.entries(nodeTypesObj)) {
|
|
2892
|
+
if (typeName === "*") {
|
|
2893
|
+
throw new Error(
|
|
2894
|
+
`yg-architecture.yaml: node type name '*' is reserved \u2014 '*' is the any-target wildcard in relation lists and cannot be used as a node type name.`
|
|
2895
|
+
);
|
|
2896
|
+
}
|
|
2892
2897
|
const entry = val;
|
|
2893
2898
|
if (!entry || typeof entry !== "object" || typeof entry.description !== "string" || entry.description.trim() === "") {
|
|
2894
2899
|
throw new Error(
|
|
@@ -2925,7 +2930,7 @@ async function parseArchitecture(filePath) {
|
|
|
2925
2930
|
`yg-architecture.yaml: node_types.${typeName}.parents`,
|
|
2926
2931
|
"parent type name"
|
|
2927
2932
|
) : void 0;
|
|
2928
|
-
const relations = parseRelations2(entry.relations, typeName);
|
|
2933
|
+
const { lists: relations, relationDefault } = parseRelations2(entry.relations, typeName);
|
|
2929
2934
|
let logRequired;
|
|
2930
2935
|
if (entry.log_required !== void 0) {
|
|
2931
2936
|
if (typeof entry.log_required !== "boolean") {
|
|
@@ -2955,6 +2960,7 @@ async function parseArchitecture(filePath) {
|
|
|
2955
2960
|
...aspectStatus && { aspectStatus },
|
|
2956
2961
|
parents: parents && parents.length > 0 ? parents : void 0,
|
|
2957
2962
|
relations,
|
|
2963
|
+
...relationDefault !== void 0 && { relationDefault },
|
|
2958
2964
|
...logRequired !== void 0 && { log_required: logRequired },
|
|
2959
2965
|
...when !== void 0 && { when },
|
|
2960
2966
|
...enforce !== void 0 && { enforce }
|
|
@@ -2966,33 +2972,42 @@ async function parseArchitecture(filePath) {
|
|
|
2966
2972
|
}
|
|
2967
2973
|
function parseRelations2(relationsRaw, typeName) {
|
|
2968
2974
|
if (relationsRaw === void 0) {
|
|
2969
|
-
return void 0;
|
|
2975
|
+
return { lists: void 0 };
|
|
2970
2976
|
}
|
|
2971
2977
|
if (typeof relationsRaw !== "object" || Array.isArray(relationsRaw)) {
|
|
2972
2978
|
throw new Error(`yg-architecture.yaml: node_types.${typeName}.relations must be an object`);
|
|
2973
2979
|
}
|
|
2974
2980
|
const relations = {};
|
|
2975
|
-
|
|
2976
|
-
|
|
2981
|
+
let relationDefault;
|
|
2982
|
+
for (const [key, value] of Object.entries(relationsRaw)) {
|
|
2983
|
+
if (key === "default") {
|
|
2984
|
+
if (value !== "allow" && value !== "deny") {
|
|
2985
|
+
throw new Error(
|
|
2986
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.default must be 'allow' or 'deny' (got: ${JSON.stringify(value)})`
|
|
2987
|
+
);
|
|
2988
|
+
}
|
|
2989
|
+
relationDefault = value;
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
if (!VALID_RELATION_TYPES.has(key)) {
|
|
2977
2993
|
throw new Error(
|
|
2978
|
-
`yg-architecture.yaml: node_types.${typeName}.relations: unknown relation type '${
|
|
2994
|
+
`yg-architecture.yaml: node_types.${typeName}.relations: unknown relation type '${key}' (valid types: ${Array.from(VALID_RELATION_TYPES).join(", ")}; or 'default')`
|
|
2979
2995
|
);
|
|
2980
2996
|
}
|
|
2981
|
-
if (!Array.isArray(
|
|
2997
|
+
if (!Array.isArray(value)) {
|
|
2982
2998
|
throw new Error(
|
|
2983
|
-
`yg-architecture.yaml: node_types.${typeName}.relations.${
|
|
2999
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.${key} must be an array`
|
|
2984
3000
|
);
|
|
2985
3001
|
}
|
|
2986
3002
|
const targetStrings = assertStringArray(
|
|
2987
|
-
|
|
2988
|
-
`yg-architecture.yaml: node_types.${typeName}.relations.${
|
|
3003
|
+
value,
|
|
3004
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.${key}`,
|
|
2989
3005
|
"target type name"
|
|
2990
3006
|
);
|
|
2991
|
-
|
|
2992
|
-
relations[relType] = targetStrings;
|
|
2993
|
-
}
|
|
3007
|
+
relations[key] = targetStrings;
|
|
2994
3008
|
}
|
|
2995
|
-
|
|
3009
|
+
const hasLists = Object.keys(relations).length > 0;
|
|
3010
|
+
return { lists: hasLists ? relations : {}, relationDefault };
|
|
2996
3011
|
}
|
|
2997
3012
|
function assertStringArray(arr, fieldPath, itemLabel) {
|
|
2998
3013
|
const offenders = arr.map((value, index) => ({ value, index })).filter((e) => typeof e.value !== "string");
|
|
@@ -3133,7 +3148,7 @@ async function loadGraph(projectRoot, options = {}) {
|
|
|
3133
3148
|
let configErrorMessage;
|
|
3134
3149
|
let config = FALLBACK_CONFIG;
|
|
3135
3150
|
try {
|
|
3136
|
-
config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"));
|
|
3151
|
+
config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"), { skipSecretsOverlay: options.noSecrets });
|
|
3137
3152
|
} catch (error) {
|
|
3138
3153
|
if (error instanceof ConfigParseError) {
|
|
3139
3154
|
configErrorMessage = error.messageData;
|
|
@@ -3322,7 +3337,8 @@ ${msg.next}`;
|
|
|
3322
3337
|
}
|
|
3323
3338
|
|
|
3324
3339
|
// src/io/lock-store.ts
|
|
3325
|
-
import { readFileSync, unlinkSync } from "fs";
|
|
3340
|
+
import { readFileSync, unlinkSync, existsSync as existsSync2 } from "fs";
|
|
3341
|
+
import { createHash } from "crypto";
|
|
3326
3342
|
import path11 from "path";
|
|
3327
3343
|
|
|
3328
3344
|
// src/model/lock.ts
|
|
@@ -3368,6 +3384,20 @@ function logsLockPath(yggRoot) {
|
|
|
3368
3384
|
function detLockPath(yggRoot) {
|
|
3369
3385
|
return path11.join(yggRoot, LOCK_DET_FILE_NAME);
|
|
3370
3386
|
}
|
|
3387
|
+
function committedLockContentHash(yggRoot) {
|
|
3388
|
+
const parts = [];
|
|
3389
|
+
for (const file of [nondetLockPath(yggRoot), logsLockPath(yggRoot)]) {
|
|
3390
|
+
if (!existsSync2(file)) continue;
|
|
3391
|
+
try {
|
|
3392
|
+
parts.push(readFileSync(file));
|
|
3393
|
+
} catch {
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
if (parts.length === 0) return "";
|
|
3397
|
+
const hash = createHash("sha256");
|
|
3398
|
+
for (const p2 of parts) hash.update(p2);
|
|
3399
|
+
return hash.digest("hex");
|
|
3400
|
+
}
|
|
3371
3401
|
function recoveryNext(ctx) {
|
|
3372
3402
|
if (ctx.committed) {
|
|
3373
3403
|
return `restore the file from git (\`git checkout HEAD -- .yggdrasil/${ctx.fileName}\`), OR
|
|
@@ -3714,7 +3744,7 @@ import { rm as rm3, stat as stat3 } from "fs/promises";
|
|
|
3714
3744
|
|
|
3715
3745
|
// src/migrations/split-lock.ts
|
|
3716
3746
|
import path12 from "path";
|
|
3717
|
-
import { readdirSync, existsSync as
|
|
3747
|
+
import { readdirSync, existsSync as existsSync3 } from "fs";
|
|
3718
3748
|
import { readFile as readFile10, writeFile as writeFile4, rm as rm2 } from "fs/promises";
|
|
3719
3749
|
async function splitLock(yggRoot) {
|
|
3720
3750
|
const legacy = readLegacyLock(yggRoot);
|
|
@@ -3732,7 +3762,7 @@ async function splitLock(yggRoot) {
|
|
|
3732
3762
|
function scanDeterministicAspectIds(yggRoot) {
|
|
3733
3763
|
const aspectsRoot = path12.join(yggRoot, "aspects");
|
|
3734
3764
|
const ids = /* @__PURE__ */ new Set();
|
|
3735
|
-
if (!
|
|
3765
|
+
if (!existsSync3(aspectsRoot)) return ids;
|
|
3736
3766
|
const walk2 = (dir) => {
|
|
3737
3767
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3738
3768
|
const full = path12.join(dir, entry.name);
|
|
@@ -3828,6 +3858,9 @@ async function ensureGitattributes(repoRoot) {
|
|
|
3828
3858
|
var YGGDRASIL_GITIGNORE_LINES = [
|
|
3829
3859
|
"yg-secrets.yaml",
|
|
3830
3860
|
".symbols-cache/",
|
|
3861
|
+
// Content-addressed per-file AST fact cache: a local speed cache the relation pass rebuilds
|
|
3862
|
+
// free on the next run; never committed.
|
|
3863
|
+
".ast-cache/",
|
|
3831
3864
|
".debug.log",
|
|
3832
3865
|
// Deterministic-verdict lock: a local cache rebuilt for free by
|
|
3833
3866
|
// `yg check --approve --only-deterministic`; never committed.
|
|
@@ -4658,6 +4691,15 @@ function attachmentMachineOrigin(att, node) {
|
|
|
4658
4691
|
return `port:${att.port.name}@${att.port.target}`;
|
|
4659
4692
|
}
|
|
4660
4693
|
}
|
|
4694
|
+
function hasNonDraftEffectiveAspects(node, graph) {
|
|
4695
|
+
const statuses = computeEffectiveAspectStatuses(node, graph);
|
|
4696
|
+
for (const [aspectId, s] of statuses) {
|
|
4697
|
+
if (s === "draft") continue;
|
|
4698
|
+
if (isAggregateAspect(graph, aspectId)) continue;
|
|
4699
|
+
return true;
|
|
4700
|
+
}
|
|
4701
|
+
return false;
|
|
4702
|
+
}
|
|
4661
4703
|
function isAggregateAspect(graph, aspectId) {
|
|
4662
4704
|
return graph.aspects.find((a) => a.id === aspectId)?.reviewer.type === "aggregate";
|
|
4663
4705
|
}
|
|
@@ -4886,11 +4928,11 @@ function normPath(p2) {
|
|
|
4886
4928
|
}
|
|
4887
4929
|
function countDependents(graph, nodePath) {
|
|
4888
4930
|
const paths = [];
|
|
4889
|
-
for (const [
|
|
4931
|
+
for (const [path62, node] of graph.nodes) {
|
|
4890
4932
|
const hasRelation = (node.meta.relations ?? []).some(
|
|
4891
4933
|
(r) => r.target === nodePath && (STRUCTURAL_RELATION_TYPES.has(r.type) || EVENT_RELATION_TYPES.has(r.type))
|
|
4892
4934
|
);
|
|
4893
|
-
if (hasRelation) paths.push(
|
|
4935
|
+
if (hasRelation) paths.push(path62);
|
|
4894
4936
|
}
|
|
4895
4937
|
return { count: paths.length, paths };
|
|
4896
4938
|
}
|
|
@@ -5457,7 +5499,7 @@ function renderNode(node, indent, lines) {
|
|
|
5457
5499
|
// src/io/hash.ts
|
|
5458
5500
|
import { readFile as readFile14, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
5459
5501
|
import path16 from "path";
|
|
5460
|
-
import { createHash } from "crypto";
|
|
5502
|
+
import { createHash as createHash2 } from "crypto";
|
|
5461
5503
|
import { createRequire as createRequire2 } from "module";
|
|
5462
5504
|
|
|
5463
5505
|
// src/io/repo-scanner.ts
|
|
@@ -5583,10 +5625,10 @@ function isIgnoredByStack2(candidatePath, stack) {
|
|
|
5583
5625
|
return false;
|
|
5584
5626
|
}
|
|
5585
5627
|
function hashString(content20) {
|
|
5586
|
-
return
|
|
5628
|
+
return createHash2("sha256").update(content20).digest("hex");
|
|
5587
5629
|
}
|
|
5588
5630
|
function hashBytes(bytes) {
|
|
5589
|
-
return
|
|
5631
|
+
return createHash2("sha256").update(normalizeLineEndings(bytes)).digest("hex");
|
|
5590
5632
|
}
|
|
5591
5633
|
async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
|
|
5592
5634
|
let stack = options.gitignoreStack ?? [];
|
|
@@ -5709,19 +5751,19 @@ function checkArchitectureParentCycles(graph) {
|
|
|
5709
5751
|
const color = new Map(typeIds.map((id) => [id, WHITE]));
|
|
5710
5752
|
const backEdges = /* @__PURE__ */ new Set();
|
|
5711
5753
|
const recordedCycles = [];
|
|
5712
|
-
function dfs(typeId,
|
|
5754
|
+
function dfs(typeId, path62) {
|
|
5713
5755
|
if (color.get(typeId) === GRAY) {
|
|
5714
|
-
const cycleStart =
|
|
5715
|
-
if (cycleStart !== -1) recordedCycles.push([...
|
|
5716
|
-
const from =
|
|
5756
|
+
const cycleStart = path62.indexOf(typeId);
|
|
5757
|
+
if (cycleStart !== -1) recordedCycles.push([...path62.slice(cycleStart), typeId]);
|
|
5758
|
+
const from = path62[path62.length - 1];
|
|
5717
5759
|
if (from !== void 0) backEdges.add(`${from}->${typeId}`);
|
|
5718
5760
|
return;
|
|
5719
5761
|
}
|
|
5720
5762
|
if (color.get(typeId) === BLACK) return;
|
|
5721
5763
|
color.set(typeId, GRAY);
|
|
5722
|
-
|
|
5723
|
-
for (const parent of types[typeId]?.parents ?? []) dfs(parent,
|
|
5724
|
-
|
|
5764
|
+
path62.push(typeId);
|
|
5765
|
+
for (const parent of types[typeId]?.parents ?? []) dfs(parent, path62);
|
|
5766
|
+
path62.pop();
|
|
5725
5767
|
color.set(typeId, BLACK);
|
|
5726
5768
|
}
|
|
5727
5769
|
for (const id of typeIds) {
|
|
@@ -5947,18 +5989,23 @@ function checkArchitectureRelations(graph) {
|
|
|
5947
5989
|
const issues = [];
|
|
5948
5990
|
for (const [nodePath, node] of graph.nodes) {
|
|
5949
5991
|
const typeConfig = graph.architecture.node_types[node.meta.type];
|
|
5950
|
-
if (!typeConfig
|
|
5992
|
+
if (!typeConfig || !node.meta.relations || node.meta.relations.length === 0) {
|
|
5993
|
+
continue;
|
|
5994
|
+
}
|
|
5995
|
+
const lists = typeConfig.relations;
|
|
5996
|
+
const def = typeConfig.relationDefault ?? "allow";
|
|
5997
|
+
if ((!lists || Object.keys(lists).length === 0) && def === "allow") {
|
|
5951
5998
|
continue;
|
|
5952
5999
|
}
|
|
5953
6000
|
for (const rel of node.meta.relations) {
|
|
5954
|
-
const
|
|
5955
|
-
if (!allowedTypes) continue;
|
|
6001
|
+
const allowed = lists?.[rel.type];
|
|
5956
6002
|
const target = graph.nodes.get(rel.target);
|
|
5957
6003
|
if (!target) continue;
|
|
5958
|
-
if (
|
|
6004
|
+
if (allowed !== void 0) {
|
|
6005
|
+
if (allowed.includes("*") || allowed.includes(target.meta.type)) continue;
|
|
5959
6006
|
const msgData = {
|
|
5960
6007
|
what: `Relation '${rel.type}' from '${nodePath}' to '${rel.target}' (type '${target.meta.type}') is not allowed by the architecture.`,
|
|
5961
|
-
why: `Allowed targets for '${rel.type}' from type '${node.meta.type}': [${
|
|
6008
|
+
why: `Allowed targets for '${rel.type}' from type '${node.meta.type}': [${allowed.join(", ")}]`,
|
|
5962
6009
|
next: `Either change the relation type, change the target node's type, or update yg-architecture.yaml to allow this relation.`
|
|
5963
6010
|
};
|
|
5964
6011
|
issues.push({
|
|
@@ -5969,6 +6016,20 @@ function checkArchitectureRelations(graph) {
|
|
|
5969
6016
|
...issueMsg(msgData),
|
|
5970
6017
|
messageData: msgData
|
|
5971
6018
|
});
|
|
6019
|
+
} else if (def === "deny") {
|
|
6020
|
+
const msgData = {
|
|
6021
|
+
what: `Relation '${rel.type}' from '${nodePath}' to '${rel.target}' (type '${target.meta.type}') is not allowed by the architecture.`,
|
|
6022
|
+
why: `Type '${node.meta.type}' denies relation '${rel.type}' by default (relations.default: deny, and no allow-list entry permits this target).`,
|
|
6023
|
+
next: `Open '${rel.type}' for type '${node.meta.type}' in yg-architecture.yaml (list target types, or ['*'] for any), change a node's type, or remove the relation.`
|
|
6024
|
+
};
|
|
6025
|
+
issues.push({
|
|
6026
|
+
severity: "error",
|
|
6027
|
+
code: "relation-target-forbidden",
|
|
6028
|
+
rule: "invalid-relation-target",
|
|
6029
|
+
nodePath,
|
|
6030
|
+
...issueMsg(msgData),
|
|
6031
|
+
messageData: msgData
|
|
6032
|
+
});
|
|
5972
6033
|
}
|
|
5973
6034
|
}
|
|
5974
6035
|
}
|
|
@@ -7860,7 +7921,7 @@ async function computeSourceFingerprint(graph, nodePath) {
|
|
|
7860
7921
|
}
|
|
7861
7922
|
|
|
7862
7923
|
// src/core/log/log-gate.ts
|
|
7863
|
-
import { createHash as
|
|
7924
|
+
import { createHash as createHash3 } from "crypto";
|
|
7864
7925
|
import path22 from "path";
|
|
7865
7926
|
|
|
7866
7927
|
// src/core/parsing/log-parser.ts
|
|
@@ -7956,7 +8017,7 @@ function computeLogBaselineFromContent(content20) {
|
|
|
7956
8017
|
const prefix = bytes.subarray(0, newest.offsetEnd);
|
|
7957
8018
|
return {
|
|
7958
8019
|
last_entry_datetime: newest.datetime,
|
|
7959
|
-
prefix_hash:
|
|
8020
|
+
prefix_hash: createHash3("sha256").update(prefix).digest("hex")
|
|
7960
8021
|
};
|
|
7961
8022
|
}
|
|
7962
8023
|
|
|
@@ -8184,11 +8245,11 @@ function registerTreeCommand(program2) {
|
|
|
8184
8245
|
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
8185
8246
|
let roots;
|
|
8186
8247
|
if (options.root?.trim()) {
|
|
8187
|
-
const
|
|
8188
|
-
const node = graph.nodes.get(
|
|
8248
|
+
const path62 = options.root.trim().replace(/\/$/, "");
|
|
8249
|
+
const node = graph.nodes.get(path62);
|
|
8189
8250
|
if (!node) {
|
|
8190
8251
|
process.stderr.write(chalk4.red(buildIssueMessage({
|
|
8191
|
-
what: `Node '${
|
|
8252
|
+
what: `Node '${path62}' not found.`,
|
|
8192
8253
|
why: `The --root path must be a valid node path in the graph.`,
|
|
8193
8254
|
next: `Run yg tree (no --root) to list all nodes, then pick a valid path.`
|
|
8194
8255
|
}) + "\n"));
|
|
@@ -8399,14 +8460,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
|
|
|
8399
8460
|
}
|
|
8400
8461
|
const chains = [];
|
|
8401
8462
|
for (const node of transitiveOnly) {
|
|
8402
|
-
const
|
|
8463
|
+
const path62 = [];
|
|
8403
8464
|
let current = node;
|
|
8404
8465
|
while (current) {
|
|
8405
|
-
|
|
8466
|
+
path62.unshift(current);
|
|
8406
8467
|
current = parent.get(current);
|
|
8407
8468
|
}
|
|
8408
|
-
if (
|
|
8409
|
-
chains.push(
|
|
8469
|
+
if (path62.length >= 3) {
|
|
8470
|
+
chains.push(path62.slice(1).map((p2) => `<- ${p2}`).join(" "));
|
|
8410
8471
|
}
|
|
8411
8472
|
}
|
|
8412
8473
|
return chains.sort();
|
|
@@ -8438,14 +8499,14 @@ function collectIndirectDependents(graph, directlyAffected) {
|
|
|
8438
8499
|
}
|
|
8439
8500
|
for (const [node] of parent) {
|
|
8440
8501
|
if (directSet.has(node)) continue;
|
|
8441
|
-
const
|
|
8502
|
+
const path62 = [node];
|
|
8442
8503
|
let current = node;
|
|
8443
8504
|
while (parent.has(current)) {
|
|
8444
8505
|
current = parent.get(current);
|
|
8445
|
-
|
|
8506
|
+
path62.push(current);
|
|
8446
8507
|
}
|
|
8447
|
-
const chain =
|
|
8448
|
-
const depth =
|
|
8508
|
+
const chain = path62.map((p2) => `<- ${p2}`).join(" ");
|
|
8509
|
+
const depth = path62.length;
|
|
8449
8510
|
const existing = bestChain.get(node);
|
|
8450
8511
|
if (!existing || depth < existing.depth) {
|
|
8451
8512
|
bestChain.set(node, { chain, depth });
|
|
@@ -8546,14 +8607,14 @@ import { register } from "module";
|
|
|
8546
8607
|
import { pathToFileURL } from "url";
|
|
8547
8608
|
import path24 from "path";
|
|
8548
8609
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8549
|
-
import { existsSync as
|
|
8610
|
+
import { existsSync as existsSync4 } from "fs";
|
|
8550
8611
|
var __filename = fileURLToPath2(import.meta.url);
|
|
8551
8612
|
var __dirname = path24.dirname(__filename);
|
|
8552
8613
|
var registered = false;
|
|
8553
8614
|
function ensureLoaderRegistered() {
|
|
8554
8615
|
if (registered) return;
|
|
8555
8616
|
let implPath = path24.resolve(__dirname, "./loader-hook-impl.js");
|
|
8556
|
-
if (!
|
|
8617
|
+
if (!existsSync4(implPath)) {
|
|
8557
8618
|
const pkgRoot = path24.resolve(__dirname, "../../");
|
|
8558
8619
|
implPath = path24.resolve(pkgRoot, "dist/loader-hook-impl.js");
|
|
8559
8620
|
}
|
|
@@ -8565,9 +8626,9 @@ function ensureLoaderRegistered() {
|
|
|
8565
8626
|
import * as fs from "fs";
|
|
8566
8627
|
import * as path25 from "path";
|
|
8567
8628
|
var UndeclaredFsReadError = class extends Error {
|
|
8568
|
-
constructor(
|
|
8569
|
-
super(`structure-aspect-undeclared-fs-read: ${
|
|
8570
|
-
this.path =
|
|
8629
|
+
constructor(path62) {
|
|
8630
|
+
super(`structure-aspect-undeclared-fs-read: ${path62}`);
|
|
8631
|
+
this.path = path62;
|
|
8571
8632
|
this.name = "UndeclaredFsReadError";
|
|
8572
8633
|
}
|
|
8573
8634
|
path;
|
|
@@ -8770,7 +8831,11 @@ function createCtxGraph(params) {
|
|
|
8770
8831
|
}
|
|
8771
8832
|
return {
|
|
8772
8833
|
node(id) {
|
|
8773
|
-
|
|
8834
|
+
const self = graph.nodes.get(currentNodePath);
|
|
8835
|
+
if (self) recordGraphNode(self);
|
|
8836
|
+
if (!allowed.has(id)) {
|
|
8837
|
+
assertAllowed(id);
|
|
8838
|
+
}
|
|
8774
8839
|
const m = graph.nodes.get(id);
|
|
8775
8840
|
if (!m) {
|
|
8776
8841
|
if (recorder) recorder.recordGraphNodeAbsent(id);
|
|
@@ -8851,8 +8916,9 @@ import { parse as parseTomlSmol } from "smol-toml";
|
|
|
8851
8916
|
import { Parser, Language } from "web-tree-sitter";
|
|
8852
8917
|
import path27 from "path";
|
|
8853
8918
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8854
|
-
import { existsSync as
|
|
8919
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
8855
8920
|
import { createRequire as createRequire3 } from "module";
|
|
8921
|
+
import { createHash as createHash4 } from "crypto";
|
|
8856
8922
|
var _require = createRequire3(import.meta.url);
|
|
8857
8923
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
8858
8924
|
var __dirname2 = path27.dirname(__filename2);
|
|
@@ -8863,6 +8929,19 @@ var GRAMMAR_DIRS = [
|
|
|
8863
8929
|
var initPromise = null;
|
|
8864
8930
|
var langCache = /* @__PURE__ */ new Map();
|
|
8865
8931
|
var parserCache = /* @__PURE__ */ new Map();
|
|
8932
|
+
var wasmHashCache = /* @__PURE__ */ new Map();
|
|
8933
|
+
function grammarWasmHash(extension) {
|
|
8934
|
+
const cached = wasmHashCache.get(extension);
|
|
8935
|
+
if (cached !== void 0) return cached;
|
|
8936
|
+
const info = getGrammarForExtension(extension);
|
|
8937
|
+
if (!info) {
|
|
8938
|
+
throw new Error(`no grammar for extension '${extension}'`);
|
|
8939
|
+
}
|
|
8940
|
+
const wasmPath = resolveWasm(info.wasmFile, info.wasmPackage);
|
|
8941
|
+
const hash = createHash4("sha256").update(readFileSync4(wasmPath)).digest("hex");
|
|
8942
|
+
wasmHashCache.set(extension, hash);
|
|
8943
|
+
return hash;
|
|
8944
|
+
}
|
|
8866
8945
|
function init() {
|
|
8867
8946
|
if (initPromise === null) {
|
|
8868
8947
|
initPromise = Parser.init();
|
|
@@ -8875,12 +8954,12 @@ function init() {
|
|
|
8875
8954
|
function resolveWasm(filename, pkg2) {
|
|
8876
8955
|
for (const dir of GRAMMAR_DIRS) {
|
|
8877
8956
|
const p2 = path27.join(dir, filename);
|
|
8878
|
-
if (
|
|
8957
|
+
if (existsSync6(p2)) return p2;
|
|
8879
8958
|
}
|
|
8880
8959
|
try {
|
|
8881
8960
|
const pkgDir = path27.dirname(_require.resolve(`${pkg2}/package.json`));
|
|
8882
8961
|
for (const candidate of [path27.join(pkgDir, filename), path27.join(pkgDir, "bindings/node", filename)]) {
|
|
8883
|
-
if (
|
|
8962
|
+
if (existsSync6(candidate)) return candidate;
|
|
8884
8963
|
}
|
|
8885
8964
|
} catch {
|
|
8886
8965
|
}
|
|
@@ -10505,10 +10584,10 @@ function registerFlowsCommand(program2) {
|
|
|
10505
10584
|
import chalk8 from "chalk";
|
|
10506
10585
|
|
|
10507
10586
|
// src/core/check.ts
|
|
10508
|
-
import
|
|
10587
|
+
import path43 from "path";
|
|
10509
10588
|
|
|
10510
10589
|
// src/core/log-integrity.ts
|
|
10511
|
-
import { createHash as
|
|
10590
|
+
import { createHash as createHash5 } from "crypto";
|
|
10512
10591
|
var DATETIME_STRICT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,3}Z$/;
|
|
10513
10592
|
function validateAppendOnly(currentContent, storedDatetime, storedPrefixHash) {
|
|
10514
10593
|
if (!DATETIME_STRICT.test(storedDatetime)) {
|
|
@@ -10521,7 +10600,7 @@ function validateAppendOnly(currentContent, storedDatetime, storedPrefixHash) {
|
|
|
10521
10600
|
if (!boundary) return { ok: false, reason: "boundary_missing" };
|
|
10522
10601
|
const bytes = Buffer.from(currentContent, "utf-8");
|
|
10523
10602
|
const prefix = bytes.subarray(0, boundary.offsetEnd);
|
|
10524
|
-
const computed =
|
|
10603
|
+
const computed = createHash5("sha256").update(prefix).digest("hex");
|
|
10525
10604
|
if (computed !== storedPrefixHash) return { ok: false, reason: "prefix_modified" };
|
|
10526
10605
|
return { ok: true };
|
|
10527
10606
|
}
|
|
@@ -11544,8 +11623,6 @@ import path33 from "path";
|
|
|
11544
11623
|
import { readFile as readFile15 } from "fs/promises";
|
|
11545
11624
|
|
|
11546
11625
|
// src/relations/symbol-table.ts
|
|
11547
|
-
import path32 from "path";
|
|
11548
|
-
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
11549
11626
|
var SymbolTable = class {
|
|
11550
11627
|
defs = /* @__PURE__ */ new Map();
|
|
11551
11628
|
// `${language}\0${symbolKey}` → set of defining files
|
|
@@ -11586,30 +11663,6 @@ var SymbolTable = class {
|
|
|
11586
11663
|
return s ? [...s] : [];
|
|
11587
11664
|
}
|
|
11588
11665
|
};
|
|
11589
|
-
var SYMBOL_INDEX_VERSION = 3;
|
|
11590
|
-
function indexPath(cacheDir, language) {
|
|
11591
|
-
return path32.join(cacheDir, `symbols-${language}.json`);
|
|
11592
|
-
}
|
|
11593
|
-
function canonBuiltFrom(b) {
|
|
11594
|
-
return JSON.stringify([...b].sort((x, y) => x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : 0));
|
|
11595
|
-
}
|
|
11596
|
-
async function writeSymbolIndex(cacheDir, language, idx) {
|
|
11597
|
-
await atomicWriteFile(indexPath(cacheDir, language), JSON.stringify({ ...idx, version: SYMBOL_INDEX_VERSION }));
|
|
11598
|
-
}
|
|
11599
|
-
function loadSymbolIndex(cacheDir, language, currentBuiltFrom) {
|
|
11600
|
-
const p2 = indexPath(cacheDir, language);
|
|
11601
|
-
if (!existsSync6(p2)) return null;
|
|
11602
|
-
let parsed;
|
|
11603
|
-
try {
|
|
11604
|
-
parsed = JSON.parse(readFileSync6(p2, "utf-8"));
|
|
11605
|
-
} catch {
|
|
11606
|
-
return null;
|
|
11607
|
-
}
|
|
11608
|
-
if (!parsed || !Array.isArray(parsed.builtFrom) || !Array.isArray(parsed.symbols)) return null;
|
|
11609
|
-
if (parsed.version !== SYMBOL_INDEX_VERSION) return null;
|
|
11610
|
-
if (canonBuiltFrom(parsed.builtFrom) !== canonBuiltFrom(currentBuiltFrom)) return null;
|
|
11611
|
-
return parsed;
|
|
11612
|
-
}
|
|
11613
11666
|
|
|
11614
11667
|
// src/relations/resolver.ts
|
|
11615
11668
|
function resolveCandidateGroup(candidates2, resolver, fromFile, language) {
|
|
@@ -11841,82 +11894,35 @@ function buildUsingScope(file) {
|
|
|
11841
11894
|
});
|
|
11842
11895
|
return { prefixes, globalPrefixes, aliases, globalAliases, staticTargets };
|
|
11843
11896
|
}
|
|
11844
|
-
function collectGlobalUsings(file) {
|
|
11845
|
-
return buildUsingScope(file).globalPrefixes;
|
|
11846
|
-
}
|
|
11847
|
-
function collectGlobalUsingAliases(file) {
|
|
11848
|
-
return [...buildUsingScope(file).globalAliases.entries()];
|
|
11849
|
-
}
|
|
11850
11897
|
function enclosingNamespaceChain(fileNs, node) {
|
|
11851
11898
|
const segments = [fileNs, blockNamespace(node)].filter((p2) => p2 !== "").join(".").split(".").filter((s) => s !== "");
|
|
11852
11899
|
const chain = [];
|
|
11853
11900
|
for (let i = segments.length; i >= 1; i--) chain.push(segments.slice(0, i).join("."));
|
|
11854
11901
|
return chain;
|
|
11855
11902
|
}
|
|
11856
|
-
function
|
|
11857
|
-
const out = [];
|
|
11903
|
+
function extractCsharpRefs(file) {
|
|
11858
11904
|
const scope = buildUsingScope(file);
|
|
11859
11905
|
const fileNs = fileScopedNamespace(file.tree.rootNode);
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
}
|
|
11863
|
-
const usingPrefixes = [
|
|
11864
|
-
.../* @__PURE__ */ new Set([
|
|
11865
|
-
...scope.prefixes,
|
|
11866
|
-
...scope.globalPrefixes,
|
|
11867
|
-
...options.projectGlobalUsings ?? []
|
|
11868
|
-
])
|
|
11869
|
-
].sort();
|
|
11870
|
-
const dedupKeys = (keys) => {
|
|
11871
|
-
const seen = /* @__PURE__ */ new Set();
|
|
11872
|
-
const out2 = [];
|
|
11873
|
-
for (const k of keys) {
|
|
11874
|
-
if (k === void 0 || k === "") continue;
|
|
11875
|
-
if (seen.has(k)) continue;
|
|
11876
|
-
seen.add(k);
|
|
11877
|
-
out2.push(k);
|
|
11878
|
-
}
|
|
11879
|
-
return out2;
|
|
11880
|
-
};
|
|
11906
|
+
const refs = [];
|
|
11907
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
11881
11908
|
const pushRef = (ref, node, line, rooted = false) => {
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
11898
|
-
if (pushed.has(hint.symbolKey)) return;
|
|
11899
|
-
pushed.add(hint.symbolKey);
|
|
11900
|
-
}
|
|
11901
|
-
candidates2.push(hint);
|
|
11902
|
-
};
|
|
11903
|
-
if (aliasTarget !== void 0) {
|
|
11904
|
-
const tail = ref.slice(lead.length);
|
|
11905
|
-
const aliasKey = `${aliasTarget}${tail}`;
|
|
11906
|
-
const coDef = dedupKeys([aliasKey, ...enclosing, ref]).map((k) => ({
|
|
11907
|
-
symbolKey: k
|
|
11908
|
-
}));
|
|
11909
|
-
add({ kind: "symbol", symbolKey: aliasKey, set: coDef });
|
|
11910
|
-
}
|
|
11911
|
-
for (const k of enclosing) add({ kind: "symbol", symbolKey: k });
|
|
11912
|
-
for (const m of usingMembers) {
|
|
11913
|
-
add({ kind: "symbol", symbolKey: m.symbolKey, nestedOnly: m.nestedOnly, set: usingMembers });
|
|
11914
|
-
}
|
|
11915
|
-
add({ kind: "symbol", symbolKey: ref });
|
|
11916
|
-
if (candidates2.length === 0) return;
|
|
11917
|
-
out.push({ candidates: candidates2, kind: "import", line });
|
|
11909
|
+
refs.push({
|
|
11910
|
+
kind: "plain",
|
|
11911
|
+
ref,
|
|
11912
|
+
line,
|
|
11913
|
+
rooted,
|
|
11914
|
+
enclosingNs: enclosingNamespaceChain(fileNs, node)
|
|
11915
|
+
});
|
|
11916
|
+
};
|
|
11917
|
+
const pushAttribute = (written, suffixed, node, line) => {
|
|
11918
|
+
refs.push({
|
|
11919
|
+
kind: "attr",
|
|
11920
|
+
written,
|
|
11921
|
+
suffixed,
|
|
11922
|
+
line,
|
|
11923
|
+
enclosingNs: enclosingNamespaceChain(fileNs, node)
|
|
11924
|
+
});
|
|
11918
11925
|
};
|
|
11919
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
11920
11926
|
const emitTypeNode = (typeNode) => {
|
|
11921
11927
|
if (typeNode === null) return;
|
|
11922
11928
|
switch (typeNode.type) {
|
|
@@ -11943,15 +11949,15 @@ function uses(file, options = {}) {
|
|
|
11943
11949
|
return;
|
|
11944
11950
|
}
|
|
11945
11951
|
if (aliasField !== null) {
|
|
11946
|
-
const
|
|
11947
|
-
if (
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
}
|
|
11952
|
+
const nameField = typeNode.childForFieldName("name");
|
|
11953
|
+
if (nameField !== null) {
|
|
11954
|
+
refs.push({
|
|
11955
|
+
kind: "alias",
|
|
11956
|
+
aliasName: aliasField.text,
|
|
11957
|
+
tail: stripGlobalQualifier(nameField.text),
|
|
11958
|
+
line: typeNode.startPosition.row + 1,
|
|
11959
|
+
enclosingNs: enclosingNamespaceChain(fileNs, typeNode)
|
|
11960
|
+
});
|
|
11955
11961
|
}
|
|
11956
11962
|
}
|
|
11957
11963
|
return;
|
|
@@ -11988,11 +11994,7 @@ function uses(file, options = {}) {
|
|
|
11988
11994
|
return null;
|
|
11989
11995
|
}
|
|
11990
11996
|
for (const target of scope.staticTargets) {
|
|
11991
|
-
|
|
11992
|
-
candidates: [{ kind: "symbol", symbolKey: target.fqn }],
|
|
11993
|
-
kind: "import",
|
|
11994
|
-
line: target.line
|
|
11995
|
-
});
|
|
11997
|
+
refs.push({ kind: "static", fqn: target.fqn, line: target.line });
|
|
11996
11998
|
}
|
|
11997
11999
|
walk(file.tree.rootNode, (node) => {
|
|
11998
12000
|
if (node.type !== "using_directive") return void 0;
|
|
@@ -12120,10 +12122,75 @@ function uses(file, options = {}) {
|
|
|
12120
12122
|
}
|
|
12121
12123
|
return void 0;
|
|
12122
12124
|
});
|
|
12123
|
-
|
|
12125
|
+
return { fileNs, scope, refs };
|
|
12126
|
+
}
|
|
12127
|
+
function assembleCsharpCandidates(extract, options = {}) {
|
|
12128
|
+
const out = [];
|
|
12129
|
+
const { scope, refs } = extract;
|
|
12130
|
+
const aliases = new Map(scope.aliases);
|
|
12131
|
+
for (const [name, fqn] of options.projectGlobalUsingAliases ?? []) {
|
|
12132
|
+
if (!aliases.has(name)) aliases.set(name, fqn);
|
|
12133
|
+
}
|
|
12134
|
+
const usingPrefixes = [
|
|
12135
|
+
.../* @__PURE__ */ new Set([
|
|
12136
|
+
...scope.prefixes,
|
|
12137
|
+
...scope.globalPrefixes,
|
|
12138
|
+
...options.projectGlobalUsings ?? []
|
|
12139
|
+
])
|
|
12140
|
+
].sort();
|
|
12141
|
+
const dedupKeys = (keys) => {
|
|
12142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12143
|
+
const out2 = [];
|
|
12144
|
+
for (const k of keys) {
|
|
12145
|
+
if (k === void 0 || k === "") continue;
|
|
12146
|
+
if (seen.has(k)) continue;
|
|
12147
|
+
seen.add(k);
|
|
12148
|
+
out2.push(k);
|
|
12149
|
+
}
|
|
12150
|
+
return out2;
|
|
12151
|
+
};
|
|
12152
|
+
const pushRef = (ref, enclosingNs, line, rooted) => {
|
|
12153
|
+
if (rooted) {
|
|
12154
|
+
out.push({ candidates: [{ kind: "symbol", symbolKey: ref }], kind: "import", line });
|
|
12155
|
+
return;
|
|
12156
|
+
}
|
|
12157
|
+
const lead = ref.split(".")[0];
|
|
12158
|
+
const multi = ref.includes(".");
|
|
12159
|
+
const aliasTarget = aliases.get(lead);
|
|
12160
|
+
const enclosing = enclosingNs.map((ns) => `${ns}.${ref}`);
|
|
12161
|
+
const usingMembers = usingPrefixes.map((p2) => ({
|
|
12162
|
+
symbolKey: `${p2}.${ref}`,
|
|
12163
|
+
nestedOnly: multi
|
|
12164
|
+
}));
|
|
12165
|
+
const candidates2 = [];
|
|
12166
|
+
const pushed = /* @__PURE__ */ new Set();
|
|
12167
|
+
const add = (hint) => {
|
|
12168
|
+
if (hint.kind === "symbol") {
|
|
12169
|
+
if (pushed.has(hint.symbolKey)) return;
|
|
12170
|
+
pushed.add(hint.symbolKey);
|
|
12171
|
+
}
|
|
12172
|
+
candidates2.push(hint);
|
|
12173
|
+
};
|
|
12174
|
+
if (aliasTarget !== void 0) {
|
|
12175
|
+
const tail = ref.slice(lead.length);
|
|
12176
|
+
const aliasKey = `${aliasTarget}${tail}`;
|
|
12177
|
+
const coDef = dedupKeys([aliasKey, ...enclosing, ref]).map((k) => ({
|
|
12178
|
+
symbolKey: k
|
|
12179
|
+
}));
|
|
12180
|
+
add({ kind: "symbol", symbolKey: aliasKey, set: coDef });
|
|
12181
|
+
}
|
|
12182
|
+
for (const k of enclosing) add({ kind: "symbol", symbolKey: k });
|
|
12183
|
+
for (const m of usingMembers) {
|
|
12184
|
+
add({ kind: "symbol", symbolKey: m.symbolKey, nestedOnly: m.nestedOnly, set: usingMembers });
|
|
12185
|
+
}
|
|
12186
|
+
add({ kind: "symbol", symbolKey: ref });
|
|
12187
|
+
if (candidates2.length === 0) return;
|
|
12188
|
+
out.push({ candidates: candidates2, kind: "import", line });
|
|
12189
|
+
};
|
|
12190
|
+
const pushAttribute = (written, suffixed, enclosingNs, line) => {
|
|
12124
12191
|
const before = out.length;
|
|
12125
|
-
pushRef(written,
|
|
12126
|
-
if (suffixed !== void 0) pushRef(suffixed,
|
|
12192
|
+
pushRef(written, enclosingNs, line, false);
|
|
12193
|
+
if (suffixed !== void 0) pushRef(suffixed, enclosingNs, line, false);
|
|
12127
12194
|
if (out.length > before + 1) {
|
|
12128
12195
|
const merged = [];
|
|
12129
12196
|
const seenK = /* @__PURE__ */ new Set();
|
|
@@ -12138,18 +12205,129 @@ function uses(file, options = {}) {
|
|
|
12138
12205
|
}
|
|
12139
12206
|
out.push({ candidates: merged, kind: "import", line });
|
|
12140
12207
|
}
|
|
12208
|
+
};
|
|
12209
|
+
for (const r of refs) {
|
|
12210
|
+
switch (r.kind) {
|
|
12211
|
+
case "static":
|
|
12212
|
+
out.push({ candidates: [{ kind: "symbol", symbolKey: r.fqn }], kind: "import", line: r.line });
|
|
12213
|
+
break;
|
|
12214
|
+
case "plain":
|
|
12215
|
+
pushRef(r.ref, r.enclosingNs, r.line, r.rooted);
|
|
12216
|
+
break;
|
|
12217
|
+
case "alias": {
|
|
12218
|
+
const aliasFqn = aliases.get(r.aliasName);
|
|
12219
|
+
if (aliasFqn !== void 0) {
|
|
12220
|
+
pushRef(`${aliasFqn}.${r.tail}`, r.enclosingNs, r.line, true);
|
|
12221
|
+
}
|
|
12222
|
+
break;
|
|
12223
|
+
}
|
|
12224
|
+
case "attr":
|
|
12225
|
+
pushAttribute(r.written, r.suffixed, r.enclosingNs, r.line);
|
|
12226
|
+
break;
|
|
12227
|
+
}
|
|
12141
12228
|
}
|
|
12142
12229
|
return out;
|
|
12143
12230
|
}
|
|
12144
|
-
function
|
|
12145
|
-
return
|
|
12231
|
+
function uses(file, options = {}) {
|
|
12232
|
+
return assembleCsharpCandidates(extractCsharpRefs(file), options);
|
|
12146
12233
|
}
|
|
12147
12234
|
var csharpExtractor = {
|
|
12148
12235
|
languages: /* @__PURE__ */ new Set(["csharp"]),
|
|
12236
|
+
rev: 2,
|
|
12149
12237
|
declarations,
|
|
12150
12238
|
uses
|
|
12151
12239
|
};
|
|
12152
12240
|
|
|
12241
|
+
// src/relations/facts-cache.ts
|
|
12242
|
+
import path32 from "path";
|
|
12243
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
12244
|
+
import { createHash as createHash6 } from "crypto";
|
|
12245
|
+
var CACHE_SCHEMA_VERSION = 1;
|
|
12246
|
+
function serializeCsharp(c) {
|
|
12247
|
+
return {
|
|
12248
|
+
fileNs: c.fileNs,
|
|
12249
|
+
scope: {
|
|
12250
|
+
prefixes: c.scope.prefixes,
|
|
12251
|
+
globalPrefixes: c.scope.globalPrefixes,
|
|
12252
|
+
aliases: [...c.scope.aliases],
|
|
12253
|
+
globalAliases: [...c.scope.globalAliases],
|
|
12254
|
+
staticTargets: c.scope.staticTargets
|
|
12255
|
+
},
|
|
12256
|
+
refs: c.refs
|
|
12257
|
+
};
|
|
12258
|
+
}
|
|
12259
|
+
function deserializeCsharp(raw) {
|
|
12260
|
+
if (!raw || typeof raw !== "object") return null;
|
|
12261
|
+
const s = raw;
|
|
12262
|
+
if (typeof s.fileNs !== "string") return null;
|
|
12263
|
+
if (!s.scope || typeof s.scope !== "object") return null;
|
|
12264
|
+
const sc = s.scope;
|
|
12265
|
+
if (!Array.isArray(sc.prefixes) || !Array.isArray(sc.globalPrefixes) || !Array.isArray(sc.aliases) || !Array.isArray(sc.globalAliases) || !Array.isArray(sc.staticTargets)) {
|
|
12266
|
+
return null;
|
|
12267
|
+
}
|
|
12268
|
+
if (!Array.isArray(s.refs)) return null;
|
|
12269
|
+
return {
|
|
12270
|
+
fileNs: s.fileNs,
|
|
12271
|
+
scope: {
|
|
12272
|
+
prefixes: sc.prefixes,
|
|
12273
|
+
globalPrefixes: sc.globalPrefixes,
|
|
12274
|
+
aliases: new Map(sc.aliases),
|
|
12275
|
+
globalAliases: new Map(sc.globalAliases),
|
|
12276
|
+
staticTargets: sc.staticTargets
|
|
12277
|
+
},
|
|
12278
|
+
refs: s.refs
|
|
12279
|
+
};
|
|
12280
|
+
}
|
|
12281
|
+
function astCacheDir(graphRoot) {
|
|
12282
|
+
return path32.join(graphRoot, ".ast-cache");
|
|
12283
|
+
}
|
|
12284
|
+
function factsKey(args) {
|
|
12285
|
+
const payload = `${args.contentHash}\0${args.language}\0${args.grammarHash}\0${args.rev}`;
|
|
12286
|
+
return createHash6("sha256").update(payload).digest("hex").slice(0, 32);
|
|
12287
|
+
}
|
|
12288
|
+
function shardPath(dir, language, key) {
|
|
12289
|
+
return path32.join(dir, `v${CACHE_SCHEMA_VERSION}`, language, `${key}.json`);
|
|
12290
|
+
}
|
|
12291
|
+
async function loadFacts(dir, language, key) {
|
|
12292
|
+
const p2 = shardPath(dir, language, key);
|
|
12293
|
+
if (!existsSync7(p2)) return null;
|
|
12294
|
+
let parsed;
|
|
12295
|
+
try {
|
|
12296
|
+
parsed = JSON.parse(readFileSync7(p2, "utf-8"));
|
|
12297
|
+
} catch {
|
|
12298
|
+
return null;
|
|
12299
|
+
}
|
|
12300
|
+
if (!parsed || typeof parsed !== "object" || parsed.v !== CACHE_SCHEMA_VERSION) return null;
|
|
12301
|
+
if (typeof parsed.key !== "string") return null;
|
|
12302
|
+
if (!Array.isArray(parsed.declarations)) return null;
|
|
12303
|
+
if (!Array.isArray(parsed.uses)) return null;
|
|
12304
|
+
if (parsed.key !== key) return null;
|
|
12305
|
+
const facts = {
|
|
12306
|
+
declarations: parsed.declarations,
|
|
12307
|
+
uses: parsed.uses
|
|
12308
|
+
};
|
|
12309
|
+
if (parsed.csharp !== void 0) {
|
|
12310
|
+
const csharp = deserializeCsharp(parsed.csharp);
|
|
12311
|
+
if (csharp === null) return null;
|
|
12312
|
+
facts.csharp = csharp;
|
|
12313
|
+
}
|
|
12314
|
+
return facts;
|
|
12315
|
+
}
|
|
12316
|
+
async function writeFacts(dir, language, key, facts) {
|
|
12317
|
+
const p2 = shardPath(dir, language, key);
|
|
12318
|
+
if (existsSync7(p2)) return;
|
|
12319
|
+
const body = {
|
|
12320
|
+
v: CACHE_SCHEMA_VERSION,
|
|
12321
|
+
key,
|
|
12322
|
+
declarations: facts.declarations,
|
|
12323
|
+
uses: facts.uses,
|
|
12324
|
+
// Flatten the C# extract's `Map`s to entry arrays BEFORE JSON.stringify — a bare `Map`
|
|
12325
|
+
// stringifies to `"{}"` and would silently lose every alias entry (the false-green vector).
|
|
12326
|
+
...facts.csharp !== void 0 ? { csharp: serializeCsharp(facts.csharp) } : {}
|
|
12327
|
+
};
|
|
12328
|
+
await atomicWriteFile(p2, JSON.stringify(body));
|
|
12329
|
+
}
|
|
12330
|
+
|
|
12153
12331
|
// src/relations/verifier.ts
|
|
12154
12332
|
function verifyNodeDeps(nodeId, deps, g) {
|
|
12155
12333
|
const out = [];
|
|
@@ -12204,6 +12382,63 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12204
12382
|
return null;
|
|
12205
12383
|
}
|
|
12206
12384
|
}
|
|
12385
|
+
async function extractFileFacts(record, extractor) {
|
|
12386
|
+
const parsed = await parseSingle(record);
|
|
12387
|
+
if (!parsed) return null;
|
|
12388
|
+
try {
|
|
12389
|
+
const declarations12 = extractor.declarations(parsed);
|
|
12390
|
+
const isCsharp = record.language === "csharp";
|
|
12391
|
+
const uses12 = isCsharp ? null : extractor.uses(parsed);
|
|
12392
|
+
const csharp = isCsharp ? extractCsharpRefs(parsed) : null;
|
|
12393
|
+
return { declarations: declarations12, uses: uses12, csharp };
|
|
12394
|
+
} finally {
|
|
12395
|
+
parsed.tree.delete();
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12398
|
+
const grammarHashByExt = /* @__PURE__ */ new Map();
|
|
12399
|
+
const grammarHashForExt = (ext) => {
|
|
12400
|
+
const hit = grammarHashByExt.get(ext);
|
|
12401
|
+
if (hit !== void 0) return hit;
|
|
12402
|
+
let h;
|
|
12403
|
+
try {
|
|
12404
|
+
h = grammarWasmHash(ext);
|
|
12405
|
+
} catch {
|
|
12406
|
+
h = null;
|
|
12407
|
+
}
|
|
12408
|
+
grammarHashByExt.set(ext, h);
|
|
12409
|
+
return h;
|
|
12410
|
+
};
|
|
12411
|
+
async function loadOrExtractFacts(record, extractor) {
|
|
12412
|
+
const language = record.language;
|
|
12413
|
+
const isCsharp = language === "csharp";
|
|
12414
|
+
const grammarHash = grammarHashForExt(path33.extname(record.path));
|
|
12415
|
+
if (grammarHash === null) return extractFileFacts(record, extractor);
|
|
12416
|
+
if (!deps.disableCache) {
|
|
12417
|
+
const key = factsKey({
|
|
12418
|
+
contentHash: record.hash,
|
|
12419
|
+
language,
|
|
12420
|
+
grammarHash,
|
|
12421
|
+
rev: extractor.rev
|
|
12422
|
+
});
|
|
12423
|
+
const cached = await loadFacts(deps.symbolIndexDir, language, key);
|
|
12424
|
+
if (cached && (!isCsharp || cached.csharp !== void 0)) {
|
|
12425
|
+
return {
|
|
12426
|
+
declarations: cached.declarations,
|
|
12427
|
+
uses: isCsharp ? null : cached.uses,
|
|
12428
|
+
csharp: isCsharp ? cached.csharp : null
|
|
12429
|
+
};
|
|
12430
|
+
}
|
|
12431
|
+
const facts = await extractFileFacts(record, extractor);
|
|
12432
|
+
if (!facts) return null;
|
|
12433
|
+
await writeFacts(deps.symbolIndexDir, language, key, {
|
|
12434
|
+
declarations: facts.declarations,
|
|
12435
|
+
uses: facts.uses ?? [],
|
|
12436
|
+
...facts.csharp !== null ? { csharp: facts.csharp } : {}
|
|
12437
|
+
});
|
|
12438
|
+
return facts;
|
|
12439
|
+
}
|
|
12440
|
+
return extractFileFacts(record, extractor);
|
|
12441
|
+
}
|
|
12207
12442
|
const symbolTable = new SymbolTable();
|
|
12208
12443
|
const recordsByLanguage = /* @__PURE__ */ new Map();
|
|
12209
12444
|
for (const record of fileRecords) {
|
|
@@ -12216,42 +12451,31 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12216
12451
|
}
|
|
12217
12452
|
list.push(record);
|
|
12218
12453
|
}
|
|
12454
|
+
const factsByPath = /* @__PURE__ */ new Map();
|
|
12455
|
+
for (const record of fileRecords) {
|
|
12456
|
+
if (!record.language) continue;
|
|
12457
|
+
const extractor = deps.extractorFor(record.language);
|
|
12458
|
+
if (!extractor) continue;
|
|
12459
|
+
const facts = await loadOrExtractFacts(record, extractor);
|
|
12460
|
+
if (facts) factsByPath.set(record.path, facts);
|
|
12461
|
+
}
|
|
12219
12462
|
for (const [language, records] of recordsByLanguage) {
|
|
12220
|
-
const extractor = deps.extractorFor(language);
|
|
12221
|
-
const builtFrom = records.map((r) => [r.path, r.hash]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
12222
|
-
const cached = loadSymbolIndex(deps.symbolIndexDir, language, builtFrom);
|
|
12223
|
-
if (cached) {
|
|
12224
|
-
for (const [symbolKey, file] of cached.symbols) symbolTable.declare(language, symbolKey, file);
|
|
12225
|
-
continue;
|
|
12226
|
-
}
|
|
12227
|
-
const symbols = [];
|
|
12228
12463
|
for (const record of records) {
|
|
12229
|
-
const
|
|
12230
|
-
if (!
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
symbols.push([decl.symbolKey, record.path]);
|
|
12234
|
-
symbolTable.declare(language, decl.symbolKey, record.path);
|
|
12235
|
-
}
|
|
12236
|
-
} finally {
|
|
12237
|
-
parsed.tree.delete();
|
|
12464
|
+
const facts = factsByPath.get(record.path);
|
|
12465
|
+
if (!facts) continue;
|
|
12466
|
+
for (const decl of facts.declarations) {
|
|
12467
|
+
symbolTable.declare(language, decl.symbolKey, record.path);
|
|
12238
12468
|
}
|
|
12239
12469
|
}
|
|
12240
|
-
const toPersist = { builtFrom, symbols };
|
|
12241
|
-
await writeSymbolIndex(deps.symbolIndexDir, language, toPersist);
|
|
12242
12470
|
}
|
|
12243
12471
|
const csharpRecords = recordsByLanguage.get("csharp") ?? [];
|
|
12244
12472
|
const projectGlobalUsings = /* @__PURE__ */ new Set();
|
|
12245
12473
|
const projectGlobalUsingAliases = /* @__PURE__ */ new Map();
|
|
12246
12474
|
for (const record of csharpRecords) {
|
|
12247
|
-
const
|
|
12248
|
-
if (!
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
for (const [name, fqn] of collectGlobalUsingAliases(parsed)) projectGlobalUsingAliases.set(name, fqn);
|
|
12252
|
-
} finally {
|
|
12253
|
-
parsed.tree.delete();
|
|
12254
|
-
}
|
|
12475
|
+
const facts = factsByPath.get(record.path);
|
|
12476
|
+
if (!facts || facts.csharp === null) continue;
|
|
12477
|
+
for (const prefix of facts.csharp.scope.globalPrefixes) projectGlobalUsings.add(prefix);
|
|
12478
|
+
for (const [name, fqn] of facts.csharp.scope.globalAliases) projectGlobalUsingAliases.set(name, fqn);
|
|
12255
12479
|
}
|
|
12256
12480
|
const csharpGlobalUsings = [...projectGlobalUsings];
|
|
12257
12481
|
const csharpGlobalUsingAliases = [...projectGlobalUsingAliases.entries()];
|
|
@@ -12278,6 +12502,15 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12278
12502
|
}
|
|
12279
12503
|
};
|
|
12280
12504
|
const violationsByNode = /* @__PURE__ */ new Map();
|
|
12505
|
+
const detectedEdgesByNode = /* @__PURE__ */ new Map();
|
|
12506
|
+
const resolveDetected = (record, detected, resolvedDeps) => {
|
|
12507
|
+
for (const dep of detected) {
|
|
12508
|
+
const ownerNode = resolveCandidateGroup(dep.candidates, resolver, record.path, record.language);
|
|
12509
|
+
if (ownerNode !== void 0) {
|
|
12510
|
+
resolvedDeps.push({ fromFile: record.path, line: dep.line, ownerNode });
|
|
12511
|
+
}
|
|
12512
|
+
}
|
|
12513
|
+
};
|
|
12281
12514
|
for (const [nodeId] of graph.nodes) {
|
|
12282
12515
|
const records = fileRecords.filter((r) => r.nodeId === nodeId);
|
|
12283
12516
|
if (records.length === 0) continue;
|
|
@@ -12286,23 +12519,28 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12286
12519
|
if (!record.language) continue;
|
|
12287
12520
|
const extractor = deps.extractorFor(record.language);
|
|
12288
12521
|
if (!extractor) continue;
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
const detected =
|
|
12522
|
+
if (record.language === "csharp") {
|
|
12523
|
+
const facts2 = factsByPath.get(record.path);
|
|
12524
|
+
if (!facts2 || facts2.csharp === null) continue;
|
|
12525
|
+
const detected = assembleCsharpCandidates(facts2.csharp, {
|
|
12293
12526
|
projectGlobalUsings: csharpGlobalUsings,
|
|
12294
12527
|
projectGlobalUsingAliases: csharpGlobalUsingAliases
|
|
12295
|
-
})
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
if (ownerNode !== void 0) {
|
|
12299
|
-
resolvedDeps.push({ fromFile: record.path, line: dep.line, ownerNode });
|
|
12300
|
-
}
|
|
12301
|
-
}
|
|
12302
|
-
} finally {
|
|
12303
|
-
parsed.tree.delete();
|
|
12528
|
+
});
|
|
12529
|
+
resolveDetected(record, detected, resolvedDeps);
|
|
12530
|
+
continue;
|
|
12304
12531
|
}
|
|
12532
|
+
const facts = factsByPath.get(record.path);
|
|
12533
|
+
if (!facts || facts.uses === null) continue;
|
|
12534
|
+
resolveDetected(record, facts.uses, resolvedDeps);
|
|
12305
12535
|
}
|
|
12536
|
+
const edges = /* @__PURE__ */ new Set();
|
|
12537
|
+
for (const d of resolvedDeps) {
|
|
12538
|
+
const m = d.ownerNode;
|
|
12539
|
+
if (m === nodeId) continue;
|
|
12540
|
+
if (graphView.isAncestorOf(m, nodeId) || graphView.isAncestorOf(nodeId, m)) continue;
|
|
12541
|
+
edges.add(m);
|
|
12542
|
+
}
|
|
12543
|
+
if (edges.size > 0) detectedEdgesByNode.set(nodeId, edges);
|
|
12306
12544
|
const violations = verifyNodeDeps(nodeId, resolvedDeps, graphView);
|
|
12307
12545
|
if (violations.length) {
|
|
12308
12546
|
const reason = violations.map((v) => `${v.fromFile}:${v.line} \u2192 undeclared dependency on ${v.ownerNode}`).join("\n");
|
|
@@ -12311,7 +12549,7 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12311
12549
|
violationsByNode.set(nodeId, { verdict: "approved", violations: [] });
|
|
12312
12550
|
}
|
|
12313
12551
|
}
|
|
12314
|
-
return { violationsByNode };
|
|
12552
|
+
return { violationsByNode, factsByPath, detectedEdgesByNode };
|
|
12315
12553
|
}
|
|
12316
12554
|
|
|
12317
12555
|
// src/relations/extractors/types.ts
|
|
@@ -12331,7 +12569,19 @@ function specifierFromStringNode(stringNode) {
|
|
|
12331
12569
|
function specifierFromSource(node) {
|
|
12332
12570
|
return specifierFromStringNode(node.childForFieldName("source"));
|
|
12333
12571
|
}
|
|
12334
|
-
function
|
|
12572
|
+
function specifierFromCallArg(arg) {
|
|
12573
|
+
if (arg.type === "string") return specifierFromStringNode(arg);
|
|
12574
|
+
if (arg.type !== "template_string") return void 0;
|
|
12575
|
+
let fragment;
|
|
12576
|
+
for (let i = 0; i < arg.namedChildCount; i++) {
|
|
12577
|
+
const child = arg.namedChild(i);
|
|
12578
|
+
if (child === null) continue;
|
|
12579
|
+
if (child.type === "template_substitution") return void 0;
|
|
12580
|
+
if (child.type === "string_fragment") fragment = child.text;
|
|
12581
|
+
}
|
|
12582
|
+
return fragment ?? "";
|
|
12583
|
+
}
|
|
12584
|
+
function isRelative(specifier) {
|
|
12335
12585
|
return specifier.startsWith(".") || specifier.startsWith("/");
|
|
12336
12586
|
}
|
|
12337
12587
|
function isWholeStatementTypeToken(child) {
|
|
@@ -12381,7 +12631,8 @@ function uses2(file) {
|
|
|
12381
12631
|
const emit = (specifier, node) => {
|
|
12382
12632
|
if (specifier === void 0 || specifier === "" || !isRelative(specifier)) return;
|
|
12383
12633
|
const line = node.startPosition.row + 1;
|
|
12384
|
-
const dedupKey = `${specifier}
|
|
12634
|
+
const dedupKey = `${specifier}
|
|
12635
|
+
${line}`;
|
|
12385
12636
|
if (seen.has(dedupKey)) return;
|
|
12386
12637
|
seen.add(dedupKey);
|
|
12387
12638
|
out.push(single({ kind: "path", specifier }, "import", line));
|
|
@@ -12428,8 +12679,8 @@ function uses2(file) {
|
|
|
12428
12679
|
const isRequire = fn.type === "identifier" && fn.text === "require";
|
|
12429
12680
|
if (!isDynamicImport && !isRequire) break;
|
|
12430
12681
|
const arg = firstArgument(node);
|
|
12431
|
-
if (arg === null
|
|
12432
|
-
emit(
|
|
12682
|
+
if (arg === null) break;
|
|
12683
|
+
emit(specifierFromCallArg(arg), node);
|
|
12433
12684
|
break;
|
|
12434
12685
|
}
|
|
12435
12686
|
default:
|
|
@@ -12470,6 +12721,7 @@ function declarations2(file) {
|
|
|
12470
12721
|
}
|
|
12471
12722
|
var typescriptExtractor = {
|
|
12472
12723
|
languages: /* @__PURE__ */ new Set(["typescript", "tsx", "javascript"]),
|
|
12724
|
+
rev: 1,
|
|
12473
12725
|
declarations: declarations2,
|
|
12474
12726
|
uses: uses2
|
|
12475
12727
|
};
|
|
@@ -12575,6 +12827,7 @@ function declarations3(file) {
|
|
|
12575
12827
|
}
|
|
12576
12828
|
var pythonExtractor = {
|
|
12577
12829
|
languages: /* @__PURE__ */ new Set(["python"]),
|
|
12830
|
+
rev: 1,
|
|
12578
12831
|
declarations: declarations3,
|
|
12579
12832
|
uses: uses3
|
|
12580
12833
|
};
|
|
@@ -12624,6 +12877,7 @@ function declarations4(file) {
|
|
|
12624
12877
|
}
|
|
12625
12878
|
var goExtractor = {
|
|
12626
12879
|
languages: /* @__PURE__ */ new Set(["go"]),
|
|
12880
|
+
rev: 1,
|
|
12627
12881
|
declarations: declarations4,
|
|
12628
12882
|
uses: uses4
|
|
12629
12883
|
};
|
|
@@ -12795,6 +13049,7 @@ function declarations5(file) {
|
|
|
12795
13049
|
}
|
|
12796
13050
|
var javaExtractor = {
|
|
12797
13051
|
languages: /* @__PURE__ */ new Set(["java"]),
|
|
13052
|
+
rev: 3,
|
|
12798
13053
|
declarations: declarations5,
|
|
12799
13054
|
uses: uses5
|
|
12800
13055
|
};
|
|
@@ -12923,6 +13178,7 @@ function declarations6(file) {
|
|
|
12923
13178
|
}
|
|
12924
13179
|
var phpExtractor = {
|
|
12925
13180
|
languages: /* @__PURE__ */ new Set(["php"]),
|
|
13181
|
+
rev: 1,
|
|
12926
13182
|
declarations: declarations6,
|
|
12927
13183
|
uses: uses6
|
|
12928
13184
|
};
|
|
@@ -13047,6 +13303,7 @@ function declarations7(file) {
|
|
|
13047
13303
|
}
|
|
13048
13304
|
var kotlinExtractor = {
|
|
13049
13305
|
languages: /* @__PURE__ */ new Set(["kotlin"]),
|
|
13306
|
+
rev: 2,
|
|
13050
13307
|
declarations: declarations7,
|
|
13051
13308
|
uses: uses7
|
|
13052
13309
|
};
|
|
@@ -13219,6 +13476,7 @@ function declarations8(file) {
|
|
|
13219
13476
|
}
|
|
13220
13477
|
var rustExtractor = {
|
|
13221
13478
|
languages: /* @__PURE__ */ new Set(["rust"]),
|
|
13479
|
+
rev: 1,
|
|
13222
13480
|
declarations: declarations8,
|
|
13223
13481
|
uses: uses8
|
|
13224
13482
|
};
|
|
@@ -13312,6 +13570,7 @@ function functionName(def) {
|
|
|
13312
13570
|
}
|
|
13313
13571
|
var cExtractor = {
|
|
13314
13572
|
languages: /* @__PURE__ */ new Set(["c"]),
|
|
13573
|
+
rev: 1,
|
|
13315
13574
|
declarations: declarations9,
|
|
13316
13575
|
uses: uses9
|
|
13317
13576
|
};
|
|
@@ -13356,6 +13615,7 @@ function functionName2(def) {
|
|
|
13356
13615
|
}
|
|
13357
13616
|
var cppExtractor = {
|
|
13358
13617
|
languages: /* @__PURE__ */ new Set(["cpp"]),
|
|
13618
|
+
rev: 1,
|
|
13359
13619
|
declarations: declarations10,
|
|
13360
13620
|
uses: uses10
|
|
13361
13621
|
};
|
|
@@ -13526,6 +13786,7 @@ function declarations11(file) {
|
|
|
13526
13786
|
}
|
|
13527
13787
|
var rubyExtractor = {
|
|
13528
13788
|
languages: /* @__PURE__ */ new Set(["ruby"]),
|
|
13789
|
+
rev: 1,
|
|
13529
13790
|
declarations: declarations11,
|
|
13530
13791
|
uses: uses11
|
|
13531
13792
|
};
|
|
@@ -13561,12 +13822,6 @@ function extractorForLanguage(language) {
|
|
|
13561
13822
|
return byLanguage.get(language);
|
|
13562
13823
|
}
|
|
13563
13824
|
|
|
13564
|
-
// src/relations/index-dir.ts
|
|
13565
|
-
import path34 from "path";
|
|
13566
|
-
function relationIndexDir(graphRootPath) {
|
|
13567
|
-
return path34.join(graphRootPath, ".symbols-cache");
|
|
13568
|
-
}
|
|
13569
|
-
|
|
13570
13825
|
// src/relations/allowed-types.ts
|
|
13571
13826
|
var RELATION_TYPES3 = [
|
|
13572
13827
|
"uses",
|
|
@@ -13579,15 +13834,16 @@ var RELATION_TYPES3 = [
|
|
|
13579
13834
|
function allowedRelationTypes(architecture, fromType, toType) {
|
|
13580
13835
|
const fromConfig = architecture.node_types[fromType];
|
|
13581
13836
|
if (!fromConfig) return [];
|
|
13582
|
-
const
|
|
13837
|
+
const lists = fromConfig.relations;
|
|
13838
|
+
const def = fromConfig.relationDefault ?? "allow";
|
|
13583
13839
|
const out = [];
|
|
13584
13840
|
for (const rt of RELATION_TYPES3) {
|
|
13585
|
-
const allowedTargets =
|
|
13586
|
-
if (
|
|
13587
|
-
out.push(rt);
|
|
13841
|
+
const allowedTargets = lists?.[rt];
|
|
13842
|
+
if (allowedTargets === void 0) {
|
|
13843
|
+
if (def === "allow") out.push(rt);
|
|
13588
13844
|
continue;
|
|
13589
13845
|
}
|
|
13590
|
-
if (allowedTargets.includes(toType)) out.push(rt);
|
|
13846
|
+
if (allowedTargets.includes("*") || allowedTargets.includes(toType)) out.push(rt);
|
|
13591
13847
|
}
|
|
13592
13848
|
return out;
|
|
13593
13849
|
}
|
|
@@ -13631,15 +13887,15 @@ ${blocks.join("\n")}`
|
|
|
13631
13887
|
}
|
|
13632
13888
|
|
|
13633
13889
|
// src/relations/resolve-path.ts
|
|
13634
|
-
import
|
|
13635
|
-
import { existsSync as
|
|
13890
|
+
import path42 from "path";
|
|
13891
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
13636
13892
|
|
|
13637
13893
|
// src/relations/extractors/typescript-resolve.ts
|
|
13638
|
-
import
|
|
13894
|
+
import path34 from "path";
|
|
13639
13895
|
function resolveTsPath(specifier, fromFile, exists) {
|
|
13640
13896
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) return void 0;
|
|
13641
|
-
const baseDir =
|
|
13642
|
-
const joined =
|
|
13897
|
+
const baseDir = path34.posix.dirname(toPosix3(fromFile));
|
|
13898
|
+
const joined = path34.posix.normalize(path34.posix.join(baseDir, specifier));
|
|
13643
13899
|
for (const cand of candidates(joined)) {
|
|
13644
13900
|
if (exists(cand)) return cand;
|
|
13645
13901
|
}
|
|
@@ -13647,7 +13903,7 @@ function resolveTsPath(specifier, fromFile, exists) {
|
|
|
13647
13903
|
}
|
|
13648
13904
|
function candidates(joined) {
|
|
13649
13905
|
const out = [];
|
|
13650
|
-
const ext =
|
|
13906
|
+
const ext = path34.posix.extname(joined);
|
|
13651
13907
|
const rewrites = {
|
|
13652
13908
|
".js": [".ts", ".tsx", ".js", ".jsx"],
|
|
13653
13909
|
".jsx": [".tsx", ".jsx"],
|
|
@@ -13662,7 +13918,7 @@ function candidates(joined) {
|
|
|
13662
13918
|
} else {
|
|
13663
13919
|
for (const e of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) out.push(joined + e);
|
|
13664
13920
|
}
|
|
13665
|
-
for (const e of [".ts", ".tsx", ".js", ".jsx"]) out.push(
|
|
13921
|
+
for (const e of [".ts", ".tsx", ".js", ".jsx"]) out.push(path34.posix.join(joined, "index" + e));
|
|
13666
13922
|
return out;
|
|
13667
13923
|
}
|
|
13668
13924
|
function toPosix3(p2) {
|
|
@@ -13670,7 +13926,7 @@ function toPosix3(p2) {
|
|
|
13670
13926
|
}
|
|
13671
13927
|
|
|
13672
13928
|
// src/relations/extractors/python-resolve.ts
|
|
13673
|
-
import
|
|
13929
|
+
import path35 from "path";
|
|
13674
13930
|
function resolvePythonModule(specifier, fromFile, exists) {
|
|
13675
13931
|
if (specifier.startsWith(".")) {
|
|
13676
13932
|
return resolveRelative(specifier, fromFile, exists);
|
|
@@ -13683,7 +13939,7 @@ function resolveAbsolute(specifier, fromFile, exists) {
|
|
|
13683
13939
|
const modulePath = segments.join("/");
|
|
13684
13940
|
const parentPath = segments.slice(0, -1).join("/");
|
|
13685
13941
|
const matches = /* @__PURE__ */ new Set();
|
|
13686
|
-
for (const dir of ancestorDirs(
|
|
13942
|
+
for (const dir of ancestorDirs(path35.posix.dirname(toPosix4(fromFile)))) {
|
|
13687
13943
|
const candidates2 = [
|
|
13688
13944
|
// module-as-file / package at this root
|
|
13689
13945
|
joinUnder(dir, modulePath + ".py"),
|
|
@@ -13707,17 +13963,17 @@ function resolveRelative(specifier, fromFile, exists) {
|
|
|
13707
13963
|
if (dotMatch === null) return void 0;
|
|
13708
13964
|
const dots = dotMatch[0].length;
|
|
13709
13965
|
const tail = specifier.slice(dots);
|
|
13710
|
-
let base =
|
|
13966
|
+
let base = path35.posix.dirname(toPosix4(fromFile));
|
|
13711
13967
|
for (let i = 0; i < dots - 1; i++) {
|
|
13712
|
-
const parent =
|
|
13968
|
+
const parent = path35.posix.dirname(base);
|
|
13713
13969
|
if (parent === base) return void 0;
|
|
13714
13970
|
base = parent;
|
|
13715
13971
|
}
|
|
13716
13972
|
const tailPath = tail.length > 0 ? tail.split(".").filter((s) => s.length > 0).join("/") : "";
|
|
13717
|
-
const target = tailPath.length > 0 ?
|
|
13718
|
-
const normalized =
|
|
13973
|
+
const target = tailPath.length > 0 ? path35.posix.join(base, tailPath) : base;
|
|
13974
|
+
const normalized = path35.posix.normalize(target);
|
|
13719
13975
|
if (normalized.startsWith("..")) return void 0;
|
|
13720
|
-
const candidates2 = tailPath.length > 0 ? [normalized + ".py",
|
|
13976
|
+
const candidates2 = tailPath.length > 0 ? [normalized + ".py", path35.posix.join(normalized, "__init__.py")] : [path35.posix.join(normalized, "__init__.py")];
|
|
13721
13977
|
for (const cand of candidates2) {
|
|
13722
13978
|
if (exists(cand)) return cand;
|
|
13723
13979
|
}
|
|
@@ -13729,20 +13985,20 @@ function ancestorDirs(dir) {
|
|
|
13729
13985
|
for (; ; ) {
|
|
13730
13986
|
out.push(cur);
|
|
13731
13987
|
if (cur === "") break;
|
|
13732
|
-
const parent =
|
|
13988
|
+
const parent = path35.posix.dirname(cur);
|
|
13733
13989
|
cur = parent === "." ? "" : parent;
|
|
13734
13990
|
}
|
|
13735
13991
|
return out;
|
|
13736
13992
|
}
|
|
13737
13993
|
function joinUnder(dir, sub) {
|
|
13738
|
-
return
|
|
13994
|
+
return path35.posix.normalize(dir === "" ? sub : path35.posix.join(dir, sub));
|
|
13739
13995
|
}
|
|
13740
13996
|
function toPosix4(p2) {
|
|
13741
13997
|
return p2.replace(/\\/g, "/");
|
|
13742
13998
|
}
|
|
13743
13999
|
|
|
13744
14000
|
// src/relations/extractors/go-resolve.ts
|
|
13745
|
-
import
|
|
14001
|
+
import path36 from "path";
|
|
13746
14002
|
function resolveGoImport(importPath, fromFile, deps) {
|
|
13747
14003
|
const resolved = deps.modulePathFor(fromFile);
|
|
13748
14004
|
if (resolved === void 0) return void 0;
|
|
@@ -13756,8 +14012,8 @@ function resolveGoImport(importPath, fromFile, deps) {
|
|
|
13756
14012
|
} else {
|
|
13757
14013
|
return void 0;
|
|
13758
14014
|
}
|
|
13759
|
-
const rel = remainder === "" ? moduleDir : moduleDir === "" ? remainder :
|
|
13760
|
-
const repoRelDir =
|
|
14015
|
+
const rel = remainder === "" ? moduleDir : moduleDir === "" ? remainder : path36.posix.join(moduleDir, remainder);
|
|
14016
|
+
const repoRelDir = path36.posix.normalize(rel === "" ? "." : rel);
|
|
13761
14017
|
const cleanDir = repoRelDir === "." ? "" : repoRelDir;
|
|
13762
14018
|
if (!deps.dirExists(cleanDir)) return void 0;
|
|
13763
14019
|
const goFiles = deps.goFilesIn(cleanDir);
|
|
@@ -13785,7 +14041,7 @@ function resolveGoImport(importPath, fromFile, deps) {
|
|
|
13785
14041
|
}
|
|
13786
14042
|
|
|
13787
14043
|
// src/relations/extractors/java-resolve.ts
|
|
13788
|
-
import
|
|
14044
|
+
import path37 from "path";
|
|
13789
14045
|
function resolveJavaFqn(specifier, fromFile, deps) {
|
|
13790
14046
|
const segments = specifier.split(".").filter((s) => s.length > 0);
|
|
13791
14047
|
if (segments.length === 0) return void 0;
|
|
@@ -13795,7 +14051,7 @@ function resolveJavaPackageFiles(packageFqn2, fromFile, deps) {
|
|
|
13795
14051
|
const segments = packageFqn2.split(".").filter((s) => s.length > 0);
|
|
13796
14052
|
if (segments.length === 0) return [];
|
|
13797
14053
|
const pkgDir = segments.join("/");
|
|
13798
|
-
for (const dir of ancestorDirs2(
|
|
14054
|
+
for (const dir of ancestorDirs2(path37.posix.dirname(toPosix5(fromFile)))) {
|
|
13799
14055
|
const repoRelDir = joinUnder2(dir, pkgDir);
|
|
13800
14056
|
const files = deps.javaFilesIn(repoRelDir);
|
|
13801
14057
|
if (files.length > 0) return [...files].sort();
|
|
@@ -13805,7 +14061,7 @@ function resolveJavaPackageFiles(packageFqn2, fromFile, deps) {
|
|
|
13805
14061
|
function resolveType(segments, fromFile, deps) {
|
|
13806
14062
|
const typePath = segments.join("/") + ".java";
|
|
13807
14063
|
const parentTypePath = segments.length >= 2 ? segments.slice(0, -1).join("/") + ".java" : void 0;
|
|
13808
|
-
for (const dir of ancestorDirs2(
|
|
14064
|
+
for (const dir of ancestorDirs2(path37.posix.dirname(toPosix5(fromFile)))) {
|
|
13809
14065
|
const candidates2 = [joinUnder2(dir, typePath)];
|
|
13810
14066
|
if (parentTypePath !== void 0) candidates2.push(joinUnder2(dir, parentTypePath));
|
|
13811
14067
|
for (const cand of candidates2) {
|
|
@@ -13820,20 +14076,20 @@ function ancestorDirs2(dir) {
|
|
|
13820
14076
|
for (; ; ) {
|
|
13821
14077
|
out.push(cur);
|
|
13822
14078
|
if (cur === "") break;
|
|
13823
|
-
const parent =
|
|
14079
|
+
const parent = path37.posix.dirname(cur);
|
|
13824
14080
|
cur = parent === "." ? "" : parent;
|
|
13825
14081
|
}
|
|
13826
14082
|
return out;
|
|
13827
14083
|
}
|
|
13828
14084
|
function joinUnder2(dir, sub) {
|
|
13829
|
-
return
|
|
14085
|
+
return path37.posix.normalize(dir === "" ? sub : path37.posix.join(dir, sub));
|
|
13830
14086
|
}
|
|
13831
14087
|
function toPosix5(p2) {
|
|
13832
14088
|
return p2.replace(/\\/g, "/");
|
|
13833
14089
|
}
|
|
13834
14090
|
|
|
13835
14091
|
// src/relations/extractors/php-resolve.ts
|
|
13836
|
-
import
|
|
14092
|
+
import path38 from "path";
|
|
13837
14093
|
function resolvePhpFqn(specifier, fromFile, deps) {
|
|
13838
14094
|
const fqn = specifier.startsWith("\\") ? specifier.slice(1) : specifier;
|
|
13839
14095
|
if (fqn === "") return void 0;
|
|
@@ -13859,7 +14115,7 @@ function resolvePhpFqn(specifier, fromFile, deps) {
|
|
|
13859
14115
|
return [...hits][0];
|
|
13860
14116
|
}
|
|
13861
14117
|
function joinUnder3(dir, sub) {
|
|
13862
|
-
return
|
|
14118
|
+
return path38.posix.normalize(dir === "" ? sub : path38.posix.join(dir, sub));
|
|
13863
14119
|
}
|
|
13864
14120
|
function parsePsr4(composerJsonText, composerDir) {
|
|
13865
14121
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13897,19 +14153,19 @@ function parsePsr4(composerJsonText, composerDir) {
|
|
|
13897
14153
|
function normalizeDir(value, composerDir) {
|
|
13898
14154
|
const trimmed = value.replace(/\/+$/, "");
|
|
13899
14155
|
if (trimmed === "" || trimmed === ".") return composerDir;
|
|
13900
|
-
const joined = composerDir === "" ? trimmed :
|
|
13901
|
-
const norm =
|
|
14156
|
+
const joined = composerDir === "" ? trimmed : path38.posix.join(composerDir, trimmed);
|
|
14157
|
+
const norm = path38.posix.normalize(joined);
|
|
13902
14158
|
return norm === "." ? "" : norm;
|
|
13903
14159
|
}
|
|
13904
14160
|
|
|
13905
14161
|
// src/relations/extractors/rust-resolve.ts
|
|
13906
|
-
import
|
|
14162
|
+
import path39 from "path";
|
|
13907
14163
|
function resolveRustPath(specifier, fromFile, exists, deps) {
|
|
13908
14164
|
const segments = specifier.split("::").filter((s) => s.length > 0);
|
|
13909
14165
|
if (segments.length === 0) return void 0;
|
|
13910
14166
|
const root = segments[0];
|
|
13911
14167
|
const rest = segments.slice(1);
|
|
13912
|
-
const fromDir =
|
|
14168
|
+
const fromDir = path39.posix.dirname(toPosix6(fromFile));
|
|
13913
14169
|
if (root === "super" || root === "self") {
|
|
13914
14170
|
return resolveRelative2(root, segments, fromFile, exists, deps);
|
|
13915
14171
|
}
|
|
@@ -13929,9 +14185,9 @@ function resolveRelative2(root, segments, fromFile, exists, deps) {
|
|
|
13929
14185
|
if (crate === void 0) return void 0;
|
|
13930
14186
|
const srcDir = crate.srcDir;
|
|
13931
14187
|
const fromPosix = toPosix6(fromFile);
|
|
13932
|
-
const fromDir =
|
|
13933
|
-
const baseName =
|
|
13934
|
-
let moduleDir = baseName === "mod" || baseName === "lib" || baseName === "main" ? fromDir :
|
|
14188
|
+
const fromDir = path39.posix.dirname(fromPosix);
|
|
14189
|
+
const baseName = path39.posix.basename(fromPosix, ".rs");
|
|
14190
|
+
let moduleDir = baseName === "mod" || baseName === "lib" || baseName === "main" ? fromDir : path39.posix.join(fromDir, baseName);
|
|
13935
14191
|
let i = 0;
|
|
13936
14192
|
for (; i < segments.length; i++) {
|
|
13937
14193
|
const seg = segments[i];
|
|
@@ -13939,7 +14195,7 @@ function resolveRelative2(root, segments, fromFile, exists, deps) {
|
|
|
13939
14195
|
continue;
|
|
13940
14196
|
}
|
|
13941
14197
|
if (seg === "super") {
|
|
13942
|
-
const parent =
|
|
14198
|
+
const parent = path39.posix.dirname(moduleDir);
|
|
13943
14199
|
if (!withinSrc(parent, srcDir)) return void 0;
|
|
13944
14200
|
moduleDir = parent;
|
|
13945
14201
|
continue;
|
|
@@ -13971,13 +14227,13 @@ function resolveFromModuleDir(moduleDir, tail, exists) {
|
|
|
13971
14227
|
return void 0;
|
|
13972
14228
|
}
|
|
13973
14229
|
function withinSrc(dir, srcDir) {
|
|
13974
|
-
const d =
|
|
13975
|
-
const s =
|
|
14230
|
+
const d = path39.posix.normalize(dir);
|
|
14231
|
+
const s = path39.posix.normalize(srcDir);
|
|
13976
14232
|
if (s === "" || s === ".") return !d.startsWith("..");
|
|
13977
14233
|
return d === s || d.startsWith(s + "/");
|
|
13978
14234
|
}
|
|
13979
14235
|
function joinUnder4(dir, sub) {
|
|
13980
|
-
const joined =
|
|
14236
|
+
const joined = path39.posix.normalize(dir === "" ? sub : path39.posix.join(dir, sub));
|
|
13981
14237
|
if (joined.startsWith("..")) return void 0;
|
|
13982
14238
|
return joined;
|
|
13983
14239
|
}
|
|
@@ -13986,16 +14242,16 @@ function toPosix6(p2) {
|
|
|
13986
14242
|
}
|
|
13987
14243
|
|
|
13988
14244
|
// src/relations/extractors/include-resolve.ts
|
|
13989
|
-
import
|
|
14245
|
+
import path40 from "path";
|
|
13990
14246
|
function resolveIncludePath(headerPath, fromFile, exists) {
|
|
13991
14247
|
if (headerPath === "") return void 0;
|
|
13992
|
-
const fromDir =
|
|
13993
|
-
const relative3 = normalizeRepoRel(
|
|
14248
|
+
const fromDir = path40.posix.dirname(toPosix7(fromFile));
|
|
14249
|
+
const relative3 = normalizeRepoRel(path40.posix.join(fromDir, headerPath));
|
|
13994
14250
|
if (relative3 !== void 0 && exists(relative3)) return relative3;
|
|
13995
14251
|
return void 0;
|
|
13996
14252
|
}
|
|
13997
14253
|
function normalizeRepoRel(p2) {
|
|
13998
|
-
const norm =
|
|
14254
|
+
const norm = path40.posix.normalize(p2);
|
|
13999
14255
|
if (norm.startsWith("..")) return void 0;
|
|
14000
14256
|
return norm;
|
|
14001
14257
|
}
|
|
@@ -14004,13 +14260,13 @@ function toPosix7(p2) {
|
|
|
14004
14260
|
}
|
|
14005
14261
|
|
|
14006
14262
|
// src/relations/extractors/ruby-resolve.ts
|
|
14007
|
-
import
|
|
14263
|
+
import path41 from "path";
|
|
14008
14264
|
function resolveRubyRequireRelative(specifier, fromFile, exists) {
|
|
14009
14265
|
if (specifier === "") return void 0;
|
|
14010
|
-
const fromDir =
|
|
14266
|
+
const fromDir = path41.posix.dirname(toPosix8(fromFile));
|
|
14011
14267
|
const withExt = /\.rb$/.test(specifier) ? specifier : `${specifier}.rb`;
|
|
14012
|
-
const joined =
|
|
14013
|
-
const normalized =
|
|
14268
|
+
const joined = path41.posix.join(fromDir, withExt);
|
|
14269
|
+
const normalized = path41.posix.normalize(joined);
|
|
14014
14270
|
if (normalized.startsWith("..")) return void 0;
|
|
14015
14271
|
return exists(normalized) ? normalized : void 0;
|
|
14016
14272
|
}
|
|
@@ -14020,7 +14276,7 @@ function toPosix8(p2) {
|
|
|
14020
14276
|
|
|
14021
14277
|
// src/relations/resolve-path.ts
|
|
14022
14278
|
function makeResolvePathToFile(projectRoot, ownerOf) {
|
|
14023
|
-
const exists = (repoRelPosix) =>
|
|
14279
|
+
const exists = (repoRelPosix) => existsSync8(path42.resolve(projectRoot, repoRelPosix));
|
|
14024
14280
|
const goDeps = makeGoResolveDeps(projectRoot, ownerOf);
|
|
14025
14281
|
const javaDeps = makeJavaResolveDeps(projectRoot, exists);
|
|
14026
14282
|
const phpDeps = makePhpResolveDeps(projectRoot, exists);
|
|
@@ -14068,10 +14324,10 @@ function makeResolvePathToFile(projectRoot, ownerOf) {
|
|
|
14068
14324
|
function makeRustResolveDeps(projectRoot) {
|
|
14069
14325
|
const byDir = /* @__PURE__ */ new Map();
|
|
14070
14326
|
function readCrateName(repoRelDir) {
|
|
14071
|
-
const abs =
|
|
14327
|
+
const abs = path42.join(projectRoot, repoRelDir, "Cargo.toml");
|
|
14072
14328
|
let text2;
|
|
14073
14329
|
try {
|
|
14074
|
-
text2 =
|
|
14330
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14075
14331
|
} catch {
|
|
14076
14332
|
return void 0;
|
|
14077
14333
|
}
|
|
@@ -14089,14 +14345,14 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14089
14345
|
return void 0;
|
|
14090
14346
|
}
|
|
14091
14347
|
function crateRootFor(fromFile) {
|
|
14092
|
-
let dir =
|
|
14348
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14093
14349
|
if (dir === ".") dir = "";
|
|
14094
14350
|
for (; ; ) {
|
|
14095
14351
|
if (byDir.has(dir)) {
|
|
14096
14352
|
const cached = byDir.get(dir);
|
|
14097
14353
|
if (cached !== void 0) return cached;
|
|
14098
|
-
} else if (
|
|
14099
|
-
const srcDir = dir === "" ? "src" :
|
|
14354
|
+
} else if (existsSync8(path42.join(projectRoot, dir, "Cargo.toml"))) {
|
|
14355
|
+
const srcDir = dir === "" ? "src" : path42.posix.join(dir, "src");
|
|
14100
14356
|
const entry = { srcDir, crateName: readCrateName(dir) };
|
|
14101
14357
|
byDir.set(dir, entry);
|
|
14102
14358
|
return entry;
|
|
@@ -14104,7 +14360,7 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14104
14360
|
byDir.set(dir, void 0);
|
|
14105
14361
|
}
|
|
14106
14362
|
if (dir === "") return void 0;
|
|
14107
|
-
const parent =
|
|
14363
|
+
const parent = path42.posix.dirname(dir);
|
|
14108
14364
|
dir = parent === "." ? "" : parent;
|
|
14109
14365
|
}
|
|
14110
14366
|
}
|
|
@@ -14113,10 +14369,10 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14113
14369
|
function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
14114
14370
|
const moduleByDir = /* @__PURE__ */ new Map();
|
|
14115
14371
|
function readModulePath(repoRelDir) {
|
|
14116
|
-
const abs =
|
|
14372
|
+
const abs = path42.join(projectRoot, repoRelDir, "go.mod");
|
|
14117
14373
|
let text2;
|
|
14118
14374
|
try {
|
|
14119
|
-
text2 =
|
|
14375
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14120
14376
|
} catch {
|
|
14121
14377
|
return void 0;
|
|
14122
14378
|
}
|
|
@@ -14129,24 +14385,24 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14129
14385
|
return void 0;
|
|
14130
14386
|
}
|
|
14131
14387
|
function modulePathFor(fromFile) {
|
|
14132
|
-
let dir =
|
|
14388
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14133
14389
|
if (dir === ".") dir = "";
|
|
14134
14390
|
for (; ; ) {
|
|
14135
14391
|
if (moduleByDir.has(dir)) {
|
|
14136
14392
|
const cached = moduleByDir.get(dir);
|
|
14137
14393
|
if (cached !== void 0) return { modulePath: cached, moduleDir: dir };
|
|
14138
14394
|
} else {
|
|
14139
|
-
const mod =
|
|
14395
|
+
const mod = existsSync8(path42.join(projectRoot, dir, "go.mod")) ? readModulePath(dir) : void 0;
|
|
14140
14396
|
moduleByDir.set(dir, mod);
|
|
14141
14397
|
if (mod !== void 0) return { modulePath: mod, moduleDir: dir };
|
|
14142
14398
|
}
|
|
14143
14399
|
if (dir === "") return void 0;
|
|
14144
|
-
const parent =
|
|
14400
|
+
const parent = path42.posix.dirname(dir);
|
|
14145
14401
|
dir = parent === "." ? "" : parent;
|
|
14146
14402
|
}
|
|
14147
14403
|
}
|
|
14148
14404
|
function dirExists(repoRelDir) {
|
|
14149
|
-
const abs =
|
|
14405
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14150
14406
|
try {
|
|
14151
14407
|
return statSync3(abs).isDirectory();
|
|
14152
14408
|
} catch {
|
|
@@ -14154,7 +14410,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14154
14410
|
}
|
|
14155
14411
|
}
|
|
14156
14412
|
function goFilesIn(repoRelDir) {
|
|
14157
|
-
const abs =
|
|
14413
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14158
14414
|
let entries;
|
|
14159
14415
|
try {
|
|
14160
14416
|
entries = readdirSync3(abs, { withFileTypes: true });
|
|
@@ -14164,7 +14420,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14164
14420
|
const out = [];
|
|
14165
14421
|
for (const e of entries) {
|
|
14166
14422
|
if (e.isFile() && e.name.endsWith(".go")) {
|
|
14167
|
-
out.push(repoRelDir === "" ? e.name :
|
|
14423
|
+
out.push(repoRelDir === "" ? e.name : path42.posix.join(repoRelDir, e.name));
|
|
14168
14424
|
}
|
|
14169
14425
|
}
|
|
14170
14426
|
return out;
|
|
@@ -14173,7 +14429,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14173
14429
|
}
|
|
14174
14430
|
function makeJavaResolveDeps(projectRoot, exists) {
|
|
14175
14431
|
function javaFilesIn(repoRelDir) {
|
|
14176
|
-
const abs =
|
|
14432
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14177
14433
|
let entries;
|
|
14178
14434
|
try {
|
|
14179
14435
|
entries = readdirSync3(abs, { withFileTypes: true });
|
|
@@ -14183,7 +14439,7 @@ function makeJavaResolveDeps(projectRoot, exists) {
|
|
|
14183
14439
|
const out = [];
|
|
14184
14440
|
for (const e of entries) {
|
|
14185
14441
|
if (e.isFile() && e.name.endsWith(".java")) {
|
|
14186
|
-
out.push(repoRelDir === "" ? e.name :
|
|
14442
|
+
out.push(repoRelDir === "" ? e.name : path42.posix.join(repoRelDir, e.name));
|
|
14187
14443
|
}
|
|
14188
14444
|
}
|
|
14189
14445
|
return out;
|
|
@@ -14193,23 +14449,23 @@ function makeJavaResolveDeps(projectRoot, exists) {
|
|
|
14193
14449
|
function makePhpResolveDeps(projectRoot, exists) {
|
|
14194
14450
|
const psr4ByDir = /* @__PURE__ */ new Map();
|
|
14195
14451
|
function readPsr4(repoRelDir) {
|
|
14196
|
-
const abs =
|
|
14452
|
+
const abs = path42.join(projectRoot, repoRelDir, "composer.json");
|
|
14197
14453
|
let text2;
|
|
14198
14454
|
try {
|
|
14199
|
-
text2 =
|
|
14455
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14200
14456
|
} catch {
|
|
14201
14457
|
return /* @__PURE__ */ new Map();
|
|
14202
14458
|
}
|
|
14203
14459
|
return parsePsr4(text2, repoRelDir);
|
|
14204
14460
|
}
|
|
14205
14461
|
function psr4For(fromFile) {
|
|
14206
|
-
let dir =
|
|
14462
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14207
14463
|
if (dir === ".") dir = "";
|
|
14208
14464
|
for (; ; ) {
|
|
14209
14465
|
if (psr4ByDir.has(dir)) {
|
|
14210
14466
|
const cached = psr4ByDir.get(dir);
|
|
14211
14467
|
if (cached !== void 0 && cached.size > 0) return cached;
|
|
14212
|
-
} else if (
|
|
14468
|
+
} else if (existsSync8(path42.join(projectRoot, dir, "composer.json"))) {
|
|
14213
14469
|
const map = readPsr4(dir);
|
|
14214
14470
|
psr4ByDir.set(dir, map);
|
|
14215
14471
|
if (map.size > 0) return map;
|
|
@@ -14217,7 +14473,7 @@ function makePhpResolveDeps(projectRoot, exists) {
|
|
|
14217
14473
|
psr4ByDir.set(dir, /* @__PURE__ */ new Map());
|
|
14218
14474
|
}
|
|
14219
14475
|
if (dir === "") return /* @__PURE__ */ new Map();
|
|
14220
|
-
const parent =
|
|
14476
|
+
const parent = path42.posix.dirname(dir);
|
|
14221
14477
|
dir = parent === "." ? "" : parent;
|
|
14222
14478
|
}
|
|
14223
14479
|
}
|
|
@@ -14387,7 +14643,7 @@ function emitPairIssue(vp) {
|
|
|
14387
14643
|
async function classifyLogStateFromLock(graph, projectRoot, lock, issues) {
|
|
14388
14644
|
for (const [nodePath] of graph.nodes) {
|
|
14389
14645
|
const logRel = `.yggdrasil/model/${nodePath}/log.md`;
|
|
14390
|
-
const logAbs =
|
|
14646
|
+
const logAbs = path43.join(projectRoot, logRel);
|
|
14391
14647
|
let logContent = null;
|
|
14392
14648
|
try {
|
|
14393
14649
|
logContent = await readTextFile(logAbs);
|
|
@@ -14472,8 +14728,8 @@ function scanUncoveredFiles(graph, gitTrackedFiles) {
|
|
|
14472
14728
|
const paths = normalizeMappingPaths(node.meta.mapping);
|
|
14473
14729
|
allMappings.push(...paths);
|
|
14474
14730
|
}
|
|
14475
|
-
const projectRoot =
|
|
14476
|
-
const yggPrefix = toPosixPath(
|
|
14731
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14732
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14477
14733
|
const uncovered = [];
|
|
14478
14734
|
const tracked = excludeNestedGraphSubtrees(gitTrackedFiles);
|
|
14479
14735
|
for (const file of tracked) {
|
|
@@ -14495,8 +14751,8 @@ async function scanGitignoredCoveredFiles(graph, gitTrackedFiles) {
|
|
|
14495
14751
|
if (!isGlobPattern(raw)) directlyNamed.add(normalizeMappingPath(raw));
|
|
14496
14752
|
}
|
|
14497
14753
|
}
|
|
14498
|
-
const projectRoot =
|
|
14499
|
-
const yggPrefix = toPosixPath(
|
|
14754
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14755
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14500
14756
|
let gitignoreStack;
|
|
14501
14757
|
try {
|
|
14502
14758
|
gitignoreStack = await loadRootGitignoreStack(projectRoot);
|
|
@@ -14514,7 +14770,7 @@ async function scanGitignoredCoveredFiles(graph, gitTrackedFiles) {
|
|
|
14514
14770
|
if (directlyNamed.has(normalizeMappingPath(normalized))) continue;
|
|
14515
14771
|
let ignored;
|
|
14516
14772
|
try {
|
|
14517
|
-
ignored = isIgnoredByStack(
|
|
14773
|
+
ignored = isIgnoredByStack(path43.join(projectRoot, normalized), gitignoreStack);
|
|
14518
14774
|
} catch (err) {
|
|
14519
14775
|
debugWrite(`[check] scanGitignoredCoveredFiles: isIgnoredByStack threw for ${normalized}: ${err.message}`);
|
|
14520
14776
|
continue;
|
|
@@ -14537,7 +14793,7 @@ function buildGitignoredCoveredIssues(offending) {
|
|
|
14537
14793
|
}));
|
|
14538
14794
|
}
|
|
14539
14795
|
async function runCheck(graph, gitTrackedFiles) {
|
|
14540
|
-
const projectRoot =
|
|
14796
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14541
14797
|
const validation = await validate(graph);
|
|
14542
14798
|
const validationIssues = validation.issues.filter((vi) => vi.code).map((vi) => ({ ...vi, code: vi.code }));
|
|
14543
14799
|
const lockIssues = [];
|
|
@@ -14560,7 +14816,9 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14560
14816
|
const relResult = await runRelationPass(graph, projectRoot, {
|
|
14561
14817
|
extractorFor: extractorForLanguage,
|
|
14562
14818
|
resolvePathToFile: makeResolvePathToFile(projectRoot, buildOwnerIndex(graph.nodes).ownerOf),
|
|
14563
|
-
|
|
14819
|
+
// Repurposed field: now the content-addressed AST fact cache root (.ast-cache), the
|
|
14820
|
+
// per-file parse cache that skips the tree-sitter re-parse of unchanged files.
|
|
14821
|
+
symbolIndexDir: astCacheDir(graph.rootPath)
|
|
14564
14822
|
});
|
|
14565
14823
|
for (const [nodeId, nv] of relResult.violationsByNode) {
|
|
14566
14824
|
if (nv.verdict !== "refused") continue;
|
|
@@ -14591,7 +14849,7 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14591
14849
|
let coveredFiles = 0;
|
|
14592
14850
|
let totalFiles = 0;
|
|
14593
14851
|
if (gitTrackedFiles !== null) {
|
|
14594
|
-
const yggPrefix = toPosixPath(
|
|
14852
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14595
14853
|
const sourceFiles = excludeNestedGraphSubtrees(gitTrackedFiles).filter((f) => {
|
|
14596
14854
|
const normalized = toPosixPath(f.trim());
|
|
14597
14855
|
return !normalized.startsWith(yggPrefix + "/") && normalized !== yggPrefix;
|
|
@@ -14622,7 +14880,7 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14622
14880
|
const advisoryWarnings = allIssues.filter((i) => i.code === "aspect-violation-advisory").length;
|
|
14623
14881
|
const draftSkipped = countDraftAspectsAcrossGraph(graph);
|
|
14624
14882
|
return {
|
|
14625
|
-
projectName:
|
|
14883
|
+
projectName: path43.basename(projectRoot),
|
|
14626
14884
|
nodeCount: graph.nodes.size,
|
|
14627
14885
|
nodeTypeCounts,
|
|
14628
14886
|
aspectCount: graph.aspects.length,
|
|
@@ -14705,7 +14963,7 @@ function computeSuggestedNext(issues) {
|
|
|
14705
14963
|
}
|
|
14706
14964
|
|
|
14707
14965
|
// src/core/fill.ts
|
|
14708
|
-
import
|
|
14966
|
+
import path46 from "path";
|
|
14709
14967
|
|
|
14710
14968
|
// src/llm/api-utils.ts
|
|
14711
14969
|
var ENV_VAR_MAP = {
|
|
@@ -15166,13 +15424,13 @@ var GeminiCliProvider = class extends CliAgentProvider {
|
|
|
15166
15424
|
registerProvider("gemini-cli", (c) => new GeminiCliProvider({ model: c.model, timeout: c.timeout }));
|
|
15167
15425
|
|
|
15168
15426
|
// src/core/fill-det.ts
|
|
15169
|
-
import
|
|
15427
|
+
import path44 from "path";
|
|
15170
15428
|
|
|
15171
15429
|
// src/core/fill-shared.ts
|
|
15172
15430
|
async function readBytesOrEmpty(absPath) {
|
|
15173
|
-
const { readFile:
|
|
15431
|
+
const { readFile: readFile21 } = await import("fs/promises");
|
|
15174
15432
|
try {
|
|
15175
|
-
return await
|
|
15433
|
+
return await readFile21(absPath);
|
|
15176
15434
|
} catch (e) {
|
|
15177
15435
|
debugWrite(`[fill] readBytesOrEmpty failed for ${absPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
15178
15436
|
return Buffer.alloc(0);
|
|
@@ -15181,7 +15439,7 @@ async function readBytesOrEmpty(absPath) {
|
|
|
15181
15439
|
|
|
15182
15440
|
// src/core/fill-det.ts
|
|
15183
15441
|
async function fillDetPair(graph, projectRoot, pair, aspect) {
|
|
15184
|
-
const aspectDirAbs =
|
|
15442
|
+
const aspectDirAbs = path44.join(projectRoot, ".yggdrasil", "aspects", aspect.id);
|
|
15185
15443
|
const fullMapping = await computeNodeMappedFiles(graph, pair.nodePath);
|
|
15186
15444
|
const subjectScope = pair.subjectFiles.length < fullMapping.length ? pair.subjectFiles : void 0;
|
|
15187
15445
|
const runOnce = async () => {
|
|
@@ -15222,7 +15480,7 @@ async function fillDetPair(graph, projectRoot, pair, aspect) {
|
|
|
15222
15480
|
const observations = run.result.observations;
|
|
15223
15481
|
const files = [];
|
|
15224
15482
|
for (const rel of pair.subjectFiles) {
|
|
15225
|
-
const abs =
|
|
15483
|
+
const abs = path44.resolve(projectRoot, rel);
|
|
15226
15484
|
const bytes = await readBytesOrEmpty(abs);
|
|
15227
15485
|
files.push([rel, hashBytes(bytes)]);
|
|
15228
15486
|
}
|
|
@@ -15254,7 +15512,7 @@ function detRuntimeNotice(aspectId, unitKey, reason) {
|
|
|
15254
15512
|
}
|
|
15255
15513
|
|
|
15256
15514
|
// src/core/fill-llm.ts
|
|
15257
|
-
import
|
|
15515
|
+
import path45 from "path";
|
|
15258
15516
|
|
|
15259
15517
|
// src/llm/aspect-verifier.ts
|
|
15260
15518
|
async function verifyWithConsensus(provider, prompt, consensus) {
|
|
@@ -15283,14 +15541,14 @@ async function verifyWithConsensus(provider, prompt, consensus) {
|
|
|
15283
15541
|
async function fillLlmPair(graph, projectRoot, pair, aspect, tier, tierName, mergedTier, provider, referencesCache) {
|
|
15284
15542
|
const subjects = [];
|
|
15285
15543
|
for (const rel of pair.subjectFiles) {
|
|
15286
|
-
const bytes = await readBytesOrEmpty(
|
|
15544
|
+
const bytes = await readBytesOrEmpty(path45.resolve(projectRoot, rel));
|
|
15287
15545
|
subjects.push({ path: rel, bytes });
|
|
15288
15546
|
}
|
|
15289
15547
|
const refInputs = aspect.references ?? [];
|
|
15290
15548
|
const referencesForHash = [];
|
|
15291
15549
|
const referencesForPrompt = [];
|
|
15292
15550
|
for (const ref of refInputs) {
|
|
15293
|
-
const absRef =
|
|
15551
|
+
const absRef = path45.resolve(projectRoot, ref.path);
|
|
15294
15552
|
let bytes = referencesCache.get(absRef);
|
|
15295
15553
|
if (bytes === void 0) {
|
|
15296
15554
|
bytes = await readFileBytes(absRef);
|
|
@@ -15770,7 +16028,7 @@ async function runFill(graph, opts) {
|
|
|
15770
16028
|
});
|
|
15771
16029
|
const emitIssue = opts.emitIssue ?? (() => {
|
|
15772
16030
|
});
|
|
15773
|
-
const projectRoot =
|
|
16031
|
+
const projectRoot = path46.dirname(graph.rootPath);
|
|
15774
16032
|
const onlyDeterministic = opts.onlyDeterministic ?? false;
|
|
15775
16033
|
const dryRun = opts.dryRun ?? false;
|
|
15776
16034
|
const isTTY2 = opts.isTTY ?? (process.stderr.isTTY ?? false);
|
|
@@ -16050,7 +16308,7 @@ async function runFill(graph, opts) {
|
|
|
16050
16308
|
}
|
|
16051
16309
|
|
|
16052
16310
|
// src/cli/check.ts
|
|
16053
|
-
import
|
|
16311
|
+
import path47 from "path";
|
|
16054
16312
|
|
|
16055
16313
|
// src/cli/group-issues.ts
|
|
16056
16314
|
var CODE_ONLY_GROUP_CODES = /* @__PURE__ */ new Set(["unverified"]);
|
|
@@ -16112,6 +16370,8 @@ function groupIssues(issues) {
|
|
|
16112
16370
|
const rep = sorted[0];
|
|
16113
16371
|
const nodes = new Set(sorted.map((m) => m.nodePath ?? ""));
|
|
16114
16372
|
const isCodeOnly = CODE_ONLY_GROUP_CODES.has(rep.code);
|
|
16373
|
+
const distinctNext = new Set(sorted.map((m) => m.messageData.next ?? ""));
|
|
16374
|
+
const distinctWhy = new Set(sorted.map((m) => m.messageData.why ?? ""));
|
|
16115
16375
|
groups.push({
|
|
16116
16376
|
code: rep.code,
|
|
16117
16377
|
aspectId: isCodeOnly ? void 0 : rep.aspectId,
|
|
@@ -16122,6 +16382,8 @@ function groupIssues(issues) {
|
|
|
16122
16382
|
sharedWhy: rep.messageData.why,
|
|
16123
16383
|
sharedNext: rep.messageData.next,
|
|
16124
16384
|
perMemberReason: FULL_WHAT_CODES.has(rep.code),
|
|
16385
|
+
divergentNext: distinctNext.size > 1,
|
|
16386
|
+
divergentWhy: distinctWhy.size > 1,
|
|
16125
16387
|
members: sorted
|
|
16126
16388
|
});
|
|
16127
16389
|
}
|
|
@@ -16156,12 +16418,12 @@ function resolveApproveMode(opts, config) {
|
|
|
16156
16418
|
return { approve: false, onlyDeterministic: false };
|
|
16157
16419
|
}
|
|
16158
16420
|
function registerCheckCommand(program2) {
|
|
16159
|
-
program2.command("check").description("Unified graph gate \u2014 verification, coverage, completeness").option("--approve", "Fill every unverified pair (deterministic first, then LLM), then report").option("--no-approve", "Force read-only mode even when auto_approve is configured (overrides config)").option("--only-deterministic", "With --approve: fill ONLY deterministic pairs (keyless, free); committed locks stay untouched. For CI and pre-commit.").option("--dry-run", "With --approve: free cost preview \u2014 print the budget + per-node/per-aspect breakdown, then exit 0 WITHOUT writing anything or calling the reviewer.").option("--top [n]", "Read-only triage: print only the N highest-priority issue blocks (bare --top = just the single suggestedNext block). Header counts + exit code stay TRUE.").option("--summary", "Read-only triage: print per-node counts only (no per-issue blocks). Header counts + exit code stay TRUE.").option("--details", "Read-only: ungrouped, one block per issue (full per-pair detail). Opposite of the default grouped view.").option("--aspect <id>", "Read-only: drill into one rule \u2014 show only that aspect's issues, grouped, with the full per-node detail.").option("-q, --quiet", "Suppress --approve progress on stderr (only the final report + exit code). No-op with a plain read
|
|
16421
|
+
program2.command("check").description("Unified graph gate \u2014 verification, coverage, completeness").option("--approve", "Fill every unverified pair (deterministic first, then LLM), then report").option("--no-approve", "Force read-only mode even when auto_approve is configured (overrides config)").option("--only-deterministic", "With --approve: fill ONLY deterministic pairs (keyless, free); committed locks stay untouched. For CI and pre-commit.").option("--dry-run", "With --approve: free cost preview \u2014 print the budget + per-node/per-aspect breakdown, then exit 0 WITHOUT writing anything or calling the reviewer.").option("--top [n]", "Read-only triage: print only the N highest-priority issue blocks (bare --top = just the single suggestedNext block). Header counts + exit code stay TRUE.").option("--summary", "Read-only triage: print per-node counts only (no per-issue blocks). Header counts + exit code stay TRUE.").option("--details", "Read-only: ungrouped, one block per issue (full per-pair detail). Opposite of the default grouped view.").option("--aspect <id>", "Read-only: drill into one rule \u2014 show only that aspect's issues, grouped, with the full per-node detail.").option("-q, --quiet", "Suppress --approve progress on stderr (only the final report + exit code). No-op with a plain read; with --dry-run the budget preview still prints (--dry-run wins).").action(async (opts) => {
|
|
16160
16422
|
try {
|
|
16161
16423
|
const cwd = process.cwd();
|
|
16162
16424
|
const graph = await loadGraphOrAbort(cwd, { tolerateInvalidConfig: true });
|
|
16163
16425
|
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
16164
|
-
const projectRoot =
|
|
16426
|
+
const projectRoot = path47.dirname(graph.rootPath);
|
|
16165
16427
|
const gitFiles = await walkRepoFiles(projectRoot);
|
|
16166
16428
|
const wantsTop = opts.top !== void 0;
|
|
16167
16429
|
if (wantsTop && opts.summary) {
|
|
@@ -16182,6 +16444,16 @@ function registerCheckCommand(program2) {
|
|
|
16182
16444
|
await exitAfterFlush(1);
|
|
16183
16445
|
return;
|
|
16184
16446
|
}
|
|
16447
|
+
if (opts.onlyDeterministic && (wantsTop || opts.summary || opts.details || opts.aspect !== void 0)) {
|
|
16448
|
+
const viewFlag = wantsTop ? "--top" : opts.summary ? "--summary" : opts.details ? "--details" : "--aspect";
|
|
16449
|
+
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16450
|
+
what: `${viewFlag} cannot be combined with --only-deterministic.`,
|
|
16451
|
+
why: `${viewFlag} is a READ-ONLY view of the plain \`yg check\` result (it narrows output and writes nothing). --only-deterministic is a FILL flag (it implies --approve, writing the deterministic verdict cache). Mixing a read-only view with the writer would silently drop the fill \u2014 the deterministic pairs would NOT be filled.`,
|
|
16452
|
+
next: `Run: yg check ${viewFlag}${opts.aspect !== void 0 ? " <id>" : wantsTop ? " <n>" : ""} (read-only view), or yg check --approve --only-deterministic (deterministic fill).`
|
|
16453
|
+
})}`) + "\n");
|
|
16454
|
+
await exitAfterFlush(1);
|
|
16455
|
+
return;
|
|
16456
|
+
}
|
|
16185
16457
|
if (opts.details && (wantsTop || opts.summary)) {
|
|
16186
16458
|
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16187
16459
|
what: "--details cannot be combined with --top or --summary.",
|
|
@@ -16229,6 +16501,18 @@ function registerCheckCommand(program2) {
|
|
|
16229
16501
|
await exitAfterFlush(1);
|
|
16230
16502
|
return;
|
|
16231
16503
|
}
|
|
16504
|
+
const knownAspectIds = (graph.aspects ?? []).map((a) => a.id);
|
|
16505
|
+
if (!knownAspectIds.includes(opts.aspect)) {
|
|
16506
|
+
const idList = knownAspectIds.slice().sort((a, b) => a.localeCompare(b, "en"));
|
|
16507
|
+
const known = idList.length === 0 ? "The graph defines no aspects." : idList.length <= 30 ? `Known aspect ids: ${idList.join(", ")}.` : `The graph defines ${idList.length} aspects.`;
|
|
16508
|
+
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16509
|
+
what: `Unknown aspect '${opts.aspect}'.`,
|
|
16510
|
+
why: `--aspect drills into ONE rule by its aspect id, but '${opts.aspect}' is not an aspect defined in this graph \u2014 so the filter would match nothing and render a misleading "0 of N errors" view. ${known}`,
|
|
16511
|
+
next: "Run: yg aspects (list every aspect id), then yg check --aspect <id> with a real id; or yg check (full wall)."
|
|
16512
|
+
})}`) + "\n");
|
|
16513
|
+
await exitAfterFlush(1);
|
|
16514
|
+
return;
|
|
16515
|
+
}
|
|
16232
16516
|
}
|
|
16233
16517
|
let view = { kind: "full" };
|
|
16234
16518
|
if (opts.aspect !== void 0) {
|
|
@@ -16274,9 +16558,9 @@ function registerCheckCommand(program2) {
|
|
|
16274
16558
|
gitTrackedFiles: gitFiles,
|
|
16275
16559
|
onlyDeterministic: mode.onlyDeterministic,
|
|
16276
16560
|
dryRun: isDryRun,
|
|
16277
|
-
write:
|
|
16278
|
-
} : isDryRun ? (s) => {
|
|
16561
|
+
write: isDryRun ? (s) => {
|
|
16279
16562
|
process.stdout.write(s);
|
|
16563
|
+
} : isQuiet ? () => {
|
|
16280
16564
|
} : (s) => {
|
|
16281
16565
|
process.stderr.write(s);
|
|
16282
16566
|
},
|
|
@@ -16375,9 +16659,9 @@ function formatOutput(result, view = { kind: "full" }, autoFilled = false, emoji
|
|
|
16375
16659
|
const nextCmd = firstFiltered.messageData.next.split("\n")[0];
|
|
16376
16660
|
sections.push("");
|
|
16377
16661
|
sections.push(`Next (this group): ${nextCmd}`);
|
|
16662
|
+
sections.push("");
|
|
16663
|
+
return sections.join("\n");
|
|
16378
16664
|
}
|
|
16379
|
-
sections.push("");
|
|
16380
|
-
return sections.join("\n");
|
|
16381
16665
|
} else if (view.kind === "details") {
|
|
16382
16666
|
if (errors.length > 0) {
|
|
16383
16667
|
sections.push("");
|
|
@@ -16618,12 +16902,23 @@ function glossLabel(label) {
|
|
|
16618
16902
|
function renderGroup(group, lines, opts) {
|
|
16619
16903
|
const aspectSeg = group.aspectId ? ` aspect '${group.aspectId}'` : "";
|
|
16620
16904
|
lines.push(` ${glossLabel(group.label)} ${group.pairCount} pairs ${group.nodeCount} nodes${aspectSeg}`);
|
|
16621
|
-
if (group.sharedWhy) lines.push(`${BLOCK_INDENT}${group.sharedWhy}`);
|
|
16622
|
-
if (group.sharedNext) {
|
|
16905
|
+
if (group.sharedWhy && !group.divergentWhy) lines.push(`${BLOCK_INDENT}${group.sharedWhy}`);
|
|
16906
|
+
if (group.sharedNext && !group.divergentNext) {
|
|
16623
16907
|
const nextLines = group.sharedNext.split("\n");
|
|
16624
16908
|
lines.push(`${BLOCK_INDENT}Fix: ${nextLines[0]}`);
|
|
16625
16909
|
for (const extra of nextLines.slice(1)) lines.push(`${BLOCK_INDENT}${extra}`);
|
|
16626
16910
|
}
|
|
16911
|
+
const MEMBER_DETAIL_INDENT = `${BLOCK_INDENT} `;
|
|
16912
|
+
const emitDivergentDetail = (m) => {
|
|
16913
|
+
if (group.divergentWhy && m.messageData.why) {
|
|
16914
|
+
lines.push(`${MEMBER_DETAIL_INDENT}Why: ${m.messageData.why.split("\n")[0]}`);
|
|
16915
|
+
}
|
|
16916
|
+
if (group.divergentNext && m.messageData.next) {
|
|
16917
|
+
const nextLines = m.messageData.next.split("\n");
|
|
16918
|
+
lines.push(`${MEMBER_DETAIL_INDENT}Fix: ${nextLines[0]}`);
|
|
16919
|
+
for (const extra of nextLines.slice(1)) lines.push(`${MEMBER_DETAIL_INDENT}${extra}`);
|
|
16920
|
+
}
|
|
16921
|
+
};
|
|
16627
16922
|
const members = group.members;
|
|
16628
16923
|
const truncate = opts.isTTY && members.length > CAP_NODES2;
|
|
16629
16924
|
const shown = truncate ? members.slice(0, CAP_NODES2) : members;
|
|
@@ -16639,10 +16934,12 @@ function renderGroup(group, lines, opts) {
|
|
|
16639
16934
|
lines.push(`${BLOCK_INDENT} ${extra}`);
|
|
16640
16935
|
}
|
|
16641
16936
|
}
|
|
16937
|
+
emitDivergentDetail(m);
|
|
16642
16938
|
} else {
|
|
16643
16939
|
const memberAspectSeg = group.aspectId === void 0 && m.aspectId !== void 0 ? ` aspect '${m.aspectId}'` : "";
|
|
16644
16940
|
const whatSeg = !memberAspectSeg && m.messageData.what ? ` ${m.messageData.what.split("\n")[0]}` : "";
|
|
16645
16941
|
lines.push(`${BLOCK_INDENT}- ${node}${memberAspectSeg || whatSeg}`);
|
|
16942
|
+
emitDivergentDetail(m);
|
|
16646
16943
|
}
|
|
16647
16944
|
}
|
|
16648
16945
|
if (truncate) {
|
|
@@ -16652,10 +16949,10 @@ function renderGroup(group, lines, opts) {
|
|
|
16652
16949
|
}
|
|
16653
16950
|
|
|
16654
16951
|
// src/cli/aspect-test.ts
|
|
16655
|
-
import
|
|
16952
|
+
import path49 from "path";
|
|
16656
16953
|
|
|
16657
16954
|
// src/ast/runner.ts
|
|
16658
|
-
import
|
|
16955
|
+
import path48 from "path";
|
|
16659
16956
|
import { readFile as readFile16 } from "fs/promises";
|
|
16660
16957
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
16661
16958
|
var AstRunnerError = class extends Error {
|
|
@@ -16672,7 +16969,7 @@ ${data.next}`);
|
|
|
16672
16969
|
};
|
|
16673
16970
|
async function runAstAspect(params) {
|
|
16674
16971
|
ensureLoaderRegistered();
|
|
16675
|
-
const checkPath =
|
|
16972
|
+
const checkPath = path48.resolve(params.projectRoot, params.aspectDir, "check.mjs");
|
|
16676
16973
|
let mod;
|
|
16677
16974
|
try {
|
|
16678
16975
|
mod = await import(pathToFileURL3(checkPath).href);
|
|
@@ -16704,8 +17001,8 @@ async function runAstAspect(params) {
|
|
|
16704
17001
|
sourceFiles.push({ path: f.path, content: cached.content, ast: cached.ast });
|
|
16705
17002
|
continue;
|
|
16706
17003
|
}
|
|
16707
|
-
const content20 = await readFile16(
|
|
16708
|
-
if (getLanguageForExtension(
|
|
17004
|
+
const content20 = await readFile16(path48.resolve(params.projectRoot, f.path), "utf-8");
|
|
17005
|
+
if (getLanguageForExtension(path48.extname(f.path).toLowerCase()) === null) {
|
|
16709
17006
|
sourceFiles.push({ path: f.path, content: content20, ast: void 0 });
|
|
16710
17007
|
continue;
|
|
16711
17008
|
}
|
|
@@ -16891,7 +17188,7 @@ function registerAspectTestCommand(program2) {
|
|
|
16891
17188
|
process.exit(1);
|
|
16892
17189
|
return;
|
|
16893
17190
|
}
|
|
16894
|
-
const aspectDir =
|
|
17191
|
+
const aspectDir = path49.join(".yggdrasil", "aspects", aspect.id);
|
|
16895
17192
|
if (hasNode) {
|
|
16896
17193
|
const nodePath = opts.node.trim().replace(/\/$/, "");
|
|
16897
17194
|
const node = graph.nodes.get(nodePath);
|
|
@@ -16951,12 +17248,17 @@ function registerAspectTestCommand(program2) {
|
|
|
16951
17248
|
await exitAfterFlush(1);
|
|
16952
17249
|
} catch (e) {
|
|
16953
17250
|
debugWrite(`[aspect-test] run failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
17251
|
+
if (e instanceof StructureRunnerError || e instanceof AstRunnerError) {
|
|
17252
|
+
process.stderr.write(buildIssueMessage(e.messageData) + "\n");
|
|
17253
|
+
await exitAfterFlush(1);
|
|
17254
|
+
return;
|
|
17255
|
+
}
|
|
16954
17256
|
abortOnUnexpectedError(e, "running aspect-test");
|
|
16955
17257
|
}
|
|
16956
17258
|
});
|
|
16957
17259
|
}
|
|
16958
17260
|
async function resolveCompanionsForTest(graph, projectRoot, pair, aspect) {
|
|
16959
|
-
const aspectDirAbs =
|
|
17261
|
+
const aspectDirAbs = path49.join(projectRoot, ".yggdrasil", "aspects", aspect.id);
|
|
16960
17262
|
const fullMapping = await computeNodeMappedFiles(graph, pair.nodePath);
|
|
16961
17263
|
const subjectScope = pair.subjectFiles.length < fullMapping.length ? pair.subjectFiles : void 0;
|
|
16962
17264
|
const run = await runCompanionHook({
|
|
@@ -17032,7 +17334,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17032
17334
|
const refInputs = aspect.references ?? [];
|
|
17033
17335
|
const referencesForPrompt = [];
|
|
17034
17336
|
for (const ref of refInputs) {
|
|
17035
|
-
const absRef =
|
|
17337
|
+
const absRef = path49.resolve(projectRoot, ref.path);
|
|
17036
17338
|
let content20;
|
|
17037
17339
|
try {
|
|
17038
17340
|
content20 = await readTextFile(absRef);
|
|
@@ -17078,7 +17380,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17078
17380
|
for (const rel of pair.subjectFiles) {
|
|
17079
17381
|
let content20;
|
|
17080
17382
|
try {
|
|
17081
|
-
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(
|
|
17383
|
+
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(path49.resolve(projectRoot, rel)));
|
|
17082
17384
|
content20 = bytes.toString("utf8");
|
|
17083
17385
|
} catch (e) {
|
|
17084
17386
|
debugWrite(`[aspect-test] subject file read failed for ${rel} on ${pair.unitKey}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -17134,7 +17436,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17134
17436
|
for (const rel of pair.subjectFiles) {
|
|
17135
17437
|
let content20;
|
|
17136
17438
|
try {
|
|
17137
|
-
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(
|
|
17439
|
+
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(path49.resolve(projectRoot, rel)));
|
|
17138
17440
|
content20 = bytes.toString("utf8");
|
|
17139
17441
|
} catch (e) {
|
|
17140
17442
|
debugWrite(`[aspect-test] subject file read failed for ${rel} on ${pair.unitKey}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -17250,10 +17552,10 @@ function printStructureViolations(violations) {
|
|
|
17250
17552
|
// src/cli/log.ts
|
|
17251
17553
|
import chalk9 from "chalk";
|
|
17252
17554
|
import { readFile as readFile18, stat as stat7 } from "fs/promises";
|
|
17253
|
-
import
|
|
17555
|
+
import path53 from "path";
|
|
17254
17556
|
|
|
17255
17557
|
// src/core/log/log-add.ts
|
|
17256
|
-
import
|
|
17558
|
+
import path50 from "path";
|
|
17257
17559
|
|
|
17258
17560
|
// src/utils/node-path-validator.ts
|
|
17259
17561
|
function validateNodePath(raw) {
|
|
@@ -17366,7 +17668,7 @@ async function logAdd(input) {
|
|
|
17366
17668
|
}
|
|
17367
17669
|
};
|
|
17368
17670
|
}
|
|
17369
|
-
const logPath2 =
|
|
17671
|
+
const logPath2 = path50.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17370
17672
|
const stats = await statLogFile(logPath2);
|
|
17371
17673
|
if (stats !== null) {
|
|
17372
17674
|
if (stats.isSymbolicLink) {
|
|
@@ -17435,7 +17737,7 @@ function reasonHasLevel2HeaderOutsideFence(reason) {
|
|
|
17435
17737
|
}
|
|
17436
17738
|
|
|
17437
17739
|
// src/core/log/log-read.ts
|
|
17438
|
-
import
|
|
17740
|
+
import path51 from "path";
|
|
17439
17741
|
var DEFAULT_TOP = 10;
|
|
17440
17742
|
async function logRead(input) {
|
|
17441
17743
|
const { graph } = input;
|
|
@@ -17481,7 +17783,7 @@ async function logRead(input) {
|
|
|
17481
17783
|
}
|
|
17482
17784
|
};
|
|
17483
17785
|
}
|
|
17484
|
-
const logPath2 =
|
|
17786
|
+
const logPath2 = path51.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17485
17787
|
const content20 = await readLogSafe(logPath2);
|
|
17486
17788
|
if (content20 === "") {
|
|
17487
17789
|
return { ok: true, entries: [] };
|
|
@@ -17504,8 +17806,8 @@ async function logRead(input) {
|
|
|
17504
17806
|
}
|
|
17505
17807
|
|
|
17506
17808
|
// src/core/log/log-merge-resolve.ts
|
|
17507
|
-
import { createHash as
|
|
17508
|
-
import
|
|
17809
|
+
import { createHash as createHash7 } from "crypto";
|
|
17810
|
+
import path52 from "path";
|
|
17509
17811
|
|
|
17510
17812
|
// src/utils/git-introspect.ts
|
|
17511
17813
|
import { exec } from "child_process";
|
|
@@ -17585,7 +17887,7 @@ async function logMergeResolve(input) {
|
|
|
17585
17887
|
}
|
|
17586
17888
|
};
|
|
17587
17889
|
}
|
|
17588
|
-
const logPath2 =
|
|
17890
|
+
const logPath2 = path52.join(yggRoot, "model", nodePath, "log.md");
|
|
17589
17891
|
let currentLog;
|
|
17590
17892
|
try {
|
|
17591
17893
|
currentLog = await readTextFile(logPath2);
|
|
@@ -17633,7 +17935,7 @@ async function logMergeResolve(input) {
|
|
|
17633
17935
|
const p2New = parseLog(parent2Log).slice(ancestorEntries.length);
|
|
17634
17936
|
const currentEntries = parseLog(currentLog);
|
|
17635
17937
|
const currentNew = currentEntries.slice(ancestorEntries.length);
|
|
17636
|
-
const entryKey = (e) =>
|
|
17938
|
+
const entryKey = (e) => createHash7("sha256").update(`${e.datetime}
|
|
17637
17939
|
${e.body}`).digest("hex");
|
|
17638
17940
|
const parentNew = [...p1New, ...p2New];
|
|
17639
17941
|
const parentNewKeys = new Set(parentNew.map(entryKey));
|
|
@@ -17792,7 +18094,7 @@ ${entry.body}`);
|
|
|
17792
18094
|
log2.command("merge-resolve").description("Reconcile log.md after a git merge (HEAD must be a merge commit)").requiredOption("--node <path>", "Node path (relative to .yggdrasil/model/)").action(async (opts) => {
|
|
17793
18095
|
try {
|
|
17794
18096
|
const graph = await loadGraphOrAbort(process.cwd(), { tolerateInvalidConfig: true });
|
|
17795
|
-
const repoRoot =
|
|
18097
|
+
const repoRoot = path53.dirname(graph.rootPath);
|
|
17796
18098
|
const nodePath = toPosix(opts.node.trim()).replace(/\/$/, "");
|
|
17797
18099
|
const result = await logMergeResolve({ graph, nodePath, repoRoot });
|
|
17798
18100
|
if (!result.ok) {
|
|
@@ -17817,15 +18119,15 @@ import chalk10 from "chalk";
|
|
|
17817
18119
|
|
|
17818
18120
|
// src/io/find-index.ts
|
|
17819
18121
|
import { readFile as readFile19, lstat as lstat3 } from "fs/promises";
|
|
17820
|
-
import
|
|
18122
|
+
import path54 from "path";
|
|
17821
18123
|
import MiniSearch from "minisearch";
|
|
17822
18124
|
var MAX_BODY_BYTES = 1048576;
|
|
17823
18125
|
async function buildIndex(graph) {
|
|
17824
|
-
const projectRoot =
|
|
18126
|
+
const projectRoot = path54.dirname(graph.rootPath);
|
|
17825
18127
|
const docs = [];
|
|
17826
18128
|
for (const [nodePath, node] of graph.nodes) {
|
|
17827
18129
|
const displayPath = `model/${nodePath}/`;
|
|
17828
|
-
const logPath2 =
|
|
18130
|
+
const logPath2 = path54.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17829
18131
|
let body = "";
|
|
17830
18132
|
try {
|
|
17831
18133
|
const st = await lstat3(logPath2);
|
|
@@ -17842,7 +18144,7 @@ This does not affect append-only integrity. No action required.
|
|
|
17842
18144
|
}
|
|
17843
18145
|
body = truncated;
|
|
17844
18146
|
} else if (st.isSymbolicLink()) {
|
|
17845
|
-
process.stderr.write(`Warning: skipping symlinked log.md at ${
|
|
18147
|
+
process.stderr.write(`Warning: skipping symlinked log.md at ${path54.relative(projectRoot, logPath2)}
|
|
17846
18148
|
`);
|
|
17847
18149
|
} else {
|
|
17848
18150
|
}
|
|
@@ -18010,18 +18312,18 @@ function registerFindCommand(program2) {
|
|
|
18010
18312
|
|
|
18011
18313
|
// src/cli/type-suggest.ts
|
|
18012
18314
|
import chalk11 from "chalk";
|
|
18013
|
-
import { existsSync as
|
|
18315
|
+
import { existsSync as existsSync9 } from "fs";
|
|
18014
18316
|
import { resolve as resolve5 } from "path";
|
|
18015
18317
|
|
|
18016
18318
|
// src/core/type-classifier.ts
|
|
18017
|
-
import
|
|
18319
|
+
import path55 from "path";
|
|
18018
18320
|
async function classifyFile(absPath, repoRelPath, graph, cache) {
|
|
18019
18321
|
const matches = [];
|
|
18020
18322
|
const partialScores = [];
|
|
18021
18323
|
const ctx = {
|
|
18022
18324
|
absPath,
|
|
18023
18325
|
repoRelPath,
|
|
18024
|
-
projectRoot:
|
|
18326
|
+
projectRoot: path55.dirname(graph.rootPath),
|
|
18025
18327
|
cache
|
|
18026
18328
|
};
|
|
18027
18329
|
for (const [typeId, def] of Object.entries(graph.architecture.node_types)) {
|
|
@@ -18077,7 +18379,7 @@ Type matching does not apply here.
|
|
|
18077
18379
|
return;
|
|
18078
18380
|
}
|
|
18079
18381
|
const gitignoreStack = await loadRootGitignoreStack(projectRoot);
|
|
18080
|
-
if (
|
|
18382
|
+
if (existsSync9(absPath) && isIgnoredByStack(absPath, gitignoreStack)) {
|
|
18081
18383
|
process.stderr.write(
|
|
18082
18384
|
chalk11.yellow(
|
|
18083
18385
|
`
|
|
@@ -18089,7 +18391,7 @@ file-mapping-gitignored. Proceeding with classification for context.
|
|
|
18089
18391
|
)
|
|
18090
18392
|
);
|
|
18091
18393
|
}
|
|
18092
|
-
if (!
|
|
18394
|
+
if (!existsSync9(absPath)) {
|
|
18093
18395
|
process.stdout.write(`
|
|
18094
18396
|
(File does not exist \u2014 evaluating path predicates only)
|
|
18095
18397
|
|
|
@@ -18317,6 +18619,10 @@ Examples:
|
|
|
18317
18619
|
Plain (non-glob) entries remain unchanged: an exact file path or a directory
|
|
18318
18620
|
prefix (e.g. \`src/handlers\`) covers that file or all files beneath it.
|
|
18319
18621
|
|
|
18622
|
+
## Allowed relations and default policy
|
|
18623
|
+
|
|
18624
|
+
A node type may also set \`relations.default: deny\` to forbid every relation type it does not explicitly list (a sink), open a single relation type to any target with \`['*']\`, or forbid one with an empty list \`[]\`. An omitted \`default\` means allow.
|
|
18625
|
+
|
|
18320
18626
|
## log_required \u2014 when to enable the log gate
|
|
18321
18627
|
|
|
18322
18628
|
Each node type may set \`log_required\` (default \`false\`). When \`true\`, a node of
|
|
@@ -20552,7 +20858,8 @@ to the \`.yggdrasil/\` directory):
|
|
|
20552
20858
|
|
|
20553
20859
|
\`\`\`
|
|
20554
20860
|
yg-secrets.yaml # provider API keys
|
|
20555
|
-
.symbols-cache/ # the relation pass's per-language symbol-index cache
|
|
20861
|
+
.symbols-cache/ # the relation pass's legacy per-language symbol-index cache
|
|
20862
|
+
.ast-cache/ # the relation pass's content-addressed per-file AST fact cache
|
|
20556
20863
|
.debug.log # the opt-in command debug log
|
|
20557
20864
|
\`\`\`
|
|
20558
20865
|
|
|
@@ -21231,9 +21538,7 @@ Event relations must be paired: if A emits to B, B must declare a
|
|
|
21231
21538
|
|
|
21232
21539
|
## Architecture controls allowed relations
|
|
21233
21540
|
|
|
21234
|
-
\`
|
|
21235
|
-
which relation types may target which node types. The validator rejects
|
|
21236
|
-
relations not permitted by the architecture.
|
|
21541
|
+
Architecture controls which relation types are allowed between which node types. By default a relation type is unconstrained \u2014 it may target any node type. A type constrains a relation type by listing its allowed targets (e.g. \`uses: [domain, data-access]\`); the validator then rejects any target not in that list. Three more controls let you lock a type down: \`default: deny\` makes every unlisted relation type target nothing (a sink); an empty list \`uses: []\` forbids that one relation type entirely; and the wildcard \`uses: ['*']\` opens a relation type to any target (useful under \`default: deny\`). An omitted \`default\` means allow, so existing graphs are unaffected.
|
|
21237
21542
|
|
|
21238
21543
|
When a needed relation is not allowed by the architecture:
|
|
21239
21544
|
1. Use a different relation type that IS allowed
|
|
@@ -22243,8 +22548,23 @@ node_types:
|
|
|
22243
22548
|
|
|
22244
22549
|
parents: [<type-id>, ...] # optional \u2014 allowed parent node types in the hierarchy.
|
|
22245
22550
|
|
|
22246
|
-
relations: # optional \u2014
|
|
22247
|
-
|
|
22551
|
+
relations: # optional \u2014 per-relation-type allow-list.
|
|
22552
|
+
# A relation type is constrained by listing its allowed target node types.
|
|
22553
|
+
# uses: [domain, data-access] \u2192 only those target types
|
|
22554
|
+
# uses: ['*'] \u2192 any target type
|
|
22555
|
+
# uses: [] \u2192 no target (relation type forbidden)
|
|
22556
|
+
# (relation type omitted) \u2192 governed by \`default\` below
|
|
22557
|
+
# default: allow | deny \u2192 policy for relation types NOT listed.
|
|
22558
|
+
# omitted \u21D2 allow (every unlisted relation type may target any type).
|
|
22559
|
+
# deny \u21D2 unlisted relation types target nothing (a sink).
|
|
22560
|
+
# Note: '*' is reserved as the any-target wildcard in relation lists and
|
|
22561
|
+
# must not be used as a node-type name (the parser rejects it).
|
|
22562
|
+
# Examples:
|
|
22563
|
+
# { default: deny } \u2192 pure sink
|
|
22564
|
+
# { default: deny, listens: ['*'] } \u2192 sink that may listen to anything
|
|
22565
|
+
# { default: allow, uses: [] } \u2192 everything open except \`uses\`
|
|
22566
|
+
uses: [<target-type>, ...]
|
|
22567
|
+
default: allow
|
|
22248
22568
|
`;
|
|
22249
22569
|
|
|
22250
22570
|
// src/templates/schemas/config.ts
|
|
@@ -22423,9 +22743,12 @@ function registerSchemasCommand(program2) {
|
|
|
22423
22743
|
}
|
|
22424
22744
|
|
|
22425
22745
|
// src/cli/suppressions.ts
|
|
22426
|
-
import chalk14 from "chalk";
|
|
22427
|
-
import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
|
|
22428
22746
|
import path57 from "path";
|
|
22747
|
+
|
|
22748
|
+
// src/portal/api/suppress-scan.ts
|
|
22749
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
22750
|
+
import path56 from "path";
|
|
22751
|
+
import chalk14 from "chalk";
|
|
22429
22752
|
function isBinaryContent(buf) {
|
|
22430
22753
|
const checkLen = Math.min(buf.length, 8192);
|
|
22431
22754
|
for (let i = 0; i < checkLen; i++) {
|
|
@@ -22448,7 +22771,7 @@ function isNoiseFile(relFile) {
|
|
|
22448
22771
|
return false;
|
|
22449
22772
|
}
|
|
22450
22773
|
async function scanMarkersForFile(relFile, text2) {
|
|
22451
|
-
const ext =
|
|
22774
|
+
const ext = path56.extname(relFile).toLowerCase();
|
|
22452
22775
|
if (getLanguageForExtension(ext) === null) {
|
|
22453
22776
|
return scanSuppressionMarkers(text2);
|
|
22454
22777
|
}
|
|
@@ -22470,11 +22793,11 @@ async function runSuppressionsScan(projectRoot, gitTrackedFiles, knownAspectIds)
|
|
|
22470
22793
|
const openDisables = /* @__PURE__ */ new Map();
|
|
22471
22794
|
for (const relFile of gitTrackedFiles) {
|
|
22472
22795
|
if (isNoiseFile(relFile)) continue;
|
|
22473
|
-
const absFile =
|
|
22474
|
-
if (!
|
|
22796
|
+
const absFile = path56.join(projectRoot, relFile);
|
|
22797
|
+
if (!existsSync10(absFile)) continue;
|
|
22475
22798
|
let buf;
|
|
22476
22799
|
try {
|
|
22477
|
-
buf =
|
|
22800
|
+
buf = readFileSync9(absFile);
|
|
22478
22801
|
} catch (error) {
|
|
22479
22802
|
debugWrite(`[suppressions] read fallback: ${relFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
22480
22803
|
continue;
|
|
@@ -22569,6 +22892,53 @@ function formatSuppressionsOutput(report2) {
|
|
|
22569
22892
|
}
|
|
22570
22893
|
return lines.join("\n") + "\n";
|
|
22571
22894
|
}
|
|
22895
|
+
function scanPortalSuppressions(report2, knownAspectIds, draftAspectIds) {
|
|
22896
|
+
const unboundedByFile = /* @__PURE__ */ new Map();
|
|
22897
|
+
for (const { file, markers } of report2.fileEntries) {
|
|
22898
|
+
const disableStack = /* @__PURE__ */ new Map();
|
|
22899
|
+
for (const m of markers) {
|
|
22900
|
+
if (m.kind === "disable") {
|
|
22901
|
+
const stack = disableStack.get(m.aspectId) ?? [];
|
|
22902
|
+
stack.push(m.line);
|
|
22903
|
+
disableStack.set(m.aspectId, stack);
|
|
22904
|
+
} else if (m.kind === "enable") {
|
|
22905
|
+
const stack = disableStack.get(m.aspectId);
|
|
22906
|
+
if (stack && stack.length > 0) {
|
|
22907
|
+
stack.pop();
|
|
22908
|
+
if (stack.length === 0) disableStack.delete(m.aspectId);
|
|
22909
|
+
}
|
|
22910
|
+
}
|
|
22911
|
+
}
|
|
22912
|
+
const open = /* @__PURE__ */ new Set();
|
|
22913
|
+
for (const lines of disableStack.values()) for (const l of lines) open.add(l);
|
|
22914
|
+
if (open.size > 0) unboundedByFile.set(file, open);
|
|
22915
|
+
}
|
|
22916
|
+
const out = [];
|
|
22917
|
+
for (const { file, markers } of report2.fileEntries) {
|
|
22918
|
+
const open = unboundedByFile.get(file);
|
|
22919
|
+
for (const m of markers) {
|
|
22920
|
+
if (m.kind === "enable") continue;
|
|
22921
|
+
const risk = resolveRisk(m, file, open, knownAspectIds, draftAspectIds);
|
|
22922
|
+
out.push({
|
|
22923
|
+
file,
|
|
22924
|
+
line: m.line,
|
|
22925
|
+
aspectId: m.aspectId,
|
|
22926
|
+
reason: m.reason,
|
|
22927
|
+
...risk ? { risk } : {}
|
|
22928
|
+
});
|
|
22929
|
+
}
|
|
22930
|
+
}
|
|
22931
|
+
return out;
|
|
22932
|
+
}
|
|
22933
|
+
function resolveRisk(m, file, openLines, knownAspectIds, draftAspectIds) {
|
|
22934
|
+
if (m.wildcard) return "wildcard";
|
|
22935
|
+
if (!knownAspectIds.has(m.aspectId)) return "typo";
|
|
22936
|
+
if (draftAspectIds.has(m.aspectId)) return "inert";
|
|
22937
|
+
if (m.kind === "disable" && openLines?.has(m.line)) return "unbounded";
|
|
22938
|
+
return void 0;
|
|
22939
|
+
}
|
|
22940
|
+
|
|
22941
|
+
// src/cli/suppressions.ts
|
|
22572
22942
|
function registerSuppressionsCommand(program2) {
|
|
22573
22943
|
program2.command("suppressions").description("Inventory active yg-suppress waivers and warn about footguns").action(async () => {
|
|
22574
22944
|
try {
|
|
@@ -22586,14 +22956,1130 @@ function registerSuppressionsCommand(program2) {
|
|
|
22586
22956
|
});
|
|
22587
22957
|
}
|
|
22588
22958
|
|
|
22589
|
-
// src/
|
|
22590
|
-
import {
|
|
22959
|
+
// src/cli/portal.ts
|
|
22960
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
22961
|
+
import path61 from "path";
|
|
22962
|
+
|
|
22963
|
+
// src/portal/engine-api.ts
|
|
22964
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
|
|
22965
|
+
import path58 from "path";
|
|
22966
|
+
|
|
22967
|
+
// src/portal/api/boundary.ts
|
|
22968
|
+
var STRUCTURAL_RELATIONS = ["calls", "uses", "extends", "implements"];
|
|
22969
|
+
function isAnyStructuralRelationAllowed(graph, sourceType, targetType) {
|
|
22970
|
+
const typeConfig = graph.architecture?.node_types?.[sourceType];
|
|
22971
|
+
if (!typeConfig) return true;
|
|
22972
|
+
const lists = typeConfig.relations;
|
|
22973
|
+
const def = typeConfig.relationDefault ?? "allow";
|
|
22974
|
+
for (const relType of STRUCTURAL_RELATIONS) {
|
|
22975
|
+
const allowed = lists?.[relType];
|
|
22976
|
+
if (allowed !== void 0) {
|
|
22977
|
+
if (allowed.includes("*") || allowed.includes(targetType)) return true;
|
|
22978
|
+
} else if (def === "allow") {
|
|
22979
|
+
return true;
|
|
22980
|
+
}
|
|
22981
|
+
}
|
|
22982
|
+
return false;
|
|
22983
|
+
}
|
|
22984
|
+
function isLineage(a, b) {
|
|
22985
|
+
return a === b || b.startsWith(a + "/") || a.startsWith(b + "/");
|
|
22986
|
+
}
|
|
22987
|
+
async function computePortalBoundary(graph, projectRoot) {
|
|
22988
|
+
let pass;
|
|
22989
|
+
try {
|
|
22990
|
+
pass = await runRelationPass(graph, projectRoot, {
|
|
22991
|
+
extractorFor: extractorForLanguage,
|
|
22992
|
+
resolvePathToFile: makeResolvePathToFile(projectRoot, buildOwnerIndex(graph.nodes).ownerOf),
|
|
22993
|
+
symbolIndexDir: astCacheDir(graph.rootPath)
|
|
22994
|
+
});
|
|
22995
|
+
} catch {
|
|
22996
|
+
return null;
|
|
22997
|
+
}
|
|
22998
|
+
const phantom = [];
|
|
22999
|
+
const declaredOnly = [];
|
|
23000
|
+
const forbiddenType = [];
|
|
23001
|
+
for (const [nodeId, nv] of pass.violationsByNode) {
|
|
23002
|
+
if (nv.verdict !== "refused") continue;
|
|
23003
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23004
|
+
for (const v of nv.violations) {
|
|
23005
|
+
if (seen.has(v.ownerNode)) continue;
|
|
23006
|
+
seen.add(v.ownerNode);
|
|
23007
|
+
phantom.push({ source: nodeId, target: v.ownerNode });
|
|
23008
|
+
}
|
|
23009
|
+
}
|
|
23010
|
+
const detected = pass.detectedEdgesByNode;
|
|
23011
|
+
for (const [nodeId, node] of graph.nodes) {
|
|
23012
|
+
const sourceType = node.meta.type;
|
|
23013
|
+
const detectedTargets = detected.get(nodeId) ?? /* @__PURE__ */ new Set();
|
|
23014
|
+
for (const rel of node.meta.relations ?? []) {
|
|
23015
|
+
if (!STRUCTURAL_RELATIONS.includes(rel.type)) continue;
|
|
23016
|
+
if (isLineage(nodeId, rel.target)) continue;
|
|
23017
|
+
if (!graph.nodes.has(rel.target)) continue;
|
|
23018
|
+
if (!detectedTargets.has(rel.target)) {
|
|
23019
|
+
declaredOnly.push({ source: nodeId, target: rel.target });
|
|
23020
|
+
}
|
|
23021
|
+
}
|
|
23022
|
+
for (const target of detectedTargets) {
|
|
23023
|
+
const targetNode = graph.nodes.get(target);
|
|
23024
|
+
if (!targetNode) continue;
|
|
23025
|
+
if (!isAnyStructuralRelationAllowed(graph, sourceType, targetNode.meta.type)) {
|
|
23026
|
+
forbiddenType.push({ source: nodeId, target });
|
|
23027
|
+
}
|
|
23028
|
+
}
|
|
23029
|
+
}
|
|
23030
|
+
return { phantom, declaredOnly, forbiddenType };
|
|
23031
|
+
}
|
|
23032
|
+
|
|
23033
|
+
// src/portal/engine-api.ts
|
|
23034
|
+
var PORTAL_SCHEMA_SUPPORTED = CLI_SUPPORTED_SCHEMA;
|
|
23035
|
+
async function loadPortalGraph(projectRoot) {
|
|
23036
|
+
return loadGraphOrAbort(projectRoot, {
|
|
23037
|
+
tolerateInvalidConfig: true,
|
|
23038
|
+
noSecrets: true
|
|
23039
|
+
});
|
|
23040
|
+
}
|
|
23041
|
+
async function walkPortalFiles(projectRoot) {
|
|
23042
|
+
return walkRepoFiles(projectRoot);
|
|
23043
|
+
}
|
|
23044
|
+
async function runPortalCheck(graph, gitFiles) {
|
|
23045
|
+
return runCheck(graph, gitFiles);
|
|
23046
|
+
}
|
|
23047
|
+
function readAndVerifyLock(graph) {
|
|
23048
|
+
const lock = readLock(graph.rootPath);
|
|
23049
|
+
return { lock, verification: verifyLock(graph, lock) };
|
|
23050
|
+
}
|
|
23051
|
+
async function computePortalPairs(graph) {
|
|
23052
|
+
return computeExpectedPairs(graph);
|
|
23053
|
+
}
|
|
23054
|
+
function scanPortalUncovered(graph, gitFiles) {
|
|
23055
|
+
return scanUncoveredFiles(graph, gitFiles);
|
|
23056
|
+
}
|
|
23057
|
+
async function readNodeLog(projectRoot, nodePath) {
|
|
23058
|
+
return readLogContent(projectRoot, nodePath);
|
|
23059
|
+
}
|
|
23060
|
+
function groupPortalIssues(issues) {
|
|
23061
|
+
return groupIssues(issues);
|
|
23062
|
+
}
|
|
23063
|
+
async function computePortalBoundary2(graph, projectRoot) {
|
|
23064
|
+
return computePortalBoundary(graph, projectRoot);
|
|
23065
|
+
}
|
|
23066
|
+
async function scanPortalSuppressions2(graph, projectRoot, gitFiles) {
|
|
23067
|
+
const knownAspectIds = new Set(graph.aspects.map((a) => a.id));
|
|
23068
|
+
const draftAspectIds = new Set(
|
|
23069
|
+
graph.aspects.filter((a) => (a.status ?? "enforced") === "draft").map((a) => a.id)
|
|
23070
|
+
);
|
|
23071
|
+
const report2 = await runSuppressionsScan(projectRoot, gitFiles, knownAspectIds);
|
|
23072
|
+
return scanPortalSuppressions(report2, knownAspectIds, draftAspectIds);
|
|
23073
|
+
}
|
|
23074
|
+
function computePortalLockHash(graph) {
|
|
23075
|
+
return committedLockContentHash(graph.rootPath);
|
|
23076
|
+
}
|
|
23077
|
+
function readGitCommitRef(projectRoot) {
|
|
23078
|
+
const gitDir = path58.join(projectRoot, ".git");
|
|
23079
|
+
const headFile = path58.join(gitDir, "HEAD");
|
|
23080
|
+
if (!existsSync11(headFile)) return null;
|
|
23081
|
+
let head;
|
|
23082
|
+
try {
|
|
23083
|
+
head = readFileSync10(headFile, "utf-8").trim();
|
|
23084
|
+
} catch {
|
|
23085
|
+
return null;
|
|
23086
|
+
}
|
|
23087
|
+
if (/^[0-9a-f]{40}$/i.test(head)) return head;
|
|
23088
|
+
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
23089
|
+
if (!refMatch) return null;
|
|
23090
|
+
const refName = refMatch[1].trim();
|
|
23091
|
+
const looseRef = path58.join(gitDir, refName);
|
|
23092
|
+
if (existsSync11(looseRef)) {
|
|
23093
|
+
try {
|
|
23094
|
+
const sha = readFileSync10(looseRef, "utf-8").trim();
|
|
23095
|
+
if (/^[0-9a-f]{40}$/i.test(sha)) return sha;
|
|
23096
|
+
} catch {
|
|
23097
|
+
}
|
|
23098
|
+
}
|
|
23099
|
+
const packed = path58.join(gitDir, "packed-refs");
|
|
23100
|
+
if (existsSync11(packed)) {
|
|
23101
|
+
try {
|
|
23102
|
+
const lines = readFileSync10(packed, "utf-8").split("\n");
|
|
23103
|
+
for (const line of lines) {
|
|
23104
|
+
const m = line.match(/^([0-9a-f]{40})\s+(.+)$/i);
|
|
23105
|
+
if (m && m[2].trim() === refName) return m[1];
|
|
23106
|
+
}
|
|
23107
|
+
} catch {
|
|
23108
|
+
return null;
|
|
23109
|
+
}
|
|
23110
|
+
}
|
|
23111
|
+
return null;
|
|
23112
|
+
}
|
|
23113
|
+
async function computePortalFreshness(graph, lock) {
|
|
23114
|
+
const out = [];
|
|
23115
|
+
for (const nodePath of graph.nodes.keys()) {
|
|
23116
|
+
const stored = lock.nodes[nodePath]?.source;
|
|
23117
|
+
if (stored === void 0) {
|
|
23118
|
+
out.push({ nodePath, sourceChanged: false });
|
|
23119
|
+
continue;
|
|
23120
|
+
}
|
|
23121
|
+
let fingerprint;
|
|
23122
|
+
try {
|
|
23123
|
+
fingerprint = await computeSourceFingerprint(graph, nodePath);
|
|
23124
|
+
} catch {
|
|
23125
|
+
out.push({ nodePath, sourceChanged: true });
|
|
23126
|
+
continue;
|
|
23127
|
+
}
|
|
23128
|
+
if (fingerprint === void 0) {
|
|
23129
|
+
out.push({ nodePath, sourceChanged: false });
|
|
23130
|
+
continue;
|
|
23131
|
+
}
|
|
23132
|
+
out.push({ nodePath, sourceChanged: fingerprint !== stored });
|
|
23133
|
+
}
|
|
23134
|
+
return out;
|
|
23135
|
+
}
|
|
23136
|
+
|
|
23137
|
+
// src/portal/derive-node-state.ts
|
|
23138
|
+
var PAIR_RANK = {
|
|
23139
|
+
refused: 4,
|
|
23140
|
+
unverified: 3,
|
|
23141
|
+
warning: 2,
|
|
23142
|
+
verified: 1,
|
|
23143
|
+
"n/a": 0
|
|
23144
|
+
};
|
|
23145
|
+
function worstPairState(states) {
|
|
23146
|
+
return states.reduce((worst, s) => PAIR_RANK[s] > PAIR_RANK[worst] ? s : worst, "verified");
|
|
23147
|
+
}
|
|
23148
|
+
var STATE_RANK = {
|
|
23149
|
+
refused: 4,
|
|
23150
|
+
unverified: 3,
|
|
23151
|
+
warning: 2,
|
|
23152
|
+
verified: 1,
|
|
23153
|
+
"no-rule": 0
|
|
23154
|
+
};
|
|
23155
|
+
function computeOwnState(checked, effectiveAspects, nodeIssues, fresh) {
|
|
23156
|
+
let worst = checked ? "verified" : "no-rule";
|
|
23157
|
+
if (checked) {
|
|
23158
|
+
for (const ea of effectiveAspects) {
|
|
23159
|
+
if (ea.pairState === "refused" && STATE_RANK["refused"] > STATE_RANK[worst]) worst = "refused";
|
|
23160
|
+
else if (ea.pairState === "unverified" && STATE_RANK["unverified"] > STATE_RANK[worst]) worst = "unverified";
|
|
23161
|
+
else if (ea.pairState === "warning" && STATE_RANK["warning"] > STATE_RANK[worst]) worst = "warning";
|
|
23162
|
+
}
|
|
23163
|
+
const hasWarning = nodeIssues.some((i) => i.severity === "warning");
|
|
23164
|
+
if (hasWarning && STATE_RANK["warning"] > STATE_RANK[worst]) worst = "warning";
|
|
23165
|
+
}
|
|
23166
|
+
if (fresh && STATE_RANK["unverified"] > STATE_RANK[worst]) worst = "unverified";
|
|
23167
|
+
return worst;
|
|
23168
|
+
}
|
|
23169
|
+
function computeRollup(node, built) {
|
|
23170
|
+
let worst = built.get(node.path).state;
|
|
23171
|
+
for (const child of node.children) {
|
|
23172
|
+
const childRollup = built.get(child.path)?.rollupState ?? "no-rule";
|
|
23173
|
+
if (STATE_RANK[childRollup] > STATE_RANK[worst]) worst = childRollup;
|
|
23174
|
+
}
|
|
23175
|
+
return worst;
|
|
23176
|
+
}
|
|
23177
|
+
function collectNodeSuppressions(mapping, suppressions) {
|
|
23178
|
+
const out = [];
|
|
23179
|
+
const owned = new Set(mapping);
|
|
23180
|
+
for (const file of owned) {
|
|
23181
|
+
const hits = suppressions.byFile.get(file);
|
|
23182
|
+
if (hits) out.push(...hits);
|
|
23183
|
+
}
|
|
23184
|
+
return out;
|
|
23185
|
+
}
|
|
23186
|
+
function computeNotApplicable(node, graph, effectiveIds) {
|
|
23187
|
+
const attachedIds = /* @__PURE__ */ new Set();
|
|
23188
|
+
for (const id of node.meta.aspects ?? []) attachedIds.add(id);
|
|
23189
|
+
const typeDef = graph.architecture?.node_types?.[node.meta.type];
|
|
23190
|
+
for (const id of typeDef?.aspects ?? []) attachedIds.add(id);
|
|
23191
|
+
const out = [];
|
|
23192
|
+
for (const id of [...attachedIds].sort()) {
|
|
23193
|
+
if (effectiveIds.has(id)) continue;
|
|
23194
|
+
out.push({ aspectId: id, why: "filtered out by a `when` predicate on this node" });
|
|
23195
|
+
}
|
|
23196
|
+
return out;
|
|
23197
|
+
}
|
|
23198
|
+
|
|
23199
|
+
// src/portal/derive-nodes.ts
|
|
23200
|
+
function displayPairState(rawKind, status) {
|
|
23201
|
+
if (rawKind === "verified") return "verified";
|
|
23202
|
+
if (rawKind === "refused") return status === "advisory" ? "warning" : "refused";
|
|
23203
|
+
return "unverified";
|
|
23204
|
+
}
|
|
23205
|
+
function buildPortalNodes(graph, _lock, verification, check, logContents, suppressions, freshByNode = /* @__PURE__ */ new Map()) {
|
|
23206
|
+
const pairsByNode = indexPairsByNode(verification.pairs);
|
|
23207
|
+
const issuesByNode = indexIssuesByNode(check.issues);
|
|
23208
|
+
const relationsIn = invertRelations(graph);
|
|
23209
|
+
const built = /* @__PURE__ */ new Map();
|
|
23210
|
+
for (const [path62, node] of graph.nodes) {
|
|
23211
|
+
built.set(
|
|
23212
|
+
path62,
|
|
23213
|
+
buildOne(
|
|
23214
|
+
node,
|
|
23215
|
+
graph,
|
|
23216
|
+
pairsByNode,
|
|
23217
|
+
issuesByNode,
|
|
23218
|
+
relationsIn,
|
|
23219
|
+
logContents,
|
|
23220
|
+
suppressions,
|
|
23221
|
+
freshByNode.get(path62) === true
|
|
23222
|
+
)
|
|
23223
|
+
);
|
|
23224
|
+
}
|
|
23225
|
+
for (const [path62, node] of graph.nodes) {
|
|
23226
|
+
const portal = built.get(path62);
|
|
23227
|
+
portal.rollupState = computeRollup(node, built);
|
|
23228
|
+
}
|
|
23229
|
+
return [...built.values()];
|
|
23230
|
+
}
|
|
23231
|
+
function indexPairsByNode(pairs) {
|
|
23232
|
+
const out = /* @__PURE__ */ new Map();
|
|
23233
|
+
for (const vp of pairs) {
|
|
23234
|
+
const np = vp.pair.nodePath;
|
|
23235
|
+
let byAspect = out.get(np);
|
|
23236
|
+
if (!byAspect) {
|
|
23237
|
+
byAspect = /* @__PURE__ */ new Map();
|
|
23238
|
+
out.set(np, byAspect);
|
|
23239
|
+
}
|
|
23240
|
+
const list = byAspect.get(vp.pair.aspectId) ?? [];
|
|
23241
|
+
list.push(toPairRecord(vp));
|
|
23242
|
+
byAspect.set(vp.pair.aspectId, list);
|
|
23243
|
+
}
|
|
23244
|
+
return out;
|
|
23245
|
+
}
|
|
23246
|
+
function toPairRecord(vp) {
|
|
23247
|
+
const subjectFiles = vp.pair.subjectFiles ?? [];
|
|
23248
|
+
const state = displayPairState(vp.state.kind, vp.pair.status);
|
|
23249
|
+
if (vp.state.kind === "refused") {
|
|
23250
|
+
return { state, subjectFiles, reason: vp.state.reason };
|
|
23251
|
+
}
|
|
23252
|
+
return { state, subjectFiles };
|
|
23253
|
+
}
|
|
23254
|
+
function indexIssuesByNode(issues) {
|
|
23255
|
+
const out = /* @__PURE__ */ new Map();
|
|
23256
|
+
for (const i of issues) {
|
|
23257
|
+
if (!i.nodePath) continue;
|
|
23258
|
+
const list = out.get(i.nodePath) ?? [];
|
|
23259
|
+
list.push(i);
|
|
23260
|
+
out.set(i.nodePath, list);
|
|
23261
|
+
}
|
|
23262
|
+
return out;
|
|
23263
|
+
}
|
|
23264
|
+
function invertRelations(graph) {
|
|
23265
|
+
const out = /* @__PURE__ */ new Map();
|
|
23266
|
+
for (const [path62, node] of graph.nodes) {
|
|
23267
|
+
for (const rel of node.meta.relations ?? []) {
|
|
23268
|
+
const list = out.get(rel.target) ?? [];
|
|
23269
|
+
list.push({ source: path62, type: rel.type });
|
|
23270
|
+
out.set(rel.target, list);
|
|
23271
|
+
}
|
|
23272
|
+
}
|
|
23273
|
+
return out;
|
|
23274
|
+
}
|
|
23275
|
+
function buildOne(node, graph, pairsByNode, issuesByNode, relationsIn, logContents, suppressions, fresh) {
|
|
23276
|
+
const path62 = node.path;
|
|
23277
|
+
const mapping = node.meta.mapping ?? [];
|
|
23278
|
+
const effectiveIds = computeEffectiveAspects(node, graph);
|
|
23279
|
+
const statuses = computeEffectiveAspectStatuses(node, graph);
|
|
23280
|
+
const nodePairs = pairsByNode.get(path62);
|
|
23281
|
+
const effectiveAspects = [];
|
|
23282
|
+
for (const aspectId of [...effectiveIds].sort()) {
|
|
23283
|
+
effectiveAspects.push(
|
|
23284
|
+
buildEffectiveAspect(aspectId, node, graph, statuses, nodePairs)
|
|
23285
|
+
);
|
|
23286
|
+
}
|
|
23287
|
+
const checked = effectiveAspects.some((ea) => ea.pairState !== "n/a");
|
|
23288
|
+
const ownState = computeOwnState(
|
|
23289
|
+
checked,
|
|
23290
|
+
effectiveAspects,
|
|
23291
|
+
issuesByNode.get(path62) ?? [],
|
|
23292
|
+
fresh
|
|
23293
|
+
);
|
|
23294
|
+
const relationsOut = (node.meta.relations ?? []).map((r) => ({
|
|
23295
|
+
target: r.target,
|
|
23296
|
+
type: r.type,
|
|
23297
|
+
...r.consumes ? { consumes: r.consumes } : {}
|
|
23298
|
+
}));
|
|
23299
|
+
const log2 = parseLog(logContents.get(path62) ?? "").map((e) => ({
|
|
23300
|
+
when: e.datetime,
|
|
23301
|
+
body: e.body
|
|
23302
|
+
}));
|
|
23303
|
+
const nodeSuppressions = collectNodeSuppressions(mapping, suppressions);
|
|
23304
|
+
const notApplicable = computeNotApplicable(node, graph, effectiveIds);
|
|
23305
|
+
return {
|
|
23306
|
+
path: path62,
|
|
23307
|
+
name: node.meta.name,
|
|
23308
|
+
type: node.meta.type,
|
|
23309
|
+
...node.meta.description ? { description: node.meta.description } : {},
|
|
23310
|
+
parent: node.parent ? node.parent.path : null,
|
|
23311
|
+
mapping,
|
|
23312
|
+
sourceFileCount: mapping.length,
|
|
23313
|
+
isTest: node.meta.type === "test-suite",
|
|
23314
|
+
checked,
|
|
23315
|
+
fresh,
|
|
23316
|
+
state: ownState,
|
|
23317
|
+
// Provisional — overwritten by the bottom-up roll-up pass; seeded to own state.
|
|
23318
|
+
rollupState: ownState,
|
|
23319
|
+
effectiveAspects,
|
|
23320
|
+
notApplicable,
|
|
23321
|
+
relationsOut,
|
|
23322
|
+
relationsIn: relationsIn.get(path62) ?? [],
|
|
23323
|
+
suppressions: nodeSuppressions,
|
|
23324
|
+
log: log2
|
|
23325
|
+
};
|
|
23326
|
+
}
|
|
23327
|
+
function buildEffectiveAspect(aspectId, node, graph, statuses, nodePairs) {
|
|
23328
|
+
const def = graph.aspects.find((a) => a.id === aspectId);
|
|
23329
|
+
const aggregate = isAggregateAspect(graph, aspectId);
|
|
23330
|
+
const kind = aggregate ? "aggregate" : def?.reviewer.type === "deterministic" ? "deterministic" : "llm";
|
|
23331
|
+
const sources = getAspectStatusSources(node, aspectId, graph);
|
|
23332
|
+
const primary = sources.length > 0 ? sources.reduce((a, b) => a.channel <= b.channel ? a : b) : void 0;
|
|
23333
|
+
const channel = primary?.channel ?? 7;
|
|
23334
|
+
const origin = primary?.origin ?? impliedOrigin(graph, aspectId);
|
|
23335
|
+
const status = statuses.get(aspectId) ?? "enforced";
|
|
23336
|
+
let tier;
|
|
23337
|
+
let consensus;
|
|
23338
|
+
let cost = "free";
|
|
23339
|
+
if (kind === "llm" && def) {
|
|
23340
|
+
cost = "billed";
|
|
23341
|
+
const reviewer = graph.config?.reviewer;
|
|
23342
|
+
if (reviewer) {
|
|
23343
|
+
const sel = selectTierForAspect(def, reviewer);
|
|
23344
|
+
if (sel.ok) {
|
|
23345
|
+
tier = sel.tierName;
|
|
23346
|
+
consensus = sel.tier.consensus;
|
|
23347
|
+
}
|
|
23348
|
+
}
|
|
23349
|
+
}
|
|
23350
|
+
const records = nodePairs?.get(aspectId);
|
|
23351
|
+
const pairState = aggregate || status === "draft" || !records || records.length === 0 ? "n/a" : worstPairState(records.map((r) => r.state));
|
|
23352
|
+
let foldedInputs;
|
|
23353
|
+
let reason;
|
|
23354
|
+
if (!aggregate && status !== "draft" && records && records.length > 0) {
|
|
23355
|
+
if (pairState === "verified") {
|
|
23356
|
+
const files = /* @__PURE__ */ new Set();
|
|
23357
|
+
for (const r of records) for (const f of r.subjectFiles) files.add(f);
|
|
23358
|
+
const sorted = [...files].sort();
|
|
23359
|
+
if (sorted.length > 0) foldedInputs = sorted;
|
|
23360
|
+
} else if (pairState === "refused" || pairState === "warning") {
|
|
23361
|
+
reason = records.find((r) => r.reason)?.reason;
|
|
23362
|
+
}
|
|
23363
|
+
}
|
|
23364
|
+
return {
|
|
23365
|
+
aspectId,
|
|
23366
|
+
kind,
|
|
23367
|
+
...tier !== void 0 ? { tier } : {},
|
|
23368
|
+
...consensus !== void 0 ? { consensus } : {},
|
|
23369
|
+
cost,
|
|
23370
|
+
status,
|
|
23371
|
+
channel,
|
|
23372
|
+
origin,
|
|
23373
|
+
pairState,
|
|
23374
|
+
...reason !== void 0 ? { reason } : {},
|
|
23375
|
+
...foldedInputs !== void 0 ? { foldedInputs } : {}
|
|
23376
|
+
};
|
|
23377
|
+
}
|
|
23378
|
+
function impliedOrigin(graph, aspectId) {
|
|
23379
|
+
for (const other of graph.aspects) {
|
|
23380
|
+
if (other.implies?.includes(aspectId)) return `implied:${other.id}`;
|
|
23381
|
+
}
|
|
23382
|
+
return "unknown";
|
|
23383
|
+
}
|
|
23384
|
+
|
|
23385
|
+
// src/portal/derive-catalogue.ts
|
|
23386
|
+
function buildAspects(graph, pairs) {
|
|
23387
|
+
const byAspect = /* @__PURE__ */ new Map();
|
|
23388
|
+
for (const vp of pairs) {
|
|
23389
|
+
const list = byAspect.get(vp.pair.aspectId) ?? [];
|
|
23390
|
+
list.push(collapsePairState(vp));
|
|
23391
|
+
byAspect.set(vp.pair.aspectId, list);
|
|
23392
|
+
}
|
|
23393
|
+
return graph.aspects.map((def) => buildAspect(def, graph, byAspect.get(def.id) ?? [])).sort((a, b) => a.id.localeCompare(b.id, "en"));
|
|
23394
|
+
}
|
|
23395
|
+
function collapsePairState(vp) {
|
|
23396
|
+
return displayPairState(vp.state.kind, vp.pair.status);
|
|
23397
|
+
}
|
|
23398
|
+
function buildAspect(def, graph, states) {
|
|
23399
|
+
const kind = def.reviewer.type;
|
|
23400
|
+
const status = def.status ?? "enforced";
|
|
23401
|
+
const scope = def.scope?.per === "file" ? "file" : "node";
|
|
23402
|
+
const ruleProse = def.artifacts.find((a) => a.filename === "content.md")?.content;
|
|
23403
|
+
const checkSource = def.artifacts.find((a) => a.filename === "check.mjs")?.content;
|
|
23404
|
+
return {
|
|
23405
|
+
id: def.id,
|
|
23406
|
+
name: def.name,
|
|
23407
|
+
kind,
|
|
23408
|
+
status,
|
|
23409
|
+
scope,
|
|
23410
|
+
hasWhen: def.when !== void 0,
|
|
23411
|
+
implies: def.implies ?? [],
|
|
23412
|
+
// Optional content fields assigned directly — an undefined value is omitted by the
|
|
23413
|
+
// JSON.stringify embed, so no conditional spread is needed (and none is left half-covered).
|
|
23414
|
+
description: def.description,
|
|
23415
|
+
ruleProse,
|
|
23416
|
+
checkSource,
|
|
23417
|
+
tally: buildTally(def, graph, status, states)
|
|
23418
|
+
};
|
|
23419
|
+
}
|
|
23420
|
+
function buildTally(def, graph, status, states) {
|
|
23421
|
+
if (def.reviewer.type === "aggregate") {
|
|
23422
|
+
return { render: "aggregate" };
|
|
23423
|
+
}
|
|
23424
|
+
if (states.length === 0) {
|
|
23425
|
+
return { render: "vacuous", reason: vacuousReason(def, graph, status) };
|
|
23426
|
+
}
|
|
23427
|
+
let verified = 0;
|
|
23428
|
+
let refused = 0;
|
|
23429
|
+
let warning = 0;
|
|
23430
|
+
let unverified = 0;
|
|
23431
|
+
for (const s of states) {
|
|
23432
|
+
if (s === "verified") verified += 1;
|
|
23433
|
+
else if (s === "refused") refused += 1;
|
|
23434
|
+
else if (s === "warning") warning += 1;
|
|
23435
|
+
else unverified += 1;
|
|
23436
|
+
}
|
|
23437
|
+
return { render: "normal", verified, refused, warning, unverified, units: states.length };
|
|
23438
|
+
}
|
|
23439
|
+
function vacuousReason(def, graph, status) {
|
|
23440
|
+
if ((status ?? "enforced") === "draft") {
|
|
23441
|
+
return "draft \u2014 produces no expected pairs (parked, not yet enforced)";
|
|
23442
|
+
}
|
|
23443
|
+
const reachesANode = [...graph.nodes.values()].some(
|
|
23444
|
+
(n) => computeEffectiveAspects(n, graph).has(def.id)
|
|
23445
|
+
);
|
|
23446
|
+
if (!reachesANode) {
|
|
23447
|
+
return "not effective on any node \u2014 no attach channel reaches a node";
|
|
23448
|
+
}
|
|
23449
|
+
return "effective on a node but every subject set is empty (scope/when excludes all files, or no mapped source)";
|
|
23450
|
+
}
|
|
23451
|
+
function buildFlows(graph, nodeStateOf) {
|
|
23452
|
+
return graph.flows.map((flow) => buildFlow(flow, graph, nodeStateOf)).sort((a, b) => a.name.localeCompare(b.name, "en"));
|
|
23453
|
+
}
|
|
23454
|
+
function buildFlow(flow, graph, nodeStateOf) {
|
|
23455
|
+
const participants = expandParticipants(flow, graph);
|
|
23456
|
+
const state = computeFlowState(participants, graph, nodeStateOf);
|
|
23457
|
+
return {
|
|
23458
|
+
name: flow.name,
|
|
23459
|
+
...flow.description ? { description: flow.description } : {},
|
|
23460
|
+
participants,
|
|
23461
|
+
aspects: flow.aspects ?? [],
|
|
23462
|
+
state
|
|
23463
|
+
};
|
|
23464
|
+
}
|
|
23465
|
+
function expandParticipants(flow, graph) {
|
|
23466
|
+
const out = /* @__PURE__ */ new Set();
|
|
23467
|
+
for (const path62 of flow.nodes) {
|
|
23468
|
+
out.add(path62);
|
|
23469
|
+
const node = graph.nodes.get(path62);
|
|
23470
|
+
if (node) {
|
|
23471
|
+
for (const d of collectDescendants(node)) out.add(d.path);
|
|
23472
|
+
}
|
|
23473
|
+
}
|
|
23474
|
+
return [...out].sort((a, b) => a.localeCompare(b, "en"));
|
|
23475
|
+
}
|
|
23476
|
+
function computeFlowState(participants, graph, nodeStateOf) {
|
|
23477
|
+
let anyChecked = false;
|
|
23478
|
+
let anyAttention = false;
|
|
23479
|
+
for (const path62 of participants) {
|
|
23480
|
+
const node = graph.nodes.get(path62);
|
|
23481
|
+
if (!node) continue;
|
|
23482
|
+
if (!hasNonDraftEffectiveAspects(node, graph)) continue;
|
|
23483
|
+
anyChecked = true;
|
|
23484
|
+
const states = nodeStateOf(path62) ?? [];
|
|
23485
|
+
if (states.some((s) => s === "refused" || s === "unverified")) anyAttention = true;
|
|
23486
|
+
}
|
|
23487
|
+
if (!anyChecked) return "nothing-checked";
|
|
23488
|
+
return anyAttention ? "attention" : "verified";
|
|
23489
|
+
}
|
|
23490
|
+
function buildTypes(graph) {
|
|
23491
|
+
const countByType = /* @__PURE__ */ new Map();
|
|
23492
|
+
for (const node of graph.nodes.values()) {
|
|
23493
|
+
countByType.set(node.meta.type, (countByType.get(node.meta.type) ?? 0) + 1);
|
|
23494
|
+
}
|
|
23495
|
+
const types = [];
|
|
23496
|
+
for (const [id, def] of Object.entries(graph.architecture?.node_types ?? {})) {
|
|
23497
|
+
const allowedRelations = {};
|
|
23498
|
+
for (const [relType, targets] of Object.entries(def.relations ?? {})) {
|
|
23499
|
+
allowedRelations[relType] = [...targets ?? []];
|
|
23500
|
+
}
|
|
23501
|
+
types.push({
|
|
23502
|
+
id,
|
|
23503
|
+
...def.description ? { description: def.description } : {},
|
|
23504
|
+
parents: def.parents ?? [],
|
|
23505
|
+
allowedRelations,
|
|
23506
|
+
defaultAspects: def.aspects ?? [],
|
|
23507
|
+
strict: def.enforce === "strict",
|
|
23508
|
+
logRequired: def.log_required === true,
|
|
23509
|
+
nodeCount: countByType.get(id) ?? 0
|
|
23510
|
+
});
|
|
23511
|
+
}
|
|
23512
|
+
return types.sort((a, b) => a.id.localeCompare(b.id, "en"));
|
|
23513
|
+
}
|
|
23514
|
+
|
|
23515
|
+
// src/portal/derive-rest.ts
|
|
23516
|
+
function buildBoundary(input) {
|
|
23517
|
+
if (input === null) {
|
|
23518
|
+
return { phantom: [], declaredOnly: [], forbiddenType: [], unknown: true };
|
|
23519
|
+
}
|
|
23520
|
+
return {
|
|
23521
|
+
phantom: dedupeEdges(input.phantom),
|
|
23522
|
+
declaredOnly: dedupeEdges(input.declaredOnly),
|
|
23523
|
+
forbiddenType: dedupeEdges(input.forbiddenType),
|
|
23524
|
+
unknown: false
|
|
23525
|
+
};
|
|
23526
|
+
}
|
|
23527
|
+
function dedupeEdges(edges) {
|
|
23528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23529
|
+
const out = [];
|
|
23530
|
+
for (const e of edges) {
|
|
23531
|
+
const key = `${e.source}\0${e.target}`;
|
|
23532
|
+
if (seen.has(key)) continue;
|
|
23533
|
+
seen.add(key);
|
|
23534
|
+
out.push(e);
|
|
23535
|
+
}
|
|
23536
|
+
return out.sort((a, b) => a.source.localeCompare(b.source, "en") || a.target.localeCompare(b.target, "en"));
|
|
23537
|
+
}
|
|
23538
|
+
function buildSuppressions(markers) {
|
|
23539
|
+
return markers.map((m) => ({
|
|
23540
|
+
aspectId: m.aspectId,
|
|
23541
|
+
file: m.file,
|
|
23542
|
+
line: m.line,
|
|
23543
|
+
reason: m.reason,
|
|
23544
|
+
...m.risk ? { risk: m.risk } : {}
|
|
23545
|
+
})).sort((a, b) => a.file.localeCompare(b.file, "en") || a.line - b.line);
|
|
23546
|
+
}
|
|
23547
|
+
function buildHubs(nodes) {
|
|
23548
|
+
const fanOut = [];
|
|
23549
|
+
const fanIn = [];
|
|
23550
|
+
for (const n of nodes) {
|
|
23551
|
+
if (n.relationsOut.length > 0) fanOut.push({ path: n.path, count: n.relationsOut.length });
|
|
23552
|
+
if (n.relationsIn.length > 0) fanIn.push({ path: n.path, count: n.relationsIn.length });
|
|
23553
|
+
}
|
|
23554
|
+
return { fanOut: rankHubs(fanOut), fanIn: rankHubs(fanIn) };
|
|
23555
|
+
}
|
|
23556
|
+
function rankHubs(entries) {
|
|
23557
|
+
return entries.sort((a, b) => b.count - a.count || a.path.localeCompare(b.path, "en"));
|
|
23558
|
+
}
|
|
23559
|
+
function buildResidue(nodes, uncoveredFiles) {
|
|
23560
|
+
const noRuleNodes = nodes.filter((n) => n.state === "no-rule" && n.mapping.length > 0).map((n) => n.path).sort((a, b) => a.localeCompare(b, "en"));
|
|
23561
|
+
return {
|
|
23562
|
+
noRuleNodes,
|
|
23563
|
+
uncoveredFiles: [...uncoveredFiles].sort((a, b) => a.localeCompare(b, "en"))
|
|
23564
|
+
};
|
|
23565
|
+
}
|
|
23566
|
+
function buildWorklist(check) {
|
|
23567
|
+
const groups = groupPortalIssues(check.issues);
|
|
23568
|
+
return groups.map((g) => ({
|
|
23569
|
+
rule: g.label,
|
|
23570
|
+
severity: g.severity,
|
|
23571
|
+
why: g.sharedWhy,
|
|
23572
|
+
fix: g.sharedNext,
|
|
23573
|
+
nodes: [...new Set(g.members.map((m) => m.nodePath ?? "").filter((p2) => p2.length > 0))].sort(
|
|
23574
|
+
(a, b) => a.localeCompare(b, "en")
|
|
23575
|
+
)
|
|
23576
|
+
}));
|
|
23577
|
+
}
|
|
23578
|
+
|
|
23579
|
+
// src/portal/extract.ts
|
|
23580
|
+
async function extractPortalData(projectRoot, opts) {
|
|
23581
|
+
const graph = await loadPortalGraph(projectRoot);
|
|
23582
|
+
const gitFiles = await walkPortalFiles(projectRoot);
|
|
23583
|
+
const checkResult = await runPortalCheck(graph, gitFiles);
|
|
23584
|
+
const { lock, verification: verificationPromise } = readAndVerifyLock(graph);
|
|
23585
|
+
const verification = await verificationPromise;
|
|
23586
|
+
const expected = await computePortalPairs(graph);
|
|
23587
|
+
const counts = buildCounts(graph, checkResult, verification.pairs, expected.pairs);
|
|
23588
|
+
const logContents = /* @__PURE__ */ new Map();
|
|
23589
|
+
for (const nodePath of graph.nodes.keys()) {
|
|
23590
|
+
logContents.set(nodePath, await readNodeLog(projectRoot, nodePath));
|
|
23591
|
+
}
|
|
23592
|
+
const suppressionMarkers = await scanPortalSuppressions2(graph, projectRoot, gitFiles);
|
|
23593
|
+
const flatSuppressions = buildSuppressions(suppressionMarkers);
|
|
23594
|
+
const suppressions = indexSuppressionsByFile(flatSuppressions);
|
|
23595
|
+
const freshnessMarkers = await computePortalFreshness(graph, lock);
|
|
23596
|
+
const freshByNode = /* @__PURE__ */ new Map();
|
|
23597
|
+
for (const m of freshnessMarkers) freshByNode.set(m.nodePath, m.sourceChanged);
|
|
23598
|
+
const nodes = buildPortalNodes(graph, lock, verification, checkResult, logContents, suppressions, freshByNode);
|
|
23599
|
+
const pairStatesByNode = /* @__PURE__ */ new Map();
|
|
23600
|
+
for (const vp of verification.pairs) {
|
|
23601
|
+
const list = pairStatesByNode.get(vp.pair.nodePath) ?? [];
|
|
23602
|
+
list.push(collapsePairState2(vp));
|
|
23603
|
+
pairStatesByNode.set(vp.pair.nodePath, list);
|
|
23604
|
+
}
|
|
23605
|
+
const aspects = buildAspects(graph, verification.pairs);
|
|
23606
|
+
const flows = buildFlows(graph, (path62) => pairStatesByNode.get(path62));
|
|
23607
|
+
const types = buildTypes(graph);
|
|
23608
|
+
const hubs = buildHubs(nodes);
|
|
23609
|
+
const uncovered = scanPortalUncovered(graph, gitFiles);
|
|
23610
|
+
const residue = buildResidue(nodes, uncovered);
|
|
23611
|
+
const worklist = buildWorklist(checkResult);
|
|
23612
|
+
counts.suppressed = flatSuppressions.length;
|
|
23613
|
+
counts.noRule = residue.noRuleNodes.length;
|
|
23614
|
+
counts.notApplicable = nodes.reduce((sum, n) => sum + n.notApplicable.length, 0);
|
|
23615
|
+
const boundary = buildBoundary(await computePortalBoundary2(graph, projectRoot));
|
|
23616
|
+
const lockHash = computePortalLockHash(graph);
|
|
23617
|
+
const commitRef = readGitCommitRef(projectRoot);
|
|
23618
|
+
const data = {
|
|
23619
|
+
meta: {
|
|
23620
|
+
projectName: checkResult.projectName,
|
|
23621
|
+
generatedAt: "",
|
|
23622
|
+
// stamped below, after generation
|
|
23623
|
+
autoApprove: normalizeAutoApprove(graph.config.auto_approve),
|
|
23624
|
+
writeEnabled: opts.writeEnabled,
|
|
23625
|
+
schemaSupported: PORTAL_SCHEMA_SUPPORTED,
|
|
23626
|
+
lockHash,
|
|
23627
|
+
commitRef,
|
|
23628
|
+
counts
|
|
23629
|
+
},
|
|
23630
|
+
nodes,
|
|
23631
|
+
aspects,
|
|
23632
|
+
flows,
|
|
23633
|
+
types,
|
|
23634
|
+
boundary,
|
|
23635
|
+
suppressions: flatSuppressions,
|
|
23636
|
+
hubs,
|
|
23637
|
+
worklist,
|
|
23638
|
+
residue
|
|
23639
|
+
};
|
|
23640
|
+
data.meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
23641
|
+
return data;
|
|
23642
|
+
}
|
|
23643
|
+
function normalizeAutoApprove(value) {
|
|
23644
|
+
if (value === "deterministic" || value === "full") return value;
|
|
23645
|
+
return "false";
|
|
23646
|
+
}
|
|
23647
|
+
function collapsePairState2(vp) {
|
|
23648
|
+
return displayPairState(vp.state.kind, vp.pair.status);
|
|
23649
|
+
}
|
|
23650
|
+
function indexSuppressionsByFile(flat) {
|
|
23651
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
23652
|
+
for (const s of flat) {
|
|
23653
|
+
const list = byFile.get(s.file) ?? [];
|
|
23654
|
+
list.push(s);
|
|
23655
|
+
byFile.set(s.file, list);
|
|
23656
|
+
}
|
|
23657
|
+
return { byFile };
|
|
23658
|
+
}
|
|
23659
|
+
function buildCounts(graph, check, pairs, expectedPairs) {
|
|
23660
|
+
let verified = 0;
|
|
23661
|
+
let refused = 0;
|
|
23662
|
+
let unverified = 0;
|
|
23663
|
+
let advisoryRefused = 0;
|
|
23664
|
+
for (const vp of pairs) {
|
|
23665
|
+
switch (displayPairState(vp.state.kind, vp.pair.status)) {
|
|
23666
|
+
case "verified":
|
|
23667
|
+
verified += 1;
|
|
23668
|
+
break;
|
|
23669
|
+
case "refused":
|
|
23670
|
+
refused += 1;
|
|
23671
|
+
break;
|
|
23672
|
+
case "warning":
|
|
23673
|
+
advisoryRefused += 1;
|
|
23674
|
+
break;
|
|
23675
|
+
default:
|
|
23676
|
+
unverified += 1;
|
|
23677
|
+
break;
|
|
23678
|
+
}
|
|
23679
|
+
}
|
|
23680
|
+
let pairsLLM = 0;
|
|
23681
|
+
let pairsDet = 0;
|
|
23682
|
+
for (const p2 of expectedPairs) {
|
|
23683
|
+
if (p2.kind === "llm") pairsLLM += 1;
|
|
23684
|
+
else pairsDet += 1;
|
|
23685
|
+
}
|
|
23686
|
+
const errors = check.issues.filter((i) => i.severity === "error").length;
|
|
23687
|
+
const warnings = check.issues.filter((i) => i.severity === "warning").length;
|
|
23688
|
+
return {
|
|
23689
|
+
nodes: graph.nodes.size,
|
|
23690
|
+
aspects: graph.aspects.length,
|
|
23691
|
+
flows: graph.flows.length,
|
|
23692
|
+
pairsTotal: expectedPairs.length,
|
|
23693
|
+
pairsLLM,
|
|
23694
|
+
pairsDet,
|
|
23695
|
+
verified,
|
|
23696
|
+
refused,
|
|
23697
|
+
unverified,
|
|
23698
|
+
advisoryRefused,
|
|
23699
|
+
// The residue-track counts (noRule / notApplicable / suppressed) are NOT part of the
|
|
23700
|
+
// count-parity identity and cannot be computed here — each depends on data derived AFTER
|
|
23701
|
+
// this seam (the built node array, the residue ledger, the suppression inventory). They
|
|
23702
|
+
// are seeded 0 and OVERWRITTEN by the post-pass in extractPortalData once that data exists.
|
|
23703
|
+
// (Never leave them 0: that prints "0 waived / 0 no rule / 0 not applicable" over a list.)
|
|
23704
|
+
noRule: 0,
|
|
23705
|
+
draft: check.draftSkipped,
|
|
23706
|
+
notApplicable: 0,
|
|
23707
|
+
suppressed: 0,
|
|
23708
|
+
uncoveredFiles: check.totalFiles - check.coveredFiles,
|
|
23709
|
+
coveredFiles: check.coveredFiles,
|
|
23710
|
+
totalFiles: check.totalFiles,
|
|
23711
|
+
errors,
|
|
23712
|
+
warnings
|
|
23713
|
+
};
|
|
23714
|
+
}
|
|
23715
|
+
|
|
23716
|
+
// src/portal/serializer.ts
|
|
23717
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
23718
|
+
import path59 from "path";
|
|
22591
23719
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23720
|
+
import { existsSync as existsSync12 } from "fs";
|
|
23721
|
+
var LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400">
|
|
23722
|
+
<defs>
|
|
23723
|
+
<style>
|
|
23724
|
+
.st0 {
|
|
23725
|
+
fill: #f8d794;
|
|
23726
|
+
fill-rule: evenodd;
|
|
23727
|
+
}
|
|
23728
|
+
</style>
|
|
23729
|
+
</defs>
|
|
23730
|
+
<path class="st0" d="M197.03,1.63c-6.22,3.3-11.49,8.23-13.84,12.96-.35.7-.67,1.32-.73,1.37-.05.06-.92-.42-1.92-1.05-2.2-1.39-6.63-3.27-6.93-2.95-.3.32-2.1,5.23-2.66,7.27-.26.93-.52,1.86-.59,2.07-.07.23-.57-.19-1.26-1.07-1.96-2.48-6.02-4.97-9.39-5.77l-.81-.19.15,3.32c.09,1.85.36,3.99.62,4.83.61,1.96.48,2.01-1.31.59-3.35-2.66-8.96-4.65-14-4.96-2.87-.17-8.36.34-8.65.81-.54.87,4.59,9.19,7.48,12.13l2.16,2.2-.95.59-.95.59-2.16-1c-7.44-3.45-16.8-2.91-25.31,1.45l-.97.5,2.4,2.66c1.32,1.46,3.18,3.34,4.13,4.18l1.73,1.52-2.03.52c-2.99.76-10.14,4.5-10.14,5.31,0,.12.52.69,1.16,1.27l1.16,1.04-1.93-.24c-6.1-.75-15.41,1.23-14.43,3.06.9,1.7,3.27,5.33,4.51,6.9,1.66,2.11,1.64,1.96.27,3.3-1.05,1.02-1.26,1.11-3.87,1.46-10.22,1.4-17.81,7.51-21.74,17.49l-.58,1.47,1.75,1.18c.96.65,2.5,1.75,3.41,2.45l1.66,1.27-.86.77c-.47.42-1.76,1.29-2.85,1.93-8.64,5.04-13.74,12.18-16.51,23.1-1.06,4.17-1.49,3.78,4.08,3.59,3.09-.1,4.79-.06,4.79.12,0,1.5-1.8,4.44-3.18,5.18-8.37,4.51-13.05,11.61-14.44,21.93-.5,3.74-.9,3.41,4.05,3.4,6.23-.02,9.25-.98,15.41-4.89.51-.32.85-.19,3.07,1.2,2.12,1.33,4.65,2.55,7.06,3.39.45.16.26.32-1.03.84-5.8,2.36-9.65,7.15-10.93,13.58-.18.91-.4,1.9-.48,2.19-.15.51-.03.53,2,.35,7.76-.66,13.37-5.48,15.94-13.7l.75-2.39,1.13.08c2.87.21,7.55-.86,11.88-2.7l2.36-1,.88.52c.48.29,2.13,1.18,3.66,1.98l2.78,1.46-2.05.35c-8.87,1.5-14.74,6.32-17.22,14.16-1.07,3.36-1.28,3.16,3.38,3.13,9.3-.07,15.85-4.12,20.15-12.47l1.34-2.6,2.16.17c1.19.09,5.01-.01,8.5-.22,3.49-.21,6.38-.35,6.44-.31.47.4-2.74,3.58-4.67,4.64-5.27,2.88-8.24,7.94-8.24,14,0,2.11.05,2.34.49,2.21.27-.08,1.06-.29,1.76-.47,6.44-1.65,10.98-7.78,10.98-14.82,0-1.87,1.14-3.79,3.1-5.2,1.77-1.29,18.19-1.51,21.69-.29,13.13,4.57,22.36,15.41,23.62,27.73.61,5.93-.8,26.41-2.36,34.39l-.71,3.6-3.73,3.99c-6.67,7.13-4.88,6.03-14.01,8.62-2.25.64-5.52,1.6-7.28,2.14-6.1,1.88-5.54,1.85-31.68,1.79l-23.61-.05-3.01,3c-2.89,2.88-3.05,2.99-3.87,2.75-.74-.21-2.74.16-3.05.57-1.15,1.53-1.32,2.87-.53,4.2,1.63,2.76,5.81,1.63,5.87-1.59.02-1.23.1-1.34,2.87-3.93l2.85-2.67h11.74l-9.57,9.58-9.57,9.58h-16.71l-.74-1.27c-2.6-4.43-9.45-2.67-9.45,2.42s7.31,6.94,9.42,2.26l.52-1.16,10.93.18,10.93.18,8.6-8.51c5.11-5.06,8.99-8.67,9.55-8.91.81-.34,4.71-.37,25.76-.22l24.8.17,10.01-9.8,10.01-9.8v-18.88c0-13.87-.09-19.13-.33-19.81-.29-.8-.26-1,.18-1.5,1.36-1.5,3.48.19,2.25,1.8-.35.47-.41,3.3-.41,19.72v19.18l-10.4,10.2-10.4,10.2h-37.46l-.84.71c-.81.68-.96.7-5.16.71-2.38,0-4.47.1-4.64.21-.18.11-5.48,5.31-11.78,11.54l-11.46,11.34h-15.46v13.48l-.63.2c-3.44,1.07-4.42,5.82-1.72,8.34,2.62,2.45,7.22,1.29,8.35-2.11.08-.25,1.99-.36,7.17-.43l7.06-.09-.14,1.25c-.28,2.47,1.16,1.58,8.12-5.04,5.59-5.31,4.28-4.83,13.63-4.96,6.62-.09,8.05-.18,8.96-.58.7-.31,5.48-4.8,13.71-12.88,15.22-14.94,13.17-13.63,21.47-13.75l6.13-.09v7.26l-2.46,2.37c-1.36,1.3-5.09,4.94-8.3,8.08l-5.83,5.71h-1.7c-1.1,0-1.7-.12-1.7-.33,0-.63-1.45-2.22-2.52-2.77-6.37-3.25-12.07,5.01-6.58,9.53,3.28,2.71,9.36.59,9.39-3.27,0-.59.13-.63,1.9-.64h1.9s7.75-7.59,7.75-7.59c4.26-4.17,7.84-7.59,7.96-7.6.12,0,.21,1.28.21,2.86v2.87l-8.8,8.89c-4.84,4.89-9.18,9.11-9.64,9.38-.78.46-2.27.49-20.2.49h-19.36l-5.93,5.66-5.93,5.66-2.1-2-2.11-2h-3.79c-5.14,0-5.35.16-5.4,4.12-.02,1.81-.15,2.8-.36,2.87-4.33,1.44-3.21,8.33,1.35,8.33,4.2,0,5.49-4.98,2.04-7.87-.95-.79-.95-.8-.79-2.71.21-2.55.12-2.48,3.27-2.48h2.76l1.94,1.71c1.38,1.22,2.01,1.99,2.18,2.68.4,1.59,3,1,3-.68,0-.59,10.18-10.37,11.21-10.77.77-.3,3.09-.32,8.03-.07l3.62.18-10.55,10.59c-13.01,13.06-11.47,10.52-11.47,18.91v6.93l-.96.46c-4.59,2.19-2.93,9.51,2.15,9.51s6.98-7.59,2.13-9.6l-.83-.34.09-5.07.09-5.07,11.11-10.94c8.65-8.52,11.33-11,12.11-11.22,1.1-.3,7.35-.32,13.05-.04l3.59.18-4.86,5.01-4.86,5h-4.45c-2.63,0-4.69-.13-5.02-.31-.51-.27-.54-.25-.26.26.17.31.3.94.3,1.39v.83l2.5-.14c1.38-.08,2.98-.05,3.56.06l1.06.2-1.47,1.49-1.47,1.49-2.63-.14c-2.72-.14-4.2.32-4.86,1.5-.12.21-2.28,2.47-4.79,5.01-4.55,4.6-5.02,5.29-4.08,5.99.44.33,1.08-.22,5.86-4.97l5.37-5.33h3.62l-2.95,2.96-2.95,2.96-.09,14.43-.09,14.43-1.24.76c-3.82,2.34-2.99,7.8,1.35,8.98,5.34,1.44,8.41-5.98,3.71-8.95l-1.25-.79v-11.14c0-14.88-1.29-12.04,10.36-22.92l9.5-8.88.09,5.42.09,5.42-3.68,3.52-3.68,3.52v12.53s0,12.53,0,12.53l-1.28.68c-4.77,2.53-2.65,9.77,2.7,9.25,4.91-.47,6.28-7.03,1.94-9.24l-1.1-.56v-12.04s0-12.03,0-12.03l3.66-3.47,3.66-3.47v-14.22l4.14-3.78c2.28-2.08,6.81-6.38,10.08-9.55s7.74-7.52,9.94-9.65l4.01-3.87v-31.96l6.36-6.37,6.36-6.37-.02-16.95c-.01-11.3-.12-17.13-.32-17.5-.6-1.11.78-2.32,1.93-1.7.78.42.96,1.13.47,1.88-.37.57-.43,2.92-.43,17.78v17.12l-6.51,6.47-6.51,6.48.14,20.98c.07,11.54.03,21.72-.09,22.63-.46,3.31-.69,3.59-14.22,17.09-6.91,6.89-13.07,13.15-13.67,13.9l-1.1,1.36v43.81l-1.32.68c-3.36,1.73-3.52,6.6-.27,8.44,3.77,2.13,8.17-1.01,7.37-5.25-.22-1.17-1.9-3.15-2.87-3.4l-.67-.17v-18.63c0-12.73.1-18.98.31-19.75.35-1.28-.59-.3,19.75-20.27,10.15-9.97,13.89-13.81,14.36-14.77.63-1.27.64-1.56.78-12.43l.14-11.13.76-1.55c.76-1.55,4.55-6.19,5.23-6.41.29-.1.35,3.48.28,18.71l-.08,18.83-.76,1.55c-.63,1.28-2.68,3.44-11.49,12.08-7.6,7.46-10.92,10.9-11.39,11.82-.44.87-2.33,2.9-5.76,6.18l-5.1,4.88v18.62l-.99.96c-2.41,2.34-.3,6.29,2.96,5.58,2.54-.56,3.44-3.74,1.57-5.55l-.96-.93-.2-8.73-.2-8.73,3.06-3c1.68-1.65,3.47-3.34,3.98-3.76l.92-.76v1.93c0,3.52.82,34.54,1,37.84.22,4.13-.11,3.63,7.44,10.94l6.21,6.01v8.57c0,7.08.07,8.57.39,8.57.21,0,.76.07,1.22.17l.83.17.19-9.89c.23-11.76.95-9.73-5.89-16.64-6.45-6.51-5.98-5.44-5.73-12.96.11-3.33.29-10.81.39-16.62.27-15.2.4-16.58,1.67-18.43.9-1.3,8.31-8.61,8.73-8.61.09,0,.17,6.37.17,14.17v14.17l-1.27.34c-7.59,2.03-6.33,13.91,1.55,14.63,8.86.8,11.56-12.07,3.06-14.62l-1.08-.32v-30.76l3.06-3.05,3.06-3.05.2,10.24c.47,24.38.21,22.87,4.71,27.18,1.76,1.68,3.67,3.52,4.25,4.08l1.06,1.02v6.03l-4.08,3.96-4.08,3.95v22.96l-.85.35c-4.98,2.06-3.29,9.58,2.15,9.58s6.84-6.61,2.32-9.46l-1.09-.69v-19.81l4.47-4.53c5.57-5.63,5.87-6.34,5.25-12.14l-.21-1.94-3.77-3.86c-2.08-2.12-3.79-4.09-3.81-4.38-.12-1.48.13-14.91.29-15.48.16-.62.97.09,8.96,7.93,10.8,10.58,10.32,9.84,10.46,16.32l.1,4.49-5.39,5.24-5.39,5.24.04,4.26.04,4.26-1.01,1.01c-2.55,2.55.32,6.74,3.46,5.06,2.12-1.13,2.54-4.19.73-5.37-.68-.45-.69-.52-.69-4.42v-3.96l4.07-4.1c2.24-2.25,4.13-4.03,4.2-3.96.07.07.22,4.38.33,9.57l.2,9.44-5.81,5.91-5.81,5.92v5.85s0,5.85,0,5.85l1.2-.18c.66-.1,1.23-.21,1.27-.24.04-.03.15-1.97.25-4.32l.19-4.27,6.08-5.82c8.21-7.85,7.3-4.98,7.8-24.62.27-10.53-.12-9.88,3.71-6.25l3.17,3.01v7.86s0,7.86,0,7.86l-1.1,1.01c-2.92,2.7-1.45,7.07,2.38,7.07s5.06-5.1,1.92-7.14l-.94-.61v-8.49s0-8.49,0-8.49l-4.42-4.36-4.42-4.36v-4.18c-.03-5.91.71-4.84-12.03-17.35-8.68-8.52-10.83-10.78-11.45-12.04l-.77-1.55-.04-27.25c-.02-14.99.06-27.25.18-27.25.82,0,5.48,5.94,5.86,7.46.16.63.36,8.37.46,17.71l.18,16.58,6.4,6.55,6.4,6.55-.1,5.03-.1,5.03,4.12,3.91,4.12,3.91-.18,1.2c-.47,3.11,2.97,5.03,5.32,2.96,2.35-2.06.69-5.92-2.38-5.54-.99.12-1.14,0-4.84-3.72l-3.82-3.84.11-4.93.11-4.93-6.46-6.48-6.46-6.49v-6.94l1.62,1.42c.89.78,4.79,4.52,8.66,8.29l7.04,6.87-.06,4.63-.06,4.63,7.74,7.73,7.74,7.73v1.42c0,1.68-3.85,5.93-5.98,6.61-2.82.89-2.36,4.52.57,4.52,1.44,0,2.36-.94,2.38-2.44.01-1.02,2.06-3.76,2.81-3.76.59,0,.25,23.05-.34,23.24-4.7,1.49-3.92,8.85,1.02,9.6,5.5.82,7.97-7.04,2.93-9.31l-1.19-.54.1-21.99c.1-21.58.09-22-.46-22.85-.31-.47-6.26-6.48-13.23-13.34-13.28-13.08-13.91-13.77-14.89-16.14-.6-1.46-.6-1.62-.6-23.64v-22.17l-6.41-6.43-6.41-6.43v-17.14c0-15.67-.04-17.19-.47-17.67-.58-.64-.22-2,.57-2.15,1.65-.32,2.64,1,1.76,2.34-.4.62-.45,2.5-.45,17.25v16.56l6.48,6.47,6.48,6.47v32.17l1.76,1.66c.97.91,5.43,5.22,9.93,9.58,4.49,4.36,9.97,9.62,12.18,11.69l4.01,3.77v14.18s3.8,3.59,3.8,3.59l3.8,3.59v23.98s-1.34.7-1.34.7c-3.95,2.08-3.24,7.86,1.11,9.03,5.96,1.6,8.7-6.43,3.23-9.45-.48-.27-.52-1.03-.58-12.79l-.06-12.5-3.73-3.55-3.73-3.55.09-5.44.09-5.44,3.93,3.61c11.08,10.18,14.41,13.42,15.09,14.68l.7,1.31.09,11.7.09,11.71-.98.47c-4.49,2.14-2.95,9.35,2,9.34,5.37-.01,7.19-7.38,2.32-9.4l-.85-.35v-28.97l-2.82-2.82c-3.33-3.33-3.24-3.07-1.07-3.07h1.74l5.03,5c4.76,4.72,5.06,4.97,5.44,4.45.68-.92.79-.77-5.83-7.55l-4.06-4.15h-6.19s-1.53-1.55-1.53-1.55l-1.53-1.55h4.43c3.05,0,4.61.11,5.01.35,1.04.64,1.63-.82.78-1.93-.15-.2-2.42-.38-6.22-.5l-5.99-.19-4.85-4.85-4.85-4.85,2.03-.17c5.1-.44,14.38-.27,15.27.27,1.48.91,21.89,21.25,22.29,22.21.26.63.38,2.27.38,5.41v4.51l-1.1.56c-4.47,2.28-2.62,9.31,2.45,9.31,4.97,0,6.69-7.06,2.27-9.31l-1.11-.57.04-6.9c.03-5.78-.03-7-.39-7.52-.24-.34-5.22-5.47-11.08-11.4l-10.65-10.78,5.49-.07c3.02-.04,5.82-.03,6.22.02.88.1,11.52,10.36,11.52,11.1,0,1.33,2.39,1.8,2.67.53.12-.55.92-1.48,2.43-2.82l2.25-2h2.2c2.85,0,3.13.27,3.13,3.03,0,1.9-.04,2.03-.85,2.61-2.61,1.86-2.2,6.06.72,7.35,5.05,2.24,8.08-5.24,3.16-7.81-.75-.39-.77-.47-.77-2.79s-.03-2.42-.96-3.44l-.96-1.05-3.13-.1c-3.58-.11-3.48-.15-6.33,2.51l-1.83,1.72-.78-.57c-.43-.31-3.06-2.86-5.85-5.66l-5.07-5.09h-19.01c-10.77,0-19.41-.11-19.92-.26-.65-.19-3.5-2.86-10.07-9.45l-9.16-9.19v-2.8c0-1.54.07-2.8.17-2.8s3.67,3.42,7.95,7.6l7.79,7.61h3.71l.33,1.11c1.93,6.43,11.68,4.6,11.25-2.12-.36-5.64-7.91-7.47-10.65-2.59-.59,1.04-.63,1.06-2.27,1.06h-1.68l-8.09-7.95c-4.45-4.37-8.17-8.05-8.26-8.18-.09-.13-.19-1.82-.23-3.77l-.06-3.53,6.29.1c8.59.13,6.53-1.19,21.35,13.61,15.01,14.98,12.43,13.46,23.24,13.68,4.49.09,8.2.19,8.25.21.04.03,1.96,1.87,4.25,4.09,4.27,4.14,8.13,7.3,8.57,7.03.14-.08.2-.64.14-1.25l-.11-1.09h14.17l.34.82c1.23,2.97,5.56,3.78,8,1.49,2.61-2.44,1.78-6.86-1.55-8.24l-.81-.33v-13.45h-15.34l-11.56-11.55-11.56-11.55h-4.76c-4.72,0-4.77,0-5.51-.72l-.75-.72-18.61.02-18.6.02-10.38-10.18-10.38-10.18-.15-23.27c-.08-12.8-.25-23.54-.37-23.87-.48-1.25,1.71-2.15,2.35-.96.23.42.19.77-.13,1.39-.38.73-.43,3.69-.43,23.34v22.51l10.16,9.94,10.16,9.94,25-.18,25-.18,1.24.84c.68.46,4.85,4.39,9.26,8.72l8.03,7.87h11.05s11.05.01,11.05.01l.38.92c1.6,3.83,6.96,3.96,8.85.21,2.84-5.62-5.06-10-8.61-4.77-.47.69-.94,1.01-1.69,1.16-1.31.25-14.32.29-15.51.05-.67-.13-3.12-2.39-10.39-9.57l-9.51-9.39h5.87s5.87-.02,5.87-.02l2.38,2.32c2.11,2.06,2.36,2.41,2.23,3.12-.48,2.47,2.29,4.36,4.62,3.15,3.21-1.66,1.77-5.95-1.93-5.73-1.15.07-1.26-.01-3.78-2.51l-2.6-2.58-24.64-.09-24.64-.09-2.96-.87c-3.88-1.14-13.44-3.94-15.8-4.62-2.01-.58-11.63-9.9-12.15-11.76-1.54-5.6-3.5-31.85-2.79-37.48,1.55-12.43,10.38-22.76,23.28-27.24,4.67-1.62,20.78-1.24,22.61.54,1.95,1.89,2.59,3.28,2.6,5.58,0,4.9,2.51,9.5,6.6,12.15,1.42.92,2.91,1.49,6.18,2.39.5.14.52-.02.39-2.77-.29-6.08-3.19-10.76-8.31-13.43-1.94-1.01-5.08-4.25-4.53-4.68.05-.04,1.74.03,3.76.15,2.01.12,5.78.24,8.37.25l4.71.03,1.19,2.17c4.79,8.76,11.2,12.76,20.57,12.84,4.24.04,4.13.11,3.53-2.39-1.83-7.64-10.52-14.9-17.83-14.9-1.73,0-1.52-.38.92-1.69,1.37-.73,3-1.62,3.62-1.98l1.14-.64,2.8,1.14c3.92,1.6,6.74,2.33,9.88,2.55l2.7.19.91,2.54c2.75,7.67,6.75,11.57,13.28,12.96,4.55.96,4.77.78,3.78-3.12-1.53-6.05-7.85-13.07-11.77-13.07-.21,0-.3-.13-.2-.28.1-.16.37-.28.61-.28.73,0,5.05-2.14,7.11-3.53l1.95-1.31,1.73,1.17c4.7,3.19,10.93,4.72,16.7,4.1l1.89-.2v-1.23c0-8.69-5.86-18.8-13.47-23.22-2.53-1.47-2.66-1.59-3.43-3.29-1.44-3.18-1.86-2.88,3.8-2.7l4.94.16-.18-.96c-2.09-11.08-7.41-19.51-15.88-25.1-1.78-1.18-3.59-2.52-4.03-2.97l-.79-.83,1.36-.98c.75-.54,2.16-1.52,3.13-2.17,2.34-1.57,2.33-1.51.53-5.18-4.61-9.4-10.72-13.86-20.89-15.23-2.77-.37-2.82-.4-3.87-1.57l-1.06-1.19,1.22-1.59c1.44-1.88,3.55-5.14,4.39-6.78l.59-1.17-1.77-.82c-3.6-1.68-8.83-2.31-13.32-1.61l-1.41.22,1.22-1.07c1.44-1.27,1.5-1.18-2.06-3.32-3.32-2-4.79-2.6-8.14-3.36-.81-.18-.77-.23,3.17-4.2,4.68-4.72,4.56-4.34,1.8-5.6-8.6-3.91-16.74-4.24-23.86-.94l-2.14.99-.99-.58-.99-.59,2.09-2.15c2.58-2.65,7.8-10.85,7.8-12.25,0-.36-5.98-.81-8.78-.66-5.93.31-10.16,1.89-15.35,5.7l-.61.45.58-1.78c.38-1.17.62-2.87.72-4.98l.14-3.2-.95.2c-2.99.63-7.56,3.33-9.3,5.5q-1.16,1.44-1.74-.79c-1.01-3.88-2.55-7.26-3.26-7.18-1.06.12-4.7,1.74-6.47,2.88l-1.67,1.08-1.42-2.32c-2.76-4.52-7.38-8.68-13.14-11.83-3.46-1.89-3.06-1.88-6.3-.16M201.93,51.02c2.01,4.18,2.37,5.58,2.37,9.19,0,4.3,1.4,10.35,3.51,15.15l.99,2.25-2.18,2.13c-2.61,2.56-2.1,3.09-4.74-5.01l-1.92-5.9-2.09,6.4c-1.15,3.52-2.19,6.37-2.3,6.32-.12-.04-1.16-.98-2.32-2.09l-2.11-2.01.81-1.65c2.19-4.46,3.57-10.14,3.9-16.02.17-3.16.38-4.58.8-5.63.98-2.43,3.12-6.9,3.3-6.9.1,0,.99,1.7,1.99,3.77M169.17,64.2c3.58,2.29,5.83,6.79,5.84,11.71,0,1.43.09,1.42-2.07.28-4.26-2.25-6.66-6.32-6.66-11.29v-1.94l.77.19c.43.11,1.38.58,2.12,1.05M233.77,64.07c.78,4.14-2,9.34-6.37,11.94-2.33,1.38-2.25,1.4-2.25-.39,0-5.05,2.2-9.34,5.93-11.53,2.23-1.31,2.44-1.31,2.69-.02M221.48,71.47c1.09,3.16,1.07,4.07-.3,20-.65,7.51-1.66,9.2-11.05,18.55l-5.2,5.17-.19-3.63c-1.13-22.16-1.07-22.42,7.29-31.09,3.39-3.51,5.67-6.5,7.61-9.95.88-1.57.98-1.52,1.83.94M180.97,71.57c1.41,2.8,2.38,4.05,6.3,8.09,4.74,4.89,5.88,6.35,7.29,9.28l1.15,2.39-.02,8.87c-.01,4.88-.12,10.24-.24,11.92l-.22,3.04-5.48-5.44c-10.11-10.03-10.35-10.51-11.25-22.06-.92-11.94-.86-13.71.58-17.12.55-1.31.77-1.19,1.89,1.02M158.37,71.92c1,.4,1.14.59,1.4,1.79,1.29,5.99,5.28,10.62,12.19,14.12l2.16,1.09.15,1.49c.09.82.23,2.37.32,3.46.09,1.08.24,2.21.33,2.51.15.51.1.51-.79-.08-.52-.34-2.28-1.33-3.91-2.19-9.03-4.81-11.86-9.03-13.09-19.54-.1-.85-.25-1.9-.33-2.32-.17-.9.03-.94,1.56-.33M243.04,73.61c-.96,10.28-4.25,15.63-12.3,20.04-1.76.96-3.74,2.08-4.41,2.49l-1.21.74.19-2.21c.1-1.21.27-2.96.37-3.89l.18-1.68,1.97-1.07c7.22-3.91,11.08-8.27,12.28-13.83.33-1.54.43-1.69,1.5-2.16,1.66-.74,1.65-.75,1.43,1.58M137.78,89.12c4.83,2.31,7.58,6.79,8.32,13.54.16,1.44.23,1.44-2.84.21-5.73-2.3-8.68-6.37-9.02-12.47l-.14-2.62.81.18c.45.1,1.74.62,2.87,1.17M265.7,90.03c0,6.43-3.37,10.82-10.3,13.43l-1.73.65.13-1.32c.77-7.69,6.15-14.58,11.69-14.97.12,0,.21.98.21,2.21M250.48,90.8c-.41,1.08-.46,3.37-.16,8.04.38,5.95-.66,7.23-9.54,11.75-5.11,2.6-5.02,2.57-4.64,1.57.16-.42.91-3.25,1.67-6.29,2.37-9.53,3.26-10.86,9.6-14.3,3.53-1.92,3.52-1.92,3.08-.76M153.1,91.82c5.83,3.26,6.78,4.7,9.11,13.82.84,3.28,1.61,6.26,1.71,6.63.11.37.08.62-.06.55-13.48-6.38-14.41-7.32-14.07-14.17.25-5.03.21-7-.14-7.92-.37-.99-.08-.9,3.45,1.08M171.49,100.15c4.64,3.5,15.53,15.39,17.21,18.79,1.38,2.8,2.71,12.81,1.71,12.81-.06,0-.59-.46-1.18-1.02-.59-.56-2.67-2.28-4.62-3.83-7.73-6.14-9.61-7.92-11.87-11.24-2.91-4.28-5.89-13.35-5.89-17.9v-.69l1.1.62c.6.34,2.2,1.45,3.55,2.46M232.62,101.42c-1.71,10.77-4.96,15.72-16.35,24.86-2.95,2.36-5.6,4.56-5.89,4.88-1,1.11-1.1.75-.92-3.43.32-7.69,1.63-9.94,12.22-21,4.28-4.47,7.15-6.99,10.09-8.85l1.13-.71.09.7c.05.38-.11,1.98-.36,3.55M148.9,109.41c6.34,4.45,16.25,12.03,16.25,12.43,0,.1-1.3.1-2.89-.01-1.59-.11-4-.12-5.36-.03-2.97.2-2.74.36-5.96-3.99-2.75-3.7-4.35-5.17-6.76-6.22l-1.8-.78.42-.81c.23-.44.49-1.42.58-2.18l.16-1.37,1.53.67c.84.37,2.57,1.4,3.84,2.3M256.42,107.31c0,.5.24,1.43.52,2.06l.52,1.14-1.86.96c-2.24,1.16-3.79,2.65-6.43,6.2-3.12,4.2-2.51,3.9-8.41,4.08-2.78.08-5.28.22-5.55.3-1.77.56,5.22-5.06,15.34-12.33,1.95-1.4,5.19-3.31,5.64-3.32.12,0,.21.41.22.91M281.43,109.13c1.11.19,1.08.75-.13,2.72-3.26,5.27-9.97,7.63-16.39,5.76l-1.23-.36,1.24-1.69c4.1-5.56,9.44-7.64,16.51-6.43M127.4,109.65c3.29,1.04,8.75,5.8,8.75,7.62,0,1.01-7.43,1.32-10.01.41-3.97-1.4-8.29-5.62-8.29-8.1,0-.91,6.61-.87,9.56.06M139.57,122.07c2.84,2.46,5.63,3.35,13.12,4.17,2.21.24,5.82.64,8.03.88,8.28.9,11.79,2.47,17.2,7.7,4.67,4.51,5.66,6.65,5.76,12.43l.08,4.65,8.44,8.31,8.44,8.31.19,34.45.18,34.45,3.22,3.01c1.77,1.66,3.55,3.1,3.94,3.21,1.28.35,1.62,1.72.64,2.61-.78.71-1.92.38-2.15-.63-.11-.47-1.55-2.16-3.65-4.29l-3.47-3.52-.14-8.87c-.08-4.88-.15-16.68-.15-26.23l-.02-17.35-6.48-6.12-6.48-6.12v-6.83l-5.14-5.14c-3.41-3.41-5.34-5.14-5.73-5.14-.88,0-1.63-.96-1.41-1.81.37-1.48,2.21-1.52,2.58-.05.09.36,2.63,3.11,5.64,6.1l5.47,5.45v6.77l1.66,1.56c.91.86,3.48,3.27,5.7,5.36l4.04,3.8.08-6.93.07-6.93-8.24-8.18c-11.45-11.36-10.86-10.93-19.51-14.16-15.71-5.86-34.99-16.39-43.33-23.67-.49-.43,6.61-2.46,8.73-2.5,1.01-.02,1.39.16,2.68,1.28M266.55,121.2c2.1.49,5.4,1.52,5.69,1.78.25.23-2.24,2.07-7.96,5.88-11.08,7.39-20.66,12.25-36.61,18.59-9.84,3.91-8.98,3.38-14.91,9.24l-5.21,5.15.17,9.1c.09,5.01.27,9.51.38,10.01.19.81.13.94-.54,1.25-1.09.5-1.73.02-1.6-1.21.05-.52.15-4.64.22-9.16l.12-8.21-3.39-3.23-3.39-3.23v-12.75l-.84-.44c-2.88-1.49-2.16-6.1,1.01-6.53,3.78-.51,5.39,5.39,1.79,6.53-.52.17-.55.47-.55,6.33v6.15l2.53,2.36,2.53,2.36,5.01-4.99,5.01-4.99.15-4.17c.17-4.71.31-5.14,2.83-8.56,1.55-2.1,5.69-6.19,7.58-7.49,4.1-2.81,6.07-3.28,19.15-4.62,9.34-.95,11.27-1.51,14.66-4.22,2-1.6,2.88-1.73,6.19-.97M127.7,130.06c3.48,1.17,7.63,4.02,15.5,10.61,2.41,2.02,4.98,4.13,5.72,4.7.74.57,1.28,1.03,1.2,1.03s-3.91.38-8.51.85c-4.6.47-8.38.82-8.4.77-3.14-8-8.34-11.85-16.02-11.86-3.89,0-3.91.02-2.53,2.96,2.5,5.33,8.2,9.11,13.99,9.28l1.15.03-1.41.27c-3.07.59-10.78.43-13.19-.27-4.65-1.35-6.04-2.26-11.48-7.59l-4.96-4.85,1.02-.16c3.21-.49,4.96-1.18,9.61-3.8,2.18-1.23,2.61-1.36,4.79-1.52,1.32-.1,3.41-.42,4.65-.73,2.85-.7,6.3-.59,8.88.28M281.9,129.92c.77.25,2.55.55,3.95.67,2.47.21,2.66.28,6.48,2.36,3.6,1.96,6.41,3.02,8.01,3.02.87,0,.91-.05-4.22,4.93-7.2,6.99-9.97,8.18-18.87,8.12-3.15-.02-5.79-.16-6.2-.33-.64-.27-.59-.3.56-.32,5.53-.1,10.86-3.49,13.38-8.52,1.86-3.72,1.78-3.87-1.97-3.87-7.01,0-12.25,3.38-15.3,9.85-.55,1.16-1.02,2.15-1.05,2.19-.05.07-16.1-1.58-16.53-1.71-.11-.03,1.09-1.1,2.67-2.38,1.59-1.27,4.02-3.28,5.42-4.46,10.93-9.26,16.95-11.69,23.66-9.55M198.97,139.81c-.7.89-.72,1.19-.15,2.01,1.14,1.63,3.83.04,2.9-1.72-.67-1.27-1.88-1.4-2.75-.3M307.84,242.86c1.01,1.54-.74,3.28-2.28,2.28-.86-.56-.92-1.64-.14-2.42.78-.78,1.85-.72,2.42.14M93.58,243.58c.56,1.01.23,2.17-.69,2.41-1.66.42-2.86-1.21-1.94-2.61.62-.94,2.06-.83,2.63.21M126.44,247.86c-1.11,1.66-19.41,19.24-20.39,19.58-2.48.86-2.63,3.72-.25,4.56,1.64.57,3.75-1.23,3.18-2.71-.14-.37,2.16-2.83,9.75-10.43l9.94-9.94h11.06c6.08,0,11.06.1,11.06.21,0,.23-6.18,6.97-7.75,8.45l-.97.92h-4.67c-2.83,0-4.9.12-5.24.31-.47.25-.71.18-1.32-.38-3.15-2.89-4.88-2.27-10.39,3.77-2.46,2.69-2.47,2.85-.45,5.04,3.56,3.84,4.99,3.32,11.41-4.14l2.25-2.62h3.07c1.69,0,3.07.09,3.07.21s-2.88,3.09-6.41,6.62l-6.4,6.41-5.35.03c-2.94.02-6.14.11-7.09.21l-1.74.18-5.66,5.56-5.66,5.56h-6.96s-6.96-.03-6.96-.03l-.58-1.18c-.42-.86-.94-1.38-1.9-1.89l-1.33-.7.07-5.49.07-5.49,7.04-.08,7.04-.07,11.56-11.54,11.55-11.54h5.77l-.42.63M290.68,258.77l11.45,11.54,7.02.08,7.02.07.13,5.39.13,5.39-1.3.76c-.9.53-1.48,1.14-1.9,2.01l-.61,1.25h-14.17l-5.61-5.64-5.61-5.64-7.11-.18-7.11-.18-6.23-6.36-6.23-6.36,3.06-.08,3.06-.09.7.93c1.39,1.85,6.59,6.97,7.55,7.43,1.96.93,5.24-.99,6.44-3.76.54-1.24.32-1.5-6.16-7.31-1.93-1.73-3.81-1.67-5.95.21-.83.73-1.1.83-1.56.58-.33-.17-2.42-.3-5.04-.3h-4.48l-3.5-3.59c-3.39-3.49-5.23-5.62-5-5.82.06-.05,4.92-.18,10.81-.29l10.7-.19,10,9.99c9.32,9.31,9.99,10.05,9.91,10.87-.28,2.72,3.41,3.74,4.58,1.27.75-1.58-.1-3.24-1.66-3.24-.37,0-4.06-3.49-10.57-9.99l-10-9.99,1.41-.1c.78-.05,2.08-.12,2.89-.15l1.48-.05,11.45,11.54M73.49,256.64c2.02,2.02-.82,5-3.07,3.23-1.55-1.22-.51-3.92,1.51-3.92.55,0,1.13.26,1.56.69M329.81,256.61c2.18,1.87-.78,5.13-3.01,3.32-1.41-1.14-.37-3.98,1.47-3.98.43,0,1.13.3,1.55.66M128.74,259.95q1.27,1.23-1.7,4.28c-3.09,3.18-3.79,3.36-5.24,1.33l-.58-.82,2.77-2.85c3.05-3.13,3.38-3.27,4.75-1.95M275.51,261.37c3.62,3.44,3.56,3.34,2.42,4.7-1.22,1.44-1.81,1.22-4.94-1.86q-3.14-3.08-1.78-4.28c1.39-1.22,1.56-1.16,4.31,1.45M107.42,269.2c.64.64.35,1.68-.46,1.68-1.24,0-1.95-1.15-1.18-1.92.49-.49.99-.42,1.64.23M294.58,269.49c.25.78-.36,1.44-1.24,1.34-.74-.09-1.06-.73-.76-1.51.25-.65,1.79-.52,2.01.17M253.71,273.96c2.17,1.43,2.11,4.35-.13,5.51-2.33,1.21-5.03-.53-4.7-3.03.33-2.44,2.89-3.76,4.82-2.48M150.08,274.31c1.82,1.43,1.78,3.66-.08,4.98-2.56,1.82-6.19-.97-4.82-3.71.93-1.85,3.36-2.48,4.9-1.27M84.2,284.81c.97.97.92,2.22-.13,3.27-1.02,1.02-2.06,1.06-2.99.13-1.46-1.46-.45-4.1,1.56-4.1.55,0,1.13.26,1.56.69M319.03,284.81c1.81,1.68-.2,4.97-2.31,3.79-1.01-.57-1.58-1.67-1.34-2.6.46-1.84,2.3-2.44,3.66-1.18M92.62,303.34c1.16,1.02,1.24,2.27.21,3.3-1.05,1.05-2.08,1.11-3.03.17-2.03-2.03.66-5.35,2.81-3.46M310.36,303.4c1.02,1.02.83,2.78-.41,3.74-1.26.97-3.24-.27-3.24-2.03,0-2.04,2.26-3.09,3.65-1.7M126.64,304.3c-.49.2-.44.77.15,1.5l.5.62.7-.59.7-.59-.66-.56c-.69-.59-.79-.61-1.38-.38M124.89,306.08c-.62.38-.04,1.81.77,1.93.92.13,1.07-.64.3-1.48-.47-.5-.78-.63-1.07-.45M273.75,306.63c-1,1.11.28,2.24,1.34,1.18.48-.48.54-.71.3-1.1-.44-.7-1.05-.74-1.64-.09M122.96,308.14c-.39.46.81,2.09,1.31,1.78.49-.3.4-1.15-.19-1.67-.62-.56-.74-.57-1.13-.1M275.46,308.29c-.57.63-.58.82-.06,1.34.46.46.58.33.85-.94.22-1.02-.09-1.18-.79-.4M276.99,309.45c-.68.68-.71.87-.2,1.56.31.43.42.45.61.12.13-.22.41-.66.61-.99.6-.94-.22-1.49-1.02-.69M121.36,309.46c-.2.32.21,1.17.79,1.65.77.64,1.14-.32.46-1.18-.62-.79-.97-.92-1.24-.47M245.06,322.5c.66.95.76,1.4.88,4.08l.13,3.01-6.59-6.43c-5.08-4.96-6.58-6.59-6.57-7.11.01-.37.04-1.76.07-3.09l.05-2.42,5.64,5.44c3.1,3,5.98,5.93,6.38,6.51M278.48,311.27c-.74.65-.77.73-.34,1.17.51.52,1.93-.1,1.93-.85,0-.87-.77-1.02-1.59-.31M119.99,311.07c-.34.34-.18,1.13.34,1.6.56.5,1.45.36,1.45-.23s-1.47-1.69-1.79-1.37M279.76,312.35c-.3.9.56,3.05.97,2.42.45-.68.68-1.66.42-1.81-.17-.1-.41-.01-.55.2-.18.29-.25.22-.25-.25,0-.49-.47-.93-.59-.56M117.97,313.15c-.22.35.93,1.67,1.45,1.67.62,0,.74-.56.27-1.28-.47-.71-1.39-.92-1.72-.39M281.54,313.97c-.76,1.22-.27,1.85.65.83.45-.5.47-.62.11-.98s-.45-.34-.76.15M116.3,315.03c-.13.34.05.7.57,1.15.98.85,1.48.45.97-.78-.39-.95-1.24-1.16-1.54-.37M282.25,315.12c-.6.35-.63.92-.08,1.37.48.4,1.82-.4,1.84-1.09,0-.29-.15-.26-.6.14q-.6.53-.27-.1c.37-.71,0-.85-.89-.33M114.81,316.27c-.49.5-.44.92.2,1.5.55.49,1.42.43,1.42-.11,0-.67-1.26-1.75-1.62-1.39M283.85,316.92c-.78,1.19.16,1.79,1.1.69.39-.46.43-.67.17-.98-.47-.57-.75-.5-1.27.29M115.61,321.12c-.36.43.71,2.47,1.22,2.33.64-.18.87-1.05.36-1.34-.33-.19-.45-.47-.33-.83.21-.66-.73-.77-1.25-.15M282.6,321.4c0,1.04.65,1.42,1.27.73.5-.55.5-.58-.03-.98-.8-.6-1.24-.51-1.24.24M113.93,322.64c-.36.67.31,1.75,1.09,1.75s1.01-.57.33-.75c-.31-.08-.51-.31-.44-.5.25-.65-.66-1.11-.98-.5M284.5,322.96c-.63.71-.68.87-.33,1.16.52.43,1.97-.89,1.71-1.56-.24-.62-.56-.53-1.39.4M109.18,328.9c2.13,1.67.06,5.04-2.34,3.8-1.54-.8-1.8-2.14-.66-3.49.86-1.03,1.95-1.14,3-.31M293.89,329.16c.92.92,1,1.39.42,2.52-.88,1.7-3.24,1.68-3.96-.05-.99-2.35,1.74-4.26,3.54-2.47M236.15,330.39c1.27.97,1.29,2.61.05,3.59-2.14,1.68-4.67-1.71-2.57-3.46.68-.57,1.88-.63,2.53-.13M234.21,331.4c-.68.43-.29,1.85.54,1.95.93.11,1.5-.56,1.27-1.49-.18-.73-1.04-.95-1.81-.46M241.3,342.73c.5.71.33,1.29-.42,1.53-1.05.33-1.82-1-1.03-1.79.5-.5.99-.41,1.46.26M191.47,348.45c4.65,2.73.94,10.5-4.03,8.43-4.06-1.69-4.17-7.02-.19-8.88,1.39-.65,2.57-.52,4.22.45M129.28,348.99c2.12,1.82-.59,5.04-2.89,3.43-1.22-.85-1.05-3.11.28-3.76.99-.48,1.8-.38,2.62.32M258.19,348.66c1.41.75,1.72,2.19.74,3.44-1.24,1.57-3.92.59-3.92-1.43,0-1.74,1.69-2.8,3.19-2M273.61,348.69c1.27.98,1.38,2.22.28,3.32-1.53,1.53-3.8.63-3.74-1.49.05-1.66,2.2-2.8,3.46-1.83M144.22,349.03c2.02,1.41-.11,4.83-2.34,3.76-2.19-1.05-1.53-4.18.88-4.18.48,0,1.14.19,1.46.42M163.46,352.56c1.08,1.08.43,2.54-1.14,2.54-.82,0-1.4-.63-1.4-1.53,0-.56.99-1.57,1.55-1.57.23,0,.68.25.98.56M154.66,365.97c1.6,1.76-.58,4.46-2.71,3.36-1.77-.91-1.72-3.2.09-3.98,1.07-.46,1.8-.29,2.63.62M248.48,365.4c1.5,1.05,1.28,3.24-.4,3.94-1.04.43-1.64.31-2.58-.54-1.89-1.7.89-4.87,2.98-3.4M214.6,374.65c1.72,1.72-.33,4.35-2.49,3.21-.84-.44-1.03-.79-1.03-1.86,0-1.9,2.15-2.73,3.53-1.35M212.04,375.39c-.31.49.12,1.63.67,1.8,1.14.36,2.15-.83,1.44-1.69-.39-.47-1.84-.55-2.11-.11M237.66,375.53c.71.71.57,2.28-.26,2.82-1.88,1.23-3.81-1.24-2.22-2.84.55-.55,1.92-.54,2.48.02M201.42,390.75c1.42,1.59.35,4.06-1.76,4.06-2.77,0-3.33-3.88-.7-4.88.73-.28,1.81.08,2.46.82"/>
|
|
23731
|
+
</svg>`;
|
|
23732
|
+
var LOGO_DATA_URI = `data:image/svg+xml;base64,${Buffer.from(LOGO_SVG, "utf8").toString("base64")}`;
|
|
23733
|
+
var __dirname3 = path59.dirname(fileURLToPath4(import.meta.url));
|
|
23734
|
+
function portalTemplateDir() {
|
|
23735
|
+
const distDir = path59.resolve(__dirname3, "templates", "portal");
|
|
23736
|
+
if (existsSync12(distDir)) return distDir;
|
|
23737
|
+
const srcDir = path59.resolve(__dirname3, "..", "templates", "portal");
|
|
23738
|
+
if (existsSync12(srcDir)) return srcDir;
|
|
23739
|
+
return path59.resolve(__dirname3, "..", "dist", "templates", "portal");
|
|
23740
|
+
}
|
|
23741
|
+
var MODULE_ORDER = [
|
|
23742
|
+
"js/namespace.js",
|
|
23743
|
+
"js/state-model.js",
|
|
23744
|
+
"js/glossary.js",
|
|
23745
|
+
"js/router.js",
|
|
23746
|
+
"js/palette.js",
|
|
23747
|
+
"js/palette-overlay.js",
|
|
23748
|
+
"js/consumer.js",
|
|
23749
|
+
"js/export.js",
|
|
23750
|
+
"js/tree.js",
|
|
23751
|
+
"js/shell.js",
|
|
23752
|
+
"js/dispatch.js",
|
|
23753
|
+
// Per-surface view modules — registered onto Yg.views, consumed by the dispatcher. The
|
|
23754
|
+
// matrix module precedes the relations view that calls Yg.matrix; the panel module is read
|
|
23755
|
+
// by the bootstrap's panel slot. All before the bootstrap, which orchestrates the rest.
|
|
23756
|
+
"js/views/overview-view.js",
|
|
23757
|
+
"js/views/coverage-view.js",
|
|
23758
|
+
"js/views/tree-view.js",
|
|
23759
|
+
"js/views/relations-matrix.js",
|
|
23760
|
+
"js/views/relations-view.js",
|
|
23761
|
+
"js/views/rulebook-view.js",
|
|
23762
|
+
"js/views/types-view.js",
|
|
23763
|
+
"js/views/flows-view.js",
|
|
23764
|
+
"js/views/suppressions-view.js",
|
|
23765
|
+
"js/views/start-view.js",
|
|
23766
|
+
"js/views/panel-aspect.js",
|
|
23767
|
+
"js/views/panel-view.js",
|
|
23768
|
+
"js/bootstrap.js"
|
|
23769
|
+
];
|
|
23770
|
+
var CSS_ORDER = [
|
|
23771
|
+
"tokens.css",
|
|
23772
|
+
"shell.css",
|
|
23773
|
+
"app.css",
|
|
23774
|
+
"views.css",
|
|
23775
|
+
"views-audit.css",
|
|
23776
|
+
"views-panel.css",
|
|
23777
|
+
"views-relations.css",
|
|
23778
|
+
"views-rulebook.css",
|
|
23779
|
+
"views-flows.css",
|
|
23780
|
+
"views-start.css"
|
|
23781
|
+
];
|
|
23782
|
+
async function readAssets() {
|
|
23783
|
+
const dir = portalTemplateDir();
|
|
23784
|
+
const read = (rel) => readFile20(path59.join(dir, rel), "utf-8");
|
|
23785
|
+
const [shell, vendor] = await Promise.all([read("shell.html"), read("vendor/d3-hierarchy.js")]);
|
|
23786
|
+
const cssSources = await Promise.all(CSS_ORDER.map((rel) => read(rel)));
|
|
23787
|
+
const css = cssSources.join("\n");
|
|
23788
|
+
const moduleSources = await Promise.all(MODULE_ORDER.map((rel) => read(rel)));
|
|
23789
|
+
const modules = moduleSources.join("\n");
|
|
23790
|
+
return { shell, css, vendor, modules };
|
|
23791
|
+
}
|
|
23792
|
+
function renderStaticHtml(data, assets) {
|
|
23793
|
+
const json = safeJsonForScript(data);
|
|
23794
|
+
return assets.shell.replace("__PORTAL_FAVICON_HREF__", () => LOGO_DATA_URI).replace("/* __PORTAL_CSS__ */", () => assets.css).replace("/* __PORTAL_DATA__ */", () => json).replace("/* __PORTAL_VENDOR__ */", () => assets.vendor).replace("/* __PORTAL_MODULES__ */", () => assets.modules);
|
|
23795
|
+
}
|
|
23796
|
+
var LINE_SEPARATORS = new RegExp(`[${String.fromCharCode(8232)}${String.fromCharCode(8233)}]`, "g");
|
|
23797
|
+
function safeJsonForScript(data) {
|
|
23798
|
+
return JSON.stringify(data).replace(LINE_SEPARATORS, (ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
23799
|
+
}
|
|
23800
|
+
async function emitStatic(data, outPath) {
|
|
23801
|
+
const assets = await readAssets();
|
|
23802
|
+
const html = renderStaticHtml(data, assets);
|
|
23803
|
+
await atomicWriteFile(outPath, html);
|
|
23804
|
+
}
|
|
23805
|
+
async function renderPortalPage(data) {
|
|
23806
|
+
const assets = await readAssets();
|
|
23807
|
+
return renderStaticHtml(data, assets);
|
|
23808
|
+
}
|
|
23809
|
+
function contentTypeFor(relPath) {
|
|
23810
|
+
if (relPath.endsWith(".html")) return "text/html; charset=utf-8";
|
|
23811
|
+
if (relPath.endsWith(".css")) return "text/css; charset=utf-8";
|
|
23812
|
+
if (relPath.endsWith(".js")) return "text/javascript; charset=utf-8";
|
|
23813
|
+
if (relPath.endsWith(".json")) return "application/json; charset=utf-8";
|
|
23814
|
+
if (relPath.endsWith(".svg")) return "image/svg+xml";
|
|
23815
|
+
return "application/octet-stream";
|
|
23816
|
+
}
|
|
23817
|
+
async function readPortalAsset(relPath) {
|
|
23818
|
+
const root = portalTemplateDir();
|
|
23819
|
+
const cleaned = relPath.replace(/^\/+/, "");
|
|
23820
|
+
if (cleaned.length === 0) return null;
|
|
23821
|
+
const resolved = path59.resolve(root, cleaned);
|
|
23822
|
+
const rel = path59.relative(root, resolved);
|
|
23823
|
+
if (rel.startsWith("..") || path59.isAbsolute(rel)) return null;
|
|
23824
|
+
if (!existsSync12(resolved)) return null;
|
|
23825
|
+
const content20 = await readFile20(resolved);
|
|
23826
|
+
return { content: content20, contentType: contentTypeFor(cleaned) };
|
|
23827
|
+
}
|
|
23828
|
+
|
|
23829
|
+
// src/portal/server/server.ts
|
|
23830
|
+
import { createServer } from "http";
|
|
23831
|
+
|
|
23832
|
+
// src/portal/server/page.ts
|
|
23833
|
+
async function freshPortalData(projectRoot, writeEnabled) {
|
|
23834
|
+
return extractPortalData(projectRoot, { writeEnabled });
|
|
23835
|
+
}
|
|
23836
|
+
async function renderLivePage(data) {
|
|
23837
|
+
return renderPortalPage(data);
|
|
23838
|
+
}
|
|
23839
|
+
async function readStaticAsset(relPath) {
|
|
23840
|
+
return readPortalAsset(relPath);
|
|
23841
|
+
}
|
|
23842
|
+
|
|
23843
|
+
// src/portal/server/approve.ts
|
|
23844
|
+
import { spawn as spawn2 } from "child_process";
|
|
23845
|
+
import path60 from "path";
|
|
23846
|
+
import { existsSync as existsSync13 } from "fs";
|
|
23847
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
23848
|
+
function resolveCliBin() {
|
|
23849
|
+
const here = fileURLToPath5(import.meta.url);
|
|
23850
|
+
if (path60.basename(here) === "bin.js") return here;
|
|
23851
|
+
let dir = path60.dirname(here);
|
|
23852
|
+
for (let i = 0; i < 6; i++) {
|
|
23853
|
+
const candidate = path60.join(dir, "dist", "bin.js");
|
|
23854
|
+
if (existsSync13(candidate)) return candidate;
|
|
23855
|
+
dir = path60.dirname(dir);
|
|
23856
|
+
}
|
|
23857
|
+
return process.argv[1];
|
|
23858
|
+
}
|
|
23859
|
+
var CLI_BIN = resolveCliBin();
|
|
23860
|
+
function approveArgs(llm) {
|
|
23861
|
+
return llm ? ["check", "--approve"] : ["check", "--approve", "--only-deterministic"];
|
|
23862
|
+
}
|
|
23863
|
+
function dryRunArgs(llm) {
|
|
23864
|
+
return llm ? ["check", "--approve", "--dry-run"] : ["check", "--approve", "--only-deterministic", "--dry-run"];
|
|
23865
|
+
}
|
|
23866
|
+
function spawnCli(args, cwd) {
|
|
23867
|
+
return new Promise((resolve6, reject) => {
|
|
23868
|
+
const child = spawn2(process.execPath, [CLI_BIN, ...args], { cwd });
|
|
23869
|
+
let stdout = "";
|
|
23870
|
+
let stderr = "";
|
|
23871
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString("utf-8"));
|
|
23872
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString("utf-8"));
|
|
23873
|
+
child.on("error", (err) => reject(err));
|
|
23874
|
+
child.on("close", (code) => resolve6({ exitCode: code ?? 1, stdout, stderr }));
|
|
23875
|
+
});
|
|
23876
|
+
}
|
|
23877
|
+
async function runApproveViaCli(projectRoot, llm) {
|
|
23878
|
+
return spawnCli(approveArgs(llm), projectRoot);
|
|
23879
|
+
}
|
|
23880
|
+
var BUDGET_RE = /Filling\s+(\d+)\s+unverified pairs across\s+\d+\s+nodes\s+—\s+(\d+)\s+deterministic\s+\(no cost\),\s+(\d+)\s+reviewer calls/;
|
|
23881
|
+
function parseDryRunBudget(output) {
|
|
23882
|
+
const m = output.match(BUDGET_RE);
|
|
23883
|
+
if (!m) {
|
|
23884
|
+
throw new Error(
|
|
23885
|
+
`Could not parse the dry-run cost preview from the CLI output. Raw output:
|
|
23886
|
+
${output.trim()}`
|
|
23887
|
+
);
|
|
23888
|
+
}
|
|
23889
|
+
return {
|
|
23890
|
+
pairs: Number.parseInt(m[1], 10),
|
|
23891
|
+
deterministic: Number.parseInt(m[2], 10),
|
|
23892
|
+
reviewerCalls: Number.parseInt(m[3], 10),
|
|
23893
|
+
raw: m[0]
|
|
23894
|
+
};
|
|
23895
|
+
}
|
|
23896
|
+
async function dryRunApproveViaCli(projectRoot, llm) {
|
|
23897
|
+
const result = await spawnCli(dryRunArgs(llm), projectRoot);
|
|
23898
|
+
return parseDryRunBudget(`${result.stdout}
|
|
23899
|
+
${result.stderr}`);
|
|
23900
|
+
}
|
|
23901
|
+
|
|
23902
|
+
// src/portal/server/router.ts
|
|
23903
|
+
function sendJson(res, status, body) {
|
|
23904
|
+
const payload = JSON.stringify(body);
|
|
23905
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
23906
|
+
res.end(payload);
|
|
23907
|
+
}
|
|
23908
|
+
function sendText(res, status, contentType, body) {
|
|
23909
|
+
res.writeHead(status, { "content-type": contentType });
|
|
23910
|
+
res.end(body);
|
|
23911
|
+
}
|
|
23912
|
+
async function readJsonBody(req) {
|
|
23913
|
+
const chunks = [];
|
|
23914
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
23915
|
+
const text2 = Buffer.concat(chunks).toString("utf-8").trim();
|
|
23916
|
+
if (text2.length === 0) return {};
|
|
23917
|
+
try {
|
|
23918
|
+
const parsed = JSON.parse(text2);
|
|
23919
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
23920
|
+
} catch {
|
|
23921
|
+
return {};
|
|
23922
|
+
}
|
|
23923
|
+
}
|
|
23924
|
+
async function handleRequest(req, res, config) {
|
|
23925
|
+
const method = req.method ?? "GET";
|
|
23926
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
23927
|
+
const pathname = url.pathname;
|
|
23928
|
+
try {
|
|
23929
|
+
if (method === "GET" && pathname === "/") {
|
|
23930
|
+
const data = await freshPortalData(config.projectRoot, config.writeEnabled);
|
|
23931
|
+
const html = await renderLivePage(data);
|
|
23932
|
+
sendText(res, 200, "text/html; charset=utf-8", html);
|
|
23933
|
+
return;
|
|
23934
|
+
}
|
|
23935
|
+
if (method === "GET" && pathname === "/data") {
|
|
23936
|
+
const data = await freshPortalData(config.projectRoot, config.writeEnabled);
|
|
23937
|
+
sendJson(res, 200, data);
|
|
23938
|
+
return;
|
|
23939
|
+
}
|
|
23940
|
+
if (method === "GET" && pathname.startsWith("/static/")) {
|
|
23941
|
+
const rel = pathname.slice("/static/".length);
|
|
23942
|
+
const asset = await readStaticAsset(rel);
|
|
23943
|
+
if (!asset) {
|
|
23944
|
+
sendJson(res, 404, { error: "not-found", path: pathname });
|
|
23945
|
+
return;
|
|
23946
|
+
}
|
|
23947
|
+
res.writeHead(200, { "content-type": asset.contentType });
|
|
23948
|
+
res.end(asset.content);
|
|
23949
|
+
return;
|
|
23950
|
+
}
|
|
23951
|
+
if (method === "GET" && pathname === "/approve/dry-run") {
|
|
23952
|
+
const llm = url.searchParams.get("llm") !== "false";
|
|
23953
|
+
const preview = await dryRunApproveViaCli(config.projectRoot, llm);
|
|
23954
|
+
sendJson(res, 200, preview);
|
|
23955
|
+
return;
|
|
23956
|
+
}
|
|
23957
|
+
if (method === "POST" && pathname === "/approve") {
|
|
23958
|
+
if (!config.writeEnabled) {
|
|
23959
|
+
sendJson(res, 409, {
|
|
23960
|
+
error: "view-only",
|
|
23961
|
+
message: "This portal runs in view-only mode (--no-write); the Approve write action is disabled."
|
|
23962
|
+
});
|
|
23963
|
+
return;
|
|
23964
|
+
}
|
|
23965
|
+
const body = await readJsonBody(req);
|
|
23966
|
+
const llm = body.llm !== false;
|
|
23967
|
+
const result = await runApproveViaCli(config.projectRoot, llm);
|
|
23968
|
+
sendJson(res, 200, {
|
|
23969
|
+
ok: result.exitCode === 0,
|
|
23970
|
+
exitCode: result.exitCode,
|
|
23971
|
+
stdout: result.stdout,
|
|
23972
|
+
stderr: result.stderr
|
|
23973
|
+
});
|
|
23974
|
+
return;
|
|
23975
|
+
}
|
|
23976
|
+
sendJson(res, 404, { error: "not-found", method, path: pathname });
|
|
23977
|
+
} catch (err) {
|
|
23978
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23979
|
+
sendJson(res, 500, { error: "internal", message });
|
|
23980
|
+
}
|
|
23981
|
+
}
|
|
23982
|
+
|
|
23983
|
+
// src/portal/server/server.ts
|
|
23984
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
23985
|
+
function startServer(opts) {
|
|
23986
|
+
const config = { projectRoot: opts.projectRoot, writeEnabled: opts.writeEnabled };
|
|
23987
|
+
const server = createServer((req, res) => {
|
|
23988
|
+
void handleRequest(req, res, config);
|
|
23989
|
+
});
|
|
23990
|
+
return new Promise((resolve6, reject) => {
|
|
23991
|
+
server.on("error", reject);
|
|
23992
|
+
server.listen(opts.port, LOOPBACK_HOST, () => {
|
|
23993
|
+
const address = server.address();
|
|
23994
|
+
const port = typeof address === "object" && address ? address.port : opts.port;
|
|
23995
|
+
resolve6({
|
|
23996
|
+
url: `http://${LOOPBACK_HOST}:${port}`,
|
|
23997
|
+
port,
|
|
23998
|
+
close: () => new Promise((res, rej) => {
|
|
23999
|
+
server.close((err) => err ? rej(err) : res());
|
|
24000
|
+
})
|
|
24001
|
+
});
|
|
24002
|
+
});
|
|
24003
|
+
});
|
|
24004
|
+
}
|
|
24005
|
+
|
|
24006
|
+
// src/cli/portal.ts
|
|
24007
|
+
function registerPortalCommand(program2) {
|
|
24008
|
+
program2.command("portal").description("Open a read-only local web portal onto the graph and its verification state").option("--static", "Emit a self-contained static page instead of serving").option("--out <path>", "Output path for the static page (with --static)").option("--port <n>", "Port for the local loopback server", (v) => {
|
|
24009
|
+
const n = parseInt(v, 10);
|
|
24010
|
+
if (Number.isNaN(n) || n < 0 || n > 65535) {
|
|
24011
|
+
throw new InvalidArgumentError2("--port must be an integer between 0 and 65535.");
|
|
24012
|
+
}
|
|
24013
|
+
return n;
|
|
24014
|
+
}).option("--open", "Open the page in the default browser").option("--no-write", "View-only mode \u2014 disable the Approve write action").action(async (options) => {
|
|
24015
|
+
try {
|
|
24016
|
+
await runPortal(options);
|
|
24017
|
+
} catch (error) {
|
|
24018
|
+
abortOnUnexpectedError(error, "opening the portal");
|
|
24019
|
+
}
|
|
24020
|
+
});
|
|
24021
|
+
}
|
|
24022
|
+
async function runPortal(options) {
|
|
24023
|
+
const graph = await loadGraphOrAbort(process.cwd());
|
|
24024
|
+
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
24025
|
+
const projectRoot = projectRootFromGraph(graph.rootPath);
|
|
24026
|
+
if (options.static) {
|
|
24027
|
+
const data = await extractPortalData(projectRoot, { writeEnabled: options.write });
|
|
24028
|
+
const outPath = path61.resolve(projectRoot, options.out ?? "yg-portal.html");
|
|
24029
|
+
await emitStatic(data, outPath);
|
|
24030
|
+
process.stdout.write(`Portal page written to ${outPath}
|
|
24031
|
+
`);
|
|
24032
|
+
if (options.open) {
|
|
24033
|
+
await openInBrowser(outPath);
|
|
24034
|
+
}
|
|
24035
|
+
return;
|
|
24036
|
+
}
|
|
24037
|
+
await servePortal(projectRoot, options);
|
|
24038
|
+
}
|
|
24039
|
+
async function servePortal(projectRoot, options) {
|
|
24040
|
+
const handle = await startServer({
|
|
24041
|
+
projectRoot,
|
|
24042
|
+
port: options.port ?? DEFAULT_PORT,
|
|
24043
|
+
writeEnabled: options.write
|
|
24044
|
+
});
|
|
24045
|
+
const mode = options.write ? "" : " (view-only \u2014 Approve disabled)";
|
|
24046
|
+
process.stdout.write(`Portal running at ${handle.url}${mode}
|
|
24047
|
+
`);
|
|
24048
|
+
process.stdout.write("Press Ctrl+C to stop.\n");
|
|
24049
|
+
const shutdown = () => {
|
|
24050
|
+
void handle.close().finally(() => process.exit(0));
|
|
24051
|
+
};
|
|
24052
|
+
process.once("SIGINT", shutdown);
|
|
24053
|
+
process.once("SIGTERM", shutdown);
|
|
24054
|
+
if (options.open) {
|
|
24055
|
+
await openInBrowser(handle.url);
|
|
24056
|
+
}
|
|
24057
|
+
}
|
|
24058
|
+
var DEFAULT_PORT = 4317;
|
|
24059
|
+
async function openInBrowser(filePath) {
|
|
24060
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
24061
|
+
const platform = process.platform;
|
|
24062
|
+
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
24063
|
+
const args = platform === "win32" ? ["/c", "start", "", filePath] : [filePath];
|
|
24064
|
+
try {
|
|
24065
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
24066
|
+
child.on("error", (error) => {
|
|
24067
|
+
debugWrite(`[portal] openInBrowser spawn error: ${error instanceof Error ? error.message : String(error)}`);
|
|
24068
|
+
});
|
|
24069
|
+
child.unref();
|
|
24070
|
+
} catch (error) {
|
|
24071
|
+
debugWrite(`[portal] openInBrowser failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
24072
|
+
}
|
|
24073
|
+
}
|
|
24074
|
+
|
|
24075
|
+
// src/bin.ts
|
|
24076
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
24077
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
22592
24078
|
import { dirname as dirname2, join as join7 } from "path";
|
|
22593
|
-
var __filename3 =
|
|
22594
|
-
var
|
|
22595
|
-
var pkg = JSON.parse(
|
|
22596
|
-
var program = new
|
|
24079
|
+
var __filename3 = fileURLToPath6(import.meta.url);
|
|
24080
|
+
var __dirname4 = dirname2(__filename3);
|
|
24081
|
+
var pkg = JSON.parse(readFileSync11(join7(__dirname4, "..", "package.json"), "utf-8"));
|
|
24082
|
+
var program = new Command3();
|
|
22597
24083
|
program.name("yg").description("Yggdrasil \u2014 architectural knowledge infrastructure for AI agents").version(pkg.version);
|
|
22598
24084
|
registerInitCommand(program);
|
|
22599
24085
|
registerBuildCommand(program);
|
|
@@ -22610,6 +24096,7 @@ registerTypeSuggestCommand(program);
|
|
|
22610
24096
|
registerKnowledgeCommand(program);
|
|
22611
24097
|
registerSchemasCommand(program);
|
|
22612
24098
|
registerSuppressionsCommand(program);
|
|
24099
|
+
registerPortalCommand(program);
|
|
22613
24100
|
process.on("unhandledRejection", (reason) => {
|
|
22614
24101
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
22615
24102
|
process.stderr.write(`Error: ${msg}
|