septum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/bin/septum.ts +4 -0
  4. package/package.json +62 -0
  5. package/src/cli/commands/check.ts +161 -0
  6. package/src/cli/commands/hook.ts +188 -0
  7. package/src/cli/commands/ingest.ts +29 -0
  8. package/src/cli/commands/init.ts +211 -0
  9. package/src/cli/commands/locate.ts +61 -0
  10. package/src/cli/commands/query.ts +64 -0
  11. package/src/cli/commands/serve.ts +5 -0
  12. package/src/cli/commands/slice.ts +90 -0
  13. package/src/cli/commands/sync.ts +31 -0
  14. package/src/cli/index.ts +178 -0
  15. package/src/cli/templates/hooks.ts +75 -0
  16. package/src/cli/templates/rules.ts +38 -0
  17. package/src/cli/templates/skill.ts +66 -0
  18. package/src/core/boundary/evaluator.ts +210 -0
  19. package/src/core/config/loader.ts +84 -0
  20. package/src/core/config/schema.ts +39 -0
  21. package/src/core/database/client.ts +132 -0
  22. package/src/core/database/repositories/dependency.repository.ts +279 -0
  23. package/src/core/database/repositories/domain.repository.ts +198 -0
  24. package/src/core/database/repositories/file.repository.ts +99 -0
  25. package/src/core/database/repositories/meta.repository.ts +33 -0
  26. package/src/core/database/repositories/symbol.repository.ts +424 -0
  27. package/src/core/database/repository.ts +242 -0
  28. package/src/core/database/schema.sql +92 -0
  29. package/src/core/discovery/topology-detector.ts +418 -0
  30. package/src/core/ingestion/hasher.ts +5 -0
  31. package/src/core/ingestion/pipeline.ts +438 -0
  32. package/src/core/parser/boundary-tracker.ts +128 -0
  33. package/src/core/parser/extractors/base.ts +60 -0
  34. package/src/core/parser/extractors/frontend.ts +194 -0
  35. package/src/core/parser/extractors/go.ts +176 -0
  36. package/src/core/parser/extractors/laravel-semantic.ts +426 -0
  37. package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -0
  38. package/src/core/parser/extractors/nestjs-semantic.ts +119 -0
  39. package/src/core/parser/extractors/php.ts +149 -0
  40. package/src/core/parser/extractors/python.ts +226 -0
  41. package/src/core/parser/extractors/registry.ts +49 -0
  42. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -0
  43. package/src/core/parser/extractors/typescript.ts +177 -0
  44. package/src/core/parser/tree-sitter.ts +39 -0
  45. package/src/core/resolver/call-graph-tracer.ts +668 -0
  46. package/src/core/resolver/module-resolver.ts +323 -0
  47. package/src/core/resolver/symbol-locator.ts +405 -0
  48. package/src/core/resolver/vertical-slice-tracer.ts +236 -0
  49. package/src/core/session/session-manager.ts +56 -0
  50. package/src/core/telemetry/telemetry.ts +86 -0
  51. package/src/index.ts +18 -0
  52. package/src/mcp/schemas.ts +68 -0
  53. package/src/mcp/server.ts +602 -0
  54. package/src/mcp/tools/check-boundary.ts +102 -0
  55. package/src/mcp/tools/get-domain-catalog.ts +91 -0
  56. package/src/mcp/tools/get-feature-context.ts +116 -0
  57. package/src/mcp/tools/get-symbol-hotspots.ts +49 -0
  58. package/src/mcp/tools/get-symbol-impact.ts +26 -0
  59. package/src/mcp/tools/get-symbol.ts +63 -0
  60. package/src/mcp/tools/locate-symbol.ts +74 -0
  61. package/src/mcp/tools/register-domain.ts +64 -0
  62. package/src/mcp/tools/trace-vertical-slice.ts +102 -0
  63. package/src/types/index.ts +296 -0
