patchwarden 0.6.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +54 -20
- package/README.md +48 -21
- package/dist/agents/agentRouter.d.ts +40 -0
- package/dist/agents/agentRouter.js +95 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/controlCenter.js +212 -3
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +32 -1
- package/dist/goal/acceptanceEngine.d.ts +62 -0
- package/dist/goal/acceptanceEngine.js +103 -0
- package/dist/goal/acceptanceTemplate.d.ts +10 -0
- package/dist/goal/acceptanceTemplate.js +104 -0
- package/dist/goal/goalGraph.d.ts +58 -0
- package/dist/goal/goalGraph.js +189 -0
- package/dist/goal/goalProgress.d.ts +81 -0
- package/dist/goal/goalProgress.js +216 -0
- package/dist/goal/goalStatus.d.ts +60 -0
- package/dist/goal/goalStatus.js +137 -0
- package/dist/goal/goalStore.d.ts +79 -0
- package/dist/goal/goalStore.js +211 -0
- package/dist/goal/handoffExport.d.ts +34 -0
- package/dist/goal/handoffExport.js +183 -0
- package/dist/goal/subgoalSync.d.ts +40 -0
- package/dist/goal/subgoalSync.js +72 -0
- package/dist/goal/worktreeManager.d.ts +96 -0
- package/dist/goal/worktreeManager.js +292 -0
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +68 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/release/releaseGate.d.ts +99 -0
- package/dist/release/releaseGate.js +475 -0
- package/dist/runner/postTaskCleanup.d.ts +13 -0
- package/dist/runner/postTaskCleanup.js +147 -0
- package/dist/runner/runTask.js +50 -10
- package/dist/security/discoveryTokenStore.d.ts +62 -0
- package/dist/security/discoveryTokenStore.js +100 -0
- package/dist/security/toolInvocationGuard.d.ts +35 -0
- package/dist/security/toolInvocationGuard.js +153 -0
- package/dist/smoke-test.js +18 -10
- package/dist/taskRuntime.d.ts +17 -0
- package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
- package/dist/test/unit/acceptance-engine.test.js +228 -0
- package/dist/test/unit/agent-router.test.d.ts +1 -0
- package/dist/test/unit/agent-router.test.js +287 -0
- package/dist/test/unit/audit-checks.test.d.ts +1 -0
- package/dist/test/unit/audit-checks.test.js +350 -0
- package/dist/test/unit/diagnose-task.test.d.ts +1 -0
- package/dist/test/unit/diagnose-task.test.js +457 -0
- package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
- package/dist/test/unit/discovery-token-store.test.js +139 -0
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/goal-graph.test.d.ts +1 -0
- package/dist/test/unit/goal-graph.test.js +298 -0
- package/dist/test/unit/goal-progress.test.d.ts +1 -0
- package/dist/test/unit/goal-progress.test.js +381 -0
- package/dist/test/unit/goal-status.test.d.ts +1 -0
- package/dist/test/unit/goal-status.test.js +215 -0
- package/dist/test/unit/goal-store.test.d.ts +1 -0
- package/dist/test/unit/goal-store.test.js +253 -0
- package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
- package/dist/test/unit/goal-subgoal-task.test.js +55 -0
- package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
- package/dist/test/unit/goal-tools-registry.test.js +190 -0
- package/dist/test/unit/handoff-export.test.d.ts +1 -0
- package/dist/test/unit/handoff-export.test.js +263 -0
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
- package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
- package/dist/test/unit/logging.test.js +127 -5
- package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
- package/dist/test/unit/post-task-cleanup.test.js +48 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
- package/dist/test/unit/reconcile-tasks.test.js +456 -0
- package/dist/test/unit/release-gate.test.d.ts +1 -0
- package/dist/test/unit/release-gate.test.js +242 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/safe-views.test.d.ts +1 -0
- package/dist/test/unit/safe-views.test.js +171 -0
- package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
- package/dist/test/unit/schema-drift-check.test.js +176 -0
- package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
- package/dist/test/unit/subgoal-sync.test.js +183 -0
- package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
- package/dist/test/unit/tool-invocation-guard.test.js +432 -0
- package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
- package/dist/test/unit/tool-usage-stats.test.js +300 -0
- package/dist/test/unit/toolSearch.test.d.ts +1 -0
- package/dist/test/unit/toolSearch.test.js +571 -0
- package/dist/test/unit/worktree-manager.test.d.ts +1 -0
- package/dist/test/unit/worktree-manager.test.js +176 -0
- package/dist/tools/auditTask.d.ts +103 -1
- package/dist/tools/auditTask.js +461 -8
- package/dist/tools/cancelTask.js +1 -1
- package/dist/tools/checkReleaseGate.d.ts +21 -0
- package/dist/tools/checkReleaseGate.js +22 -0
- package/dist/tools/createTask.d.ts +18 -2
- package/dist/tools/createTask.js +29 -1
- package/dist/tools/diagnoseTask.d.ts +45 -0
- package/dist/tools/diagnoseTask.js +347 -0
- package/dist/tools/discardWorktree.d.ts +24 -0
- package/dist/tools/discardWorktree.js +27 -0
- package/dist/tools/discoverTools.d.ts +11 -0
- package/dist/tools/discoverTools.js +39 -0
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/explainTool.d.ts +11 -0
- package/dist/tools/explainTool.js +21 -0
- package/dist/tools/getTaskSummary.js +2 -1
- package/dist/tools/goalSubgoalTask.d.ts +51 -0
- package/dist/tools/goalSubgoalTask.js +110 -0
- package/dist/tools/healthCheck.d.ts +1 -0
- package/dist/tools/healthCheck.js +5 -1
- package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
- package/dist/tools/invokeDiscoveredTool.js +112 -0
- package/dist/tools/listTasks.d.ts +3 -1
- package/dist/tools/listTasks.js +14 -1
- package/dist/tools/mergeWorktree.d.ts +24 -0
- package/dist/tools/mergeWorktree.js +27 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +926 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +262 -0
- package/dist/tools/safeViews.js +252 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +39 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +834 -0
- package/dist/tools/toolSearch.d.ts +110 -0
- package/dist/tools/toolSearch.js +331 -0
- package/dist/tools/toolUsageStats.d.ts +41 -0
- package/dist/tools/toolUsageStats.js +116 -0
- package/dist/tools/waitForTask.js +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +29 -0
- package/dist/watcherStatus.js +92 -1
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +3 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/doctor-smoke.js +1 -0
- package/scripts/checks/lifecycle-smoke.js +13 -9
- package/scripts/checks/mcp-manifest-check.js +65 -6
- package/scripts/checks/mcp-smoke.js +60 -9
- package/scripts/checks/watcher-supervisor-smoke.js +2 -2
- package/src/agents/agentRouter.ts +149 -0
- package/src/config.ts +9 -3
- package/src/controlCenter.ts +215 -4
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +36 -1
- package/src/goal/acceptanceEngine.ts +160 -0
- package/src/goal/acceptanceTemplate.ts +121 -0
- package/src/goal/goalGraph.ts +225 -0
- package/src/goal/goalProgress.ts +301 -0
- package/src/goal/goalStatus.ts +234 -0
- package/src/goal/goalStore.ts +306 -0
- package/src/goal/handoffExport.ts +211 -0
- package/src/goal/subgoalSync.ts +82 -0
- package/src/goal/worktreeManager.ts +404 -0
- package/src/logging.ts +91 -0
- package/src/policy/projectPolicy.ts +344 -0
- package/src/release/releaseGate.ts +567 -0
- package/src/runner/postTaskCleanup.ts +154 -0
- package/src/runner/runTask.ts +49 -10
- package/src/security/discoveryTokenStore.ts +157 -0
- package/src/security/toolInvocationGuard.ts +251 -0
- package/src/smoke-test.ts +15 -7
- package/src/taskRuntime.ts +17 -0
- package/src/test/unit/acceptance-engine.test.ts +261 -0
- package/src/test/unit/agent-router.test.ts +342 -0
- package/src/test/unit/audit-checks.test.ts +567 -0
- package/src/test/unit/diagnose-task.test.ts +544 -0
- package/src/test/unit/discovery-token-store.test.ts +181 -0
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/goal-graph.test.ts +347 -0
- package/src/test/unit/goal-progress.test.ts +538 -0
- package/src/test/unit/goal-status.test.ts +270 -0
- package/src/test/unit/goal-store.test.ts +318 -0
- package/src/test/unit/goal-subgoal-task.test.ts +72 -0
- package/src/test/unit/goal-tools-registry.test.ts +243 -0
- package/src/test/unit/handoff-export.test.ts +295 -0
- package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
- package/src/test/unit/logging.test.ts +177 -5
- package/src/test/unit/post-task-cleanup.test.ts +53 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +260 -0
- package/src/test/unit/subgoal-sync.test.ts +236 -0
- package/src/test/unit/tool-invocation-guard.test.ts +542 -0
- package/src/test/unit/tool-usage-stats.test.ts +384 -0
- package/src/test/unit/toolSearch.test.ts +756 -0
- package/src/test/unit/worktree-manager.test.ts +247 -0
- package/src/tools/auditTask.ts +831 -402
- package/src/tools/cancelTask.ts +1 -1
- package/src/tools/checkReleaseGate.ts +45 -0
- package/src/tools/createTask.ts +64 -6
- package/src/tools/diagnoseTask.ts +460 -0
- package/src/tools/discardWorktree.ts +42 -0
- package/src/tools/discoverTools.ts +51 -0
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/explainTool.ts +26 -0
- package/src/tools/getTaskSummary.ts +2 -1
- package/src/tools/goalSubgoalTask.ts +170 -0
- package/src/tools/healthCheck.ts +4 -1
- package/src/tools/invokeDiscoveredTool.ts +163 -0
- package/src/tools/listTasks.ts +16 -2
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/mergeWorktree.ts +42 -0
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +1093 -73
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +273 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +40 -11
- package/src/tools/toolRegistry.ts +896 -0
- package/src/tools/toolSearch.ts +464 -0
- package/src/tools/toolUsageStats.ts +151 -0
- package/src/tools/waitForTask.ts +1 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +97 -1
- package/ui/pages/dashboard.html +143 -2
package/dist/controlCenter.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Port override: PATCHWARDEN_CONTROL_PORT=<n> or --port <n>
|
|
13
13
|
*/
|
|
14
14
|
import { createServer, get as httpGet } from "node:http";
|
|
15
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
@@ -25,6 +25,10 @@ import { readWatcherStatus } from "./watcherStatus.js";
|
|
|
25
25
|
import { redactSensitiveContent } from "./security/contentRedaction.js";
|
|
26
26
|
import { guardWorkspacePath } from "./security/pathGuard.js";
|
|
27
27
|
import { auditTask } from "./tools/auditTask.js";
|
|
28
|
+
import { getProjectPolicySummary } from "./policy/projectPolicy.js";
|
|
29
|
+
import { safeDirectSummary } from "./tools/safeViews.js";
|
|
30
|
+
import { toSafeTaskLineage } from "./tools/taskLineage.js";
|
|
31
|
+
import { listEvidencePacks, readEvidencePack } from "./tools/evidencePack.js";
|
|
28
32
|
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "./version.js";
|
|
29
33
|
// ── Paths ─────────────────────────────────────────────────────────
|
|
30
34
|
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -231,6 +235,26 @@ function readJsonFileSafe(filePath) {
|
|
|
231
235
|
return null;
|
|
232
236
|
}
|
|
233
237
|
}
|
|
238
|
+
function isPathInside(root, target) {
|
|
239
|
+
const rel = relative(root, target);
|
|
240
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
241
|
+
}
|
|
242
|
+
function readJsonFileSafeUnder(root, relPath) {
|
|
243
|
+
const base = resolve(root);
|
|
244
|
+
const target = resolve(base, relPath);
|
|
245
|
+
if (!isPathInside(base, target) || !existsSync(target))
|
|
246
|
+
return null;
|
|
247
|
+
try {
|
|
248
|
+
const realBase = realpathSync(base);
|
|
249
|
+
const realTarget = realpathSync(target);
|
|
250
|
+
if (!isPathInside(realBase, realTarget))
|
|
251
|
+
return null;
|
|
252
|
+
return readJsonFileSafe(realTarget);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
234
258
|
function readTextFileSafe(filePath) {
|
|
235
259
|
if (!existsSync(filePath))
|
|
236
260
|
return null;
|
|
@@ -419,6 +443,7 @@ function resolveWorkspaceRootSafe() {
|
|
|
419
443
|
}
|
|
420
444
|
const TERMINAL_TASK_STATUSES = new Set([
|
|
421
445
|
"done",
|
|
446
|
+
"done_by_agent",
|
|
422
447
|
"failed",
|
|
423
448
|
"failed_verification",
|
|
424
449
|
"failed_scope_violation",
|
|
@@ -901,6 +926,105 @@ function handleStaleTasks(res) {
|
|
|
901
926
|
sendJson(res, 200, { stale_tasks: [], total: 0, reason: errorMessage(err) });
|
|
902
927
|
}
|
|
903
928
|
}
|
|
929
|
+
function handleLineages(res) {
|
|
930
|
+
try {
|
|
931
|
+
const root = join(config.workspaceRoot, ".patchwarden", "lineages");
|
|
932
|
+
if (!existsSync(root)) {
|
|
933
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: null });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const lineages = readdirSync(root, { withFileTypes: true })
|
|
937
|
+
.filter((entry) => entry.isDirectory())
|
|
938
|
+
.map((entry) => readJsonFileSafeUnder(root, join(entry.name, "lineage.json")))
|
|
939
|
+
.filter((entry) => entry !== null)
|
|
940
|
+
.map((entry) => toSafeTaskLineage(entry, 6))
|
|
941
|
+
.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
942
|
+
.slice(0, 50);
|
|
943
|
+
sendJson(res, 200, { lineages, total: lineages.length, reason: null });
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
946
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: errorMessage(err) });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function handleLineageDetail(res, lineageId) {
|
|
950
|
+
try {
|
|
951
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
952
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const data = readJsonFileSafeUnder(join(config.workspaceRoot, ".patchwarden", "lineages"), join(lineageId, "lineage.json"));
|
|
956
|
+
if (!data) {
|
|
957
|
+
sendJson(res, 404, { error: "Lineage not found" });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
sendJson(res, 200, toSafeTaskLineage(data, 20));
|
|
961
|
+
}
|
|
962
|
+
catch (err) {
|
|
963
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err) });
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function handleProjectPolicy(res, repoPath) {
|
|
967
|
+
try {
|
|
968
|
+
sendJson(res, 200, getProjectPolicySummary(repoPath || "."));
|
|
969
|
+
}
|
|
970
|
+
catch (err) {
|
|
971
|
+
sendJson(res, 200, {
|
|
972
|
+
repo_path: repoPath || ".",
|
|
973
|
+
valid: false,
|
|
974
|
+
issues: [{ code: "policy_unavailable", severity: "error", field: "repo_path", message: errorMessage(err) }],
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function handleReleaseStatus(res, repoPath) {
|
|
979
|
+
try {
|
|
980
|
+
const policy = getProjectPolicySummary(repoPath || ".");
|
|
981
|
+
sendJson(res, 200, {
|
|
982
|
+
repo_path: repoPath || ".",
|
|
983
|
+
resolved_repo_path: policy.resolved_repo_path,
|
|
984
|
+
policy_valid: policy.valid,
|
|
985
|
+
policy_issue_count: policy.issues.length,
|
|
986
|
+
policy_issues: policy.issues.slice(0, 10),
|
|
987
|
+
release_readiness: policy.release_readiness,
|
|
988
|
+
next_action: policy.valid && policy.release_readiness.version_consistent !== false
|
|
989
|
+
? "Run release_check or release_prepare from the full MCP profile when ready."
|
|
990
|
+
: "Fix project-policy or version consistency issues before release preparation.",
|
|
991
|
+
remote_write_performed: false,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
catch (err) {
|
|
995
|
+
sendJson(res, 200, {
|
|
996
|
+
repo_path: repoPath || ".",
|
|
997
|
+
policy_valid: false,
|
|
998
|
+
error: errorMessage(err),
|
|
999
|
+
remote_write_performed: false,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function handleEvidencePacks(res) {
|
|
1004
|
+
try {
|
|
1005
|
+
sendJson(res, 200, listEvidencePacks({ max_items: 50 }));
|
|
1006
|
+
}
|
|
1007
|
+
catch (err) {
|
|
1008
|
+
sendJson(res, 200, { evidence_packs: [], total: 0, reason: errorMessage(err) });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function handleEvidencePackDetail(res, lineageId) {
|
|
1012
|
+
try {
|
|
1013
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
1014
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const pack = readEvidencePack(lineageId);
|
|
1018
|
+
if (!pack) {
|
|
1019
|
+
sendJson(res, 404, { error: "Evidence pack not found" });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
sendJson(res, 200, pack);
|
|
1023
|
+
}
|
|
1024
|
+
catch (err) {
|
|
1025
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err), bounded: true });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
904
1028
|
/**
|
|
905
1029
|
* Reconcile a stale task. Does NOT delete the task. Reads the task files,
|
|
906
1030
|
* decides whether it is safe to mark the task as stale/archived, writes a
|
|
@@ -928,13 +1052,19 @@ function handleReconcile(res, taskId) {
|
|
|
928
1052
|
const statusData = readJsonFileSafe(statusPath) ?? {};
|
|
929
1053
|
const runtimeData = readJsonFileSafe(runtimePath) ?? {};
|
|
930
1054
|
const watcher = readWatcherStatusSafe();
|
|
1055
|
+
const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
1056
|
+
const taskStatus = String(statusData.status || "pending");
|
|
1057
|
+
const taskAcceptanceStatus = taskStatus === "done_by_agent"
|
|
1058
|
+
? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE.includes(statusData.acceptance_status) ? statusData.acceptance_status : "pending")
|
|
1059
|
+
: null;
|
|
931
1060
|
const taskEntry = {
|
|
932
1061
|
task_id: taskId,
|
|
933
1062
|
plan_id: String(statusData.plan_id || ""),
|
|
934
1063
|
title: "",
|
|
935
1064
|
agent: String(statusData.agent || ""),
|
|
936
|
-
status:
|
|
1065
|
+
status: taskStatus,
|
|
937
1066
|
phase: String(runtimeData.phase || statusData.phase || "queued"),
|
|
1067
|
+
acceptance_status: taskAcceptanceStatus,
|
|
938
1068
|
created_at: String(statusData.created_at || ""),
|
|
939
1069
|
updated_at: String(statusData.updated_at || ""),
|
|
940
1070
|
workspace_root: String(statusData.workspace_root || config.workspaceRoot),
|
|
@@ -1072,13 +1202,19 @@ function handleTaskDetail(res, taskId) {
|
|
|
1072
1202
|
let stale = null;
|
|
1073
1203
|
if (statusData) {
|
|
1074
1204
|
const watcher = readWatcherStatusSafe();
|
|
1205
|
+
const VALID_ACCEPTANCE2 = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
1206
|
+
const taskStatus2 = String(statusData.status || "pending");
|
|
1207
|
+
const taskAcceptanceStatus2 = taskStatus2 === "done_by_agent"
|
|
1208
|
+
? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE2.includes(statusData.acceptance_status) ? statusData.acceptance_status : "pending")
|
|
1209
|
+
: null;
|
|
1075
1210
|
const entry = {
|
|
1076
1211
|
task_id: taskId,
|
|
1077
1212
|
plan_id: String(statusData.plan_id || ""),
|
|
1078
1213
|
title: "",
|
|
1079
1214
|
agent: String(statusData.agent || ""),
|
|
1080
|
-
status:
|
|
1215
|
+
status: taskStatus2,
|
|
1081
1216
|
phase: String(runtimeData?.phase || statusData.phase || "queued"),
|
|
1217
|
+
acceptance_status: taskAcceptanceStatus2,
|
|
1082
1218
|
created_at: String(statusData.created_at || ""),
|
|
1083
1219
|
updated_at: String(statusData.updated_at || ""),
|
|
1084
1220
|
workspace_root: String(statusData.workspace_root || config.workspaceRoot),
|
|
@@ -1485,6 +1621,27 @@ function handleDirectSessionDetail(res, sessionId) {
|
|
|
1485
1621
|
sendJson(res, 200, { session_id: sessionId, error: errorMessage(err) });
|
|
1486
1622
|
}
|
|
1487
1623
|
}
|
|
1624
|
+
function handleDirectSessionSafeSummary(res, sessionId) {
|
|
1625
|
+
try {
|
|
1626
|
+
if (sessionId === "." ||
|
|
1627
|
+
sessionId === ".." ||
|
|
1628
|
+
sessionId.includes("/") ||
|
|
1629
|
+
sessionId.includes("\\") ||
|
|
1630
|
+
sessionId.includes("\0")) {
|
|
1631
|
+
sendJson(res, 400, { error: "Invalid session id" });
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
sendJson(res, 200, safeDirectSummary(sessionId, { max_items: 12 }));
|
|
1635
|
+
}
|
|
1636
|
+
catch (err) {
|
|
1637
|
+
sendJson(res, 200, {
|
|
1638
|
+
session_id: sessionId,
|
|
1639
|
+
error: errorMessage(err),
|
|
1640
|
+
large_logs_omitted: true,
|
|
1641
|
+
diff_omitted: true,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1488
1645
|
function parseReviewVerdict(content) {
|
|
1489
1646
|
// independent-review.md format: "**Verdict**: PASS" (case-insensitive)
|
|
1490
1647
|
const m = content.match(/\*\*Verdict\*\*\s*:\s*([A-Za-z]+)/);
|
|
@@ -1774,6 +1931,46 @@ async function handleRequest(req, res) {
|
|
|
1774
1931
|
handleStaleTasks(res);
|
|
1775
1932
|
return;
|
|
1776
1933
|
}
|
|
1934
|
+
if (method === "GET" && pathname === "/api/lineages") {
|
|
1935
|
+
handleLineages(res);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const lineageMatch = pathname.match(/^\/api\/lineages\/([^/]+)$/);
|
|
1939
|
+
if (method === "GET" && lineageMatch) {
|
|
1940
|
+
let lineageId;
|
|
1941
|
+
try {
|
|
1942
|
+
lineageId = decodeURIComponent(lineageMatch[1]);
|
|
1943
|
+
}
|
|
1944
|
+
catch {
|
|
1945
|
+
lineageId = lineageMatch[1];
|
|
1946
|
+
}
|
|
1947
|
+
handleLineageDetail(res, lineageId);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (method === "GET" && pathname === "/api/project-policy") {
|
|
1951
|
+
handleProjectPolicy(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (method === "GET" && pathname === "/api/release/status") {
|
|
1955
|
+
handleReleaseStatus(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (method === "GET" && pathname === "/api/evidence-packs") {
|
|
1959
|
+
handleEvidencePacks(res);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
const evidencePackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)$/);
|
|
1963
|
+
if (method === "GET" && evidencePackMatch) {
|
|
1964
|
+
let lineageId;
|
|
1965
|
+
try {
|
|
1966
|
+
lineageId = decodeURIComponent(evidencePackMatch[1]);
|
|
1967
|
+
}
|
|
1968
|
+
catch {
|
|
1969
|
+
lineageId = evidencePackMatch[1];
|
|
1970
|
+
}
|
|
1971
|
+
handleEvidencePackDetail(res, lineageId);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1777
1974
|
const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
|
|
1778
1975
|
if (method === "GET" && taskMatch) {
|
|
1779
1976
|
let taskId;
|
|
@@ -1832,6 +2029,18 @@ async function handleRequest(req, res) {
|
|
|
1832
2029
|
handleDirectSessions(res);
|
|
1833
2030
|
return;
|
|
1834
2031
|
}
|
|
2032
|
+
const directSessionSummaryMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/summary$/);
|
|
2033
|
+
if (method === "GET" && directSessionSummaryMatch) {
|
|
2034
|
+
let sessionId;
|
|
2035
|
+
try {
|
|
2036
|
+
sessionId = decodeURIComponent(directSessionSummaryMatch[1]);
|
|
2037
|
+
}
|
|
2038
|
+
catch {
|
|
2039
|
+
sessionId = directSessionSummaryMatch[1];
|
|
2040
|
+
}
|
|
2041
|
+
handleDirectSessionSafeSummary(res, sessionId);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1835
2044
|
const directSessionMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)$/);
|
|
1836
2045
|
if (method === "GET" && directSessionMatch) {
|
|
1837
2046
|
let sessionId;
|
|
@@ -40,6 +40,13 @@ export async function runDirectVerification(input) {
|
|
|
40
40
|
exit_code: result.exitCode,
|
|
41
41
|
passed,
|
|
42
42
|
timed_out: result.timedOut,
|
|
43
|
+
redacted: stdoutRedacted.redacted || stderrRedacted.redacted,
|
|
44
|
+
redaction_categories: [
|
|
45
|
+
...new Set([
|
|
46
|
+
...stdoutRedacted.redaction_categories,
|
|
47
|
+
...stderrRedacted.redaction_categories,
|
|
48
|
+
]),
|
|
49
|
+
],
|
|
43
50
|
stdout_tail: stdoutTail,
|
|
44
51
|
stderr_tail: stderrTail,
|
|
45
52
|
started_at: startedAt,
|
package/dist/doctor.js
CHANGED
|
@@ -18,6 +18,9 @@ import { guardPlanContent } from "./security/planGuard.js";
|
|
|
18
18
|
import { TASK_READ_ONLY_FILES } from "./tools/getTaskFile.js";
|
|
19
19
|
import { getToolDefs } from "./tools/registry.js";
|
|
20
20
|
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/toolCatalog.js";
|
|
21
|
+
import { buildToolRegistry } from "./tools/toolRegistry.js";
|
|
22
|
+
import { runAllSchemaDriftChecks } from "./tools/schemaDriftCheck.js";
|
|
23
|
+
import { runReleaseGateCheck } from "./release/releaseGate.js";
|
|
21
24
|
import { PATCHWARDEN_VERSION } from "./version.js";
|
|
22
25
|
// ── State ──────────────────────────────────────────────────────────
|
|
23
26
|
let ok = 0;
|
|
@@ -218,7 +221,7 @@ async function main() {
|
|
|
218
221
|
const coreTools = selectToolsForProfile(fullTools, "chatgpt_core", config?.enableDirectProfile);
|
|
219
222
|
const createSchema = coreTools.find((tool) => tool.name === "create_task")?.inputSchema;
|
|
220
223
|
const waitSchema = coreTools.find((tool) => tool.name === "wait_for_task")?.inputSchema;
|
|
221
|
-
check("Full tool profile exposes
|
|
224
|
+
check("Full tool profile exposes 64 tools", fullTools.length === 64, `${fullTools.length} tools`);
|
|
222
225
|
check(`chatgpt_core profile exposes the exact ${CHATGPT_CORE_TOOL_NAMES.length}-tool manifest`, JSON.stringify(coreTools.map((tool) => tool.name)) === JSON.stringify(CHATGPT_CORE_TOOL_NAMES), coreTools.map((tool) => tool.name).join(", "));
|
|
223
226
|
check("Core task schemas expose inline_plan, verify_commands, and wait aliases", Boolean(createSchema?.properties?.inline_plan &&
|
|
224
227
|
createSchema?.properties?.verify_commands &&
|
|
@@ -235,6 +238,25 @@ async function main() {
|
|
|
235
238
|
results.push(`[OK] chatgpt_direct enabled check skipped (enableDirectProfile=false)`);
|
|
236
239
|
ok++;
|
|
237
240
|
}
|
|
241
|
+
// 16. Schema drift 检查(v0.9.0)—— warn 级别,不阻断 doctor:ci
|
|
242
|
+
// 用 fullTools 同时构建 registry 和 toolDefs Map,确保 schema digest 比对基准一致。
|
|
243
|
+
const driftRegistry = buildToolRegistry(fullTools);
|
|
244
|
+
const driftToolDefs = new Map();
|
|
245
|
+
for (const tool of fullTools) {
|
|
246
|
+
driftToolDefs.set(tool.name, { inputSchema: tool.inputSchema });
|
|
247
|
+
}
|
|
248
|
+
const driftResult = runAllSchemaDriftChecks(driftRegistry, driftToolDefs);
|
|
249
|
+
if (driftResult.ok) {
|
|
250
|
+
results.push(`[OK] Schema drift check — no drift detected`);
|
|
251
|
+
ok++;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// 每个 warning 单独输出为 WARN,与其他自检项的多警告模式一致
|
|
255
|
+
for (const w of driftResult.warnings) {
|
|
256
|
+
results.push(`[WARN] Schema drift: ${w}`);
|
|
257
|
+
warn++;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
238
260
|
}
|
|
239
261
|
finally {
|
|
240
262
|
if (previousProfile === undefined)
|
|
@@ -242,6 +264,15 @@ async function main() {
|
|
|
242
264
|
else
|
|
243
265
|
process.env.PATCHWARDEN_TOOL_PROFILE = previousProfile;
|
|
244
266
|
}
|
|
267
|
+
// 17. Release gate module loadable (v1.0.0) — module integrity only.
|
|
268
|
+
// Does NOT execute local_ready (would recurse into doctor:ci).
|
|
269
|
+
try {
|
|
270
|
+
const releaseGateReady = typeof runReleaseGateCheck === "function";
|
|
271
|
+
check("Release gate module loadable", releaseGateReady, releaseGateReady ? "runReleaseGateCheck exported" : "runReleaseGateCheck missing or invalid");
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
check("Release gate module loadable", false, error instanceof Error ? error.message : String(error));
|
|
275
|
+
}
|
|
245
276
|
// 9. HTTP port check
|
|
246
277
|
const httpPort = config?.http?.port || 7331;
|
|
247
278
|
try {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7.2: 验收引擎 — 基于证据推导 4 级验收结论。
|
|
3
|
+
*
|
|
4
|
+
* 输入:status.json、task_meta(goal/scope/forbidden/verification/done_evidence)、
|
|
5
|
+
* audit checks(pass/warn/fail)、release claims、artifact 信息
|
|
6
|
+
* 输出:ACCEPTED / NEEDS_FIX / REJECTED / BLOCKED_BY_APPROVAL
|
|
7
|
+
* + reasons + required_evidence + next_suggested_task
|
|
8
|
+
*
|
|
9
|
+
* 决策规则:
|
|
10
|
+
* 1. 存在 fail 级检查项 → REJECTED
|
|
11
|
+
* 2. 存在 release claims 但无法验证远端 → BLOCKED_BY_APPROVAL
|
|
12
|
+
* 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
|
|
13
|
+
* 4. 所有检查通过 → ACCEPTED
|
|
14
|
+
*/
|
|
15
|
+
import type { AcceptanceStatus } from "../tools/createTask.js";
|
|
16
|
+
export type AcceptanceVerdict = "ACCEPTED" | "NEEDS_FIX" | "REJECTED" | "BLOCKED_BY_APPROVAL";
|
|
17
|
+
export interface AcceptanceCheck {
|
|
18
|
+
name: string;
|
|
19
|
+
result: "pass" | "warn" | "fail";
|
|
20
|
+
detail: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AcceptanceEvidence {
|
|
23
|
+
task_id: string;
|
|
24
|
+
task_status: string;
|
|
25
|
+
result_md_exists: boolean;
|
|
26
|
+
result_json_exists: boolean;
|
|
27
|
+
verify_json_exists: boolean;
|
|
28
|
+
test_log_exists: boolean;
|
|
29
|
+
git_diff_exists: boolean;
|
|
30
|
+
verify_status: "passed" | "failed" | "skipped" | null;
|
|
31
|
+
new_out_of_scope_changes: Array<{
|
|
32
|
+
path: string;
|
|
33
|
+
change: string;
|
|
34
|
+
}>;
|
|
35
|
+
goal: string | null;
|
|
36
|
+
scope: string[] | null;
|
|
37
|
+
forbidden: string[] | null;
|
|
38
|
+
verification: string[] | null;
|
|
39
|
+
done_evidence: string[] | null;
|
|
40
|
+
artifact_status: string | null;
|
|
41
|
+
release_claims_unverified: boolean;
|
|
42
|
+
checks: AcceptanceCheck[];
|
|
43
|
+
}
|
|
44
|
+
export interface AcceptanceResult {
|
|
45
|
+
verdict: AcceptanceVerdict;
|
|
46
|
+
acceptance_status: AcceptanceStatus;
|
|
47
|
+
reason: string;
|
|
48
|
+
reasons: string[];
|
|
49
|
+
required_evidence: string[];
|
|
50
|
+
next_suggested_task: string;
|
|
51
|
+
fail_checks: AcceptanceCheck[];
|
|
52
|
+
warn_checks: AcceptanceCheck[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 核心验收决策函数。
|
|
56
|
+
* 输入所有证据,输出 4 级验收结论。
|
|
57
|
+
*/
|
|
58
|
+
export declare function evaluateAcceptance(evidence: AcceptanceEvidence): AcceptanceResult;
|
|
59
|
+
/**
|
|
60
|
+
* 将 AcceptanceVerdict 映射到 AcceptanceStatus(用于回写 status.json)。
|
|
61
|
+
*/
|
|
62
|
+
export declare function verdictToStatus(verdict: AcceptanceVerdict): AcceptanceStatus;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7.2: 验收引擎 — 基于证据推导 4 级验收结论。
|
|
3
|
+
*
|
|
4
|
+
* 输入:status.json、task_meta(goal/scope/forbidden/verification/done_evidence)、
|
|
5
|
+
* audit checks(pass/warn/fail)、release claims、artifact 信息
|
|
6
|
+
* 输出:ACCEPTED / NEEDS_FIX / REJECTED / BLOCKED_BY_APPROVAL
|
|
7
|
+
* + reasons + required_evidence + next_suggested_task
|
|
8
|
+
*
|
|
9
|
+
* 决策规则:
|
|
10
|
+
* 1. 存在 fail 级检查项 → REJECTED
|
|
11
|
+
* 2. 存在 release claims 但无法验证远端 → BLOCKED_BY_APPROVAL
|
|
12
|
+
* 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
|
|
13
|
+
* 4. 所有检查通过 → ACCEPTED
|
|
14
|
+
*/
|
|
15
|
+
// ── 验收决策 ──────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* 核心验收决策函数。
|
|
18
|
+
* 输入所有证据,输出 4 级验收结论。
|
|
19
|
+
*/
|
|
20
|
+
export function evaluateAcceptance(evidence) {
|
|
21
|
+
const reasons = [];
|
|
22
|
+
const requiredEvidence = [];
|
|
23
|
+
const failChecks = [];
|
|
24
|
+
const warnChecks = [];
|
|
25
|
+
// 收集 fail 和 warn 检查项
|
|
26
|
+
for (const check of evidence.checks) {
|
|
27
|
+
if (check.result === "fail") {
|
|
28
|
+
failChecks.push(check);
|
|
29
|
+
}
|
|
30
|
+
else if (check.result === "warn") {
|
|
31
|
+
warnChecks.push(check);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// ── Rule 1: fail 级检查项 → REJECTED ──────────────────────
|
|
35
|
+
if (failChecks.length > 0) {
|
|
36
|
+
for (const check of failChecks) {
|
|
37
|
+
reasons.push(`[FAIL] ${check.name}: ${check.detail}`);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
verdict: "REJECTED",
|
|
41
|
+
acceptance_status: "rejected",
|
|
42
|
+
reason: `${failChecks.length} fail-level check(s): ${failChecks.map((c) => c.name).join(", ")}`,
|
|
43
|
+
reasons,
|
|
44
|
+
required_evidence: requiredEvidence.length > 0 ? requiredEvidence : ["Fix all fail-level issues and re-run audit_task"],
|
|
45
|
+
next_suggested_task: `Fix fail-level issues: ${failChecks.map((c) => c.name).join(", ")}`,
|
|
46
|
+
fail_checks: failChecks,
|
|
47
|
+
warn_checks: warnChecks,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// ── Rule 2: release claims 无法验证 → BLOCKED_BY_APPROVAL ──
|
|
51
|
+
if (evidence.release_claims_unverified) {
|
|
52
|
+
reasons.push("[BLOCKED] Release claims detected but remote state cannot be verified");
|
|
53
|
+
requiredEvidence.push("Verify remote release state (npm publish, GitHub release, git tag)");
|
|
54
|
+
return {
|
|
55
|
+
verdict: "BLOCKED_BY_APPROVAL",
|
|
56
|
+
acceptance_status: "blocked",
|
|
57
|
+
reason: "Release claims require manual verification of remote state",
|
|
58
|
+
reasons,
|
|
59
|
+
required_evidence: requiredEvidence,
|
|
60
|
+
next_suggested_task: "Manually verify remote release state, then re-run audit_task",
|
|
61
|
+
fail_checks: failChecks,
|
|
62
|
+
warn_checks: warnChecks,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ── Rule 3: warn 级检查项 → NEEDS_FIX ──────────────────────
|
|
66
|
+
if (warnChecks.length > 0) {
|
|
67
|
+
for (const check of warnChecks) {
|
|
68
|
+
reasons.push(`[WARN] ${check.name}: ${check.detail}`);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
verdict: "NEEDS_FIX",
|
|
72
|
+
acceptance_status: "needs_fix",
|
|
73
|
+
reason: `${warnChecks.length} warn-level check(s) require fixes: ${warnChecks.map((c) => c.name).join(", ")}`,
|
|
74
|
+
reasons,
|
|
75
|
+
required_evidence: requiredEvidence.length > 0 ? requiredEvidence : ["Resolve all warn-level issues"],
|
|
76
|
+
next_suggested_task: `Fix warn-level issues: ${warnChecks.map((c) => c.name).join(", ")}`,
|
|
77
|
+
fail_checks: failChecks,
|
|
78
|
+
warn_checks: warnChecks,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ── Rule 4: 所有检查通过 → ACCEPTED ───────────────────────
|
|
82
|
+
return {
|
|
83
|
+
verdict: "ACCEPTED",
|
|
84
|
+
acceptance_status: "accepted",
|
|
85
|
+
reason: "All acceptance checks passed",
|
|
86
|
+
reasons: ["All checks passed, no warnings or failures"],
|
|
87
|
+
required_evidence: [],
|
|
88
|
+
next_suggested_task: "Task accepted. Proceed to next goal or task.",
|
|
89
|
+
fail_checks: failChecks,
|
|
90
|
+
warn_checks: warnChecks,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 将 AcceptanceVerdict 映射到 AcceptanceStatus(用于回写 status.json)。
|
|
95
|
+
*/
|
|
96
|
+
export function verdictToStatus(verdict) {
|
|
97
|
+
switch (verdict) {
|
|
98
|
+
case "ACCEPTED": return "accepted";
|
|
99
|
+
case "REJECTED": return "rejected";
|
|
100
|
+
case "NEEDS_FIX": return "needs_fix";
|
|
101
|
+
case "BLOCKED_BY_APPROVAL": return "blocked";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7.2: ACCEPTANCE.md 模板生成 — 人类可读的验收报告导出。
|
|
3
|
+
*
|
|
4
|
+
* 将 AcceptanceResult 渲染为 Markdown,落盘到 .patchwarden/tasks/{task_id}/ACCEPTANCE.md。
|
|
5
|
+
*/
|
|
6
|
+
import type { AcceptanceResult, AcceptanceEvidence } from "./acceptanceEngine.js";
|
|
7
|
+
/**
|
|
8
|
+
* 生成 ACCEPTANCE.md 内容。
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderAcceptanceMarkdown(taskId: string, result: AcceptanceResult, evidence: AcceptanceEvidence): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7.2: ACCEPTANCE.md 模板生成 — 人类可读的验收报告导出。
|
|
3
|
+
*
|
|
4
|
+
* 将 AcceptanceResult 渲染为 Markdown,落盘到 .patchwarden/tasks/{task_id}/ACCEPTANCE.md。
|
|
5
|
+
*/
|
|
6
|
+
import { PATCHWARDEN_VERSION } from "../version.js";
|
|
7
|
+
/**
|
|
8
|
+
* 生成 ACCEPTANCE.md 内容。
|
|
9
|
+
*/
|
|
10
|
+
export function renderAcceptanceMarkdown(taskId, result, evidence) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
const now = new Date().toISOString();
|
|
13
|
+
lines.push("# Acceptance Report — " + taskId);
|
|
14
|
+
lines.push("");
|
|
15
|
+
lines.push("Generated: " + now);
|
|
16
|
+
lines.push("");
|
|
17
|
+
// ── Verdict ──────────────────────────────────────────────
|
|
18
|
+
lines.push("## Verdict");
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push("| Field | Value |");
|
|
21
|
+
lines.push("| --- | --- |");
|
|
22
|
+
lines.push("| Verdict | **" + result.verdict + "** |");
|
|
23
|
+
lines.push("| Acceptance Status | " + (result.acceptance_status ?? "null") + " |");
|
|
24
|
+
lines.push("| Reason | " + result.reason + " |");
|
|
25
|
+
lines.push("| Next Suggested Task | " + result.next_suggested_task + " |");
|
|
26
|
+
lines.push("");
|
|
27
|
+
// ── Evidence Summary ────────────────────────────────────
|
|
28
|
+
lines.push("## Evidence Summary");
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("| Evidence | Present |");
|
|
31
|
+
lines.push("| --- | --- |");
|
|
32
|
+
lines.push("| result.md | " + (evidence.result_md_exists ? "YES" : "NO") + " |");
|
|
33
|
+
lines.push("| result.json | " + (evidence.result_json_exists ? "YES" : "NO") + " |");
|
|
34
|
+
lines.push("| verify.json | " + (evidence.verify_json_exists ? "YES" : "NO") + " |");
|
|
35
|
+
lines.push("| test.log | " + (evidence.test_log_exists ? "YES" : "NO") + " |");
|
|
36
|
+
lines.push("| git.diff | " + (evidence.git_diff_exists ? "YES" : "NO") + " |");
|
|
37
|
+
lines.push("| verify_status | " + (evidence.verify_status ?? "N/A") + " |");
|
|
38
|
+
lines.push("| artifact_status | " + (evidence.artifact_status ?? "N/A") + " |");
|
|
39
|
+
lines.push("| out_of_scope_changes | " + evidence.new_out_of_scope_changes.length + " |");
|
|
40
|
+
lines.push("| release_claims_unverified | " + (evidence.release_claims_unverified ? "YES" : "NO") + " |");
|
|
41
|
+
lines.push("");
|
|
42
|
+
// ── Task Metadata ────────────────────────────────────────
|
|
43
|
+
if (evidence.goal || evidence.scope || evidence.forbidden || evidence.verification || evidence.done_evidence) {
|
|
44
|
+
lines.push("## Task Acceptance Criteria");
|
|
45
|
+
lines.push("");
|
|
46
|
+
if (evidence.goal) {
|
|
47
|
+
lines.push("- **Goal**: " + evidence.goal);
|
|
48
|
+
}
|
|
49
|
+
if (evidence.scope && evidence.scope.length > 0) {
|
|
50
|
+
lines.push("- **Scope**: " + evidence.scope.join(", "));
|
|
51
|
+
}
|
|
52
|
+
if (evidence.forbidden && evidence.forbidden.length > 0) {
|
|
53
|
+
lines.push("- **Forbidden**: " + evidence.forbidden.join(", "));
|
|
54
|
+
}
|
|
55
|
+
if (evidence.verification && evidence.verification.length > 0) {
|
|
56
|
+
lines.push("- **Verification**: " + evidence.verification.join(", "));
|
|
57
|
+
}
|
|
58
|
+
if (evidence.done_evidence && evidence.done_evidence.length > 0) {
|
|
59
|
+
lines.push("- **Done Evidence**: " + evidence.done_evidence.join(", "));
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
}
|
|
63
|
+
// ── Fail Checks ─────────────────────────────────────────
|
|
64
|
+
if (result.fail_checks.length > 0) {
|
|
65
|
+
lines.push("## Fail-Level Checks");
|
|
66
|
+
lines.push("");
|
|
67
|
+
for (const check of result.fail_checks) {
|
|
68
|
+
lines.push("- **" + check.name + "**: " + check.detail);
|
|
69
|
+
}
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
// ── Warn Checks ─────────────────────────────────────────
|
|
73
|
+
if (result.warn_checks.length > 0) {
|
|
74
|
+
lines.push("## Warn-Level Checks");
|
|
75
|
+
lines.push("");
|
|
76
|
+
for (const check of result.warn_checks) {
|
|
77
|
+
lines.push("- **" + check.name + "**: " + check.detail);
|
|
78
|
+
}
|
|
79
|
+
lines.push("");
|
|
80
|
+
}
|
|
81
|
+
// ── Reasons ─────────────────────────────────────────────
|
|
82
|
+
if (result.reasons.length > 0) {
|
|
83
|
+
lines.push("## Reasons");
|
|
84
|
+
lines.push("");
|
|
85
|
+
for (const reason of result.reasons) {
|
|
86
|
+
lines.push("- " + reason);
|
|
87
|
+
}
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
// ── Required Evidence ────────────────────────────────────
|
|
91
|
+
if (result.required_evidence.length > 0) {
|
|
92
|
+
lines.push("## Required Evidence");
|
|
93
|
+
lines.push("");
|
|
94
|
+
for (const reqEvidence of result.required_evidence) {
|
|
95
|
+
lines.push("- " + reqEvidence);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
lines.push("---");
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("This report was generated by PatchWarden v" + PATCHWARDEN_VERSION + " acceptanceEngine.");
|
|
102
|
+
lines.push("");
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|