patchwarden 0.6.4 → 1.1.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 +18 -18
- package/README.md +20 -20
- 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 +15 -2
- 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/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/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/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/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 +175 -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/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/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +550 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +256 -0
- package/dist/tools/safeViews.js +250 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +33 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +724 -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/package.json +3 -2
- 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 +53 -6
- package/scripts/checks/mcp-smoke.js +36 -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 +17 -3
- 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/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/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/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +258 -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/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/mergeWorktree.ts +42 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +618 -1
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +271 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/toolCatalog.ts +34 -11
- package/src/tools/toolRegistry.ts +786 -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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.8.0: Goal Session 依赖图 — 就绪/阻塞判定、下一子目标建议、环检测、拓扑排序。
|
|
3
|
+
*
|
|
4
|
+
* 所有函数均为纯函数,不修改传入的 goalStatus 对象。
|
|
5
|
+
* 依赖语义:subgoal.depends_on 引用其他 subgoal 的 id,被引用者 status 必须为
|
|
6
|
+
* "accepted" 才算依赖满足。引用不存在的 id 视为数据不一致,按阻塞处理。
|
|
7
|
+
*/
|
|
8
|
+
import type { GoalStatus, Subgoal } from "./goalStatus.js";
|
|
9
|
+
/**
|
|
10
|
+
* 返回所有 status 为 "ready" 且依赖全部满足的子目标。
|
|
11
|
+
* 依赖满足 = depends_on 中所有 id 都存在且对应 subgoal status 为 "accepted"。
|
|
12
|
+
* depends_on 为空数组视为依赖满足。
|
|
13
|
+
* depends_on 引用不存在的 subgoal 时,该子目标不算 ready(跳过)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function getReadySubgoals(goalStatus: GoalStatus): Subgoal[];
|
|
16
|
+
/**
|
|
17
|
+
* 返回所有 status 为 "ready" 但依赖未全部满足的子目标,附带阻塞它的依赖 id 列表。
|
|
18
|
+
* blocked_by 包含 depends_on 中 status 不为 "accepted" 的 subgoal_id,
|
|
19
|
+
* 以及引用不存在的 id。
|
|
20
|
+
*/
|
|
21
|
+
export declare function getBlockedSubgoals(goalStatus: GoalStatus): Array<{
|
|
22
|
+
subgoal: Subgoal;
|
|
23
|
+
blocked_by: string[];
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* 建议下一个可执行的子目标。
|
|
27
|
+
*
|
|
28
|
+
* 优先级:
|
|
29
|
+
* 1. 有 ready 子目标 → 返回第一个(按 subgoal 添加顺序)的 { subgoal_id, title, depends_on }
|
|
30
|
+
* 2. 无 ready 但有 blocked → 返回 { subgoal_id: null, reason: "dependencies_not_met", blocked_by: [...] }
|
|
31
|
+
* 3. 无 ready 无 blocked → 返回 { subgoal_id: null, reason: "no_ready_subgoal" }
|
|
32
|
+
*/
|
|
33
|
+
export declare function suggestNextSubgoal(goalStatus: GoalStatus): {
|
|
34
|
+
subgoal_id: string | null;
|
|
35
|
+
title?: string;
|
|
36
|
+
depends_on?: string[];
|
|
37
|
+
reason?: string;
|
|
38
|
+
blocked_by?: string[];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 检测 subgoal 依赖图是否有环(DFS 三色标记法)。
|
|
42
|
+
*
|
|
43
|
+
* 沿 depends_on 深度优先遍历,遇到当前路径上正在访问的节点即为环。
|
|
44
|
+
* depends_on 引用不存在的 subgoal 不算环(忽略)。
|
|
45
|
+
*
|
|
46
|
+
* @returns 有环时返回环上的 subgoal_id 数组(任一环即可);无环返回 null。
|
|
47
|
+
*/
|
|
48
|
+
export declare function detectCycle(goalStatus: GoalStatus): string[] | null;
|
|
49
|
+
/**
|
|
50
|
+
* 对 subgoal 做拓扑排序(依赖在前)。
|
|
51
|
+
*
|
|
52
|
+
* 如果 B depends_on A,则 A 排在 B 之前。
|
|
53
|
+
* depends_on 引用不存在的 id 会被忽略(不影响排序)。
|
|
54
|
+
*
|
|
55
|
+
* @throws {PatchWardenError} reason="dependency_cycle" 当依赖图存在环时。
|
|
56
|
+
* @returns subgoal_id 数组,依赖项在前。
|
|
57
|
+
*/
|
|
58
|
+
export declare function topologicalSort(goalStatus: GoalStatus): string[];
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.8.0: Goal Session 依赖图 — 就绪/阻塞判定、下一子目标建议、环检测、拓扑排序。
|
|
3
|
+
*
|
|
4
|
+
* 所有函数均为纯函数,不修改传入的 goalStatus 对象。
|
|
5
|
+
* 依赖语义:subgoal.depends_on 引用其他 subgoal 的 id,被引用者 status 必须为
|
|
6
|
+
* "accepted" 才算依赖满足。引用不存在的 id 视为数据不一致,按阻塞处理。
|
|
7
|
+
*/
|
|
8
|
+
import { PatchWardenError } from "../errors.js";
|
|
9
|
+
// ── 内部工具 ──────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* 构建 subgoal_id → Subgoal 的查找表。
|
|
12
|
+
*/
|
|
13
|
+
function buildSubgoalMap(goalStatus) {
|
|
14
|
+
const map = new Map();
|
|
15
|
+
for (const s of goalStatus.subgoals) {
|
|
16
|
+
map.set(s.id, s);
|
|
17
|
+
}
|
|
18
|
+
return map;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 判断单个 subgoal 的依赖是否全部满足(所有 depends_on 引用存在且 status 为 accepted)。
|
|
22
|
+
* depends_on 为空视为依赖满足。引用不存在的 id 视为未满足。
|
|
23
|
+
*/
|
|
24
|
+
function areDependenciesMet(subgoal, subgoalMap) {
|
|
25
|
+
for (const depId of subgoal.depends_on) {
|
|
26
|
+
const dep = subgoalMap.get(depId);
|
|
27
|
+
if (!dep)
|
|
28
|
+
return false;
|
|
29
|
+
if (dep.status !== "accepted")
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
// ── 公共 API ──────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* 返回所有 status 为 "ready" 且依赖全部满足的子目标。
|
|
37
|
+
* 依赖满足 = depends_on 中所有 id 都存在且对应 subgoal status 为 "accepted"。
|
|
38
|
+
* depends_on 为空数组视为依赖满足。
|
|
39
|
+
* depends_on 引用不存在的 subgoal 时,该子目标不算 ready(跳过)。
|
|
40
|
+
*/
|
|
41
|
+
export function getReadySubgoals(goalStatus) {
|
|
42
|
+
const subgoalMap = buildSubgoalMap(goalStatus);
|
|
43
|
+
return goalStatus.subgoals.filter((s) => s.status === "ready" && areDependenciesMet(s, subgoalMap));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 返回所有 status 为 "ready" 但依赖未全部满足的子目标,附带阻塞它的依赖 id 列表。
|
|
47
|
+
* blocked_by 包含 depends_on 中 status 不为 "accepted" 的 subgoal_id,
|
|
48
|
+
* 以及引用不存在的 id。
|
|
49
|
+
*/
|
|
50
|
+
export function getBlockedSubgoals(goalStatus) {
|
|
51
|
+
const subgoalMap = buildSubgoalMap(goalStatus);
|
|
52
|
+
const result = [];
|
|
53
|
+
for (const s of goalStatus.subgoals) {
|
|
54
|
+
if (s.status !== "ready")
|
|
55
|
+
continue;
|
|
56
|
+
const blockedBy = [];
|
|
57
|
+
for (const depId of s.depends_on) {
|
|
58
|
+
const dep = subgoalMap.get(depId);
|
|
59
|
+
if (!dep) {
|
|
60
|
+
blockedBy.push(depId);
|
|
61
|
+
}
|
|
62
|
+
else if (dep.status !== "accepted") {
|
|
63
|
+
blockedBy.push(depId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (blockedBy.length > 0) {
|
|
67
|
+
result.push({ subgoal: s, blocked_by: blockedBy });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 建议下一个可执行的子目标。
|
|
74
|
+
*
|
|
75
|
+
* 优先级:
|
|
76
|
+
* 1. 有 ready 子目标 → 返回第一个(按 subgoal 添加顺序)的 { subgoal_id, title, depends_on }
|
|
77
|
+
* 2. 无 ready 但有 blocked → 返回 { subgoal_id: null, reason: "dependencies_not_met", blocked_by: [...] }
|
|
78
|
+
* 3. 无 ready 无 blocked → 返回 { subgoal_id: null, reason: "no_ready_subgoal" }
|
|
79
|
+
*/
|
|
80
|
+
export function suggestNextSubgoal(goalStatus) {
|
|
81
|
+
const ready = getReadySubgoals(goalStatus);
|
|
82
|
+
if (ready.length > 0) {
|
|
83
|
+
const first = ready[0];
|
|
84
|
+
return {
|
|
85
|
+
subgoal_id: first.id,
|
|
86
|
+
title: first.title,
|
|
87
|
+
depends_on: [...first.depends_on],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const blocked = getBlockedSubgoals(goalStatus);
|
|
91
|
+
if (blocked.length > 0) {
|
|
92
|
+
return {
|
|
93
|
+
subgoal_id: null,
|
|
94
|
+
reason: "dependencies_not_met",
|
|
95
|
+
blocked_by: blocked.map((b) => b.subgoal.id),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
subgoal_id: null,
|
|
100
|
+
reason: "no_ready_subgoal",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 检测 subgoal 依赖图是否有环(DFS 三色标记法)。
|
|
105
|
+
*
|
|
106
|
+
* 沿 depends_on 深度优先遍历,遇到当前路径上正在访问的节点即为环。
|
|
107
|
+
* depends_on 引用不存在的 subgoal 不算环(忽略)。
|
|
108
|
+
*
|
|
109
|
+
* @returns 有环时返回环上的 subgoal_id 数组(任一环即可);无环返回 null。
|
|
110
|
+
*/
|
|
111
|
+
export function detectCycle(goalStatus) {
|
|
112
|
+
const subgoalMap = buildSubgoalMap(goalStatus);
|
|
113
|
+
const WHITE = 0;
|
|
114
|
+
const GRAY = 1;
|
|
115
|
+
const BLACK = 2;
|
|
116
|
+
const color = new Map();
|
|
117
|
+
for (const s of goalStatus.subgoals) {
|
|
118
|
+
color.set(s.id, WHITE);
|
|
119
|
+
}
|
|
120
|
+
let cycle = null;
|
|
121
|
+
const dfs = (id, path) => {
|
|
122
|
+
color.set(id, GRAY);
|
|
123
|
+
path.push(id);
|
|
124
|
+
const node = subgoalMap.get(id);
|
|
125
|
+
if (node) {
|
|
126
|
+
for (const depId of node.depends_on) {
|
|
127
|
+
if (!subgoalMap.has(depId))
|
|
128
|
+
continue; // 引用不存在,忽略
|
|
129
|
+
const depColor = color.get(depId);
|
|
130
|
+
if (depColor === GRAY) {
|
|
131
|
+
// 发现环:截取从依赖节点到当前节点的路径
|
|
132
|
+
const cycleStart = path.indexOf(depId);
|
|
133
|
+
cycle = path.slice(cycleStart);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (depColor === WHITE) {
|
|
137
|
+
if (dfs(depId, path))
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
path.pop();
|
|
143
|
+
color.set(id, BLACK);
|
|
144
|
+
return false;
|
|
145
|
+
};
|
|
146
|
+
for (const s of goalStatus.subgoals) {
|
|
147
|
+
if (color.get(s.id) === WHITE) {
|
|
148
|
+
if (dfs(s.id, []))
|
|
149
|
+
return cycle;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 对 subgoal 做拓扑排序(依赖在前)。
|
|
156
|
+
*
|
|
157
|
+
* 如果 B depends_on A,则 A 排在 B 之前。
|
|
158
|
+
* depends_on 引用不存在的 id 会被忽略(不影响排序)。
|
|
159
|
+
*
|
|
160
|
+
* @throws {PatchWardenError} reason="dependency_cycle" 当依赖图存在环时。
|
|
161
|
+
* @returns subgoal_id 数组,依赖项在前。
|
|
162
|
+
*/
|
|
163
|
+
export function topologicalSort(goalStatus) {
|
|
164
|
+
const cycle = detectCycle(goalStatus);
|
|
165
|
+
if (cycle) {
|
|
166
|
+
throw new PatchWardenError("dependency_cycle", `Dependency cycle detected among subgoals: ${cycle.join(" -> ")}`, "Remove the circular dependency among subgoals before performing topological sort.", true, { cycle });
|
|
167
|
+
}
|
|
168
|
+
const subgoalMap = buildSubgoalMap(goalStatus);
|
|
169
|
+
const visited = new Set();
|
|
170
|
+
const result = [];
|
|
171
|
+
const visit = (id) => {
|
|
172
|
+
if (visited.has(id))
|
|
173
|
+
return;
|
|
174
|
+
visited.add(id);
|
|
175
|
+
const node = subgoalMap.get(id);
|
|
176
|
+
if (node) {
|
|
177
|
+
for (const depId of node.depends_on) {
|
|
178
|
+
if (subgoalMap.has(depId)) {
|
|
179
|
+
visit(depId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
result.push(id);
|
|
184
|
+
};
|
|
185
|
+
for (const s of goalStatus.subgoals) {
|
|
186
|
+
visit(s.id);
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.8.0: Goal Session 子目标验收业务逻辑 — accept / reject / summarize。
|
|
3
|
+
*
|
|
4
|
+
* 三个公共函数:
|
|
5
|
+
* - acceptSubgoal:校验所有关联 task 已 accepted 后,将 subgoal 置为 accepted
|
|
6
|
+
* - rejectSubgoal:从任意非终态拒绝 subgoal,记录 rejected_reason
|
|
7
|
+
* - summarizeGoalProgress:统计 goal 进度,返回 completion_rate / blocked / risks
|
|
8
|
+
*
|
|
9
|
+
* 任务 status.json 路径解析:
|
|
10
|
+
* 优先用 getConfig().tasksDir(绝对路径直接用,相对路径 join workspaceRoot)。
|
|
11
|
+
* 所有函数接受可选的 workspaceRoot 参数用于测试。
|
|
12
|
+
*/
|
|
13
|
+
export interface GoalProgressSummary {
|
|
14
|
+
goal_id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
total: number;
|
|
17
|
+
accepted: number;
|
|
18
|
+
rejected: number;
|
|
19
|
+
running: number;
|
|
20
|
+
ready: number;
|
|
21
|
+
needs_fix: number;
|
|
22
|
+
done_by_agent: number;
|
|
23
|
+
completion_rate: string;
|
|
24
|
+
blocked_subgoals: Array<{
|
|
25
|
+
subgoal_id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
blocked_by: string[];
|
|
28
|
+
}>;
|
|
29
|
+
risks: Array<{
|
|
30
|
+
subgoal_id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
status: string;
|
|
33
|
+
reason: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 验收子目标:校验所有关联 task 已 accepted 后,将 subgoal 置为 accepted。
|
|
38
|
+
*
|
|
39
|
+
* 校验规则(理解 B — 更严格):
|
|
40
|
+
* 1. task_ids 不能为空(无关联任务无法验收)
|
|
41
|
+
* 2. 所有 task_ids 对应的 task status 必须为 "accepted"
|
|
42
|
+
* 3. 通过 updateSubgoalStatus 状态机校验(要求 subgoal 当前为 done_by_agent)
|
|
43
|
+
*
|
|
44
|
+
* @param goalId Goal 标识
|
|
45
|
+
* @param subgoalId Subgoal 标识
|
|
46
|
+
* @param workspaceRoot 可选,用于测试
|
|
47
|
+
* @returns { subgoal_id, status, accepted_at }
|
|
48
|
+
* @throws {PatchWardenError} reason="goal_not_found" | "subgoal_not_found" | "subgoal_not_ready" | "invalid_status_transition"
|
|
49
|
+
*/
|
|
50
|
+
export declare function acceptSubgoal(goalId: string, subgoalId: string, workspaceRoot?: string): {
|
|
51
|
+
subgoal_id: string;
|
|
52
|
+
status: "accepted";
|
|
53
|
+
accepted_at: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* 拒绝子目标:从任意非终态(ready/running/done_by_agent/needs_fix)拒绝 subgoal。
|
|
57
|
+
*
|
|
58
|
+
* 不使用 updateSubgoalStatus 的状态机校验(因为状态机只允许 done_by_agent → rejected),
|
|
59
|
+
* 直接手动设置 status 和 rejected_reason。
|
|
60
|
+
*
|
|
61
|
+
* @param goalId Goal 标识
|
|
62
|
+
* @param subgoalId Subgoal 标识
|
|
63
|
+
* @param reason 拒绝原因
|
|
64
|
+
* @param workspaceRoot 可选,用于测试
|
|
65
|
+
* @returns { subgoal_id, status, rejected_reason }
|
|
66
|
+
* @throws {PatchWardenError} reason="goal_not_found" | "subgoal_not_found" | "invalid_status_transition"
|
|
67
|
+
*/
|
|
68
|
+
export declare function rejectSubgoal(goalId: string, subgoalId: string, reason: string, workspaceRoot?: string): {
|
|
69
|
+
subgoal_id: string;
|
|
70
|
+
status: "rejected";
|
|
71
|
+
rejected_reason: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 汇总 Goal 进度:统计各状态数量、完成率、阻塞子目标、风险项。
|
|
75
|
+
*
|
|
76
|
+
* @param goalId Goal 标识
|
|
77
|
+
* @param workspaceRoot 可选,用于测试
|
|
78
|
+
* @returns GoalProgressSummary
|
|
79
|
+
* @throws {PatchWardenError} reason="goal_not_found"
|
|
80
|
+
*/
|
|
81
|
+
export declare function summarizeGoalProgress(goalId: string, workspaceRoot?: string): GoalProgressSummary;
|