merge: reconcile remote main deployment updates

# Conflicts:
#	.project-docs/00-brief/project-positioning.md
#	.project-docs/00-brief/success-criteria.md
#	.project-docs/05-agent-entry/memory-index.md
#	.project-docs/10-decisions/decision-index.md
#	.project-docs/20-architecture/data-flow.md
#	.project-docs/20-architecture/module-map.md
#	.project-docs/20-architecture/system-overview.md
#	.project-docs/30-worklog/current-state.md
#	.project-docs/30-worklog/task-history.md
#	.project-docs/40-domain/business-rules.md
#	.project-docs/40-domain/glossary.md
#	.project-docs/50-evidence/evidence-index.md
#	.project-docs/50-evidence/topic-template.md
#	.project-docs/60-reflection/reflection-index.md
#	.project-docs/80-commitments/commitments.md
#	.project-docs/90-maintenance/stale-items.md
#	archive/project-history/2026-08-28/legacy-planning-with-files-progress-final.md
This commit is contained in:
inman committed 2026-08-31 10:28:45 +08:00
commit c4c469f444
40 files changed
+1412 -47

No files matched your search

+19 -7
View File
@@ -45,9 +45,23 @@ const ROOT_ALLOWLIST = new Set([
const SKIP_WALK = new Set(['.git', '.build', 'node_modules']);
const RESIDUE_PATTERN = /(^\.DS_Store$|\.log$|\.tmp$|\.swp$|\.pid$|~$)/i;
const TEXT_ARTIFACT_EXTENSIONS = new Set(['.css', '.html', '.js', '.json', '.md', '.mjs', '.py', '.ts', '.txt', '.yaml', '.yml']);
function hashBytes(value) {
return createHash('sha256').update(value).digest('hex');
}
async function sha256(filePath) {
return createHash('sha256').update(await readFile(filePath)).digest('hex');
return hashBytes(await readFile(filePath));
}
function artifactContentHash(entry, value) {
if (!TEXT_ARTIFACT_EXTENSIONS.has(path.extname(entry).toLowerCase())) return hashBytes(value);
return hashBytes(Buffer.from(value.toString('utf8').replace(/\r\n/gu, '\n'), 'utf8'));
}
async function normalizedTextSha256(filePath) {
return artifactContentHash(filePath, await readFile(filePath));
}
async function walkFiles(directory, options = {}) {
@@ -88,10 +102,8 @@ async function assertArchiveMatchesSource({ artifactPath, sourcePath, ignore = n
assert.deepEqual(artifactEntries, sourceEntries, `${path.basename(artifactPath)} 文件集合与源码不一致`);
for (const entry of sourceEntries) {
const sourceHash = createHash('sha256')
.update(await readFile(path.join(sourcePath, ...entry.split('/'))))
.digest('hex');
const archiveHash = createHash('sha256').update(archiveEntry(artifactPath, entry)).digest('hex');
const sourceHash = artifactContentHash(entry, await readFile(path.join(sourcePath, ...entry.split('/'))));
const archiveHash = artifactContentHash(entry, archiveEntry(artifactPath, entry));
assert.equal(archiveHash, sourceHash, `${path.basename(artifactPath)}:${entry} 与源码内容不一致`);
}
}
@@ -165,8 +177,8 @@ test('dist is an exact versioned release set with valid hashes', async () => {
assert.equal(manifest.artifacts.filter(({ kind }) => kind === 'business_instruction_docx').length, 1);
const docx = manifest.artifacts.find(({ kind }) => kind === 'business_instruction_docx');
assert.equal(await sha256(path.resolve(ROOT, docx.source)), docx.source_sha256);
assert.equal(await sha256(path.resolve(ROOT, docx.builder)), docx.builder_sha256);
assert.equal(await normalizedTextSha256(path.resolve(ROOT, docx.source)), docx.source_sha256);
assert.equal(await normalizedTextSha256(path.resolve(ROOT, docx.builder)), docx.builder_sha256);
});
test('extension and release baselines agree', async () => {