septum 0.1.3 → 0.1.4

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 (35) hide show
  1. package/README.md +5 -2
  2. package/package.json +1 -1
  3. package/src/cli/commands/slice.ts +4 -46
  4. package/src/cli/commands/topology.ts +119 -0
  5. package/src/cli/index.ts +11 -0
  6. package/src/core/database/repositories/environment.repository.ts +207 -0
  7. package/src/core/database/repositories/symbol.repository.ts +28 -9
  8. package/src/core/database/repository.ts +6 -1
  9. package/src/core/database/schema.ts +153 -120
  10. package/src/core/parser/behavioral-analyzer.ts +119 -0
  11. package/src/core/parser/boundary-tracker.ts +160 -0
  12. package/src/core/parser/extractors/base.ts +1 -0
  13. package/src/core/parser/extractors/blade.ts +158 -158
  14. package/src/core/parser/extractors/go.ts +5 -1
  15. package/src/core/parser/extractors/laravel-semantic.ts +320 -4
  16. package/src/core/parser/extractors/php.ts +5 -1
  17. package/src/core/parser/extractors/python.ts +3 -1
  18. package/src/core/parser/extractors/typescript.ts +5 -1
  19. package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
  20. package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
  21. package/src/core/parser/schema/schema-provider-registry.ts +43 -0
  22. package/src/core/parser/schema/schema-provider.interface.ts +33 -0
  23. package/src/core/resolver/path-resolver.ts +147 -147
  24. package/src/core/resolver/vertical-slice-tracer.ts +34 -1
  25. package/src/core/topology/environment-detector.ts +860 -0
  26. package/src/core/topology/types.ts +83 -0
  27. package/src/index.ts +3 -0
  28. package/src/mcp/schemas.ts +48 -0
  29. package/src/mcp/server.ts +62 -2
  30. package/src/mcp/tools/get-environment-topology.ts +141 -0
  31. package/src/mcp/tools/get-symbol-hotspots.ts +13 -3
  32. package/src/mcp/tools/register-domain.ts +1 -1
  33. package/src/mcp/tools/trace-vertical-slice.ts +9 -64
  34. package/src/types/index.ts +16 -0
  35. package/src/version.ts +3 -3
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  > **Deterministic Bounded-Context & Anti-Poisoning Architecture Guard for AI Coding Agents.**
4
4
 
