docs: migrate project memory governance

This commit is contained in:
inman committed 2026-08-30 15:31:12 +08:00
1 parent 7b5d855b09
commit e7aa58a203
37 files changed
+942 -34

No files matched your search

+29 -14
View File
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
import { readdir, readFile, stat } from 'node:fs/promises';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
@@ -18,6 +18,7 @@ const ROOT_ALLOWLIST = new Set([
'.env.production.example',
'.git',
'.gitignore',
'.project-docs',
'AGENTS.md',
'Dockerfile',
'LianSyn-platform',
@@ -28,19 +29,16 @@ const ROOT_ALLOWLIST = new Set([
'control-plane',
'dist',
'docker-compose.yml',
'findings.md',
'infra',
'mappings',
'node_modules',
'package-lock.json',
'package.json',
'pnpm-lock.yaml',
'progress.md',
'quarantine',
'reports',
'samples',
'schemas',
'task_plan.md',
'tools',
'tsconfig.json',
]);
@@ -102,21 +100,38 @@ test('root contains only governed long-lived entries', async () => {
const names = (await readdir(ROOT)).sort();
const unexpected = names.filter((name) => !ROOT_ALLOWLIST.has(name));
assert.deepEqual(unexpected, []);
for (const required of ['README.md', 'AGENTS.md', 'task_plan.md', 'findings.md', 'progress.md']) {
for (const required of ['README.md', 'AGENTS.md', '.project-docs']) {
assert.ok(names.includes(required), `缺少根目录治理文件 ${required}`);
}
});
test('Planning with Files stays current and bounded', async () => {
const limits = new Map([
['task_plan.md', 16 * 1024],
['findings.md', 32 * 1024],
['progress.md', 32 * 1024],
]);
for (const [name, limit] of limits) {
const size = (await stat(path.join(ROOT, name))).size;
assert.ok(size <= limit, `${name} 为 ${size} 字节,超过 ${limit} 字节;请先冻结再语义压缩`);
test('maintain-project-docs is the sole active project memory', async () => {
const projectDocs = path.join(ROOT, '.project-docs');
const required = [
'00-brief/project-positioning.md',
'05-agent-entry/concurrent-task-gate.md',
'05-agent-entry/read-before-planning.md',
'10-decisions/decision-index.md',
'20-architecture/system-overview.md',
'30-worklog/current-state.md',
'30-worklog/task-template.md',
'90-maintenance/doc-update-policy.md',
];
for (const relativePath of required) {
assert.ok(existsSync(path.join(projectDocs, relativePath)), `缺少项目记忆文件 .project-docs/${relativePath}`);
}
for (const legacy of ['task_plan.md', 'findings.md', 'progress.md']) {
assert.ok(!existsSync(path.join(ROOT, legacy)), `旧 Planning with Files 文件不得恢复到根目录:${legacy}`);
}
const positioning = await readFile(path.join(projectDocs, '00-brief/project-positioning.md'), 'utf8');
const currentState = await readFile(path.join(projectDocs, '30-worklog/current-state.md'), 'utf8');
const taskRecords = (await readdir(path.join(projectDocs, '30-worklog/tasks')))
.filter((name) => name.endsWith('.md'));
assert.doesNotMatch(positioning, /\{(?:one-line project positioning|primary project goal|primary user or consumer)\}/u);
assert.match(currentState, /^## Integrated Through$/mu);
assert.match(currentState, /Commit `[a-f0-9]{40}`/u);
assert.ok(taskRecords.some((name) => /^\d{8}-[a-z0-9-]+-[a-f0-9]{4,16}\.md$/u.test(name)), '缺少规范 task ID 的项目任务记录');
});
test('temporary residue and report outputs do not pollute the repository', async () => {