@event4u/agent-config 17.2.1 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/skills/install-agent-config/SKILL.md +9 -9
- package/CHANGELOG.md +167 -193
- package/CREDITS.md +2 -2
- package/README.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +16 -12
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/check_memory.ts +1 -1
- package/dist/cli/commands/recordTriggerEval.js +1 -1
- package/dist/cli/registry.js +2 -2
- package/dist/cli-delegate/{chunk-KR5ZIFPO.js → chunk-5AYJGUA3.js} +1 -3
- package/dist/cli-delegate/chunk-DIFZBP4G.js +1336 -0
- package/dist/cli-delegate/chunk-E7ACTJTJ.js +6792 -0
- package/dist/cli-delegate/{chunk-VYZGJLCA.js → chunk-RKNT4LIK.js} +22 -1286
- package/dist/cli-delegate/{chunk-BZJ4Y3CI.js → chunk-WY5ZZPGL.js} +2 -2
- package/dist/cli-delegate/{chunk-CMZEKSNN.js → chunk-X6LZ7W5A.js} +73 -10
- package/dist/cli-delegate/cmd_conformance.js +190 -192
- package/dist/cli-delegate/cmd_doctor.js +5 -5
- package/dist/cli-delegate/cmd_doctor_neighbours.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +8 -4
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_update.js +24 -13
- package/dist/cli-delegate/cmd_upgrade.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dist-25AZAHOP.js +3240 -0
- package/dist/cli-delegate/dist-P7MFPMF3.js +411 -0
- package/dist/cli-delegate/dist-XAS5ZLQB.js +6 -0
- package/dist/cli-delegate/{upgrade_unattended-VVL6JLSX.js → upgrade_unattended-QCWOKTUN.js} +178 -163
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/code_graph_refresh.chunk.cjs +443 -0
- package/dist/hooks/code_graph_refresh.chunk.cjs.sha256 +1 -0
- package/dist/hooks/dispatch.js +1001 -938
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/hooks/egress_engine.chunk.cjs +57 -7
- package/dist/hooks/egress_engine.chunk.cjs.sha256 +1 -1
- package/dist/hooks/update_notice.chunk.cjs +914 -0
- package/dist/hooks/update_notice.chunk.cjs.sha256 +1 -0
- package/dist/install/install.mjs +6974 -6037
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -0
- package/dist/mcp/registry-manifest.json +3 -2
- package/dist/mcp/server.json +13 -4
- package/dist/mcp/server.mjs +71202 -63282
- package/dist/scripts/_lib/egress_rules.js +8 -2
- package/dist/scripts/condense.js +18 -15
- package/dist/server/routes/discovery.js +1 -1
- package/dist/server/routes/install.js +1 -1
- package/dist/server/routes/ping.js +1 -1
- package/dist/server/routes/schema.js +4 -4
- package/dist/server/routes/settings.js +2 -6
- package/dist/server/routes/wizard.js +6 -6
- package/dist/server/schemas/settings.js +42 -42
- package/dist/shared/settingsSurface.js +1 -1
- package/dist/shared/userMd/schema.js +1 -1
- package/dist/shared/zodJsonSchema.js +47 -0
- package/dist/ui/assets/index-B0bcIgi0.js +77 -0
- package/dist/ui/index.html +1 -1
- package/package.json +6 -3
- package/src/agent-src/templates/scripts/check_memory.ts +1 -1
- package/src/config/ci-local-parity.yml +11 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/gitignore-block.txt +6 -5
- package/src/config/hook-bundle-budget.json +46 -6
- package/src/config/pack-size-budget.json +12 -1
- package/src/config/packed-binary-manifest.json +18 -0
- package/src/config/sigstore/npm_keys.json +50 -0
- package/src/config/sigstore/trust-material.json +9 -0
- package/src/config/sigstore/trusted_root.json +126 -0
- package/src/config/skill-body-budget.json +7 -1
- package/src/scripts/_cli/cmd_settings_set.ts +3 -2
- package/src/scripts/_cli/cmd_update.ts +48 -20
- package/src/scripts/_cli/cmd_upgrade.ts +7 -1
- package/src/scripts/_cli/upgrade_unattended.ts +90 -76
- package/src/scripts/_dispatch.bash +4 -3
- package/src/scripts/_lib/confusables.ts +0 -19
- package/src/scripts/_lib/egress_normalize.ts +53 -23
- package/src/scripts/_lib/egress_rules.ts +8 -2
- package/src/scripts/_lib/egress_text.ts +5 -3
- package/src/scripts/_lib/egress_token.ts +7 -5
- package/src/scripts/_lib/gate_collector_experimental.ts +633 -0
- package/src/scripts/_lib/git_common_dir.ts +10 -0
- package/src/scripts/_lib/graph_feeder_record.ts +3 -2
- package/src/scripts/_lib/install_reach_checks.ts +32 -2
- package/src/scripts/_lib/lineage_backfill.ts +136 -0
- package/src/scripts/_lib/lineage_ledger.ts +211 -0
- package/src/scripts/_lib/lineage_map.ts +55 -0
- package/src/scripts/_lib/lineage_query.ts +140 -0
- package/src/scripts/_lib/lineage_seed.ts +116 -0
- package/src/scripts/_lib/lineage_watch.ts +249 -0
- package/src/scripts/_lib/link_crypto.ts +8 -4
- package/src/scripts/_lib/listing_manifests.ts +85 -0
- package/src/scripts/_lib/neighbour_census.ts +1 -0
- package/src/scripts/_lib/production_reach.ts +3 -3
- package/src/scripts/_lib/project_registry.ts +227 -0
- package/src/scripts/_lib/provenance_verify.ts +14 -242
- package/src/scripts/_lib/release_settings_surface.ts +2 -6
- package/src/scripts/_lib/sigstore_provenance.ts +177 -0
- package/src/scripts/_lib/source_id.ts +48 -0
- package/src/scripts/_lib/source_shape.ts +4 -0
- package/src/scripts/_lib/unattended_gates.ts +6 -1
- package/src/scripts/adoption_status.ts +231 -27
- package/src/scripts/advisories.ts +26 -10
- package/src/scripts/ai_council/council_egress.ts +5 -6
- package/src/scripts/bench_graph_feeder_latency.ts +10 -3
- package/src/scripts/build_mcp_registry_manifest.ts +23 -12
- package/src/scripts/check_dependency_floors.ts +14 -0
- package/src/scripts/check_egress_mutations.ts +1 -0
- package/src/scripts/check_release_manifest_content.ts +17 -2
- package/src/scripts/check_release_pr_shape.ts +5 -0
- package/src/scripts/code_graph/build.ts +145 -45
- package/src/scripts/code_graph/cli.ts +382 -35
- package/src/scripts/code_graph/conventions_default.ts +247 -0
- package/src/scripts/code_graph/detect.ts +5 -5
- package/src/scripts/code_graph/estate_cli.ts +135 -0
- package/src/scripts/code_graph/estate_index.ts +394 -0
- package/src/scripts/code_graph/estate_manifests.ts +223 -0
- package/src/scripts/code_graph/estate_map.ts +141 -0
- package/src/scripts/code_graph/extract.ts +8 -2
- package/src/scripts/code_graph/extract_more.ts +241 -2
- package/src/scripts/code_graph/graph_budget.ts +158 -0
- package/src/scripts/code_graph/graph_home.ts +80 -0
- package/src/scripts/code_graph/lineage_cli.ts +315 -0
- package/src/scripts/code_graph/loader.ts +10 -2
- package/src/scripts/code_graph/map.ts +195 -53
- package/src/scripts/code_graph/map_model.ts +113 -20
- package/src/scripts/code_graph/map_verify.ts +80 -5
- package/src/scripts/code_graph/map_viewer/viewer.css +18 -0
- package/src/scripts/code_graph/map_viewer/viewer.js +146 -15
- package/src/scripts/code_graph/passes.ts +173 -0
- package/src/scripts/code_graph/project_ref.ts +25 -0
- package/src/scripts/code_graph/projects_cli.ts +254 -0
- package/src/scripts/code_graph/providers/angular.ts +55 -0
- package/src/scripts/code_graph/providers/aspnetcore.ts +182 -0
- package/src/scripts/code_graph/providers/catalog.ts +299 -0
- package/src/scripts/code_graph/providers/django.ts +206 -0
- package/src/scripts/code_graph/providers/express.ts +25 -0
- package/src/scripts/code_graph/providers/fastapi.ts +49 -0
- package/src/scripts/code_graph/providers/flask.ts +99 -0
- package/src/scripts/code_graph/providers/go_http.ts +121 -0
- package/src/scripts/code_graph/providers/index.ts +103 -0
- package/src/scripts/code_graph/{laravel_overlay.ts → providers/laravel.ts} +113 -7
- package/src/scripts/code_graph/providers/nestjs.ts +34 -0
- package/src/scripts/code_graph/providers/nextjs.ts +91 -0
- package/src/scripts/code_graph/providers/nuxt.ts +55 -0
- package/src/scripts/code_graph/providers/rails.ts +204 -0
- package/src/scripts/code_graph/providers/react-router.ts +58 -0
- package/src/scripts/code_graph/providers/shared/detect.ts +78 -0
- package/src/scripts/code_graph/providers/shared/extractors.ts +249 -0
- package/src/scripts/code_graph/providers/shared/js.ts +252 -0
- package/src/scripts/code_graph/providers/shared/kit.ts +210 -0
- package/src/scripts/code_graph/providers/shared/php.ts +64 -0
- package/src/scripts/code_graph/providers/shared/python.ts +223 -0
- package/src/scripts/code_graph/providers/spring.ts +175 -0
- package/src/scripts/code_graph/providers/sqlalchemy.ts +62 -0
- package/src/scripts/code_graph/providers/sveltekit.ts +59 -0
- package/src/scripts/code_graph/providers/symfony.ts +142 -0
- package/src/scripts/code_graph/providers/typeorm.ts +58 -0
- package/src/scripts/code_graph/providers/types.ts +56 -0
- package/src/scripts/code_graph/providers/vue-router.ts +21 -0
- package/src/scripts/code_graph/providers/wordpress.ts +128 -0
- package/src/scripts/code_graph/query.ts +9 -9
- package/src/scripts/code_graph/refresh_cli.ts +81 -0
- package/src/scripts/code_graph/refresh_lock.ts +197 -0
- package/src/scripts/code_graph/repo_identity.ts +169 -0
- package/src/scripts/code_graph/resolution_tiers.ts +19 -7
- package/src/scripts/code_graph/scip_reader.ts +2 -2
- package/src/scripts/code_graph/sqlite_store.ts +31 -28
- package/src/scripts/code_graph/types.ts +79 -41
- package/src/scripts/code_graph/validate.ts +6 -8
- package/src/scripts/condense.ts +15 -15
- package/src/scripts/generate_settings_reference.ts +2 -6
- package/src/scripts/hook_chunks.mjs +11 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +43 -23
- package/src/scripts/hooks/code_graph_refresh_hook.ts +163 -0
- package/src/scripts/hooks/dispatch_hook.ts +6 -7
- package/src/scripts/hooks/host_semantics.ts +72 -0
- package/src/scripts/hooks/lazy_concern.ts +8 -0
- package/src/scripts/hooks/user_notice.ts +105 -0
- package/src/scripts/install-hooks.sh +54 -33
- package/src/scripts/install.ts +2 -6
- package/src/scripts/lint_harvest_provenance.ts +137 -11
- package/src/scripts/lint_hook_manifest.ts +2 -0
- package/src/scripts/lint_marketplace.ts +33 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +36 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +70 -15
- package/src/scripts/mcp_server/graph_tools.ts +165 -41
- package/src/scripts/network_sources.ts +24 -6
- package/src/scripts/release.ts +2 -5
- package/src/scripts/release_drill.ts +4 -1
- package/src/scripts/release_env.ts +17 -0
- package/src/scripts/release_mix_taxonomy.json +3 -0
- package/src/scripts/schemas/provenance-mechanism.schema.json +46 -0
- package/src/scripts/update_notice_hook.ts +75 -7
- package/src/server/schemas/settings.ts +42 -42
- package/src/shared/settingsSurface.ts +1 -1
- package/src/shared/userMd/schema.ts +1 -1
- package/src/shared/zodJsonSchema.ts +41 -0
- package/src/vendor/grammars/README.md +8 -6
- package/src/vendor/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/cli-delegate/chunk-XIAPY44N.js +0 -4587
- package/dist/ui/assets/index-w70Ado09.js +0 -23
- /package/dist/cli-delegate/{chunk-FKYZOWVD.js → chunk-QBAFP67O.js} +0 -0
- /package/dist/cli-delegate/{chunk-BMGUGSY6.js → chunk-TQG6UMPC.js} +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed the two lineage ledgers from the provenance stores that already exist.
|
|
3
|
+
*
|
|
4
|
+
* Tracked side: one `mechanisms.jsonl` row per `harvests.jsonl` row — id, verdict, the
|
|
5
|
+
* asserting artefact and the date carried over, nothing else. `harvests.jsonl` stays, so
|
|
6
|
+
* every citation keeps resolving.
|
|
7
|
+
*
|
|
8
|
+
* Local side: every harvest `source_ref` and every `skills-provenance.yml` entry becomes a
|
|
9
|
+
* sealed source plus an edge to its mechanism. Nothing is invented on the way: an opaque
|
|
10
|
+
* harvest ref has no revision, so its edge records `pin: unknown`; a skills entry keeps the
|
|
11
|
+
* sealed pin it already carries. The tracked yml is read, never written.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from 'node:fs';
|
|
15
|
+
import * as path from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { parse as parseYaml } from 'yaml';
|
|
18
|
+
|
|
19
|
+
import { canonicalize } from '../harvest_reference_tokens.js';
|
|
20
|
+
import type { HarvestRecord, MechanismRecord } from '../lint_harvest_provenance.js';
|
|
21
|
+
import { PIN_UNKNOWN, type EdgeRow, type Ledger, type SourceKind, type SourceRow } from './lineage_ledger.js';
|
|
22
|
+
import { decrypt, encrypt, is_token } from './link_crypto.js';
|
|
23
|
+
import { sourceId } from './source_id.js';
|
|
24
|
+
|
|
25
|
+
export const HARVESTS_REL = 'provenance/harvests.jsonl';
|
|
26
|
+
export const SKILLS_PROVENANCE_REL = 'agents/settings/contexts/skills-provenance.yml';
|
|
27
|
+
|
|
28
|
+
export function seedMechanismsFromHarvests(harvests: readonly HarvestRecord[]): MechanismRecord[] {
|
|
29
|
+
return harvests.map((h) => ({
|
|
30
|
+
mechanism_id: h.harvest_id,
|
|
31
|
+
verdict: h.verdict,
|
|
32
|
+
realised_in: [h.stated_in],
|
|
33
|
+
decided_at: h.harvested_at,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PINNED_REF_RE = /^(.+)@([0-9a-fA-F]{7,64})$/;
|
|
38
|
+
|
|
39
|
+
function kindOf(identity: string): SourceKind {
|
|
40
|
+
if (identity.startsWith('opaque:')) return 'unknown';
|
|
41
|
+
return canonicalize(identity) === null ? 'url' : 'repo';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ImportResult {
|
|
45
|
+
readonly ledger: Ledger;
|
|
46
|
+
/** One line per entry that could not be imported, naming why. Never a guess in its place. */
|
|
47
|
+
readonly refused: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readHarvests(root: string): HarvestRecord[] {
|
|
51
|
+
const file = path.join(root, HARVESTS_REL);
|
|
52
|
+
if (!fs.existsSync(file)) return [];
|
|
53
|
+
return fs
|
|
54
|
+
.readFileSync(file, 'utf-8')
|
|
55
|
+
.split('\n')
|
|
56
|
+
.filter((l) => l.trim() !== '')
|
|
57
|
+
.map((l) => JSON.parse(l) as HarvestRecord);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Build the local rows for `root` without writing anything. `keys[0]` seals; all keys decrypt. */
|
|
61
|
+
export function buildLocalImport(root: string, keys: readonly string[]): ImportResult {
|
|
62
|
+
const key = keys[0] as string;
|
|
63
|
+
const sources = new Map<string, SourceRow>();
|
|
64
|
+
const edges: EdgeRow[] = [];
|
|
65
|
+
const refused: string[] = [];
|
|
66
|
+
const addSource = (identity: string, sealed: string, license: string): string => {
|
|
67
|
+
const id = sourceId(identity, key);
|
|
68
|
+
if (!sources.has(id)) sources.set(id, { source_id: id, kind: kindOf(identity), identity: sealed, license, watch: false });
|
|
69
|
+
return id;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
for (const h of readHarvests(root)) {
|
|
73
|
+
let identity = h.source_ref;
|
|
74
|
+
let pin: string = PIN_UNKNOWN;
|
|
75
|
+
if (is_token(h.source_ref)) {
|
|
76
|
+
try {
|
|
77
|
+
identity = decrypt(h.source_ref, [...keys]);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
refused.push(`${h.harvest_id}: source_ref does not decrypt with the configured key (${(e as Error).message})`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
} else if (!identity.startsWith('opaque:')) {
|
|
83
|
+
const m = PINNED_REF_RE.exec(identity);
|
|
84
|
+
if (m) {
|
|
85
|
+
identity = m[1] as string;
|
|
86
|
+
pin = encrypt(m[2] as string, key);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const id = addSource(identity, encrypt(identity, key), 'unknown');
|
|
90
|
+
edges.push({ source_id: id, mechanism_id: h.harvest_id, pin, locator: encrypt(h.evidence_locator, key) });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const ymlFile = path.join(root, SKILLS_PROVENANCE_REL);
|
|
94
|
+
if (fs.existsSync(ymlFile)) {
|
|
95
|
+
const doc = (parseYaml(fs.readFileSync(ymlFile, 'utf-8')) ?? {}) as Record<string, Record<string, unknown> | null>;
|
|
96
|
+
for (const [skill, entry] of Object.entries(doc)) {
|
|
97
|
+
const sealedSource = entry?.source_enc;
|
|
98
|
+
if (typeof sealedSource !== 'string' || !is_token(sealedSource)) {
|
|
99
|
+
refused.push(`${skill}: no sealed source_enc`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let identity: string;
|
|
103
|
+
try {
|
|
104
|
+
identity = decrypt(sealedSource, [...keys]);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
refused.push(`${skill}: source_enc does not decrypt with the configured key (${(e as Error).message})`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const pinned = entry?.pinned_to_enc;
|
|
110
|
+
const license = typeof entry?.license === 'string' && entry.license !== '' ? entry.license : 'unknown';
|
|
111
|
+
const id = addSource(identity, sealedSource, license);
|
|
112
|
+
edges.push({ source_id: id, mechanism_id: skill, pin: typeof pinned === 'string' && is_token(pinned) ? pinned : PIN_UNKNOWN, locator: null });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { ledger: { sources: [...sources.values()], edges }, refused };
|
|
116
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The upstream delta watch — one local run that turns an upstream move into findings.
|
|
3
|
+
*
|
|
4
|
+
* Per source the owner switched on (`watch: true`, off by default — council O7): one
|
|
5
|
+
* `git ls-remote`; if HEAD is the newest recorded pin (a full or short SHA, or a tag
|
|
6
|
+
* resolved through `ls-remote`), stop there. Otherwise a depth-1
|
|
7
|
+
* fetch of both commits into a private cache under the user-global home, then two diffs —
|
|
8
|
+
* the files under the edges' locators, and the top-level listing — classified as:
|
|
9
|
+
*
|
|
10
|
+
* reverify a locator's file changed under an adopted or adapted mechanism
|
|
11
|
+
* reopen a locator's file changed under a rejected or deferred mechanism
|
|
12
|
+
* drift a locator's file was deleted or renamed away
|
|
13
|
+
* candidate a new top-level entry, which no mechanism has looked at yet
|
|
14
|
+
* license-alert a top-level license file changed
|
|
15
|
+
*
|
|
16
|
+
* It never runs under CI (the key and the identities are local, CI logs are not), never
|
|
17
|
+
* fetches without consent, and never writes code: the output is a report and a stub.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
|
|
24
|
+
import type { MechanismRecord } from '../lint_harvest_provenance.js';
|
|
25
|
+
import { gitEnv } from './git_env.js';
|
|
26
|
+
import { assertOutsideWorkTree, LineageError, PIN_UNKNOWN, readLedger, type SourceRow } from './lineage_ledger.js';
|
|
27
|
+
import { latestDecisions } from './lineage_query.js';
|
|
28
|
+
import { decrypt, encrypt } from './link_crypto.js';
|
|
29
|
+
import type { EnvMap } from './user_global_paths.js';
|
|
30
|
+
|
|
31
|
+
export type GitRunner = (args: string[], cwd?: string) => string;
|
|
32
|
+
|
|
33
|
+
const GIT_TIMEOUT_MS = 120_000;
|
|
34
|
+
|
|
35
|
+
export const systemGit: GitRunner = (args, cwd) =>
|
|
36
|
+
execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'protocol.ext.allow=never', '-c', 'http.followRedirects=false', ...args], {
|
|
37
|
+
cwd,
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
timeout: GIT_TIMEOUT_MS,
|
|
41
|
+
env: { ...gitEnv(), GIT_TERMINAL_PROMPT: '0' },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export type FindingClass = 'reverify' | 'reopen' | 'drift' | 'candidate' | 'license-alert';
|
|
45
|
+
|
|
46
|
+
export interface WatchFinding {
|
|
47
|
+
readonly class: FindingClass;
|
|
48
|
+
/** null for a candidate or a license alert — no mechanism has looked at it. */
|
|
49
|
+
readonly mechanism_id: string | null;
|
|
50
|
+
/** Plaintext, local only: the locator or top-level entry. */
|
|
51
|
+
readonly locator: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SourceReport {
|
|
55
|
+
readonly source_id: string;
|
|
56
|
+
readonly identity: string;
|
|
57
|
+
readonly status: 'unchanged' | 'changed' | 'unpinned' | 'not-watchable' | 'error';
|
|
58
|
+
readonly pin: string | null;
|
|
59
|
+
readonly head: string | null;
|
|
60
|
+
readonly findings: WatchFinding[];
|
|
61
|
+
/** Mechanisms the source fed, for a license alert that concerns all of them. */
|
|
62
|
+
readonly mechanisms: string[];
|
|
63
|
+
readonly note?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface WatchOptions {
|
|
67
|
+
readonly dir: string;
|
|
68
|
+
readonly keys: readonly string[];
|
|
69
|
+
readonly rows: readonly MechanismRecord[];
|
|
70
|
+
readonly git: GitRunner;
|
|
71
|
+
readonly env: EnvMap;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function refuseUnderCi(env: EnvMap): void {
|
|
75
|
+
if (env.CI !== undefined && env.CI !== '' && env.CI !== 'false' && env.CI !== '0') {
|
|
76
|
+
throw new LineageError('the lineage watch never runs under CI — the key and the source identities are local, CI logs are not');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const LICENSE_RE = /^(licen[cs]e|copying)(\.[a-z0-9]+)?$/i;
|
|
81
|
+
|
|
82
|
+
/** A locator names a path, optionally suffixed `:<line>` or `#<anchor>`. */
|
|
83
|
+
function locatorPath(locator: string): string {
|
|
84
|
+
return locator.replace(/#.*$/, '').replace(/:\d+(-\d+)?$/, '').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const under = (file: string, loc: string): boolean => file === loc || file.startsWith(`${loc}/`);
|
|
88
|
+
|
|
89
|
+
/** The commit a tag names on the remote, peeled through an annotated tag; null when absent. */
|
|
90
|
+
function tagCommit(git: GitRunner, identity: string, tag: string): string | null {
|
|
91
|
+
if (tag.startsWith('-')) return null;
|
|
92
|
+
const refs = new Map<string, string>();
|
|
93
|
+
// The trailing glob is what makes ls-remote print the peeled `^{}` line; the exact match is below.
|
|
94
|
+
for (const line of git(['ls-remote', '--tags', identity, `refs/tags/${tag}*`]).split('\n')) {
|
|
95
|
+
const [sha, ref] = line.trim().split(/\s+/);
|
|
96
|
+
if (sha && ref) refs.set(ref, sha);
|
|
97
|
+
}
|
|
98
|
+
const sha = refs.get(`refs/tags/${tag}^{}`) ?? refs.get(`refs/tags/${tag}`) ?? null;
|
|
99
|
+
return sha !== null && /^[0-9a-f]{40,64}$/.test(sha) ? sha : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function watchOne(source: SourceRow, opts: WatchOptions): SourceReport {
|
|
103
|
+
const { keys, git } = opts;
|
|
104
|
+
const ledger = readLedger(opts.dir);
|
|
105
|
+
// The ledger is append-only, so only a mechanism's newest edge speaks for it.
|
|
106
|
+
const edges = [...new Map(ledger.edges.filter((e) => e.source_id === source.source_id).map((e) => [e.mechanism_id, e])).values()];
|
|
107
|
+
const mechanisms = [...new Set(edges.map((e) => e.mechanism_id))];
|
|
108
|
+
const identity = decrypt(source.identity, [...keys]);
|
|
109
|
+
const base = { source_id: source.source_id, identity, mechanisms };
|
|
110
|
+
if (source.kind !== 'repo') {
|
|
111
|
+
return { ...base, status: 'not-watchable', pin: null, head: null, findings: [], note: `kind ${source.kind} has no revision to compare` };
|
|
112
|
+
}
|
|
113
|
+
const pinned = edges.filter((e) => e.pin !== PIN_UNKNOWN);
|
|
114
|
+
if (pinned.length === 0) {
|
|
115
|
+
return { ...base, status: 'unpinned', pin: null, head: null, findings: [], note: 'no edge records a pin, so there is nothing to compare against' };
|
|
116
|
+
}
|
|
117
|
+
const pin = decrypt((pinned[pinned.length - 1] as { pin: string }).pin, [...keys]);
|
|
118
|
+
|
|
119
|
+
if (identity.startsWith('-')) return { ...base, status: 'error', pin, head: null, findings: [], note: 'identity is not a repository location' };
|
|
120
|
+
const head = (git(['ls-remote', identity, 'HEAD']).split(/\s+/)[0] ?? '').trim();
|
|
121
|
+
if (!/^[0-9a-f]{7,64}$/.test(head)) return { ...base, status: 'error', pin, head: null, findings: [], note: 'ls-remote returned no HEAD' };
|
|
122
|
+
const hexPin = /^[0-9a-f]{7,64}$/i.test(pin);
|
|
123
|
+
let pinRev = pin.toLowerCase();
|
|
124
|
+
if (!hexPin) {
|
|
125
|
+
const peeled = tagCommit(git, identity, pin);
|
|
126
|
+
if (peeled === null) return { ...base, status: 'error', pin, head, findings: [], note: `pin ${pin} is neither a commit nor a tag the remote has` };
|
|
127
|
+
pinRev = peeled;
|
|
128
|
+
}
|
|
129
|
+
if (head.startsWith(pinRev)) return { ...base, status: 'unchanged', pin, head, findings: [] };
|
|
130
|
+
|
|
131
|
+
const cache = path.join(opts.dir, 'cache', source.source_id);
|
|
132
|
+
// The cache is a repository of its own; what must sit outside every work tree is its container.
|
|
133
|
+
assertOutsideWorkTree(path.dirname(cache));
|
|
134
|
+
if (!fs.existsSync(path.join(cache, '.git'))) {
|
|
135
|
+
fs.mkdirSync(cache, { recursive: true, mode: 0o700 });
|
|
136
|
+
git(['init', '-q'], cache);
|
|
137
|
+
}
|
|
138
|
+
git(['fetch', '-q', '--depth', '1', '--no-tags', identity, head], cache);
|
|
139
|
+
if (pinRev.length >= 40) git(['fetch', '-q', '--depth', '1', '--no-tags', identity, pinRev], cache);
|
|
140
|
+
else {
|
|
141
|
+
// A remote serves no object by an abbreviated name, so a short pin is found in HEAD's history.
|
|
142
|
+
const shallow = git(['rev-parse', '--is-shallow-repository'], cache).trim() === 'true';
|
|
143
|
+
git(['fetch', '-q', '--no-tags', ...(shallow ? ['--unshallow'] : []), identity, head], cache);
|
|
144
|
+
pinRev = git(['rev-parse', '--verify', '-q', `${pinRev}^{commit}`], cache).trim();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const changed = new Map<string, string>();
|
|
148
|
+
for (const line of git(['diff', '--name-status', '-M', pinRev, head], cache).split('\n')) {
|
|
149
|
+
const [status, from] = line.split('\t');
|
|
150
|
+
if (status && from) changed.set(from, status.charAt(0));
|
|
151
|
+
}
|
|
152
|
+
const listing = (rev: string): Set<string> => new Set(git(['ls-tree', '--name-only', rev], cache).split('\n').filter((x) => x !== ''));
|
|
153
|
+
const before = listing(pinRev);
|
|
154
|
+
const after = listing(head);
|
|
155
|
+
|
|
156
|
+
const latest = latestDecisions(opts.rows);
|
|
157
|
+
const findings: WatchFinding[] = [];
|
|
158
|
+
for (const e of edges) {
|
|
159
|
+
if (e.locator === null) continue;
|
|
160
|
+
const locator = decrypt(e.locator, [...keys]);
|
|
161
|
+
const loc = locatorPath(locator);
|
|
162
|
+
const hits = [...changed.entries()].filter(([f]) => under(f, loc));
|
|
163
|
+
if (hits.length === 0) continue;
|
|
164
|
+
const gone = hits.every(([, s]) => s === 'D' || s === 'R');
|
|
165
|
+
const verdict = latest.get(e.mechanism_id)?.verdict;
|
|
166
|
+
const cls: FindingClass = gone ? 'drift' : verdict === 'reject' || verdict === 'deferred' ? 'reopen' : 'reverify';
|
|
167
|
+
findings.push({ class: cls, mechanism_id: e.mechanism_id, locator });
|
|
168
|
+
}
|
|
169
|
+
for (const entry of [...after].sort()) if (!before.has(entry)) findings.push({ class: 'candidate', mechanism_id: null, locator: entry });
|
|
170
|
+
for (const [f] of changed) if (!f.includes('/') && LICENSE_RE.test(f)) findings.push({ class: 'license-alert', mechanism_id: null, locator: f });
|
|
171
|
+
return { ...base, status: 'changed', pin, head, findings };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** One run over every watched source. Throws under CI before any git call. */
|
|
175
|
+
export function runWatch(opts: WatchOptions): SourceReport[] {
|
|
176
|
+
refuseUnderCi(opts.env);
|
|
177
|
+
return readLedger(opts.dir)
|
|
178
|
+
.sources.filter((s) => s.watch === true)
|
|
179
|
+
.map((s) => {
|
|
180
|
+
try {
|
|
181
|
+
return watchOne(s, opts);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e instanceof LineageError) throw e;
|
|
184
|
+
return { source_id: s.source_id, identity: '', status: 'error' as const, pin: null, head: null, findings: [], mechanisms: [], note: (e as Error).message.split('\n')[0] ?? 'watch failed' };
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const CLASS_ORDER: readonly FindingClass[] = ['reopen', 'drift', 'reverify', 'license-alert', 'candidate'];
|
|
190
|
+
|
|
191
|
+
/** The local report: identities in plaintext, so it is written only under the user-global home. */
|
|
192
|
+
export function renderWatchReport(reports: readonly SourceReport[], date: string): string {
|
|
193
|
+
const out = [`# Lineage watch — ${date} (local, never shared)`, ''];
|
|
194
|
+
for (const r of reports) {
|
|
195
|
+
out.push(`## ${r.source_id} — ${r.identity}`, '', `status ${r.status}${r.pin ? ` · pin ${r.pin}` : ''}${r.head ? ` · head ${r.head}` : ''}${r.note ? ` · ${r.note}` : ''}`, '');
|
|
196
|
+
for (const f of [...r.findings].sort((a, b) => CLASS_ORDER.indexOf(a.class) - CLASS_ORDER.indexOf(b.class))) {
|
|
197
|
+
out.push(`- ${f.class} ${f.mechanism_id ?? '(no mechanism)'} — ${f.locator}`);
|
|
198
|
+
}
|
|
199
|
+
out.push('');
|
|
200
|
+
}
|
|
201
|
+
return out.join('\n');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The tracked stub: mechanism ids and freshly sealed locators, nothing else — no source id,
|
|
206
|
+
* no identity, no pin. One per run, grouped by mechanism rather than by commit, so a busy
|
|
207
|
+
* upstream produces one decision to make per mechanism. Null when there is nothing to plan.
|
|
208
|
+
*/
|
|
209
|
+
export function renderWatchStub(reports: readonly SourceReport[], date: string, key: string): string | null {
|
|
210
|
+
const byMechanism = new Map<string, WatchFinding[]>();
|
|
211
|
+
const unowned: WatchFinding[] = [];
|
|
212
|
+
for (const r of reports) {
|
|
213
|
+
for (const f of r.findings) {
|
|
214
|
+
if (f.class === 'license-alert') {
|
|
215
|
+
for (const m of r.mechanisms) byMechanism.set(m, [...(byMechanism.get(m) ?? []), f]);
|
|
216
|
+
} else if (f.mechanism_id === null) unowned.push(f);
|
|
217
|
+
else byMechanism.set(f.mechanism_id, [...(byMechanism.get(f.mechanism_id) ?? []), f]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (byMechanism.size === 0 && unowned.length === 0) return null;
|
|
221
|
+
const review = new Date(`${date}T00:00:00Z`);
|
|
222
|
+
review.setUTCDate(review.getUTCDate() + 90);
|
|
223
|
+
const line = (f: WatchFinding): string => `- ${f.class} — \`${encrypt(f.locator, key)}\``;
|
|
224
|
+
const out = [
|
|
225
|
+
'---',
|
|
226
|
+
'complexity: lightweight',
|
|
227
|
+
`review_by: ${review.toISOString().slice(0, 10)}`,
|
|
228
|
+
'---',
|
|
229
|
+
'',
|
|
230
|
+
`# Stub: lineage delta of ${date}`,
|
|
231
|
+
'',
|
|
232
|
+
'> **Stub — not active work.** Written by `code-graph lineage watch`. An upstream that a',
|
|
233
|
+
'> recorded mechanism was read from has moved. Nothing was implemented or merged: each',
|
|
234
|
+
'> entry below is a decision for the owner. Locators are sealed (`link_crypto decrypt`);',
|
|
235
|
+
'> the local report under the user-global `runtime/lineage/watch/` has the rest.',
|
|
236
|
+
'',
|
|
237
|
+
];
|
|
238
|
+
for (const m of [...byMechanism.keys()].sort()) {
|
|
239
|
+
out.push(`## Mechanism \`${m}\``, '', ...(byMechanism.get(m) as WatchFinding[]).map(line), '');
|
|
240
|
+
}
|
|
241
|
+
if (unowned.length > 0) out.push('## New upstream entries no mechanism covers', '', ...unowned.map(line), '');
|
|
242
|
+
out.push(
|
|
243
|
+
'## Kill criteria',
|
|
244
|
+
'',
|
|
245
|
+
'- Every entry above is re-verified, reopened or dismissed in `provenance/mechanisms.jsonl` — then delete this stub.',
|
|
246
|
+
'',
|
|
247
|
+
);
|
|
248
|
+
return out.join('\n');
|
|
249
|
+
}
|
|
@@ -58,6 +58,8 @@ import * as path from 'node:path';
|
|
|
58
58
|
import process from 'node:process';
|
|
59
59
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
60
60
|
|
|
61
|
+
import { sourceId } from './source_id.js';
|
|
62
|
+
|
|
61
63
|
export const MAGIC = 'ENC1';
|
|
62
64
|
const _SALT_LEN = 16;
|
|
63
65
|
const _NONCE_LEN = 16;
|
|
@@ -232,19 +234,19 @@ export class ValueError extends Error {}
|
|
|
232
234
|
export function _cli(argv: readonly string[]): number {
|
|
233
235
|
const prog = 'link_crypto';
|
|
234
236
|
const cmd = argv[0];
|
|
235
|
-
const validCmds = new Set(['encrypt', 'decrypt', 'keygen', 'keystatus']);
|
|
237
|
+
const validCmds = new Set(['encrypt', 'decrypt', 'keygen', 'keystatus', 'source-id']);
|
|
236
238
|
|
|
237
239
|
// argparse: subparser required → error when missing/invalid (exit 2).
|
|
238
240
|
if (cmd === undefined || !validCmds.has(cmd)) {
|
|
239
|
-
const choices = "'encrypt', 'decrypt', 'keygen', 'keystatus'";
|
|
241
|
+
const choices = "'encrypt', 'decrypt', 'keygen', 'keystatus', 'source-id'";
|
|
240
242
|
if (cmd === undefined) {
|
|
241
243
|
process.stderr.write(
|
|
242
|
-
`usage: ${prog} [-h] {encrypt,decrypt,keygen,keystatus} ...\n` +
|
|
244
|
+
`usage: ${prog} [-h] {encrypt,decrypt,keygen,keystatus,source-id} ...\n` +
|
|
243
245
|
`${prog}: error: the following arguments are required: cmd\n`,
|
|
244
246
|
);
|
|
245
247
|
} else {
|
|
246
248
|
process.stderr.write(
|
|
247
|
-
`usage: ${prog} [-h] {encrypt,decrypt,keygen,keystatus} ...\n` +
|
|
249
|
+
`usage: ${prog} [-h] {encrypt,decrypt,keygen,keystatus,source-id} ...\n` +
|
|
248
250
|
`${prog}: error: argument cmd: invalid choice: '${cmd}' (choose from ${choices})\n`,
|
|
249
251
|
);
|
|
250
252
|
}
|
|
@@ -292,6 +294,8 @@ export function _cli(argv: readonly string[]): number {
|
|
|
292
294
|
}
|
|
293
295
|
if (cmd === 'encrypt') {
|
|
294
296
|
process.stdout.write(encrypt(v, keys[0] as string) + '\n');
|
|
297
|
+
} else if (cmd === 'source-id') {
|
|
298
|
+
process.stdout.write(sourceId(v, keys[0] as string) + '\n');
|
|
295
299
|
} else {
|
|
296
300
|
process.stdout.write(decrypt(v, keys) + '\n');
|
|
297
301
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The listing manifests outside the npm tarball that start the MCP server
|
|
3
|
+
* through `npx`: the GitHub Copilot discovery plugin and the Gemini CLI
|
|
4
|
+
* extension. Both carry the package version twice — in `version` and pinned
|
|
5
|
+
* inside the `npx` argument — and a crawler installs whatever the default
|
|
6
|
+
* branch says, so both copies follow `package.json` through the release.
|
|
7
|
+
*
|
|
8
|
+
* One module because three readers must agree on the same two facts: the
|
|
9
|
+
* release writes them (`release_env`), the release-PR content gate admits a
|
|
10
|
+
* change to them and nothing else (`check_release_manifest_content`), and the
|
|
11
|
+
* marketplace lint fails a tree where they drifted (`lint_marketplace`).
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
|
|
16
|
+
export const LISTING_MANIFESTS = ['.github/plugin/plugin.json', 'gemini-extension.json'] as const;
|
|
17
|
+
|
|
18
|
+
export const PACKAGE_NAME = '@event4u/agent-config';
|
|
19
|
+
|
|
20
|
+
/** An `npx` argument pinning this package to an exact release, prereleases included. */
|
|
21
|
+
export const PINNED_PACKAGE_ARG_RE = /^@event4u\/agent-config@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
22
|
+
|
|
23
|
+
export function pinnedPackageArg(version: string): string {
|
|
24
|
+
return `${PACKAGE_NAME}@${version}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Obj = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
function _isObject(v: unknown): v is Obj {
|
|
30
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every `[serverName, args]` pair under `mcpServers`, args left `unknown` for the caller to judge. */
|
|
34
|
+
export function mcpServerArgs(manifest: unknown): Array<[string, unknown]> {
|
|
35
|
+
if (!_isObject(manifest) || !_isObject(manifest['mcpServers'])) return [];
|
|
36
|
+
return Object.entries(manifest['mcpServers']).map(([name, server]) => [
|
|
37
|
+
name,
|
|
38
|
+
_isObject(server) ? server['args'] : undefined,
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Every way `manifest` disagrees with `version`: the `version` field, and each
|
|
44
|
+
* server whose `npx` arguments do not pin exactly this release. An unpinned
|
|
45
|
+
* `@event4u/agent-config` argument is a finding too — it resolves to whatever
|
|
46
|
+
* is latest on the day a user installs, which is the drift being prevented.
|
|
47
|
+
*/
|
|
48
|
+
export function listingVersionFindings(label: string, manifest: unknown, version: string): string[] {
|
|
49
|
+
const out: string[] = [];
|
|
50
|
+
const declared = _isObject(manifest) ? manifest['version'] : undefined;
|
|
51
|
+
if (declared !== version) {
|
|
52
|
+
out.push(`${label}: version \`${String(declared)}\` does not match package.json version \`${version}\``);
|
|
53
|
+
}
|
|
54
|
+
const servers = mcpServerArgs(manifest);
|
|
55
|
+
if (servers.length === 0) out.push(`${label}: declares no mcpServers entry`);
|
|
56
|
+
for (const [name, args] of servers) {
|
|
57
|
+
const own = Array.isArray(args)
|
|
58
|
+
? args.filter((a): a is string => typeof a === 'string' && a.startsWith(PACKAGE_NAME))
|
|
59
|
+
: [];
|
|
60
|
+
if (own.length !== 1 || own[0] !== pinnedPackageArg(version)) {
|
|
61
|
+
out.push(
|
|
62
|
+
`${label}: mcpServers.${name}.args must pin \`${pinnedPackageArg(version)}\` ` +
|
|
63
|
+
`(found ${own.length === 0 ? 'none' : own.map((a) => `\`${a}\``).join(', ')})`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Rewrite `version` and every pinned package argument; `jsonDump` is the release's own serializer. */
|
|
71
|
+
export function setListingManifestVersion(p: string, version: string, jsonDump: (v: unknown) => string): void {
|
|
72
|
+
const data = JSON.parse(fs.readFileSync(p, 'utf-8')) as unknown;
|
|
73
|
+
if (_isObject(data) && 'version' in data) data['version'] = version;
|
|
74
|
+
for (const [, args] of mcpServerArgs(data)) {
|
|
75
|
+
if (!Array.isArray(args)) continue;
|
|
76
|
+
args.forEach((a, i) => {
|
|
77
|
+
if (typeof a === 'string' && PINNED_PACKAGE_ARG_RE.test(a)) args[i] = pinnedPackageArg(version);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
fs.writeFileSync(p, jsonDump(data) + '\n', 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function listingManifestPaths(root: string): string[] {
|
|
84
|
+
return LISTING_MANIFESTS.map((rel) => path.join(root, rel));
|
|
85
|
+
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* dynamic `import()` not followed. A symbol counts as reached when some module
|
|
6
6
|
* in the closure imports it BY NAME from its defining module (or imports that
|
|
7
7
|
* module whole), or when the defining module is itself an entry. Reaching the
|
|
8
|
-
* module is not enough — `egress_normalize.ts`
|
|
9
|
-
* while `detectNormalized`
|
|
10
|
-
* this exists to see.
|
|
8
|
+
* module is not enough — `egress_normalize.ts` was in the closure for one regex
|
|
9
|
+
* while `detectNormalized` had no production importer (until one-detector 2.5
|
|
10
|
+
* wired it), and that is the case this exists to see.
|
|
11
11
|
*/
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
13
|
import * as fs from 'node:fs';
|