完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)

这个提交包含在:
2026-07-16 05:17:38 +08:00
父节点 abb6a22f5c
当前提交 28e8e746b7
共修改 29 个文件,包含 1849 行新增和 233 行删除
+45 -17
查看文件
@@ -2,6 +2,7 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = process.cwd();
@@ -87,17 +88,33 @@ function parseJsonl(relativePath) {
});
}
function checkContextManifest(result, taskDir, action) {
export function contextManifestPolicy(dispatchMode) {
const inlineDispatch = dispatchMode === "inline";
return {
required: !inlineDispatch,
requireRealEntries: !inlineDispatch,
rejectSeedRows: !inlineDispatch,
};
}
function checkContextManifest(result, taskDir, action, dispatchMode) {
const relativePath = `${taskDir}/${action}.jsonl`;
const policy = contextManifestPolicy(dispatchMode);
if (!exists(relativePath)) {
fail(result, `task.${action}_missing`, `${taskDir} missing ${action}.jsonl`);
if (policy.required) {
fail(result, `task.${action}_missing`, `${taskDir} missing ${action}.jsonl`);
} else {
pass(result, `task.${action}_inline_optional`, `${taskDir} uses inline dispatch; ${action}.jsonl is optional`);
}
return;
}
const entries = parseJsonl(relativePath);
let realEntryCount = 0;
for (const { value, line } of entries) {
if (value._example) {
fail(result, `task.${action}_seed`, `${relativePath}:${line} still contains seed _example row`);
if (policy.rejectSeedRows) {
fail(result, `task.${action}_seed`, `${relativePath}:${line} still contains seed _example row`);
}
continue;
}
if (typeof value.file !== "string" || !value.file.trim()) {
@@ -117,8 +134,10 @@ function checkContextManifest(result, taskDir, action) {
}
realEntryCount += 1;
}
if (realEntryCount === 0) {
if (realEntryCount === 0 && policy.requireRealEntries) {
fail(result, `task.${action}_empty`, `${relativePath} must contain at least one real context entry`);
} else if (realEntryCount === 0) {
pass(result, `task.${action}_inline_optional`, `${relativePath} has no injected context because dispatch_mode=inline`);
}
}
@@ -187,6 +206,8 @@ function checkCoreFiles(result) {
function checkTaskContracts(result) {
const taskDirs = listTaskDirs();
const config = exists(".trellis/config.yaml") ? readText(".trellis/config.yaml") : "";
const dispatchMode = /dispatch_mode:\s*sub-agent\b/.test(config) ? "sub-agent" : "inline";
if (taskDirs.length === 0) {
fail(result, "tasks.none", "no .trellis/tasks/* directories found");
return;
@@ -227,8 +248,8 @@ function checkTaskContracts(result) {
if (complexTaskPattern.test(complexText) && !exists(`${dir}/design.md`) && !exists(`${dir}/implement.md`)) {
fail(result, "task.complex_design", `${dir} complex task must include design.md or implement.md`);
}
checkContextManifest(result, dir, "implement");
checkContextManifest(result, dir, "check");
checkContextManifest(result, dir, "implement", dispatchMode);
checkContextManifest(result, dir, "check", dispatchMode);
}
if (result.status === "PASS") pass(result, "task_contracts", `${taskDirs.length} task directories have Trellis contracts`);
}
@@ -296,16 +317,23 @@ function checkSensitiveData(result) {
if (result.status === "PASS") pass(result, "sensitive_scan", `${scanFiles.length} Trellis files scanned`);
}
const result = createResult();
try {
checkCoreFiles(result);
checkTaskContracts(result);
checkDocsRegistration(result);
checkRemovedProjectSkillReferences(result);
checkSensitiveData(result);
} catch (error) {
fail(result, "exception", error instanceof Error ? error.message : String(error));
export function runGovernanceChecks() {
const result = createResult();
try {
checkCoreFiles(result);
checkTaskContracts(result);
checkDocsRegistration(result);
checkRemovedProjectSkillReferences(result);
checkSensitiveData(result);
} catch (error) {
fail(result, "exception", error instanceof Error ? error.message : String(error));
}
return result;
}
console.log(JSON.stringify(result, null, 2));
process.exit(result.status === "PASS" ? 0 : 1);
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
const result = runGovernanceChecks();
console.log(JSON.stringify(result, null, 2));
process.exit(result.status === "PASS" ? 0 : 1);
}