@chrisdudek/yg 5.3.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1985 -397
- 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";
|
|
@@ -17,6 +17,17 @@ var DEFAULT_CONFIG = `version: "5.1.0"
|
|
|
17
17
|
quality:
|
|
18
18
|
max_direct_relations: 10
|
|
19
19
|
|
|
20
|
+
# Coverage \u2014 which files must belong to a node.
|
|
21
|
+
# Fresh projects start in "require nothing" mode: every unmapped file surfaces as
|
|
22
|
+
# a NON-blocking warning (not a blocking error), so your very first \`yg check\` is
|
|
23
|
+
# green while you adopt incrementally. Add a path prefix to \`required\` (e.g.
|
|
24
|
+
# "src/") to make that area blocking once you start mapping it. NOTE: an ABSENT
|
|
25
|
+
# coverage block defaults to requiring the WHOLE repo \u2014 this explicit empty list
|
|
26
|
+
# is what opts a fresh project into require-nothing.
|
|
27
|
+
coverage:
|
|
28
|
+
required: []
|
|
29
|
+
excluded: []
|
|
30
|
+
|
|
20
31
|
# Reviewer configuration added by: yg init
|
|
21
32
|
# (see yg schemas read config + yg knowledge read configuration)
|
|
22
33
|
|
|
@@ -128,7 +139,7 @@ Verification runs per \`(aspect, unit)\` pair. A **unit** is the subject of one
|
|
|
128
139
|
|
|
129
140
|
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
141
|
|
|
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.
|
|
142
|
+
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
143
|
|
|
133
144
|
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
145
|
|
|
@@ -1440,7 +1451,7 @@ var PROVIDER_DEFAULTS = {
|
|
|
1440
1451
|
"codex": { model: "o4-mini" },
|
|
1441
1452
|
"gemini-cli": { model: "gemini-2.5-flash" }
|
|
1442
1453
|
};
|
|
1443
|
-
async function parseConfig(filePath) {
|
|
1454
|
+
async function parseConfig(filePath, opts) {
|
|
1444
1455
|
const filename = path4.basename(filePath);
|
|
1445
1456
|
const content20 = await readFile4(filePath, "utf-8");
|
|
1446
1457
|
const baseRaw = parseYaml3(content20);
|
|
@@ -1451,7 +1462,7 @@ async function parseConfig(filePath) {
|
|
|
1451
1462
|
next: "restore the file from version control, or regenerate it via `yg init`"
|
|
1452
1463
|
}, "config-invalid");
|
|
1453
1464
|
}
|
|
1454
|
-
const overlay = await loadConfigOverlay(path4.dirname(filePath));
|
|
1465
|
+
const overlay = opts?.skipSecretsOverlay ? void 0 : await loadConfigOverlay(path4.dirname(filePath));
|
|
1455
1466
|
const raw = overlay ? deepMerge(baseRaw, overlay) : baseRaw;
|
|
1456
1467
|
const version = typeof raw.version === "string" ? raw.version.trim() : void 0;
|
|
1457
1468
|
const qualityRaw = raw.quality;
|
|
@@ -2889,6 +2900,11 @@ async function parseArchitecture(filePath) {
|
|
|
2889
2900
|
const nodeTypesObj = nodeTypesRaw ?? {};
|
|
2890
2901
|
const nodeTypes = {};
|
|
2891
2902
|
for (const [typeName, val] of Object.entries(nodeTypesObj)) {
|
|
2903
|
+
if (typeName === "*") {
|
|
2904
|
+
throw new Error(
|
|
2905
|
+
`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.`
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2892
2908
|
const entry = val;
|
|
2893
2909
|
if (!entry || typeof entry !== "object" || typeof entry.description !== "string" || entry.description.trim() === "") {
|
|
2894
2910
|
throw new Error(
|
|
@@ -2925,7 +2941,7 @@ async function parseArchitecture(filePath) {
|
|
|
2925
2941
|
`yg-architecture.yaml: node_types.${typeName}.parents`,
|
|
2926
2942
|
"parent type name"
|
|
2927
2943
|
) : void 0;
|
|
2928
|
-
const relations = parseRelations2(entry.relations, typeName);
|
|
2944
|
+
const { lists: relations, relationDefault } = parseRelations2(entry.relations, typeName);
|
|
2929
2945
|
let logRequired;
|
|
2930
2946
|
if (entry.log_required !== void 0) {
|
|
2931
2947
|
if (typeof entry.log_required !== "boolean") {
|
|
@@ -2955,6 +2971,7 @@ async function parseArchitecture(filePath) {
|
|
|
2955
2971
|
...aspectStatus && { aspectStatus },
|
|
2956
2972
|
parents: parents && parents.length > 0 ? parents : void 0,
|
|
2957
2973
|
relations,
|
|
2974
|
+
...relationDefault !== void 0 && { relationDefault },
|
|
2958
2975
|
...logRequired !== void 0 && { log_required: logRequired },
|
|
2959
2976
|
...when !== void 0 && { when },
|
|
2960
2977
|
...enforce !== void 0 && { enforce }
|
|
@@ -2966,33 +2983,42 @@ async function parseArchitecture(filePath) {
|
|
|
2966
2983
|
}
|
|
2967
2984
|
function parseRelations2(relationsRaw, typeName) {
|
|
2968
2985
|
if (relationsRaw === void 0) {
|
|
2969
|
-
return void 0;
|
|
2986
|
+
return { lists: void 0 };
|
|
2970
2987
|
}
|
|
2971
2988
|
if (typeof relationsRaw !== "object" || Array.isArray(relationsRaw)) {
|
|
2972
2989
|
throw new Error(`yg-architecture.yaml: node_types.${typeName}.relations must be an object`);
|
|
2973
2990
|
}
|
|
2974
2991
|
const relations = {};
|
|
2975
|
-
|
|
2976
|
-
|
|
2992
|
+
let relationDefault;
|
|
2993
|
+
for (const [key, value] of Object.entries(relationsRaw)) {
|
|
2994
|
+
if (key === "default") {
|
|
2995
|
+
if (value !== "allow" && value !== "deny") {
|
|
2996
|
+
throw new Error(
|
|
2997
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.default must be 'allow' or 'deny' (got: ${JSON.stringify(value)})`
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
relationDefault = value;
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (!VALID_RELATION_TYPES.has(key)) {
|
|
2977
3004
|
throw new Error(
|
|
2978
|
-
`yg-architecture.yaml: node_types.${typeName}.relations: unknown relation type '${
|
|
3005
|
+
`yg-architecture.yaml: node_types.${typeName}.relations: unknown relation type '${key}' (valid types: ${Array.from(VALID_RELATION_TYPES).join(", ")}; or 'default')`
|
|
2979
3006
|
);
|
|
2980
3007
|
}
|
|
2981
|
-
if (!Array.isArray(
|
|
3008
|
+
if (!Array.isArray(value)) {
|
|
2982
3009
|
throw new Error(
|
|
2983
|
-
`yg-architecture.yaml: node_types.${typeName}.relations.${
|
|
3010
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.${key} must be an array`
|
|
2984
3011
|
);
|
|
2985
3012
|
}
|
|
2986
3013
|
const targetStrings = assertStringArray(
|
|
2987
|
-
|
|
2988
|
-
`yg-architecture.yaml: node_types.${typeName}.relations.${
|
|
3014
|
+
value,
|
|
3015
|
+
`yg-architecture.yaml: node_types.${typeName}.relations.${key}`,
|
|
2989
3016
|
"target type name"
|
|
2990
3017
|
);
|
|
2991
|
-
|
|
2992
|
-
relations[relType] = targetStrings;
|
|
2993
|
-
}
|
|
3018
|
+
relations[key] = targetStrings;
|
|
2994
3019
|
}
|
|
2995
|
-
|
|
3020
|
+
const hasLists = Object.keys(relations).length > 0;
|
|
3021
|
+
return { lists: hasLists ? relations : {}, relationDefault };
|
|
2996
3022
|
}
|
|
2997
3023
|
function assertStringArray(arr, fieldPath, itemLabel) {
|
|
2998
3024
|
const offenders = arr.map((value, index) => ({ value, index })).filter((e) => typeof e.value !== "string");
|
|
@@ -3133,7 +3159,7 @@ async function loadGraph(projectRoot, options = {}) {
|
|
|
3133
3159
|
let configErrorMessage;
|
|
3134
3160
|
let config = FALLBACK_CONFIG;
|
|
3135
3161
|
try {
|
|
3136
|
-
config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"));
|
|
3162
|
+
config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"), { skipSecretsOverlay: options.noSecrets });
|
|
3137
3163
|
} catch (error) {
|
|
3138
3164
|
if (error instanceof ConfigParseError) {
|
|
3139
3165
|
configErrorMessage = error.messageData;
|
|
@@ -3322,7 +3348,8 @@ ${msg.next}`;
|
|
|
3322
3348
|
}
|
|
3323
3349
|
|
|
3324
3350
|
// src/io/lock-store.ts
|
|
3325
|
-
import { readFileSync, unlinkSync } from "fs";
|
|
3351
|
+
import { readFileSync, unlinkSync, existsSync as existsSync2 } from "fs";
|
|
3352
|
+
import { createHash } from "crypto";
|
|
3326
3353
|
import path11 from "path";
|
|
3327
3354
|
|
|
3328
3355
|
// src/model/lock.ts
|
|
@@ -3368,6 +3395,20 @@ function logsLockPath(yggRoot) {
|
|
|
3368
3395
|
function detLockPath(yggRoot) {
|
|
3369
3396
|
return path11.join(yggRoot, LOCK_DET_FILE_NAME);
|
|
3370
3397
|
}
|
|
3398
|
+
function committedLockContentHash(yggRoot) {
|
|
3399
|
+
const parts = [];
|
|
3400
|
+
for (const file of [nondetLockPath(yggRoot), logsLockPath(yggRoot)]) {
|
|
3401
|
+
if (!existsSync2(file)) continue;
|
|
3402
|
+
try {
|
|
3403
|
+
parts.push(readFileSync(file));
|
|
3404
|
+
} catch {
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
if (parts.length === 0) return "";
|
|
3408
|
+
const hash = createHash("sha256");
|
|
3409
|
+
for (const p2 of parts) hash.update(p2);
|
|
3410
|
+
return hash.digest("hex");
|
|
3411
|
+
}
|
|
3371
3412
|
function recoveryNext(ctx) {
|
|
3372
3413
|
if (ctx.committed) {
|
|
3373
3414
|
return `restore the file from git (\`git checkout HEAD -- .yggdrasil/${ctx.fileName}\`), OR
|
|
@@ -3714,7 +3755,7 @@ import { rm as rm3, stat as stat3 } from "fs/promises";
|
|
|
3714
3755
|
|
|
3715
3756
|
// src/migrations/split-lock.ts
|
|
3716
3757
|
import path12 from "path";
|
|
3717
|
-
import { readdirSync, existsSync as
|
|
3758
|
+
import { readdirSync, existsSync as existsSync3 } from "fs";
|
|
3718
3759
|
import { readFile as readFile10, writeFile as writeFile4, rm as rm2 } from "fs/promises";
|
|
3719
3760
|
async function splitLock(yggRoot) {
|
|
3720
3761
|
const legacy = readLegacyLock(yggRoot);
|
|
@@ -3732,7 +3773,7 @@ async function splitLock(yggRoot) {
|
|
|
3732
3773
|
function scanDeterministicAspectIds(yggRoot) {
|
|
3733
3774
|
const aspectsRoot = path12.join(yggRoot, "aspects");
|
|
3734
3775
|
const ids = /* @__PURE__ */ new Set();
|
|
3735
|
-
if (!
|
|
3776
|
+
if (!existsSync3(aspectsRoot)) return ids;
|
|
3736
3777
|
const walk2 = (dir) => {
|
|
3737
3778
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3738
3779
|
const full = path12.join(dir, entry.name);
|
|
@@ -3828,6 +3869,9 @@ async function ensureGitattributes(repoRoot) {
|
|
|
3828
3869
|
var YGGDRASIL_GITIGNORE_LINES = [
|
|
3829
3870
|
"yg-secrets.yaml",
|
|
3830
3871
|
".symbols-cache/",
|
|
3872
|
+
// Content-addressed per-file AST fact cache: a local speed cache the relation pass rebuilds
|
|
3873
|
+
// free on the next run; never committed.
|
|
3874
|
+
".ast-cache/",
|
|
3831
3875
|
".debug.log",
|
|
3832
3876
|
// Deterministic-verdict lock: a local cache rebuilt for free by
|
|
3833
3877
|
// `yg check --approve --only-deterministic`; never committed.
|
|
@@ -3857,6 +3901,13 @@ async function ensureYggdrasilGitignore(yggRoot) {
|
|
|
3857
3901
|
}
|
|
3858
3902
|
var API_PROVIDERS = ["anthropic", "openai", "google", "openai-compatible", "ollama"];
|
|
3859
3903
|
var CLI_PROVIDERS = ["claude-code", "codex", "gemini-cli"];
|
|
3904
|
+
var ALL_PROVIDERS = [...CLI_PROVIDERS, ...API_PROVIDERS];
|
|
3905
|
+
var API_KEY_ENV = {
|
|
3906
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
3907
|
+
openai: "OPENAI_API_KEY",
|
|
3908
|
+
google: "GOOGLE_API_KEY",
|
|
3909
|
+
"openai-compatible": "OPENAI_API_KEY"
|
|
3910
|
+
};
|
|
3860
3911
|
var CLAUDE_CODE_ALIASES = [
|
|
3861
3912
|
{ value: "haiku", label: "haiku" },
|
|
3862
3913
|
{ value: "sonnet", label: "sonnet" },
|
|
@@ -3951,15 +4002,16 @@ async function promptModelText(provider) {
|
|
|
3951
4002
|
async function runReviewerConfigFlow() {
|
|
3952
4003
|
const provider = await p.select({
|
|
3953
4004
|
message: "Which provider should verify your code?",
|
|
4005
|
+
initialValue: "claude-code",
|
|
3954
4006
|
options: [
|
|
3955
|
-
{ value: "
|
|
3956
|
-
{ value: "
|
|
3957
|
-
{ value: "
|
|
3958
|
-
{ value: "ollama", label: "Ollama", hint: "Local \u2014 no API costs" },
|
|
3959
|
-
{ value: "
|
|
3960
|
-
{ value: "
|
|
3961
|
-
{ value: "
|
|
3962
|
-
{ value: "
|
|
4007
|
+
{ value: "claude-code", label: "Claude Code", hint: "CLI \u2014 free, no API key; uses installed claude" },
|
|
4008
|
+
{ value: "codex", label: "Codex", hint: "CLI \u2014 free, no API key; uses installed codex" },
|
|
4009
|
+
{ value: "gemini-cli", label: "Gemini CLI", hint: "CLI \u2014 free, no API key; uses installed gemini" },
|
|
4010
|
+
{ value: "ollama", label: "Ollama", hint: "Local \u2014 no API costs; needs a local install" },
|
|
4011
|
+
{ value: "anthropic", label: "Anthropic", hint: "API key \u2014 Claude models" },
|
|
4012
|
+
{ value: "openai", label: "OpenAI", hint: "API key \u2014 GPT models" },
|
|
4013
|
+
{ value: "google", label: "Google", hint: "API key \u2014 Gemini models" },
|
|
4014
|
+
{ value: "openai-compatible", label: "OpenAI-compatible", hint: "API key \u2014 custom endpoint" }
|
|
3963
4015
|
]
|
|
3964
4016
|
});
|
|
3965
4017
|
assertNotCancelled(provider);
|
|
@@ -4090,9 +4142,9 @@ async function freshInit(projectRoot) {
|
|
|
4090
4142
|
const yggRoot = path14.join(projectRoot, ".yggdrasil");
|
|
4091
4143
|
if (!isTTY()) {
|
|
4092
4144
|
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4093
|
-
what: "yg init requires an interactive terminal.",
|
|
4094
|
-
why: "
|
|
4095
|
-
next:
|
|
4145
|
+
what: "yg init requires an interactive terminal (no --provider given).",
|
|
4146
|
+
why: "The interactive wizard needs prompts to configure platform and reviewer. Docker, devcontainer, and CI runs have no TTY.",
|
|
4147
|
+
next: `Run yg init in an interactive terminal, OR bootstrap non-interactively: yg init --platform <name> --provider <name> [--model <m>] [--endpoint <url>]. Supported providers: ${ALL_PROVIDERS.join(", ")}.`
|
|
4096
4148
|
})}
|
|
4097
4149
|
`));
|
|
4098
4150
|
process.exit(1);
|
|
@@ -4106,7 +4158,7 @@ async function freshInit(projectRoot) {
|
|
|
4106
4158
|
const platform = await promptPlatform();
|
|
4107
4159
|
p.log.step("Step 2: Reviewer provider");
|
|
4108
4160
|
p.log.info(
|
|
4109
|
-
"The reviewer checks your source code against aspect rules during yg check --approve.\n
|
|
4161
|
+
"The reviewer checks your source code against aspect rules during yg check --approve.\n If you already run an agent CLI (Claude Code, Codex, Gemini), pick it \u2014 the\n reviewer then needs no API key and adds no separate API bill. Ollama runs\n locally with no API cost. API providers (Anthropic, OpenAI, Google) need a key."
|
|
4110
4162
|
);
|
|
4111
4163
|
const reviewerConfig = await runReviewerConfigFlow();
|
|
4112
4164
|
await createYggdrasilStructure(projectRoot, yggRoot, platform);
|
|
@@ -4126,6 +4178,54 @@ async function createYggdrasilStructure(projectRoot, yggRoot, platform) {
|
|
|
4126
4178
|
await ensureYggdrasilGitignore(yggRoot);
|
|
4127
4179
|
await installRulesForPlatform(projectRoot, platform);
|
|
4128
4180
|
}
|
|
4181
|
+
async function freshInitNonInteractive(projectRoot, yggRoot, opts) {
|
|
4182
|
+
const { platform, provider } = opts;
|
|
4183
|
+
const model = opts.model?.trim();
|
|
4184
|
+
if (!model) {
|
|
4185
|
+
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4186
|
+
what: "--model is required for non-interactive init.",
|
|
4187
|
+
why: "Non-interactive init records the reviewer model verbatim and applies no default \u2014 the model must be named explicitly.",
|
|
4188
|
+
next: `Re-run naming a model: yg init --platform ${platform} --provider ${provider} --model <name>.`
|
|
4189
|
+
})}
|
|
4190
|
+
`));
|
|
4191
|
+
process.exit(1);
|
|
4192
|
+
}
|
|
4193
|
+
let endpoint = opts.endpoint?.trim() || void 0;
|
|
4194
|
+
if (needsEndpoint(provider) && !endpoint) {
|
|
4195
|
+
if (provider === "ollama") {
|
|
4196
|
+
endpoint = "http://localhost:11434";
|
|
4197
|
+
} else {
|
|
4198
|
+
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4199
|
+
what: `--endpoint is required for provider '${provider}'.`,
|
|
4200
|
+
why: "An OpenAI-compatible provider has no default base URL \u2014 the reviewer needs an endpoint to call.",
|
|
4201
|
+
next: `Re-run naming an endpoint: yg init --platform ${platform} --provider ${provider} --model ${model} --endpoint <url>.`
|
|
4202
|
+
})}
|
|
4203
|
+
`));
|
|
4204
|
+
process.exit(1);
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
await createYggdrasilStructure(projectRoot, yggRoot, platform);
|
|
4208
|
+
await writeReviewerConfig(yggRoot, { provider, model, endpoint });
|
|
4209
|
+
if (needsApiKey(provider)) {
|
|
4210
|
+
const envVar = API_KEY_ENV[provider];
|
|
4211
|
+
const apiKey = (envVar ? process.env[envVar] : void 0)?.trim();
|
|
4212
|
+
if (apiKey) {
|
|
4213
|
+
await writeSecretsFile(yggRoot, apiKey);
|
|
4214
|
+
} else {
|
|
4215
|
+
process.stdout.write(chalk2.yellow(`${buildIssueMessage({
|
|
4216
|
+
what: `No API key found${envVar ? ` in $${envVar}` : ""}; wrote the config without one.`,
|
|
4217
|
+
why: "An API provider needs a key before the reviewer can run; init records the config anyway so setup is not blocked.",
|
|
4218
|
+
next: `Set ${envVar ?? "the provider API key environment variable"} (or add the key to .yggdrasil/yg-secrets.yaml) before running yg check --approve.`
|
|
4219
|
+
})}
|
|
4220
|
+
`));
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
await ensureGitattributes(projectRoot);
|
|
4224
|
+
process.stdout.write(chalk2.green(
|
|
4225
|
+
`Yggdrasil initialized (platform: ${platform}, provider: ${provider}, model: ${model}). Run yg check to get started.
|
|
4226
|
+
`
|
|
4227
|
+
));
|
|
4228
|
+
}
|
|
4129
4229
|
async function runVersionUpgrade2(projectRoot, yggRoot, platform) {
|
|
4130
4230
|
const { migrationActions, migrationWarnings, withheld } = await runVersionUpgrade({
|
|
4131
4231
|
yggRoot,
|
|
@@ -4216,7 +4316,7 @@ async function existingInit(projectRoot) {
|
|
|
4216
4316
|
}
|
|
4217
4317
|
}
|
|
4218
4318
|
function registerInitCommand(program2) {
|
|
4219
|
-
program2.command("init").description("Initialize Yggdrasil graph in current project").option("--upgrade", "Non-interactive: refresh rules").option("--platform <name>", `Platform for rules file (${PLATFORMS.join(", ")})`).action(async (options) => {
|
|
4319
|
+
program2.command("init").description("Initialize Yggdrasil graph in current project").option("--upgrade", "Non-interactive: refresh rules").option("--platform <name>", `Platform for rules file (${PLATFORMS.join(", ")})`).option("--provider <name>", `Non-interactive fresh init: reviewer provider (${ALL_PROVIDERS.join(", ")})`).option("--model <name>", "Non-interactive fresh init: reviewer model (required)").option("--endpoint <url>", "Non-interactive fresh init: reviewer endpoint (ollama / openai-compatible)").action(async (options) => {
|
|
4220
4320
|
try {
|
|
4221
4321
|
const projectRoot = process.cwd();
|
|
4222
4322
|
const yggRoot = path14.join(projectRoot, ".yggdrasil");
|
|
@@ -4326,6 +4426,40 @@ function registerInitCommand(program2) {
|
|
|
4326
4426
|
}
|
|
4327
4427
|
if (exists) {
|
|
4328
4428
|
await existingInit(projectRoot);
|
|
4429
|
+
} else if (options.provider) {
|
|
4430
|
+
if (!options.platform) {
|
|
4431
|
+
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4432
|
+
what: "--provider requires --platform for non-interactive init.",
|
|
4433
|
+
why: "A fresh graph must install the rules file for a specific agent platform; there is no prompt to ask in non-interactive mode.",
|
|
4434
|
+
next: `Pass --platform <name>. Supported: ${PLATFORMS.join(", ")}.`
|
|
4435
|
+
})}
|
|
4436
|
+
`));
|
|
4437
|
+
process.exit(1);
|
|
4438
|
+
}
|
|
4439
|
+
if (!PLATFORMS.includes(options.platform)) {
|
|
4440
|
+
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4441
|
+
what: `Unknown platform '${options.platform}'.`,
|
|
4442
|
+
why: "The --platform value must match one of the supported agent platforms.",
|
|
4443
|
+
next: `Use one of: ${PLATFORMS.join(", ")}`
|
|
4444
|
+
})}
|
|
4445
|
+
`));
|
|
4446
|
+
process.exit(1);
|
|
4447
|
+
}
|
|
4448
|
+
if (!ALL_PROVIDERS.includes(options.provider)) {
|
|
4449
|
+
process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
|
|
4450
|
+
what: `Unknown provider '${options.provider}'.`,
|
|
4451
|
+
why: "The --provider value must match one of the supported reviewer providers.",
|
|
4452
|
+
next: `Use one of: ${ALL_PROVIDERS.join(", ")}`
|
|
4453
|
+
})}
|
|
4454
|
+
`));
|
|
4455
|
+
process.exit(1);
|
|
4456
|
+
}
|
|
4457
|
+
await freshInitNonInteractive(projectRoot, yggRoot, {
|
|
4458
|
+
platform: options.platform,
|
|
4459
|
+
provider: options.provider,
|
|
4460
|
+
model: options.model,
|
|
4461
|
+
endpoint: options.endpoint
|
|
4462
|
+
});
|
|
4329
4463
|
} else {
|
|
4330
4464
|
await freshInit(projectRoot);
|
|
4331
4465
|
}
|
|
@@ -4658,6 +4792,15 @@ function attachmentMachineOrigin(att, node) {
|
|
|
4658
4792
|
return `port:${att.port.name}@${att.port.target}`;
|
|
4659
4793
|
}
|
|
4660
4794
|
}
|
|
4795
|
+
function hasNonDraftEffectiveAspects(node, graph) {
|
|
4796
|
+
const statuses = computeEffectiveAspectStatuses(node, graph);
|
|
4797
|
+
for (const [aspectId, s] of statuses) {
|
|
4798
|
+
if (s === "draft") continue;
|
|
4799
|
+
if (isAggregateAspect(graph, aspectId)) continue;
|
|
4800
|
+
return true;
|
|
4801
|
+
}
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4661
4804
|
function isAggregateAspect(graph, aspectId) {
|
|
4662
4805
|
return graph.aspects.find((a) => a.id === aspectId)?.reviewer.type === "aggregate";
|
|
4663
4806
|
}
|
|
@@ -4886,11 +5029,11 @@ function normPath(p2) {
|
|
|
4886
5029
|
}
|
|
4887
5030
|
function countDependents(graph, nodePath) {
|
|
4888
5031
|
const paths = [];
|
|
4889
|
-
for (const [
|
|
5032
|
+
for (const [path62, node] of graph.nodes) {
|
|
4890
5033
|
const hasRelation = (node.meta.relations ?? []).some(
|
|
4891
5034
|
(r) => r.target === nodePath && (STRUCTURAL_RELATION_TYPES.has(r.type) || EVENT_RELATION_TYPES.has(r.type))
|
|
4892
5035
|
);
|
|
4893
|
-
if (hasRelation) paths.push(
|
|
5036
|
+
if (hasRelation) paths.push(path62);
|
|
4894
5037
|
}
|
|
4895
5038
|
return { count: paths.length, paths };
|
|
4896
5039
|
}
|
|
@@ -5457,7 +5600,7 @@ function renderNode(node, indent, lines) {
|
|
|
5457
5600
|
// src/io/hash.ts
|
|
5458
5601
|
import { readFile as readFile14, readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
5459
5602
|
import path16 from "path";
|
|
5460
|
-
import { createHash } from "crypto";
|
|
5603
|
+
import { createHash as createHash2 } from "crypto";
|
|
5461
5604
|
import { createRequire as createRequire2 } from "module";
|
|
5462
5605
|
|
|
5463
5606
|
// src/io/repo-scanner.ts
|
|
@@ -5583,10 +5726,10 @@ function isIgnoredByStack2(candidatePath, stack) {
|
|
|
5583
5726
|
return false;
|
|
5584
5727
|
}
|
|
5585
5728
|
function hashString(content20) {
|
|
5586
|
-
return
|
|
5729
|
+
return createHash2("sha256").update(content20).digest("hex");
|
|
5587
5730
|
}
|
|
5588
5731
|
function hashBytes(bytes) {
|
|
5589
|
-
return
|
|
5732
|
+
return createHash2("sha256").update(normalizeLineEndings(bytes)).digest("hex");
|
|
5590
5733
|
}
|
|
5591
5734
|
async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
|
|
5592
5735
|
let stack = options.gitignoreStack ?? [];
|
|
@@ -5709,19 +5852,19 @@ function checkArchitectureParentCycles(graph) {
|
|
|
5709
5852
|
const color = new Map(typeIds.map((id) => [id, WHITE]));
|
|
5710
5853
|
const backEdges = /* @__PURE__ */ new Set();
|
|
5711
5854
|
const recordedCycles = [];
|
|
5712
|
-
function dfs(typeId,
|
|
5855
|
+
function dfs(typeId, path62) {
|
|
5713
5856
|
if (color.get(typeId) === GRAY) {
|
|
5714
|
-
const cycleStart =
|
|
5715
|
-
if (cycleStart !== -1) recordedCycles.push([...
|
|
5716
|
-
const from =
|
|
5857
|
+
const cycleStart = path62.indexOf(typeId);
|
|
5858
|
+
if (cycleStart !== -1) recordedCycles.push([...path62.slice(cycleStart), typeId]);
|
|
5859
|
+
const from = path62[path62.length - 1];
|
|
5717
5860
|
if (from !== void 0) backEdges.add(`${from}->${typeId}`);
|
|
5718
5861
|
return;
|
|
5719
5862
|
}
|
|
5720
5863
|
if (color.get(typeId) === BLACK) return;
|
|
5721
5864
|
color.set(typeId, GRAY);
|
|
5722
|
-
|
|
5723
|
-
for (const parent of types[typeId]?.parents ?? []) dfs(parent,
|
|
5724
|
-
|
|
5865
|
+
path62.push(typeId);
|
|
5866
|
+
for (const parent of types[typeId]?.parents ?? []) dfs(parent, path62);
|
|
5867
|
+
path62.pop();
|
|
5725
5868
|
color.set(typeId, BLACK);
|
|
5726
5869
|
}
|
|
5727
5870
|
for (const id of typeIds) {
|
|
@@ -5947,18 +6090,23 @@ function checkArchitectureRelations(graph) {
|
|
|
5947
6090
|
const issues = [];
|
|
5948
6091
|
for (const [nodePath, node] of graph.nodes) {
|
|
5949
6092
|
const typeConfig = graph.architecture.node_types[node.meta.type];
|
|
5950
|
-
if (!typeConfig
|
|
6093
|
+
if (!typeConfig || !node.meta.relations || node.meta.relations.length === 0) {
|
|
6094
|
+
continue;
|
|
6095
|
+
}
|
|
6096
|
+
const lists = typeConfig.relations;
|
|
6097
|
+
const def = typeConfig.relationDefault ?? "allow";
|
|
6098
|
+
if ((!lists || Object.keys(lists).length === 0) && def === "allow") {
|
|
5951
6099
|
continue;
|
|
5952
6100
|
}
|
|
5953
6101
|
for (const rel of node.meta.relations) {
|
|
5954
|
-
const
|
|
5955
|
-
if (!allowedTypes) continue;
|
|
6102
|
+
const allowed = lists?.[rel.type];
|
|
5956
6103
|
const target = graph.nodes.get(rel.target);
|
|
5957
6104
|
if (!target) continue;
|
|
5958
|
-
if (
|
|
6105
|
+
if (allowed !== void 0) {
|
|
6106
|
+
if (allowed.includes("*") || allowed.includes(target.meta.type)) continue;
|
|
5959
6107
|
const msgData = {
|
|
5960
6108
|
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}': [${
|
|
6109
|
+
why: `Allowed targets for '${rel.type}' from type '${node.meta.type}': [${allowed.join(", ")}]`,
|
|
5962
6110
|
next: `Either change the relation type, change the target node's type, or update yg-architecture.yaml to allow this relation.`
|
|
5963
6111
|
};
|
|
5964
6112
|
issues.push({
|
|
@@ -5969,6 +6117,20 @@ function checkArchitectureRelations(graph) {
|
|
|
5969
6117
|
...issueMsg(msgData),
|
|
5970
6118
|
messageData: msgData
|
|
5971
6119
|
});
|
|
6120
|
+
} else if (def === "deny") {
|
|
6121
|
+
const msgData = {
|
|
6122
|
+
what: `Relation '${rel.type}' from '${nodePath}' to '${rel.target}' (type '${target.meta.type}') is not allowed by the architecture.`,
|
|
6123
|
+
why: `Type '${node.meta.type}' denies relation '${rel.type}' by default (relations.default: deny, and no allow-list entry permits this target).`,
|
|
6124
|
+
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.`
|
|
6125
|
+
};
|
|
6126
|
+
issues.push({
|
|
6127
|
+
severity: "error",
|
|
6128
|
+
code: "relation-target-forbidden",
|
|
6129
|
+
rule: "invalid-relation-target",
|
|
6130
|
+
nodePath,
|
|
6131
|
+
...issueMsg(msgData),
|
|
6132
|
+
messageData: msgData
|
|
6133
|
+
});
|
|
5972
6134
|
}
|
|
5973
6135
|
}
|
|
5974
6136
|
}
|
|
@@ -7860,7 +8022,7 @@ async function computeSourceFingerprint(graph, nodePath) {
|
|
|
7860
8022
|
}
|
|
7861
8023
|
|
|
7862
8024
|
// src/core/log/log-gate.ts
|
|
7863
|
-
import { createHash as
|
|
8025
|
+
import { createHash as createHash3 } from "crypto";
|
|
7864
8026
|
import path22 from "path";
|
|
7865
8027
|
|
|
7866
8028
|
// src/core/parsing/log-parser.ts
|
|
@@ -7956,7 +8118,7 @@ function computeLogBaselineFromContent(content20) {
|
|
|
7956
8118
|
const prefix = bytes.subarray(0, newest.offsetEnd);
|
|
7957
8119
|
return {
|
|
7958
8120
|
last_entry_datetime: newest.datetime,
|
|
7959
|
-
prefix_hash:
|
|
8121
|
+
prefix_hash: createHash3("sha256").update(prefix).digest("hex")
|
|
7960
8122
|
};
|
|
7961
8123
|
}
|
|
7962
8124
|
|
|
@@ -8184,11 +8346,11 @@ function registerTreeCommand(program2) {
|
|
|
8184
8346
|
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
8185
8347
|
let roots;
|
|
8186
8348
|
if (options.root?.trim()) {
|
|
8187
|
-
const
|
|
8188
|
-
const node = graph.nodes.get(
|
|
8349
|
+
const path62 = options.root.trim().replace(/\/$/, "");
|
|
8350
|
+
const node = graph.nodes.get(path62);
|
|
8189
8351
|
if (!node) {
|
|
8190
8352
|
process.stderr.write(chalk4.red(buildIssueMessage({
|
|
8191
|
-
what: `Node '${
|
|
8353
|
+
what: `Node '${path62}' not found.`,
|
|
8192
8354
|
why: `The --root path must be a valid node path in the graph.`,
|
|
8193
8355
|
next: `Run yg tree (no --root) to list all nodes, then pick a valid path.`
|
|
8194
8356
|
}) + "\n"));
|
|
@@ -8399,14 +8561,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
|
|
|
8399
8561
|
}
|
|
8400
8562
|
const chains = [];
|
|
8401
8563
|
for (const node of transitiveOnly) {
|
|
8402
|
-
const
|
|
8564
|
+
const path62 = [];
|
|
8403
8565
|
let current = node;
|
|
8404
8566
|
while (current) {
|
|
8405
|
-
|
|
8567
|
+
path62.unshift(current);
|
|
8406
8568
|
current = parent.get(current);
|
|
8407
8569
|
}
|
|
8408
|
-
if (
|
|
8409
|
-
chains.push(
|
|
8570
|
+
if (path62.length >= 3) {
|
|
8571
|
+
chains.push(path62.slice(1).map((p2) => `<- ${p2}`).join(" "));
|
|
8410
8572
|
}
|
|
8411
8573
|
}
|
|
8412
8574
|
return chains.sort();
|
|
@@ -8438,14 +8600,14 @@ function collectIndirectDependents(graph, directlyAffected) {
|
|
|
8438
8600
|
}
|
|
8439
8601
|
for (const [node] of parent) {
|
|
8440
8602
|
if (directSet.has(node)) continue;
|
|
8441
|
-
const
|
|
8603
|
+
const path62 = [node];
|
|
8442
8604
|
let current = node;
|
|
8443
8605
|
while (parent.has(current)) {
|
|
8444
8606
|
current = parent.get(current);
|
|
8445
|
-
|
|
8607
|
+
path62.push(current);
|
|
8446
8608
|
}
|
|
8447
|
-
const chain =
|
|
8448
|
-
const depth =
|
|
8609
|
+
const chain = path62.map((p2) => `<- ${p2}`).join(" ");
|
|
8610
|
+
const depth = path62.length;
|
|
8449
8611
|
const existing = bestChain.get(node);
|
|
8450
8612
|
if (!existing || depth < existing.depth) {
|
|
8451
8613
|
bestChain.set(node, { chain, depth });
|
|
@@ -8546,14 +8708,14 @@ import { register } from "module";
|
|
|
8546
8708
|
import { pathToFileURL } from "url";
|
|
8547
8709
|
import path24 from "path";
|
|
8548
8710
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8549
|
-
import { existsSync as
|
|
8711
|
+
import { existsSync as existsSync4 } from "fs";
|
|
8550
8712
|
var __filename = fileURLToPath2(import.meta.url);
|
|
8551
8713
|
var __dirname = path24.dirname(__filename);
|
|
8552
8714
|
var registered = false;
|
|
8553
8715
|
function ensureLoaderRegistered() {
|
|
8554
8716
|
if (registered) return;
|
|
8555
8717
|
let implPath = path24.resolve(__dirname, "./loader-hook-impl.js");
|
|
8556
|
-
if (!
|
|
8718
|
+
if (!existsSync4(implPath)) {
|
|
8557
8719
|
const pkgRoot = path24.resolve(__dirname, "../../");
|
|
8558
8720
|
implPath = path24.resolve(pkgRoot, "dist/loader-hook-impl.js");
|
|
8559
8721
|
}
|
|
@@ -8565,9 +8727,9 @@ function ensureLoaderRegistered() {
|
|
|
8565
8727
|
import * as fs from "fs";
|
|
8566
8728
|
import * as path25 from "path";
|
|
8567
8729
|
var UndeclaredFsReadError = class extends Error {
|
|
8568
|
-
constructor(
|
|
8569
|
-
super(`structure-aspect-undeclared-fs-read: ${
|
|
8570
|
-
this.path =
|
|
8730
|
+
constructor(path62) {
|
|
8731
|
+
super(`structure-aspect-undeclared-fs-read: ${path62}`);
|
|
8732
|
+
this.path = path62;
|
|
8571
8733
|
this.name = "UndeclaredFsReadError";
|
|
8572
8734
|
}
|
|
8573
8735
|
path;
|
|
@@ -8770,7 +8932,11 @@ function createCtxGraph(params) {
|
|
|
8770
8932
|
}
|
|
8771
8933
|
return {
|
|
8772
8934
|
node(id) {
|
|
8773
|
-
|
|
8935
|
+
const self = graph.nodes.get(currentNodePath);
|
|
8936
|
+
if (self) recordGraphNode(self);
|
|
8937
|
+
if (!allowed.has(id)) {
|
|
8938
|
+
assertAllowed(id);
|
|
8939
|
+
}
|
|
8774
8940
|
const m = graph.nodes.get(id);
|
|
8775
8941
|
if (!m) {
|
|
8776
8942
|
if (recorder) recorder.recordGraphNodeAbsent(id);
|
|
@@ -8851,8 +9017,9 @@ import { parse as parseTomlSmol } from "smol-toml";
|
|
|
8851
9017
|
import { Parser, Language } from "web-tree-sitter";
|
|
8852
9018
|
import path27 from "path";
|
|
8853
9019
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8854
|
-
import { existsSync as
|
|
9020
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
8855
9021
|
import { createRequire as createRequire3 } from "module";
|
|
9022
|
+
import { createHash as createHash4 } from "crypto";
|
|
8856
9023
|
var _require = createRequire3(import.meta.url);
|
|
8857
9024
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
8858
9025
|
var __dirname2 = path27.dirname(__filename2);
|
|
@@ -8863,6 +9030,19 @@ var GRAMMAR_DIRS = [
|
|
|
8863
9030
|
var initPromise = null;
|
|
8864
9031
|
var langCache = /* @__PURE__ */ new Map();
|
|
8865
9032
|
var parserCache = /* @__PURE__ */ new Map();
|
|
9033
|
+
var wasmHashCache = /* @__PURE__ */ new Map();
|
|
9034
|
+
function grammarWasmHash(extension) {
|
|
9035
|
+
const cached = wasmHashCache.get(extension);
|
|
9036
|
+
if (cached !== void 0) return cached;
|
|
9037
|
+
const info = getGrammarForExtension(extension);
|
|
9038
|
+
if (!info) {
|
|
9039
|
+
throw new Error(`no grammar for extension '${extension}'`);
|
|
9040
|
+
}
|
|
9041
|
+
const wasmPath = resolveWasm(info.wasmFile, info.wasmPackage);
|
|
9042
|
+
const hash = createHash4("sha256").update(readFileSync4(wasmPath)).digest("hex");
|
|
9043
|
+
wasmHashCache.set(extension, hash);
|
|
9044
|
+
return hash;
|
|
9045
|
+
}
|
|
8866
9046
|
function init() {
|
|
8867
9047
|
if (initPromise === null) {
|
|
8868
9048
|
initPromise = Parser.init();
|
|
@@ -8875,12 +9055,12 @@ function init() {
|
|
|
8875
9055
|
function resolveWasm(filename, pkg2) {
|
|
8876
9056
|
for (const dir of GRAMMAR_DIRS) {
|
|
8877
9057
|
const p2 = path27.join(dir, filename);
|
|
8878
|
-
if (
|
|
9058
|
+
if (existsSync6(p2)) return p2;
|
|
8879
9059
|
}
|
|
8880
9060
|
try {
|
|
8881
9061
|
const pkgDir = path27.dirname(_require.resolve(`${pkg2}/package.json`));
|
|
8882
9062
|
for (const candidate of [path27.join(pkgDir, filename), path27.join(pkgDir, "bindings/node", filename)]) {
|
|
8883
|
-
if (
|
|
9063
|
+
if (existsSync6(candidate)) return candidate;
|
|
8884
9064
|
}
|
|
8885
9065
|
} catch {
|
|
8886
9066
|
}
|
|
@@ -10505,10 +10685,10 @@ function registerFlowsCommand(program2) {
|
|
|
10505
10685
|
import chalk8 from "chalk";
|
|
10506
10686
|
|
|
10507
10687
|
// src/core/check.ts
|
|
10508
|
-
import
|
|
10688
|
+
import path43 from "path";
|
|
10509
10689
|
|
|
10510
10690
|
// src/core/log-integrity.ts
|
|
10511
|
-
import { createHash as
|
|
10691
|
+
import { createHash as createHash5 } from "crypto";
|
|
10512
10692
|
var DATETIME_STRICT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,3}Z$/;
|
|
10513
10693
|
function validateAppendOnly(currentContent, storedDatetime, storedPrefixHash) {
|
|
10514
10694
|
if (!DATETIME_STRICT.test(storedDatetime)) {
|
|
@@ -10521,7 +10701,7 @@ function validateAppendOnly(currentContent, storedDatetime, storedPrefixHash) {
|
|
|
10521
10701
|
if (!boundary) return { ok: false, reason: "boundary_missing" };
|
|
10522
10702
|
const bytes = Buffer.from(currentContent, "utf-8");
|
|
10523
10703
|
const prefix = bytes.subarray(0, boundary.offsetEnd);
|
|
10524
|
-
const computed =
|
|
10704
|
+
const computed = createHash5("sha256").update(prefix).digest("hex");
|
|
10525
10705
|
if (computed !== storedPrefixHash) return { ok: false, reason: "prefix_modified" };
|
|
10526
10706
|
return { ok: true };
|
|
10527
10707
|
}
|
|
@@ -11544,8 +11724,6 @@ import path33 from "path";
|
|
|
11544
11724
|
import { readFile as readFile15 } from "fs/promises";
|
|
11545
11725
|
|
|
11546
11726
|
// src/relations/symbol-table.ts
|
|
11547
|
-
import path32 from "path";
|
|
11548
|
-
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
11549
11727
|
var SymbolTable = class {
|
|
11550
11728
|
defs = /* @__PURE__ */ new Map();
|
|
11551
11729
|
// `${language}\0${symbolKey}` → set of defining files
|
|
@@ -11586,30 +11764,6 @@ var SymbolTable = class {
|
|
|
11586
11764
|
return s ? [...s] : [];
|
|
11587
11765
|
}
|
|
11588
11766
|
};
|
|
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
11767
|
|
|
11614
11768
|
// src/relations/resolver.ts
|
|
11615
11769
|
function resolveCandidateGroup(candidates2, resolver, fromFile, language) {
|
|
@@ -11841,82 +11995,35 @@ function buildUsingScope(file) {
|
|
|
11841
11995
|
});
|
|
11842
11996
|
return { prefixes, globalPrefixes, aliases, globalAliases, staticTargets };
|
|
11843
11997
|
}
|
|
11844
|
-
function collectGlobalUsings(file) {
|
|
11845
|
-
return buildUsingScope(file).globalPrefixes;
|
|
11846
|
-
}
|
|
11847
|
-
function collectGlobalUsingAliases(file) {
|
|
11848
|
-
return [...buildUsingScope(file).globalAliases.entries()];
|
|
11849
|
-
}
|
|
11850
11998
|
function enclosingNamespaceChain(fileNs, node) {
|
|
11851
11999
|
const segments = [fileNs, blockNamespace(node)].filter((p2) => p2 !== "").join(".").split(".").filter((s) => s !== "");
|
|
11852
12000
|
const chain = [];
|
|
11853
12001
|
for (let i = segments.length; i >= 1; i--) chain.push(segments.slice(0, i).join("."));
|
|
11854
12002
|
return chain;
|
|
11855
12003
|
}
|
|
11856
|
-
function
|
|
11857
|
-
const out = [];
|
|
12004
|
+
function extractCsharpRefs(file) {
|
|
11858
12005
|
const scope = buildUsingScope(file);
|
|
11859
12006
|
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
|
-
};
|
|
12007
|
+
const refs = [];
|
|
12008
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
11881
12009
|
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 });
|
|
12010
|
+
refs.push({
|
|
12011
|
+
kind: "plain",
|
|
12012
|
+
ref,
|
|
12013
|
+
line,
|
|
12014
|
+
rooted,
|
|
12015
|
+
enclosingNs: enclosingNamespaceChain(fileNs, node)
|
|
12016
|
+
});
|
|
12017
|
+
};
|
|
12018
|
+
const pushAttribute = (written, suffixed, node, line) => {
|
|
12019
|
+
refs.push({
|
|
12020
|
+
kind: "attr",
|
|
12021
|
+
written,
|
|
12022
|
+
suffixed,
|
|
12023
|
+
line,
|
|
12024
|
+
enclosingNs: enclosingNamespaceChain(fileNs, node)
|
|
12025
|
+
});
|
|
11918
12026
|
};
|
|
11919
|
-
const emitted = /* @__PURE__ */ new Set();
|
|
11920
12027
|
const emitTypeNode = (typeNode) => {
|
|
11921
12028
|
if (typeNode === null) return;
|
|
11922
12029
|
switch (typeNode.type) {
|
|
@@ -11943,15 +12050,15 @@ function uses(file, options = {}) {
|
|
|
11943
12050
|
return;
|
|
11944
12051
|
}
|
|
11945
12052
|
if (aliasField !== null) {
|
|
11946
|
-
const
|
|
11947
|
-
if (
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
}
|
|
12053
|
+
const nameField = typeNode.childForFieldName("name");
|
|
12054
|
+
if (nameField !== null) {
|
|
12055
|
+
refs.push({
|
|
12056
|
+
kind: "alias",
|
|
12057
|
+
aliasName: aliasField.text,
|
|
12058
|
+
tail: stripGlobalQualifier(nameField.text),
|
|
12059
|
+
line: typeNode.startPosition.row + 1,
|
|
12060
|
+
enclosingNs: enclosingNamespaceChain(fileNs, typeNode)
|
|
12061
|
+
});
|
|
11955
12062
|
}
|
|
11956
12063
|
}
|
|
11957
12064
|
return;
|
|
@@ -11988,11 +12095,7 @@ function uses(file, options = {}) {
|
|
|
11988
12095
|
return null;
|
|
11989
12096
|
}
|
|
11990
12097
|
for (const target of scope.staticTargets) {
|
|
11991
|
-
|
|
11992
|
-
candidates: [{ kind: "symbol", symbolKey: target.fqn }],
|
|
11993
|
-
kind: "import",
|
|
11994
|
-
line: target.line
|
|
11995
|
-
});
|
|
12098
|
+
refs.push({ kind: "static", fqn: target.fqn, line: target.line });
|
|
11996
12099
|
}
|
|
11997
12100
|
walk(file.tree.rootNode, (node) => {
|
|
11998
12101
|
if (node.type !== "using_directive") return void 0;
|
|
@@ -12120,10 +12223,75 @@ function uses(file, options = {}) {
|
|
|
12120
12223
|
}
|
|
12121
12224
|
return void 0;
|
|
12122
12225
|
});
|
|
12123
|
-
|
|
12226
|
+
return { fileNs, scope, refs };
|
|
12227
|
+
}
|
|
12228
|
+
function assembleCsharpCandidates(extract, options = {}) {
|
|
12229
|
+
const out = [];
|
|
12230
|
+
const { scope, refs } = extract;
|
|
12231
|
+
const aliases = new Map(scope.aliases);
|
|
12232
|
+
for (const [name, fqn] of options.projectGlobalUsingAliases ?? []) {
|
|
12233
|
+
if (!aliases.has(name)) aliases.set(name, fqn);
|
|
12234
|
+
}
|
|
12235
|
+
const usingPrefixes = [
|
|
12236
|
+
.../* @__PURE__ */ new Set([
|
|
12237
|
+
...scope.prefixes,
|
|
12238
|
+
...scope.globalPrefixes,
|
|
12239
|
+
...options.projectGlobalUsings ?? []
|
|
12240
|
+
])
|
|
12241
|
+
].sort();
|
|
12242
|
+
const dedupKeys = (keys) => {
|
|
12243
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12244
|
+
const out2 = [];
|
|
12245
|
+
for (const k of keys) {
|
|
12246
|
+
if (k === void 0 || k === "") continue;
|
|
12247
|
+
if (seen.has(k)) continue;
|
|
12248
|
+
seen.add(k);
|
|
12249
|
+
out2.push(k);
|
|
12250
|
+
}
|
|
12251
|
+
return out2;
|
|
12252
|
+
};
|
|
12253
|
+
const pushRef = (ref, enclosingNs, line, rooted) => {
|
|
12254
|
+
if (rooted) {
|
|
12255
|
+
out.push({ candidates: [{ kind: "symbol", symbolKey: ref }], kind: "import", line });
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
12258
|
+
const lead = ref.split(".")[0];
|
|
12259
|
+
const multi = ref.includes(".");
|
|
12260
|
+
const aliasTarget = aliases.get(lead);
|
|
12261
|
+
const enclosing = enclosingNs.map((ns) => `${ns}.${ref}`);
|
|
12262
|
+
const usingMembers = usingPrefixes.map((p2) => ({
|
|
12263
|
+
symbolKey: `${p2}.${ref}`,
|
|
12264
|
+
nestedOnly: multi
|
|
12265
|
+
}));
|
|
12266
|
+
const candidates2 = [];
|
|
12267
|
+
const pushed = /* @__PURE__ */ new Set();
|
|
12268
|
+
const add = (hint) => {
|
|
12269
|
+
if (hint.kind === "symbol") {
|
|
12270
|
+
if (pushed.has(hint.symbolKey)) return;
|
|
12271
|
+
pushed.add(hint.symbolKey);
|
|
12272
|
+
}
|
|
12273
|
+
candidates2.push(hint);
|
|
12274
|
+
};
|
|
12275
|
+
if (aliasTarget !== void 0) {
|
|
12276
|
+
const tail = ref.slice(lead.length);
|
|
12277
|
+
const aliasKey = `${aliasTarget}${tail}`;
|
|
12278
|
+
const coDef = dedupKeys([aliasKey, ...enclosing, ref]).map((k) => ({
|
|
12279
|
+
symbolKey: k
|
|
12280
|
+
}));
|
|
12281
|
+
add({ kind: "symbol", symbolKey: aliasKey, set: coDef });
|
|
12282
|
+
}
|
|
12283
|
+
for (const k of enclosing) add({ kind: "symbol", symbolKey: k });
|
|
12284
|
+
for (const m of usingMembers) {
|
|
12285
|
+
add({ kind: "symbol", symbolKey: m.symbolKey, nestedOnly: m.nestedOnly, set: usingMembers });
|
|
12286
|
+
}
|
|
12287
|
+
add({ kind: "symbol", symbolKey: ref });
|
|
12288
|
+
if (candidates2.length === 0) return;
|
|
12289
|
+
out.push({ candidates: candidates2, kind: "import", line });
|
|
12290
|
+
};
|
|
12291
|
+
const pushAttribute = (written, suffixed, enclosingNs, line) => {
|
|
12124
12292
|
const before = out.length;
|
|
12125
|
-
pushRef(written,
|
|
12126
|
-
if (suffixed !== void 0) pushRef(suffixed,
|
|
12293
|
+
pushRef(written, enclosingNs, line, false);
|
|
12294
|
+
if (suffixed !== void 0) pushRef(suffixed, enclosingNs, line, false);
|
|
12127
12295
|
if (out.length > before + 1) {
|
|
12128
12296
|
const merged = [];
|
|
12129
12297
|
const seenK = /* @__PURE__ */ new Set();
|
|
@@ -12138,31 +12306,142 @@ function uses(file, options = {}) {
|
|
|
12138
12306
|
}
|
|
12139
12307
|
out.push({ candidates: merged, kind: "import", line });
|
|
12140
12308
|
}
|
|
12309
|
+
};
|
|
12310
|
+
for (const r of refs) {
|
|
12311
|
+
switch (r.kind) {
|
|
12312
|
+
case "static":
|
|
12313
|
+
out.push({ candidates: [{ kind: "symbol", symbolKey: r.fqn }], kind: "import", line: r.line });
|
|
12314
|
+
break;
|
|
12315
|
+
case "plain":
|
|
12316
|
+
pushRef(r.ref, r.enclosingNs, r.line, r.rooted);
|
|
12317
|
+
break;
|
|
12318
|
+
case "alias": {
|
|
12319
|
+
const aliasFqn = aliases.get(r.aliasName);
|
|
12320
|
+
if (aliasFqn !== void 0) {
|
|
12321
|
+
pushRef(`${aliasFqn}.${r.tail}`, r.enclosingNs, r.line, true);
|
|
12322
|
+
}
|
|
12323
|
+
break;
|
|
12324
|
+
}
|
|
12325
|
+
case "attr":
|
|
12326
|
+
pushAttribute(r.written, r.suffixed, r.enclosingNs, r.line);
|
|
12327
|
+
break;
|
|
12328
|
+
}
|
|
12141
12329
|
}
|
|
12142
12330
|
return out;
|
|
12143
12331
|
}
|
|
12144
|
-
function
|
|
12145
|
-
return
|
|
12332
|
+
function uses(file, options = {}) {
|
|
12333
|
+
return assembleCsharpCandidates(extractCsharpRefs(file), options);
|
|
12146
12334
|
}
|
|
12147
12335
|
var csharpExtractor = {
|
|
12148
12336
|
languages: /* @__PURE__ */ new Set(["csharp"]),
|
|
12337
|
+
rev: 2,
|
|
12149
12338
|
declarations,
|
|
12150
12339
|
uses
|
|
12151
12340
|
};
|
|
12152
12341
|
|
|
12153
|
-
// src/relations/
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12342
|
+
// src/relations/facts-cache.ts
|
|
12343
|
+
import path32 from "path";
|
|
12344
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
12345
|
+
import { createHash as createHash6 } from "crypto";
|
|
12346
|
+
var CACHE_SCHEMA_VERSION = 1;
|
|
12347
|
+
function serializeCsharp(c) {
|
|
12348
|
+
return {
|
|
12349
|
+
fileNs: c.fileNs,
|
|
12350
|
+
scope: {
|
|
12351
|
+
prefixes: c.scope.prefixes,
|
|
12352
|
+
globalPrefixes: c.scope.globalPrefixes,
|
|
12353
|
+
aliases: [...c.scope.aliases],
|
|
12354
|
+
globalAliases: [...c.scope.globalAliases],
|
|
12355
|
+
staticTargets: c.scope.staticTargets
|
|
12356
|
+
},
|
|
12357
|
+
refs: c.refs
|
|
12358
|
+
};
|
|
12359
|
+
}
|
|
12360
|
+
function deserializeCsharp(raw) {
|
|
12361
|
+
if (!raw || typeof raw !== "object") return null;
|
|
12362
|
+
const s = raw;
|
|
12363
|
+
if (typeof s.fileNs !== "string") return null;
|
|
12364
|
+
if (!s.scope || typeof s.scope !== "object") return null;
|
|
12365
|
+
const sc = s.scope;
|
|
12366
|
+
if (!Array.isArray(sc.prefixes) || !Array.isArray(sc.globalPrefixes) || !Array.isArray(sc.aliases) || !Array.isArray(sc.globalAliases) || !Array.isArray(sc.staticTargets)) {
|
|
12367
|
+
return null;
|
|
12164
12368
|
}
|
|
12165
|
-
return
|
|
12369
|
+
if (!Array.isArray(s.refs)) return null;
|
|
12370
|
+
return {
|
|
12371
|
+
fileNs: s.fileNs,
|
|
12372
|
+
scope: {
|
|
12373
|
+
prefixes: sc.prefixes,
|
|
12374
|
+
globalPrefixes: sc.globalPrefixes,
|
|
12375
|
+
aliases: new Map(sc.aliases),
|
|
12376
|
+
globalAliases: new Map(sc.globalAliases),
|
|
12377
|
+
staticTargets: sc.staticTargets
|
|
12378
|
+
},
|
|
12379
|
+
refs: s.refs
|
|
12380
|
+
};
|
|
12381
|
+
}
|
|
12382
|
+
function astCacheDir(graphRoot) {
|
|
12383
|
+
return path32.join(graphRoot, ".ast-cache");
|
|
12384
|
+
}
|
|
12385
|
+
function factsKey(args) {
|
|
12386
|
+
const payload = `${args.contentHash}\0${args.language}\0${args.grammarHash}\0${args.rev}`;
|
|
12387
|
+
return createHash6("sha256").update(payload).digest("hex").slice(0, 32);
|
|
12388
|
+
}
|
|
12389
|
+
function shardPath(dir, language, key) {
|
|
12390
|
+
return path32.join(dir, `v${CACHE_SCHEMA_VERSION}`, language, `${key}.json`);
|
|
12391
|
+
}
|
|
12392
|
+
async function loadFacts(dir, language, key) {
|
|
12393
|
+
const p2 = shardPath(dir, language, key);
|
|
12394
|
+
if (!existsSync7(p2)) return null;
|
|
12395
|
+
let parsed;
|
|
12396
|
+
try {
|
|
12397
|
+
parsed = JSON.parse(readFileSync7(p2, "utf-8"));
|
|
12398
|
+
} catch {
|
|
12399
|
+
return null;
|
|
12400
|
+
}
|
|
12401
|
+
if (!parsed || typeof parsed !== "object" || parsed.v !== CACHE_SCHEMA_VERSION) return null;
|
|
12402
|
+
if (typeof parsed.key !== "string") return null;
|
|
12403
|
+
if (!Array.isArray(parsed.declarations)) return null;
|
|
12404
|
+
if (!Array.isArray(parsed.uses)) return null;
|
|
12405
|
+
if (parsed.key !== key) return null;
|
|
12406
|
+
const facts = {
|
|
12407
|
+
declarations: parsed.declarations,
|
|
12408
|
+
uses: parsed.uses
|
|
12409
|
+
};
|
|
12410
|
+
if (parsed.csharp !== void 0) {
|
|
12411
|
+
const csharp = deserializeCsharp(parsed.csharp);
|
|
12412
|
+
if (csharp === null) return null;
|
|
12413
|
+
facts.csharp = csharp;
|
|
12414
|
+
}
|
|
12415
|
+
return facts;
|
|
12416
|
+
}
|
|
12417
|
+
async function writeFacts(dir, language, key, facts) {
|
|
12418
|
+
const p2 = shardPath(dir, language, key);
|
|
12419
|
+
if (existsSync7(p2)) return;
|
|
12420
|
+
const body = {
|
|
12421
|
+
v: CACHE_SCHEMA_VERSION,
|
|
12422
|
+
key,
|
|
12423
|
+
declarations: facts.declarations,
|
|
12424
|
+
uses: facts.uses,
|
|
12425
|
+
// Flatten the C# extract's `Map`s to entry arrays BEFORE JSON.stringify — a bare `Map`
|
|
12426
|
+
// stringifies to `"{}"` and would silently lose every alias entry (the false-green vector).
|
|
12427
|
+
...facts.csharp !== void 0 ? { csharp: serializeCsharp(facts.csharp) } : {}
|
|
12428
|
+
};
|
|
12429
|
+
await atomicWriteFile(p2, JSON.stringify(body));
|
|
12430
|
+
}
|
|
12431
|
+
|
|
12432
|
+
// src/relations/verifier.ts
|
|
12433
|
+
function verifyNodeDeps(nodeId, deps, g) {
|
|
12434
|
+
const out = [];
|
|
12435
|
+
const declared = g.declaredTargets(nodeId);
|
|
12436
|
+
for (const d of deps) {
|
|
12437
|
+
const m = d.ownerNode;
|
|
12438
|
+
if (m === nodeId) continue;
|
|
12439
|
+
if (g.isAncestorOf(m, nodeId) || g.isAncestorOf(nodeId, m)) continue;
|
|
12440
|
+
if (declared.has(m)) continue;
|
|
12441
|
+
if (g.parentChain(m).some((anc) => declared.has(anc))) continue;
|
|
12442
|
+
out.push({ fromFile: d.fromFile, line: d.line, ownerNode: m });
|
|
12443
|
+
}
|
|
12444
|
+
return out;
|
|
12166
12445
|
}
|
|
12167
12446
|
|
|
12168
12447
|
// src/relations/pass.ts
|
|
@@ -12204,6 +12483,63 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12204
12483
|
return null;
|
|
12205
12484
|
}
|
|
12206
12485
|
}
|
|
12486
|
+
async function extractFileFacts(record, extractor) {
|
|
12487
|
+
const parsed = await parseSingle(record);
|
|
12488
|
+
if (!parsed) return null;
|
|
12489
|
+
try {
|
|
12490
|
+
const declarations12 = extractor.declarations(parsed);
|
|
12491
|
+
const isCsharp = record.language === "csharp";
|
|
12492
|
+
const uses12 = isCsharp ? null : extractor.uses(parsed);
|
|
12493
|
+
const csharp = isCsharp ? extractCsharpRefs(parsed) : null;
|
|
12494
|
+
return { declarations: declarations12, uses: uses12, csharp };
|
|
12495
|
+
} finally {
|
|
12496
|
+
parsed.tree.delete();
|
|
12497
|
+
}
|
|
12498
|
+
}
|
|
12499
|
+
const grammarHashByExt = /* @__PURE__ */ new Map();
|
|
12500
|
+
const grammarHashForExt = (ext) => {
|
|
12501
|
+
const hit = grammarHashByExt.get(ext);
|
|
12502
|
+
if (hit !== void 0) return hit;
|
|
12503
|
+
let h;
|
|
12504
|
+
try {
|
|
12505
|
+
h = grammarWasmHash(ext);
|
|
12506
|
+
} catch {
|
|
12507
|
+
h = null;
|
|
12508
|
+
}
|
|
12509
|
+
grammarHashByExt.set(ext, h);
|
|
12510
|
+
return h;
|
|
12511
|
+
};
|
|
12512
|
+
async function loadOrExtractFacts(record, extractor) {
|
|
12513
|
+
const language = record.language;
|
|
12514
|
+
const isCsharp = language === "csharp";
|
|
12515
|
+
const grammarHash = grammarHashForExt(path33.extname(record.path));
|
|
12516
|
+
if (grammarHash === null) return extractFileFacts(record, extractor);
|
|
12517
|
+
if (!deps.disableCache) {
|
|
12518
|
+
const key = factsKey({
|
|
12519
|
+
contentHash: record.hash,
|
|
12520
|
+
language,
|
|
12521
|
+
grammarHash,
|
|
12522
|
+
rev: extractor.rev
|
|
12523
|
+
});
|
|
12524
|
+
const cached = await loadFacts(deps.symbolIndexDir, language, key);
|
|
12525
|
+
if (cached && (!isCsharp || cached.csharp !== void 0)) {
|
|
12526
|
+
return {
|
|
12527
|
+
declarations: cached.declarations,
|
|
12528
|
+
uses: isCsharp ? null : cached.uses,
|
|
12529
|
+
csharp: isCsharp ? cached.csharp : null
|
|
12530
|
+
};
|
|
12531
|
+
}
|
|
12532
|
+
const facts = await extractFileFacts(record, extractor);
|
|
12533
|
+
if (!facts) return null;
|
|
12534
|
+
await writeFacts(deps.symbolIndexDir, language, key, {
|
|
12535
|
+
declarations: facts.declarations,
|
|
12536
|
+
uses: facts.uses ?? [],
|
|
12537
|
+
...facts.csharp !== null ? { csharp: facts.csharp } : {}
|
|
12538
|
+
});
|
|
12539
|
+
return facts;
|
|
12540
|
+
}
|
|
12541
|
+
return extractFileFacts(record, extractor);
|
|
12542
|
+
}
|
|
12207
12543
|
const symbolTable = new SymbolTable();
|
|
12208
12544
|
const recordsByLanguage = /* @__PURE__ */ new Map();
|
|
12209
12545
|
for (const record of fileRecords) {
|
|
@@ -12216,42 +12552,31 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12216
12552
|
}
|
|
12217
12553
|
list.push(record);
|
|
12218
12554
|
}
|
|
12555
|
+
const factsByPath = /* @__PURE__ */ new Map();
|
|
12556
|
+
for (const record of fileRecords) {
|
|
12557
|
+
if (!record.language) continue;
|
|
12558
|
+
const extractor = deps.extractorFor(record.language);
|
|
12559
|
+
if (!extractor) continue;
|
|
12560
|
+
const facts = await loadOrExtractFacts(record, extractor);
|
|
12561
|
+
if (facts) factsByPath.set(record.path, facts);
|
|
12562
|
+
}
|
|
12219
12563
|
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
12564
|
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();
|
|
12565
|
+
const facts = factsByPath.get(record.path);
|
|
12566
|
+
if (!facts) continue;
|
|
12567
|
+
for (const decl of facts.declarations) {
|
|
12568
|
+
symbolTable.declare(language, decl.symbolKey, record.path);
|
|
12238
12569
|
}
|
|
12239
12570
|
}
|
|
12240
|
-
const toPersist = { builtFrom, symbols };
|
|
12241
|
-
await writeSymbolIndex(deps.symbolIndexDir, language, toPersist);
|
|
12242
12571
|
}
|
|
12243
12572
|
const csharpRecords = recordsByLanguage.get("csharp") ?? [];
|
|
12244
12573
|
const projectGlobalUsings = /* @__PURE__ */ new Set();
|
|
12245
12574
|
const projectGlobalUsingAliases = /* @__PURE__ */ new Map();
|
|
12246
12575
|
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
|
-
}
|
|
12576
|
+
const facts = factsByPath.get(record.path);
|
|
12577
|
+
if (!facts || facts.csharp === null) continue;
|
|
12578
|
+
for (const prefix of facts.csharp.scope.globalPrefixes) projectGlobalUsings.add(prefix);
|
|
12579
|
+
for (const [name, fqn] of facts.csharp.scope.globalAliases) projectGlobalUsingAliases.set(name, fqn);
|
|
12255
12580
|
}
|
|
12256
12581
|
const csharpGlobalUsings = [...projectGlobalUsings];
|
|
12257
12582
|
const csharpGlobalUsingAliases = [...projectGlobalUsingAliases.entries()];
|
|
@@ -12278,6 +12603,15 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12278
12603
|
}
|
|
12279
12604
|
};
|
|
12280
12605
|
const violationsByNode = /* @__PURE__ */ new Map();
|
|
12606
|
+
const detectedEdgesByNode = /* @__PURE__ */ new Map();
|
|
12607
|
+
const resolveDetected = (record, detected, resolvedDeps) => {
|
|
12608
|
+
for (const dep of detected) {
|
|
12609
|
+
const ownerNode = resolveCandidateGroup(dep.candidates, resolver, record.path, record.language);
|
|
12610
|
+
if (ownerNode !== void 0) {
|
|
12611
|
+
resolvedDeps.push({ fromFile: record.path, line: dep.line, ownerNode });
|
|
12612
|
+
}
|
|
12613
|
+
}
|
|
12614
|
+
};
|
|
12281
12615
|
for (const [nodeId] of graph.nodes) {
|
|
12282
12616
|
const records = fileRecords.filter((r) => r.nodeId === nodeId);
|
|
12283
12617
|
if (records.length === 0) continue;
|
|
@@ -12286,23 +12620,28 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12286
12620
|
if (!record.language) continue;
|
|
12287
12621
|
const extractor = deps.extractorFor(record.language);
|
|
12288
12622
|
if (!extractor) continue;
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
const detected =
|
|
12623
|
+
if (record.language === "csharp") {
|
|
12624
|
+
const facts2 = factsByPath.get(record.path);
|
|
12625
|
+
if (!facts2 || facts2.csharp === null) continue;
|
|
12626
|
+
const detected = assembleCsharpCandidates(facts2.csharp, {
|
|
12293
12627
|
projectGlobalUsings: csharpGlobalUsings,
|
|
12294
12628
|
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();
|
|
12629
|
+
});
|
|
12630
|
+
resolveDetected(record, detected, resolvedDeps);
|
|
12631
|
+
continue;
|
|
12304
12632
|
}
|
|
12633
|
+
const facts = factsByPath.get(record.path);
|
|
12634
|
+
if (!facts || facts.uses === null) continue;
|
|
12635
|
+
resolveDetected(record, facts.uses, resolvedDeps);
|
|
12636
|
+
}
|
|
12637
|
+
const edges = /* @__PURE__ */ new Set();
|
|
12638
|
+
for (const d of resolvedDeps) {
|
|
12639
|
+
const m = d.ownerNode;
|
|
12640
|
+
if (m === nodeId) continue;
|
|
12641
|
+
if (graphView.isAncestorOf(m, nodeId) || graphView.isAncestorOf(nodeId, m)) continue;
|
|
12642
|
+
edges.add(m);
|
|
12305
12643
|
}
|
|
12644
|
+
if (edges.size > 0) detectedEdgesByNode.set(nodeId, edges);
|
|
12306
12645
|
const violations = verifyNodeDeps(nodeId, resolvedDeps, graphView);
|
|
12307
12646
|
if (violations.length) {
|
|
12308
12647
|
const reason = violations.map((v) => `${v.fromFile}:${v.line} \u2192 undeclared dependency on ${v.ownerNode}`).join("\n");
|
|
@@ -12311,7 +12650,7 @@ async function runRelationPass(graph, projectRoot, deps) {
|
|
|
12311
12650
|
violationsByNode.set(nodeId, { verdict: "approved", violations: [] });
|
|
12312
12651
|
}
|
|
12313
12652
|
}
|
|
12314
|
-
return { violationsByNode };
|
|
12653
|
+
return { violationsByNode, factsByPath, detectedEdgesByNode };
|
|
12315
12654
|
}
|
|
12316
12655
|
|
|
12317
12656
|
// src/relations/extractors/types.ts
|
|
@@ -12331,6 +12670,18 @@ function specifierFromStringNode(stringNode) {
|
|
|
12331
12670
|
function specifierFromSource(node) {
|
|
12332
12671
|
return specifierFromStringNode(node.childForFieldName("source"));
|
|
12333
12672
|
}
|
|
12673
|
+
function specifierFromCallArg(arg) {
|
|
12674
|
+
if (arg.type === "string") return specifierFromStringNode(arg);
|
|
12675
|
+
if (arg.type !== "template_string") return void 0;
|
|
12676
|
+
let fragment;
|
|
12677
|
+
for (let i = 0; i < arg.namedChildCount; i++) {
|
|
12678
|
+
const child = arg.namedChild(i);
|
|
12679
|
+
if (child === null) continue;
|
|
12680
|
+
if (child.type === "template_substitution") return void 0;
|
|
12681
|
+
if (child.type === "string_fragment") fragment = child.text;
|
|
12682
|
+
}
|
|
12683
|
+
return fragment ?? "";
|
|
12684
|
+
}
|
|
12334
12685
|
function isRelative(specifier) {
|
|
12335
12686
|
return specifier.startsWith(".") || specifier.startsWith("/");
|
|
12336
12687
|
}
|
|
@@ -12381,7 +12732,8 @@ function uses2(file) {
|
|
|
12381
12732
|
const emit = (specifier, node) => {
|
|
12382
12733
|
if (specifier === void 0 || specifier === "" || !isRelative(specifier)) return;
|
|
12383
12734
|
const line = node.startPosition.row + 1;
|
|
12384
|
-
const dedupKey = `${specifier}
|
|
12735
|
+
const dedupKey = `${specifier}
|
|
12736
|
+
${line}`;
|
|
12385
12737
|
if (seen.has(dedupKey)) return;
|
|
12386
12738
|
seen.add(dedupKey);
|
|
12387
12739
|
out.push(single({ kind: "path", specifier }, "import", line));
|
|
@@ -12428,8 +12780,8 @@ function uses2(file) {
|
|
|
12428
12780
|
const isRequire = fn.type === "identifier" && fn.text === "require";
|
|
12429
12781
|
if (!isDynamicImport && !isRequire) break;
|
|
12430
12782
|
const arg = firstArgument(node);
|
|
12431
|
-
if (arg === null
|
|
12432
|
-
emit(
|
|
12783
|
+
if (arg === null) break;
|
|
12784
|
+
emit(specifierFromCallArg(arg), node);
|
|
12433
12785
|
break;
|
|
12434
12786
|
}
|
|
12435
12787
|
default:
|
|
@@ -12470,6 +12822,7 @@ function declarations2(file) {
|
|
|
12470
12822
|
}
|
|
12471
12823
|
var typescriptExtractor = {
|
|
12472
12824
|
languages: /* @__PURE__ */ new Set(["typescript", "tsx", "javascript"]),
|
|
12825
|
+
rev: 1,
|
|
12473
12826
|
declarations: declarations2,
|
|
12474
12827
|
uses: uses2
|
|
12475
12828
|
};
|
|
@@ -12575,6 +12928,7 @@ function declarations3(file) {
|
|
|
12575
12928
|
}
|
|
12576
12929
|
var pythonExtractor = {
|
|
12577
12930
|
languages: /* @__PURE__ */ new Set(["python"]),
|
|
12931
|
+
rev: 1,
|
|
12578
12932
|
declarations: declarations3,
|
|
12579
12933
|
uses: uses3
|
|
12580
12934
|
};
|
|
@@ -12624,6 +12978,7 @@ function declarations4(file) {
|
|
|
12624
12978
|
}
|
|
12625
12979
|
var goExtractor = {
|
|
12626
12980
|
languages: /* @__PURE__ */ new Set(["go"]),
|
|
12981
|
+
rev: 1,
|
|
12627
12982
|
declarations: declarations4,
|
|
12628
12983
|
uses: uses4
|
|
12629
12984
|
};
|
|
@@ -12795,6 +13150,7 @@ function declarations5(file) {
|
|
|
12795
13150
|
}
|
|
12796
13151
|
var javaExtractor = {
|
|
12797
13152
|
languages: /* @__PURE__ */ new Set(["java"]),
|
|
13153
|
+
rev: 3,
|
|
12798
13154
|
declarations: declarations5,
|
|
12799
13155
|
uses: uses5
|
|
12800
13156
|
};
|
|
@@ -12923,6 +13279,7 @@ function declarations6(file) {
|
|
|
12923
13279
|
}
|
|
12924
13280
|
var phpExtractor = {
|
|
12925
13281
|
languages: /* @__PURE__ */ new Set(["php"]),
|
|
13282
|
+
rev: 1,
|
|
12926
13283
|
declarations: declarations6,
|
|
12927
13284
|
uses: uses6
|
|
12928
13285
|
};
|
|
@@ -13047,6 +13404,7 @@ function declarations7(file) {
|
|
|
13047
13404
|
}
|
|
13048
13405
|
var kotlinExtractor = {
|
|
13049
13406
|
languages: /* @__PURE__ */ new Set(["kotlin"]),
|
|
13407
|
+
rev: 2,
|
|
13050
13408
|
declarations: declarations7,
|
|
13051
13409
|
uses: uses7
|
|
13052
13410
|
};
|
|
@@ -13219,6 +13577,7 @@ function declarations8(file) {
|
|
|
13219
13577
|
}
|
|
13220
13578
|
var rustExtractor = {
|
|
13221
13579
|
languages: /* @__PURE__ */ new Set(["rust"]),
|
|
13580
|
+
rev: 1,
|
|
13222
13581
|
declarations: declarations8,
|
|
13223
13582
|
uses: uses8
|
|
13224
13583
|
};
|
|
@@ -13312,6 +13671,7 @@ function functionName(def) {
|
|
|
13312
13671
|
}
|
|
13313
13672
|
var cExtractor = {
|
|
13314
13673
|
languages: /* @__PURE__ */ new Set(["c"]),
|
|
13674
|
+
rev: 1,
|
|
13315
13675
|
declarations: declarations9,
|
|
13316
13676
|
uses: uses9
|
|
13317
13677
|
};
|
|
@@ -13356,6 +13716,7 @@ function functionName2(def) {
|
|
|
13356
13716
|
}
|
|
13357
13717
|
var cppExtractor = {
|
|
13358
13718
|
languages: /* @__PURE__ */ new Set(["cpp"]),
|
|
13719
|
+
rev: 1,
|
|
13359
13720
|
declarations: declarations10,
|
|
13360
13721
|
uses: uses10
|
|
13361
13722
|
};
|
|
@@ -13526,6 +13887,7 @@ function declarations11(file) {
|
|
|
13526
13887
|
}
|
|
13527
13888
|
var rubyExtractor = {
|
|
13528
13889
|
languages: /* @__PURE__ */ new Set(["ruby"]),
|
|
13890
|
+
rev: 1,
|
|
13529
13891
|
declarations: declarations11,
|
|
13530
13892
|
uses: uses11
|
|
13531
13893
|
};
|
|
@@ -13561,12 +13923,6 @@ function extractorForLanguage(language) {
|
|
|
13561
13923
|
return byLanguage.get(language);
|
|
13562
13924
|
}
|
|
13563
13925
|
|
|
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
13926
|
// src/relations/allowed-types.ts
|
|
13571
13927
|
var RELATION_TYPES3 = [
|
|
13572
13928
|
"uses",
|
|
@@ -13579,15 +13935,16 @@ var RELATION_TYPES3 = [
|
|
|
13579
13935
|
function allowedRelationTypes(architecture, fromType, toType) {
|
|
13580
13936
|
const fromConfig = architecture.node_types[fromType];
|
|
13581
13937
|
if (!fromConfig) return [];
|
|
13582
|
-
const
|
|
13938
|
+
const lists = fromConfig.relations;
|
|
13939
|
+
const def = fromConfig.relationDefault ?? "allow";
|
|
13583
13940
|
const out = [];
|
|
13584
13941
|
for (const rt of RELATION_TYPES3) {
|
|
13585
|
-
const allowedTargets =
|
|
13586
|
-
if (
|
|
13587
|
-
out.push(rt);
|
|
13942
|
+
const allowedTargets = lists?.[rt];
|
|
13943
|
+
if (allowedTargets === void 0) {
|
|
13944
|
+
if (def === "allow") out.push(rt);
|
|
13588
13945
|
continue;
|
|
13589
13946
|
}
|
|
13590
|
-
if (allowedTargets.includes(toType)) out.push(rt);
|
|
13947
|
+
if (allowedTargets.includes("*") || allowedTargets.includes(toType)) out.push(rt);
|
|
13591
13948
|
}
|
|
13592
13949
|
return out;
|
|
13593
13950
|
}
|
|
@@ -13631,15 +13988,15 @@ ${blocks.join("\n")}`
|
|
|
13631
13988
|
}
|
|
13632
13989
|
|
|
13633
13990
|
// src/relations/resolve-path.ts
|
|
13634
|
-
import
|
|
13635
|
-
import { existsSync as
|
|
13991
|
+
import path42 from "path";
|
|
13992
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
13636
13993
|
|
|
13637
13994
|
// src/relations/extractors/typescript-resolve.ts
|
|
13638
|
-
import
|
|
13995
|
+
import path34 from "path";
|
|
13639
13996
|
function resolveTsPath(specifier, fromFile, exists) {
|
|
13640
13997
|
if (!specifier.startsWith(".") && !specifier.startsWith("/")) return void 0;
|
|
13641
|
-
const baseDir =
|
|
13642
|
-
const joined =
|
|
13998
|
+
const baseDir = path34.posix.dirname(toPosix3(fromFile));
|
|
13999
|
+
const joined = path34.posix.normalize(path34.posix.join(baseDir, specifier));
|
|
13643
14000
|
for (const cand of candidates(joined)) {
|
|
13644
14001
|
if (exists(cand)) return cand;
|
|
13645
14002
|
}
|
|
@@ -13647,7 +14004,7 @@ function resolveTsPath(specifier, fromFile, exists) {
|
|
|
13647
14004
|
}
|
|
13648
14005
|
function candidates(joined) {
|
|
13649
14006
|
const out = [];
|
|
13650
|
-
const ext =
|
|
14007
|
+
const ext = path34.posix.extname(joined);
|
|
13651
14008
|
const rewrites = {
|
|
13652
14009
|
".js": [".ts", ".tsx", ".js", ".jsx"],
|
|
13653
14010
|
".jsx": [".tsx", ".jsx"],
|
|
@@ -13662,7 +14019,7 @@ function candidates(joined) {
|
|
|
13662
14019
|
} else {
|
|
13663
14020
|
for (const e of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) out.push(joined + e);
|
|
13664
14021
|
}
|
|
13665
|
-
for (const e of [".ts", ".tsx", ".js", ".jsx"]) out.push(
|
|
14022
|
+
for (const e of [".ts", ".tsx", ".js", ".jsx"]) out.push(path34.posix.join(joined, "index" + e));
|
|
13666
14023
|
return out;
|
|
13667
14024
|
}
|
|
13668
14025
|
function toPosix3(p2) {
|
|
@@ -13670,7 +14027,7 @@ function toPosix3(p2) {
|
|
|
13670
14027
|
}
|
|
13671
14028
|
|
|
13672
14029
|
// src/relations/extractors/python-resolve.ts
|
|
13673
|
-
import
|
|
14030
|
+
import path35 from "path";
|
|
13674
14031
|
function resolvePythonModule(specifier, fromFile, exists) {
|
|
13675
14032
|
if (specifier.startsWith(".")) {
|
|
13676
14033
|
return resolveRelative(specifier, fromFile, exists);
|
|
@@ -13683,7 +14040,7 @@ function resolveAbsolute(specifier, fromFile, exists) {
|
|
|
13683
14040
|
const modulePath = segments.join("/");
|
|
13684
14041
|
const parentPath = segments.slice(0, -1).join("/");
|
|
13685
14042
|
const matches = /* @__PURE__ */ new Set();
|
|
13686
|
-
for (const dir of ancestorDirs(
|
|
14043
|
+
for (const dir of ancestorDirs(path35.posix.dirname(toPosix4(fromFile)))) {
|
|
13687
14044
|
const candidates2 = [
|
|
13688
14045
|
// module-as-file / package at this root
|
|
13689
14046
|
joinUnder(dir, modulePath + ".py"),
|
|
@@ -13707,17 +14064,17 @@ function resolveRelative(specifier, fromFile, exists) {
|
|
|
13707
14064
|
if (dotMatch === null) return void 0;
|
|
13708
14065
|
const dots = dotMatch[0].length;
|
|
13709
14066
|
const tail = specifier.slice(dots);
|
|
13710
|
-
let base =
|
|
14067
|
+
let base = path35.posix.dirname(toPosix4(fromFile));
|
|
13711
14068
|
for (let i = 0; i < dots - 1; i++) {
|
|
13712
|
-
const parent =
|
|
14069
|
+
const parent = path35.posix.dirname(base);
|
|
13713
14070
|
if (parent === base) return void 0;
|
|
13714
14071
|
base = parent;
|
|
13715
14072
|
}
|
|
13716
14073
|
const tailPath = tail.length > 0 ? tail.split(".").filter((s) => s.length > 0).join("/") : "";
|
|
13717
|
-
const target = tailPath.length > 0 ?
|
|
13718
|
-
const normalized =
|
|
14074
|
+
const target = tailPath.length > 0 ? path35.posix.join(base, tailPath) : base;
|
|
14075
|
+
const normalized = path35.posix.normalize(target);
|
|
13719
14076
|
if (normalized.startsWith("..")) return void 0;
|
|
13720
|
-
const candidates2 = tailPath.length > 0 ? [normalized + ".py",
|
|
14077
|
+
const candidates2 = tailPath.length > 0 ? [normalized + ".py", path35.posix.join(normalized, "__init__.py")] : [path35.posix.join(normalized, "__init__.py")];
|
|
13721
14078
|
for (const cand of candidates2) {
|
|
13722
14079
|
if (exists(cand)) return cand;
|
|
13723
14080
|
}
|
|
@@ -13729,20 +14086,20 @@ function ancestorDirs(dir) {
|
|
|
13729
14086
|
for (; ; ) {
|
|
13730
14087
|
out.push(cur);
|
|
13731
14088
|
if (cur === "") break;
|
|
13732
|
-
const parent =
|
|
14089
|
+
const parent = path35.posix.dirname(cur);
|
|
13733
14090
|
cur = parent === "." ? "" : parent;
|
|
13734
14091
|
}
|
|
13735
14092
|
return out;
|
|
13736
14093
|
}
|
|
13737
14094
|
function joinUnder(dir, sub) {
|
|
13738
|
-
return
|
|
14095
|
+
return path35.posix.normalize(dir === "" ? sub : path35.posix.join(dir, sub));
|
|
13739
14096
|
}
|
|
13740
14097
|
function toPosix4(p2) {
|
|
13741
14098
|
return p2.replace(/\\/g, "/");
|
|
13742
14099
|
}
|
|
13743
14100
|
|
|
13744
14101
|
// src/relations/extractors/go-resolve.ts
|
|
13745
|
-
import
|
|
14102
|
+
import path36 from "path";
|
|
13746
14103
|
function resolveGoImport(importPath, fromFile, deps) {
|
|
13747
14104
|
const resolved = deps.modulePathFor(fromFile);
|
|
13748
14105
|
if (resolved === void 0) return void 0;
|
|
@@ -13756,8 +14113,8 @@ function resolveGoImport(importPath, fromFile, deps) {
|
|
|
13756
14113
|
} else {
|
|
13757
14114
|
return void 0;
|
|
13758
14115
|
}
|
|
13759
|
-
const rel = remainder === "" ? moduleDir : moduleDir === "" ? remainder :
|
|
13760
|
-
const repoRelDir =
|
|
14116
|
+
const rel = remainder === "" ? moduleDir : moduleDir === "" ? remainder : path36.posix.join(moduleDir, remainder);
|
|
14117
|
+
const repoRelDir = path36.posix.normalize(rel === "" ? "." : rel);
|
|
13761
14118
|
const cleanDir = repoRelDir === "." ? "" : repoRelDir;
|
|
13762
14119
|
if (!deps.dirExists(cleanDir)) return void 0;
|
|
13763
14120
|
const goFiles = deps.goFilesIn(cleanDir);
|
|
@@ -13785,7 +14142,7 @@ function resolveGoImport(importPath, fromFile, deps) {
|
|
|
13785
14142
|
}
|
|
13786
14143
|
|
|
13787
14144
|
// src/relations/extractors/java-resolve.ts
|
|
13788
|
-
import
|
|
14145
|
+
import path37 from "path";
|
|
13789
14146
|
function resolveJavaFqn(specifier, fromFile, deps) {
|
|
13790
14147
|
const segments = specifier.split(".").filter((s) => s.length > 0);
|
|
13791
14148
|
if (segments.length === 0) return void 0;
|
|
@@ -13795,7 +14152,7 @@ function resolveJavaPackageFiles(packageFqn2, fromFile, deps) {
|
|
|
13795
14152
|
const segments = packageFqn2.split(".").filter((s) => s.length > 0);
|
|
13796
14153
|
if (segments.length === 0) return [];
|
|
13797
14154
|
const pkgDir = segments.join("/");
|
|
13798
|
-
for (const dir of ancestorDirs2(
|
|
14155
|
+
for (const dir of ancestorDirs2(path37.posix.dirname(toPosix5(fromFile)))) {
|
|
13799
14156
|
const repoRelDir = joinUnder2(dir, pkgDir);
|
|
13800
14157
|
const files = deps.javaFilesIn(repoRelDir);
|
|
13801
14158
|
if (files.length > 0) return [...files].sort();
|
|
@@ -13805,7 +14162,7 @@ function resolveJavaPackageFiles(packageFqn2, fromFile, deps) {
|
|
|
13805
14162
|
function resolveType(segments, fromFile, deps) {
|
|
13806
14163
|
const typePath = segments.join("/") + ".java";
|
|
13807
14164
|
const parentTypePath = segments.length >= 2 ? segments.slice(0, -1).join("/") + ".java" : void 0;
|
|
13808
|
-
for (const dir of ancestorDirs2(
|
|
14165
|
+
for (const dir of ancestorDirs2(path37.posix.dirname(toPosix5(fromFile)))) {
|
|
13809
14166
|
const candidates2 = [joinUnder2(dir, typePath)];
|
|
13810
14167
|
if (parentTypePath !== void 0) candidates2.push(joinUnder2(dir, parentTypePath));
|
|
13811
14168
|
for (const cand of candidates2) {
|
|
@@ -13820,20 +14177,20 @@ function ancestorDirs2(dir) {
|
|
|
13820
14177
|
for (; ; ) {
|
|
13821
14178
|
out.push(cur);
|
|
13822
14179
|
if (cur === "") break;
|
|
13823
|
-
const parent =
|
|
14180
|
+
const parent = path37.posix.dirname(cur);
|
|
13824
14181
|
cur = parent === "." ? "" : parent;
|
|
13825
14182
|
}
|
|
13826
14183
|
return out;
|
|
13827
14184
|
}
|
|
13828
14185
|
function joinUnder2(dir, sub) {
|
|
13829
|
-
return
|
|
14186
|
+
return path37.posix.normalize(dir === "" ? sub : path37.posix.join(dir, sub));
|
|
13830
14187
|
}
|
|
13831
14188
|
function toPosix5(p2) {
|
|
13832
14189
|
return p2.replace(/\\/g, "/");
|
|
13833
14190
|
}
|
|
13834
14191
|
|
|
13835
14192
|
// src/relations/extractors/php-resolve.ts
|
|
13836
|
-
import
|
|
14193
|
+
import path38 from "path";
|
|
13837
14194
|
function resolvePhpFqn(specifier, fromFile, deps) {
|
|
13838
14195
|
const fqn = specifier.startsWith("\\") ? specifier.slice(1) : specifier;
|
|
13839
14196
|
if (fqn === "") return void 0;
|
|
@@ -13859,7 +14216,7 @@ function resolvePhpFqn(specifier, fromFile, deps) {
|
|
|
13859
14216
|
return [...hits][0];
|
|
13860
14217
|
}
|
|
13861
14218
|
function joinUnder3(dir, sub) {
|
|
13862
|
-
return
|
|
14219
|
+
return path38.posix.normalize(dir === "" ? sub : path38.posix.join(dir, sub));
|
|
13863
14220
|
}
|
|
13864
14221
|
function parsePsr4(composerJsonText, composerDir) {
|
|
13865
14222
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -13897,19 +14254,19 @@ function parsePsr4(composerJsonText, composerDir) {
|
|
|
13897
14254
|
function normalizeDir(value, composerDir) {
|
|
13898
14255
|
const trimmed = value.replace(/\/+$/, "");
|
|
13899
14256
|
if (trimmed === "" || trimmed === ".") return composerDir;
|
|
13900
|
-
const joined = composerDir === "" ? trimmed :
|
|
13901
|
-
const norm =
|
|
14257
|
+
const joined = composerDir === "" ? trimmed : path38.posix.join(composerDir, trimmed);
|
|
14258
|
+
const norm = path38.posix.normalize(joined);
|
|
13902
14259
|
return norm === "." ? "" : norm;
|
|
13903
14260
|
}
|
|
13904
14261
|
|
|
13905
14262
|
// src/relations/extractors/rust-resolve.ts
|
|
13906
|
-
import
|
|
14263
|
+
import path39 from "path";
|
|
13907
14264
|
function resolveRustPath(specifier, fromFile, exists, deps) {
|
|
13908
14265
|
const segments = specifier.split("::").filter((s) => s.length > 0);
|
|
13909
14266
|
if (segments.length === 0) return void 0;
|
|
13910
14267
|
const root = segments[0];
|
|
13911
14268
|
const rest = segments.slice(1);
|
|
13912
|
-
const fromDir =
|
|
14269
|
+
const fromDir = path39.posix.dirname(toPosix6(fromFile));
|
|
13913
14270
|
if (root === "super" || root === "self") {
|
|
13914
14271
|
return resolveRelative2(root, segments, fromFile, exists, deps);
|
|
13915
14272
|
}
|
|
@@ -13929,9 +14286,9 @@ function resolveRelative2(root, segments, fromFile, exists, deps) {
|
|
|
13929
14286
|
if (crate === void 0) return void 0;
|
|
13930
14287
|
const srcDir = crate.srcDir;
|
|
13931
14288
|
const fromPosix = toPosix6(fromFile);
|
|
13932
|
-
const fromDir =
|
|
13933
|
-
const baseName =
|
|
13934
|
-
let moduleDir = baseName === "mod" || baseName === "lib" || baseName === "main" ? fromDir :
|
|
14289
|
+
const fromDir = path39.posix.dirname(fromPosix);
|
|
14290
|
+
const baseName = path39.posix.basename(fromPosix, ".rs");
|
|
14291
|
+
let moduleDir = baseName === "mod" || baseName === "lib" || baseName === "main" ? fromDir : path39.posix.join(fromDir, baseName);
|
|
13935
14292
|
let i = 0;
|
|
13936
14293
|
for (; i < segments.length; i++) {
|
|
13937
14294
|
const seg = segments[i];
|
|
@@ -13939,7 +14296,7 @@ function resolveRelative2(root, segments, fromFile, exists, deps) {
|
|
|
13939
14296
|
continue;
|
|
13940
14297
|
}
|
|
13941
14298
|
if (seg === "super") {
|
|
13942
|
-
const parent =
|
|
14299
|
+
const parent = path39.posix.dirname(moduleDir);
|
|
13943
14300
|
if (!withinSrc(parent, srcDir)) return void 0;
|
|
13944
14301
|
moduleDir = parent;
|
|
13945
14302
|
continue;
|
|
@@ -13971,13 +14328,13 @@ function resolveFromModuleDir(moduleDir, tail, exists) {
|
|
|
13971
14328
|
return void 0;
|
|
13972
14329
|
}
|
|
13973
14330
|
function withinSrc(dir, srcDir) {
|
|
13974
|
-
const d =
|
|
13975
|
-
const s =
|
|
14331
|
+
const d = path39.posix.normalize(dir);
|
|
14332
|
+
const s = path39.posix.normalize(srcDir);
|
|
13976
14333
|
if (s === "" || s === ".") return !d.startsWith("..");
|
|
13977
14334
|
return d === s || d.startsWith(s + "/");
|
|
13978
14335
|
}
|
|
13979
14336
|
function joinUnder4(dir, sub) {
|
|
13980
|
-
const joined =
|
|
14337
|
+
const joined = path39.posix.normalize(dir === "" ? sub : path39.posix.join(dir, sub));
|
|
13981
14338
|
if (joined.startsWith("..")) return void 0;
|
|
13982
14339
|
return joined;
|
|
13983
14340
|
}
|
|
@@ -13986,16 +14343,16 @@ function toPosix6(p2) {
|
|
|
13986
14343
|
}
|
|
13987
14344
|
|
|
13988
14345
|
// src/relations/extractors/include-resolve.ts
|
|
13989
|
-
import
|
|
14346
|
+
import path40 from "path";
|
|
13990
14347
|
function resolveIncludePath(headerPath, fromFile, exists) {
|
|
13991
14348
|
if (headerPath === "") return void 0;
|
|
13992
|
-
const fromDir =
|
|
13993
|
-
const relative3 = normalizeRepoRel(
|
|
14349
|
+
const fromDir = path40.posix.dirname(toPosix7(fromFile));
|
|
14350
|
+
const relative3 = normalizeRepoRel(path40.posix.join(fromDir, headerPath));
|
|
13994
14351
|
if (relative3 !== void 0 && exists(relative3)) return relative3;
|
|
13995
14352
|
return void 0;
|
|
13996
14353
|
}
|
|
13997
14354
|
function normalizeRepoRel(p2) {
|
|
13998
|
-
const norm =
|
|
14355
|
+
const norm = path40.posix.normalize(p2);
|
|
13999
14356
|
if (norm.startsWith("..")) return void 0;
|
|
14000
14357
|
return norm;
|
|
14001
14358
|
}
|
|
@@ -14004,13 +14361,13 @@ function toPosix7(p2) {
|
|
|
14004
14361
|
}
|
|
14005
14362
|
|
|
14006
14363
|
// src/relations/extractors/ruby-resolve.ts
|
|
14007
|
-
import
|
|
14364
|
+
import path41 from "path";
|
|
14008
14365
|
function resolveRubyRequireRelative(specifier, fromFile, exists) {
|
|
14009
14366
|
if (specifier === "") return void 0;
|
|
14010
|
-
const fromDir =
|
|
14367
|
+
const fromDir = path41.posix.dirname(toPosix8(fromFile));
|
|
14011
14368
|
const withExt = /\.rb$/.test(specifier) ? specifier : `${specifier}.rb`;
|
|
14012
|
-
const joined =
|
|
14013
|
-
const normalized =
|
|
14369
|
+
const joined = path41.posix.join(fromDir, withExt);
|
|
14370
|
+
const normalized = path41.posix.normalize(joined);
|
|
14014
14371
|
if (normalized.startsWith("..")) return void 0;
|
|
14015
14372
|
return exists(normalized) ? normalized : void 0;
|
|
14016
14373
|
}
|
|
@@ -14020,7 +14377,7 @@ function toPosix8(p2) {
|
|
|
14020
14377
|
|
|
14021
14378
|
// src/relations/resolve-path.ts
|
|
14022
14379
|
function makeResolvePathToFile(projectRoot, ownerOf) {
|
|
14023
|
-
const exists = (repoRelPosix) =>
|
|
14380
|
+
const exists = (repoRelPosix) => existsSync8(path42.resolve(projectRoot, repoRelPosix));
|
|
14024
14381
|
const goDeps = makeGoResolveDeps(projectRoot, ownerOf);
|
|
14025
14382
|
const javaDeps = makeJavaResolveDeps(projectRoot, exists);
|
|
14026
14383
|
const phpDeps = makePhpResolveDeps(projectRoot, exists);
|
|
@@ -14068,10 +14425,10 @@ function makeResolvePathToFile(projectRoot, ownerOf) {
|
|
|
14068
14425
|
function makeRustResolveDeps(projectRoot) {
|
|
14069
14426
|
const byDir = /* @__PURE__ */ new Map();
|
|
14070
14427
|
function readCrateName(repoRelDir) {
|
|
14071
|
-
const abs =
|
|
14428
|
+
const abs = path42.join(projectRoot, repoRelDir, "Cargo.toml");
|
|
14072
14429
|
let text2;
|
|
14073
14430
|
try {
|
|
14074
|
-
text2 =
|
|
14431
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14075
14432
|
} catch {
|
|
14076
14433
|
return void 0;
|
|
14077
14434
|
}
|
|
@@ -14089,14 +14446,14 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14089
14446
|
return void 0;
|
|
14090
14447
|
}
|
|
14091
14448
|
function crateRootFor(fromFile) {
|
|
14092
|
-
let dir =
|
|
14449
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14093
14450
|
if (dir === ".") dir = "";
|
|
14094
14451
|
for (; ; ) {
|
|
14095
14452
|
if (byDir.has(dir)) {
|
|
14096
14453
|
const cached = byDir.get(dir);
|
|
14097
14454
|
if (cached !== void 0) return cached;
|
|
14098
|
-
} else if (
|
|
14099
|
-
const srcDir = dir === "" ? "src" :
|
|
14455
|
+
} else if (existsSync8(path42.join(projectRoot, dir, "Cargo.toml"))) {
|
|
14456
|
+
const srcDir = dir === "" ? "src" : path42.posix.join(dir, "src");
|
|
14100
14457
|
const entry = { srcDir, crateName: readCrateName(dir) };
|
|
14101
14458
|
byDir.set(dir, entry);
|
|
14102
14459
|
return entry;
|
|
@@ -14104,7 +14461,7 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14104
14461
|
byDir.set(dir, void 0);
|
|
14105
14462
|
}
|
|
14106
14463
|
if (dir === "") return void 0;
|
|
14107
|
-
const parent =
|
|
14464
|
+
const parent = path42.posix.dirname(dir);
|
|
14108
14465
|
dir = parent === "." ? "" : parent;
|
|
14109
14466
|
}
|
|
14110
14467
|
}
|
|
@@ -14113,10 +14470,10 @@ function makeRustResolveDeps(projectRoot) {
|
|
|
14113
14470
|
function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
14114
14471
|
const moduleByDir = /* @__PURE__ */ new Map();
|
|
14115
14472
|
function readModulePath(repoRelDir) {
|
|
14116
|
-
const abs =
|
|
14473
|
+
const abs = path42.join(projectRoot, repoRelDir, "go.mod");
|
|
14117
14474
|
let text2;
|
|
14118
14475
|
try {
|
|
14119
|
-
text2 =
|
|
14476
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14120
14477
|
} catch {
|
|
14121
14478
|
return void 0;
|
|
14122
14479
|
}
|
|
@@ -14129,24 +14486,24 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14129
14486
|
return void 0;
|
|
14130
14487
|
}
|
|
14131
14488
|
function modulePathFor(fromFile) {
|
|
14132
|
-
let dir =
|
|
14489
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14133
14490
|
if (dir === ".") dir = "";
|
|
14134
14491
|
for (; ; ) {
|
|
14135
14492
|
if (moduleByDir.has(dir)) {
|
|
14136
14493
|
const cached = moduleByDir.get(dir);
|
|
14137
14494
|
if (cached !== void 0) return { modulePath: cached, moduleDir: dir };
|
|
14138
14495
|
} else {
|
|
14139
|
-
const mod =
|
|
14496
|
+
const mod = existsSync8(path42.join(projectRoot, dir, "go.mod")) ? readModulePath(dir) : void 0;
|
|
14140
14497
|
moduleByDir.set(dir, mod);
|
|
14141
14498
|
if (mod !== void 0) return { modulePath: mod, moduleDir: dir };
|
|
14142
14499
|
}
|
|
14143
14500
|
if (dir === "") return void 0;
|
|
14144
|
-
const parent =
|
|
14501
|
+
const parent = path42.posix.dirname(dir);
|
|
14145
14502
|
dir = parent === "." ? "" : parent;
|
|
14146
14503
|
}
|
|
14147
14504
|
}
|
|
14148
14505
|
function dirExists(repoRelDir) {
|
|
14149
|
-
const abs =
|
|
14506
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14150
14507
|
try {
|
|
14151
14508
|
return statSync3(abs).isDirectory();
|
|
14152
14509
|
} catch {
|
|
@@ -14154,7 +14511,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14154
14511
|
}
|
|
14155
14512
|
}
|
|
14156
14513
|
function goFilesIn(repoRelDir) {
|
|
14157
|
-
const abs =
|
|
14514
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14158
14515
|
let entries;
|
|
14159
14516
|
try {
|
|
14160
14517
|
entries = readdirSync3(abs, { withFileTypes: true });
|
|
@@ -14164,7 +14521,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14164
14521
|
const out = [];
|
|
14165
14522
|
for (const e of entries) {
|
|
14166
14523
|
if (e.isFile() && e.name.endsWith(".go")) {
|
|
14167
|
-
out.push(repoRelDir === "" ? e.name :
|
|
14524
|
+
out.push(repoRelDir === "" ? e.name : path42.posix.join(repoRelDir, e.name));
|
|
14168
14525
|
}
|
|
14169
14526
|
}
|
|
14170
14527
|
return out;
|
|
@@ -14173,7 +14530,7 @@ function makeGoResolveDeps(projectRoot, ownerOf) {
|
|
|
14173
14530
|
}
|
|
14174
14531
|
function makeJavaResolveDeps(projectRoot, exists) {
|
|
14175
14532
|
function javaFilesIn(repoRelDir) {
|
|
14176
|
-
const abs =
|
|
14533
|
+
const abs = path42.resolve(projectRoot, repoRelDir);
|
|
14177
14534
|
let entries;
|
|
14178
14535
|
try {
|
|
14179
14536
|
entries = readdirSync3(abs, { withFileTypes: true });
|
|
@@ -14183,7 +14540,7 @@ function makeJavaResolveDeps(projectRoot, exists) {
|
|
|
14183
14540
|
const out = [];
|
|
14184
14541
|
for (const e of entries) {
|
|
14185
14542
|
if (e.isFile() && e.name.endsWith(".java")) {
|
|
14186
|
-
out.push(repoRelDir === "" ? e.name :
|
|
14543
|
+
out.push(repoRelDir === "" ? e.name : path42.posix.join(repoRelDir, e.name));
|
|
14187
14544
|
}
|
|
14188
14545
|
}
|
|
14189
14546
|
return out;
|
|
@@ -14193,23 +14550,23 @@ function makeJavaResolveDeps(projectRoot, exists) {
|
|
|
14193
14550
|
function makePhpResolveDeps(projectRoot, exists) {
|
|
14194
14551
|
const psr4ByDir = /* @__PURE__ */ new Map();
|
|
14195
14552
|
function readPsr4(repoRelDir) {
|
|
14196
|
-
const abs =
|
|
14553
|
+
const abs = path42.join(projectRoot, repoRelDir, "composer.json");
|
|
14197
14554
|
let text2;
|
|
14198
14555
|
try {
|
|
14199
|
-
text2 =
|
|
14556
|
+
text2 = readFileSync8(abs, "utf-8");
|
|
14200
14557
|
} catch {
|
|
14201
14558
|
return /* @__PURE__ */ new Map();
|
|
14202
14559
|
}
|
|
14203
14560
|
return parsePsr4(text2, repoRelDir);
|
|
14204
14561
|
}
|
|
14205
14562
|
function psr4For(fromFile) {
|
|
14206
|
-
let dir =
|
|
14563
|
+
let dir = path42.posix.dirname(toPosix9(fromFile));
|
|
14207
14564
|
if (dir === ".") dir = "";
|
|
14208
14565
|
for (; ; ) {
|
|
14209
14566
|
if (psr4ByDir.has(dir)) {
|
|
14210
14567
|
const cached = psr4ByDir.get(dir);
|
|
14211
14568
|
if (cached !== void 0 && cached.size > 0) return cached;
|
|
14212
|
-
} else if (
|
|
14569
|
+
} else if (existsSync8(path42.join(projectRoot, dir, "composer.json"))) {
|
|
14213
14570
|
const map = readPsr4(dir);
|
|
14214
14571
|
psr4ByDir.set(dir, map);
|
|
14215
14572
|
if (map.size > 0) return map;
|
|
@@ -14217,7 +14574,7 @@ function makePhpResolveDeps(projectRoot, exists) {
|
|
|
14217
14574
|
psr4ByDir.set(dir, /* @__PURE__ */ new Map());
|
|
14218
14575
|
}
|
|
14219
14576
|
if (dir === "") return /* @__PURE__ */ new Map();
|
|
14220
|
-
const parent =
|
|
14577
|
+
const parent = path42.posix.dirname(dir);
|
|
14221
14578
|
dir = parent === "." ? "" : parent;
|
|
14222
14579
|
}
|
|
14223
14580
|
}
|
|
@@ -14387,7 +14744,7 @@ function emitPairIssue(vp) {
|
|
|
14387
14744
|
async function classifyLogStateFromLock(graph, projectRoot, lock, issues) {
|
|
14388
14745
|
for (const [nodePath] of graph.nodes) {
|
|
14389
14746
|
const logRel = `.yggdrasil/model/${nodePath}/log.md`;
|
|
14390
|
-
const logAbs =
|
|
14747
|
+
const logAbs = path43.join(projectRoot, logRel);
|
|
14391
14748
|
let logContent = null;
|
|
14392
14749
|
try {
|
|
14393
14750
|
logContent = await readTextFile(logAbs);
|
|
@@ -14472,8 +14829,8 @@ function scanUncoveredFiles(graph, gitTrackedFiles) {
|
|
|
14472
14829
|
const paths = normalizeMappingPaths(node.meta.mapping);
|
|
14473
14830
|
allMappings.push(...paths);
|
|
14474
14831
|
}
|
|
14475
|
-
const projectRoot =
|
|
14476
|
-
const yggPrefix = toPosixPath(
|
|
14832
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14833
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14477
14834
|
const uncovered = [];
|
|
14478
14835
|
const tracked = excludeNestedGraphSubtrees(gitTrackedFiles);
|
|
14479
14836
|
for (const file of tracked) {
|
|
@@ -14495,8 +14852,8 @@ async function scanGitignoredCoveredFiles(graph, gitTrackedFiles) {
|
|
|
14495
14852
|
if (!isGlobPattern(raw)) directlyNamed.add(normalizeMappingPath(raw));
|
|
14496
14853
|
}
|
|
14497
14854
|
}
|
|
14498
|
-
const projectRoot =
|
|
14499
|
-
const yggPrefix = toPosixPath(
|
|
14855
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14856
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14500
14857
|
let gitignoreStack;
|
|
14501
14858
|
try {
|
|
14502
14859
|
gitignoreStack = await loadRootGitignoreStack(projectRoot);
|
|
@@ -14514,7 +14871,7 @@ async function scanGitignoredCoveredFiles(graph, gitTrackedFiles) {
|
|
|
14514
14871
|
if (directlyNamed.has(normalizeMappingPath(normalized))) continue;
|
|
14515
14872
|
let ignored;
|
|
14516
14873
|
try {
|
|
14517
|
-
ignored = isIgnoredByStack(
|
|
14874
|
+
ignored = isIgnoredByStack(path43.join(projectRoot, normalized), gitignoreStack);
|
|
14518
14875
|
} catch (err) {
|
|
14519
14876
|
debugWrite(`[check] scanGitignoredCoveredFiles: isIgnoredByStack threw for ${normalized}: ${err.message}`);
|
|
14520
14877
|
continue;
|
|
@@ -14537,7 +14894,7 @@ function buildGitignoredCoveredIssues(offending) {
|
|
|
14537
14894
|
}));
|
|
14538
14895
|
}
|
|
14539
14896
|
async function runCheck(graph, gitTrackedFiles) {
|
|
14540
|
-
const projectRoot =
|
|
14897
|
+
const projectRoot = path43.dirname(graph.rootPath);
|
|
14541
14898
|
const validation = await validate(graph);
|
|
14542
14899
|
const validationIssues = validation.issues.filter((vi) => vi.code).map((vi) => ({ ...vi, code: vi.code }));
|
|
14543
14900
|
const lockIssues = [];
|
|
@@ -14560,7 +14917,9 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14560
14917
|
const relResult = await runRelationPass(graph, projectRoot, {
|
|
14561
14918
|
extractorFor: extractorForLanguage,
|
|
14562
14919
|
resolvePathToFile: makeResolvePathToFile(projectRoot, buildOwnerIndex(graph.nodes).ownerOf),
|
|
14563
|
-
|
|
14920
|
+
// Repurposed field: now the content-addressed AST fact cache root (.ast-cache), the
|
|
14921
|
+
// per-file parse cache that skips the tree-sitter re-parse of unchanged files.
|
|
14922
|
+
symbolIndexDir: astCacheDir(graph.rootPath)
|
|
14564
14923
|
});
|
|
14565
14924
|
for (const [nodeId, nv] of relResult.violationsByNode) {
|
|
14566
14925
|
if (nv.verdict !== "refused") continue;
|
|
@@ -14591,7 +14950,7 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14591
14950
|
let coveredFiles = 0;
|
|
14592
14951
|
let totalFiles = 0;
|
|
14593
14952
|
if (gitTrackedFiles !== null) {
|
|
14594
|
-
const yggPrefix = toPosixPath(
|
|
14953
|
+
const yggPrefix = toPosixPath(path43.relative(projectRoot, graph.rootPath));
|
|
14595
14954
|
const sourceFiles = excludeNestedGraphSubtrees(gitTrackedFiles).filter((f) => {
|
|
14596
14955
|
const normalized = toPosixPath(f.trim());
|
|
14597
14956
|
return !normalized.startsWith(yggPrefix + "/") && normalized !== yggPrefix;
|
|
@@ -14622,7 +14981,7 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
14622
14981
|
const advisoryWarnings = allIssues.filter((i) => i.code === "aspect-violation-advisory").length;
|
|
14623
14982
|
const draftSkipped = countDraftAspectsAcrossGraph(graph);
|
|
14624
14983
|
return {
|
|
14625
|
-
projectName:
|
|
14984
|
+
projectName: path43.basename(projectRoot),
|
|
14626
14985
|
nodeCount: graph.nodes.size,
|
|
14627
14986
|
nodeTypeCounts,
|
|
14628
14987
|
aspectCount: graph.aspects.length,
|
|
@@ -14705,7 +15064,7 @@ function computeSuggestedNext(issues) {
|
|
|
14705
15064
|
}
|
|
14706
15065
|
|
|
14707
15066
|
// src/core/fill.ts
|
|
14708
|
-
import
|
|
15067
|
+
import path46 from "path";
|
|
14709
15068
|
|
|
14710
15069
|
// src/llm/api-utils.ts
|
|
14711
15070
|
var ENV_VAR_MAP = {
|
|
@@ -15166,13 +15525,13 @@ var GeminiCliProvider = class extends CliAgentProvider {
|
|
|
15166
15525
|
registerProvider("gemini-cli", (c) => new GeminiCliProvider({ model: c.model, timeout: c.timeout }));
|
|
15167
15526
|
|
|
15168
15527
|
// src/core/fill-det.ts
|
|
15169
|
-
import
|
|
15528
|
+
import path44 from "path";
|
|
15170
15529
|
|
|
15171
15530
|
// src/core/fill-shared.ts
|
|
15172
15531
|
async function readBytesOrEmpty(absPath) {
|
|
15173
|
-
const { readFile:
|
|
15532
|
+
const { readFile: readFile21 } = await import("fs/promises");
|
|
15174
15533
|
try {
|
|
15175
|
-
return await
|
|
15534
|
+
return await readFile21(absPath);
|
|
15176
15535
|
} catch (e) {
|
|
15177
15536
|
debugWrite(`[fill] readBytesOrEmpty failed for ${absPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
15178
15537
|
return Buffer.alloc(0);
|
|
@@ -15181,7 +15540,7 @@ async function readBytesOrEmpty(absPath) {
|
|
|
15181
15540
|
|
|
15182
15541
|
// src/core/fill-det.ts
|
|
15183
15542
|
async function fillDetPair(graph, projectRoot, pair, aspect) {
|
|
15184
|
-
const aspectDirAbs =
|
|
15543
|
+
const aspectDirAbs = path44.join(projectRoot, ".yggdrasil", "aspects", aspect.id);
|
|
15185
15544
|
const fullMapping = await computeNodeMappedFiles(graph, pair.nodePath);
|
|
15186
15545
|
const subjectScope = pair.subjectFiles.length < fullMapping.length ? pair.subjectFiles : void 0;
|
|
15187
15546
|
const runOnce = async () => {
|
|
@@ -15222,7 +15581,7 @@ async function fillDetPair(graph, projectRoot, pair, aspect) {
|
|
|
15222
15581
|
const observations = run.result.observations;
|
|
15223
15582
|
const files = [];
|
|
15224
15583
|
for (const rel of pair.subjectFiles) {
|
|
15225
|
-
const abs =
|
|
15584
|
+
const abs = path44.resolve(projectRoot, rel);
|
|
15226
15585
|
const bytes = await readBytesOrEmpty(abs);
|
|
15227
15586
|
files.push([rel, hashBytes(bytes)]);
|
|
15228
15587
|
}
|
|
@@ -15254,7 +15613,7 @@ function detRuntimeNotice(aspectId, unitKey, reason) {
|
|
|
15254
15613
|
}
|
|
15255
15614
|
|
|
15256
15615
|
// src/core/fill-llm.ts
|
|
15257
|
-
import
|
|
15616
|
+
import path45 from "path";
|
|
15258
15617
|
|
|
15259
15618
|
// src/llm/aspect-verifier.ts
|
|
15260
15619
|
async function verifyWithConsensus(provider, prompt, consensus) {
|
|
@@ -15283,14 +15642,14 @@ async function verifyWithConsensus(provider, prompt, consensus) {
|
|
|
15283
15642
|
async function fillLlmPair(graph, projectRoot, pair, aspect, tier, tierName, mergedTier, provider, referencesCache) {
|
|
15284
15643
|
const subjects = [];
|
|
15285
15644
|
for (const rel of pair.subjectFiles) {
|
|
15286
|
-
const bytes = await readBytesOrEmpty(
|
|
15645
|
+
const bytes = await readBytesOrEmpty(path45.resolve(projectRoot, rel));
|
|
15287
15646
|
subjects.push({ path: rel, bytes });
|
|
15288
15647
|
}
|
|
15289
15648
|
const refInputs = aspect.references ?? [];
|
|
15290
15649
|
const referencesForHash = [];
|
|
15291
15650
|
const referencesForPrompt = [];
|
|
15292
15651
|
for (const ref of refInputs) {
|
|
15293
|
-
const absRef =
|
|
15652
|
+
const absRef = path45.resolve(projectRoot, ref.path);
|
|
15294
15653
|
let bytes = referencesCache.get(absRef);
|
|
15295
15654
|
if (bytes === void 0) {
|
|
15296
15655
|
bytes = await readFileBytes(absRef);
|
|
@@ -15770,7 +16129,7 @@ async function runFill(graph, opts) {
|
|
|
15770
16129
|
});
|
|
15771
16130
|
const emitIssue = opts.emitIssue ?? (() => {
|
|
15772
16131
|
});
|
|
15773
|
-
const projectRoot =
|
|
16132
|
+
const projectRoot = path46.dirname(graph.rootPath);
|
|
15774
16133
|
const onlyDeterministic = opts.onlyDeterministic ?? false;
|
|
15775
16134
|
const dryRun = opts.dryRun ?? false;
|
|
15776
16135
|
const isTTY2 = opts.isTTY ?? (process.stderr.isTTY ?? false);
|
|
@@ -16050,7 +16409,7 @@ async function runFill(graph, opts) {
|
|
|
16050
16409
|
}
|
|
16051
16410
|
|
|
16052
16411
|
// src/cli/check.ts
|
|
16053
|
-
import
|
|
16412
|
+
import path47 from "path";
|
|
16054
16413
|
|
|
16055
16414
|
// src/cli/group-issues.ts
|
|
16056
16415
|
var CODE_ONLY_GROUP_CODES = /* @__PURE__ */ new Set(["unverified"]);
|
|
@@ -16112,6 +16471,8 @@ function groupIssues(issues) {
|
|
|
16112
16471
|
const rep = sorted[0];
|
|
16113
16472
|
const nodes = new Set(sorted.map((m) => m.nodePath ?? ""));
|
|
16114
16473
|
const isCodeOnly = CODE_ONLY_GROUP_CODES.has(rep.code);
|
|
16474
|
+
const distinctNext = new Set(sorted.map((m) => m.messageData.next ?? ""));
|
|
16475
|
+
const distinctWhy = new Set(sorted.map((m) => m.messageData.why ?? ""));
|
|
16115
16476
|
groups.push({
|
|
16116
16477
|
code: rep.code,
|
|
16117
16478
|
aspectId: isCodeOnly ? void 0 : rep.aspectId,
|
|
@@ -16122,6 +16483,8 @@ function groupIssues(issues) {
|
|
|
16122
16483
|
sharedWhy: rep.messageData.why,
|
|
16123
16484
|
sharedNext: rep.messageData.next,
|
|
16124
16485
|
perMemberReason: FULL_WHAT_CODES.has(rep.code),
|
|
16486
|
+
divergentNext: distinctNext.size > 1,
|
|
16487
|
+
divergentWhy: distinctWhy.size > 1,
|
|
16125
16488
|
members: sorted
|
|
16126
16489
|
});
|
|
16127
16490
|
}
|
|
@@ -16156,12 +16519,12 @@ function resolveApproveMode(opts, config) {
|
|
|
16156
16519
|
return { approve: false, onlyDeterministic: false };
|
|
16157
16520
|
}
|
|
16158
16521
|
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
|
|
16522
|
+
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
16523
|
try {
|
|
16161
16524
|
const cwd = process.cwd();
|
|
16162
16525
|
const graph = await loadGraphOrAbort(cwd, { tolerateInvalidConfig: true });
|
|
16163
16526
|
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
16164
|
-
const projectRoot =
|
|
16527
|
+
const projectRoot = path47.dirname(graph.rootPath);
|
|
16165
16528
|
const gitFiles = await walkRepoFiles(projectRoot);
|
|
16166
16529
|
const wantsTop = opts.top !== void 0;
|
|
16167
16530
|
if (wantsTop && opts.summary) {
|
|
@@ -16182,6 +16545,16 @@ function registerCheckCommand(program2) {
|
|
|
16182
16545
|
await exitAfterFlush(1);
|
|
16183
16546
|
return;
|
|
16184
16547
|
}
|
|
16548
|
+
if (opts.onlyDeterministic && (wantsTop || opts.summary || opts.details || opts.aspect !== void 0)) {
|
|
16549
|
+
const viewFlag = wantsTop ? "--top" : opts.summary ? "--summary" : opts.details ? "--details" : "--aspect";
|
|
16550
|
+
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16551
|
+
what: `${viewFlag} cannot be combined with --only-deterministic.`,
|
|
16552
|
+
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.`,
|
|
16553
|
+
next: `Run: yg check ${viewFlag}${opts.aspect !== void 0 ? " <id>" : wantsTop ? " <n>" : ""} (read-only view), or yg check --approve --only-deterministic (deterministic fill).`
|
|
16554
|
+
})}`) + "\n");
|
|
16555
|
+
await exitAfterFlush(1);
|
|
16556
|
+
return;
|
|
16557
|
+
}
|
|
16185
16558
|
if (opts.details && (wantsTop || opts.summary)) {
|
|
16186
16559
|
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16187
16560
|
what: "--details cannot be combined with --top or --summary.",
|
|
@@ -16229,6 +16602,18 @@ function registerCheckCommand(program2) {
|
|
|
16229
16602
|
await exitAfterFlush(1);
|
|
16230
16603
|
return;
|
|
16231
16604
|
}
|
|
16605
|
+
const knownAspectIds = (graph.aspects ?? []).map((a) => a.id);
|
|
16606
|
+
if (!knownAspectIds.includes(opts.aspect)) {
|
|
16607
|
+
const idList = knownAspectIds.slice().sort((a, b) => a.localeCompare(b, "en"));
|
|
16608
|
+
const known = idList.length === 0 ? "The graph defines no aspects." : idList.length <= 30 ? `Known aspect ids: ${idList.join(", ")}.` : `The graph defines ${idList.length} aspects.`;
|
|
16609
|
+
process.stderr.write(chalk8.red(`Error: ${buildIssueMessage({
|
|
16610
|
+
what: `Unknown aspect '${opts.aspect}'.`,
|
|
16611
|
+
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}`,
|
|
16612
|
+
next: "Run: yg aspects (list every aspect id), then yg check --aspect <id> with a real id; or yg check (full wall)."
|
|
16613
|
+
})}`) + "\n");
|
|
16614
|
+
await exitAfterFlush(1);
|
|
16615
|
+
return;
|
|
16616
|
+
}
|
|
16232
16617
|
}
|
|
16233
16618
|
let view = { kind: "full" };
|
|
16234
16619
|
if (opts.aspect !== void 0) {
|
|
@@ -16274,9 +16659,9 @@ function registerCheckCommand(program2) {
|
|
|
16274
16659
|
gitTrackedFiles: gitFiles,
|
|
16275
16660
|
onlyDeterministic: mode.onlyDeterministic,
|
|
16276
16661
|
dryRun: isDryRun,
|
|
16277
|
-
write:
|
|
16278
|
-
} : isDryRun ? (s) => {
|
|
16662
|
+
write: isDryRun ? (s) => {
|
|
16279
16663
|
process.stdout.write(s);
|
|
16664
|
+
} : isQuiet ? () => {
|
|
16280
16665
|
} : (s) => {
|
|
16281
16666
|
process.stderr.write(s);
|
|
16282
16667
|
},
|
|
@@ -16375,9 +16760,9 @@ function formatOutput(result, view = { kind: "full" }, autoFilled = false, emoji
|
|
|
16375
16760
|
const nextCmd = firstFiltered.messageData.next.split("\n")[0];
|
|
16376
16761
|
sections.push("");
|
|
16377
16762
|
sections.push(`Next (this group): ${nextCmd}`);
|
|
16763
|
+
sections.push("");
|
|
16764
|
+
return sections.join("\n");
|
|
16378
16765
|
}
|
|
16379
|
-
sections.push("");
|
|
16380
|
-
return sections.join("\n");
|
|
16381
16766
|
} else if (view.kind === "details") {
|
|
16382
16767
|
if (errors.length > 0) {
|
|
16383
16768
|
sections.push("");
|
|
@@ -16618,12 +17003,23 @@ function glossLabel(label) {
|
|
|
16618
17003
|
function renderGroup(group, lines, opts) {
|
|
16619
17004
|
const aspectSeg = group.aspectId ? ` aspect '${group.aspectId}'` : "";
|
|
16620
17005
|
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) {
|
|
17006
|
+
if (group.sharedWhy && !group.divergentWhy) lines.push(`${BLOCK_INDENT}${group.sharedWhy}`);
|
|
17007
|
+
if (group.sharedNext && !group.divergentNext) {
|
|
16623
17008
|
const nextLines = group.sharedNext.split("\n");
|
|
16624
17009
|
lines.push(`${BLOCK_INDENT}Fix: ${nextLines[0]}`);
|
|
16625
17010
|
for (const extra of nextLines.slice(1)) lines.push(`${BLOCK_INDENT}${extra}`);
|
|
16626
17011
|
}
|
|
17012
|
+
const MEMBER_DETAIL_INDENT = `${BLOCK_INDENT} `;
|
|
17013
|
+
const emitDivergentDetail = (m) => {
|
|
17014
|
+
if (group.divergentWhy && m.messageData.why) {
|
|
17015
|
+
lines.push(`${MEMBER_DETAIL_INDENT}Why: ${m.messageData.why.split("\n")[0]}`);
|
|
17016
|
+
}
|
|
17017
|
+
if (group.divergentNext && m.messageData.next) {
|
|
17018
|
+
const nextLines = m.messageData.next.split("\n");
|
|
17019
|
+
lines.push(`${MEMBER_DETAIL_INDENT}Fix: ${nextLines[0]}`);
|
|
17020
|
+
for (const extra of nextLines.slice(1)) lines.push(`${MEMBER_DETAIL_INDENT}${extra}`);
|
|
17021
|
+
}
|
|
17022
|
+
};
|
|
16627
17023
|
const members = group.members;
|
|
16628
17024
|
const truncate = opts.isTTY && members.length > CAP_NODES2;
|
|
16629
17025
|
const shown = truncate ? members.slice(0, CAP_NODES2) : members;
|
|
@@ -16639,10 +17035,12 @@ function renderGroup(group, lines, opts) {
|
|
|
16639
17035
|
lines.push(`${BLOCK_INDENT} ${extra}`);
|
|
16640
17036
|
}
|
|
16641
17037
|
}
|
|
17038
|
+
emitDivergentDetail(m);
|
|
16642
17039
|
} else {
|
|
16643
17040
|
const memberAspectSeg = group.aspectId === void 0 && m.aspectId !== void 0 ? ` aspect '${m.aspectId}'` : "";
|
|
16644
17041
|
const whatSeg = !memberAspectSeg && m.messageData.what ? ` ${m.messageData.what.split("\n")[0]}` : "";
|
|
16645
17042
|
lines.push(`${BLOCK_INDENT}- ${node}${memberAspectSeg || whatSeg}`);
|
|
17043
|
+
emitDivergentDetail(m);
|
|
16646
17044
|
}
|
|
16647
17045
|
}
|
|
16648
17046
|
if (truncate) {
|
|
@@ -16652,10 +17050,10 @@ function renderGroup(group, lines, opts) {
|
|
|
16652
17050
|
}
|
|
16653
17051
|
|
|
16654
17052
|
// src/cli/aspect-test.ts
|
|
16655
|
-
import
|
|
17053
|
+
import path49 from "path";
|
|
16656
17054
|
|
|
16657
17055
|
// src/ast/runner.ts
|
|
16658
|
-
import
|
|
17056
|
+
import path48 from "path";
|
|
16659
17057
|
import { readFile as readFile16 } from "fs/promises";
|
|
16660
17058
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
16661
17059
|
var AstRunnerError = class extends Error {
|
|
@@ -16672,7 +17070,7 @@ ${data.next}`);
|
|
|
16672
17070
|
};
|
|
16673
17071
|
async function runAstAspect(params) {
|
|
16674
17072
|
ensureLoaderRegistered();
|
|
16675
|
-
const checkPath =
|
|
17073
|
+
const checkPath = path48.resolve(params.projectRoot, params.aspectDir, "check.mjs");
|
|
16676
17074
|
let mod;
|
|
16677
17075
|
try {
|
|
16678
17076
|
mod = await import(pathToFileURL3(checkPath).href);
|
|
@@ -16704,8 +17102,8 @@ async function runAstAspect(params) {
|
|
|
16704
17102
|
sourceFiles.push({ path: f.path, content: cached.content, ast: cached.ast });
|
|
16705
17103
|
continue;
|
|
16706
17104
|
}
|
|
16707
|
-
const content20 = await readFile16(
|
|
16708
|
-
if (getLanguageForExtension(
|
|
17105
|
+
const content20 = await readFile16(path48.resolve(params.projectRoot, f.path), "utf-8");
|
|
17106
|
+
if (getLanguageForExtension(path48.extname(f.path).toLowerCase()) === null) {
|
|
16709
17107
|
sourceFiles.push({ path: f.path, content: content20, ast: void 0 });
|
|
16710
17108
|
continue;
|
|
16711
17109
|
}
|
|
@@ -16891,7 +17289,7 @@ function registerAspectTestCommand(program2) {
|
|
|
16891
17289
|
process.exit(1);
|
|
16892
17290
|
return;
|
|
16893
17291
|
}
|
|
16894
|
-
const aspectDir =
|
|
17292
|
+
const aspectDir = path49.join(".yggdrasil", "aspects", aspect.id);
|
|
16895
17293
|
if (hasNode) {
|
|
16896
17294
|
const nodePath = opts.node.trim().replace(/\/$/, "");
|
|
16897
17295
|
const node = graph.nodes.get(nodePath);
|
|
@@ -16951,12 +17349,17 @@ function registerAspectTestCommand(program2) {
|
|
|
16951
17349
|
await exitAfterFlush(1);
|
|
16952
17350
|
} catch (e) {
|
|
16953
17351
|
debugWrite(`[aspect-test] run failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
17352
|
+
if (e instanceof StructureRunnerError || e instanceof AstRunnerError) {
|
|
17353
|
+
process.stderr.write(buildIssueMessage(e.messageData) + "\n");
|
|
17354
|
+
await exitAfterFlush(1);
|
|
17355
|
+
return;
|
|
17356
|
+
}
|
|
16954
17357
|
abortOnUnexpectedError(e, "running aspect-test");
|
|
16955
17358
|
}
|
|
16956
17359
|
});
|
|
16957
17360
|
}
|
|
16958
17361
|
async function resolveCompanionsForTest(graph, projectRoot, pair, aspect) {
|
|
16959
|
-
const aspectDirAbs =
|
|
17362
|
+
const aspectDirAbs = path49.join(projectRoot, ".yggdrasil", "aspects", aspect.id);
|
|
16960
17363
|
const fullMapping = await computeNodeMappedFiles(graph, pair.nodePath);
|
|
16961
17364
|
const subjectScope = pair.subjectFiles.length < fullMapping.length ? pair.subjectFiles : void 0;
|
|
16962
17365
|
const run = await runCompanionHook({
|
|
@@ -17032,7 +17435,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17032
17435
|
const refInputs = aspect.references ?? [];
|
|
17033
17436
|
const referencesForPrompt = [];
|
|
17034
17437
|
for (const ref of refInputs) {
|
|
17035
|
-
const absRef =
|
|
17438
|
+
const absRef = path49.resolve(projectRoot, ref.path);
|
|
17036
17439
|
let content20;
|
|
17037
17440
|
try {
|
|
17038
17441
|
content20 = await readTextFile(absRef);
|
|
@@ -17078,7 +17481,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17078
17481
|
for (const rel of pair.subjectFiles) {
|
|
17079
17482
|
let content20;
|
|
17080
17483
|
try {
|
|
17081
|
-
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(
|
|
17484
|
+
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(path49.resolve(projectRoot, rel)));
|
|
17082
17485
|
content20 = bytes.toString("utf8");
|
|
17083
17486
|
} catch (e) {
|
|
17084
17487
|
debugWrite(`[aspect-test] subject file read failed for ${rel} on ${pair.unitKey}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -17134,7 +17537,7 @@ async function runLlmAspectTest(graph, projectRoot, aspect, nodePath, dryRun) {
|
|
|
17134
17537
|
for (const rel of pair.subjectFiles) {
|
|
17135
17538
|
let content20;
|
|
17136
17539
|
try {
|
|
17137
|
-
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(
|
|
17540
|
+
const bytes = await import("fs/promises").then((fs5) => fs5.readFile(path49.resolve(projectRoot, rel)));
|
|
17138
17541
|
content20 = bytes.toString("utf8");
|
|
17139
17542
|
} catch (e) {
|
|
17140
17543
|
debugWrite(`[aspect-test] subject file read failed for ${rel} on ${pair.unitKey}: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -17250,10 +17653,10 @@ function printStructureViolations(violations) {
|
|
|
17250
17653
|
// src/cli/log.ts
|
|
17251
17654
|
import chalk9 from "chalk";
|
|
17252
17655
|
import { readFile as readFile18, stat as stat7 } from "fs/promises";
|
|
17253
|
-
import
|
|
17656
|
+
import path53 from "path";
|
|
17254
17657
|
|
|
17255
17658
|
// src/core/log/log-add.ts
|
|
17256
|
-
import
|
|
17659
|
+
import path50 from "path";
|
|
17257
17660
|
|
|
17258
17661
|
// src/utils/node-path-validator.ts
|
|
17259
17662
|
function validateNodePath(raw) {
|
|
@@ -17366,7 +17769,7 @@ async function logAdd(input) {
|
|
|
17366
17769
|
}
|
|
17367
17770
|
};
|
|
17368
17771
|
}
|
|
17369
|
-
const logPath2 =
|
|
17772
|
+
const logPath2 = path50.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17370
17773
|
const stats = await statLogFile(logPath2);
|
|
17371
17774
|
if (stats !== null) {
|
|
17372
17775
|
if (stats.isSymbolicLink) {
|
|
@@ -17435,7 +17838,7 @@ function reasonHasLevel2HeaderOutsideFence(reason) {
|
|
|
17435
17838
|
}
|
|
17436
17839
|
|
|
17437
17840
|
// src/core/log/log-read.ts
|
|
17438
|
-
import
|
|
17841
|
+
import path51 from "path";
|
|
17439
17842
|
var DEFAULT_TOP = 10;
|
|
17440
17843
|
async function logRead(input) {
|
|
17441
17844
|
const { graph } = input;
|
|
@@ -17481,7 +17884,7 @@ async function logRead(input) {
|
|
|
17481
17884
|
}
|
|
17482
17885
|
};
|
|
17483
17886
|
}
|
|
17484
|
-
const logPath2 =
|
|
17887
|
+
const logPath2 = path51.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17485
17888
|
const content20 = await readLogSafe(logPath2);
|
|
17486
17889
|
if (content20 === "") {
|
|
17487
17890
|
return { ok: true, entries: [] };
|
|
@@ -17504,8 +17907,8 @@ async function logRead(input) {
|
|
|
17504
17907
|
}
|
|
17505
17908
|
|
|
17506
17909
|
// src/core/log/log-merge-resolve.ts
|
|
17507
|
-
import { createHash as
|
|
17508
|
-
import
|
|
17910
|
+
import { createHash as createHash7 } from "crypto";
|
|
17911
|
+
import path52 from "path";
|
|
17509
17912
|
|
|
17510
17913
|
// src/utils/git-introspect.ts
|
|
17511
17914
|
import { exec } from "child_process";
|
|
@@ -17585,7 +17988,7 @@ async function logMergeResolve(input) {
|
|
|
17585
17988
|
}
|
|
17586
17989
|
};
|
|
17587
17990
|
}
|
|
17588
|
-
const logPath2 =
|
|
17991
|
+
const logPath2 = path52.join(yggRoot, "model", nodePath, "log.md");
|
|
17589
17992
|
let currentLog;
|
|
17590
17993
|
try {
|
|
17591
17994
|
currentLog = await readTextFile(logPath2);
|
|
@@ -17633,7 +18036,7 @@ async function logMergeResolve(input) {
|
|
|
17633
18036
|
const p2New = parseLog(parent2Log).slice(ancestorEntries.length);
|
|
17634
18037
|
const currentEntries = parseLog(currentLog);
|
|
17635
18038
|
const currentNew = currentEntries.slice(ancestorEntries.length);
|
|
17636
|
-
const entryKey = (e) =>
|
|
18039
|
+
const entryKey = (e) => createHash7("sha256").update(`${e.datetime}
|
|
17637
18040
|
${e.body}`).digest("hex");
|
|
17638
18041
|
const parentNew = [...p1New, ...p2New];
|
|
17639
18042
|
const parentNewKeys = new Set(parentNew.map(entryKey));
|
|
@@ -17792,7 +18195,7 @@ ${entry.body}`);
|
|
|
17792
18195
|
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
18196
|
try {
|
|
17794
18197
|
const graph = await loadGraphOrAbort(process.cwd(), { tolerateInvalidConfig: true });
|
|
17795
|
-
const repoRoot =
|
|
18198
|
+
const repoRoot = path53.dirname(graph.rootPath);
|
|
17796
18199
|
const nodePath = toPosix(opts.node.trim()).replace(/\/$/, "");
|
|
17797
18200
|
const result = await logMergeResolve({ graph, nodePath, repoRoot });
|
|
17798
18201
|
if (!result.ok) {
|
|
@@ -17817,15 +18220,15 @@ import chalk10 from "chalk";
|
|
|
17817
18220
|
|
|
17818
18221
|
// src/io/find-index.ts
|
|
17819
18222
|
import { readFile as readFile19, lstat as lstat3 } from "fs/promises";
|
|
17820
|
-
import
|
|
18223
|
+
import path54 from "path";
|
|
17821
18224
|
import MiniSearch from "minisearch";
|
|
17822
18225
|
var MAX_BODY_BYTES = 1048576;
|
|
17823
18226
|
async function buildIndex(graph) {
|
|
17824
|
-
const projectRoot =
|
|
18227
|
+
const projectRoot = path54.dirname(graph.rootPath);
|
|
17825
18228
|
const docs = [];
|
|
17826
18229
|
for (const [nodePath, node] of graph.nodes) {
|
|
17827
18230
|
const displayPath = `model/${nodePath}/`;
|
|
17828
|
-
const logPath2 =
|
|
18231
|
+
const logPath2 = path54.join(graph.rootPath, "model", nodePath, "log.md");
|
|
17829
18232
|
let body = "";
|
|
17830
18233
|
try {
|
|
17831
18234
|
const st = await lstat3(logPath2);
|
|
@@ -17842,7 +18245,7 @@ This does not affect append-only integrity. No action required.
|
|
|
17842
18245
|
}
|
|
17843
18246
|
body = truncated;
|
|
17844
18247
|
} else if (st.isSymbolicLink()) {
|
|
17845
|
-
process.stderr.write(`Warning: skipping symlinked log.md at ${
|
|
18248
|
+
process.stderr.write(`Warning: skipping symlinked log.md at ${path54.relative(projectRoot, logPath2)}
|
|
17846
18249
|
`);
|
|
17847
18250
|
} else {
|
|
17848
18251
|
}
|
|
@@ -18010,18 +18413,18 @@ function registerFindCommand(program2) {
|
|
|
18010
18413
|
|
|
18011
18414
|
// src/cli/type-suggest.ts
|
|
18012
18415
|
import chalk11 from "chalk";
|
|
18013
|
-
import { existsSync as
|
|
18416
|
+
import { existsSync as existsSync9 } from "fs";
|
|
18014
18417
|
import { resolve as resolve5 } from "path";
|
|
18015
18418
|
|
|
18016
18419
|
// src/core/type-classifier.ts
|
|
18017
|
-
import
|
|
18420
|
+
import path55 from "path";
|
|
18018
18421
|
async function classifyFile(absPath, repoRelPath, graph, cache) {
|
|
18019
18422
|
const matches = [];
|
|
18020
18423
|
const partialScores = [];
|
|
18021
18424
|
const ctx = {
|
|
18022
18425
|
absPath,
|
|
18023
18426
|
repoRelPath,
|
|
18024
|
-
projectRoot:
|
|
18427
|
+
projectRoot: path55.dirname(graph.rootPath),
|
|
18025
18428
|
cache
|
|
18026
18429
|
};
|
|
18027
18430
|
for (const [typeId, def] of Object.entries(graph.architecture.node_types)) {
|
|
@@ -18077,7 +18480,7 @@ Type matching does not apply here.
|
|
|
18077
18480
|
return;
|
|
18078
18481
|
}
|
|
18079
18482
|
const gitignoreStack = await loadRootGitignoreStack(projectRoot);
|
|
18080
|
-
if (
|
|
18483
|
+
if (existsSync9(absPath) && isIgnoredByStack(absPath, gitignoreStack)) {
|
|
18081
18484
|
process.stderr.write(
|
|
18082
18485
|
chalk11.yellow(
|
|
18083
18486
|
`
|
|
@@ -18089,7 +18492,7 @@ file-mapping-gitignored. Proceeding with classification for context.
|
|
|
18089
18492
|
)
|
|
18090
18493
|
);
|
|
18091
18494
|
}
|
|
18092
|
-
if (!
|
|
18495
|
+
if (!existsSync9(absPath)) {
|
|
18093
18496
|
process.stdout.write(`
|
|
18094
18497
|
(File does not exist \u2014 evaluating path predicates only)
|
|
18095
18498
|
|
|
@@ -18317,6 +18720,10 @@ Examples:
|
|
|
18317
18720
|
Plain (non-glob) entries remain unchanged: an exact file path or a directory
|
|
18318
18721
|
prefix (e.g. \`src/handlers\`) covers that file or all files beneath it.
|
|
18319
18722
|
|
|
18723
|
+
## Allowed relations and default policy
|
|
18724
|
+
|
|
18725
|
+
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.
|
|
18726
|
+
|
|
18320
18727
|
## log_required \u2014 when to enable the log gate
|
|
18321
18728
|
|
|
18322
18729
|
Each node type may set \`log_required\` (default \`false\`). When \`true\`, a node of
|
|
@@ -20552,7 +20959,8 @@ to the \`.yggdrasil/\` directory):
|
|
|
20552
20959
|
|
|
20553
20960
|
\`\`\`
|
|
20554
20961
|
yg-secrets.yaml # provider API keys
|
|
20555
|
-
.symbols-cache/ # the relation pass's per-language symbol-index cache
|
|
20962
|
+
.symbols-cache/ # the relation pass's legacy per-language symbol-index cache
|
|
20963
|
+
.ast-cache/ # the relation pass's content-addressed per-file AST fact cache
|
|
20556
20964
|
.debug.log # the opt-in command debug log
|
|
20557
20965
|
\`\`\`
|
|
20558
20966
|
|
|
@@ -21231,9 +21639,7 @@ Event relations must be paired: if A emits to B, B must declare a
|
|
|
21231
21639
|
|
|
21232
21640
|
## Architecture controls allowed relations
|
|
21233
21641
|
|
|
21234
|
-
\`
|
|
21235
|
-
which relation types may target which node types. The validator rejects
|
|
21236
|
-
relations not permitted by the architecture.
|
|
21642
|
+
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
21643
|
|
|
21238
21644
|
When a needed relation is not allowed by the architecture:
|
|
21239
21645
|
1. Use a different relation type that IS allowed
|
|
@@ -22243,8 +22649,23 @@ node_types:
|
|
|
22243
22649
|
|
|
22244
22650
|
parents: [<type-id>, ...] # optional \u2014 allowed parent node types in the hierarchy.
|
|
22245
22651
|
|
|
22246
|
-
relations: # optional \u2014
|
|
22247
|
-
|
|
22652
|
+
relations: # optional \u2014 per-relation-type allow-list.
|
|
22653
|
+
# A relation type is constrained by listing its allowed target node types.
|
|
22654
|
+
# uses: [domain, data-access] \u2192 only those target types
|
|
22655
|
+
# uses: ['*'] \u2192 any target type
|
|
22656
|
+
# uses: [] \u2192 no target (relation type forbidden)
|
|
22657
|
+
# (relation type omitted) \u2192 governed by \`default\` below
|
|
22658
|
+
# default: allow | deny \u2192 policy for relation types NOT listed.
|
|
22659
|
+
# omitted \u21D2 allow (every unlisted relation type may target any type).
|
|
22660
|
+
# deny \u21D2 unlisted relation types target nothing (a sink).
|
|
22661
|
+
# Note: '*' is reserved as the any-target wildcard in relation lists and
|
|
22662
|
+
# must not be used as a node-type name (the parser rejects it).
|
|
22663
|
+
# Examples:
|
|
22664
|
+
# { default: deny } \u2192 pure sink
|
|
22665
|
+
# { default: deny, listens: ['*'] } \u2192 sink that may listen to anything
|
|
22666
|
+
# { default: allow, uses: [] } \u2192 everything open except \`uses\`
|
|
22667
|
+
uses: [<target-type>, ...]
|
|
22668
|
+
default: allow
|
|
22248
22669
|
`;
|
|
22249
22670
|
|
|
22250
22671
|
// src/templates/schemas/config.ts
|
|
@@ -22423,9 +22844,12 @@ function registerSchemasCommand(program2) {
|
|
|
22423
22844
|
}
|
|
22424
22845
|
|
|
22425
22846
|
// src/cli/suppressions.ts
|
|
22426
|
-
import chalk14 from "chalk";
|
|
22427
|
-
import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
|
|
22428
22847
|
import path57 from "path";
|
|
22848
|
+
|
|
22849
|
+
// src/portal/api/suppress-scan.ts
|
|
22850
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
22851
|
+
import path56 from "path";
|
|
22852
|
+
import chalk14 from "chalk";
|
|
22429
22853
|
function isBinaryContent(buf) {
|
|
22430
22854
|
const checkLen = Math.min(buf.length, 8192);
|
|
22431
22855
|
for (let i = 0; i < checkLen; i++) {
|
|
@@ -22448,7 +22872,7 @@ function isNoiseFile(relFile) {
|
|
|
22448
22872
|
return false;
|
|
22449
22873
|
}
|
|
22450
22874
|
async function scanMarkersForFile(relFile, text2) {
|
|
22451
|
-
const ext =
|
|
22875
|
+
const ext = path56.extname(relFile).toLowerCase();
|
|
22452
22876
|
if (getLanguageForExtension(ext) === null) {
|
|
22453
22877
|
return scanSuppressionMarkers(text2);
|
|
22454
22878
|
}
|
|
@@ -22470,11 +22894,11 @@ async function runSuppressionsScan(projectRoot, gitTrackedFiles, knownAspectIds)
|
|
|
22470
22894
|
const openDisables = /* @__PURE__ */ new Map();
|
|
22471
22895
|
for (const relFile of gitTrackedFiles) {
|
|
22472
22896
|
if (isNoiseFile(relFile)) continue;
|
|
22473
|
-
const absFile =
|
|
22474
|
-
if (!
|
|
22897
|
+
const absFile = path56.join(projectRoot, relFile);
|
|
22898
|
+
if (!existsSync10(absFile)) continue;
|
|
22475
22899
|
let buf;
|
|
22476
22900
|
try {
|
|
22477
|
-
buf =
|
|
22901
|
+
buf = readFileSync9(absFile);
|
|
22478
22902
|
} catch (error) {
|
|
22479
22903
|
debugWrite(`[suppressions] read fallback: ${relFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
22480
22904
|
continue;
|
|
@@ -22569,6 +22993,53 @@ function formatSuppressionsOutput(report2) {
|
|
|
22569
22993
|
}
|
|
22570
22994
|
return lines.join("\n") + "\n";
|
|
22571
22995
|
}
|
|
22996
|
+
function scanPortalSuppressions(report2, knownAspectIds, draftAspectIds) {
|
|
22997
|
+
const unboundedByFile = /* @__PURE__ */ new Map();
|
|
22998
|
+
for (const { file, markers } of report2.fileEntries) {
|
|
22999
|
+
const disableStack = /* @__PURE__ */ new Map();
|
|
23000
|
+
for (const m of markers) {
|
|
23001
|
+
if (m.kind === "disable") {
|
|
23002
|
+
const stack = disableStack.get(m.aspectId) ?? [];
|
|
23003
|
+
stack.push(m.line);
|
|
23004
|
+
disableStack.set(m.aspectId, stack);
|
|
23005
|
+
} else if (m.kind === "enable") {
|
|
23006
|
+
const stack = disableStack.get(m.aspectId);
|
|
23007
|
+
if (stack && stack.length > 0) {
|
|
23008
|
+
stack.pop();
|
|
23009
|
+
if (stack.length === 0) disableStack.delete(m.aspectId);
|
|
23010
|
+
}
|
|
23011
|
+
}
|
|
23012
|
+
}
|
|
23013
|
+
const open = /* @__PURE__ */ new Set();
|
|
23014
|
+
for (const lines of disableStack.values()) for (const l of lines) open.add(l);
|
|
23015
|
+
if (open.size > 0) unboundedByFile.set(file, open);
|
|
23016
|
+
}
|
|
23017
|
+
const out = [];
|
|
23018
|
+
for (const { file, markers } of report2.fileEntries) {
|
|
23019
|
+
const open = unboundedByFile.get(file);
|
|
23020
|
+
for (const m of markers) {
|
|
23021
|
+
if (m.kind === "enable") continue;
|
|
23022
|
+
const risk = resolveRisk(m, file, open, knownAspectIds, draftAspectIds);
|
|
23023
|
+
out.push({
|
|
23024
|
+
file,
|
|
23025
|
+
line: m.line,
|
|
23026
|
+
aspectId: m.aspectId,
|
|
23027
|
+
reason: m.reason,
|
|
23028
|
+
...risk ? { risk } : {}
|
|
23029
|
+
});
|
|
23030
|
+
}
|
|
23031
|
+
}
|
|
23032
|
+
return out;
|
|
23033
|
+
}
|
|
23034
|
+
function resolveRisk(m, file, openLines, knownAspectIds, draftAspectIds) {
|
|
23035
|
+
if (m.wildcard) return "wildcard";
|
|
23036
|
+
if (!knownAspectIds.has(m.aspectId)) return "typo";
|
|
23037
|
+
if (draftAspectIds.has(m.aspectId)) return "inert";
|
|
23038
|
+
if (m.kind === "disable" && openLines?.has(m.line)) return "unbounded";
|
|
23039
|
+
return void 0;
|
|
23040
|
+
}
|
|
23041
|
+
|
|
23042
|
+
// src/cli/suppressions.ts
|
|
22572
23043
|
function registerSuppressionsCommand(program2) {
|
|
22573
23044
|
program2.command("suppressions").description("Inventory active yg-suppress waivers and warn about footguns").action(async () => {
|
|
22574
23045
|
try {
|
|
@@ -22586,14 +23057,1130 @@ function registerSuppressionsCommand(program2) {
|
|
|
22586
23057
|
});
|
|
22587
23058
|
}
|
|
22588
23059
|
|
|
22589
|
-
// src/
|
|
22590
|
-
import {
|
|
23060
|
+
// src/cli/portal.ts
|
|
23061
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
23062
|
+
import path61 from "path";
|
|
23063
|
+
|
|
23064
|
+
// src/portal/engine-api.ts
|
|
23065
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
|
|
23066
|
+
import path58 from "path";
|
|
23067
|
+
|
|
23068
|
+
// src/portal/api/boundary.ts
|
|
23069
|
+
var STRUCTURAL_RELATIONS = ["calls", "uses", "extends", "implements"];
|
|
23070
|
+
function isAnyStructuralRelationAllowed(graph, sourceType, targetType) {
|
|
23071
|
+
const typeConfig = graph.architecture?.node_types?.[sourceType];
|
|
23072
|
+
if (!typeConfig) return true;
|
|
23073
|
+
const lists = typeConfig.relations;
|
|
23074
|
+
const def = typeConfig.relationDefault ?? "allow";
|
|
23075
|
+
for (const relType of STRUCTURAL_RELATIONS) {
|
|
23076
|
+
const allowed = lists?.[relType];
|
|
23077
|
+
if (allowed !== void 0) {
|
|
23078
|
+
if (allowed.includes("*") || allowed.includes(targetType)) return true;
|
|
23079
|
+
} else if (def === "allow") {
|
|
23080
|
+
return true;
|
|
23081
|
+
}
|
|
23082
|
+
}
|
|
23083
|
+
return false;
|
|
23084
|
+
}
|
|
23085
|
+
function isLineage(a, b) {
|
|
23086
|
+
return a === b || b.startsWith(a + "/") || a.startsWith(b + "/");
|
|
23087
|
+
}
|
|
23088
|
+
async function computePortalBoundary(graph, projectRoot) {
|
|
23089
|
+
let pass;
|
|
23090
|
+
try {
|
|
23091
|
+
pass = await runRelationPass(graph, projectRoot, {
|
|
23092
|
+
extractorFor: extractorForLanguage,
|
|
23093
|
+
resolvePathToFile: makeResolvePathToFile(projectRoot, buildOwnerIndex(graph.nodes).ownerOf),
|
|
23094
|
+
symbolIndexDir: astCacheDir(graph.rootPath)
|
|
23095
|
+
});
|
|
23096
|
+
} catch {
|
|
23097
|
+
return null;
|
|
23098
|
+
}
|
|
23099
|
+
const phantom = [];
|
|
23100
|
+
const declaredOnly = [];
|
|
23101
|
+
const forbiddenType = [];
|
|
23102
|
+
for (const [nodeId, nv] of pass.violationsByNode) {
|
|
23103
|
+
if (nv.verdict !== "refused") continue;
|
|
23104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23105
|
+
for (const v of nv.violations) {
|
|
23106
|
+
if (seen.has(v.ownerNode)) continue;
|
|
23107
|
+
seen.add(v.ownerNode);
|
|
23108
|
+
phantom.push({ source: nodeId, target: v.ownerNode });
|
|
23109
|
+
}
|
|
23110
|
+
}
|
|
23111
|
+
const detected = pass.detectedEdgesByNode;
|
|
23112
|
+
for (const [nodeId, node] of graph.nodes) {
|
|
23113
|
+
const sourceType = node.meta.type;
|
|
23114
|
+
const detectedTargets = detected.get(nodeId) ?? /* @__PURE__ */ new Set();
|
|
23115
|
+
for (const rel of node.meta.relations ?? []) {
|
|
23116
|
+
if (!STRUCTURAL_RELATIONS.includes(rel.type)) continue;
|
|
23117
|
+
if (isLineage(nodeId, rel.target)) continue;
|
|
23118
|
+
if (!graph.nodes.has(rel.target)) continue;
|
|
23119
|
+
if (!detectedTargets.has(rel.target)) {
|
|
23120
|
+
declaredOnly.push({ source: nodeId, target: rel.target });
|
|
23121
|
+
}
|
|
23122
|
+
}
|
|
23123
|
+
for (const target of detectedTargets) {
|
|
23124
|
+
const targetNode = graph.nodes.get(target);
|
|
23125
|
+
if (!targetNode) continue;
|
|
23126
|
+
if (!isAnyStructuralRelationAllowed(graph, sourceType, targetNode.meta.type)) {
|
|
23127
|
+
forbiddenType.push({ source: nodeId, target });
|
|
23128
|
+
}
|
|
23129
|
+
}
|
|
23130
|
+
}
|
|
23131
|
+
return { phantom, declaredOnly, forbiddenType };
|
|
23132
|
+
}
|
|
23133
|
+
|
|
23134
|
+
// src/portal/engine-api.ts
|
|
23135
|
+
var PORTAL_SCHEMA_SUPPORTED = CLI_SUPPORTED_SCHEMA;
|
|
23136
|
+
async function loadPortalGraph(projectRoot) {
|
|
23137
|
+
return loadGraphOrAbort(projectRoot, {
|
|
23138
|
+
tolerateInvalidConfig: true,
|
|
23139
|
+
noSecrets: true
|
|
23140
|
+
});
|
|
23141
|
+
}
|
|
23142
|
+
async function walkPortalFiles(projectRoot) {
|
|
23143
|
+
return walkRepoFiles(projectRoot);
|
|
23144
|
+
}
|
|
23145
|
+
async function runPortalCheck(graph, gitFiles) {
|
|
23146
|
+
return runCheck(graph, gitFiles);
|
|
23147
|
+
}
|
|
23148
|
+
function readAndVerifyLock(graph) {
|
|
23149
|
+
const lock = readLock(graph.rootPath);
|
|
23150
|
+
return { lock, verification: verifyLock(graph, lock) };
|
|
23151
|
+
}
|
|
23152
|
+
async function computePortalPairs(graph) {
|
|
23153
|
+
return computeExpectedPairs(graph);
|
|
23154
|
+
}
|
|
23155
|
+
function scanPortalUncovered(graph, gitFiles) {
|
|
23156
|
+
return scanUncoveredFiles(graph, gitFiles);
|
|
23157
|
+
}
|
|
23158
|
+
async function readNodeLog(projectRoot, nodePath) {
|
|
23159
|
+
return readLogContent(projectRoot, nodePath);
|
|
23160
|
+
}
|
|
23161
|
+
function groupPortalIssues(issues) {
|
|
23162
|
+
return groupIssues(issues);
|
|
23163
|
+
}
|
|
23164
|
+
async function computePortalBoundary2(graph, projectRoot) {
|
|
23165
|
+
return computePortalBoundary(graph, projectRoot);
|
|
23166
|
+
}
|
|
23167
|
+
async function scanPortalSuppressions2(graph, projectRoot, gitFiles) {
|
|
23168
|
+
const knownAspectIds = new Set(graph.aspects.map((a) => a.id));
|
|
23169
|
+
const draftAspectIds = new Set(
|
|
23170
|
+
graph.aspects.filter((a) => (a.status ?? "enforced") === "draft").map((a) => a.id)
|
|
23171
|
+
);
|
|
23172
|
+
const report2 = await runSuppressionsScan(projectRoot, gitFiles, knownAspectIds);
|
|
23173
|
+
return scanPortalSuppressions(report2, knownAspectIds, draftAspectIds);
|
|
23174
|
+
}
|
|
23175
|
+
function computePortalLockHash(graph) {
|
|
23176
|
+
return committedLockContentHash(graph.rootPath);
|
|
23177
|
+
}
|
|
23178
|
+
function readGitCommitRef(projectRoot) {
|
|
23179
|
+
const gitDir = path58.join(projectRoot, ".git");
|
|
23180
|
+
const headFile = path58.join(gitDir, "HEAD");
|
|
23181
|
+
if (!existsSync11(headFile)) return null;
|
|
23182
|
+
let head;
|
|
23183
|
+
try {
|
|
23184
|
+
head = readFileSync10(headFile, "utf-8").trim();
|
|
23185
|
+
} catch {
|
|
23186
|
+
return null;
|
|
23187
|
+
}
|
|
23188
|
+
if (/^[0-9a-f]{40}$/i.test(head)) return head;
|
|
23189
|
+
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
23190
|
+
if (!refMatch) return null;
|
|
23191
|
+
const refName = refMatch[1].trim();
|
|
23192
|
+
const looseRef = path58.join(gitDir, refName);
|
|
23193
|
+
if (existsSync11(looseRef)) {
|
|
23194
|
+
try {
|
|
23195
|
+
const sha = readFileSync10(looseRef, "utf-8").trim();
|
|
23196
|
+
if (/^[0-9a-f]{40}$/i.test(sha)) return sha;
|
|
23197
|
+
} catch {
|
|
23198
|
+
}
|
|
23199
|
+
}
|
|
23200
|
+
const packed = path58.join(gitDir, "packed-refs");
|
|
23201
|
+
if (existsSync11(packed)) {
|
|
23202
|
+
try {
|
|
23203
|
+
const lines = readFileSync10(packed, "utf-8").split("\n");
|
|
23204
|
+
for (const line of lines) {
|
|
23205
|
+
const m = line.match(/^([0-9a-f]{40})\s+(.+)$/i);
|
|
23206
|
+
if (m && m[2].trim() === refName) return m[1];
|
|
23207
|
+
}
|
|
23208
|
+
} catch {
|
|
23209
|
+
return null;
|
|
23210
|
+
}
|
|
23211
|
+
}
|
|
23212
|
+
return null;
|
|
23213
|
+
}
|
|
23214
|
+
async function computePortalFreshness(graph, lock) {
|
|
23215
|
+
const out = [];
|
|
23216
|
+
for (const nodePath of graph.nodes.keys()) {
|
|
23217
|
+
const stored = lock.nodes[nodePath]?.source;
|
|
23218
|
+
if (stored === void 0) {
|
|
23219
|
+
out.push({ nodePath, sourceChanged: false });
|
|
23220
|
+
continue;
|
|
23221
|
+
}
|
|
23222
|
+
let fingerprint;
|
|
23223
|
+
try {
|
|
23224
|
+
fingerprint = await computeSourceFingerprint(graph, nodePath);
|
|
23225
|
+
} catch {
|
|
23226
|
+
out.push({ nodePath, sourceChanged: true });
|
|
23227
|
+
continue;
|
|
23228
|
+
}
|
|
23229
|
+
if (fingerprint === void 0) {
|
|
23230
|
+
out.push({ nodePath, sourceChanged: false });
|
|
23231
|
+
continue;
|
|
23232
|
+
}
|
|
23233
|
+
out.push({ nodePath, sourceChanged: fingerprint !== stored });
|
|
23234
|
+
}
|
|
23235
|
+
return out;
|
|
23236
|
+
}
|
|
23237
|
+
|
|
23238
|
+
// src/portal/derive-node-state.ts
|
|
23239
|
+
var PAIR_RANK = {
|
|
23240
|
+
refused: 4,
|
|
23241
|
+
unverified: 3,
|
|
23242
|
+
warning: 2,
|
|
23243
|
+
verified: 1,
|
|
23244
|
+
"n/a": 0
|
|
23245
|
+
};
|
|
23246
|
+
function worstPairState(states) {
|
|
23247
|
+
return states.reduce((worst, s) => PAIR_RANK[s] > PAIR_RANK[worst] ? s : worst, "verified");
|
|
23248
|
+
}
|
|
23249
|
+
var STATE_RANK = {
|
|
23250
|
+
refused: 4,
|
|
23251
|
+
unverified: 3,
|
|
23252
|
+
warning: 2,
|
|
23253
|
+
verified: 1,
|
|
23254
|
+
"no-rule": 0
|
|
23255
|
+
};
|
|
23256
|
+
function computeOwnState(checked, effectiveAspects, nodeIssues, fresh) {
|
|
23257
|
+
let worst = checked ? "verified" : "no-rule";
|
|
23258
|
+
if (checked) {
|
|
23259
|
+
for (const ea of effectiveAspects) {
|
|
23260
|
+
if (ea.pairState === "refused" && STATE_RANK["refused"] > STATE_RANK[worst]) worst = "refused";
|
|
23261
|
+
else if (ea.pairState === "unverified" && STATE_RANK["unverified"] > STATE_RANK[worst]) worst = "unverified";
|
|
23262
|
+
else if (ea.pairState === "warning" && STATE_RANK["warning"] > STATE_RANK[worst]) worst = "warning";
|
|
23263
|
+
}
|
|
23264
|
+
const hasWarning = nodeIssues.some((i) => i.severity === "warning");
|
|
23265
|
+
if (hasWarning && STATE_RANK["warning"] > STATE_RANK[worst]) worst = "warning";
|
|
23266
|
+
}
|
|
23267
|
+
if (fresh && STATE_RANK["unverified"] > STATE_RANK[worst]) worst = "unverified";
|
|
23268
|
+
return worst;
|
|
23269
|
+
}
|
|
23270
|
+
function computeRollup(node, built) {
|
|
23271
|
+
let worst = built.get(node.path).state;
|
|
23272
|
+
for (const child of node.children) {
|
|
23273
|
+
const childRollup = built.get(child.path)?.rollupState ?? "no-rule";
|
|
23274
|
+
if (STATE_RANK[childRollup] > STATE_RANK[worst]) worst = childRollup;
|
|
23275
|
+
}
|
|
23276
|
+
return worst;
|
|
23277
|
+
}
|
|
23278
|
+
function collectNodeSuppressions(mapping, suppressions) {
|
|
23279
|
+
const out = [];
|
|
23280
|
+
const owned = new Set(mapping);
|
|
23281
|
+
for (const file of owned) {
|
|
23282
|
+
const hits = suppressions.byFile.get(file);
|
|
23283
|
+
if (hits) out.push(...hits);
|
|
23284
|
+
}
|
|
23285
|
+
return out;
|
|
23286
|
+
}
|
|
23287
|
+
function computeNotApplicable(node, graph, effectiveIds) {
|
|
23288
|
+
const attachedIds = /* @__PURE__ */ new Set();
|
|
23289
|
+
for (const id of node.meta.aspects ?? []) attachedIds.add(id);
|
|
23290
|
+
const typeDef = graph.architecture?.node_types?.[node.meta.type];
|
|
23291
|
+
for (const id of typeDef?.aspects ?? []) attachedIds.add(id);
|
|
23292
|
+
const out = [];
|
|
23293
|
+
for (const id of [...attachedIds].sort()) {
|
|
23294
|
+
if (effectiveIds.has(id)) continue;
|
|
23295
|
+
out.push({ aspectId: id, why: "filtered out by a `when` predicate on this node" });
|
|
23296
|
+
}
|
|
23297
|
+
return out;
|
|
23298
|
+
}
|
|
23299
|
+
|
|
23300
|
+
// src/portal/derive-nodes.ts
|
|
23301
|
+
function displayPairState(rawKind, status) {
|
|
23302
|
+
if (rawKind === "verified") return "verified";
|
|
23303
|
+
if (rawKind === "refused") return status === "advisory" ? "warning" : "refused";
|
|
23304
|
+
return "unverified";
|
|
23305
|
+
}
|
|
23306
|
+
function buildPortalNodes(graph, _lock, verification, check, logContents, suppressions, freshByNode = /* @__PURE__ */ new Map()) {
|
|
23307
|
+
const pairsByNode = indexPairsByNode(verification.pairs);
|
|
23308
|
+
const issuesByNode = indexIssuesByNode(check.issues);
|
|
23309
|
+
const relationsIn = invertRelations(graph);
|
|
23310
|
+
const built = /* @__PURE__ */ new Map();
|
|
23311
|
+
for (const [path62, node] of graph.nodes) {
|
|
23312
|
+
built.set(
|
|
23313
|
+
path62,
|
|
23314
|
+
buildOne(
|
|
23315
|
+
node,
|
|
23316
|
+
graph,
|
|
23317
|
+
pairsByNode,
|
|
23318
|
+
issuesByNode,
|
|
23319
|
+
relationsIn,
|
|
23320
|
+
logContents,
|
|
23321
|
+
suppressions,
|
|
23322
|
+
freshByNode.get(path62) === true
|
|
23323
|
+
)
|
|
23324
|
+
);
|
|
23325
|
+
}
|
|
23326
|
+
for (const [path62, node] of graph.nodes) {
|
|
23327
|
+
const portal = built.get(path62);
|
|
23328
|
+
portal.rollupState = computeRollup(node, built);
|
|
23329
|
+
}
|
|
23330
|
+
return [...built.values()];
|
|
23331
|
+
}
|
|
23332
|
+
function indexPairsByNode(pairs) {
|
|
23333
|
+
const out = /* @__PURE__ */ new Map();
|
|
23334
|
+
for (const vp of pairs) {
|
|
23335
|
+
const np = vp.pair.nodePath;
|
|
23336
|
+
let byAspect = out.get(np);
|
|
23337
|
+
if (!byAspect) {
|
|
23338
|
+
byAspect = /* @__PURE__ */ new Map();
|
|
23339
|
+
out.set(np, byAspect);
|
|
23340
|
+
}
|
|
23341
|
+
const list = byAspect.get(vp.pair.aspectId) ?? [];
|
|
23342
|
+
list.push(toPairRecord(vp));
|
|
23343
|
+
byAspect.set(vp.pair.aspectId, list);
|
|
23344
|
+
}
|
|
23345
|
+
return out;
|
|
23346
|
+
}
|
|
23347
|
+
function toPairRecord(vp) {
|
|
23348
|
+
const subjectFiles = vp.pair.subjectFiles ?? [];
|
|
23349
|
+
const state = displayPairState(vp.state.kind, vp.pair.status);
|
|
23350
|
+
if (vp.state.kind === "refused") {
|
|
23351
|
+
return { state, subjectFiles, reason: vp.state.reason };
|
|
23352
|
+
}
|
|
23353
|
+
return { state, subjectFiles };
|
|
23354
|
+
}
|
|
23355
|
+
function indexIssuesByNode(issues) {
|
|
23356
|
+
const out = /* @__PURE__ */ new Map();
|
|
23357
|
+
for (const i of issues) {
|
|
23358
|
+
if (!i.nodePath) continue;
|
|
23359
|
+
const list = out.get(i.nodePath) ?? [];
|
|
23360
|
+
list.push(i);
|
|
23361
|
+
out.set(i.nodePath, list);
|
|
23362
|
+
}
|
|
23363
|
+
return out;
|
|
23364
|
+
}
|
|
23365
|
+
function invertRelations(graph) {
|
|
23366
|
+
const out = /* @__PURE__ */ new Map();
|
|
23367
|
+
for (const [path62, node] of graph.nodes) {
|
|
23368
|
+
for (const rel of node.meta.relations ?? []) {
|
|
23369
|
+
const list = out.get(rel.target) ?? [];
|
|
23370
|
+
list.push({ source: path62, type: rel.type });
|
|
23371
|
+
out.set(rel.target, list);
|
|
23372
|
+
}
|
|
23373
|
+
}
|
|
23374
|
+
return out;
|
|
23375
|
+
}
|
|
23376
|
+
function buildOne(node, graph, pairsByNode, issuesByNode, relationsIn, logContents, suppressions, fresh) {
|
|
23377
|
+
const path62 = node.path;
|
|
23378
|
+
const mapping = node.meta.mapping ?? [];
|
|
23379
|
+
const effectiveIds = computeEffectiveAspects(node, graph);
|
|
23380
|
+
const statuses = computeEffectiveAspectStatuses(node, graph);
|
|
23381
|
+
const nodePairs = pairsByNode.get(path62);
|
|
23382
|
+
const effectiveAspects = [];
|
|
23383
|
+
for (const aspectId of [...effectiveIds].sort()) {
|
|
23384
|
+
effectiveAspects.push(
|
|
23385
|
+
buildEffectiveAspect(aspectId, node, graph, statuses, nodePairs)
|
|
23386
|
+
);
|
|
23387
|
+
}
|
|
23388
|
+
const checked = effectiveAspects.some((ea) => ea.pairState !== "n/a");
|
|
23389
|
+
const ownState = computeOwnState(
|
|
23390
|
+
checked,
|
|
23391
|
+
effectiveAspects,
|
|
23392
|
+
issuesByNode.get(path62) ?? [],
|
|
23393
|
+
fresh
|
|
23394
|
+
);
|
|
23395
|
+
const relationsOut = (node.meta.relations ?? []).map((r) => ({
|
|
23396
|
+
target: r.target,
|
|
23397
|
+
type: r.type,
|
|
23398
|
+
...r.consumes ? { consumes: r.consumes } : {}
|
|
23399
|
+
}));
|
|
23400
|
+
const log2 = parseLog(logContents.get(path62) ?? "").map((e) => ({
|
|
23401
|
+
when: e.datetime,
|
|
23402
|
+
body: e.body
|
|
23403
|
+
}));
|
|
23404
|
+
const nodeSuppressions = collectNodeSuppressions(mapping, suppressions);
|
|
23405
|
+
const notApplicable = computeNotApplicable(node, graph, effectiveIds);
|
|
23406
|
+
return {
|
|
23407
|
+
path: path62,
|
|
23408
|
+
name: node.meta.name,
|
|
23409
|
+
type: node.meta.type,
|
|
23410
|
+
...node.meta.description ? { description: node.meta.description } : {},
|
|
23411
|
+
parent: node.parent ? node.parent.path : null,
|
|
23412
|
+
mapping,
|
|
23413
|
+
sourceFileCount: mapping.length,
|
|
23414
|
+
isTest: node.meta.type === "test-suite",
|
|
23415
|
+
checked,
|
|
23416
|
+
fresh,
|
|
23417
|
+
state: ownState,
|
|
23418
|
+
// Provisional — overwritten by the bottom-up roll-up pass; seeded to own state.
|
|
23419
|
+
rollupState: ownState,
|
|
23420
|
+
effectiveAspects,
|
|
23421
|
+
notApplicable,
|
|
23422
|
+
relationsOut,
|
|
23423
|
+
relationsIn: relationsIn.get(path62) ?? [],
|
|
23424
|
+
suppressions: nodeSuppressions,
|
|
23425
|
+
log: log2
|
|
23426
|
+
};
|
|
23427
|
+
}
|
|
23428
|
+
function buildEffectiveAspect(aspectId, node, graph, statuses, nodePairs) {
|
|
23429
|
+
const def = graph.aspects.find((a) => a.id === aspectId);
|
|
23430
|
+
const aggregate = isAggregateAspect(graph, aspectId);
|
|
23431
|
+
const kind = aggregate ? "aggregate" : def?.reviewer.type === "deterministic" ? "deterministic" : "llm";
|
|
23432
|
+
const sources = getAspectStatusSources(node, aspectId, graph);
|
|
23433
|
+
const primary = sources.length > 0 ? sources.reduce((a, b) => a.channel <= b.channel ? a : b) : void 0;
|
|
23434
|
+
const channel = primary?.channel ?? 7;
|
|
23435
|
+
const origin = primary?.origin ?? impliedOrigin(graph, aspectId);
|
|
23436
|
+
const status = statuses.get(aspectId) ?? "enforced";
|
|
23437
|
+
let tier;
|
|
23438
|
+
let consensus;
|
|
23439
|
+
let cost = "free";
|
|
23440
|
+
if (kind === "llm" && def) {
|
|
23441
|
+
cost = "billed";
|
|
23442
|
+
const reviewer = graph.config?.reviewer;
|
|
23443
|
+
if (reviewer) {
|
|
23444
|
+
const sel = selectTierForAspect(def, reviewer);
|
|
23445
|
+
if (sel.ok) {
|
|
23446
|
+
tier = sel.tierName;
|
|
23447
|
+
consensus = sel.tier.consensus;
|
|
23448
|
+
}
|
|
23449
|
+
}
|
|
23450
|
+
}
|
|
23451
|
+
const records = nodePairs?.get(aspectId);
|
|
23452
|
+
const pairState = aggregate || status === "draft" || !records || records.length === 0 ? "n/a" : worstPairState(records.map((r) => r.state));
|
|
23453
|
+
let foldedInputs;
|
|
23454
|
+
let reason;
|
|
23455
|
+
if (!aggregate && status !== "draft" && records && records.length > 0) {
|
|
23456
|
+
if (pairState === "verified") {
|
|
23457
|
+
const files = /* @__PURE__ */ new Set();
|
|
23458
|
+
for (const r of records) for (const f of r.subjectFiles) files.add(f);
|
|
23459
|
+
const sorted = [...files].sort();
|
|
23460
|
+
if (sorted.length > 0) foldedInputs = sorted;
|
|
23461
|
+
} else if (pairState === "refused" || pairState === "warning") {
|
|
23462
|
+
reason = records.find((r) => r.reason)?.reason;
|
|
23463
|
+
}
|
|
23464
|
+
}
|
|
23465
|
+
return {
|
|
23466
|
+
aspectId,
|
|
23467
|
+
kind,
|
|
23468
|
+
...tier !== void 0 ? { tier } : {},
|
|
23469
|
+
...consensus !== void 0 ? { consensus } : {},
|
|
23470
|
+
cost,
|
|
23471
|
+
status,
|
|
23472
|
+
channel,
|
|
23473
|
+
origin,
|
|
23474
|
+
pairState,
|
|
23475
|
+
...reason !== void 0 ? { reason } : {},
|
|
23476
|
+
...foldedInputs !== void 0 ? { foldedInputs } : {}
|
|
23477
|
+
};
|
|
23478
|
+
}
|
|
23479
|
+
function impliedOrigin(graph, aspectId) {
|
|
23480
|
+
for (const other of graph.aspects) {
|
|
23481
|
+
if (other.implies?.includes(aspectId)) return `implied:${other.id}`;
|
|
23482
|
+
}
|
|
23483
|
+
return "unknown";
|
|
23484
|
+
}
|
|
23485
|
+
|
|
23486
|
+
// src/portal/derive-catalogue.ts
|
|
23487
|
+
function buildAspects(graph, pairs) {
|
|
23488
|
+
const byAspect = /* @__PURE__ */ new Map();
|
|
23489
|
+
for (const vp of pairs) {
|
|
23490
|
+
const list = byAspect.get(vp.pair.aspectId) ?? [];
|
|
23491
|
+
list.push(collapsePairState(vp));
|
|
23492
|
+
byAspect.set(vp.pair.aspectId, list);
|
|
23493
|
+
}
|
|
23494
|
+
return graph.aspects.map((def) => buildAspect(def, graph, byAspect.get(def.id) ?? [])).sort((a, b) => a.id.localeCompare(b.id, "en"));
|
|
23495
|
+
}
|
|
23496
|
+
function collapsePairState(vp) {
|
|
23497
|
+
return displayPairState(vp.state.kind, vp.pair.status);
|
|
23498
|
+
}
|
|
23499
|
+
function buildAspect(def, graph, states) {
|
|
23500
|
+
const kind = def.reviewer.type;
|
|
23501
|
+
const status = def.status ?? "enforced";
|
|
23502
|
+
const scope = def.scope?.per === "file" ? "file" : "node";
|
|
23503
|
+
const ruleProse = def.artifacts.find((a) => a.filename === "content.md")?.content;
|
|
23504
|
+
const checkSource = def.artifacts.find((a) => a.filename === "check.mjs")?.content;
|
|
23505
|
+
return {
|
|
23506
|
+
id: def.id,
|
|
23507
|
+
name: def.name,
|
|
23508
|
+
kind,
|
|
23509
|
+
status,
|
|
23510
|
+
scope,
|
|
23511
|
+
hasWhen: def.when !== void 0,
|
|
23512
|
+
implies: def.implies ?? [],
|
|
23513
|
+
// Optional content fields assigned directly — an undefined value is omitted by the
|
|
23514
|
+
// JSON.stringify embed, so no conditional spread is needed (and none is left half-covered).
|
|
23515
|
+
description: def.description,
|
|
23516
|
+
ruleProse,
|
|
23517
|
+
checkSource,
|
|
23518
|
+
tally: buildTally(def, graph, status, states)
|
|
23519
|
+
};
|
|
23520
|
+
}
|
|
23521
|
+
function buildTally(def, graph, status, states) {
|
|
23522
|
+
if (def.reviewer.type === "aggregate") {
|
|
23523
|
+
return { render: "aggregate" };
|
|
23524
|
+
}
|
|
23525
|
+
if (states.length === 0) {
|
|
23526
|
+
return { render: "vacuous", reason: vacuousReason(def, graph, status) };
|
|
23527
|
+
}
|
|
23528
|
+
let verified = 0;
|
|
23529
|
+
let refused = 0;
|
|
23530
|
+
let warning = 0;
|
|
23531
|
+
let unverified = 0;
|
|
23532
|
+
for (const s of states) {
|
|
23533
|
+
if (s === "verified") verified += 1;
|
|
23534
|
+
else if (s === "refused") refused += 1;
|
|
23535
|
+
else if (s === "warning") warning += 1;
|
|
23536
|
+
else unverified += 1;
|
|
23537
|
+
}
|
|
23538
|
+
return { render: "normal", verified, refused, warning, unverified, units: states.length };
|
|
23539
|
+
}
|
|
23540
|
+
function vacuousReason(def, graph, status) {
|
|
23541
|
+
if ((status ?? "enforced") === "draft") {
|
|
23542
|
+
return "draft \u2014 produces no expected pairs (parked, not yet enforced)";
|
|
23543
|
+
}
|
|
23544
|
+
const reachesANode = [...graph.nodes.values()].some(
|
|
23545
|
+
(n) => computeEffectiveAspects(n, graph).has(def.id)
|
|
23546
|
+
);
|
|
23547
|
+
if (!reachesANode) {
|
|
23548
|
+
return "not effective on any node \u2014 no attach channel reaches a node";
|
|
23549
|
+
}
|
|
23550
|
+
return "effective on a node but every subject set is empty (scope/when excludes all files, or no mapped source)";
|
|
23551
|
+
}
|
|
23552
|
+
function buildFlows(graph, nodeStateOf) {
|
|
23553
|
+
return graph.flows.map((flow) => buildFlow(flow, graph, nodeStateOf)).sort((a, b) => a.name.localeCompare(b.name, "en"));
|
|
23554
|
+
}
|
|
23555
|
+
function buildFlow(flow, graph, nodeStateOf) {
|
|
23556
|
+
const participants = expandParticipants(flow, graph);
|
|
23557
|
+
const state = computeFlowState(participants, graph, nodeStateOf);
|
|
23558
|
+
return {
|
|
23559
|
+
name: flow.name,
|
|
23560
|
+
...flow.description ? { description: flow.description } : {},
|
|
23561
|
+
participants,
|
|
23562
|
+
aspects: flow.aspects ?? [],
|
|
23563
|
+
state
|
|
23564
|
+
};
|
|
23565
|
+
}
|
|
23566
|
+
function expandParticipants(flow, graph) {
|
|
23567
|
+
const out = /* @__PURE__ */ new Set();
|
|
23568
|
+
for (const path62 of flow.nodes) {
|
|
23569
|
+
out.add(path62);
|
|
23570
|
+
const node = graph.nodes.get(path62);
|
|
23571
|
+
if (node) {
|
|
23572
|
+
for (const d of collectDescendants(node)) out.add(d.path);
|
|
23573
|
+
}
|
|
23574
|
+
}
|
|
23575
|
+
return [...out].sort((a, b) => a.localeCompare(b, "en"));
|
|
23576
|
+
}
|
|
23577
|
+
function computeFlowState(participants, graph, nodeStateOf) {
|
|
23578
|
+
let anyChecked = false;
|
|
23579
|
+
let anyAttention = false;
|
|
23580
|
+
for (const path62 of participants) {
|
|
23581
|
+
const node = graph.nodes.get(path62);
|
|
23582
|
+
if (!node) continue;
|
|
23583
|
+
if (!hasNonDraftEffectiveAspects(node, graph)) continue;
|
|
23584
|
+
anyChecked = true;
|
|
23585
|
+
const states = nodeStateOf(path62) ?? [];
|
|
23586
|
+
if (states.some((s) => s === "refused" || s === "unverified")) anyAttention = true;
|
|
23587
|
+
}
|
|
23588
|
+
if (!anyChecked) return "nothing-checked";
|
|
23589
|
+
return anyAttention ? "attention" : "verified";
|
|
23590
|
+
}
|
|
23591
|
+
function buildTypes(graph) {
|
|
23592
|
+
const countByType = /* @__PURE__ */ new Map();
|
|
23593
|
+
for (const node of graph.nodes.values()) {
|
|
23594
|
+
countByType.set(node.meta.type, (countByType.get(node.meta.type) ?? 0) + 1);
|
|
23595
|
+
}
|
|
23596
|
+
const types = [];
|
|
23597
|
+
for (const [id, def] of Object.entries(graph.architecture?.node_types ?? {})) {
|
|
23598
|
+
const allowedRelations = {};
|
|
23599
|
+
for (const [relType, targets] of Object.entries(def.relations ?? {})) {
|
|
23600
|
+
allowedRelations[relType] = [...targets ?? []];
|
|
23601
|
+
}
|
|
23602
|
+
types.push({
|
|
23603
|
+
id,
|
|
23604
|
+
...def.description ? { description: def.description } : {},
|
|
23605
|
+
parents: def.parents ?? [],
|
|
23606
|
+
allowedRelations,
|
|
23607
|
+
defaultAspects: def.aspects ?? [],
|
|
23608
|
+
strict: def.enforce === "strict",
|
|
23609
|
+
logRequired: def.log_required === true,
|
|
23610
|
+
nodeCount: countByType.get(id) ?? 0
|
|
23611
|
+
});
|
|
23612
|
+
}
|
|
23613
|
+
return types.sort((a, b) => a.id.localeCompare(b.id, "en"));
|
|
23614
|
+
}
|
|
23615
|
+
|
|
23616
|
+
// src/portal/derive-rest.ts
|
|
23617
|
+
function buildBoundary(input) {
|
|
23618
|
+
if (input === null) {
|
|
23619
|
+
return { phantom: [], declaredOnly: [], forbiddenType: [], unknown: true };
|
|
23620
|
+
}
|
|
23621
|
+
return {
|
|
23622
|
+
phantom: dedupeEdges(input.phantom),
|
|
23623
|
+
declaredOnly: dedupeEdges(input.declaredOnly),
|
|
23624
|
+
forbiddenType: dedupeEdges(input.forbiddenType),
|
|
23625
|
+
unknown: false
|
|
23626
|
+
};
|
|
23627
|
+
}
|
|
23628
|
+
function dedupeEdges(edges) {
|
|
23629
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23630
|
+
const out = [];
|
|
23631
|
+
for (const e of edges) {
|
|
23632
|
+
const key = `${e.source}\0${e.target}`;
|
|
23633
|
+
if (seen.has(key)) continue;
|
|
23634
|
+
seen.add(key);
|
|
23635
|
+
out.push(e);
|
|
23636
|
+
}
|
|
23637
|
+
return out.sort((a, b) => a.source.localeCompare(b.source, "en") || a.target.localeCompare(b.target, "en"));
|
|
23638
|
+
}
|
|
23639
|
+
function buildSuppressions(markers) {
|
|
23640
|
+
return markers.map((m) => ({
|
|
23641
|
+
aspectId: m.aspectId,
|
|
23642
|
+
file: m.file,
|
|
23643
|
+
line: m.line,
|
|
23644
|
+
reason: m.reason,
|
|
23645
|
+
...m.risk ? { risk: m.risk } : {}
|
|
23646
|
+
})).sort((a, b) => a.file.localeCompare(b.file, "en") || a.line - b.line);
|
|
23647
|
+
}
|
|
23648
|
+
function buildHubs(nodes) {
|
|
23649
|
+
const fanOut = [];
|
|
23650
|
+
const fanIn = [];
|
|
23651
|
+
for (const n of nodes) {
|
|
23652
|
+
if (n.relationsOut.length > 0) fanOut.push({ path: n.path, count: n.relationsOut.length });
|
|
23653
|
+
if (n.relationsIn.length > 0) fanIn.push({ path: n.path, count: n.relationsIn.length });
|
|
23654
|
+
}
|
|
23655
|
+
return { fanOut: rankHubs(fanOut), fanIn: rankHubs(fanIn) };
|
|
23656
|
+
}
|
|
23657
|
+
function rankHubs(entries) {
|
|
23658
|
+
return entries.sort((a, b) => b.count - a.count || a.path.localeCompare(b.path, "en"));
|
|
23659
|
+
}
|
|
23660
|
+
function buildResidue(nodes, uncoveredFiles) {
|
|
23661
|
+
const noRuleNodes = nodes.filter((n) => n.state === "no-rule" && n.mapping.length > 0).map((n) => n.path).sort((a, b) => a.localeCompare(b, "en"));
|
|
23662
|
+
return {
|
|
23663
|
+
noRuleNodes,
|
|
23664
|
+
uncoveredFiles: [...uncoveredFiles].sort((a, b) => a.localeCompare(b, "en"))
|
|
23665
|
+
};
|
|
23666
|
+
}
|
|
23667
|
+
function buildWorklist(check) {
|
|
23668
|
+
const groups = groupPortalIssues(check.issues);
|
|
23669
|
+
return groups.map((g) => ({
|
|
23670
|
+
rule: g.label,
|
|
23671
|
+
severity: g.severity,
|
|
23672
|
+
why: g.sharedWhy,
|
|
23673
|
+
fix: g.sharedNext,
|
|
23674
|
+
nodes: [...new Set(g.members.map((m) => m.nodePath ?? "").filter((p2) => p2.length > 0))].sort(
|
|
23675
|
+
(a, b) => a.localeCompare(b, "en")
|
|
23676
|
+
)
|
|
23677
|
+
}));
|
|
23678
|
+
}
|
|
23679
|
+
|
|
23680
|
+
// src/portal/extract.ts
|
|
23681
|
+
async function extractPortalData(projectRoot, opts) {
|
|
23682
|
+
const graph = await loadPortalGraph(projectRoot);
|
|
23683
|
+
const gitFiles = await walkPortalFiles(projectRoot);
|
|
23684
|
+
const checkResult = await runPortalCheck(graph, gitFiles);
|
|
23685
|
+
const { lock, verification: verificationPromise } = readAndVerifyLock(graph);
|
|
23686
|
+
const verification = await verificationPromise;
|
|
23687
|
+
const expected = await computePortalPairs(graph);
|
|
23688
|
+
const counts = buildCounts(graph, checkResult, verification.pairs, expected.pairs);
|
|
23689
|
+
const logContents = /* @__PURE__ */ new Map();
|
|
23690
|
+
for (const nodePath of graph.nodes.keys()) {
|
|
23691
|
+
logContents.set(nodePath, await readNodeLog(projectRoot, nodePath));
|
|
23692
|
+
}
|
|
23693
|
+
const suppressionMarkers = await scanPortalSuppressions2(graph, projectRoot, gitFiles);
|
|
23694
|
+
const flatSuppressions = buildSuppressions(suppressionMarkers);
|
|
23695
|
+
const suppressions = indexSuppressionsByFile(flatSuppressions);
|
|
23696
|
+
const freshnessMarkers = await computePortalFreshness(graph, lock);
|
|
23697
|
+
const freshByNode = /* @__PURE__ */ new Map();
|
|
23698
|
+
for (const m of freshnessMarkers) freshByNode.set(m.nodePath, m.sourceChanged);
|
|
23699
|
+
const nodes = buildPortalNodes(graph, lock, verification, checkResult, logContents, suppressions, freshByNode);
|
|
23700
|
+
const pairStatesByNode = /* @__PURE__ */ new Map();
|
|
23701
|
+
for (const vp of verification.pairs) {
|
|
23702
|
+
const list = pairStatesByNode.get(vp.pair.nodePath) ?? [];
|
|
23703
|
+
list.push(collapsePairState2(vp));
|
|
23704
|
+
pairStatesByNode.set(vp.pair.nodePath, list);
|
|
23705
|
+
}
|
|
23706
|
+
const aspects = buildAspects(graph, verification.pairs);
|
|
23707
|
+
const flows = buildFlows(graph, (path62) => pairStatesByNode.get(path62));
|
|
23708
|
+
const types = buildTypes(graph);
|
|
23709
|
+
const hubs = buildHubs(nodes);
|
|
23710
|
+
const uncovered = scanPortalUncovered(graph, gitFiles);
|
|
23711
|
+
const residue = buildResidue(nodes, uncovered);
|
|
23712
|
+
const worklist = buildWorklist(checkResult);
|
|
23713
|
+
counts.suppressed = flatSuppressions.length;
|
|
23714
|
+
counts.noRule = residue.noRuleNodes.length;
|
|
23715
|
+
counts.notApplicable = nodes.reduce((sum, n) => sum + n.notApplicable.length, 0);
|
|
23716
|
+
const boundary = buildBoundary(await computePortalBoundary2(graph, projectRoot));
|
|
23717
|
+
const lockHash = computePortalLockHash(graph);
|
|
23718
|
+
const commitRef = readGitCommitRef(projectRoot);
|
|
23719
|
+
const data = {
|
|
23720
|
+
meta: {
|
|
23721
|
+
projectName: checkResult.projectName,
|
|
23722
|
+
generatedAt: "",
|
|
23723
|
+
// stamped below, after generation
|
|
23724
|
+
autoApprove: normalizeAutoApprove(graph.config.auto_approve),
|
|
23725
|
+
writeEnabled: opts.writeEnabled,
|
|
23726
|
+
schemaSupported: PORTAL_SCHEMA_SUPPORTED,
|
|
23727
|
+
lockHash,
|
|
23728
|
+
commitRef,
|
|
23729
|
+
counts
|
|
23730
|
+
},
|
|
23731
|
+
nodes,
|
|
23732
|
+
aspects,
|
|
23733
|
+
flows,
|
|
23734
|
+
types,
|
|
23735
|
+
boundary,
|
|
23736
|
+
suppressions: flatSuppressions,
|
|
23737
|
+
hubs,
|
|
23738
|
+
worklist,
|
|
23739
|
+
residue
|
|
23740
|
+
};
|
|
23741
|
+
data.meta.generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
23742
|
+
return data;
|
|
23743
|
+
}
|
|
23744
|
+
function normalizeAutoApprove(value) {
|
|
23745
|
+
if (value === "deterministic" || value === "full") return value;
|
|
23746
|
+
return "false";
|
|
23747
|
+
}
|
|
23748
|
+
function collapsePairState2(vp) {
|
|
23749
|
+
return displayPairState(vp.state.kind, vp.pair.status);
|
|
23750
|
+
}
|
|
23751
|
+
function indexSuppressionsByFile(flat) {
|
|
23752
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
23753
|
+
for (const s of flat) {
|
|
23754
|
+
const list = byFile.get(s.file) ?? [];
|
|
23755
|
+
list.push(s);
|
|
23756
|
+
byFile.set(s.file, list);
|
|
23757
|
+
}
|
|
23758
|
+
return { byFile };
|
|
23759
|
+
}
|
|
23760
|
+
function buildCounts(graph, check, pairs, expectedPairs) {
|
|
23761
|
+
let verified = 0;
|
|
23762
|
+
let refused = 0;
|
|
23763
|
+
let unverified = 0;
|
|
23764
|
+
let advisoryRefused = 0;
|
|
23765
|
+
for (const vp of pairs) {
|
|
23766
|
+
switch (displayPairState(vp.state.kind, vp.pair.status)) {
|
|
23767
|
+
case "verified":
|
|
23768
|
+
verified += 1;
|
|
23769
|
+
break;
|
|
23770
|
+
case "refused":
|
|
23771
|
+
refused += 1;
|
|
23772
|
+
break;
|
|
23773
|
+
case "warning":
|
|
23774
|
+
advisoryRefused += 1;
|
|
23775
|
+
break;
|
|
23776
|
+
default:
|
|
23777
|
+
unverified += 1;
|
|
23778
|
+
break;
|
|
23779
|
+
}
|
|
23780
|
+
}
|
|
23781
|
+
let pairsLLM = 0;
|
|
23782
|
+
let pairsDet = 0;
|
|
23783
|
+
for (const p2 of expectedPairs) {
|
|
23784
|
+
if (p2.kind === "llm") pairsLLM += 1;
|
|
23785
|
+
else pairsDet += 1;
|
|
23786
|
+
}
|
|
23787
|
+
const errors = check.issues.filter((i) => i.severity === "error").length;
|
|
23788
|
+
const warnings = check.issues.filter((i) => i.severity === "warning").length;
|
|
23789
|
+
return {
|
|
23790
|
+
nodes: graph.nodes.size,
|
|
23791
|
+
aspects: graph.aspects.length,
|
|
23792
|
+
flows: graph.flows.length,
|
|
23793
|
+
pairsTotal: expectedPairs.length,
|
|
23794
|
+
pairsLLM,
|
|
23795
|
+
pairsDet,
|
|
23796
|
+
verified,
|
|
23797
|
+
refused,
|
|
23798
|
+
unverified,
|
|
23799
|
+
advisoryRefused,
|
|
23800
|
+
// The residue-track counts (noRule / notApplicable / suppressed) are NOT part of the
|
|
23801
|
+
// count-parity identity and cannot be computed here — each depends on data derived AFTER
|
|
23802
|
+
// this seam (the built node array, the residue ledger, the suppression inventory). They
|
|
23803
|
+
// are seeded 0 and OVERWRITTEN by the post-pass in extractPortalData once that data exists.
|
|
23804
|
+
// (Never leave them 0: that prints "0 waived / 0 no rule / 0 not applicable" over a list.)
|
|
23805
|
+
noRule: 0,
|
|
23806
|
+
draft: check.draftSkipped,
|
|
23807
|
+
notApplicable: 0,
|
|
23808
|
+
suppressed: 0,
|
|
23809
|
+
uncoveredFiles: check.totalFiles - check.coveredFiles,
|
|
23810
|
+
coveredFiles: check.coveredFiles,
|
|
23811
|
+
totalFiles: check.totalFiles,
|
|
23812
|
+
errors,
|
|
23813
|
+
warnings
|
|
23814
|
+
};
|
|
23815
|
+
}
|
|
23816
|
+
|
|
23817
|
+
// src/portal/serializer.ts
|
|
23818
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
23819
|
+
import path59 from "path";
|
|
22591
23820
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
23821
|
+
import { existsSync as existsSync12 } from "fs";
|
|
23822
|
+
var LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400">
|
|
23823
|
+
<defs>
|
|
23824
|
+
<style>
|
|
23825
|
+
.st0 {
|
|
23826
|
+
fill: #f8d794;
|
|
23827
|
+
fill-rule: evenodd;
|
|
23828
|
+
}
|
|
23829
|
+
</style>
|
|
23830
|
+
</defs>
|
|
23831
|
+
<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"/>
|
|
23832
|
+
</svg>`;
|
|
23833
|
+
var LOGO_DATA_URI = `data:image/svg+xml;base64,${Buffer.from(LOGO_SVG, "utf8").toString("base64")}`;
|
|
23834
|
+
var __dirname3 = path59.dirname(fileURLToPath4(import.meta.url));
|
|
23835
|
+
function portalTemplateDir() {
|
|
23836
|
+
const distDir = path59.resolve(__dirname3, "templates", "portal");
|
|
23837
|
+
if (existsSync12(distDir)) return distDir;
|
|
23838
|
+
const srcDir = path59.resolve(__dirname3, "..", "templates", "portal");
|
|
23839
|
+
if (existsSync12(srcDir)) return srcDir;
|
|
23840
|
+
return path59.resolve(__dirname3, "..", "dist", "templates", "portal");
|
|
23841
|
+
}
|
|
23842
|
+
var MODULE_ORDER = [
|
|
23843
|
+
"js/namespace.js",
|
|
23844
|
+
"js/state-model.js",
|
|
23845
|
+
"js/glossary.js",
|
|
23846
|
+
"js/router.js",
|
|
23847
|
+
"js/palette.js",
|
|
23848
|
+
"js/palette-overlay.js",
|
|
23849
|
+
"js/consumer.js",
|
|
23850
|
+
"js/export.js",
|
|
23851
|
+
"js/tree.js",
|
|
23852
|
+
"js/shell.js",
|
|
23853
|
+
"js/dispatch.js",
|
|
23854
|
+
// Per-surface view modules — registered onto Yg.views, consumed by the dispatcher. The
|
|
23855
|
+
// matrix module precedes the relations view that calls Yg.matrix; the panel module is read
|
|
23856
|
+
// by the bootstrap's panel slot. All before the bootstrap, which orchestrates the rest.
|
|
23857
|
+
"js/views/overview-view.js",
|
|
23858
|
+
"js/views/coverage-view.js",
|
|
23859
|
+
"js/views/tree-view.js",
|
|
23860
|
+
"js/views/relations-matrix.js",
|
|
23861
|
+
"js/views/relations-view.js",
|
|
23862
|
+
"js/views/rulebook-view.js",
|
|
23863
|
+
"js/views/types-view.js",
|
|
23864
|
+
"js/views/flows-view.js",
|
|
23865
|
+
"js/views/suppressions-view.js",
|
|
23866
|
+
"js/views/start-view.js",
|
|
23867
|
+
"js/views/panel-aspect.js",
|
|
23868
|
+
"js/views/panel-view.js",
|
|
23869
|
+
"js/bootstrap.js"
|
|
23870
|
+
];
|
|
23871
|
+
var CSS_ORDER = [
|
|
23872
|
+
"tokens.css",
|
|
23873
|
+
"shell.css",
|
|
23874
|
+
"app.css",
|
|
23875
|
+
"views.css",
|
|
23876
|
+
"views-audit.css",
|
|
23877
|
+
"views-panel.css",
|
|
23878
|
+
"views-relations.css",
|
|
23879
|
+
"views-rulebook.css",
|
|
23880
|
+
"views-flows.css",
|
|
23881
|
+
"views-start.css"
|
|
23882
|
+
];
|
|
23883
|
+
async function readAssets() {
|
|
23884
|
+
const dir = portalTemplateDir();
|
|
23885
|
+
const read = (rel) => readFile20(path59.join(dir, rel), "utf-8");
|
|
23886
|
+
const [shell, vendor] = await Promise.all([read("shell.html"), read("vendor/d3-hierarchy.js")]);
|
|
23887
|
+
const cssSources = await Promise.all(CSS_ORDER.map((rel) => read(rel)));
|
|
23888
|
+
const css = cssSources.join("\n");
|
|
23889
|
+
const moduleSources = await Promise.all(MODULE_ORDER.map((rel) => read(rel)));
|
|
23890
|
+
const modules = moduleSources.join("\n");
|
|
23891
|
+
return { shell, css, vendor, modules };
|
|
23892
|
+
}
|
|
23893
|
+
function renderStaticHtml(data, assets) {
|
|
23894
|
+
const json = safeJsonForScript(data);
|
|
23895
|
+
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);
|
|
23896
|
+
}
|
|
23897
|
+
var LINE_SEPARATORS = new RegExp(`[${String.fromCharCode(8232)}${String.fromCharCode(8233)}]`, "g");
|
|
23898
|
+
function safeJsonForScript(data) {
|
|
23899
|
+
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");
|
|
23900
|
+
}
|
|
23901
|
+
async function emitStatic(data, outPath) {
|
|
23902
|
+
const assets = await readAssets();
|
|
23903
|
+
const html = renderStaticHtml(data, assets);
|
|
23904
|
+
await atomicWriteFile(outPath, html);
|
|
23905
|
+
}
|
|
23906
|
+
async function renderPortalPage(data) {
|
|
23907
|
+
const assets = await readAssets();
|
|
23908
|
+
return renderStaticHtml(data, assets);
|
|
23909
|
+
}
|
|
23910
|
+
function contentTypeFor(relPath) {
|
|
23911
|
+
if (relPath.endsWith(".html")) return "text/html; charset=utf-8";
|
|
23912
|
+
if (relPath.endsWith(".css")) return "text/css; charset=utf-8";
|
|
23913
|
+
if (relPath.endsWith(".js")) return "text/javascript; charset=utf-8";
|
|
23914
|
+
if (relPath.endsWith(".json")) return "application/json; charset=utf-8";
|
|
23915
|
+
if (relPath.endsWith(".svg")) return "image/svg+xml";
|
|
23916
|
+
return "application/octet-stream";
|
|
23917
|
+
}
|
|
23918
|
+
async function readPortalAsset(relPath) {
|
|
23919
|
+
const root = portalTemplateDir();
|
|
23920
|
+
const cleaned = relPath.replace(/^\/+/, "");
|
|
23921
|
+
if (cleaned.length === 0) return null;
|
|
23922
|
+
const resolved = path59.resolve(root, cleaned);
|
|
23923
|
+
const rel = path59.relative(root, resolved);
|
|
23924
|
+
if (rel.startsWith("..") || path59.isAbsolute(rel)) return null;
|
|
23925
|
+
if (!existsSync12(resolved)) return null;
|
|
23926
|
+
const content20 = await readFile20(resolved);
|
|
23927
|
+
return { content: content20, contentType: contentTypeFor(cleaned) };
|
|
23928
|
+
}
|
|
23929
|
+
|
|
23930
|
+
// src/portal/server/server.ts
|
|
23931
|
+
import { createServer } from "http";
|
|
23932
|
+
|
|
23933
|
+
// src/portal/server/page.ts
|
|
23934
|
+
async function freshPortalData(projectRoot, writeEnabled) {
|
|
23935
|
+
return extractPortalData(projectRoot, { writeEnabled });
|
|
23936
|
+
}
|
|
23937
|
+
async function renderLivePage(data) {
|
|
23938
|
+
return renderPortalPage(data);
|
|
23939
|
+
}
|
|
23940
|
+
async function readStaticAsset(relPath) {
|
|
23941
|
+
return readPortalAsset(relPath);
|
|
23942
|
+
}
|
|
23943
|
+
|
|
23944
|
+
// src/portal/server/approve.ts
|
|
23945
|
+
import { spawn as spawn2 } from "child_process";
|
|
23946
|
+
import path60 from "path";
|
|
23947
|
+
import { existsSync as existsSync13 } from "fs";
|
|
23948
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
23949
|
+
function resolveCliBin() {
|
|
23950
|
+
const here = fileURLToPath5(import.meta.url);
|
|
23951
|
+
if (path60.basename(here) === "bin.js") return here;
|
|
23952
|
+
let dir = path60.dirname(here);
|
|
23953
|
+
for (let i = 0; i < 6; i++) {
|
|
23954
|
+
const candidate = path60.join(dir, "dist", "bin.js");
|
|
23955
|
+
if (existsSync13(candidate)) return candidate;
|
|
23956
|
+
dir = path60.dirname(dir);
|
|
23957
|
+
}
|
|
23958
|
+
return process.argv[1];
|
|
23959
|
+
}
|
|
23960
|
+
var CLI_BIN = resolveCliBin();
|
|
23961
|
+
function approveArgs(llm) {
|
|
23962
|
+
return llm ? ["check", "--approve"] : ["check", "--approve", "--only-deterministic"];
|
|
23963
|
+
}
|
|
23964
|
+
function dryRunArgs(llm) {
|
|
23965
|
+
return llm ? ["check", "--approve", "--dry-run"] : ["check", "--approve", "--only-deterministic", "--dry-run"];
|
|
23966
|
+
}
|
|
23967
|
+
function spawnCli(args, cwd) {
|
|
23968
|
+
return new Promise((resolve6, reject) => {
|
|
23969
|
+
const child = spawn2(process.execPath, [CLI_BIN, ...args], { cwd });
|
|
23970
|
+
let stdout = "";
|
|
23971
|
+
let stderr = "";
|
|
23972
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString("utf-8"));
|
|
23973
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString("utf-8"));
|
|
23974
|
+
child.on("error", (err) => reject(err));
|
|
23975
|
+
child.on("close", (code) => resolve6({ exitCode: code ?? 1, stdout, stderr }));
|
|
23976
|
+
});
|
|
23977
|
+
}
|
|
23978
|
+
async function runApproveViaCli(projectRoot, llm) {
|
|
23979
|
+
return spawnCli(approveArgs(llm), projectRoot);
|
|
23980
|
+
}
|
|
23981
|
+
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/;
|
|
23982
|
+
function parseDryRunBudget(output) {
|
|
23983
|
+
const m = output.match(BUDGET_RE);
|
|
23984
|
+
if (!m) {
|
|
23985
|
+
throw new Error(
|
|
23986
|
+
`Could not parse the dry-run cost preview from the CLI output. Raw output:
|
|
23987
|
+
${output.trim()}`
|
|
23988
|
+
);
|
|
23989
|
+
}
|
|
23990
|
+
return {
|
|
23991
|
+
pairs: Number.parseInt(m[1], 10),
|
|
23992
|
+
deterministic: Number.parseInt(m[2], 10),
|
|
23993
|
+
reviewerCalls: Number.parseInt(m[3], 10),
|
|
23994
|
+
raw: m[0]
|
|
23995
|
+
};
|
|
23996
|
+
}
|
|
23997
|
+
async function dryRunApproveViaCli(projectRoot, llm) {
|
|
23998
|
+
const result = await spawnCli(dryRunArgs(llm), projectRoot);
|
|
23999
|
+
return parseDryRunBudget(`${result.stdout}
|
|
24000
|
+
${result.stderr}`);
|
|
24001
|
+
}
|
|
24002
|
+
|
|
24003
|
+
// src/portal/server/router.ts
|
|
24004
|
+
function sendJson(res, status, body) {
|
|
24005
|
+
const payload = JSON.stringify(body);
|
|
24006
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
24007
|
+
res.end(payload);
|
|
24008
|
+
}
|
|
24009
|
+
function sendText(res, status, contentType, body) {
|
|
24010
|
+
res.writeHead(status, { "content-type": contentType });
|
|
24011
|
+
res.end(body);
|
|
24012
|
+
}
|
|
24013
|
+
async function readJsonBody(req) {
|
|
24014
|
+
const chunks = [];
|
|
24015
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
24016
|
+
const text2 = Buffer.concat(chunks).toString("utf-8").trim();
|
|
24017
|
+
if (text2.length === 0) return {};
|
|
24018
|
+
try {
|
|
24019
|
+
const parsed = JSON.parse(text2);
|
|
24020
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
24021
|
+
} catch {
|
|
24022
|
+
return {};
|
|
24023
|
+
}
|
|
24024
|
+
}
|
|
24025
|
+
async function handleRequest(req, res, config) {
|
|
24026
|
+
const method = req.method ?? "GET";
|
|
24027
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
24028
|
+
const pathname = url.pathname;
|
|
24029
|
+
try {
|
|
24030
|
+
if (method === "GET" && pathname === "/") {
|
|
24031
|
+
const data = await freshPortalData(config.projectRoot, config.writeEnabled);
|
|
24032
|
+
const html = await renderLivePage(data);
|
|
24033
|
+
sendText(res, 200, "text/html; charset=utf-8", html);
|
|
24034
|
+
return;
|
|
24035
|
+
}
|
|
24036
|
+
if (method === "GET" && pathname === "/data") {
|
|
24037
|
+
const data = await freshPortalData(config.projectRoot, config.writeEnabled);
|
|
24038
|
+
sendJson(res, 200, data);
|
|
24039
|
+
return;
|
|
24040
|
+
}
|
|
24041
|
+
if (method === "GET" && pathname.startsWith("/static/")) {
|
|
24042
|
+
const rel = pathname.slice("/static/".length);
|
|
24043
|
+
const asset = await readStaticAsset(rel);
|
|
24044
|
+
if (!asset) {
|
|
24045
|
+
sendJson(res, 404, { error: "not-found", path: pathname });
|
|
24046
|
+
return;
|
|
24047
|
+
}
|
|
24048
|
+
res.writeHead(200, { "content-type": asset.contentType });
|
|
24049
|
+
res.end(asset.content);
|
|
24050
|
+
return;
|
|
24051
|
+
}
|
|
24052
|
+
if (method === "GET" && pathname === "/approve/dry-run") {
|
|
24053
|
+
const llm = url.searchParams.get("llm") !== "false";
|
|
24054
|
+
const preview = await dryRunApproveViaCli(config.projectRoot, llm);
|
|
24055
|
+
sendJson(res, 200, preview);
|
|
24056
|
+
return;
|
|
24057
|
+
}
|
|
24058
|
+
if (method === "POST" && pathname === "/approve") {
|
|
24059
|
+
if (!config.writeEnabled) {
|
|
24060
|
+
sendJson(res, 409, {
|
|
24061
|
+
error: "view-only",
|
|
24062
|
+
message: "This portal runs in view-only mode (--no-write); the Approve write action is disabled."
|
|
24063
|
+
});
|
|
24064
|
+
return;
|
|
24065
|
+
}
|
|
24066
|
+
const body = await readJsonBody(req);
|
|
24067
|
+
const llm = body.llm !== false;
|
|
24068
|
+
const result = await runApproveViaCli(config.projectRoot, llm);
|
|
24069
|
+
sendJson(res, 200, {
|
|
24070
|
+
ok: result.exitCode === 0,
|
|
24071
|
+
exitCode: result.exitCode,
|
|
24072
|
+
stdout: result.stdout,
|
|
24073
|
+
stderr: result.stderr
|
|
24074
|
+
});
|
|
24075
|
+
return;
|
|
24076
|
+
}
|
|
24077
|
+
sendJson(res, 404, { error: "not-found", method, path: pathname });
|
|
24078
|
+
} catch (err) {
|
|
24079
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24080
|
+
sendJson(res, 500, { error: "internal", message });
|
|
24081
|
+
}
|
|
24082
|
+
}
|
|
24083
|
+
|
|
24084
|
+
// src/portal/server/server.ts
|
|
24085
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
24086
|
+
function startServer(opts) {
|
|
24087
|
+
const config = { projectRoot: opts.projectRoot, writeEnabled: opts.writeEnabled };
|
|
24088
|
+
const server = createServer((req, res) => {
|
|
24089
|
+
void handleRequest(req, res, config);
|
|
24090
|
+
});
|
|
24091
|
+
return new Promise((resolve6, reject) => {
|
|
24092
|
+
server.on("error", reject);
|
|
24093
|
+
server.listen(opts.port, LOOPBACK_HOST, () => {
|
|
24094
|
+
const address = server.address();
|
|
24095
|
+
const port = typeof address === "object" && address ? address.port : opts.port;
|
|
24096
|
+
resolve6({
|
|
24097
|
+
url: `http://${LOOPBACK_HOST}:${port}`,
|
|
24098
|
+
port,
|
|
24099
|
+
close: () => new Promise((res, rej) => {
|
|
24100
|
+
server.close((err) => err ? rej(err) : res());
|
|
24101
|
+
})
|
|
24102
|
+
});
|
|
24103
|
+
});
|
|
24104
|
+
});
|
|
24105
|
+
}
|
|
24106
|
+
|
|
24107
|
+
// src/cli/portal.ts
|
|
24108
|
+
function registerPortalCommand(program2) {
|
|
24109
|
+
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) => {
|
|
24110
|
+
const n = parseInt(v, 10);
|
|
24111
|
+
if (Number.isNaN(n) || n < 0 || n > 65535) {
|
|
24112
|
+
throw new InvalidArgumentError2("--port must be an integer between 0 and 65535.");
|
|
24113
|
+
}
|
|
24114
|
+
return n;
|
|
24115
|
+
}).option("--open", "Open the page in the default browser").option("--no-write", "View-only mode \u2014 disable the Approve write action").action(async (options) => {
|
|
24116
|
+
try {
|
|
24117
|
+
await runPortal(options);
|
|
24118
|
+
} catch (error) {
|
|
24119
|
+
abortOnUnexpectedError(error, "opening the portal");
|
|
24120
|
+
}
|
|
24121
|
+
});
|
|
24122
|
+
}
|
|
24123
|
+
async function runPortal(options) {
|
|
24124
|
+
const graph = await loadGraphOrAbort(process.cwd());
|
|
24125
|
+
initDebugLog(graph.rootPath, graph.config.debug ?? false, appendToDebugLog);
|
|
24126
|
+
const projectRoot = projectRootFromGraph(graph.rootPath);
|
|
24127
|
+
if (options.static) {
|
|
24128
|
+
const data = await extractPortalData(projectRoot, { writeEnabled: options.write });
|
|
24129
|
+
const outPath = path61.resolve(projectRoot, options.out ?? "yg-portal.html");
|
|
24130
|
+
await emitStatic(data, outPath);
|
|
24131
|
+
process.stdout.write(`Portal page written to ${outPath}
|
|
24132
|
+
`);
|
|
24133
|
+
if (options.open) {
|
|
24134
|
+
await openInBrowser(outPath);
|
|
24135
|
+
}
|
|
24136
|
+
return;
|
|
24137
|
+
}
|
|
24138
|
+
await servePortal(projectRoot, options);
|
|
24139
|
+
}
|
|
24140
|
+
async function servePortal(projectRoot, options) {
|
|
24141
|
+
const handle = await startServer({
|
|
24142
|
+
projectRoot,
|
|
24143
|
+
port: options.port ?? DEFAULT_PORT,
|
|
24144
|
+
writeEnabled: options.write
|
|
24145
|
+
});
|
|
24146
|
+
const mode = options.write ? "" : " (view-only \u2014 Approve disabled)";
|
|
24147
|
+
process.stdout.write(`Portal running at ${handle.url}${mode}
|
|
24148
|
+
`);
|
|
24149
|
+
process.stdout.write("Press Ctrl+C to stop.\n");
|
|
24150
|
+
const shutdown = () => {
|
|
24151
|
+
void handle.close().finally(() => process.exit(0));
|
|
24152
|
+
};
|
|
24153
|
+
process.once("SIGINT", shutdown);
|
|
24154
|
+
process.once("SIGTERM", shutdown);
|
|
24155
|
+
if (options.open) {
|
|
24156
|
+
await openInBrowser(handle.url);
|
|
24157
|
+
}
|
|
24158
|
+
}
|
|
24159
|
+
var DEFAULT_PORT = 4317;
|
|
24160
|
+
async function openInBrowser(filePath) {
|
|
24161
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
24162
|
+
const platform = process.platform;
|
|
24163
|
+
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
24164
|
+
const args = platform === "win32" ? ["/c", "start", "", filePath] : [filePath];
|
|
24165
|
+
try {
|
|
24166
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
24167
|
+
child.on("error", (error) => {
|
|
24168
|
+
debugWrite(`[portal] openInBrowser spawn error: ${error instanceof Error ? error.message : String(error)}`);
|
|
24169
|
+
});
|
|
24170
|
+
child.unref();
|
|
24171
|
+
} catch (error) {
|
|
24172
|
+
debugWrite(`[portal] openInBrowser failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
24173
|
+
}
|
|
24174
|
+
}
|
|
24175
|
+
|
|
24176
|
+
// src/bin.ts
|
|
24177
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
24178
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
22592
24179
|
import { dirname as dirname2, join as join7 } from "path";
|
|
22593
|
-
var __filename3 =
|
|
22594
|
-
var
|
|
22595
|
-
var pkg = JSON.parse(
|
|
22596
|
-
var program = new
|
|
24180
|
+
var __filename3 = fileURLToPath6(import.meta.url);
|
|
24181
|
+
var __dirname4 = dirname2(__filename3);
|
|
24182
|
+
var pkg = JSON.parse(readFileSync11(join7(__dirname4, "..", "package.json"), "utf-8"));
|
|
24183
|
+
var program = new Command3();
|
|
22597
24184
|
program.name("yg").description("Yggdrasil \u2014 architectural knowledge infrastructure for AI agents").version(pkg.version);
|
|
22598
24185
|
registerInitCommand(program);
|
|
22599
24186
|
registerBuildCommand(program);
|
|
@@ -22610,6 +24197,7 @@ registerTypeSuggestCommand(program);
|
|
|
22610
24197
|
registerKnowledgeCommand(program);
|
|
22611
24198
|
registerSchemasCommand(program);
|
|
22612
24199
|
registerSuppressionsCommand(program);
|
|
24200
|
+
registerPortalCommand(program);
|
|
22613
24201
|
process.on("unhandledRejection", (reason) => {
|
|
22614
24202
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
22615
24203
|
process.stderr.write(`Error: ${msg}
|