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.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/bin/septum.ts +4 -0
- package/package.json +62 -0
- package/src/cli/commands/check.ts +161 -0
- package/src/cli/commands/hook.ts +188 -0
- package/src/cli/commands/ingest.ts +29 -0
- package/src/cli/commands/init.ts +211 -0
- package/src/cli/commands/locate.ts +61 -0
- package/src/cli/commands/query.ts +64 -0
- package/src/cli/commands/serve.ts +5 -0
- package/src/cli/commands/slice.ts +90 -0
- package/src/cli/commands/sync.ts +31 -0
- package/src/cli/index.ts +178 -0
- package/src/cli/templates/hooks.ts +75 -0
- package/src/cli/templates/rules.ts +38 -0
- package/src/cli/templates/skill.ts +66 -0
- package/src/core/boundary/evaluator.ts +210 -0
- package/src/core/config/loader.ts +84 -0
- package/src/core/config/schema.ts +39 -0
- package/src/core/database/client.ts +132 -0
- package/src/core/database/repositories/dependency.repository.ts +279 -0
- package/src/core/database/repositories/domain.repository.ts +198 -0
- package/src/core/database/repositories/file.repository.ts +99 -0
- package/src/core/database/repositories/meta.repository.ts +33 -0
- package/src/core/database/repositories/symbol.repository.ts +424 -0
- package/src/core/database/repository.ts +242 -0
- package/src/core/database/schema.sql +92 -0
- package/src/core/discovery/topology-detector.ts +418 -0
- package/src/core/ingestion/hasher.ts +5 -0
- package/src/core/ingestion/pipeline.ts +438 -0
- package/src/core/parser/boundary-tracker.ts +128 -0
- package/src/core/parser/extractors/base.ts +60 -0
- package/src/core/parser/extractors/frontend.ts +194 -0
- package/src/core/parser/extractors/go.ts +176 -0
- package/src/core/parser/extractors/laravel-semantic.ts +426 -0
- package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -0
- package/src/core/parser/extractors/nestjs-semantic.ts +119 -0
- package/src/core/parser/extractors/php.ts +149 -0
- package/src/core/parser/extractors/python.ts +226 -0
- package/src/core/parser/extractors/registry.ts +49 -0
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -0
- package/src/core/parser/extractors/typescript.ts +177 -0
- package/src/core/parser/tree-sitter.ts +39 -0
- package/src/core/resolver/call-graph-tracer.ts +668 -0
- package/src/core/resolver/module-resolver.ts +323 -0
- package/src/core/resolver/symbol-locator.ts +405 -0
- package/src/core/resolver/vertical-slice-tracer.ts +236 -0
- package/src/core/session/session-manager.ts +56 -0
- package/src/core/telemetry/telemetry.ts +86 -0
- package/src/index.ts +18 -0
- package/src/mcp/schemas.ts +68 -0
- package/src/mcp/server.ts +602 -0
- package/src/mcp/tools/check-boundary.ts +102 -0
- package/src/mcp/tools/get-domain-catalog.ts +91 -0
- package/src/mcp/tools/get-feature-context.ts +116 -0
- package/src/mcp/tools/get-symbol-hotspots.ts +49 -0
- package/src/mcp/tools/get-symbol-impact.ts +26 -0
- package/src/mcp/tools/get-symbol.ts +63 -0
- package/src/mcp/tools/locate-symbol.ts +74 -0
- package/src/mcp/tools/register-domain.ts +64 -0
- package/src/mcp/tools/trace-vertical-slice.ts +102 -0
- package/src/types/index.ts +296 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { handleIngestCommand } from "./ingest.ts";
|
|
4
|
+
import {
|
|
5
|
+
SEPTUM_HOOKS_CONFIG,
|
|
6
|
+
SEPTUM_POST_WRITE_SCRIPT,
|
|
7
|
+
SEPTUM_PRE_WRITE_SCRIPT,
|
|
8
|
+
} from "../templates/hooks.ts";
|
|
9
|
+
import { SEPTUM_RULES_TEMPLATE } from "../templates/rules.ts";
|
|
10
|
+
import { SEPTUM_MAP_SKILL_TEMPLATE } from "../templates/skill.ts";
|
|
11
|
+
import { TopologyDetector } from "../../core/discovery/topology-detector.ts";
|
|
12
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
13
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
14
|
+
|
|
15
|
+
export async function handleInitCommand(force: boolean = false): Promise<void> {
|
|
16
|
+
console.log("[Septum] Initializing Septum bounded-context environment (Zero-Config)...\n");
|
|
17
|
+
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const dbPath = ".septum/septum.db";
|
|
20
|
+
|
|
21
|
+
// 1. Auto-discover project topology and persist to SQLite SSOT
|
|
22
|
+
console.log(" • Analyzing codebase topology and modular boundaries...");
|
|
23
|
+
const db = new SeptumDatabase(dbPath);
|
|
24
|
+
const repo = new SeptumRepository(db.raw);
|
|
25
|
+
const domains = TopologyDetector.discoverAndPersist(repo, cwd);
|
|
26
|
+
const domainNames = Object.keys(domains);
|
|
27
|
+
console.log(` ✓ Discovered and registered ${domainNames.length} domains directly in SQLite (${dbPath}):`);
|
|
28
|
+
for (const name of domainNames) {
|
|
29
|
+
console.log(` - ${name}: root -> '${domains[name].root}'`);
|
|
30
|
+
}
|
|
31
|
+
db.close();
|
|
32
|
+
|
|
33
|
+
// 2. Ensure .gitignore excludes local SQLite cache
|
|
34
|
+
ensureGitIgnore(cwd);
|
|
35
|
+
|
|
36
|
+
// 3. Install Git Pre-Commit Hook
|
|
37
|
+
installGitHook(cwd);
|
|
38
|
+
|
|
39
|
+
// 4. Scaffold Agent Customizations (.agents/skills, .agents/hooks, .agents/rules)
|
|
40
|
+
scaffoldAgentCustomizations(cwd);
|
|
41
|
+
|
|
42
|
+
// 5. Run initial codebase ingestion
|
|
43
|
+
console.log("\n[Septum] Running initial ingestion to build deterministic structural catalog...");
|
|
44
|
+
try {
|
|
45
|
+
await handleIngestCommand();
|
|
46
|
+
console.log("\n✓ Septum successfully initialized and ready for production use!");
|
|
47
|
+
console.log(" • Pre-commit hook is active (runs 'septum check --staged' before each commit).");
|
|
48
|
+
console.log(" • AI Agent skill '/septum-map' and boundary hooks installed in '.agents/'.");
|
|
49
|
+
console.log(" • MCP server is ready for AI coding agents via 'septum serve'.\n");
|
|
50
|
+
} catch (err: unknown) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
console.warn(`\n[Septum Warning] Initial ingestion encountered an error: ${message}`);
|
|
53
|
+
console.warn("Run 'septum ingest' manually to retry.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureGitIgnore(cwd: string): void {
|
|
58
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
59
|
+
const entriesToEnsure = [".septum/*.db*", ".septum/"];
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(gitignorePath)) {
|
|
62
|
+
let content = fs.readFileSync(gitignorePath, "utf-8");
|
|
63
|
+
const missing = entriesToEnsure.filter((entry) => !content.includes(entry));
|
|
64
|
+
|
|
65
|
+
if (missing.length > 0) {
|
|
66
|
+
const addition = `\n# Septum local database and cache\n${missing.join("\n")}\n`;
|
|
67
|
+
fs.appendFileSync(gitignorePath, addition, "utf-8");
|
|
68
|
+
console.log(" ✓ Updated .gitignore to exclude local SQLite cache (.septum/).");
|
|
69
|
+
} else {
|
|
70
|
+
console.log(" ✓ .gitignore already configured for Septum.");
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
gitignorePath,
|
|
75
|
+
`# Septum local database and cache\n${entriesToEnsure.join("\n")}\n`,
|
|
76
|
+
"utf-8"
|
|
77
|
+
);
|
|
78
|
+
console.log(" ✓ Created .gitignore excluding local SQLite cache (.septum/).");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function installGitHook(cwd: string): void {
|
|
83
|
+
const gitDir = path.join(cwd, ".git");
|
|
84
|
+
if (!fs.existsSync(gitDir)) {
|
|
85
|
+
console.log(" ⚠ No .git directory found. Skipping pre-commit hook installation.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const huskyDir = path.join(cwd, ".husky");
|
|
90
|
+
if (fs.existsSync(huskyDir)) {
|
|
91
|
+
const huskyPreCommit = path.join(huskyDir, "pre-commit");
|
|
92
|
+
const hookCommand = "[ -f ./bin/septum.ts ] && bun run ./bin/septum.ts check --staged || command -v septum >/dev/null 2>&1 && septum check --staged || [ -f ./node_modules/.bin/septum ] && ./node_modules/.bin/septum check --staged || bunx septum check --staged";
|
|
93
|
+
if (fs.existsSync(huskyPreCommit)) {
|
|
94
|
+
const existing = fs.readFileSync(huskyPreCommit, "utf-8");
|
|
95
|
+
if (!existing.includes("septum check")) {
|
|
96
|
+
fs.appendFileSync(huskyPreCommit, `\n${hookCommand}\n`, "utf-8");
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
fs.writeFileSync(huskyPreCommit, `#!/usr/bin/env sh\n${hookCommand}\n`, "utf-8");
|
|
100
|
+
try {
|
|
101
|
+
fs.chmodSync(huskyPreCommit, 0o755);
|
|
102
|
+
} catch (err: unknown) {
|
|
103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
104
|
+
console.warn(` Notice: Could not set executable permission on '${huskyPreCommit}': ${message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log(" ✓ Installed pre-commit hook in .husky/pre-commit.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const hooksDir = path.join(gitDir, "hooks");
|
|
112
|
+
if (!fs.existsSync(hooksDir)) {
|
|
113
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const preCommitHook = path.join(hooksDir, "pre-commit");
|
|
117
|
+
const hookScript = `#!/usr/bin/env sh
|
|
118
|
+
# Septum deterministic boundary pre-commit hook
|
|
119
|
+
echo "[Septum Hook] Auditing staged changes against domain boundaries..."
|
|
120
|
+
|
|
121
|
+
if command -v bun >/dev/null 2>&1 && [ -f "./bin/septum.ts" ]; then
|
|
122
|
+
bun run ./bin/septum.ts check --staged
|
|
123
|
+
elif command -v septum >/dev/null 2>&1; then
|
|
124
|
+
septum check --staged
|
|
125
|
+
elif [ -f "./node_modules/.bin/septum" ]; then
|
|
126
|
+
./node_modules/.bin/septum check --staged
|
|
127
|
+
elif command -v bunx >/dev/null 2>&1; then
|
|
128
|
+
bunx septum check --staged
|
|
129
|
+
else
|
|
130
|
+
echo "[Septum Hook] Notice: Septum executable not found. Skipping boundary check."
|
|
131
|
+
fi
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(preCommitHook, hookScript, "utf-8");
|
|
135
|
+
try {
|
|
136
|
+
fs.chmodSync(preCommitHook, 0o755);
|
|
137
|
+
} catch (err: unknown) {
|
|
138
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
+
console.warn(` Notice: Could not set executable permission on '${preCommitHook}': ${message}`);
|
|
140
|
+
}
|
|
141
|
+
console.log(" ✓ Installed executable git pre-commit hook at '.git/hooks/pre-commit'.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function scaffoldAgentCustomizations(cwd: string): void {
|
|
145
|
+
const agentsDir = path.join(cwd, ".agents");
|
|
146
|
+
|
|
147
|
+
// 1. Skill: .agents/skills/septum-map/SKILL.md
|
|
148
|
+
const skillDir = path.join(agentsDir, "skills", "septum-map");
|
|
149
|
+
if (!fs.existsSync(skillDir)) {
|
|
150
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
const skillPath = path.join(skillDir, "SKILL.md");
|
|
153
|
+
fs.writeFileSync(skillPath, SEPTUM_MAP_SKILL_TEMPLATE, "utf-8");
|
|
154
|
+
console.log(" ✓ Deployed AI Agent skill: '.agents/skills/septum-map/SKILL.md'.");
|
|
155
|
+
|
|
156
|
+
// 2. Rule: .agents/rules/septum-boundary.md
|
|
157
|
+
const rulesDir = path.join(agentsDir, "rules");
|
|
158
|
+
if (!fs.existsSync(rulesDir)) {
|
|
159
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
const rulePath = path.join(rulesDir, "septum-boundary.md");
|
|
162
|
+
fs.writeFileSync(rulePath, SEPTUM_RULES_TEMPLATE, "utf-8");
|
|
163
|
+
console.log(" ✓ Deployed AI Agent boundary rules: '.agents/rules/septum-boundary.md'.");
|
|
164
|
+
|
|
165
|
+
// 3. Hook Script: .agents/hooks/septum-pre-write.sh
|
|
166
|
+
const hooksDir = path.join(agentsDir, "hooks");
|
|
167
|
+
if (!fs.existsSync(hooksDir)) {
|
|
168
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
const preWriteHook = path.join(hooksDir, "septum-pre-write.sh");
|
|
171
|
+
fs.writeFileSync(preWriteHook, SEPTUM_PRE_WRITE_SCRIPT, "utf-8");
|
|
172
|
+
try {
|
|
173
|
+
fs.chmodSync(preWriteHook, 0o755);
|
|
174
|
+
} catch (err: unknown) {
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
console.warn(` Notice: Could not set executable permission on '${preWriteHook}': ${message}`);
|
|
177
|
+
}
|
|
178
|
+
console.log(" ✓ Deployed pre-write hook script: '.agents/hooks/septum-pre-write.sh'.");
|
|
179
|
+
|
|
180
|
+
// 3b. Post-Write Hook Script: .agents/hooks/septum-post-write.sh
|
|
181
|
+
const postWriteHook = path.join(hooksDir, "septum-post-write.sh");
|
|
182
|
+
fs.writeFileSync(postWriteHook, SEPTUM_POST_WRITE_SCRIPT, "utf-8");
|
|
183
|
+
try {
|
|
184
|
+
fs.chmodSync(postWriteHook, 0o755);
|
|
185
|
+
} catch (err: unknown) {
|
|
186
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
187
|
+
console.warn(` Notice: Could not set executable permission on '${postWriteHook}': ${message}`);
|
|
188
|
+
}
|
|
189
|
+
console.log(" ✓ Deployed post-write hook script: '.agents/hooks/septum-post-write.sh'.");
|
|
190
|
+
|
|
191
|
+
// 4. Hooks Config: .agents/hooks.json
|
|
192
|
+
const hooksJsonPath = path.join(agentsDir, "hooks.json");
|
|
193
|
+
let existingHooks: Record<string, unknown> = {};
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(hooksJsonPath)) {
|
|
196
|
+
try {
|
|
197
|
+
const raw = fs.readFileSync(hooksJsonPath, "utf-8");
|
|
198
|
+
existingHooks = JSON.parse(raw);
|
|
199
|
+
} catch {
|
|
200
|
+
// Malformed JSON fallback
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const mergedHooks = {
|
|
205
|
+
...existingHooks,
|
|
206
|
+
...SEPTUM_HOOKS_CONFIG,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(hooksJsonPath, JSON.stringify(mergedHooks, null, 2) + "\n", "utf-8");
|
|
210
|
+
console.log(" ✓ Configured AI Agent lifecycle hooks: '.agents/hooks.json'.");
|
|
211
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
|
|
5
|
+
|
|
6
|
+
export async function handleLocateCommand(
|
|
7
|
+
query: string,
|
|
8
|
+
options?: { domain?: string; json?: boolean }
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const config = ConfigLoader.load();
|
|
11
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
12
|
+
const repo = new SeptumRepository(db.raw);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const locator = new SymbolLocator(repo);
|
|
16
|
+
const result = locator.locate(query, options?.domain);
|
|
17
|
+
|
|
18
|
+
if (options?.json) {
|
|
19
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (result.found) {
|
|
24
|
+
console.log(`\n✅ Symbol Found:`);
|
|
25
|
+
console.log(` File: ${result.file_path}`);
|
|
26
|
+
if (result.exact_symbol) {
|
|
27
|
+
console.log(` Symbol: ${result.exact_symbol.name} [${result.exact_symbol.kind}]`);
|
|
28
|
+
console.log(` Signature: ${result.exact_symbol.signature}`);
|
|
29
|
+
console.log(` Lines: ${result.exact_symbol.line_start} - ${result.exact_symbol.line_end}`);
|
|
30
|
+
}
|
|
31
|
+
if (result.sibling_methods && result.sibling_methods.length > 0) {
|
|
32
|
+
console.log(`\n Available Methods in Container:`);
|
|
33
|
+
console.log(` ${result.sibling_methods.join(", ")}`);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`\n❌ Diagnostic Result:`);
|
|
37
|
+
if (result.file_path) {
|
|
38
|
+
console.log(` File: ${result.file_path}`);
|
|
39
|
+
console.log(` Container: ${result.container_name}`);
|
|
40
|
+
}
|
|
41
|
+
console.log(` Message: ${result.message}`);
|
|
42
|
+
|
|
43
|
+
if (result.sibling_methods && result.sibling_methods.length > 0) {
|
|
44
|
+
console.log(`\n Available Methods:`);
|
|
45
|
+
console.log(` ${result.sibling_methods.join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (result.suggestions.length > 0) {
|
|
49
|
+
console.log(`\n Suggestions (Ranked by Match):`);
|
|
50
|
+
for (const sug of result.suggestions) {
|
|
51
|
+
const matchPercent = Math.round(sug.similarity_score * 100);
|
|
52
|
+
console.log(
|
|
53
|
+
` • ${sug.name} (${matchPercent}% match) [line ${sug.line_start}] -> ${sug.signature}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
db.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
|
|
5
|
+
export async function handleQueryCommand(domainName?: string, archetype?: string, jsonOutput?: boolean): Promise<void> {
|
|
6
|
+
const config = ConfigLoader.load();
|
|
7
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
8
|
+
const repo = new SeptumRepository(db.raw);
|
|
9
|
+
|
|
10
|
+
if (!domainName) {
|
|
11
|
+
const allDomains = repo.getAllDomains();
|
|
12
|
+
if (allDomains.length === 0) {
|
|
13
|
+
console.log("[Septum] No domains found in database. Run 'septum ingest' first.");
|
|
14
|
+
db.close();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log("=== Registered Domains in Septum ===");
|
|
19
|
+
for (const d of allDomains) {
|
|
20
|
+
console.log(`- ${d.name} (root: ${d.root_path})`);
|
|
21
|
+
}
|
|
22
|
+
db.close();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const catalog = repo.getDomainCatalog(domainName, archetype);
|
|
27
|
+
if (!catalog) {
|
|
28
|
+
console.error(`[Septum Error] Domain '${domainName}' not found in database. Run 'septum ingest' first.`);
|
|
29
|
+
db.close();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (jsonOutput) {
|
|
34
|
+
console.log(JSON.stringify(catalog, null, 2));
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`=================================================`);
|
|
37
|
+
console.log(` Domain: ${catalog.domain.toUpperCase()} (Root: ${catalog.root})`);
|
|
38
|
+
console.log(`=================================================`);
|
|
39
|
+
console.log(` Allowed Dependencies: [${catalog.allowed_dependencies.join(", ")}]`);
|
|
40
|
+
console.log(` Forbidden Dependencies: [${catalog.forbidden_dependencies.join(", ")}]`);
|
|
41
|
+
console.log(` Total Files Cataloged: ${catalog.files.length}`);
|
|
42
|
+
console.log(`-------------------------------------------------`);
|
|
43
|
+
|
|
44
|
+
for (const file of catalog.files) {
|
|
45
|
+
console.log(`\n File: ${file.path} [archetype: ${file.archetype}]`);
|
|
46
|
+
for (const sym of file.symbols) {
|
|
47
|
+
console.log(` • ${sym.kind.toUpperCase()} ${sym.name}`);
|
|
48
|
+
if (sym.methods && sym.methods.length > 0) {
|
|
49
|
+
for (const m of sym.methods) {
|
|
50
|
+
console.log(` - ${m}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (sym.signatures && sym.signatures.length > 0) {
|
|
54
|
+
for (const s of sym.signatures) {
|
|
55
|
+
console.log(` - ${s}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(`\n=================================================`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
db.close();
|
|
64
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
import { VerticalSliceTracer } from "../../core/resolver/vertical-slice-tracer.ts";
|
|
5
|
+
|
|
6
|
+
export async function handleSliceCommand(
|
|
7
|
+
query: string,
|
|
8
|
+
options?: { json?: boolean }
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const config = ConfigLoader.load();
|
|
11
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
12
|
+
const repo = new SeptumRepository(db.raw);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const tracer = new VerticalSliceTracer(repo);
|
|
16
|
+
const result = tracer.trace(query);
|
|
17
|
+
|
|
18
|
+
if (options?.json) {
|
|
19
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
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
|
+
|
|
67
|
+
if (result.alternatives && result.alternatives.length > 0) {
|
|
68
|
+
console.log(`\n • Alternative Matches:`);
|
|
69
|
+
for (const alt of result.alternatives) {
|
|
70
|
+
console.log(` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log(`\n=================================================\n`);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(`\n❌ Vertical Slice Not Found:`);
|
|
76
|
+
console.log(` Query: ${result.query}`);
|
|
77
|
+
console.log(` Message: ${result.message}`);
|
|
78
|
+
|
|
79
|
+
if (result.alternatives && result.alternatives.length > 0) {
|
|
80
|
+
console.log(`\n Available Slices:`);
|
|
81
|
+
for (const alt of result.alternatives) {
|
|
82
|
+
console.log(` - ${alt.method} ${alt.uri} -> ${alt.controller}@${alt.action}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.log(``);
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
db.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ConfigLoader } from "../../core/config/loader.ts";
|
|
2
|
+
import { SeptumDatabase } from "../../core/database/client.ts";
|
|
3
|
+
import { SeptumRepository } from "../../core/database/repository.ts";
|
|
4
|
+
import { IngestionPipeline } from "../../core/ingestion/pipeline.ts";
|
|
5
|
+
|
|
6
|
+
export interface SyncCommandOptions {
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function handleSyncCommand(options: SyncCommandOptions = {}): Promise<void> {
|
|
11
|
+
const config = ConfigLoader.load();
|
|
12
|
+
const db = new SeptumDatabase(config.settings.db_path);
|
|
13
|
+
const repo = new SeptumRepository(db.raw);
|
|
14
|
+
const pipeline = new IngestionPipeline(repo);
|
|
15
|
+
|
|
16
|
+
const metrics = await pipeline.run(config);
|
|
17
|
+
|
|
18
|
+
if (options.json) {
|
|
19
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(
|
|
22
|
+
`[Septum Sync] Ingested in ${metrics.duration_ms.toFixed(0)}ms: ` +
|
|
23
|
+
`${metrics.files_updated} updated/added, ` +
|
|
24
|
+
`${metrics.files_skipped} unchanged, ` +
|
|
25
|
+
`${metrics.symbols_indexed} symbols, ` +
|
|
26
|
+
`${metrics.dependencies_indexed} deps.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
db.close();
|
|
31
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { SessionManager } from "../core/session/session-manager.ts";
|
|
2
|
+
import { handleCheckCommand } from "./commands/check.ts";
|
|
3
|
+
import { handleHookCommand } from "./commands/hook.ts";
|
|
4
|
+
import { handleIngestCommand } from "./commands/ingest.ts";
|
|
5
|
+
import { handleInitCommand } from "./commands/init.ts";
|
|
6
|
+
import { handleLocateCommand } from "./commands/locate.ts";
|
|
7
|
+
import { handleQueryCommand } from "./commands/query.ts";
|
|
8
|
+
import { handleServeCommand } from "./commands/serve.ts";
|
|
9
|
+
import { handleSliceCommand } from "./commands/slice.ts";
|
|
10
|
+
import { handleSyncCommand } from "./commands/sync.ts";
|
|
11
|
+
|
|
12
|
+
export async function runCLI(argv: string[]): Promise<void> {
|
|
13
|
+
const args = argv.slice(2);
|
|
14
|
+
const command = args[0];
|
|
15
|
+
|
|
16
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
17
|
+
printHelp();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
22
|
+
console.log("Septum v0.1.0");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
switch (command) {
|
|
28
|
+
case "init": {
|
|
29
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
30
|
+
await handleInitCommand(force);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "ingest": {
|
|
35
|
+
await handleIngestCommand();
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "sync": {
|
|
40
|
+
const json = args.includes("--json");
|
|
41
|
+
await handleSyncCommand({ json });
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case "query": {
|
|
46
|
+
const domainName = args[1] && !args[1].startsWith("-") ? args[1] : undefined;
|
|
47
|
+
let archetype: string | undefined;
|
|
48
|
+
const archIndex = args.indexOf("--archetype");
|
|
49
|
+
if (archIndex !== -1 && args[archIndex + 1]) {
|
|
50
|
+
archetype = args[archIndex + 1];
|
|
51
|
+
}
|
|
52
|
+
const jsonOutput = args.includes("--json");
|
|
53
|
+
await handleQueryCommand(domainName, archetype, jsonOutput);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case "locate": {
|
|
58
|
+
const query = args[1];
|
|
59
|
+
if (!query) {
|
|
60
|
+
console.error("Usage: septum locate <symbol-or-error> [--domain <domain>] [--json]");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
let domain: string | undefined;
|
|
64
|
+
const domIndex = args.indexOf("--domain");
|
|
65
|
+
if (domIndex !== -1 && args[domIndex + 1]) {
|
|
66
|
+
domain = args[domIndex + 1];
|
|
67
|
+
}
|
|
68
|
+
const json = args.includes("--json");
|
|
69
|
+
await handleLocateCommand(query, { domain, json });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case "slice": {
|
|
74
|
+
const query = args[1];
|
|
75
|
+
if (!query) {
|
|
76
|
+
console.error("Usage: septum slice <route-or-intent> [--json]");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const json = args.includes("--json");
|
|
80
|
+
await handleSliceCommand(query, { json });
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
case "check": {
|
|
85
|
+
const strict = args.includes("--strict");
|
|
86
|
+
const staged = args.includes("--staged");
|
|
87
|
+
let feature: string | undefined;
|
|
88
|
+
const featIndex = args.indexOf("--feature");
|
|
89
|
+
if (featIndex !== -1 && args[featIndex + 1]) {
|
|
90
|
+
feature = args[featIndex + 1];
|
|
91
|
+
}
|
|
92
|
+
await handleCheckCommand({ strict, staged, feature });
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case "serve": {
|
|
97
|
+
await handleServeCommand();
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "feature": {
|
|
102
|
+
const sub = args[1];
|
|
103
|
+
if (sub === "clear") {
|
|
104
|
+
const cleared = SessionManager.clearActiveSession(process.cwd());
|
|
105
|
+
if (cleared) {
|
|
106
|
+
console.log("✓ Active feature session successfully cleared.");
|
|
107
|
+
} else {
|
|
108
|
+
console.log("ℹ No active feature session found.");
|
|
109
|
+
}
|
|
110
|
+
} else if (sub === "status" || !sub) {
|
|
111
|
+
const current = SessionManager.getActiveSession(process.cwd());
|
|
112
|
+
if (current) {
|
|
113
|
+
console.log(`\nActive Feature Session:`);
|
|
114
|
+
console.log(`▶ Feature Key : ${current.feature_key}`);
|
|
115
|
+
console.log(`▶ Domain : ${current.domain}`);
|
|
116
|
+
console.log(`▶ Locked At : ${current.locked_at}`);
|
|
117
|
+
console.log(`▶ Touchpoints : ${current.touchpoints.join(", ")}\n`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log("ℹ No active feature session. All domain files accessible according to boundary rules.");
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.error(`Unknown feature subcommand: '${sub}'. Use 'status' or 'clear'.`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "hook": {
|
|
129
|
+
await handleHookCommand(args);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
default: {
|
|
134
|
+
console.error(`[Septum] Unknown command: '${command}'`);
|
|
135
|
+
console.error("Run 'septum --help' for available commands.");
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
console.error(`\x1b[31m[Septum Error]\x1b[0m ${message}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function printHelp(): void {
|
|
147
|
+
console.log(`
|
|
148
|
+
Septum — Deterministic Bounded-Context & Structural File Catalog
|
|
149
|
+
Version: 0.1.0
|
|
150
|
+
|
|
151
|
+
USAGE:
|
|
152
|
+
septum <command> [options]
|
|
153
|
+
|
|
154
|
+
COMMANDS:
|
|
155
|
+
init Initialize configuration, gitignore, pre-commit hook, and ingest catalog
|
|
156
|
+
Options:
|
|
157
|
+
--force, -f Overwrite existing catalog and configuration
|
|
158
|
+
ingest Scan codebase, parse AST, and synchronize .septum/septum.db
|
|
159
|
+
sync Fast incremental synchronization of changed files to SQLite SSOT
|
|
160
|
+
Options:
|
|
161
|
+
--json Output formatted JSON metrics
|
|
162
|
+
query [domain] Query structural catalog for a domain or list all domains
|
|
163
|
+
Options:
|
|
164
|
+
--archetype <name> Filter by archetype (service, model, controller)
|
|
165
|
+
--json Output formatted JSON
|
|
166
|
+
check Audit codebase for cross-domain boundary violations
|
|
167
|
+
Options:
|
|
168
|
+
--staged Audit only git staged changes (ideal for pre-commit)
|
|
169
|
+
--feature <name> Enforce feature-specific allowed_touchpoints
|
|
170
|
+
--strict Exit with code 1 on any violation
|
|
171
|
+
feature [status|clear] View or release active feature context session lock
|
|
172
|
+
serve Start Model Context Protocol (MCP) server over stdio
|
|
173
|
+
|
|
174
|
+
FLAGS:
|
|
175
|
+
-h, --help Show this help message
|
|
176
|
+
-v, --version Show current version
|
|
177
|
+
`);
|
|
178
|
+
}
|