5
+ [![Version: 0.1.4](https://img.shields.io/badge/Version-0.1.4-blue)](package.json)
5
6
  [![Runtime: Bun](https://img.shields.io/badge/Runtime-Bun-black?logo=bun)](https://bun.sh)
6
7
  [![Protocol: Model Context Protocol](https://img.shields.io/badge/Protocol-MCP-green)](https://modelcontextprotocol.io/)
7
8
  [![Database: SQLite WAL](https://img.shields.io/badge/Database-SQLite%20(bun:sqlite)-003B57?logo=sqlite)](https://sqlite.org/)
@@ -63,6 +64,7 @@ Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
63
64
  | `septum query [domain]` | Inspect domain catalog, archetypes, and public signatures |
64
65
  | `septum locate <query>` | Locate symbol, class, interface, method, or error source |
65
66
  | `septum slice <route\|intent>` | Trace end-to-end vertical execution slice for route/intent |
67
+ | `septum topology [action]` | Inspect 6-layer environment topology plane & distributed nodes |
66
68
  | `septum feature status\|clear` | Manage active feature lease and editing scope |
67
69
  | `septum serve` | Launch stdio MCP server for AI coding agents |
68
70
 
@@ -72,12 +74,13 @@ Add Septum to your MCP client (`mcp.json` or `claude_desktop_config.json`):
72
74
 
73
75
  All tools expose typed schemas automatically via the Model Context Protocol:
74
76
 
77
+ - **Self-Aware Environment Topology (`septum_get_environment_topology`):** Maps and verifies the 6-layer production architecture (Edge/WAF, Gateway/Web Server, Host, Runtime, Storage/Cache, Telemetry) and distributed entities with cross-layer invariants.
75
78
  - **Whole-Project Telescope (`septum_get_domain_catalog`):** Returns macro architecture maps in < 400 tokens.
76
- - **Vertical Slice Tracer (`septum_trace_vertical_slice`):** Traces execution chains (*Ingress ➔ Validation ➔ Orchestration ➔ Domain ➔ Egress*) across frameworks.
79
+ - **Omni-Trigger Vertical Slice Tracer (`septum_trace_vertical_slice`):** Traces execution chains across HTTP routes, queue jobs, console commands, and event listeners (*Ingress ➔ Validation ➔ Orchestration ➔ Domain ➔ Egress*).
77
80
  - **Pre-Flight Boundary Guard (`septum_check_boundary`):** Validates proposed edits and imports against bounded-context rules.
78
81
  - **Scope Locking (`septum_get_feature_context` / `septum_clear_feature_context`):** Locks active feature scope to eliminate context drift.
79
82
  - **Symbol & Blast Radius (`septum_locate_symbol`, `septum_get_symbol`, `septum_get_symbol_impact`):** Resolves call chains, signatures, and refactoring impact.
80
- - **God Function Detector (`septum_get_symbol_hotspots`):** Surfaces oversized functions/methods by physical lines-of-code.
83
+ - **God Function & Nesting Detector (`septum_get_symbol_hotspots`):** Surfaces oversized functions/methods ranked by physical lines, cyclomatic nesting depth, and composite risk score.
81
84
  - **Dynamic Topology (`septum_register_domain`):** Registers or updates domains and boundary rules on-the-fly.
82
85
 
83
86
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "septum",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Deterministic Bounded-Context, Structural File Catalog & Anti-Poisoning Partition for AI Agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -20,57 +20,15 @@ export async function handleSliceCommand(
20
20
  return;
21
21
  }
22
22
 
23
- if (result.found && result.slice) {
24
- const s = result.slice;
25
- console.log(`\n=================================================`);
26
- console.log(` VERTICAL SLICE: ${s.route.method} ${s.route.uri}`);
27
- console.log(`=================================================`);
28
- if (s.route.name) {
29
- console.log(` • Route Name: ${s.route.name}`);
30
- }
31
-
32
- if (s.request) {
33
- console.log(`\n • Request Validation:`);
34
- console.log(` Class: ${s.request.class} (${s.request.file || "inline"})`);
35
- const rules = Object.keys(s.request.rules);
36
- if (rules.length > 0) {
37
- console.log(` Rules:`);
38
- for (const r of rules) {
39
- console.log(` - ${r}: ${s.request.rules[r]}`);
40
- }
41
- }
42
- }
43
-
44
- console.log(`\n • Controller Action:`);
45
- console.log(` Class: ${s.controller.class}@${s.controller.action}`);
46
- console.log(` Target: ${s.controller.file || "unknown"}:${s.controller.line}`);
47
-
48
- if (s.model) {
49
- console.log(`\n • Model:`);
50
- console.log(` Class: ${s.model.class} (${s.model.file || "unknown"})`);
51
- if (s.model.fillable && s.model.fillable.length > 0) {
52
- console.log(` Fillable: [${s.model.fillable.join(", ")}]`);
53
- }
54
- if (s.model.casts && Object.keys(s.model.casts).length > 0) {
55
- console.log(` Casts: ${JSON.stringify(s.model.casts)}`);
56
- }
57
- }
58
-
59
- if (s.frontend) {
60
- console.log(`\n • Frontend Target:`);
61
- console.log(` Page: ${s.frontend.target}`);
62
- if (s.frontend.props.length > 0) {
63
- console.log(` Props: [${s.frontend.props.join(", ")}]`);
64
- }
65
- }
66
-
23
+ if (result.found) {
24
+ console.log(`\n${result.message}\n`);
67
25
  if (result.alternatives && result.alternatives.length > 0) {
68
- console.log(`\n • Alternative Matches:`);
26
+ console.log(` • Alternative Matches:`);
69
27
  for (const alt of result.alternatives) {
70
28
  console.log(` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}`);
71
29
  }
30
+ console.log(``);
72
31
  }
73
- console.log(`\n=================================================\n`);
74
32
  } else {
75
33
  console.log(`\n❌ Vertical Slice Not Found:`);
76
34
  console.log(` Query: ${result.query}`);
@@ -0,0 +1,119 @@
1
+ import { SeptumDatabase } from "../../core/database/client.ts";
2
+ import { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { EnvironmentDetector } from "../../core/topology/environment-detector.ts";
4
+ import type { EnvironmentLayer, TopologyNode } from "../../core/topology/types.ts";
5
+
6
+ export async function handleTopologyCommand(
7
+ subcommand?: string,
8
+ arg1?: string,
9
+ arg2?: string,
10
+ arg3?: string,
11
+ arg4?: string
12
+ ): Promise<void> {
13
+ const cwd = process.cwd();
14
+ const db = new SeptumDatabase();
15
+ const repo = new SeptumRepository(db.raw);
16
+
17
+ if (subcommand === "resolve" && arg1 && arg2) {
18
+ const layer = arg1.toLowerCase() as EnvironmentLayer;
19
+ const platform = arg2;
20
+ // Fix: Preserve existing constraints instead of overwriting with empty array
21
+ const existing = repo.environment.getLayer(layer);
22
+ repo.environment.saveLayer({
23
+ layer,
24
+ platform,
25
+ canonical_tag: EnvironmentDetector.matchCanonicalTag(platform),
26
+ status: "VERIFIED",
27
+ detected_from: ["CLI Manual Resolution"],
28
+ constraints: existing?.constraints && existing.constraints.length > 0 ? existing.constraints : [
29
+ `Configured manually as '${platform}'.`
30
+ ],
31
+ });
32
+ console.log(`[Septum Topology] Layer '${layer}' successfully verified as '${platform}' in SQLite SSOT.\n`);
33
+ } else if (subcommand === "add-node" && arg1 && arg2 && arg3) {
34
+ const nodeId = arg1;
35
+ const layer = arg2.toLowerCase() as EnvironmentLayer;
36
+ const platform = arg3;
37
+ const domainOrIp = arg4;
38
+
39
+ const node: TopologyNode = {
40
+ id: nodeId,
41
+ layer,
42
+ name: nodeId,
43
+ platform,
44
+ canonical_tag: EnvironmentDetector.matchCanonicalTag(platform),
45
+ domain_or_ip: domainOrIp,
46
+ status: "VERIFIED",
47
+ detected_from: ["CLI Manual Node Resolution"],
48
+ constraints: [`Node added manually with platform '${platform}'.`],
49
+ };
50
+ repo.environment.saveNode(node);
51
+ console.log(`[Septum Topology] Node '${nodeId}' (${layer.toUpperCase()}) added to distributed topology graph.\n`);
52
+ } else if (subcommand === "remove-node" && arg1) {
53
+ repo.environment.deleteNode(arg1);
54
+ console.log(`[Septum Topology] Node '${arg1}' removed from distributed topology graph.\n`);
55
+ } else if (subcommand === "clear") {
56
+ repo.environment.clearAll();
57
+ console.log("[Septum Topology] Cleared environment topology and nodes in SQLite SSOT.\n");
58
+ }
59
+
60
+ const topology = EnvironmentDetector.detect(cwd, repo);
61
+
62
+ console.log("================================================================================");
63
+ console.log("🌐 SEPTUM SELF-AWARE ENVIRONMENT TOPOLOGY PLANE");
64
+ console.log("================================================================================");
65
+ console.log(`Overall Status: ${topology.overall_status}`);
66
+ console.log(`Topology Mode: ${topology.is_distributed ? "DISTRIBUTED MULTI-NODE GRAPH" : "MONOLITHIC SINGLE-NODE"}`);
67
+ console.log(`Summary: ${topology.architectural_summary}\n`);
68
+
69
+ if (topology.nodes.length > 0) {
70
+ console.log("--- DISTRIBUTED TOPOLOGY NODES ---");
71
+ for (const node of topology.nodes) {
72
+ const statusIcon = node.status === "VERIFIED" ? "✓ [VERIFIED]" : node.status === "DETECTED" ? "• [DETECTED]" : "? [AMBIGUOUS]";
73
+ const domainStr = node.domain_or_ip ? ` @ ${node.domain_or_ip}` : "";
74
+ const roleStr = node.role ? ` [Role: ${node.role}]` : "";
75
+ console.log(` ${statusIcon} ${node.id.padEnd(20)} (${node.layer.toUpperCase()}): ${node.platform}${domainStr}${roleStr}`);
76
+ if (node.connected_to && node.connected_to.length > 0) {
77
+ console.log(` ➔ Connected to: ${node.connected_to.join(", ")}`);
78
+ }
79
+ if (node.constraints && node.constraints.length > 0) {
80
+ for (const c of node.constraints) {
81
+ console.log(` - ${c}`);
82
+ }
83
+ }
84
+ }
85
+ console.log("");
86
+ }
87
+
88
+ console.log("--- 6-LAYER PRODUCTION SUMMARY PLANE ---");
89
+ for (const [layerKey, info] of Object.entries(topology.layers)) {
90
+ const statusIcon = info.status === "VERIFIED" ? "✓ [VERIFIED]" : info.status === "DETECTED" ? "• [DETECTED]" : "? [AMBIGUOUS]";
91
+ console.log(` ${statusIcon} ${layerKey.toUpperCase().padEnd(10)}: ${info.platform || "Unresolved"}`);
92
+ }
93
+
94
+ if (topology.interplays.length > 0) {
95
+ console.log("\n--- CROSS-LAYER & DISTRIBUTED INVARIANTS ---");
96
+ for (const interplay of topology.interplays) {
97
+ console.log(` ⚡ ${interplay.title} (${interplay.involved_layers.join(" ➔ ")})`);
98
+ console.log(` Rule: ${interplay.risk_or_rule}`);
99
+ console.log(` Action: ${interplay.required_action}`);
100
+ }
101
+ }
102
+
103
+ if (topology.pending_inquiries.length > 0) {
104
+ console.log("\n--- PENDING ARCHITECTURAL INQUIRIES ---");
105
+ console.log("Action Required: Please clarify these ambiguous layers/nodes for your AI Agent:\n");
106
+ for (const inquiry of topology.pending_inquiries) {
107
+ console.log(` ❓ ${inquiry}`);
108
+ }
109
+ console.log("\nTip: To resolve an inquiry or add distributed nodes, run:");
110
+ console.log(" septum topology resolve <layer> \"<Platform Name>\"");
111
+ console.log(" septum topology add-node <id> <layer> \"<Platform Name>\" [domain_or_ip]");
112
+ console.log("Example:");
113
+ console.log(" septum topology add-node host:vps-worker host \"Ubuntu 24.04 (Dedicated Worker)\" 10.0.0.6");
114
+ }
115
+
116
+ console.log("================================================================================\n");
117
+
118
+ db.close();
119
+ }
package/src/cli/index.ts CHANGED
@@ -5,6 +5,7 @@ import { handleCheckCommand } from "./commands/check.ts";
5
5
  import { handleHookCommand } from "./commands/hook.ts";
6
6
  import { handleIngestCommand } from "./commands/ingest.ts";
7
7
  import { handleInitCommand } from "./commands/init.ts";
8
+ import { handleTopologyCommand } from "./commands/topology.ts";
8
9
  import { handleLocateCommand } from "./commands/locate.ts";
9
10
  import { handleQueryCommand } from "./commands/query.ts";
10
11
  import { handleServeCommand } from "./commands/serve.ts";
@@ -112,6 +113,12 @@ export async function runCLI(argv: string[]): Promise<void> {
112
113
  break;
113
114
  }
114
115
 
116
+ case "topology":
117
+ case "env": {
118
+ await handleTopologyCommand(positionals[1], positionals[2], positionals[3], positionals[4], positionals[5]);
119
+ break;
120
+ }
121
+
115
122
  case "feature": {
116
123
  const sub = positionals[1];
117
124
  if (sub === "clear") {
@@ -188,6 +195,10 @@ COMMANDS:
188
195
  feature [status|clear] View or release active feature context session lock
189
196
  hook [subhook] Internal pre-tool guardrail hook (e.g. pre-write)
190
197
 
198
+ Environment & Topology:
199
+ topology [action] Inspect 6-layer environment topology & distributed nodes
200
+ Subcommands: resolve <layer> <platform>, add-node, remove-node, clear
201
+
191
202
  Daemon & Protocol:
192
203
  serve Start Model Context Protocol (MCP) server over stdio
193
204
 
@@ -0,0 +1,207 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { CanonicalPlatformTag, EnvironmentLayer, EnvironmentLayerInfo, TopologyNode } from "../../topology/types.ts";
3
+
4
+ export class EnvironmentRepository {
5
+ constructor(private db: Database) {}
6
+
7
+ public getLayer(layer: EnvironmentLayer): EnvironmentLayerInfo | null {
8
+ const row = this.db
9
+ .query<{
10
+ layer: string;
11
+ platform: string | null;
12
+ status: string;
13
+ detected_from_json: string;
14
+ constraints_json: string;
15
+ unresolved_inquiry: string | null;
16
+ }, [string]>("SELECT * FROM environment_topology WHERE layer = ?")
17
+ .get(layer);
18
+
19
+ if (!row) return null;
20
+ return {
21
+ layer: row.layer as EnvironmentLayer,
22
+ platform: row.platform ?? undefined,
23
+ status: row.status as any,
24
+ detected_from: JSON.parse(row.detected_from_json || "[]"),
25
+ constraints: JSON.parse(row.constraints_json || "[]"),
26
+ unresolved_inquiry: row.unresolved_inquiry ?? undefined,
27
+ };
28
+ }
29
+
30
+ public getAllLayers(): Record<EnvironmentLayer, EnvironmentLayerInfo> {
31
+ const rows = this.db
32
+ .query<{
33
+ layer: string;
34
+ platform: string | null;
35
+ status: string;
36
+ detected_from_json: string;
37
+ constraints_json: string;
38
+ unresolved_inquiry: string | null;
39
+ }, []>("SELECT * FROM environment_topology")
40
+ .all();
41
+
42
+ const result: Partial<Record<EnvironmentLayer, EnvironmentLayerInfo>> = {};
43
+ for (const row of rows) {
44
+ result[row.layer as EnvironmentLayer] = {
45
+ layer: row.layer as EnvironmentLayer,
46
+ platform: row.platform ?? undefined,
47
+ status: row.status as any,
48
+ detected_from: JSON.parse(row.detected_from_json || "[]"),
49
+ constraints: JSON.parse(row.constraints_json || "[]"),
50
+ unresolved_inquiry: row.unresolved_inquiry ?? undefined,
51
+ };
52
+ }
53
+ return result as Record<EnvironmentLayer, EnvironmentLayerInfo>;
54
+ }
55
+
56
+ public saveLayer(info: EnvironmentLayerInfo): void {
57
+ this.db
58
+ .query(
59
+ `INSERT INTO environment_topology (
60
+ layer, platform, status, detected_from_json, constraints_json, unresolved_inquiry, updated_at
61
+ ) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
62
+ ON CONFLICT(layer) DO UPDATE SET
63
+ platform = excluded.platform,
64
+ status = excluded.status,
65
+ detected_from_json = excluded.detected_from_json,
66
+ constraints_json = excluded.constraints_json,
67
+ unresolved_inquiry = excluded.unresolved_inquiry,
68
+ updated_at = CURRENT_TIMESTAMP`
69
+ )
70
+ .run(
71
+ info.layer,
72
+ info.platform ?? null,
73
+ info.status,
74
+ JSON.stringify(info.detected_from ?? []),
75
+ JSON.stringify(info.constraints ?? []),
76
+ info.unresolved_inquiry ?? null
77
+ );
78
+ }
79
+
80
+ public clearAll(): void {
81
+ this.db.run("DELETE FROM environment_topology");
82
+ this.db.run("DELETE FROM environment_nodes");
83
+ }
84
+
85
+ // Node CRUD operations for Multi-Node Topology
86
+ public getNode(id: string): TopologyNode | null {
87
+ const row = this.db
88
+ .query<{
89
+ id: string;
90
+ layer: string;
91
+ name: string;
92
+ platform: string;
93
+ canonical_tag: string;
94
+ domain_or_ip: string | null;
95
+ role: string | null;
96
+ status: string;
97
+ detected_from_json: string;
98
+ constraints_json: string;
99
+ connected_to_json: string;
100
+ unresolved_inquiry: string | null;
101
+ }, [string]>("SELECT * FROM environment_nodes WHERE id = ?")
102
+ .get(id);
103
+
104
+ if (!row) return null;
105
+ return this.mapNodeRow(row);
106
+ }
107
+
108
+ public getAllNodes(): TopologyNode[] {
109
+ const rows = this.db
110
+ .query<{
111
+ id: string;
112
+ layer: string;
113
+ name: string;
114
+ platform: string;
115
+ canonical_tag: string;
116
+ domain_or_ip: string | null;
117
+ role: string | null;
118
+ status: string;
119
+ detected_from_json: string;
120
+ constraints_json: string;
121
+ connected_to_json: string;
122
+ unresolved_inquiry: string | null;
123
+ }, []>("SELECT * FROM environment_nodes ORDER BY layer, id")
124
+ .all();
125
+
126
+ return rows.map((r) => this.mapNodeRow(r));
127
+ }
128
+
129
+ public getNodesByLayer(layer: EnvironmentLayer): TopologyNode[] {
130
+ const rows = this.db
131
+ .query<{
132
+ id: string;
133
+ layer: string;
134
+ name: string;
135
+ platform: string;
136
+ canonical_tag: string;
137
+ domain_or_ip: string | null;
138
+ role: string | null;
139
+ status: string;
140
+ detected_from_json: string;
141
+ constraints_json: string;
142
+ connected_to_json: string;
143
+ unresolved_inquiry: string | null;
144
+ }, [string]>("SELECT * FROM environment_nodes WHERE layer = ? ORDER BY id")
145
+ .all(layer);
146
+
147
+ return rows.map((r) => this.mapNodeRow(r));
148
+ }
149
+
150
+ public saveNode(node: TopologyNode): void {
151
+ this.db
152
+ .query(
153
+ `INSERT INTO environment_nodes (
154
+ id, layer, name, platform, canonical_tag, domain_or_ip, role, status,
155
+ detected_from_json, constraints_json, connected_to_json, unresolved_inquiry, updated_at
156
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
157
+ ON CONFLICT(id) DO UPDATE SET
158
+ layer = excluded.layer,
159
+ name = excluded.name,
160
+ platform = excluded.platform,
161
+ canonical_tag = excluded.canonical_tag,
162
+ domain_or_ip = excluded.domain_or_ip,
163
+ role = excluded.role,
164
+ status = excluded.status,
165
+ detected_from_json = excluded.detected_from_json,
166
+ constraints_json = excluded.constraints_json,
167
+ connected_to_json = excluded.connected_to_json,
168
+ unresolved_inquiry = excluded.unresolved_inquiry,
169
+ updated_at = CURRENT_TIMESTAMP`
170
+ )
171
+ .run(
172
+ node.id,
173
+ node.layer,
174
+ node.name,
175
+ node.platform,
176
+ node.canonical_tag || "unspecified",
177
+ node.domain_or_ip ?? null,
178
+ node.role ?? null,
179
+ node.status,
180
+ JSON.stringify(node.detected_from ?? []),
181
+ JSON.stringify(node.constraints ?? []),
182
+ JSON.stringify(node.connected_to ?? []),
183
+ node.unresolved_inquiry ?? null
184
+ );
185
+ }
186
+
187
+ public deleteNode(id: string): void {
188
+ this.db.query("DELETE FROM environment_nodes WHERE id = ?").run(id);
189
+ }
190
+
191
+ private mapNodeRow(row: any): TopologyNode {
192
+ return {
193
+ id: row.id,
194
+ layer: row.layer as EnvironmentLayer,
195
+ name: row.name,
196
+ platform: row.platform,
197
+ canonical_tag: (row.canonical_tag as CanonicalPlatformTag) || "unspecified",
198
+ domain_or_ip: row.domain_or_ip ?? undefined,
199
+ role: row.role ?? undefined,
200
+ status: row.status as any,
201
+ detected_from: JSON.parse(row.detected_from_json || "[]"),
202
+ constraints: JSON.parse(row.constraints_json || "[]"),
203
+ connected_to: JSON.parse(row.connected_to_json || "[]"),
204
+ unresolved_inquiry: row.unresolved_inquiry ?? undefined,
205
+ };
206
+ }
207
+ }
@@ -17,12 +17,13 @@ export class SymbolRepository {
17
17
  if (symbols.length === 0) return;
18
18
 
19
19
  const stmt = this.db.query(
20
- `INSERT INTO symbols (file_id, name, kind, signature, visibility, line_start, line_end, line_count)
21
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
20
+ `INSERT INTO symbols (file_id, name, kind, signature, visibility, line_start, line_end, line_count, nesting_depth)
21
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
22
22
  );
23
23
 
24
24
  for (const sym of symbols) {
25
25
  const lineCount = sym.line_count ?? Math.max(1, sym.line_end - sym.line_start + 1);
26
+ const nestingDepth = sym.nesting_depth ?? 1;
26
27
  stmt.run(
27
28
  fileId,
28
29
  sym.name,
@@ -31,7 +32,8 @@ export class SymbolRepository {
31
32
  sym.visibility,
32
33
  sym.line_start,
33
34
  sym.line_end,
34
- lineCount
35
+ lineCount,
36
+ nestingDepth
35
37
  );
36
38
  }
37
39
  }
@@ -98,6 +100,7 @@ export class SymbolRepository {
98
100
  line_start: r.line_start,
99
101
  line_end: r.line_end,
100
102
  line_count: r.line_count ?? (r.line_end - r.line_start + 1),
103
+ nesting_depth: r.nesting_depth ?? 1,
101
104
  },
102
105
  }));
103
106
  }
@@ -225,12 +228,16 @@ export class SymbolRepository {
225
228
 
226
229
  public getHotspotSymbols(options: {
227
230
  minLines?: number;
231
+ minNesting?: number;
228
232
  kind?: SymbolKind;
229
233
  domain?: string;
230
234
  limit?: number;
231
- } = {}): Array<SymbolRecord & { file_path: string; domain_name: string }> {
232
- const minLines = options.minLines ?? 30;
235
+ sortBy?: "lines" | "nesting" | "risk_score";
236
+ } = {}): Array<SymbolRecord & { file_path: string; domain_name: string; risk_score: number }> {
237
+ const minLines = options.minLines ?? 0;
238
+ const minNesting = options.minNesting ?? 1;
233
239
  const limit = options.limit ?? 50;
240
+ const sortBy = options.sortBy ?? "risk_score";
234
241
 
235
242
  let sql = `
236
243
  SELECT
@@ -243,15 +250,16 @@ export class SymbolRepository {
243
250
  s.line_start,
244
251
  s.line_end,
245
252
  s.line_count,
253
+ s.nesting_depth,
246
254
  f.path AS file_path,
247
255
  d.name AS domain_name
248
256
  FROM symbols s
249
257
  JOIN files f ON s.file_id = f.id
250
258
  JOIN domains d ON f.domain_id = d.id
251
- WHERE s.line_count >= ?
259
+ WHERE s.line_count >= ? AND s.nesting_depth >= ?
252
260
  `;
253
261
 
254
- const params: any[] = [minLines];
262
+ const params: any[] = [minLines, minNesting];
255
263
 
256
264
  if (options.kind) {
257
265
  sql += " AND s.kind = ?";
@@ -263,10 +271,21 @@ export class SymbolRepository {
263
271
  params.push(options.domain);
264
272
  }
265
273
 
266
- sql += " ORDER BY s.line_count DESC, s.name ASC LIMIT ?";
274
+ if (sortBy === "nesting") {
275
+ sql += " ORDER BY s.nesting_depth DESC, s.line_count DESC, s.name ASC LIMIT ?";
276
+ } else if (sortBy === "lines") {
277
+ sql += " ORDER BY s.line_count DESC, s.nesting_depth DESC, s.name ASC LIMIT ?";
278
+ } else {
279
+ sql += " ORDER BY (s.line_count * (1.0 + s.nesting_depth * 0.3)) DESC, s.nesting_depth DESC, s.name ASC LIMIT ?";
280
+ }
267
281
  params.push(limit);
268
282
 
269
- return this.db.query<any, any[]>(sql).all(...params);
283
+ const rows = this.db.query<any, any[]>(sql).all(...params);
284
+ return rows.map((r) => ({
285
+ ...r,
286
+ nesting_depth: r.nesting_depth ?? 1,
287
+ risk_score: Math.round((r.line_count ?? 1) * (1.0 + (r.nesting_depth ?? 1) * 0.3)),
288
+ }));
270
289
  }
271
290
 
272
291
  public getSymbolDetails(
@@ -32,6 +32,7 @@ export type {
32
32
  } from "../../types/index.ts";
33
33
  import { DependencyRepository } from "./repositories/dependency.repository.ts";
34
34
  import { DomainRepository } from "./repositories/domain.repository.ts";
35
+ import { EnvironmentRepository } from "./repositories/environment.repository.ts";
35
36
  import { FileRepository } from "./repositories/file.repository.ts";
36
37
  import { MetaRepository } from "./repositories/meta.repository.ts";
37
38
  import { SymbolRepository } from "./repositories/symbol.repository.ts";
@@ -44,6 +45,7 @@ export class SeptumRepository {
44
45
  public readonly symbols: SymbolRepository;
45
46
  public readonly dependencies: DependencyRepository;
46
47
  public readonly meta: MetaRepository;
48
+ public readonly environment: EnvironmentRepository;
47
49
 
48
50
  constructor(private db: Database) {
49
51
  this.domains = new DomainRepository(db);
@@ -51,6 +53,7 @@ export class SeptumRepository {
51
53
  this.symbols = new SymbolRepository(db);
52
54
  this.dependencies = new DependencyRepository(db);
53
55
  this.meta = new MetaRepository(db);
56
+ this.environment = new EnvironmentRepository(db);
54
57
  }
55
58
 
56
59
  public runInTransaction<T>(fn: () => T): T {
@@ -212,10 +215,12 @@ export class SeptumRepository {
212
215
 
213
216
  public getHotspotSymbols(options: {
214
217
  minLines?: number;
218
+ minNesting?: number;
215
219
  kind?: SymbolKind;
216
220
  domain?: string;
217
221
  limit?: number;
218
- } = {}): Array<SymbolRecord & { file_path: string; domain_name: string }> {
222
+ sortBy?: "lines" | "nesting" | "risk_score";
223
+ } = {}): Array<SymbolRecord & { file_path: string; domain_name: string; risk_score: number }> {
219
224
  return this.symbols.getHotspotSymbols(options);
220
225
  }
221
226