@oriro/orirocli 0.1.9 → 0.1.12

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 (166) hide show
  1. package/README.md +16 -18
  2. package/dist/cli.js +4776 -2964
  3. package/package.json +2 -2
  4. package/skills/craft/ai-engineering/SKILL.md +2 -2
  5. package/skills/graphify/SKILL.md +0 -619
  6. package/skills/graphify/__init__.py +0 -28
  7. package/skills/graphify/__main__.py +0 -4582
  8. package/skills/graphify/affected.py +0 -154
  9. package/skills/graphify/always_on/agents-md.md +0 -12
  10. package/skills/graphify/always_on/antigravity-rules.md +0 -14
  11. package/skills/graphify/always_on/claude-md.md +0 -9
  12. package/skills/graphify/always_on/gemini-md.md +0 -9
  13. package/skills/graphify/always_on/kiro-steering.md +0 -5
  14. package/skills/graphify/always_on/vscode-instructions.md +0 -17
  15. package/skills/graphify/analyze.py +0 -724
  16. package/skills/graphify/benchmark.py +0 -155
  17. package/skills/graphify/build.py +0 -487
  18. package/skills/graphify/cache.py +0 -417
  19. package/skills/graphify/callflow_html.py +0 -2020
  20. package/skills/graphify/cluster.py +0 -272
  21. package/skills/graphify/command-kilo.md +0 -15
  22. package/skills/graphify/dedup.py +0 -429
  23. package/skills/graphify/detect.py +0 -1379
  24. package/skills/graphify/diagnostics.py +0 -390
  25. package/skills/graphify/export.py +0 -1408
  26. package/skills/graphify/extract.py +0 -11570
  27. package/skills/graphify/global_graph.py +0 -159
  28. package/skills/graphify/google_workspace.py +0 -223
  29. package/skills/graphify/hooks.py +0 -457
  30. package/skills/graphify/ingest.py +0 -331
  31. package/skills/graphify/llm.py +0 -1896
  32. package/skills/graphify/manifest.py +0 -4
  33. package/skills/graphify/mcp_ingest.py +0 -392
  34. package/skills/graphify/multigraph_compat.py +0 -212
  35. package/skills/graphify/pg_introspect.py +0 -142
  36. package/skills/graphify/prs.py +0 -748
  37. package/skills/graphify/querylog.py +0 -70
  38. package/skills/graphify/report.py +0 -218
  39. package/skills/graphify/scip_ingest.py +0 -363
  40. package/skills/graphify/security.py +0 -336
  41. package/skills/graphify/semantic_cleanup.py +0 -319
  42. package/skills/graphify/serve.py +0 -1309
  43. package/skills/graphify/skill-aider.md +0 -1246
  44. package/skills/graphify/skill-amp.md +0 -613
  45. package/skills/graphify/skill-claw.md +0 -616
  46. package/skills/graphify/skill-codex.md +0 -613
  47. package/skills/graphify/skill-copilot.md +0 -616
  48. package/skills/graphify/skill-devin.md +0 -1372
  49. package/skills/graphify/skill-droid.md +0 -613
  50. package/skills/graphify/skill-kilo.md +0 -625
  51. package/skills/graphify/skill-kiro.md +0 -615
  52. package/skills/graphify/skill-opencode.md +0 -608
  53. package/skills/graphify/skill-pi.md +0 -615
  54. package/skills/graphify/skill-trae.md +0 -614
  55. package/skills/graphify/skill-vscode.md +0 -612
  56. package/skills/graphify/skill-windows.md +0 -651
  57. package/skills/graphify/skills/amp/references/add-watch.md +0 -56
  58. package/skills/graphify/skills/amp/references/exports.md +0 -71
  59. package/skills/graphify/skills/amp/references/extraction-spec.md +0 -68
  60. package/skills/graphify/skills/amp/references/github-and-merge.md +0 -46
  61. package/skills/graphify/skills/amp/references/hooks.md +0 -33
  62. package/skills/graphify/skills/amp/references/query.md +0 -249
  63. package/skills/graphify/skills/amp/references/transcribe.md +0 -48
  64. package/skills/graphify/skills/amp/references/update.md +0 -179
  65. package/skills/graphify/skills/claude/references/add-watch.md +0 -56
  66. package/skills/graphify/skills/claude/references/exports.md +0 -71
  67. package/skills/graphify/skills/claude/references/extraction-spec.md +0 -68
  68. package/skills/graphify/skills/claude/references/github-and-merge.md +0 -46
  69. package/skills/graphify/skills/claude/references/hooks.md +0 -33
  70. package/skills/graphify/skills/claude/references/query.md +0 -103
  71. package/skills/graphify/skills/claude/references/transcribe.md +0 -48
  72. package/skills/graphify/skills/claude/references/update.md +0 -179
  73. package/skills/graphify/skills/claw/references/add-watch.md +0 -56
  74. package/skills/graphify/skills/claw/references/exports.md +0 -71
  75. package/skills/graphify/skills/claw/references/extraction-spec.md +0 -29
  76. package/skills/graphify/skills/claw/references/github-and-merge.md +0 -46
  77. package/skills/graphify/skills/claw/references/hooks.md +0 -33
  78. package/skills/graphify/skills/claw/references/query.md +0 -249
  79. package/skills/graphify/skills/claw/references/transcribe.md +0 -48
  80. package/skills/graphify/skills/claw/references/update.md +0 -179
  81. package/skills/graphify/skills/codex/references/add-watch.md +0 -56
  82. package/skills/graphify/skills/codex/references/exports.md +0 -71
  83. package/skills/graphify/skills/codex/references/extraction-spec.md +0 -29
  84. package/skills/graphify/skills/codex/references/github-and-merge.md +0 -46
  85. package/skills/graphify/skills/codex/references/hooks.md +0 -33
  86. package/skills/graphify/skills/codex/references/query.md +0 -249
  87. package/skills/graphify/skills/codex/references/transcribe.md +0 -48
  88. package/skills/graphify/skills/codex/references/update.md +0 -179
  89. package/skills/graphify/skills/copilot/references/add-watch.md +0 -56
  90. package/skills/graphify/skills/copilot/references/exports.md +0 -71
  91. package/skills/graphify/skills/copilot/references/extraction-spec.md +0 -68
  92. package/skills/graphify/skills/copilot/references/github-and-merge.md +0 -46
  93. package/skills/graphify/skills/copilot/references/hooks.md +0 -33
  94. package/skills/graphify/skills/copilot/references/query.md +0 -249
  95. package/skills/graphify/skills/copilot/references/transcribe.md +0 -48
  96. package/skills/graphify/skills/copilot/references/update.md +0 -179
  97. package/skills/graphify/skills/droid/references/add-watch.md +0 -56
  98. package/skills/graphify/skills/droid/references/exports.md +0 -71
  99. package/skills/graphify/skills/droid/references/extraction-spec.md +0 -68
  100. package/skills/graphify/skills/droid/references/github-and-merge.md +0 -46
  101. package/skills/graphify/skills/droid/references/hooks.md +0 -33
  102. package/skills/graphify/skills/droid/references/query.md +0 -249
  103. package/skills/graphify/skills/droid/references/transcribe.md +0 -48
  104. package/skills/graphify/skills/droid/references/update.md +0 -179
  105. package/skills/graphify/skills/kilo/references/add-watch.md +0 -56
  106. package/skills/graphify/skills/kilo/references/exports.md +0 -71
  107. package/skills/graphify/skills/kilo/references/extraction-spec.md +0 -68
  108. package/skills/graphify/skills/kilo/references/github-and-merge.md +0 -46
  109. package/skills/graphify/skills/kilo/references/hooks.md +0 -33
  110. package/skills/graphify/skills/kilo/references/query.md +0 -249
  111. package/skills/graphify/skills/kilo/references/transcribe.md +0 -48
  112. package/skills/graphify/skills/kilo/references/update.md +0 -179
  113. package/skills/graphify/skills/kiro/references/add-watch.md +0 -56
  114. package/skills/graphify/skills/kiro/references/exports.md +0 -71
  115. package/skills/graphify/skills/kiro/references/extraction-spec.md +0 -29
  116. package/skills/graphify/skills/kiro/references/github-and-merge.md +0 -46
  117. package/skills/graphify/skills/kiro/references/hooks.md +0 -33
  118. package/skills/graphify/skills/kiro/references/query.md +0 -249
  119. package/skills/graphify/skills/kiro/references/transcribe.md +0 -48
  120. package/skills/graphify/skills/kiro/references/update.md +0 -179
  121. package/skills/graphify/skills/opencode/references/add-watch.md +0 -56
  122. package/skills/graphify/skills/opencode/references/exports.md +0 -71
  123. package/skills/graphify/skills/opencode/references/extraction-spec.md +0 -68
  124. package/skills/graphify/skills/opencode/references/github-and-merge.md +0 -46
  125. package/skills/graphify/skills/opencode/references/hooks.md +0 -33
  126. package/skills/graphify/skills/opencode/references/query.md +0 -249
  127. package/skills/graphify/skills/opencode/references/transcribe.md +0 -48
  128. package/skills/graphify/skills/opencode/references/update.md +0 -179
  129. package/skills/graphify/skills/pi/references/add-watch.md +0 -56
  130. package/skills/graphify/skills/pi/references/exports.md +0 -71
  131. package/skills/graphify/skills/pi/references/extraction-spec.md +0 -29
  132. package/skills/graphify/skills/pi/references/github-and-merge.md +0 -46
  133. package/skills/graphify/skills/pi/references/hooks.md +0 -33
  134. package/skills/graphify/skills/pi/references/query.md +0 -249
  135. package/skills/graphify/skills/pi/references/transcribe.md +0 -48
  136. package/skills/graphify/skills/pi/references/update.md +0 -179
  137. package/skills/graphify/skills/trae/references/add-watch.md +0 -56
  138. package/skills/graphify/skills/trae/references/exports.md +0 -71
  139. package/skills/graphify/skills/trae/references/extraction-spec.md +0 -68
  140. package/skills/graphify/skills/trae/references/github-and-merge.md +0 -46
  141. package/skills/graphify/skills/trae/references/hooks.md +0 -35
  142. package/skills/graphify/skills/trae/references/query.md +0 -249
  143. package/skills/graphify/skills/trae/references/transcribe.md +0 -48
  144. package/skills/graphify/skills/trae/references/update.md +0 -179
  145. package/skills/graphify/skills/vscode/references/add-watch.md +0 -56
  146. package/skills/graphify/skills/vscode/references/exports.md +0 -71
  147. package/skills/graphify/skills/vscode/references/extraction-spec.md +0 -68
  148. package/skills/graphify/skills/vscode/references/github-and-merge.md +0 -46
  149. package/skills/graphify/skills/vscode/references/hooks.md +0 -33
  150. package/skills/graphify/skills/vscode/references/query.md +0 -249
  151. package/skills/graphify/skills/vscode/references/transcribe.md +0 -48
  152. package/skills/graphify/skills/vscode/references/update.md +0 -179
  153. package/skills/graphify/skills/windows/references/add-watch.md +0 -56
  154. package/skills/graphify/skills/windows/references/exports.md +0 -71
  155. package/skills/graphify/skills/windows/references/extraction-spec.md +0 -68
  156. package/skills/graphify/skills/windows/references/github-and-merge.md +0 -46
  157. package/skills/graphify/skills/windows/references/hooks.md +0 -33
  158. package/skills/graphify/skills/windows/references/query.md +0 -249
  159. package/skills/graphify/skills/windows/references/transcribe.md +0 -48
  160. package/skills/graphify/skills/windows/references/update.md +0 -179
  161. package/skills/graphify/symbol_resolution.py +0 -538
  162. package/skills/graphify/transcribe.py +0 -184
  163. package/skills/graphify/tree_html.py +0 -582
  164. package/skills/graphify/validate.py +0 -72
  165. package/skills/graphify/watch.py +0 -898
  166. package/skills/graphify/wiki.py +0 -282