@@ -0,0 +1,91 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
4
+ import type { SeptumRepository } from "../../core/database/repository.ts";
5
+
6
+ export interface GetDomainCatalogArgs {
7
+ domain?: string;
8
+ archetype_filter?: string;
9
+ }
10
+
11
+ export function handleGetDomainCatalog(
12
+ repo: SeptumRepository,
13
+ config: ValidatedSeptumConfig,
14
+ args: GetDomainCatalogArgs
15
+ ) {
16
+ if (!args.domain) {
17
+ const allDomains = repo.getAllDomains();
18
+ const projectType = repo.getMeta("project_type") || "generic";
19
+ const framework = repo.getMeta("framework") || "unknown";
20
+ const archStyle = repo.getMeta("architecture_style") || "modular";
21
+ const totalSlices = repo.getAllVerticalSlices().length;
22
+
23
+ const macroMap = {
24
+ telescope_view: {
25
+ project_type: projectType,
26
+ framework,
27
+ architecture_style: archStyle,
28
+ total_domains: allDomains.length,
29
+ total_vertical_slices: totalSlices,
30
+ },
31
+ domains: allDomains.map((d) => ({
32
+ name: d.name,
33
+ root: d.root_path,
34
+ archetypes: Object.keys(JSON.parse(d.archetypes_json || "{}")),
35
+ allowed_dependencies: JSON.parse(d.allowed_deps_json || "[]"),
36
+ forbidden_dependencies: JSON.parse(d.forbidden_deps_json || "[]"),
37
+ })),
38
+ agent_instruction:
39
+ "Macro semantic map loaded from SQLite SSOT. Use 'septum_get_domain_catalog' with domain='<name>' for granular file/symbol catalogs, or 'septum_trace_vertical_slice' with query='<route|command|intent>' for end-to-end execution flow.",
40
+ };
41
+
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text" as const,
46
+ text: JSON.stringify(macroMap, null, 2),
47
+ },
48
+ ],
49
+ };
50
+ }
51
+
52
+ const catalog = repo.getDomainCatalog(args.domain, args.archetype_filter);
53
+ if (!catalog) {
54
+ const available = Object.keys(config.domains).join(", ");
55
+ throw new Error(
56
+ `Domain '${args.domain}' not found. Available domains in Septum catalog: [${available}]. Run 'septum ingest' or 'septum_register_domain' if recently added.`
57
+ );
58
+ }
59
+
60
+ // Record access log for Gate 3 (Mandatory Catalog Consultation)
61
+ recordCatalogAccess(args.domain);
62
+
63
+ // Token-efficient serialization: keep payload dense and under 500 tokens
64
+ return {
65
+ content: [
66
+ {
67
+ type: "text" as const,
68
+ text: JSON.stringify(catalog, null, 2),
69
+ },
70
+ ],
71
+ };
72
+ }
73
+
74
+ function recordCatalogAccess(domain: string): void {
75
+ try {
76
+ const septumDir = path.resolve(process.cwd(), ".septum");
77
+ if (!fs.existsSync(septumDir)) {
78
+ fs.mkdirSync(septumDir, { recursive: true });
79
+ }
80
+ const logPath = path.join(septumDir, "catalog_access.json");
81
+ let log: { domains?: Record<string, string>; features?: Record<string, string> } = {};
82
+ if (fs.existsSync(logPath)) {
83
+ try {
84
+ log = JSON.parse(fs.readFileSync(logPath, "utf-8"));
85
+ } catch {}
86
+ }
87
+ log.domains = log.domains || {};
88
+ log.domains[domain] = new Date().toISOString();
89
+ fs.writeFileSync(logPath, JSON.stringify(log, null, 2), "utf-8");
90
+ } catch {}
91
+ }
@@ -0,0 +1,116 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
4
+ import type { SeptumRepository } from "../../core/database/repository.ts";
5
+ import { SessionManager } from "../../core/session/session-manager.ts";
6
+ import type { FeatureContextResponse } from "../../types/index.ts";
7
+
8
+ export interface GetFeatureContextArgs {
9
+ feature: string;
10
+ }
11
+
12
+ export function handleClearFeatureContext(workspaceRoot: string = process.cwd()): boolean {
13
+ return SessionManager.clearActiveSession(workspaceRoot);
14
+ }
15
+
16
+ export function handleGetFeatureContext(
17
+ repo: SeptumRepository,
18
+ config: ValidatedSeptumConfig,
19
+ args: GetFeatureContextArgs
20
+ ) {
21
+ if (!args.feature) {
22
+ throw new Error("Missing required argument: 'feature'");
23
+ }
24
+
25
+ const features = config.features ?? {};
26
+ const featureConfig = features[args.feature];
27
+
28
+ if (!featureConfig) {
29
+ const available = Object.keys(features).join(", ") || "none";
30
+ throw new Error(
31
+ `Feature '${args.feature}' not found in configuration. Available features: [${available}]`
32
+ );
33
+ }
34
+
35
+ // Record Active Feature Session Lease (.septum/session.json)
36
+ SessionManager.saveActiveSession(process.cwd(), {
37
+ feature_key: args.feature,
38
+ domain: featureConfig.domain,
39
+ touchpoints: featureConfig.allowed_touchpoints ?? [],
40
+ locked_at: new Date().toISOString(),
41
+ });
42
+
43
+ // Deterministically fetch signatures, methods, and properties for reuse_symbols from SQLite
44
+ const reuseSymbolsWithMeta: FeatureContextResponse["reuse_symbols"] = [];
45
+ const requestedSymbols = featureConfig.reuse_symbols ?? [];
46
+
47
+ for (const symName of requestedSymbols) {
48
+ const record = repo.findSymbolWithMembers(symName);
49
+ if (record) {
50
+ reuseSymbolsWithMeta.push({
51
+ name: record.symbol_name,
52
+ kind: record.kind,
53
+ signature: record.signature,
54
+ methods: record.methods.length > 0 ? record.methods : undefined,
55
+ properties: record.properties.length > 0 ? record.properties : undefined,
56
+ file_path: record.file_path,
57
+ domain: record.domain_name,
58
+ });
59
+ } else {
60
+ reuseSymbolsWithMeta.push({
61
+ name: symName,
62
+ kind: "unknown",
63
+ domain: featureConfig.domain,
64
+ });
65
+ }
66
+ }
67
+
68
+ const response: FeatureContextResponse = {
69
+ feature: args.feature,
70
+ domain: featureConfig.domain,
71
+ description: featureConfig.description,
72
+ allowed_touchpoints: featureConfig.allowed_touchpoints,
73
+ reuse_symbols: reuseSymbolsWithMeta,
74
+ input_contract: (featureConfig.input_contract as Record<string, unknown>) ?? {},
75
+ output_contract: (featureConfig.output_contract as Record<string, unknown>) ?? {},
76
+ governance_directives: [
77
+ "DO NOT modify or edit any files outside allowed_touchpoints.",
78
+ "DO NOT create duplicate helpers/services; reuse the existing symbols and methods declared in reuse_symbols.",
79
+ "DO NOT emit or expect phantom payload fields outside input_contract and output_contract.",
80
+ "DO NOT import cross-domain modules without declared allowed_dependencies in Septum domain boundaries.",
81
+ ],
82
+ };
83
+
84
+ recordFeatureAccess(featureConfig.domain, args.feature);
85
+
86
+ return {
87
+ content: [
88
+ {
89
+ type: "text" as const,
90
+ text: JSON.stringify(response, null, 2),
91
+ },
92
+ ],
93
+ };
94
+ }
95
+
96
+ function recordFeatureAccess(domain: string, feature: string): void {
97
+ try {
98
+ const septumDir = path.resolve(process.cwd(), ".septum");
99
+ if (!fs.existsSync(septumDir)) {
100
+ fs.mkdirSync(septumDir, { recursive: true });
101
+ }
102
+ const logPath = path.join(septumDir, "catalog_access.json");
103
+ let log: { domains?: Record<string, string>; features?: Record<string, string> } = {};
104
+ if (fs.existsSync(logPath)) {
105
+ try {
106
+ log = JSON.parse(fs.readFileSync(logPath, "utf-8"));
107
+ } catch {}
108
+ }
109
+ log.domains = log.domains || {};
110
+ log.features = log.features || {};
111
+ const now = new Date().toISOString();
112
+ log.domains[domain] = now;
113
+ log.features[feature] = now;
114
+ fs.writeFileSync(logPath, JSON.stringify(log, null, 2), "utf-8");
115
+ } catch {}
116
+ }
@@ -0,0 +1,49 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { DatabaseRepository } from "../../core/database/repository.ts";
3
+ import type { SymbolKind } from "../../types/index.ts";
4
+
5
+ export interface GetSymbolHotspotsArgs {
6
+ min_lines?: number;
7
+ kind?: SymbolKind;
8
+ domain?: string;
9
+ limit?: number;
10
+ }
11
+
12
+ export function handleGetSymbolHotspots(
13
+ repo: DatabaseRepository,
14
+ _config: ValidatedSeptumConfig,
15
+ args: GetSymbolHotspotsArgs
16
+ ) {
17
+ const minLines = args.min_lines ?? 30;
18
+ const limit = args.limit ?? 30;
19
+
20
+ const hotspots = repo.getHotspotSymbols({
21
+ minLines,
22
+ kind: args.kind,
23
+ domain: args.domain,
24
+ limit,
25
+ });
26
+
27
+ const payload = {
28
+ total_found: hotspots.length,
29
+ threshold_min_lines: minLines,
30
+ hotspots: hotspots.map((h) => ({
31
+ name: h.name,
32
+ kind: h.kind,
33
+ line_count: h.line_count,
34
+ line_span: `${h.line_start}-${h.line_end}`,
35
+ file_path: h.file_path,
36
+ domain: h.domain_name,
37
+ signature: h.signature,
38
+ })),
39
+ };
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text" as const,
45
+ text: JSON.stringify(payload, null, 2),
46
+ },
47
+ ],
48
+ };
49
+ }
@@ -0,0 +1,26 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import type { GetSymbolImpactArgs } from "../../types/index.ts";
4
+
5
+ export function handleGetSymbolImpact(
6
+ repo: SeptumRepository,
7
+ _config: ValidatedSeptumConfig,
8
+ args: GetSymbolImpactArgs
9
+ ) {
10
+ if (!args.symbol) {
11
+ throw new Error(
12
+ "Missing required argument: 'symbol' (e.g. 'OrderService::cancelOrder', 'OrderService', or 'cancelOrder')"
13
+ );
14
+ }
15
+
16
+ const impact = repo.getSymbolImpact(args.symbol);
17
+
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text" as const,
22
+ text: JSON.stringify(impact, null, 2),
23
+ },
24
+ ],
25
+ };
26
+ }
@@ -0,0 +1,63 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
4
+ import type { GetSymbolArgs } from "../../types/index.ts";
5
+
6
+ export function handleGetSymbol(
7
+ repo: SeptumRepository,
8
+ _config: ValidatedSeptumConfig,
9
+ args: GetSymbolArgs
10
+ ) {
11
+ if (!args.symbol) {
12
+ throw new Error(
13
+ "Missing required argument: 'symbol' (e.g. 'OrderController::cancelOrder', 'OrderService', or 'cancelOrder')"
14
+ );
15
+ }
16
+
17
+ const details = repo.getSymbolDetails(args.symbol, {
18
+ domain: args.domain,
19
+ includeDependencies: args.include_dependencies,
20
+ });
21
+
22
+ if (details) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text" as const,
27
+ text: JSON.stringify(details, null, 2),
28
+ },
29
+ ],
30
+ };
31
+ }
32
+
33
+ // Fallback to fuzzy SymbolLocator to provide actionable hints
34
+ const locator = new SymbolLocator(repo);
35
+ const fallback = locator.locate(args.symbol, args.domain);
36
+
37
+ const payload = {
38
+ symbol: args.symbol,
39
+ found: false,
40
+ message: `Symbol '${args.symbol}' was not found in the deterministic catalog.`,
41
+ suggestions: fallback.suggestions.map((s) => ({
42
+ name: s.name,
43
+ similarity_score: Math.round(s.similarity_score * 100) / 100,
44
+ exact_range: {
45
+ start_line: s.line_start,
46
+ end_line: s.line_end,
47
+ total_lines: s.line_end - s.line_start + 1,
48
+ },
49
+ signature: s.signature,
50
+ file_path: s.file_path,
51
+ })),
52
+ sibling_methods: fallback.sibling_methods,
53
+ };
54
+
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text" as const,
59
+ text: JSON.stringify(payload, null, 2),
60
+ },
61
+ ],
62
+ };
63
+ }
@@ -0,0 +1,74 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import {
4
+ SymbolLocator,
5
+ type SymbolLocationResult,
6
+ } from "../../core/resolver/symbol-locator.ts";
7
+
8
+ export interface LocateSymbolArgs {
9
+ query: string;
10
+ domain?: string;
11
+ }
12
+
13
+ export function handleLocateSymbol(
14
+ repo: SeptumRepository,
15
+ config: ValidatedSeptumConfig,
16
+ args: LocateSymbolArgs
17
+ ) {
18
+ if (!args.query) {
19
+ throw new Error(
20
+ "Missing required argument: 'query' (e.g. 'OrderController::calculateTotal' or error log snippet)"
21
+ );
22
+ }
23
+
24
+ const locator = new SymbolLocator(repo);
25
+ const result: SymbolLocationResult = locator.locate(args.query, args.domain);
26
+
27
+ let formattedText = "";
28
+
29
+ if (result.found) {
30
+ formattedText = `=== SYMBOL LOCATED ===\n`;
31
+ formattedText += `Status: FOUND (Exact Match)\n`;
32
+ formattedText += `File: ${result.file_path}\n`;
33
+ if (result.exact_symbol) {
34
+ formattedText += `Symbol: ${result.exact_symbol.name} [${result.exact_symbol.kind.toUpperCase()}]\n`;
35
+ formattedText += `Signature: ${result.exact_symbol.signature}\n`;
36
+ formattedText += `Lines: ${result.exact_symbol.line_start} - ${result.exact_symbol.line_end}\n`;
37
+ }
38
+ if (result.sibling_methods && result.sibling_methods.length > 0) {
39
+ formattedText += `\nSibling Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
40
+ formattedText += ` ${result.sibling_methods.join(", ")}\n`;
41
+ }
42
+ } else {
43
+ formattedText = `=== SYMBOL NOT FOUND / DIAGNOSTIC RESOLUTION ===\n`;
44
+ formattedText += `Status: NOT FOUND\n`;
45
+ if (result.file_path) {
46
+ formattedText += `Target File: ${result.file_path}\n`;
47
+ formattedText += `Target Container: ${result.container_name || "Unknown"}\n`;
48
+ }
49
+ formattedText += `Message: ${result.message}\n`;
50
+
51
+ if (result.sibling_methods && result.sibling_methods.length > 0) {
52
+ formattedText += `\nExisting Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
53
+ formattedText += ` ${result.sibling_methods.join(", ")}\n`;
54
+ }
55
+
56
+ if (result.suggestions.length > 0) {
57
+ formattedText += `\nTop Suggestions (Ranked by Similarity):\n`;
58
+ for (const sug of result.suggestions) {
59
+ const percent = Math.round(sug.similarity_score * 100);
60
+ formattedText += ` • ${sug.name} [${percent}% match] -> lines ${sug.line_start}-${sug.line_end} (${sug.signature})\n`;
61
+ }
62
+ }
63
+ }
64
+
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: formattedText.trim(),
70
+ },
71
+ ],
72
+ metadata: result,
73
+ };
74
+ }
@@ -0,0 +1,64 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { IngestionPipeline, type IngestionMetrics } from "../../core/ingestion/pipeline.ts";
4
+ import type { DomainConfig } from "../../types/index.ts";
5
+
6
+ export interface RegisterDomainArgs {
7
+ name: string;
8
+ root: string;
9
+ description?: string;
10
+ allowed_dependencies?: string[];
11
+ forbidden_dependencies?: string[];
12
+ archetypes?: Record<string, string>;
13
+ ingest_now?: boolean;
14
+ }
15
+
16
+ export async function handleRegisterDomain(
17
+ repo: SeptumRepository,
18
+ config: ValidatedSeptumConfig,
19
+ args: RegisterDomainArgs
20
+ ) {
21
+ if (!args.name || !args.root) {
22
+ throw new Error("Missing required arguments: 'name' and 'root' are required.");
23
+ }
24
+
25
+ const domainConfig: DomainConfig = {
26
+ root: args.root,
27
+ description: args.description || `Domain: ${args.name}`,
28
+ allowed_dependencies: args.allowed_dependencies || [],
29
+ forbidden_dependencies: args.forbidden_dependencies || [],
30
+ archetypes: args.archetypes || {},
31
+ };
32
+
33
+ const domainId = repo.domains.upsertDomain(args.name, domainConfig);
34
+
35
+ config.domains[args.name] = domainConfig;
36
+
37
+ let ingestionMetrics: IngestionMetrics | null = null;
38
+ if (args.ingest_now) {
39
+ const pipeline = new IngestionPipeline(repo);
40
+ ingestionMetrics = await pipeline.run(config);
41
+ }
42
+
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text" as const,
47
+ text: JSON.stringify(
48
+ {
49
+ status: "success",
50
+ message: `Domain '${args.name}' successfully registered in SQLite SSOT.${
51
+ args.ingest_now ? " Initial ingestion completed." : ""
52
+ }`,
53
+ domain_id: domainId,
54
+ domain: args.name,
55
+ config: domainConfig,
56
+ ...(ingestionMetrics ? { ingestion_metrics: ingestionMetrics } : {}),
57
+ },
58
+ null,
59
+ 2
60
+ ),
61
+ },
62
+ ],
63
+ };
64
+ }
@@ -0,0 +1,102 @@
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { VerticalSliceTracer } from "../../core/resolver/vertical-slice-tracer.ts";
4
+ import type { VerticalSliceTraceResponse } from "../../types/index.ts";
5
+
6
+ export interface TraceVerticalSliceArgs {
7
+ query: string;
8
+ }
9
+
10
+ export function handleTraceVerticalSlice(
11
+ repo: SeptumRepository,
12
+ config: ValidatedSeptumConfig,
13
+ args: TraceVerticalSliceArgs
14
+ ) {
15
+ if (!args.query) {
16
+ throw new Error(
17
+ "Missing required argument: 'query' (e.g. 'POST /orders/{id}/status', 'OrderController@updateStatus', or task intent 'Ubah status order di dashboard')"
18
+ );
19
+ }
20
+
21
+ const tracer = new VerticalSliceTracer(repo);
22
+ const result: VerticalSliceTraceResponse = tracer.trace(args.query);
23
+
24
+ let formattedText = "";
25
+
26
+ if (result.found && result.slice) {
27
+ const s = result.slice;
28
+ formattedText = `=== VERTICAL SLICE TRACE ===\n`;
29
+ formattedText += `Query: ${result.query}\n`;
30
+ formattedText += `Status: RESOLVED\n\n`;
31
+
32
+ formattedText += `• Route:\n`;
33
+ formattedText += ` Method: ${s.route.method}\n`;
34
+ formattedText += ` URI: ${s.route.uri}\n`;
35
+ if (s.route.name) {
36
+ formattedText += ` Name: ${s.route.name}\n`;
37
+ }
38
+
39
+ if (s.request) {
40
+ formattedText += `\n• Request / Validation:\n`;
41
+ formattedText += ` Class: ${s.request.class} (${s.request.file || "inline"})\n`;
42
+ const ruleKeys = Object.keys(s.request.rules);
43
+ if (ruleKeys.length > 0) {
44
+ formattedText += ` Rules:\n`;
45
+ for (const k of ruleKeys) {
46
+ formattedText += ` - ${k} => ${s.request.rules[k]}\n`;
47
+ }
48
+ }
49
+ }
50
+
51
+ formattedText += `\n• Controller Action:\n`;
52
+ formattedText += ` Class: ${s.controller.class}@${s.controller.action}\n`;
53
+ formattedText += ` File: ${s.controller.file || "unknown"}:${s.controller.line}\n`;
54
+
55
+ if (s.model) {
56
+ formattedText += `\n• Model:\n`;
57
+ formattedText += ` Class: ${s.model.class} (${s.model.file || "unknown"})\n`;
58
+ if (s.model.fillable && s.model.fillable.length > 0) {
59
+ formattedText += ` Fillable: [${s.model.fillable.join(", ")}]\n`;
60
+ }
61
+ if (s.model.casts && Object.keys(s.model.casts).length > 0) {
62
+ formattedText += ` Casts: ${JSON.stringify(s.model.casts)}\n`;
63
+ }
64
+ }
65
+
66
+ if (s.frontend) {
67
+ formattedText += `\n• Frontend Target:\n`;
68
+ formattedText += ` Page: ${s.frontend.target}\n`;
69
+ if (s.frontend.props.length > 0) {
70
+ formattedText += ` Props: [${s.frontend.props.join(", ")}]\n`;
71
+ }
72
+ }
73
+
74
+ if (result.alternatives && result.alternatives.length > 0) {
75
+ formattedText += `\n• Other Matching Slices:\n`;
76
+ for (const alt of result.alternatives) {
77
+ formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
78
+ }
79
+ }
80
+ } else {
81
+ formattedText = `=== VERTICAL SLICE NOT FOUND ===\n`;
82
+ formattedText += `Query: ${result.query}\n`;
83
+ formattedText += `Message: ${result.message}\n`;
84
+
85
+ if (result.alternatives && result.alternatives.length > 0) {
86
+ formattedText += `\nAvailable Slices:\n`;
87
+ for (const alt of result.alternatives) {
88
+ formattedText += ` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}\n`;
89
+ }
90
+ }
91
+ }
92
+
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: formattedText.trim(),
98
+ },
99
+ ],
100
+ metadata: result,
101
+ };
102
+ }