Archived
M0: 工作区骨架 + 五道门禁 + 环境自举
- pnpm workspaces / tsconfig 双工程(node · web)/ 路径别名(§11.1) - eslint flat(§11.3 + §12.5 可机检项)/ dependency-cruiser 基线八条 + JCAI 增补(§11.4) - tools/module-tiers.json + gen-depcruise-tiers.ts 生成 tier-order 逐层规则 - tools/api-report.mjs 契约快照只留契约面(ADR-005) - docker-compose(PG16)+ db/init 四 schema;种子数据五岗位五员工冻结为常量 - docs/debt.md · docs/adr/ADR-005 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 契约快照门禁 —— 编码规范 §11.5。
|
||||
*
|
||||
* tsc --emitDeclarationOnly 出全量 .d.ts,只留**契约面**落 api-report/:
|
||||
* - packages/contracts/** (前后端唯一共享点,§8.1)
|
||||
* - packages/<pkg>/src/index.d.ts
|
||||
* - apps/api/src/modules/<m>/{index,contract,port}.d.ts
|
||||
* 然后与 git 比对:改 internal/ 时 api-report/ 纹丝不动;碰了公开契约,diff 立刻可见。
|
||||
*/
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
||||
import { dirname, join, relative, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const tmp = join(root, '.api-tmp');
|
||||
const out = join(root, 'api-report');
|
||||
|
||||
function run(cmd, args, opts = {}) {
|
||||
return execFileSync(cmd, args, { cwd: root, encoding: 'utf8', stdio: 'pipe', ...opts });
|
||||
}
|
||||
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
|
||||
try {
|
||||
run('node', [
|
||||
join(root, 'node_modules', 'typescript', 'bin', 'tsc'),
|
||||
'-p',
|
||||
'tsconfig.node.json',
|
||||
'--emitDeclarationOnly',
|
||||
'--declaration',
|
||||
'--noEmit',
|
||||
'false',
|
||||
'--outDir',
|
||||
'.api-tmp',
|
||||
]);
|
||||
} catch (err) {
|
||||
process.stderr.write((err.stdout ?? '') + (err.stderr ?? ''));
|
||||
process.stderr.write('\napi:check —— 声明生成失败,先修 tsc。\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** 契约面判据:相对 .api-tmp 的路径 */
|
||||
function isContractFace(rel) {
|
||||
const p = rel.split(sep).join('/');
|
||||
if (p.startsWith('packages/contracts/')) return true;
|
||||
if (/^packages\/[^/]+\/src\/index\.d\.ts$/.test(p)) return true;
|
||||
if (/^apps\/api\/src\/modules\/[^/]+\/(index|contract|port)\.d\.ts$/.test(p)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function walk(dir, acc = []) {
|
||||
if (!existsSync(dir)) return acc;
|
||||
for (const name of readdirSync(dir)) {
|
||||
const full = join(dir, name);
|
||||
if (statSync(full).isDirectory()) walk(full, acc);
|
||||
else if (full.endsWith('.d.ts')) acc.push(full);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
rmSync(out, { recursive: true, force: true });
|
||||
mkdirSync(out, { recursive: true });
|
||||
|
||||
let kept = 0;
|
||||
for (const file of walk(tmp)) {
|
||||
const rel = relative(tmp, file);
|
||||
if (!isContractFace(rel)) continue;
|
||||
const dest = join(out, rel);
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
cpSync(file, dest);
|
||||
kept += 1;
|
||||
}
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
|
||||
process.stdout.write(`api:check —— 契约面 ${kept} 个声明文件 → api-report/\n`);
|
||||
|
||||
if (process.argv.includes('--update')) {
|
||||
process.stdout.write('api:check —— --update:已刷新快照,请连同变更申请一并提交。\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let status = '';
|
||||
try {
|
||||
status = run('git', ['status', '--porcelain', '--', 'api-report']);
|
||||
} catch {
|
||||
process.stdout.write('api:check —— 非 git 仓库,跳过比对。\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (status.trim() === '') {
|
||||
process.stdout.write('api:check —— 公开契约无变化 ✔\n');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.stderr.write('\napi:check —— 公开契约发生变化(§12.4):\n' + status + '\n');
|
||||
try {
|
||||
process.stderr.write(run('git', ['diff', '--', 'api-report']));
|
||||
} catch {
|
||||
/* 首次建立基线时无 diff 可打 */
|
||||
}
|
||||
process.stderr.write(
|
||||
'\n契约变更须:①depcruise 反查下游 ②同步 CONTRACT.md ③本快照单独说明 ④破坏性走三步弃用。\n' +
|
||||
'确认无误后:pnpm api:check --update && git add api-report\n',
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -0,0 +1,237 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* 本文件由 tools/gen-depcruise-tiers.ts 从 tools/module-tiers.json 生成。
|
||||
* 请勿手改 —— 改层级请改 module-tiers.json 与 编码规范.md §4,然后 pnpm gen:tiers。
|
||||
*/
|
||||
module.exports = [
|
||||
{
|
||||
"name": "tier-order-activity-registry",
|
||||
"comment": "模块 activity-registry(L4)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/activity-registry/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(flow-engine|loop-engine|minute|process|release|report|skill-engine|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-channel",
|
||||
"comment": "模块 channel(L2)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/channel/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|flow-engine|human-tool|interaction|loop-engine|memory|message|minute|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-event",
|
||||
"comment": "模块 event(L1)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/event/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|flow-engine|human-tool|interaction|ledger|llm|loop-engine|memory|message|minute|org|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-flow-engine",
|
||||
"comment": "模块 flow-engine(L5)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/flow-engine/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(loop-engine|minute|process|report|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-human-tool",
|
||||
"comment": "模块 human-tool(L3)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/human-tool/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|flow-engine|interaction|loop-engine|minute|process|release|report|skill-engine|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-interaction",
|
||||
"comment": "模块 interaction(L3)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/interaction/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|flow-engine|human-tool|loop-engine|minute|process|release|report|skill-engine|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-ledger",
|
||||
"comment": "模块 ledger(L1)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/ledger/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|event|flow-engine|human-tool|interaction|llm|loop-engine|memory|message|minute|org|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-llm",
|
||||
"comment": "模块 llm(L1)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/llm/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|event|flow-engine|human-tool|interaction|ledger|loop-engine|memory|message|minute|org|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-loop-engine",
|
||||
"comment": "模块 loop-engine(L5)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/loop-engine/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(flow-engine|minute|process|report|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-memory",
|
||||
"comment": "模块 memory(L2)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/memory/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|flow-engine|human-tool|interaction|loop-engine|message|minute|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-message",
|
||||
"comment": "模块 message(L2)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/message/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|flow-engine|human-tool|interaction|loop-engine|memory|minute|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-minute",
|
||||
"comment": "模块 minute(L8)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/minute/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(report)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-org",
|
||||
"comment": "模块 org(L1)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/org/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|event|flow-engine|human-tool|interaction|ledger|llm|loop-engine|memory|message|minute|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-process",
|
||||
"comment": "模块 process(L7)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/process/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(minute|report|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-release",
|
||||
"comment": "模块 release(L4)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/release/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|flow-engine|loop-engine|minute|process|report|skill-engine|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-report",
|
||||
"comment": "模块 report(L8)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/report/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(minute)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-skill-engine",
|
||||
"comment": "模块 skill-engine(L4)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/skill-engine/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|flow-engine|loop-engine|minute|process|release|report|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-task",
|
||||
"comment": "模块 task(L6)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/task/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(minute|process|report|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-toolbox",
|
||||
"comment": "模块 toolbox(L2)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/toolbox/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(activity-registry|channel|flow-engine|human-tool|interaction|loop-engine|memory|message|minute|process|release|report|skill-engine|task|trigger)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tier-order-trigger",
|
||||
"comment": "模块 trigger(L7)只准依赖更低层级模块;同层互需一律 port + app 装配(编码规范 §4 / §5.2)",
|
||||
"severity": "error",
|
||||
"from": {
|
||||
"path": "^apps/api/src/modules/trigger/"
|
||||
},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/(minute|process|report)/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "module-registered-in-tiers",
|
||||
"comment": "modules/ 下的每个模块必须登记在 tools/module-tiers.json(编码规范 §16)",
|
||||
"severity": "error",
|
||||
"from": {},
|
||||
"to": {
|
||||
"path": "^apps/api/src/modules/([^/]+)/",
|
||||
"pathNot": "^apps/api/src/modules/(activity-registry|channel|event|flow-engine|human-tool|interaction|ledger|llm|loop-engine|memory|message|minute|org|process|release|report|skill-engine|task|toolbox|trigger)/"
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 从 tools/module-tiers.json(编码规范 §4 层级表的机器形式)生成 depcruise 的
|
||||
* tier-order 规则,写入 tools/depcruise-tiers.generated.cjs。
|
||||
*
|
||||
* 规则语义(§5.2):模块只准 import §4 表中**层级更低**者的 index.ts;
|
||||
* 同层互不 import(协作走 port + app 装配),更高层当然也不行。
|
||||
*
|
||||
* 运行:pnpm gen:tiers(gates 的第一步,产物入库,diff 即规则变更)
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const tiersPath = join(here, 'module-tiers.json');
|
||||
const outPath = join(here, 'depcruise-tiers.generated.cjs');
|
||||
|
||||
type TierTable = { tiers: Record<string, string[]> };
|
||||
|
||||
const raw = readFileSync(tiersPath, 'utf8');
|
||||
const table = JSON.parse(raw) as TierTable;
|
||||
|
||||
const modulesBase = '^apps/api/src/modules';
|
||||
|
||||
/** 模块名 → 层级 */
|
||||
const tierOf = new Map<string, number>();
|
||||
for (const [tier, modules] of Object.entries(table.tiers)) {
|
||||
for (const m of modules) {
|
||||
if (tierOf.has(m)) throw new Error(`模块 ${m} 在层级表中重复出现`);
|
||||
tierOf.set(m, Number(tier));
|
||||
}
|
||||
}
|
||||
|
||||
const allModules = [...tierOf.keys()].sort();
|
||||
|
||||
type Rule = {
|
||||
name: string;
|
||||
comment: string;
|
||||
severity: 'error';
|
||||
from: { path: string };
|
||||
to: { path: string };
|
||||
};
|
||||
|
||||
const rules: Rule[] = [];
|
||||
|
||||
for (const from of allModules) {
|
||||
const fromTier = tierOf.get(from)!;
|
||||
// 不得依赖的模块 = 同层其它模块 + 全部更高层模块
|
||||
const forbidden = allModules.filter((to) => to !== from && tierOf.get(to)! >= fromTier);
|
||||
if (forbidden.length === 0) continue;
|
||||
rules.push({
|
||||
name: `tier-order-${from}`,
|
||||
comment:
|
||||
`模块 ${from}(L${fromTier})只准依赖更低层级模块;` +
|
||||
`同层互需一律 port + app 装配(编码规范 §4 / §5.2)`,
|
||||
severity: 'error',
|
||||
from: { path: `${modulesBase}/${from}/` },
|
||||
to: { path: `${modulesBase}/(${forbidden.join('|')})/` },
|
||||
});
|
||||
}
|
||||
|
||||
// 未登记模块拦截:modules/ 下出现层级表里没有的目录即报错(§16 检查清单第一条)
|
||||
const knownGroup = allModules.join('|');
|
||||
|
||||
const banner = `/* eslint-disable */
|
||||
/**
|
||||
* 本文件由 tools/gen-depcruise-tiers.ts 从 tools/module-tiers.json 生成。
|
||||
* 请勿手改 —— 改层级请改 module-tiers.json 与 编码规范.md §4,然后 pnpm gen:tiers。
|
||||
*/
|
||||
`;
|
||||
|
||||
const body =
|
||||
banner +
|
||||
'module.exports = ' +
|
||||
JSON.stringify(
|
||||
[
|
||||
...rules,
|
||||
{
|
||||
name: 'module-registered-in-tiers',
|
||||
comment: 'modules/ 下的每个模块必须登记在 tools/module-tiers.json(编码规范 §16)',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
path: `${modulesBase}/([^/]+)/`,
|
||||
pathNot: `${modulesBase}/(${knownGroup})/`,
|
||||
},
|
||||
},
|
||||
],
|
||||
null,
|
||||
2,
|
||||
) +
|
||||
';\n';
|
||||
|
||||
writeFileSync(outPath, body, 'utf8');
|
||||
process.stdout.write(
|
||||
`gen-depcruise-tiers: ${rules.length + 1} 条规则 → tools/depcruise-tiers.generated.cjs\n`,
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$comment": "编码规范 §4 后端模块层级表的机器形式。改层级 = 改本文件 + 改 编码规范.md §4,一起提交(§11.4)。",
|
||||
"tiers": {
|
||||
"1": ["event", "org", "llm", "ledger"],
|
||||
"2": ["memory", "toolbox", "channel", "message"],
|
||||
"3": ["interaction", "human-tool"],
|
||||
"4": ["release", "activity-registry", "skill-engine"],
|
||||
"5": ["loop-engine", "flow-engine"],
|
||||
"6": ["task"],
|
||||
"7": ["process", "trigger"],
|
||||
"8": ["minute", "report"]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user