@event4u/agent-config 14.21.0 → 14.23.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/CHANGELOG.md +164 -187
- package/CREDITS.md +37 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/commands/pr/merge.md +47 -38
- package/dist/agent-src/commands/roadmap/next.md +8 -0
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +12 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/notes-first-reasoning.md +36 -25
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +82 -1
- package/dist/agent-src/skills/git-workflow/references/push-closes-its-loop.md +9 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +52 -2
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +542 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-2KKXJW25.js} +23 -9
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/{chunk-4FS4WRXP.js → chunk-IAI3XL43.js} +15 -3
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-KCNTUSPI.js} +7 -1
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +45 -13
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_session_recycle.js +4 -14
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +13 -13
- 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/dispatch.js +1813 -946
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +21 -3
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +10870 -7641
- package/dist/scripts/_lib/lean_projection_mode.js +108 -0
- package/dist/scripts/_lib/router_match.js +243 -0
- package/dist/scripts/_lib/rule_injection.js +216 -0
- package/dist/scripts/condense.js +52 -45
- package/dist/scripts/project_thin_rules.js +139 -19
- package/dist/server/schemas/settings.js +16 -3
- package/docs/MIGRATION.md +105 -1
- package/docs/contracts/settings-classes.md +40 -8
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/docs/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/llms.txt +1 -1
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +120 -27
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/agent-settings.template.yml +47 -1
- package/src/config/continuity-surface.json +516 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/hook-token-budget.json +7 -6
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/config/routing-coverage-seed.json +9 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_cli/cmd_session_recycle.ts +2 -25
- package/src/scripts/_cli/handoff_sessions.ts +14 -44
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_slot.ts +295 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/continuity_writer.ts +253 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +86 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/lean_projection_mode.ts +127 -0
- package/src/scripts/_lib/packed_binary_predicate.ts +262 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/recycle_envelope_paths.ts +109 -21
- package/src/scripts/_lib/regression_neighbourhood.ts +159 -22
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/session_eol.ts +44 -0
- package/src/scripts/_lib/session_index_trust.ts +346 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/standing_bound_ratchet.ts +154 -0
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/_lib/vendored_grammar_upstream.ts +280 -0
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/audit_mcp_tools.ts +69 -25
- package/src/scripts/build_mcp_catalog.ts +24 -1
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_host_tree_parity.ts +291 -0
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_preamble_payload_budget.ts +136 -7
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_rule_projection_integrity.ts +102 -2
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/ci_settle.ts +205 -5
- package/src/scripts/code_graph/build.ts +217 -15
- package/src/scripts/code_graph/cli.ts +334 -15
- package/src/scripts/code_graph/detect.ts +38 -0
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +144 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +262 -6
- package/src/scripts/code_graph/types.ts +117 -3
- package/src/scripts/code_graph/validate.ts +26 -1
- package/src/scripts/code_graph/verbs.ts +590 -0
- package/src/scripts/condense.ts +54 -42
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +118 -1
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +87 -12
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +182 -16
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +201 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/push_settle_hook.ts +3 -2
- package/src/scripts/hooks/rule_inject_hook.ts +108 -21
- package/src/scripts/hooks/session_eol_hook.ts +120 -2
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/hot_context_hook.ts +62 -7
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/lint_roadmap_blockers.ts +164 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +152 -2
- package/src/scripts/mcp_server/graph_tools.ts +294 -0
- package/src/scripts/mcp_server/path_util.ts +31 -0
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tool_catalog_source.ts +7 -1
- package/src/scripts/mcp_server/tools.ts +5 -69
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/model_rule_injection.ts +61 -4
- package/src/scripts/project_thin_rules.ts +149 -16
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/report_standing_payload_by_host.ts +650 -0
- package/src/scripts/schemas/agent-settings.schema.json +7 -1
- package/src/scripts/session_memory_index.ts +81 -8
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/server/schemas/settings.ts +23 -4
- package/src/vendor/grammars/README.md +86 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-3 verbs a gate can read (`road-to-a-graph-that-is-shipped` 3.1–3.3).
|
|
3
|
+
*
|
|
4
|
+
* `query` / `explain` / `affected` / `path` answer a question a human asked.
|
|
5
|
+
* These four answer a question a GATE asks, which is a different shape: the
|
|
6
|
+
* output has to be a decidable set rather than a readable neighbourhood, and it
|
|
7
|
+
* has to say what it filtered out, because a gate that acts on a set nobody can
|
|
8
|
+
* audit is a gate nobody should trust.
|
|
9
|
+
*
|
|
10
|
+
* THE ACCEPTED-EDGE FILTER, and why it is `resolved_via` and not `confidence`.
|
|
11
|
+
*
|
|
12
|
+
* An edge is ACCEPTED when its `resolved_via` is not in
|
|
13
|
+
* {@link NON_ACCEPTED_VIA} — `name-lookup` and `dynamic`. Both are guesses:
|
|
14
|
+
* `name-lookup` is the repo-wide same-name table (or a lookup that found
|
|
15
|
+
* nothing), and `dynamic` is a receiver the extractor cannot type. Neither
|
|
16
|
+
* states where the target came from, so neither may drive a gate.
|
|
17
|
+
*
|
|
18
|
+
* The filter cannot be expressed on the confidence axis at all, which is the
|
|
19
|
+
* whole reason 2.2 added the field: a `name-lookup` hit and a `same-file` hit
|
|
20
|
+
* can both be INFERRED, and an AMBIGUOUS `import-specifier` edge carries a real
|
|
21
|
+
* specifier the file itself states. Filtering on `confidence` would drop the
|
|
22
|
+
* second and keep the first — exactly backwards.
|
|
23
|
+
*
|
|
24
|
+
* EVERY VERB REPORTS ITS OWN FILTERING.
|
|
25
|
+
*
|
|
26
|
+
* Each result carries a `resolved_via` histogram over the edges it CONSIDERED
|
|
27
|
+
* (accepted and rejected, separately) plus the graph's staleness state. A
|
|
28
|
+
* caller can therefore tell "no callers" from "no callers I would trust", and
|
|
29
|
+
* "dead" from "dead as of a graph 40 commits behind".
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
|
|
34
|
+
import type { GraphState } from './detect.js';
|
|
35
|
+
import type { LoadedGraph, RecommendedRead } from './query.js';
|
|
36
|
+
import { resolveSeeds } from './query.js';
|
|
37
|
+
import { sanitizeLabel } from './sanitize.js';
|
|
38
|
+
import { type CodeEdge, EXT_LANG, GUESS_RESOLVED_VIA, isStatedResolution, type ResolvedVia } from './types.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Mechanisms a gate-facing verb refuses to walk.
|
|
42
|
+
*
|
|
43
|
+
* Named as the REJECTED set rather than the accepted one deliberately: the
|
|
44
|
+
* accepted set grows every time a resolution tier lands (2.3 added `path-alias`
|
|
45
|
+
* and `psr4`; a future route table adds `route-table`), and an allowlist would
|
|
46
|
+
* have silently excluded each new mechanism until someone remembered to add it.
|
|
47
|
+
* A denylist of the two guesses is the invariant.
|
|
48
|
+
*/
|
|
49
|
+
export const NON_ACCEPTED_VIA: ReadonlySet<ResolvedVia> = GUESS_RESOLVED_VIA;
|
|
50
|
+
|
|
51
|
+
export function isAcceptedEdge(e: CodeEdge): boolean {
|
|
52
|
+
return isStatedResolution(e.resolved_via);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Relations that constitute a REFERENCE to the target, as opposed to
|
|
57
|
+
* containment of it.
|
|
58
|
+
*
|
|
59
|
+
* `member` is the one exclusion and it is load-bearing for `dead`: the build
|
|
60
|
+
* pass emits `file --member--> symbol` and `class --member--> method` for every
|
|
61
|
+
* declaration, so on the fixture that motivated this every one of the ten nodes
|
|
62
|
+
* had an accepted in-edge and `dead` returned the empty set — a verb that can
|
|
63
|
+
* never report anything, which is worse than a wrong one because it looks calm.
|
|
64
|
+
* A declaration is not a use.
|
|
65
|
+
*
|
|
66
|
+
* `tests` IS a reference: a symbol only a test imports is referenced, and
|
|
67
|
+
* whether that makes it live is the caller's judgement, not the graph's.
|
|
68
|
+
* `untested` is the verb that answers the other question.
|
|
69
|
+
*/
|
|
70
|
+
export const REFERENCE_RELATIONS: ReadonlySet<CodeEdge['relation']> = new Set<CodeEdge['relation']>([
|
|
71
|
+
'calls',
|
|
72
|
+
'imports',
|
|
73
|
+
'uses',
|
|
74
|
+
'inherits',
|
|
75
|
+
'tests',
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
export function isReferenceEdge(e: CodeEdge): boolean {
|
|
79
|
+
return REFERENCE_RELATIONS.has(e.relation);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Descending-by-count `resolved_via` histogram, ties broken alphabetically. */
|
|
83
|
+
export function viaHistogram(edges: readonly CodeEdge[]): string {
|
|
84
|
+
const counts = new Map<string, number>();
|
|
85
|
+
for (const e of edges) counts.set(e.resolved_via, (counts.get(e.resolved_via) ?? 0) + 1);
|
|
86
|
+
if (counts.size === 0) return '(none)';
|
|
87
|
+
return [...counts.entries()]
|
|
88
|
+
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))
|
|
89
|
+
.map(([via, n]) => `${via} ${n}`)
|
|
90
|
+
.join(' · ');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Common envelope: what answered, how stale it was, what was filtered. */
|
|
94
|
+
export interface VerbReport {
|
|
95
|
+
source: string;
|
|
96
|
+
state: GraphState;
|
|
97
|
+
/** `resolved_via` histogram of the edges the walk ACCEPTED. */
|
|
98
|
+
accepted_via: string;
|
|
99
|
+
/** `resolved_via` histogram of the edges the walk REFUSED to walk. */
|
|
100
|
+
rejected_via: string;
|
|
101
|
+
lines: string[];
|
|
102
|
+
recommended_reads: RecommendedRead[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readFor(g: LoadedGraph, id: string): RecommendedRead | null {
|
|
106
|
+
const n = g.byId.get(id);
|
|
107
|
+
if (!n) return null;
|
|
108
|
+
const loc = n.source_location;
|
|
109
|
+
return { path: n.source_file, lines: loc.length >= 3 ? [loc[0] as number, loc[2] as number] : null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function dedupeReads(reads: readonly (RecommendedRead | null)[]): RecommendedRead[] {
|
|
113
|
+
const m = new Map<string, RecommendedRead>();
|
|
114
|
+
for (const r of reads) {
|
|
115
|
+
if (!r) continue;
|
|
116
|
+
m.set(r.lines ? `${r.path}:${r.lines[0]}-${r.lines[1]}` : r.path, r);
|
|
117
|
+
}
|
|
118
|
+
return [...m.values()];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 3.1 impact.
|
|
122
|
+
|
|
123
|
+
/** One reverse-reachable node, with how far away it is and what reached it. */
|
|
124
|
+
export interface ReachedNode {
|
|
125
|
+
node: string;
|
|
126
|
+
/** Hop distance from the nearest seed. Never 0 — a seed is not a dependent. */
|
|
127
|
+
depth: number;
|
|
128
|
+
/** The relation of the edge that FIRST reached it, at its shallowest depth. */
|
|
129
|
+
via: CodeEdge['relation'];
|
|
130
|
+
/** That edge's mechanism. */
|
|
131
|
+
resolved_via: ResolvedVia;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ImpactResult extends VerbReport {
|
|
135
|
+
/** Node ids the diff touched that the graph knows. */
|
|
136
|
+
seeds: string[];
|
|
137
|
+
/**
|
|
138
|
+
* Touched files the graph SHOULD know and does not — a real gap.
|
|
139
|
+
*
|
|
140
|
+
* A file in a language the engine indexes (php / ts / js) that carries no
|
|
141
|
+
* node. Distinct from {@link ImpactResult.not_indexed_files}, and the
|
|
142
|
+
* distinction is the finding: an unindexed `.md` is not an unknown
|
|
143
|
+
* neighbourhood.
|
|
144
|
+
*/
|
|
145
|
+
unresolved_files: string[];
|
|
146
|
+
/** Touched files in a language the engine does not index. Not a gap. */
|
|
147
|
+
not_indexed_files: string[];
|
|
148
|
+
/** Reverse-reachable callers/dependents over accepted edges, sorted. */
|
|
149
|
+
dependents: string[];
|
|
150
|
+
/**
|
|
151
|
+
* The same set as `dependents`, carrying depth and the reaching relation.
|
|
152
|
+
*
|
|
153
|
+
* Two shapes for one set, deliberately: `dependents` is what a human reads
|
|
154
|
+
* and what the golden fixtures pin, while a consumer that has to explain
|
|
155
|
+
* WHY a node is in the set — the regression selector, 3.4 — needs the hop
|
|
156
|
+
* count and the relation, and reconstructing them by re-running the walk at
|
|
157
|
+
* increasing depths would be the same BFS three times.
|
|
158
|
+
*/
|
|
159
|
+
reached: ReachedNode[];
|
|
160
|
+
/** The subset of `dependents` declared in a test file. */
|
|
161
|
+
test_files: string[];
|
|
162
|
+
/** The edges that produced `dependents`, rendered. */
|
|
163
|
+
producing_edges: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Node ids declared in `files`, plus the file nodes themselves.
|
|
168
|
+
*
|
|
169
|
+
* `idsInFiles` deliberately excludes `kind: 'file'` nodes (it exists to seed a
|
|
170
|
+
* symbol walk), but an `imports` edge's source IS the file node, so a diff that
|
|
171
|
+
* only changes a file's import list would seed nothing without them. Added here
|
|
172
|
+
* rather than by changing `idsInFiles`, whose two existing callers want the
|
|
173
|
+
* symbol-only behaviour.
|
|
174
|
+
*/
|
|
175
|
+
export function seedsForFiles(
|
|
176
|
+
g: LoadedGraph,
|
|
177
|
+
files: readonly string[],
|
|
178
|
+
): { seeds: string[]; unresolved: string[]; not_indexed: string[] } {
|
|
179
|
+
const symbols = g.idsInFiles(files);
|
|
180
|
+
const seeds = new Set(symbols);
|
|
181
|
+
const unresolved: string[] = [];
|
|
182
|
+
const notIndexed: string[] = [];
|
|
183
|
+
for (const f of files) {
|
|
184
|
+
if (g.byId.has(f)) {
|
|
185
|
+
seeds.add(f);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (symbols.some((s) => s.startsWith(`${f}#`))) continue;
|
|
189
|
+
// A `.md`, `.json` or `.yaml` the engine never indexes is NOT an
|
|
190
|
+
// unknown neighbourhood — it is a file with no symbols, which is a fact
|
|
191
|
+
// about the language set and not a gap in the graph. Conflating the two
|
|
192
|
+
// made `selectionVerdict` refuse unconditionally for any candidate that
|
|
193
|
+
// touched a doc, and made `impact --diff` print a long
|
|
194
|
+
// "unresolved changed files" list on every normal diff. Found by an
|
|
195
|
+
// independent review, demonstrated with `touches: ['…Mailer.php',
|
|
196
|
+
// 'README.md']`.
|
|
197
|
+
if (EXT_LANG[path.extname(f).toLowerCase()] === undefined) notIndexed.push(f);
|
|
198
|
+
else unresolved.push(f);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
seeds: [...seeds].sort(),
|
|
202
|
+
unresolved: unresolved.sort(),
|
|
203
|
+
not_indexed: notIndexed.sort(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* `impact` — everything that reaches the changed symbols over ACCEPTED edges.
|
|
209
|
+
*
|
|
210
|
+
* Reverse BFS, so `dependents` are the things a change to the seeds can break,
|
|
211
|
+
* never the things the seeds themselves use. `depth` bounds the walk; the
|
|
212
|
+
* default of 2 matches `affected`'s so the two verbs do not disagree about what
|
|
213
|
+
* "reachable" means.
|
|
214
|
+
*/
|
|
215
|
+
export function impact(
|
|
216
|
+
g: LoadedGraph,
|
|
217
|
+
files: readonly string[],
|
|
218
|
+
state: GraphState,
|
|
219
|
+
depth = 2,
|
|
220
|
+
isTest: (relPath: string) => boolean = () => false,
|
|
221
|
+
): ImpactResult {
|
|
222
|
+
const { seeds, unresolved, not_indexed } = seedsForFiles(g, files);
|
|
223
|
+
const accepted: CodeEdge[] = [];
|
|
224
|
+
const rejected: CodeEdge[] = [];
|
|
225
|
+
const seen = new Set(seeds);
|
|
226
|
+
const reached = new Map<string, ReachedNode>();
|
|
227
|
+
let frontier = [...seeds];
|
|
228
|
+
for (let d = 0; d < depth && frontier.length; d += 1) {
|
|
229
|
+
const next: string[] = [];
|
|
230
|
+
for (const t of frontier) {
|
|
231
|
+
// Containment first, for the same reason `dead` does it: a
|
|
232
|
+
// `member` in-edge is the declaring file, never a dependent, and
|
|
233
|
+
// counting it as a rejected MECHANISM would misreport the filter.
|
|
234
|
+
for (const e of (g.in.get(t) ?? []).filter(isReferenceEdge)) {
|
|
235
|
+
if (!isAcceptedEdge(e)) {
|
|
236
|
+
rejected.push(e);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
accepted.push(e);
|
|
240
|
+
if (seen.has(e.source)) continue;
|
|
241
|
+
seen.add(e.source);
|
|
242
|
+
reached.set(e.source, {
|
|
243
|
+
node: e.source,
|
|
244
|
+
depth: d + 1,
|
|
245
|
+
via: e.relation,
|
|
246
|
+
resolved_via: e.resolved_via,
|
|
247
|
+
});
|
|
248
|
+
next.push(e.source);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
frontier = next;
|
|
252
|
+
}
|
|
253
|
+
const deps = [...reached.keys()].sort();
|
|
254
|
+
const fileOf = (id: string): string => g.byId.get(id)?.source_file ?? '';
|
|
255
|
+
const testFiles = [...new Set(deps.map(fileOf).filter((f) => f !== '' && isTest(f)))].sort();
|
|
256
|
+
return {
|
|
257
|
+
source: g.source,
|
|
258
|
+
state,
|
|
259
|
+
accepted_via: viaHistogram(accepted),
|
|
260
|
+
rejected_via: viaHistogram(rejected),
|
|
261
|
+
seeds,
|
|
262
|
+
unresolved_files: unresolved,
|
|
263
|
+
not_indexed_files: not_indexed,
|
|
264
|
+
dependents: deps,
|
|
265
|
+
reached: deps.map((d) => reached.get(d) as ReachedNode),
|
|
266
|
+
test_files: testFiles,
|
|
267
|
+
producing_edges: accepted.map(renderEdge).sort(),
|
|
268
|
+
lines: deps.map((d) => `${d}`),
|
|
269
|
+
recommended_reads: dedupeReads(deps.map((d) => readFor(g, d))),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function renderEdge(e: CodeEdge): string {
|
|
274
|
+
return `${e.confidence} ${sanitizeLabel(e.source)} --${e.relation}/${e.resolved_via}--> ${sanitizeLabel(e.target)}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3.2 tests-for / untested.
|
|
278
|
+
|
|
279
|
+
export interface TestsForResult extends VerbReport {
|
|
280
|
+
seeds: string[];
|
|
281
|
+
/** Test-file node ids whose `tests` edge reaches a seed, sorted. */
|
|
282
|
+
tests: string[];
|
|
283
|
+
/** The `tests` edges this walk accepted — so a caller aggregating several
|
|
284
|
+
* symbols reports the histogram of the walk the decision came from. */
|
|
285
|
+
accepted_edges: CodeEdge[];
|
|
286
|
+
/** The `tests` edges this walk refused, for the same reason. */
|
|
287
|
+
rejected_edges: CodeEdge[];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* `tests-for <symbol>` — the test files that import the symbol, or its file.
|
|
292
|
+
*
|
|
293
|
+
* Two hops of one specific shape, not a general BFS: a `tests` edge points at
|
|
294
|
+
* whatever the test file imported, which is usually the SYMBOL but is the FILE
|
|
295
|
+
* node when the test imports a module rather than a name. So a seed's own
|
|
296
|
+
* `tests` in-edges are collected, and so are its declaring file's — otherwise
|
|
297
|
+
* `tests-for someFunction` would answer empty for a test that does
|
|
298
|
+
* `import * as m from './mod.js'`.
|
|
299
|
+
*/
|
|
300
|
+
export function testsFor(g: LoadedGraph, symbol: string, state: GraphState): TestsForResult {
|
|
301
|
+
const seeds = resolveSeeds(g, symbol);
|
|
302
|
+
const targets = new Set(seeds);
|
|
303
|
+
for (const s of seeds) {
|
|
304
|
+
const f = g.byId.get(s)?.source_file;
|
|
305
|
+
if (f && g.byId.has(f)) targets.add(f);
|
|
306
|
+
}
|
|
307
|
+
const accepted: CodeEdge[] = [];
|
|
308
|
+
const rejected: CodeEdge[] = [];
|
|
309
|
+
const tests = new Set<string>();
|
|
310
|
+
for (const t of targets) {
|
|
311
|
+
for (const e of g.in.get(t) ?? []) {
|
|
312
|
+
if (e.relation !== 'tests') continue;
|
|
313
|
+
if (!isAcceptedEdge(e)) {
|
|
314
|
+
rejected.push(e);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
accepted.push(e);
|
|
318
|
+
tests.add(e.source);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const list = [...tests].sort();
|
|
322
|
+
return {
|
|
323
|
+
source: g.source,
|
|
324
|
+
state,
|
|
325
|
+
accepted_via: viaHistogram(accepted),
|
|
326
|
+
rejected_via: viaHistogram(rejected),
|
|
327
|
+
seeds,
|
|
328
|
+
tests: list,
|
|
329
|
+
accepted_edges: accepted,
|
|
330
|
+
rejected_edges: rejected,
|
|
331
|
+
lines: list,
|
|
332
|
+
recommended_reads: dedupeReads(list.map((t) => readFor(g, t))),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface UntestedResult extends VerbReport {
|
|
337
|
+
seeds: string[];
|
|
338
|
+
unresolved_files: string[];
|
|
339
|
+
not_indexed_files: string[];
|
|
340
|
+
/** Changed nodes with no `tests` edge reaching them or their file. */
|
|
341
|
+
untested: string[];
|
|
342
|
+
/** Changed nodes that do have one — reported so the ratio is readable. */
|
|
343
|
+
tested: string[];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* `untested --diff <rev>` — changed symbols no test file imports.
|
|
348
|
+
*
|
|
349
|
+
* File nodes are excluded from the answer: a file is "tested" exactly when
|
|
350
|
+
* something in it is, so listing both the file and its symbols would double
|
|
351
|
+
* count and make the ratio meaningless.
|
|
352
|
+
*/
|
|
353
|
+
export function untested(g: LoadedGraph, files: readonly string[], state: GraphState): UntestedResult {
|
|
354
|
+
const { seeds, unresolved, not_indexed } = seedsForFiles(g, files);
|
|
355
|
+
const accepted: CodeEdge[] = [];
|
|
356
|
+
const rejected: CodeEdge[] = [];
|
|
357
|
+
const tested: string[] = [];
|
|
358
|
+
const bare: string[] = [];
|
|
359
|
+
for (const s of seeds) {
|
|
360
|
+
if (g.byId.get(s)?.kind === 'file') continue;
|
|
361
|
+
const r = testsFor(g, s, state);
|
|
362
|
+
// Both histograms come from the SAME walk the decision came from —
|
|
363
|
+
// `testsFor`, which also consults the declaring FILE's edges. They used
|
|
364
|
+
// to be assembled here from the seed's own edges with `rejected_via`
|
|
365
|
+
// hardcoded to `(none)`, so the envelope could report "nothing was
|
|
366
|
+
// rejected" without measuring, and "accepted: (none)" for a symbol it
|
|
367
|
+
// had just called tested. An independent review found both.
|
|
368
|
+
accepted.push(...r.accepted_edges);
|
|
369
|
+
rejected.push(...r.rejected_edges);
|
|
370
|
+
if (r.tests.length) tested.push(s);
|
|
371
|
+
else bare.push(s);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
source: g.source,
|
|
375
|
+
state,
|
|
376
|
+
accepted_via: viaHistogram(accepted),
|
|
377
|
+
rejected_via: viaHistogram(rejected),
|
|
378
|
+
seeds,
|
|
379
|
+
unresolved_files: unresolved,
|
|
380
|
+
not_indexed_files: not_indexed,
|
|
381
|
+
untested: bare.sort(),
|
|
382
|
+
tested: tested.sort(),
|
|
383
|
+
lines: bare.sort(),
|
|
384
|
+
recommended_reads: dedupeReads(bare.map((b) => readFor(g, b))),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 3.3 dead.
|
|
389
|
+
|
|
390
|
+
/** One declared entry-point source, and whether it could be read. */
|
|
391
|
+
export interface EntryPointSource {
|
|
392
|
+
name: 'routes' | 'exports' | 'cli-registry' | 'hook-manifest' | 'allowlist';
|
|
393
|
+
/** `read` — consulted successfully. `empty` — consulted, nothing declared.
|
|
394
|
+
* `unavailable` — the graph or the tree cannot answer it at all. */
|
|
395
|
+
status: 'read' | 'empty' | 'unavailable';
|
|
396
|
+
/** Why, when `unavailable`. */
|
|
397
|
+
detail?: string;
|
|
398
|
+
/** Node ids or labels this source declares as entry points. */
|
|
399
|
+
entries: string[];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export interface DeadResult extends VerbReport {
|
|
403
|
+
sources: EntryPointSource[];
|
|
404
|
+
/** Non-null when the verb REFUSED to answer. The list is then empty. */
|
|
405
|
+
refusal: string | null;
|
|
406
|
+
dead: string[];
|
|
407
|
+
/** Entry points excluded from `dead` — the audit trail for the exclusion. */
|
|
408
|
+
excluded: string[];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Symbols with zero accepted in-edges, minus declared entry points.
|
|
413
|
+
*
|
|
414
|
+
* WHY THIS VERB CAN REFUSE.
|
|
415
|
+
*
|
|
416
|
+
* AI council 2026-09-08 (2/2 convergent, round 2 Fork 4, option 2). Step 3.3
|
|
417
|
+
* names four entry-point sources — routes, exports, the CLI registry, the hook
|
|
418
|
+
* manifest — and one of them, `exports`, cannot be evaluated from this graph at
|
|
419
|
+
* all: the extractor records no exportedness (`FileExtract` carries
|
|
420
|
+
* `nodes / rawEdges / inherits / parseError`; `CodeNode` carries no export
|
|
421
|
+
* flag). Without it, every exported-but-not-yet-imported public symbol in a
|
|
422
|
+
* library-shaped tree reports as dead — which is Risk-Register rank 4 arriving
|
|
423
|
+
* through the verb rather than around it: *"A confident false 'dead' is worse
|
|
424
|
+
* than no verb: it invites a deletion the graph cannot justify."*
|
|
425
|
+
*
|
|
426
|
+
* So the verb is fail-closed in the shape this repository already uses for
|
|
427
|
+
* `selectionVerdict` (`_lib/regression_neighbourhood.ts`): the refusal is not
|
|
428
|
+
* relaxable by an option object, only by the caller supplying the missing
|
|
429
|
+
* source (`--entry-points <file>`) or explicitly acknowledging its absence
|
|
430
|
+
* (`--accept-missing-exports`). Both are visible in a command line and in a
|
|
431
|
+
* diff; a boolean buried in an options bag is not.
|
|
432
|
+
*
|
|
433
|
+
* WHAT IT DOES NOT CLAIM.
|
|
434
|
+
*
|
|
435
|
+
* Nothing here says an unreferenced symbol should be deleted. `dead` reports
|
|
436
|
+
* "no accepted in-edge, and no declared entry point names it", which is a
|
|
437
|
+
* question worth asking and not an answer.
|
|
438
|
+
*/
|
|
439
|
+
export function dead(
|
|
440
|
+
g: LoadedGraph,
|
|
441
|
+
sources: readonly EntryPointSource[],
|
|
442
|
+
state: GraphState,
|
|
443
|
+
opts: { acceptMissingExports?: boolean } = {},
|
|
444
|
+
): DeadResult {
|
|
445
|
+
const unavailable = sources.filter((s) => s.status === 'unavailable');
|
|
446
|
+
const base = {
|
|
447
|
+
source: g.source,
|
|
448
|
+
state,
|
|
449
|
+
sources: [...sources],
|
|
450
|
+
};
|
|
451
|
+
if (unavailable.length > 0 && opts.acceptMissingExports !== true) {
|
|
452
|
+
return {
|
|
453
|
+
...base,
|
|
454
|
+
accepted_via: '(none)',
|
|
455
|
+
rejected_via: '(none)',
|
|
456
|
+
refusal:
|
|
457
|
+
`entry-point source(s) unavailable: ${unavailable
|
|
458
|
+
.map((s) => `${s.name} (${s.detail ?? 'no detail'})`)
|
|
459
|
+
.join('; ')}. ` +
|
|
460
|
+
'A symbol they would have declared an entry point is indistinguishable from a dead one, ' +
|
|
461
|
+
'so no list is produced. Supply the missing source with --entry-points <file>, or state ' +
|
|
462
|
+
'that you accept the gap with --accept-missing-exports.',
|
|
463
|
+
dead: [],
|
|
464
|
+
excluded: [],
|
|
465
|
+
lines: [],
|
|
466
|
+
recommended_reads: [],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const entries = new Set<string>();
|
|
471
|
+
for (const s of sources) for (const e of s.entries) entries.add(e);
|
|
472
|
+
const accepted: CodeEdge[] = [];
|
|
473
|
+
const rejected: CodeEdge[] = [];
|
|
474
|
+
const candidates: string[] = [];
|
|
475
|
+
const excluded: string[] = [];
|
|
476
|
+
for (const id of g.allNodeIds()) {
|
|
477
|
+
const n = g.byId.get(id);
|
|
478
|
+
if (!n || n.kind === 'file' || n.kind === 'skipped') continue;
|
|
479
|
+
// Containment is filtered BEFORE acceptance, so the histogram counts
|
|
480
|
+
// only edges the verb actually reasoned about — a `member` edge in
|
|
481
|
+
// `rejected_via` would read as a rejected mechanism, which it is not.
|
|
482
|
+
const incoming = (g.in.get(id) ?? []).filter(isReferenceEdge);
|
|
483
|
+
const acc = incoming.filter(isAcceptedEdge);
|
|
484
|
+
accepted.push(...acc);
|
|
485
|
+
rejected.push(...incoming.filter((e) => !isAcceptedEdge(e)));
|
|
486
|
+
if (acc.length > 0) continue;
|
|
487
|
+
if (entries.has(id) || entries.has(n.label)) {
|
|
488
|
+
excluded.push(id);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
candidates.push(id);
|
|
492
|
+
}
|
|
493
|
+
const list = candidates.sort();
|
|
494
|
+
return {
|
|
495
|
+
...base,
|
|
496
|
+
accepted_via: viaHistogram(accepted),
|
|
497
|
+
rejected_via: viaHistogram(rejected),
|
|
498
|
+
refusal: null,
|
|
499
|
+
dead: list,
|
|
500
|
+
excluded: excluded.sort(),
|
|
501
|
+
lines: list,
|
|
502
|
+
recommended_reads: dedupeReads(list.map((d) => readFor(g, d))),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Entry-point providers.
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* DECLARED names in a registry or manifest — quoted string literals and YAML
|
|
510
|
+
* scalar values, never every identifier in the file.
|
|
511
|
+
*
|
|
512
|
+
* The first version matched `/[A-Za-z_$][A-Za-z0-9_$]*/g` over the whole file,
|
|
513
|
+
* which on this tree yielded ~2,595 tokens including `name`, `build`, `query`,
|
|
514
|
+
* `path`, `main`, `run`, `get`, `set`, `value` and `status`. Every symbol whose
|
|
515
|
+
* LABEL happened to be one of those was silently excluded from `dead` and
|
|
516
|
+
* dumped into a list described as "the audit trail for the exclusion" — at that
|
|
517
|
+
* size unable to distinguish a declared entry point from a coincidental word.
|
|
518
|
+
* An independent review measured it.
|
|
519
|
+
*
|
|
520
|
+
* A registry and a manifest both DECLARE by value: `{ name: 'code-graph', … }`,
|
|
521
|
+
* `id: code-graph-context`. So the extraction is quoted strings plus
|
|
522
|
+
* `key: value` scalars, which is narrower by construction and still a scan
|
|
523
|
+
* rather than a parse — a provider that half-parses a TypeScript module is a
|
|
524
|
+
* provider that goes wrong quietly.
|
|
525
|
+
*
|
|
526
|
+
* Still a heuristic, and the direction is the safe one: a missed declaration
|
|
527
|
+
* makes `dead` report a real entry point (loud, and caught by the reader), a
|
|
528
|
+
* spurious one makes it miss a dead symbol (quiet). That asymmetry is why the
|
|
529
|
+
* narrowing is worth the risk of missing one.
|
|
530
|
+
*/
|
|
531
|
+
function declaredNames(text: string): string[] {
|
|
532
|
+
const out = new Set<string>();
|
|
533
|
+
for (const m of text.matchAll(/'([^'\n]{1,120})'|"([^"\n]{1,120})"|`([^`\n]{1,120})`/g)) {
|
|
534
|
+
const v = (m[1] ?? m[2] ?? m[3] ?? '').trim();
|
|
535
|
+
if (v !== '') out.add(v);
|
|
536
|
+
}
|
|
537
|
+
for (const m of text.matchAll(/^[ \t-]*[A-Za-z_][\w-]*:[ \t]+([^\n#]{1,120})$/gm)) {
|
|
538
|
+
const v = (m[1] ?? '').trim().replace(/^['"]|['"]$/g, '');
|
|
539
|
+
if (v !== '') out.add(v);
|
|
540
|
+
}
|
|
541
|
+
return [...out];
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* The four sources step 3.3 names, read from `root`.
|
|
546
|
+
*
|
|
547
|
+
* `exports` is ALWAYS `unavailable` here, with the reason stated, until the
|
|
548
|
+
* extractor records exportedness. That is the honest reading and it is what
|
|
549
|
+
* makes {@link dead} refuse by default rather than lie by default.
|
|
550
|
+
*/
|
|
551
|
+
export function repoEntryPoints(root: string): EntryPointSource[] {
|
|
552
|
+
const out: EntryPointSource[] = [];
|
|
553
|
+
|
|
554
|
+
const registry = path.join(root, 'src', 'cli', 'registry.ts');
|
|
555
|
+
if (fs.existsSync(registry)) {
|
|
556
|
+
out.push({ name: 'cli-registry', status: 'read', entries: declaredNames(fs.readFileSync(registry, 'utf-8')) });
|
|
557
|
+
} else {
|
|
558
|
+
out.push({ name: 'cli-registry', status: 'empty', entries: [] });
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const manifest = path.join(root, 'src', 'scripts', 'hook_manifest.yaml');
|
|
562
|
+
if (fs.existsSync(manifest)) {
|
|
563
|
+
out.push({ name: 'hook-manifest', status: 'read', entries: declaredNames(fs.readFileSync(manifest, 'utf-8')) });
|
|
564
|
+
} else {
|
|
565
|
+
out.push({ name: 'hook-manifest', status: 'empty', entries: [] });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Routes: no route table exists in this repository. `empty` rather than
|
|
569
|
+
// `unavailable` — consulted, nothing declared — because an absent route
|
|
570
|
+
// table is a fact about the tree, not a gap in the graph.
|
|
571
|
+
out.push({ name: 'routes', status: 'empty', entries: [] });
|
|
572
|
+
|
|
573
|
+
out.push({
|
|
574
|
+
name: 'exports',
|
|
575
|
+
status: 'unavailable',
|
|
576
|
+
detail: 'the extractor records no exportedness — CodeNode carries no export flag',
|
|
577
|
+
entries: [],
|
|
578
|
+
});
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** An operator-supplied entry-point list: one id or label per line, `#` comments. */
|
|
583
|
+
export function entryPointsFromFile(file: string): EntryPointSource {
|
|
584
|
+
const lines = fs
|
|
585
|
+
.readFileSync(file, 'utf-8')
|
|
586
|
+
.split('\n')
|
|
587
|
+
.map((l) => l.trim())
|
|
588
|
+
.filter((l) => l !== '' && !l.startsWith('#'));
|
|
589
|
+
return { name: 'allowlist', status: lines.length ? 'read' : 'empty', entries: lines };
|
|
590
|
+
}
|