ambitgraph 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (89) hide show
  1. {ambitgraph-0.2.0/ambitgraph.egg-info → ambitgraph-0.3.0}/PKG-INFO +1 -1
  2. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/graph.py +31 -6
  3. ambitgraph-0.3.0/ambit_map/langmirror.py +243 -0
  4. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/library.py +25 -9
  5. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/load.py +29 -6
  6. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/matched.py +6 -0
  7. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/model.py +34 -11
  8. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/objects.py +12 -5
  9. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/orchestrate.py +20 -13
  10. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/paths.py +33 -36
  11. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/registry.py +185 -40
  12. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/unitpage.py +57 -2
  13. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/__init__.py +1 -1
  14. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/cli.py +15 -0
  15. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/codelib.py +50 -5
  16. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/config.py +7 -4
  17. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/edges.py +326 -27
  18. ambitgraph-0.3.0/ambitgraph/langs.py +462 -0
  19. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/portal.py +4 -4
  20. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/shell.py +41 -37
  21. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/symbols.py +998 -216
  22. {ambitgraph-0.2.0 → ambitgraph-0.3.0/ambitgraph.egg-info}/PKG-INFO +1 -1
  23. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph.egg-info/SOURCES.txt +2 -0
  24. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/pyproject.toml +1 -1
  25. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/LICENSE +0 -0
  26. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/MANIFEST.in +0 -0
  27. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/README.md +0 -0
  28. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/__init__.py +0 -0
  29. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/__main__.py +0 -0
  30. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/ambit-map-enrich.md +0 -0
  31. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/ambit-mapllm-contract.md +0 -0
  32. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/cli.py +0 -0
  33. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/daemon.py +0 -0
  34. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/__init__.py +0 -0
  35. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/contract.py +0 -0
  36. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/express_pack.py +0 -0
  37. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/fastapi_pack.py +0 -0
  38. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/flask_pack.py +0 -0
  39. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/nextjs_pack.py +0 -0
  40. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/dialects/supabase_pack.py +0 -0
  41. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/export.py +0 -0
  42. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/families.py +0 -0
  43. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/island_layout.py +0 -0
  44. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/islands.py +0 -0
  45. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/judged.py +0 -0
  46. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/kitdemo.py +0 -0
  47. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/mapllm.py +0 -0
  48. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/page.py +0 -0
  49. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/serve.py +0 -0
  50. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambit_map/theme.py +0 -0
  51. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/WALKTHROUGH.md +0 -0
  52. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/__main__.py +0 -0
  53. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/cockpit.py +0 -0
  54. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/console.py +0 -0
  55. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/demo.py +0 -0
  56. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/docs_discovery.py +0 -0
  57. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/doctor.py +0 -0
  58. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/duplication.py +0 -0
  59. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/emit.py +0 -0
  60. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/engine_identity.py +0 -0
  61. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/estimate.py +0 -0
  62. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/eval.py +0 -0
  63. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/feeds.py +0 -0
  64. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/gate.py +0 -0
  65. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/gate_check.py +0 -0
  66. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/gate_server.py +0 -0
  67. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/harvest.py +0 -0
  68. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/inventory.py +0 -0
  69. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/jscalls.py +0 -0
  70. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/mds.py +0 -0
  71. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/merge.py +0 -0
  72. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/merge_jsp.py +0 -0
  73. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/parameterize.py +0 -0
  74. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/pipeline.py +0 -0
  75. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/pycalls.py +0 -0
  76. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/ratify.py +0 -0
  77. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/report.py +0 -0
  78. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/reports.py +0 -0
  79. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/sample.py +0 -0
  80. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/secretary.py +0 -0
  81. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/stages.py +0 -0
  82. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/tables.py +0 -0
  83. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/tasks.py +0 -0
  84. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph/urcode.py +0 -0
  85. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph.egg-info/dependency_links.txt +0 -0
  86. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph.egg-info/entry_points.txt +0 -0
  87. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph.egg-info/requires.txt +0 -0
  88. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/ambitgraph.egg-info/top_level.txt +0 -0
  89. {ambitgraph-0.2.0 → ambitgraph-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ambitgraph
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Read-only codebase scan: reusable units, families, roles, ownership candidates, duplication.
5
5
  Author: AmbitGraph
6
6
  License: Proprietary
@@ -49,6 +49,19 @@ from . import matched as mtc
49
49
  # 3.1 Constants
50
50
  # ---------------------------------------------------------------------------
51
51
 
52
+ # R4.5: precomputed once, a set UNION that tolerates reg.PY_LANGS/
53
+ # reg.JS_LANGS being either tuples (today's committed type) or frozensets
54
+ # (JUDGES-DIGEST.txt LENS 1, C1's named "declared future shape" -- the
55
+ # adversarial finding that KIND_READER_LANGS is already frozenset-valued,
56
+ # so the natural next step for these two groupings is the same). The
57
+ # branch below used to read `reg.PY_LANGS + reg.JS_LANGS`, tuple
58
+ # CONCATENATION: the instant either grouping becomes a frozenset that
59
+ # expression TypeErrors ("can only concatenate tuple (not frozenset) to
60
+ # tuple") in this exact no-edges unit-view render branch -- one the
61
+ # codelib byte-golden (an engine-side export) can never reach. `in a | b`
62
+ # never cares which collection type either operand is.
63
+ _PY_OR_JS_LANGS = frozenset(reg.PY_LANGS) | frozenset(reg.JS_LANGS)
64
+
52
65
  N_CAP_DEFAULT = 28
53
66
  N_CAP_MIN = 8
54
67
  N_CAP_MAX = 80
@@ -1504,11 +1517,23 @@ def _quarantine_note(drawn):
1504
1517
  # read") is false for a cross-root HELD disclosure -- the scan
1505
1518
  # read the candidate, under a root this file does not sit in.
1506
1519
  # Branch on the ref's own reason, never on a guess.
1507
- if r.reason == "cross_root_import_held":
1508
- out += ob.note(reg.cross_root_import_held_sentence(
1509
- A=drawn["id"], raw=r.raw))
1510
- else:
1511
- out += ob.note(reg.sentence("unresolved", A=drawn["id"], raw=r.raw))
1520
+ #
1521
+ # Java W1 (2026-09-02, DESIGN.md D3): two more machine-token
1522
+ # branches, same law -- "wildcard_import" and "importer_root_
1523
+ # underivable" are both false under the generic "never read"
1524
+ # sentence too (see registry.py's own comment on the two
1525
+ # sentences below for why). cross_root_import_held's own sentence
1526
+ # is reused byte-identical for a java-sourced cross-root hold --
1527
+ # its text names no language ("a matching file exists, but only
1528
+ # under a root this file does not sit in"), so it needs no java
1529
+ # variant. The token-to-sentence dispatch itself lives in
1530
+ # reg.unresolved_reason_sentence, shared with unitpage.py's
1531
+ # connections block -- one origin, so a token can never render
1532
+ # here and fall through to the generic caption there.
1533
+ sent = reg.unresolved_reason_sentence(r.reason, A=drawn["id"], raw=r.raw)
1534
+ if not sent:
1535
+ sent = reg.sentence("unresolved", A=drawn["id"], raw=r.raw)
1536
+ out += ob.note(sent)
1512
1537
  cut_phrase = reg.count_phrase(len(shown), total, reg.NOUN_UNRESOLVED)
1513
1538
  if cut_phrase:
1514
1539
  out += ob.note(reg.esc(cut_phrase))
@@ -1717,7 +1742,7 @@ def _canvas_block(model, drawn, n_cap, pinned, dialects):
1717
1742
  if not merged:
1718
1743
  body = ob.note(_library_view_link(drawn))
1719
1744
  body += ob.note(reg.esc(reg.GRAPH_NO_EDGES.format(seed=_seed_display(drawn))))
1720
- if drawn["state"] == "unit" and drawn["unit"].lang in (reg.PY_LANGS + reg.JS_LANGS):
1745
+ if drawn["state"] == "unit" and drawn["unit"].lang in _PY_OR_JS_LANGS:
1721
1746
  blind = lib._combined_blind(model.coverage)
1722
1747
  found = lib._find_blind(blind, reg.CALL_BLIND_MATCH)
1723
1748
  text = found if found else reg.CALL_BLIND_NO_REASON
@@ -0,0 +1,243 @@
1
+ """ambit_map/langmirror.py -- the product-side ASSERTED MIRROR of
2
+ ambitgraph/langs.py's declared language vocabulary (R4-DESIGN.md, v2;
3
+ CENSUS.md).
4
+
5
+ ONE file replacing five scatterings: ambit_map/orchestrate.py's admission
6
+ copy + MAPLLM_EXTRA_INCLUDE_EXT, ambit_map/registry.py's JS_LANGS/PY_LANGS/
7
+ ROUTEDNESS_LANGS, ambit_map/paths.py's KIND_READER_LANGS/_GRAMMAR_EXTS, and
8
+ ambit_map/library.py's inline blind-filter regex vocabulary. Every value
9
+ below is UNCHANGED from what its scattered original held -- this module
10
+ only gives them one address; see each field's own comment for its prior
11
+ location, and the module-docstring note below for R4's "no member
12
+ deletion" ruling.
13
+
14
+ THE WALL (tests/test_import_boundaries.py's violations helper, the
15
+ ambit_map stdlib-only rule inside rule_message, and that file's own
16
+ test_self_test_map_importing_contracts_is_caught test): ambit_map may
17
+ NEVER import ambitgraph, not even ambit_contracts, the house's own
18
+ shared-stdlib-only package -- there was never a choice of a third shared
19
+ package; the wall forecloses it outright (R4-DESIGN.md v2, "The
20
+ architecture ruling"). So this module cannot import ambitgraph/
21
+ langs.py and DERIVE its values the way ambitgraph/symbols.py, edges.py,
22
+ config.py and codelib.py now derive theirs from langs.py -- it is instead
23
+ an ASSERTED COPY: every value below is retyped by hand, checked against
24
+ langs.py's own values ONLY by a TEST (tests/test_langs_mirror_diff.py),
25
+ NEVER by any import at runtime. This is not a new exception -- the
26
+ identical both-sides-import shape is already live at
27
+ tests/test_map_serve.py's own
28
+ test_engine_include_ext_is_duplicated_by_value_and_never_drifts test
29
+ (ENGINE_DEFAULT_INCLUDE_EXT vs ScanConfig().include_ext), which this
30
+ module's own admission field below continues rather than replaces.
31
+
32
+ Unlike ambitgraph/langs.py (which imports `re` and `dataclasses` for its
33
+ own LangSpec declarations), this module is a pure, dependency-free LEAF:
34
+ no imports at all, not even stdlib (every value below is a literal). Every
35
+ consumer inside this package (orchestrate.py, paths.py, registry.py,
36
+ library.py) imports IT; it imports NONE of them, so no existing consumer
37
+ of registry.py/paths.py/library.py/orchestrate.py risks a cycle by their
38
+ now also depending, transitively, on this module.
39
+
40
+ NO MEMBER DELETION (R4-DESIGN.md v2, ruling 1, refuting the v1 draft's
41
+ "delete the dead members" plan twice over -- JUDGES-DIGEST.txt LENS 3,
42
+ finding on ruling 1): tests/test_map_registry.py's own
43
+ test_kind_key_maps_import_by_source_language_not_by_kind test already
44
+ pins kind_key("include", "import", "ts") == "include/import.js" --
45
+ deleting "ts" from JS_LANGS reds that live pin. And ambit_map/load.py's
46
+ load_run reads a unit's `lang` value VERBATIM off of symbols.json with NO
47
+ vocabulary gate at all, under this product's own shipped producer-
48
+ portability contract (ambit-mapllm-contract.md, pyproject.toml) -- an
49
+ older or foreign producer's artifact can carry a `lang` this engine's OWN
50
+ current writer (ambitgraph/symbols.py's build_file_plane) never emits
51
+ today. So every grouping below keeps its READER-TOLERANCE members: they
52
+ document what a FILE may say, not only what this engine currently
53
+ writes.
54
+ """
55
+
56
+ # ===========================================================================
57
+ # Admission -- CENSUS fact 1. Byte-duplicated from ambitgraph/langs.py's
58
+ # ENGINE_DEFAULT_INCLUDE_EXT, IN ORDER: the order is byte-load-bearing
59
+ # through ambitgraph/symbols.py's config_hash and through the cross-import
60
+ # equality pin at tests/test_map_serve.py's own
61
+ # test_engine_include_ext_is_duplicated_by_value_and_never_drifts test
62
+ # (see that engine module's own docstring for the full argument). Formerly
63
+ # hand-duplicated separately inside ambit_map/orchestrate.py;
64
+ # orchestrate.py now aliases this tuple instead of carrying its own
65
+ # literal.
66
+ # ===========================================================================
67
+ ENGINE_DEFAULT_INCLUDE_EXT = (
68
+ ".py", ".js", ".ts", ".tsx", ".jsx", ".java", ".jsp", ".jspf",
69
+ ".css", ".html", ".go", ".rb", ".php", ".cs", ".sql", ".sh",
70
+ )
71
+
72
+ # The extra extensions a mapllm run admits beyond the shared engine default
73
+ # above -- world-popularity order per the founder's list, the C/C++
74
+ # foundation first (formerly ambit_map/orchestrate.py's own
75
+ # MAPLLM_EXTRA_INCLUDE_EXT literal; this constant is not part of
76
+ # ambitgraph/langs.py at all -- it is this product's OWN scope extension,
77
+ # mirrored here only so orchestrate.py can source every admission fact
78
+ # from this one file).
79
+ MAPLLM_EXTRA_INCLUDE_EXT = (".c", ".h", ".cpp", ".hpp", ".rs")
80
+
81
+
82
+ # ===========================================================================
83
+ # Naming's product-side grouping (CENSUS fact 2's third spelling) --
84
+ # formerly ambit_map/registry.py's own JS_LANGS/PY_LANGS literals, the pair
85
+ # kind_key's src_lang branch reads. READER-TOLERANCE VOCABULARY (see module
86
+ # docstring): today ambitgraph/symbols.py's build_file_plane (the only
87
+ # writer of a file row's `lang` field) folds .jsx/.mjs/.cjs/.ts/.tsx all
88
+ # into the single tag "js" -- so "jsx"/"mjs"/"cjs"/"ts"/"tsx" can never
89
+ # appear as a REAL `lang` value THIS engine emits today. They stay in
90
+ # JS_LANGS anyway: an older or foreign producer's symbols.json is free to
91
+ # spell it differently under this product's own producer-portability
92
+ # contract, and ambit_map/load.py's load_run reads `lang` with no
93
+ # vocabulary gate at all. PY_LANGS carries no such extra member -- "py" is
94
+ # the only tag ambitgraph/langs.py's "py" LangSpec ever declares.
95
+ #
96
+ # JAVA_LANGS (Java W1, 2026-09-02, additive): the identical one-tag
97
+ # grouping shape as PY_LANGS -- "java" is the only tag ambitgraph/langs.py's
98
+ # "java" LangSpec ever declares, so this carries no reader-tolerance member
99
+ # either. Exists as its own named grouping (never folded into JS_LANGS or
100
+ # PY_LANGS) because kind_key's import branch needs a THIRD disjoint
101
+ # src_lang bucket once include/import.java joins include/import.py and
102
+ # include/import.js.
103
+ # ===========================================================================
104
+ JS_LANGS = ("js", "jsx", "mjs", "cjs", "ts", "tsx")
105
+ PY_LANGS = ("py",)
106
+ JAVA_LANGS = ("java",)
107
+
108
+ # TYPE_SYMBOL_KINDS (Java W1 repair -- the wall-legal PRODUCT-side
109
+ # mirror of ambitgraph/codelib.py's own _TYPE_SYMBOL_KINDS): the literal
110
+ # kind strings symbols.py's own class alternation emits for a Java type
111
+ # declaration ("class"/"interface"/"enum"/"record") -- "constructor" is
112
+ # DELIBERATELY not a member, the same exclusion codelib.py's own set
113
+ # states (a constructor is a member OF a type, never itself a type
114
+ # declaration). Consumed by ambit_map/unitpage.py's own class-count
115
+ # bucket so a Java interface/enum/record renders in the class(es) count
116
+ # on the unit story, not silently in "other named symbol(s)". Diffed
117
+ # against ambitgraph.codelib._TYPE_SYMBOL_KINDS by
118
+ # tests/test_langs_mirror_diff.py (DIFFED_FACT_NAMES entry
119
+ # "type_symbol_kinds") -- one origin (symbols.py's own kind literals), two
120
+ # independently-declared copies, never a cross-wall import.
121
+ TYPE_SYMBOL_KINDS = ("class", "interface", "enum", "record")
122
+
123
+ # NAMESPACE_LANGS (Java W1 repair -- the wall-legal PRODUCT-side mirror of
124
+ # ambitgraph/langs.py's own NAMESPACE_LANGS): the `lang` tags whose units
125
+ # get the java-namespace treatment (the languages whose LangSpec declares
126
+ # namespace=True). Consumed by ambit_map/unitpage.py's class-count bucket
127
+ # as the language gate on TYPE_SYMBOL_KINDS above -- symbols.py emits the
128
+ # SAME literal kinds ("interface"/"enum") for TypeScript declarations,
129
+ # which must keep rendering as "other named symbol(s)", exactly the
130
+ # boundary ambitgraph/codelib.py draws with its own is_java_namespace
131
+ # check at export. Diffed against ambitgraph.langs.NAMESPACE_LANGS by
132
+ # tests/test_langs_mirror_diff.py (DIFFED_FACT_NAMES entry
133
+ # "namespace_langs") -- one origin (the LangSpec namespace flags), two
134
+ # independently-declared copies, never a cross-wall import.
135
+ NAMESPACE_LANGS = frozenset({"java", "jsp"})
136
+
137
+
138
+ # ===========================================================================
139
+ # CENSUS fact 8 -- formerly ambit_map/registry.py's own ROUTEDNESS_LANGS
140
+ # literal. READER-TOLERANCE VOCABULARY, same law as JS_LANGS above: "jspf"
141
+ # can never appear as a real `lang` value today (ambitgraph/symbols.py's
142
+ # build_file_plane folds .jspf into the tag "jsp", never into "jspf"
143
+ # itself) but stays for the identical producer-portability reason.
144
+ # Consumed DIRECTLY by ambit_map/library.py's routedness gates
145
+ # (_routedness_is_silent, _blind_for_run) via `langm.ROUTEDNESS_LANGS` --
146
+ # no production call site reads it through registry.py anymore.
147
+ # ambit_map/registry.py still re-exports the name (`ROUTEDNESS_LANGS =
148
+ # langm.ROUTEDNESS_LANGS`), kept alive for backward compatibility and
149
+ # because tests/test_langs_value_pins.py's own R4.0 locked oracle pin
150
+ # still asserts `reg.ROUTEDNESS_LANGS` by value.
151
+ # ===========================================================================
152
+ ROUTEDNESS_LANGS = ("jsp", "jspf", "java")
153
+
154
+
155
+ # ===========================================================================
156
+ # CENSUS fact 4's product-side mirror -- formerly ambit_map/paths.py's own
157
+ # KIND_READER_LANGS literal (the full reader-coverage matrix: 19
158
+ # ALLOWED_KINDS rows -> the Unit.lang frozenset each kind's reader covers).
159
+ # Java W1 (2026-09-02): the 19th row, include/import.java, is additive --
160
+ # every pre-existing row's value is UNCHANGED. Values UNCHANGED otherwise;
161
+ # the "why" narrative for each individual row (which real reader function
162
+ # backs it, what its own language gate looks like) stays in
163
+ # ambit_map/paths.py's own module comment beside its KIND_READER_LANGS
164
+ # alias -- this module only carries the literal.
165
+ #
166
+ # _kind_applicable's THREE NAMED DIVERGENCES (ambit_map/paths.py, unmoved
167
+ # -- these are about HOW paths.py USES this table, not part of the table's
168
+ # own values, so the overriding logic and its own inputs (TERMINAL_KINDS,
169
+ # MOUNT_CAPABLE_PACKS) stay declared in paths.py, not here):
170
+ # 1. TERMINAL_KINDS (table/read|write|ddl) -- LANGUAGE-AGNOSTIC by the
171
+ # real reader's own construction (ambitgraph/tables.py's build_tables
172
+ # carries no language gate at all): applicable whenever the model
173
+ # holds any unit at all -- this table's own three TERMINAL rows are
174
+ # never actually consulted for that decision.
175
+ # 2. "reference/script" -- unconditionally False: CONFIG-gated on
176
+ # ScanConfig.asset_roots, never language-gated, and this product's own
177
+ # scan config never sets asset_roots -- so this row's frozenset below
178
+ # is never actually consulted either; kept only so this dict's key set
179
+ # still equals paths.ALLOWED_KINDS.
180
+ # 3. "route/mount" -- language overlap against this row IS consulted, but
181
+ # is NECESSARY, not SUFFICIENT: applicability additionally requires at
182
+ # least one of MOUNT_CAPABLE_PACKS (express_pack/fastapi_pack/
183
+ # flask_pack) to have DETECTED this run.
184
+ # ===========================================================================
185
+ KIND_READER_LANGS = {
186
+ "include/static": frozenset({"jsp"}),
187
+ "include/import.py": frozenset({"py"}),
188
+ "include/import.js": frozenset({"js"}),
189
+ # Java W1 (2026-09-02), additive: langs.py's java LangSpec gains
190
+ # include_lane=True this wave -- the row sits beside its import.py/
191
+ # import.js siblings, same shape, one reader language.
192
+ "include/import.java": frozenset({"java"}),
193
+ "include/require": frozenset({"js"}),
194
+ "include/dynamic": frozenset({"jsp"}),
195
+ "include/reexport": frozenset({"js"}),
196
+ "include/dynamic-import": frozenset({"js"}),
197
+ "include/member-resolved": frozenset({"js"}),
198
+ "calls": frozenset({"jsp", "java", "py", "js"}),
199
+ "reference/script": frozenset({"jsp"}),
200
+ "route/mount": frozenset({"py", "js"}),
201
+ "ui/component-use": frozenset({"js"}),
202
+ "net/fetch-route": frozenset({"js", "py"}),
203
+ "ui/component-use-matched": frozenset({"js"}),
204
+ "event/emit": frozenset({"js"}),
205
+ "table/read": frozenset({"sql", "js", "py", "jsp"}),
206
+ "table/write": frozenset({"sql", "js", "py", "jsp"}),
207
+ "table/ddl": frozenset({"sql", "js", "py", "jsp"}),
208
+ }
209
+
210
+
211
+ # ===========================================================================
212
+ # CENSUS fact 2's product-side mirror -- formerly ambit_map/paths.py's own
213
+ # _GRAMMAR_EXTS literal, a hand-kept copy of ambitgraph/symbols.py's
214
+ # _LANG_FOR_EXT key set (exactly the extensions with a real comment-
215
+ # blanking grammar). Unlike JS_LANGS/PY_LANGS/ROUTEDNESS_LANGS above, this
216
+ # set carries NO reader-tolerance members: it is a copy of the ENGINE's own
217
+ # key set (every ambitgraph/langs.py LangSpec's naming_exts, unioned), not
218
+ # a copy of a possibly-foreign `lang` value's vocabulary.
219
+ # ===========================================================================
220
+ _GRAMMAR_EXTS = frozenset({
221
+ ".jsp", ".jspf", ".java", ".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx",
222
+ ".py", ".sh", ".sql",
223
+ })
224
+
225
+
226
+ # ===========================================================================
227
+ # CENSUS fact 8's disclosure-gate vocabulary -- formerly
228
+ # ambit_map/library.py's own inline regex literal, r"\bjsp\b|\bjava\b".
229
+ # Declared as its OWN field, NEVER derived from a lang-tag grouping above
230
+ # (R4-DESIGN.md v2's explicit ruling, JUDGES-DIGEST.txt LENS 1/2 finding on
231
+ # the blind filter): this is a vocabulary of ENGLISH WORDS a disclosure
232
+ # SENTENCE spells, not a vocabulary of `lang` tag values, and the two
233
+ # differ on purpose. Joining it from ROUTEDNESS_LANGS above would either
234
+ # resurrect "jspf" into the regex (no disclosure sentence anywhere spells
235
+ # the standalone word "jspf" -- ROUTEDNESS_LANGS keeps it only for
236
+ # producer-portability tolerance, never because prose says it) or, joined
237
+ # from JS_LANGS, mint useless/dangerous words no disclosure sentence uses
238
+ # standalone this way ("ts", "cjs") or that collide with ordinary English
239
+ # ("js" inside other tokens). ambit_map/library.py's own _BLIND_LANG_FILTER
240
+ # compiles its regex from exactly these two words, `\b<word>\b` each,
241
+ # joined with `|` -- byte-identical to the pattern it replaces.
242
+ # ===========================================================================
243
+ BLIND_LANG_PROSE_WORDS = ("jsp", "java")
@@ -2,8 +2,9 @@
2
2
  screens (U1 rewrite, wave/u1-library-home): the COMPOSITION is now built on
3
3
  ambit_map/objects.py's object kit and ambit_map/families.py's human family
4
4
  taxonomy. No file I/O, no clock, no server. Imports: registry, objects,
5
- families (same package) + collections, threading (stdlib -- W7, the
6
- _resolved_mounts cache below; see that cache's own module comment).
5
+ families, and (R4) langmirror (same package) + collections, threading
6
+ (stdlib -- W7, the _resolved_mounts cache below; see that cache's own
7
+ module comment).
7
8
 
8
9
  U3 (wave/u3-graph, U3-A): the transitional chrome fence that kept the old
9
10
  `_hero`/`_block`/`_note`/... renderers alive for graph.py's own sake is
@@ -27,6 +28,7 @@ import threading
27
28
  from . import registry as reg
28
29
  from . import objects as ob
29
30
  from . import families as fam
31
+ from . import langmirror as langm
30
32
 
31
33
  # ---------------------------------------------------------------------------
32
34
  # Cap constants
@@ -66,7 +68,12 @@ JUDGED_HOME_ROWS_CAP = 24
66
68
  JUDGED_FOLD_ROWS_CAP = 8
67
69
  JUDGED_UNJOINED_CAP = 24
68
70
 
69
- _COUNTED_SYMBOL_KINDS = ("function", "method", "class")
71
+ # R4.4-product: _COUNTED_SYMBOL_KINDS deleted here (was `("function",
72
+ # "method", "class")`) -- grep-verified ZERO references tree-wide before
73
+ # deletion (JUDGES-DIGEST.txt LENS 3 finding: dead code, not a shared
74
+ # vocabulary with this module's own _fn_count or unitpage.py's own
75
+ # per-unit symbol partition -- see those two sites' own comments, both
76
+ # left untouched).
70
77
  _SLUG_OK = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
71
78
 
72
79
 
@@ -528,7 +535,7 @@ def _routedness_is_silent(model):
528
535
  servlet container's web.xml is not a thing that could exist)."""
529
536
  units = model.units.values()
530
537
  for u in units:
531
- if u.lang in reg.ROUTEDNESS_LANGS:
538
+ if u.lang in langm.ROUTEDNESS_LANGS:
532
539
  return False
533
540
  for u in units:
534
541
  r = u.routedness
@@ -889,16 +896,25 @@ def _method_note1(dialects):
889
896
  # neither is the language name this filter exists to drop. `\bjava\b`
890
897
  # never matches "javascript" (no boundary follows "java" there) or
891
898
  # "javadoc" (same reason), so only the standalone word is caught.
892
- _BLIND_LANG_FILTER = re.compile(r"\bjsp\b|\bjava\b", re.IGNORECASE)
899
+ #
900
+ # R4: the two words themselves now live at ambit_map/langmirror.py's own
901
+ # BLIND_LANG_PROSE_WORDS, declared as their OWN field rather than derived
902
+ # from ROUTEDNESS_LANGS/JS_LANGS above (that module's own comment states
903
+ # why: a vocabulary of English disclosure words, not of `lang` tags). The
904
+ # compiled pattern is byte-identical to the literal it replaces,
905
+ # r"\bjsp\b|\bjava\b".
906
+ _BLIND_LANG_FILTER = re.compile(
907
+ "|".join(r"\b%s\b" % re.escape(w) for w in langm.BLIND_LANG_PROSE_WORDS),
908
+ re.IGNORECASE)
893
909
 
894
910
 
895
911
  def _blind_for_run(cov, model):
896
912
  """`_combined_blind` (DATA/LOGIC, kept verbatim -- other call sites in
897
913
  this module still want the RAW list) always carries the engine's
898
914
  static, always-present disclosures, including the JSP/Java-scriptlet
899
- and JSP/Java-namespace sentences -- ambitgraph/symbols.py:43-46 says so
900
- explicitly ("always present, regardless of what the scanned tree
901
- happens to contain"). Review fix (major): on a run with zero jsp/jspf/
915
+ and JSP/Java-namespace sentences -- the comment above
916
+ ambitgraph.symbols.BLIND_SENTENCES says so explicitly ("always
917
+ present, regardless of what the scanned tree happens to contain"). Review fix (major): on a run with zero jsp/jspf/
902
918
  java units those two sentences named a language the page never scanned
903
919
  (chatbot-ui, flask), so THIS caller -- the only live caller of
904
920
  `_combined_blind` in the new render_library flow -- drops any sentence
@@ -907,7 +923,7 @@ def _blind_for_run(cov, model):
907
923
  callers unfiltered, since ZERO_DDL_MATCH/CALL_BLIND_MATCH need the
908
924
  full list regardless of language mix."""
909
925
  blind = _combined_blind(cov)
910
- if any(u.lang in reg.ROUTEDNESS_LANGS for u in model.units.values()):
926
+ if any(u.lang in langm.ROUTEDNESS_LANGS for u in model.units.values()):
911
927
  return blind
912
928
  return [s for s in blind if not _BLIND_LANG_FILTER.search(s)]
913
929
 
@@ -295,12 +295,35 @@ def load_run(run_dir):
295
295
  line = row.get("line") if isinstance(row.get("line"), int) else 0
296
296
  if not row.get("resolved"):
297
297
  if src in unresolved_acc:
298
- # R3/R9 (W7a, additive): "unresolved_reason" is a
299
- # NEW, optional key ambitgraph/edges.py sets only
300
- # on a Python absolute import HELD on the
301
- # cross-root tier (R9) -- absent (the row.get
302
- # default) on every ordinary unresolved row,
303
- # exactly as before this wave.
298
+ # R3/R9 (W7a, additive) + Java W1 (D3, "machine
299
+ # tokens are interfaces" law; load.py repair):
300
+ # "unresolved_reason" is an OPTIONAL
301
+ # key ambitgraph/edges.py sets on an unresolved
302
+ # row it can name a SPECIFIC held/refused reason
303
+ # for -- absent (the row.get default) on every
304
+ # ORDINARY unresolved row (a target this scan
305
+ # genuinely never read at all), exactly as
306
+ # before R9. THE THREE TOKENS this key actually
307
+ # carries today, this being the one place any of
308
+ # them is parsed off the wire: "cross_root_
309
+ # import_held" (a Python absolute import R9-held
310
+ # on the cross-root tier, OR a Java import
311
+ # HELD the same way -- java's tier reuses this
312
+ # SAME token, never a java-named twin, per D3);
313
+ # "wildcard_import" (a Java plain wildcard import
314
+ # -- `import com.foo.*;` names a package, never a
315
+ # single file, so it is never even tried against
316
+ # the resolver); "importer_root_underivable" (a
317
+ # Java file whose OWN package root could not be
318
+ # derived -- a default-package file, or one whose
319
+ # directory does not match its `package`
320
+ # declaration's suffix -- so none of its imports
321
+ # is tried against either resolution tier at
322
+ # all). registry.py's unresolved_reason_sentence
323
+ # is the dispatch that maps these three literals
324
+ # to their sentences, for every caption site
325
+ # (graph.py's _quarantine_note, unitpage.py's
326
+ # connections block).
304
327
  reason = row.get("unresolved_reason")
305
328
  unresolved_acc[src].append(
306
329
  model.UnresolvedRef(
@@ -24,6 +24,12 @@ from . import library as lib
24
24
  from . import model as model_mod
25
25
 
26
26
  _DEFINER_KINDS = ("function", "class")
27
+ # Java W1 repair (2026-09-02): DELIBERATELY unwidened this wave --
28
+ # symbols.py's D2 closure now also emits "interface"/"enum"/"record", but
29
+ # whether any of those should ever be a call-binding DEFINER is roadmap
30
+ # material (JAVA-W1-DESIGN.md v2's own "out of W1": "call-binding via
31
+ # imports"), not decided here. Left exactly as it was pre-wave; not an
32
+ # oversight.
27
33
 
28
34
 
29
35
  def _collapse_matched_edges(edges):
@@ -47,17 +47,40 @@ class UnresolvedRef:
47
47
  # ruling on the verify pass's finding [2], additive): "" (the
48
48
  # default) is byte-identical to every pre-W7a UnresolvedRef -- every
49
49
  # existing keyword-only construction site stays valid unchanged.
50
- # "cross_root_import_held" marks a Python absolute import HELD on
51
- # the cross-root tier (ambitgraph/edges.py's R9 evidence ladder --
52
- # neither '' nor the importer's own derived root resolved it, but
53
- # some OTHER one did; carried on the edges row as "unresolved_
54
- # reason"), distinct from an ordinary external/never-read import --
55
- # ambit_map/load.py sets it, ambit_map/graph.py's _quarantine_note
56
- # reads it to pick the correct sentence (never the false "this scan
57
- # never read" one for a target the scan genuinely read). (Was
58
- # "ambiguous_import_roots" pre-R9 -- renamed along with the ladder
59
- # change; see edges.py's own R9 docstring for why the old name no
60
- # longer names a distinct state.)
50
+ # "cross_root_import_held" marks an import HELD on the cross-root
51
+ # tier of its language's own evidence ladder (ambitgraph/edges.py's
52
+ # R9 ladder for Python -- neither '' nor the importer's own derived
53
+ # root resolved it, but some OTHER one did; carried on the edges row
54
+ # as "unresolved_reason"), distinct from an ordinary external/
55
+ # never-read import -- ambit_map/load.py sets it, and the caption
56
+ # sites (graph.py's _quarantine_note, unitpage.py's connections
57
+ # block) read it through registry.py's unresolved_reason_sentence to
58
+ # pick the correct sentence (never the false "this scan never read"
59
+ # one for a target the scan genuinely read). Java W1 repair (2026-09-02): this token is NOT
60
+ # Python-only -- D3's java ladder tier (ii) REUSES this exact same
61
+ # token, byte-identically, for a java-sourced cross-root hold, rather
62
+ # than minting a java-named twin (the sentence text itself already
63
+ # names no language, by deliberate choice -- see registry.py's
64
+ # CROSS_ROOT_IMPORT_HELD_SENTENCE); this comment used to describe the
65
+ # token as if only a Python import could ever carry it, which stopped
66
+ # being true the moment D3 landed. (Was "ambiguous_import_roots"
67
+ # pre-R9 -- renamed along with the ladder change; see edges.py's own
68
+ # R9 docstring for why the old name no longer names a distinct
69
+ # state.) Java W1 (2026-09-02, DESIGN.md D3,
70
+ # "machine tokens are interfaces" law), additive, same posture as
71
+ # cross_root_import_held -- each ships WITH its own graph.py branch
72
+ # and registry.py sentence, never a free-text reason (a free-text
73
+ # reason falls through to the generic "never read" sentence, which is
74
+ # false for a target this scan genuinely did read):
75
+ # "wildcard_import" marks a Java wildcard import (`import com.foo.*;`)
76
+ # -- a wildcard names a PACKAGE, never a single file, so it is refused
77
+ # and counted rather than guessed at one target.
78
+ # "importer_root_underivable" marks an import whose OWN importer file
79
+ # has no derivable package root (a default-package file, or one whose
80
+ # directory does not match its `package` declaration's suffix) -- its
81
+ # imports are never tested against another file's derived root at
82
+ # all, for a reason that has nothing to do with whether some target
83
+ # file was ever read.
61
84
  reason: str = ""
62
85
 
63
86
 
@@ -797,12 +797,16 @@ def sub_head(text):
797
797
 
798
798
  def sym_table(headers, rows):
799
799
  """headers = iterable of column header strings. rows = iterable of
800
- (anchor_id, href, name, kind, line, signature, doc[, clipped]) --
801
- `href` falsy renders the name as plain text, never an anchor; a
800
+ (anchor_id, href, name, kind, line, signature, doc[, clipped[, vis]])
801
+ -- `href` falsy renders the name as plain text, never an anchor; a
802
802
  trailing 8th `clipped` element (bool) appends the CLIPPED_LABEL chip
803
- inside the signature cell. EVERY cell is escaped inside this emitter --
804
- there is no RAW slot at all. Stamped `data-amg-object="sym-table"` on
805
- the `.cl-scroll` root -- it IS a container root."""
803
+ inside the signature cell; a trailing `vis` element after it (a
804
+ display word the caller already chose, e.g. reg.sym_visibility_label's)
805
+ appends a scope chip stating the symbol's captured visibility in the
806
+ same cell, omitted entirely when falsy. EVERY cell is escaped inside this
807
+ emitter -- there is no RAW slot at all. Stamped
808
+ `data-amg-object="sym-table"` on the `.cl-scroll` root -- it IS a
809
+ container root."""
806
810
  parts = ['<div class="cl-scroll" data-amg-object="sym-table"><table class="cl-table"><thead><tr>']
807
811
  for h in headers:
808
812
  parts.append('<th>' + esc(h) + '</th>')
@@ -810,11 +814,14 @@ def sym_table(headers, rows):
810
814
  for row in rows:
811
815
  anchor_id, href, name, kind, line, signature, doc = row[:7]
812
816
  clipped = row[7] if len(row) > 7 else False
817
+ vis = row[8] if len(row) > 8 else ""
813
818
  name_html = ('<a href="' + esc(_safe_href(href)) + '">' + esc(name) + '</a>'
814
819
  if href else esc(name))
815
820
  sig_html = esc(signature)
816
821
  if clipped:
817
822
  sig_html += dchip("scope", CLIPPED_LABEL)
823
+ if vis:
824
+ sig_html += dchip("scope", vis)
818
825
  parts.append(
819
826
  '<tr><td class="cl-fn" id="' + esc(anchor_id) + '">' + name_html + '</td>'
820
827
  '<td>' + esc(kind) + '</td><td>' + esc(line) + '</td>'
@@ -2,9 +2,10 @@
2
2
  rotate its runs, and speak the greeting. NEW for K4.
3
3
 
4
4
  Imports: stdlib only (`hashlib, json, os, re, shutil, subprocess, sys, time,
5
- webbrowser`, `datetime`, `pathlib`) plus `from . import registry as reg` and
6
- `from . import load` -- never `ambitgraph`, never any dynamic import, never
7
- eval/exec (the absolute wall, tests/test_import_boundaries.py:83-102).
5
+ webbrowser`, `datetime`, `pathlib`) plus `from . import registry as reg`,
6
+ `from . import load` and (R4) `from . import langmirror as langm` -- never
7
+ `ambitgraph`, never any dynamic import, never eval/exec (the absolute
8
+ wall, test_import_boundaries.py's own rule_message wall rule).
8
9
 
9
10
  `serve` is imported LAZILY, inside run_scan() only: serve.py imports this
10
11
  module (`orchestrate as orch`) at its own top level, so a module-level
@@ -33,6 +34,7 @@ from . import load
33
34
  from . import paths
34
35
  from . import graph
35
36
  from . import islands
37
+ from . import langmirror as langm
36
38
  from .dialects import contract as dcontract
37
39
 
38
40
  # ---------------------------------------------------------------------------
@@ -90,14 +92,18 @@ PATIENCE_BUDGET_SECONDS = 3600
90
92
  ENVELOPE_FILES = 750
91
93
  MEASURED_SECONDS_PER_FILE = 0.080
92
94
 
93
- # duplicated BY VALUE from ambitgraph/config.py:32-35 -- ambit_map imports no
94
- # engine code, the same posture ENGINE_DEFAULT_EXCLUDE_DIRS already takes.
95
- # tests/test_map_serve.py asserts this still equals ScanConfig().include_ext,
96
- # so the two can never silently drift.
97
- ENGINE_DEFAULT_INCLUDE_EXT = (
98
- ".py", ".js", ".ts", ".tsx", ".jsx", ".java", ".jsp", ".jspf",
99
- ".css", ".html", ".go", ".rb", ".php", ".cs", ".sql", ".sh",
100
- )
95
+ # R4: sourced from ambit_map/langmirror.py, the product-side mirror of
96
+ # ambitgraph/langs.py -- the engine truth this tuple duplicates by value
97
+ # now lives at ambitgraph/langs.py's own ENGINE_DEFAULT_INCLUDE_EXT
98
+ # (citation-rot fix, R4-DESIGN.md v2 ruling 3: this comment used to point
99
+ # at config.py:32-35 while ScanConfig's real default had drifted to
100
+ # different lines -- a one-line pointer, never a line number, so the
101
+ # comment cannot rot the same way again). ambit_map still imports no
102
+ # engine code -- langmirror.py is an ASSERTED COPY, the same posture
103
+ # ENGINE_DEFAULT_EXCLUDE_DIRS already takes. tests/test_map_serve.py
104
+ # asserts this still equals ScanConfig().include_ext, so the two can never
105
+ # silently drift.
106
+ ENGINE_DEFAULT_INCLUDE_EXT = langm.ENGINE_DEFAULT_INCLUDE_EXT
101
107
  # C2 (mapllm wave): the extra extensions a mapllm run admits beyond the
102
108
  # shared engine default above -- world-popularity order per the founder's
103
109
  # list, the C/C++ foundation first. `map`'s and `scan`'s own defaults stay
@@ -105,8 +111,9 @@ ENGINE_DEFAULT_INCLUDE_EXT = (
105
111
  # stays green, unchanged): this list only ever widens a mapllm run's OWN
106
112
  # generated product config (product_config()/write_product_config()/
107
113
  # plan()'s own extra_include_ext argument, below), never
108
- # ENGINE_DEFAULT_INCLUDE_EXT itself.
109
- MAPLLM_EXTRA_INCLUDE_EXT = (".c", ".h", ".cpp", ".hpp", ".rs")
114
+ # ENGINE_DEFAULT_INCLUDE_EXT itself. R4: sourced from langmirror.py, same
115
+ # posture as the admission tuple above.
116
+ MAPLLM_EXTRA_INCLUDE_EXT = langm.MAPLLM_EXTRA_INCLUDE_EXT
110
117
  FAIL_TAIL_CHARS = 1200
111
118
  DIALECT_FILENAME = "product-dialect.json"
112
119
  # W5 D-W5-D -- the per-run path-plane data artifact, written beside