@@ -1,272 +0,0 @@
1
- """Community detection on NetworkX graphs. Uses Leiden (graspologic) if available, falls back to Louvain (networkx). Splits oversized communities. Returns cohesion scores."""
2
- from __future__ import annotations
3
- import contextlib
4
- import inspect
5
- import io
6
- import json
7
- import sys
8
- import networkx as nx
9
-
10
-
11
- def _suppress_output():
12
- """Context manager to suppress stdout/stderr during library calls.
13
-
14
- graspologic's leiden() emits ANSI escape sequences (progress bars,
15
- colored warnings) that corrupt PowerShell 5.1's scroll buffer on
16
- Windows (see issue #19). Redirecting stdout/stderr to devnull during
17
- the call prevents this without losing any graphify output.
18
- """
19
- return contextlib.redirect_stdout(io.StringIO())
20
-
21
-
22
- def _partition(G: nx.Graph, resolution: float = 1.0) -> dict[str, int]:
23
- """Run community detection. Returns {node_id: community_id}.
24
-
25
- Tries Leiden (graspologic) first — best quality.
26
- Falls back to Louvain (built into networkx) if graspologic is not installed.
27
-
28
- resolution > 1.0 → more, smaller communities.
29
- resolution < 1.0 → fewer, larger communities.
30
-
31
- Output from graspologic is suppressed to prevent ANSI escape codes
32
- from corrupting terminal scroll buffers on Windows PowerShell 5.1.
33
- """
34
- stable = nx.Graph()
35
- stable.add_nodes_from(sorted(G.nodes(), key=str))
36
- edge_rows = sorted(
37
- G.edges(data=True),
38
- key=lambda row: (
39
- str(row[0]),
40
- str(row[1]),
41
- json.dumps(row[2], sort_keys=True, ensure_ascii=False, default=str),
42
- ),
43
- )
44
- for src, tgt, attrs in edge_rows:
45
- stable.add_edge(src, tgt, **attrs)
46
-
47
- try:
48
- from graspologic.partition import leiden
49
- lsig = inspect.signature(leiden).parameters
50
- kwargs: dict = {}
51
- if "random_seed" in lsig:
52
- kwargs["random_seed"] = 42
53
- if "trials" in lsig:
54
- kwargs["trials"] = 1
55
- if "resolution" in lsig:
56
- kwargs["resolution"] = resolution
57
- # Suppress graspologic output to prevent ANSI escape codes from
58
- # corrupting PowerShell 5.1 scroll buffer (issue #19)
59
- old_stderr = sys.stderr
60
- try:
61
- sys.stderr = io.StringIO()
62
- with _suppress_output():
63
- result = leiden(stable, **kwargs)
64
- finally:
65
- sys.stderr = old_stderr
66
- return result
67
- except ImportError:
68
- pass
69
-
70
- # Fallback: networkx louvain (available since networkx 2.7).
71
- # Inspect kwargs to stay compatible across NetworkX versions — max_level
72
- # was added in a later release and prevents hangs on large sparse graphs.
73
- kwargs: dict = {"seed": 42, "threshold": 1e-4, "resolution": resolution}
74
- if "max_level" in inspect.signature(nx.community.louvain_communities).parameters:
75
- kwargs["max_level"] = 10
76
- communities = nx.community.louvain_communities(stable, **kwargs)
77
- return {node: cid for cid, nodes in enumerate(communities) for node in nodes}
78
-
79
-
80
- _MAX_COMMUNITY_FRACTION = 0.25 # communities larger than 25% of graph get split
81
- _MIN_SPLIT_SIZE = 10 # only split if community has at least this many nodes
82
- _COHESION_SPLIT_THRESHOLD = 0.05 # re-split communities with cohesion below this
83
- _COHESION_SPLIT_MIN_SIZE = 50 # only cohesion-split if community has at least this many nodes
84
-
85
-
86
- def cluster(
87
- G: nx.Graph,
88
- resolution: float = 1.0,
89
- exclude_hubs_percentile: float | None = None,
90
- ) -> dict[int, list[str]]:
91
- """Run Leiden community detection. Returns {community_id: [node_ids]}.
92
-
93
- Community IDs are stable across runs: 0 = largest community after splitting.
94
- Oversized communities (> 25% of graph nodes, min 10) are split by running
95
- a second Leiden pass on the subgraph.
96
-
97
- Accepts directed or undirected graphs. DiGraphs are converted to undirected
98
- internally since Louvain/Leiden require undirected input.
99
-
100
- resolution: passed to Leiden/Louvain. >1.0 = more smaller communities,
101
- <1.0 = fewer larger communities. Default 1.0.
102
- exclude_hubs_percentile: if set (0-100), nodes whose degree exceeds this
103
- percentile are excluded from partitioning and reattached to their
104
- majority-vote neighbour community afterwards. Useful for staging/utility
105
- super-hubs that inflate god-node rankings (#919).
106
- """
107
- if G.number_of_nodes() == 0:
108
- return {}
109
- if G.is_directed():
110
- G = G.to_undirected()
111
- if G.number_of_edges() == 0:
112
- return {i: [n] for i, n in enumerate(sorted(G.nodes))}
113
-
114
- # Compute hub exclusion set before removing anything so degree is based on full graph
115
- hub_nodes: set[str] = set()
116
- if exclude_hubs_percentile is not None:
117
- degrees = sorted(d for _, d in G.degree())
118
- if degrees:
119
- idx = max(0, int(len(degrees) * exclude_hubs_percentile / 100) - 1)
120
- threshold = degrees[idx]
121
- hub_nodes = {n for n, d in G.degree() if d > threshold}
122
-
123
- # Leiden warns and drops isolates - handle them separately
124
- # Also exclude hub nodes from partitioning so they don't pull unrelated
125
- # subsystems into the same community
126
- excluded = hub_nodes
127
- isolates = [n for n in G.nodes() if G.degree(n) == 0 and n not in excluded]
128
- connected_nodes = [n for n in G.nodes() if G.degree(n) > 0 and n not in excluded]
129
- connected = G.subgraph(connected_nodes)
130
-
131
- raw: dict[int, list[str]] = {}
132
- if connected.number_of_nodes() > 0:
133
- partition = _partition(connected, resolution=resolution)
134
- for node, cid in partition.items():
135
- raw.setdefault(cid, []).append(node)
136
-
137
- # Each isolate becomes its own single-node community
138
- next_cid = max(raw.keys(), default=-1) + 1
139
- for node in isolates:
140
- raw[next_cid] = [node]
141
- next_cid += 1
142
-
143
- # Reattach excluded hubs by majority-vote neighbour community
144
- if hub_nodes:
145
- node_community: dict[str, int] = {n: cid for cid, nodes in raw.items() for n in nodes}
146
- for hub in sorted(hub_nodes):
147
- votes: dict[int, int] = {}
148
- for nb in G.neighbors(hub):
149
- cid = node_community.get(nb)
150
- if cid is not None:
151
- votes[cid] = votes.get(cid, 0) + 1
152
- if votes:
153
- best = min(votes, key=lambda c: (-votes[c], c))
154
- raw.setdefault(best, []).append(hub)
155
- node_community[hub] = best
156
- else:
157
- raw[next_cid] = [hub]
158
- node_community[hub] = next_cid
159
- next_cid += 1
160
-
161
- # Split oversized communities
162
- max_size = max(_MIN_SPLIT_SIZE, int(G.number_of_nodes() * _MAX_COMMUNITY_FRACTION))
163
- final_communities: list[list[str]] = []
164
- for nodes in raw.values():
165
- if len(nodes) > max_size:
166
- final_communities.extend(_split_community(G, nodes))
167
- else:
168
- final_communities.append(nodes)
169
-
170
- # Second pass: re-split low-cohesion communities caused by doc-hub nodes
171
- # that bridge otherwise-unrelated subsystems (e.g. CLAUDE.md connected to everything).
172
- second_pass: list[list[str]] = []
173
- for nodes in final_communities:
174
- if len(nodes) >= _COHESION_SPLIT_MIN_SIZE and cohesion_score(G, nodes) < _COHESION_SPLIT_THRESHOLD:
175
- splits = _split_community(G, nodes)
176
- second_pass.extend(splits if len(splits) > 1 else [nodes])
177
- else:
178
- second_pass.append(nodes)
179
- final_communities = second_pass
180
-
181
- # Re-index by size descending. The tuple(sorted(nodes)) tiebreak makes this a
182
- # TOTAL order, so an identical grouping always gets identical community IDs.
183
- # Without it, the hundreds of equal-sized small communities are ordered by the
184
- # partitioner's (not seed-stable) enumeration order, so their integer IDs
185
- # permute run-to-run - which reads as massive "community churn" in a per-node
186
- # cid diff even though the actual grouping is reproducible (#1090 follow-up).
187
- final_communities.sort(key=lambda nodes: (-len(nodes), tuple(sorted(map(str, nodes)))))
188
- return {i: sorted(nodes) for i, nodes in enumerate(final_communities)}
189
-
190
-
191
- def _split_community(G: nx.Graph, nodes: list[str]) -> list[list[str]]:
192
- """Run a second Leiden pass on a community subgraph to split it further."""
193
- subgraph = G.subgraph(nodes)
194
- if subgraph.number_of_edges() == 0:
195
- # No edges - split into individual nodes
196
- return [[n] for n in sorted(nodes)]
197
- try:
198
- sub_partition = _partition(subgraph)
199
- sub_communities: dict[int, list[str]] = {}
200
- for node, cid in sub_partition.items():
201
- sub_communities.setdefault(cid, []).append(node)
202
- if len(sub_communities) <= 1:
203
- return [sorted(nodes)]
204
- return [sorted(v) for v in sub_communities.values()]
205
- except Exception:
206
- return [sorted(nodes)]
207
-
208
-
209
- def cohesion_score(G: nx.Graph, community_nodes: list[str]) -> float:
210
- """Ratio of actual intra-community edges to maximum possible."""
211
- n = len(community_nodes)
212
- if n <= 1:
213
- return 1.0
214
- subgraph = G.subgraph(community_nodes)
215
- actual = subgraph.number_of_edges()
216
- possible = n * (n - 1) / 2
217
- return actual / possible if possible > 0 else 0.0
218
-
219
-
220
- def score_all(G: nx.Graph, communities: dict[int, list[str]]) -> dict[int, float]:
221
- return {cid: cohesion_score(G, nodes) for cid, nodes in communities.items()}
222
-
223
-
224
- def remap_communities_to_previous(
225
- communities: dict[int, list[str]],
226
- previous_node_community: dict[str, int],
227
- ) -> dict[int, list[str]]:
228
- """Remap community IDs to maximize overlap with a previous assignment.
229
-
230
- Uses greedy one-to-one matching by intersection size, then assigns fresh IDs
231
- to unmatched communities in deterministic order (size desc, lexical tie-break).
232
- """
233
- if not communities:
234
- return {}
235
-
236
- new_sets = {cid: set(nodes) for cid, nodes in communities.items()}
237
- old_sets: dict[int, set[str]] = {}
238
- for node, old_cid in previous_node_community.items():
239
- old_sets.setdefault(old_cid, set()).add(node)
240
-
241
- overlaps: list[tuple[int, int, int]] = []
242
- for old_cid, old_nodes in old_sets.items():
243
- for new_cid, new_nodes in new_sets.items():
244
- overlap = len(old_nodes & new_nodes)
245
- if overlap > 0:
246
- overlaps.append((overlap, old_cid, new_cid))
247
- overlaps.sort(key=lambda x: (-x[0], x[1], x[2]))
248
-
249
- new_to_final: dict[int, int] = {}
250
- used_old_ids: set[int] = set()
251
- matched_new_ids: set[int] = set()
252
- for _overlap, old_cid, new_cid in overlaps:
253
- if old_cid in used_old_ids or new_cid in matched_new_ids:
254
- continue
255
- new_to_final[new_cid] = old_cid
256
- used_old_ids.add(old_cid)
257
- matched_new_ids.add(new_cid)
258
-
259
- unmatched = [cid for cid in communities if cid not in matched_new_ids]
260
- unmatched.sort(key=lambda cid: (-len(communities[cid]), tuple(sorted(communities[cid]))))
261
- next_id = 0
262
- for new_cid in unmatched:
263
- while next_id in used_old_ids:
264
- next_id += 1
265
- new_to_final[new_cid] = next_id
266
- used_old_ids.add(next_id)
267
- next_id += 1
268
-
269
- remapped: dict[int, list[str]] = {}
270
- for new_cid, nodes in communities.items():
271
- remapped[new_to_final[new_cid]] = sorted(nodes)
272
- return dict(sorted(remapped.items(), key=lambda kv: kv[0]))
@@ -1,15 +0,0 @@
1
- ---
2
- description: Build or query a graphify knowledge graph
3
- ---
4
-
5
- Invoke the `graphify` skill immediately.
6
-
7
- Pass the full `/graphify` argument string through unchanged.
8
- If no arguments were supplied, treat the target path as `.`.
9
-
10
- Examples:
11
- - `/graphify`
12
- - `/graphify src --update`
13
- - `/graphify query "what connects auth to billing?"`
14
-
15
- Do not answer from raw files before handing off to the `graphify` skill.