完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -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);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户