feat(p1): complete P1 foundation stage
Some checks failed
CI Go / test (push) Has been cancelled
CI Proto / lint (push) Has been cancelled
CI Python / test (push) Has been cancelled
CI TypeScript / test (push) Has been cancelled

- monorepo: pnpm workspace + go.work + pyproject.toml + commitlint/husky
- infra: docker-compose (minimal + full profiles) + init-sql + prometheus
- arch-scan: multi-language scanner skeleton (TS/Go/Python/Proto)
- shared-proto: buf v2 + classes.proto (ClassService CRUD contract)
- api-gateway: Go/Gin + JWT HS256 auth + reverse proxy + request ID
- classes: NestJS golden template (error system + observability + middleware + CRUD + tests)
- teacher-portal: Next.js + paper-feel UI design system
- CI/CD: 4 workflows (go/ts/py/proto)
- docs: migration guide + project_rules + coding-standards + git-workflow + ui-design-system + 004 + 9 module READMEs + known-issues + spec/plan migration + roadmap
This commit is contained in:
SpecialX
2026-07-07 23:39:37 +08:00
commit 2ba4250165
100 changed files with 15242 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
{
"name": "@edu/arch-scan",
"version": "0.1.0",
"private": true,
"type": "module",
"bin": {
"arch-scan": "./scanner.ts",
"arch-query": "./query.ts"
},
"scripts": {
"scan": "tsx scanner.ts",
"query": "tsx query.ts"
},
"dependencies": {
"better-sqlite3": "^11.3.0",
"ts-morph": "^24.0.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.0",
"tsx": "^4.19.0"
}
}

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env tsx
import { createDb } from './schema.js';
const args = process.argv.slice(2);
const command = args[0];
function main(): void {
const db = createDb('arch.db');
switch (command) {
case 'stats':
printStats(db);
break;
case 'modules':
printModules(db);
break;
case 'symbols':
printSymbols(db, args[1]);
break;
case 'deps':
printDependencies(db, args[1]);
break;
case 'violations':
printViolations(db);
break;
case 'sql':
printSql(db, args[1]);
break;
default:
console.log(`Usage: arch-query <command> [args]
Commands:
stats 显示统计信息
modules 列出所有模块
symbols <name> 查询符号
deps <module> 查模块依赖
violations 查架构违规
sql <SQL> 自定义 SQL 查询`);
}
db.close();
}
function printStats(db: ReturnType<typeof createDb>): void {
const modules = (db.prepare('SELECT COUNT(*) as c FROM modules').get() as { c: number }).c;
const symbols = (db.prepare('SELECT COUNT(*) as c FROM symbols').get() as { c: number }).c;
const calls = (db.prepare('SELECT COUNT(*) as c FROM calls').get() as { c: number }).c;
const deps = (db.prepare('SELECT COUNT(*) as c FROM dependencies').get() as { c: number }).c;
console.log(`模块: ${modules}\n符号: ${symbols}\n调用: ${calls}\n依赖: ${deps}`);
}
function printModules(db: ReturnType<typeof createDb>): void {
const rows = db.prepare('SELECT name, language, service, type FROM modules ORDER BY name').all();
rows.forEach((r) => console.log(r));
}
function printSymbols(db: ReturnType<typeof createDb>, name: string | undefined): void {
if (!name) {
console.log('Usage: arch-query symbols <name>');
return;
}
const rows = db.prepare('SELECT * FROM symbols WHERE name LIKE ?').all(`%${name}%`);
rows.forEach((r) => console.log(r));
}
function printDependencies(db: ReturnType<typeof createDb>, module: string | undefined): void {
if (!module) {
console.log('Usage: arch-query deps <module>');
return;
}
const rows = db.prepare(`
SELECT m2.name as depends_on FROM dependencies d
JOIN modules m1 ON d.source_module_id = m1.id
JOIN modules m2 ON d.target_module_id = m2.id
WHERE m1.name = ?
`).all(module);
rows.forEach((r) => console.log(r));
}
function printViolations(db: ReturnType<typeof createDb>): void {
// 骨架:检查长文件、未校验权限的 Action 等
console.log('骨架实现P1 后期补全违规检测');
}
function printSql(db: ReturnType<typeof createDb>, sql: string | undefined): void {
if (!sql) {
console.log('Usage: arch-query sql "<SQL>"');
return;
}
const rows = db.prepare(sql).all();
rows.forEach((r) => console.log(r));
}
main();

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env tsx
import { createDb } from './schema.js';
import { scanTypeScript } from './scanners/ts-scanner.js';
import { scanGo } from './scanners/go-scanner.js';
import { scanPython } from './scanners/py-scanner.js';
import { scanProtobuf } from './scanners/proto-scanner.js';
import path from 'node:path';
const ROOT = process.cwd();
function main(): void {
const db = createDb(path.join(ROOT, 'arch.db'));
console.log('🔍 arch-scan: 开始多语言扫描...');
// 串行扫描(并行会导致 FOREIGN KEY 错误)
const tsStats = scanTypeScript(db, ROOT);
console.log(` TypeScript: ${tsStats.modules} 模块, ${tsStats.symbols} 符号`);
const goStats = scanGo(db, ROOT);
console.log(` Go: ${goStats.modules} 模块, ${goStats.symbols} 符号`);
const pyStats = scanPython(db, ROOT);
console.log(` Python: ${pyStats.modules} 模块, ${pyStats.symbols} 符号`);
const protoStats = scanProtobuf(db, ROOT);
console.log(` Protobuf: ${protoStats.contracts} 契约`);
console.log('✅ arch-scan 完成');
db.close();
}
main();

