septum 0.1.2 → 0.1.3

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.
@@ -1,5 +1,8 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { isAbsolute, join, basename } from "node:path";
1
3
  import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
4
  import type { SeptumRepository } from "../../core/database/repository.ts";
5
+ import type { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
3
6
  import {
4
7
  SymbolLocator,
5
8
  type SymbolLocationResult,
@@ -8,12 +11,15 @@ import {
8
11
  export interface LocateSymbolArgs {
9
12
  query: string;
10
13
  domain?: string;
14
+ workspace_path?: string;
11
15
  }
12
16
 
13
- export function handleLocateSymbol(
17
+ export async function handleLocateSymbol(
14
18
  repo: SeptumRepository,
15
19
  config: ValidatedSeptumConfig,
16
- args: LocateSymbolArgs
20
+ args: LocateSymbolArgs,
21
+ pipeline?: IngestionPipeline,
22
+ workspaceRoot?: string
17
23
  ) {
18
24
  if (!args.query) {
19
25
  throw new Error(
@@ -21,8 +27,79 @@ export function handleLocateSymbol(
21
27
  );
22
28
  }
23
29
 
30
+ const root = workspaceRoot || process.cwd();
31
+
32
+ // 1. Guarded Lazy Ingestion on Empty Catalog / Cold Start
33
+ if (repo.countFiles() === 0 && pipeline) {
34
+ try {
35
+ await pipeline.run(config);
36
+ } catch {
37
+ // Ingestion fallback
38
+ }
39
+
40
+ if (repo.countFiles() === 0) {
41
+ const diagnostic = {
42
+ status: "NOT_INDEXED",
43
+ found: false,
44
+ query: args.query,
45
+ message: `Workspace '${root}' belum terindeks dalam Septum catalog.`,
46
+ suggested_action: {
47
+ tool: "septum_register_domain",
48
+ params: { name: "core", root: "src", ingest_now: true },
49
+ },
50
+ agent_guidance:
51
+ "Panggil tool 'septum_register_domain(name: \"core\", root: \"src\", ingest_now: true)' untuk mendaftarkan dan mengindeks codebase ini, atau jalankan 'septum init' di terminal workspace.",
52
+ };
53
+
54
+ return {
55
+ content: [
56
+ {
57
+ type: "text",
58
+ text: JSON.stringify(diagnostic, null, 2),
59
+ },
60
+ ],
61
+ metadata: diagnostic,
62
+ };
63
+ }
64
+ }
65
+
24
66
  const locator = new SymbolLocator(repo);
25
- const result: SymbolLocationResult = locator.locate(args.query, args.domain);
67
+ let result: SymbolLocationResult = locator.locate(args.query, args.domain);
68
+
69
+ // 2. JIT Delta Sync: Check if located file has changed on disk since last ingest
70
+ if (result.found && result.file_path && pipeline) {
71
+ const fullPath = isAbsolute(result.file_path)
72
+ ? result.file_path
73
+ : join(root, result.file_path);
74
+ try {
75
+ const reindexed = await pipeline.ingestFile(fullPath, config);
76
+ if (reindexed) {
77
+ result = locator.locate(args.query, args.domain);
78
+ }
79
+ } catch {
80
+ // Silently fall back to existing result
81
+ }
82
+ }
83
+
84
+ // 3. Fallback on Cache Miss: Search for candidate source files matching the query terms
85
+ if (!result.found && pipeline) {
86
+ try {
87
+ const cleanTerm = args.query.replace(/[^a-zA-Z0-9_]/g, " ").trim().split(/\s+/)[0] || "";
88
+ if (cleanTerm.length >= 3) {
89
+ const candidateFiles = findCandidateFiles(root, cleanTerm);
90
+ let anyReindexed = false;
91
+ for (const candidate of candidateFiles) {
92
+ const reindexed = await pipeline.ingestFile(candidate, config);
93
+ if (reindexed) anyReindexed = true;
94
+ }
95
+ if (anyReindexed) {
96
+ result = locator.locate(args.query, args.domain);
97
+ }
98
+ }
99
+ } catch {
100
+ // Ignore candidate scan errors
101
+ }
102
+ }
26
103
 
27
104
  let formattedText = "";
28
105
 
@@ -72,3 +149,42 @@ export function handleLocateSymbol(
72
149
  metadata: result,
73
150
  };
74
151
  }
152
+
153
+ /**
154
+ * Searches for potential candidate files whose names resemble parts of the query term.
155
+ */
156
+ function findCandidateFiles(root: string, term: string): string[] {
157
+ const matched: string[] = [];
158
+ const lowerTerm = term.toLowerCase().replace(/_/g, "");
159
+ const searchDirs = ["src", "app", "lib"].map((d) => join(root, d)).filter((d) => existsSync(d));
160
+
161
+ function scan(dir: string, depth: number = 0) {
162
+ if (depth > 4) return;
163
+ try {
164
+ const entries = readdirSync(dir, { withFileTypes: true });
165
+ for (const e of entries) {
166
+ if (e.name.startsWith(".") || e.name === "node_modules" || e.name === "dist") continue;
167
+ const full = join(dir, e.name);
168
+ if (e.isDirectory()) {
169
+ scan(full, depth + 1);
170
+ } else if (e.isFile() && /\.(ts|tsx|js|jsx|php|py|go)$/i.test(e.name)) {
171
+ const lowerName = e.name.toLowerCase().replace(/[-_.]/g, "");
172
+ if (lowerName.includes(lowerTerm) || lowerTerm.includes(lowerName.replace(/\.[^.]+$/, ""))) {
173
+ matched.push(full);
174
+ if (matched.length >= 5) return;
175
+ }
176
+ }
177
+ }
178
+ } catch {
179
+ // Ignore
180
+ }
181
+ }
182
+
183
+ for (const sDir of searchDirs) {
184
+ scan(sDir);
185
+ if (matched.length >= 5) break;
186
+ }
187
+
188
+ return matched;
189
+ }
190
+
@@ -18,7 +18,8 @@ export function handleTraceVerticalSlice(
18
18
  );
19
19
  }
20
20
 
21
- const tracer = new VerticalSliceTracer(repo);
21
+ const workspaceRoot = (config.settings as Record<string, unknown>)?.workspace_root as string || process.cwd();
22
+ const tracer = new VerticalSliceTracer(repo, workspaceRoot);
22
23
  const result: VerticalSliceTraceResponse = tracer.trace(args.query);
23
24
 
24
25
  let formattedText = "";
@@ -13,6 +13,7 @@ export type ArchetypeKind =
13
13
  | "request"
14
14
  | "resource"
15
15
  | "util"
16
+ | "view"
16
17
  | "unknown";
17
18
 
18
19
  export type SymbolKind =
@@ -23,7 +24,9 @@ export type SymbolKind =
23
24
  | "method"
24
25
  | "function"
25
26
  | "type"
26
- | "property";
27
+ | "property"
28
+ | "component"
29
+ | "view";
27
30
 
28
31
  export type Visibility = "public" | "protected" | "private";
29
32