docs: migrate project memory governance
This commit is contained in:
1 parent
7b5d855b09
commit
e7aa58a203
37 files changed
+942
-34
No files matched your search
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user