View File

@@ -0,0 +1,12 @@
import type { Database as DBType } from 'better-sqlite3';
interface ScanStats {
modules: number;
symbols: number;
}
export function scanGo(db: DBType, root: string): ScanStats {
// Go 扫描器骨架P1 后期用 tree-sitter-go 实现
// 当前仅扫描 go.mod 识别模块
return { modules: 0, symbols: 0 };
}

View File

@@ -0,0 +1,10 @@
import type { Database as DBType } from 'better-sqlite3';
interface ScanStats {
contracts: number;
}
export function scanProtobuf(db: DBType, root: string): ScanStats {
// Protobuf 扫描器骨架:扫描 packages/shared-proto/proto/*.proto
return { contracts: 0 };
}

View File

@@ -0,0 +1,11 @@
import type { Database as DBType } from 'better-sqlite3';
interface ScanStats {
modules: number;
symbols: number;
}
export function scanPython(db: DBType, root: string): ScanStats {
// Python 扫描器骨架P1 后期用 tree-sitter-python 实现
return { modules: 0, symbols: 0 };
}

View File

@@ -0,0 +1,38 @@
import { Project, SyntaxKind } from 'ts-morph';
import path from 'node:path';
import type { Database as DBType } from 'better-sqlite3';
interface ScanStats {
modules: number;
symbols: number;
}
export function scanTypeScript(db: DBType, root: string): ScanStats {
const project = new Project({
tsConfigFilePath: undefined,
skipAddingFilesFromTsConfig: true,
compilerOptions: {
allowJs: true,
declaration: false,
resolveJsonModule: true,
},
});
const patterns = ['services/*/src/**/*.ts', 'apps/*/src/**/*.ts', 'packages/*/src/**/*.ts'];
let modules = 0;
let symbols = 0;
const insertModule = db.prepare(
'INSERT OR IGNORE INTO modules (name, path, language, service, type) VALUES (?, ?, ?, ?, ?)'
);
const insertSymbol = db.prepare(
'INSERT INTO symbols (module_id, name, kind, language, file_path, line_start, line_end, is_exported) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
);
// 简化实现:扫描 services/*/src 目录作为模块
const servicesDir = path.join(root, 'services');
// 实际实现用 fast-glob 模式匹配
// 此处为骨架P1 后期补全
return { modules, symbols };
}

View File

@@ -0,0 +1,83 @@
import Database from 'better-sqlite3';
import type { Database as DBType } from 'better-sqlite3';
export function initSchema(db: DBType): void {
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
db.exec(`
CREATE TABLE IF NOT EXISTS modules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
path TEXT NOT NULL,
language TEXT NOT NULL DEFAULT 'ts',
service TEXT,
type TEXT NOT NULL DEFAULT 'module'
);
CREATE TABLE IF NOT EXISTS symbols (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id INTEGER NOT NULL,
name TEXT NOT NULL,
kind TEXT NOT NULL,
language TEXT NOT NULL,
file_path TEXT NOT NULL,
line_start INTEGER,
line_end INTEGER,
is_exported INTEGER DEFAULT 0,
is_public INTEGER DEFAULT 0,
FOREIGN KEY (module_id) REFERENCES modules(id)
);
CREATE TABLE IF NOT EXISTS calls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
caller_id INTEGER,
callee_id INTEGER,
callee_name TEXT NOT NULL,
file_path TEXT NOT NULL,
line INTEGER,
FOREIGN KEY (caller_id) REFERENCES symbols(id),
FOREIGN KEY (callee_id) REFERENCES symbols(id)
);
CREATE TABLE IF NOT EXISTS dependencies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_module_id INTEGER NOT NULL,
target_module_id INTEGER NOT NULL,
FOREIGN KEY (source_module_id) REFERENCES modules(id),
FOREIGN KEY (target_module_id) REFERENCES modules(id),
UNIQUE (source_module_id, target_module_id)
);
CREATE TABLE IF NOT EXISTS contracts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
file_path TEXT NOT NULL,
service TEXT,
content TEXT
);
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
producer_module_id INTEGER,
consumer_count INTEGER DEFAULT 0,
file_path TEXT,
FOREIGN KEY (producer_module_id) REFERENCES modules(id)
);
CREATE INDEX IF NOT EXISTS idx_symbols_module ON symbols(module_id);
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
CREATE INDEX IF NOT EXISTS idx_calls_caller ON calls(caller_id);
CREATE INDEX IF NOT EXISTS idx_calls_callee_name ON calls(callee_name);
CREATE INDEX IF NOT EXISTS idx_deps_source ON dependencies(source_module_id);
CREATE INDEX IF NOT EXISTS idx_deps_target ON dependencies(target_module_id);
`);
}
export function createDb(dbPath: string = 'arch.db'): DBType {
const db = new Database(dbPath);
initSchema(db);
return db;
}