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/tools/auditTask.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { join, resolve, relative, basename, sep, isAbsolute } from "node:path";
|
|
3
4
|
import { getTasksDir, getConfig } from "../config.js";
|
|
4
5
|
import { guardReadPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
5
|
-
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
6
|
+
import { guardSensitivePath, isSensitivePath } from "../security/sensitiveGuard.js";
|
|
7
|
+
import { evaluateAcceptance } from "../goal/acceptanceEngine.js";
|
|
8
|
+
import { renderAcceptanceMarkdown } from "../goal/acceptanceTemplate.js";
|
|
6
9
|
// Release claim patterns — anything that claims remote publish/release/deploy
|
|
7
10
|
const RELEASE_PATTERNS = [
|
|
8
11
|
/npm\s+package\s+version\s+published/i,
|
|
@@ -42,6 +45,361 @@ function findMdFiles(dir, maxDepth = 3) {
|
|
|
42
45
|
catch { /* skip unreadable dirs */ }
|
|
43
46
|
return results;
|
|
44
47
|
}
|
|
48
|
+
// ── v0.7.2: New audit checks (exported for testing) ────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Convert a glob pattern (supporting `**` and `*`) into a RegExp.
|
|
51
|
+
* `**` matches any number of path segments (including zero);
|
|
52
|
+
* `*` matches a single path segment (no `/`).
|
|
53
|
+
* Path separators in the pattern are expected to be `/`.
|
|
54
|
+
*/
|
|
55
|
+
function globToRegExp(pattern) {
|
|
56
|
+
// Normalize backslashes to forward slashes for matching consistency.
|
|
57
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
58
|
+
let re = "^";
|
|
59
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
60
|
+
const ch = normalized[i];
|
|
61
|
+
if (ch === "*") {
|
|
62
|
+
if (normalized[i + 1] === "*") {
|
|
63
|
+
// Consume the second '*' (and an optional following '/')
|
|
64
|
+
i++;
|
|
65
|
+
if (normalized[i + 1] === "/") {
|
|
66
|
+
i++;
|
|
67
|
+
// `**/` matches zero or more leading segments, or nothing.
|
|
68
|
+
re += "(?:.*/)?";
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// `**` at end or without trailing slash — match anything.
|
|
72
|
+
re += ".*";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Single `*` matches one segment (no `/`).
|
|
77
|
+
re += "[^/]*";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if ("/.+^$(){}|[]\\".includes(ch)) {
|
|
81
|
+
re += "\\" + ch;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
re += ch;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
re += "$";
|
|
88
|
+
return new RegExp(re);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check 1: forbidden_scope_violation (fail level).
|
|
92
|
+
* Returns null when no forbidden patterns are configured (skip).
|
|
93
|
+
* Returns pass when changed files do not match any forbidden pattern.
|
|
94
|
+
* Returns fail when any changed file path (or old_path) matches a forbidden glob.
|
|
95
|
+
*/
|
|
96
|
+
export function checkForbiddenScope(changedFiles, forbidden) {
|
|
97
|
+
if (!forbidden || forbidden.length === 0)
|
|
98
|
+
return null;
|
|
99
|
+
if (changedFiles.length === 0) {
|
|
100
|
+
return { name: "forbidden_scope_violation", result: "pass", detail: "No forbidden path violations." };
|
|
101
|
+
}
|
|
102
|
+
const patterns = forbidden.map((p) => globToRegExp(p));
|
|
103
|
+
const hits = [];
|
|
104
|
+
for (const file of changedFiles) {
|
|
105
|
+
const candidates = [file.path];
|
|
106
|
+
if (file.old_path)
|
|
107
|
+
candidates.push(file.old_path);
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
const normalized = candidate.replace(/\\/g, "/");
|
|
110
|
+
if (patterns.some((re) => re.test(normalized))) {
|
|
111
|
+
hits.push(normalized);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (hits.length > 0) {
|
|
116
|
+
return {
|
|
117
|
+
name: "forbidden_scope_violation",
|
|
118
|
+
result: "fail",
|
|
119
|
+
detail: `Changed files hit forbidden paths: ${[...new Set(hits)].join(", ")}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { name: "forbidden_scope_violation", result: "pass", detail: "No forbidden path violations." };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check 2: done_evidence_missing (warn level).
|
|
126
|
+
* Returns null when no done_evidence list is configured (skip).
|
|
127
|
+
* Returns pass when all listed files exist in the task directory.
|
|
128
|
+
* Returns warn when any listed file is missing.
|
|
129
|
+
*/
|
|
130
|
+
export function checkDoneEvidenceMissing(taskDir, doneEvidence) {
|
|
131
|
+
if (!doneEvidence || doneEvidence.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
const missing = [];
|
|
134
|
+
for (const filename of doneEvidence) {
|
|
135
|
+
if (!existsSync(join(taskDir, filename))) {
|
|
136
|
+
missing.push(filename);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (missing.length > 0) {
|
|
140
|
+
return {
|
|
141
|
+
name: "done_evidence_missing",
|
|
142
|
+
result: "warn",
|
|
143
|
+
detail: `Missing: ${missing.join(", ")}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return { name: "done_evidence_missing", result: "pass", detail: "All done_evidence files present." };
|
|
147
|
+
}
|
|
148
|
+
const CODE_EXTENSIONS = new Set([
|
|
149
|
+
".ts", ".js", ".jsx", ".tsx", ".py", ".go", ".java", ".rs",
|
|
150
|
+
".c", ".cpp", ".cs", ".rb", ".php", ".swift", ".kt", ".scala",
|
|
151
|
+
]);
|
|
152
|
+
/**
|
|
153
|
+
* Check 3: readme_changelog_sync (warn level).
|
|
154
|
+
* Returns null when no code files (by extension) were changed (skip).
|
|
155
|
+
* Returns pass when README.md or CHANGELOG.md (basename, case-insensitive) is also changed.
|
|
156
|
+
* Returns warn when code changed but neither documentation file was updated.
|
|
157
|
+
*/
|
|
158
|
+
export function checkReadmeChangelogSync(changedFiles) {
|
|
159
|
+
const paths = changedFiles.map((f) => f.path);
|
|
160
|
+
const hasCodeChange = paths.some((p) => {
|
|
161
|
+
const lower = p.toLowerCase();
|
|
162
|
+
const dot = lower.lastIndexOf(".");
|
|
163
|
+
if (dot === -1)
|
|
164
|
+
return false;
|
|
165
|
+
return CODE_EXTENSIONS.has(lower.slice(dot));
|
|
166
|
+
});
|
|
167
|
+
if (!hasCodeChange)
|
|
168
|
+
return null;
|
|
169
|
+
const docsUpdated = paths.some((p) => {
|
|
170
|
+
const name = basename(p).toLowerCase();
|
|
171
|
+
return name === "readme.md" || name === "changelog.md";
|
|
172
|
+
});
|
|
173
|
+
if (docsUpdated) {
|
|
174
|
+
return {
|
|
175
|
+
name: "readme_changelog_sync",
|
|
176
|
+
result: "pass",
|
|
177
|
+
detail: "README.md or CHANGELOG.md updated with code changes.",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name: "readme_changelog_sync",
|
|
182
|
+
result: "warn",
|
|
183
|
+
detail: "Code changes detected but README.md/CHANGELOG.md not updated.",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check 4: package_manifest_consistency (warn level).
|
|
188
|
+
* Returns null when package.json is not among changed files (skip).
|
|
189
|
+
* Returns pass when package.json parses as JSON and has non-empty name and version strings.
|
|
190
|
+
* Returns warn otherwise.
|
|
191
|
+
*/
|
|
192
|
+
export function checkPackageManifestConsistency(changedFiles, repoPathSafe) {
|
|
193
|
+
const includesPackageJson = changedFiles.some((f) => basename(f.path).toLowerCase() === "package.json");
|
|
194
|
+
if (!includesPackageJson)
|
|
195
|
+
return null;
|
|
196
|
+
try {
|
|
197
|
+
const pkgPath = join(repoPathSafe, "package.json");
|
|
198
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
199
|
+
const parsed = JSON.parse(raw);
|
|
200
|
+
const hasName = typeof parsed.name === "string" && parsed.name.length > 0;
|
|
201
|
+
const hasVersion = typeof parsed.version === "string" && parsed.version.length > 0;
|
|
202
|
+
if (hasName && hasVersion) {
|
|
203
|
+
return {
|
|
204
|
+
name: "package_manifest_consistency",
|
|
205
|
+
result: "pass",
|
|
206
|
+
detail: "package.json manifest fields are valid.",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// fall through to warn
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
name: "package_manifest_consistency",
|
|
215
|
+
result: "warn",
|
|
216
|
+
detail: "package.json could not be parsed or missing name/version.",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check 5: sensitive_path_access (fail level).
|
|
221
|
+
* Returns null when there are no changed files (skip).
|
|
222
|
+
* Returns pass when no changed file path (or old_path) is sensitive.
|
|
223
|
+
* Returns fail when any changed file path is flagged by isSensitivePath.
|
|
224
|
+
*/
|
|
225
|
+
export function checkSensitivePathAccess(changedFiles) {
|
|
226
|
+
if (changedFiles.length === 0)
|
|
227
|
+
return null;
|
|
228
|
+
const hits = [];
|
|
229
|
+
for (const file of changedFiles) {
|
|
230
|
+
const candidates = [file.path];
|
|
231
|
+
if (file.old_path)
|
|
232
|
+
candidates.push(file.old_path);
|
|
233
|
+
for (const candidate of candidates) {
|
|
234
|
+
if (isSensitivePath(candidate)) {
|
|
235
|
+
hits.push(candidate);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (hits.length > 0) {
|
|
240
|
+
return {
|
|
241
|
+
name: "sensitive_path_access",
|
|
242
|
+
result: "fail",
|
|
243
|
+
detail: `Sensitive paths accessed: ${[...new Set(hits)].join(", ")}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return { name: "sensitive_path_access", result: "pass", detail: "No sensitive path access detected." };
|
|
247
|
+
}
|
|
248
|
+
const HIGH_RISK_COMMAND_PATTERNS = [
|
|
249
|
+
/\bnpm(?:\.cmd)?\s+publish\b/i,
|
|
250
|
+
/\bgit\s+push\b/i,
|
|
251
|
+
/\bcurl\s+https?:\/\//i,
|
|
252
|
+
/\bInvoke-WebRequest\b/i,
|
|
253
|
+
/\bRemove-Item\b[\s\S]{0,80}\b-Recurse\b/i,
|
|
254
|
+
/\brm\s+-rf\b/i,
|
|
255
|
+
];
|
|
256
|
+
function extractCommands(text) {
|
|
257
|
+
const found = [];
|
|
258
|
+
const runRe = /npm(?:\.cmd)?\s+run\s+([a-zA-Z0-9:_-]+)/gi;
|
|
259
|
+
let m;
|
|
260
|
+
while ((m = runRe.exec(text)) !== null)
|
|
261
|
+
found.push({ type: "npm-run", name: m[1] });
|
|
262
|
+
const bareRe = /npm(?:\.cmd)?\s+(?!run\b)([a-zA-Z]+)/gi;
|
|
263
|
+
while ((m = bareRe.exec(text)) !== null)
|
|
264
|
+
found.push({ type: "npm-bare", name: m[1] });
|
|
265
|
+
const nodeRe = /node\s+([a-zA-Z0-9_./\\-]+)/gi;
|
|
266
|
+
while ((m = nodeRe.exec(text)) !== null)
|
|
267
|
+
found.push({ type: "node", name: normalizeCommandName(m[1]) });
|
|
268
|
+
const npxRe = /npx\s+([a-zA-Z0-9_./\\@-]+)/gi;
|
|
269
|
+
while ((m = npxRe.exec(text)) !== null)
|
|
270
|
+
found.push({ type: "npx", name: normalizeCommandName(m[1]) });
|
|
271
|
+
const pythonRe = /python(?:3|\.exe)?\s+([a-zA-Z0-9_./\\-]+)/gi;
|
|
272
|
+
while ((m = pythonRe.exec(text)) !== null)
|
|
273
|
+
found.push({ type: "python", name: normalizeCommandName(m[1]) });
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
function normalizeCommandName(value) {
|
|
277
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
278
|
+
}
|
|
279
|
+
function commandKey(command) {
|
|
280
|
+
return command.name;
|
|
281
|
+
}
|
|
282
|
+
function readPackageScripts(repoPath) {
|
|
283
|
+
if (!repoPath)
|
|
284
|
+
return {};
|
|
285
|
+
const pkgJsonPath = join(repoPath, "package.json");
|
|
286
|
+
if (!existsSync(pkgJsonPath))
|
|
287
|
+
return {};
|
|
288
|
+
try {
|
|
289
|
+
guardSensitivePath(pkgJsonPath);
|
|
290
|
+
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
291
|
+
if (!parsed.scripts || typeof parsed.scripts !== "object" || Array.isArray(parsed.scripts))
|
|
292
|
+
return {};
|
|
293
|
+
return Object.fromEntries(Object.entries(parsed.scripts).filter(([, value]) => typeof value === "string"));
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function buildCommandWhitelist(verifyCommands, testCommand, repoPath) {
|
|
300
|
+
const scripts = readPackageScripts(repoPath);
|
|
301
|
+
const allowed = new Set();
|
|
302
|
+
const transitive = new Set();
|
|
303
|
+
const queue = [];
|
|
304
|
+
const sources = [...verifyCommands];
|
|
305
|
+
if (testCommand)
|
|
306
|
+
sources.push(testCommand);
|
|
307
|
+
for (const src of sources) {
|
|
308
|
+
for (const command of extractCommands(src)) {
|
|
309
|
+
allowed.add(commandKey(command));
|
|
310
|
+
queue.push({ command, depth: 0 });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let inspectedFiles = 0;
|
|
314
|
+
const seen = new Set();
|
|
315
|
+
while (queue.length > 0) {
|
|
316
|
+
const { command, depth } = queue.shift();
|
|
317
|
+
if (depth > 4)
|
|
318
|
+
continue;
|
|
319
|
+
const key = `${command.type}:${command.name}`;
|
|
320
|
+
if (seen.has(key))
|
|
321
|
+
continue;
|
|
322
|
+
seen.add(key);
|
|
323
|
+
const scriptName = command.type === "npm-run" || command.type === "npm-bare" ? command.name : null;
|
|
324
|
+
if (scriptName && scripts[scriptName]) {
|
|
325
|
+
for (const nested of extractCommands(scripts[scriptName])) {
|
|
326
|
+
transitive.add(commandKey(nested));
|
|
327
|
+
allowed.add(commandKey(nested));
|
|
328
|
+
queue.push({ command: nested, depth: depth + 1 });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if ((command.type === "node" || command.type === "python") && repoPath && inspectedFiles < 20) {
|
|
332
|
+
const nestedText = readLocalScript(repoPath, command.name);
|
|
333
|
+
if (nestedText) {
|
|
334
|
+
inspectedFiles++;
|
|
335
|
+
for (const nested of extractCommands(nestedText)) {
|
|
336
|
+
transitive.add(commandKey(nested));
|
|
337
|
+
allowed.add(commandKey(nested));
|
|
338
|
+
queue.push({ command: nested, depth: depth + 1 });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { allowed, transitive };
|
|
344
|
+
}
|
|
345
|
+
function readLocalScript(repoPath, scriptPath) {
|
|
346
|
+
const resolvedRepo = resolve(repoPath);
|
|
347
|
+
const resolvedScript = resolve(resolvedRepo, scriptPath);
|
|
348
|
+
const rel = relative(resolvedRepo, resolvedScript);
|
|
349
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || rel === "" || isAbsolute(rel))
|
|
350
|
+
return null;
|
|
351
|
+
if (!existsSync(resolvedScript))
|
|
352
|
+
return null;
|
|
353
|
+
try {
|
|
354
|
+
const stat = statSync(resolvedScript);
|
|
355
|
+
if (!stat.isFile() || stat.size > 200000)
|
|
356
|
+
return null;
|
|
357
|
+
return readFileSync(resolvedScript, "utf-8");
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function findHighRiskCommandEvidence(text) {
|
|
364
|
+
const found = [];
|
|
365
|
+
for (const pattern of HIGH_RISK_COMMAND_PATTERNS) {
|
|
366
|
+
const match = text.match(pattern);
|
|
367
|
+
if (match)
|
|
368
|
+
found.push(match[0]);
|
|
369
|
+
}
|
|
370
|
+
return [...new Set(found)];
|
|
371
|
+
}
|
|
372
|
+
export function checkUnrecordedCommandExecution(testLogContent, resultMdContent, verifyCommands, testCommand, repoPath = null) {
|
|
373
|
+
const hasLog = testLogContent && testLogContent.length > 0;
|
|
374
|
+
const hasResult = resultMdContent && resultMdContent.length > 0;
|
|
375
|
+
if (!hasLog && !hasResult)
|
|
376
|
+
return null;
|
|
377
|
+
const combined = `${testLogContent || ""}\n${resultMdContent || ""}`;
|
|
378
|
+
const highRisk = findHighRiskCommandEvidence(combined);
|
|
379
|
+
if (highRisk.length > 0) {
|
|
380
|
+
return { name: "unrecorded_command_execution", result: "fail", detail: `High-risk command evidence: ${highRisk.join(", ")}` };
|
|
381
|
+
}
|
|
382
|
+
const whitelist = buildCommandWhitelist(verifyCommands, testCommand, repoPath);
|
|
383
|
+
const discovered = [];
|
|
384
|
+
if (hasLog)
|
|
385
|
+
discovered.push(...extractCommands(testLogContent));
|
|
386
|
+
if (hasResult)
|
|
387
|
+
discovered.push(...extractCommands(resultMdContent));
|
|
388
|
+
const unrecorded = new Set();
|
|
389
|
+
for (const cmd of discovered) {
|
|
390
|
+
if (!whitelist.allowed.has(commandKey(cmd)))
|
|
391
|
+
unrecorded.add(commandKey(cmd));
|
|
392
|
+
}
|
|
393
|
+
if (unrecorded.size > 0) {
|
|
394
|
+
return { name: "unrecorded_command_execution", result: "warn", detail: `Unrecorded commands: ${[...unrecorded].join(", ")}` };
|
|
395
|
+
}
|
|
396
|
+
const transitive = [...whitelist.transitive].filter((command) => discovered.some((entry) => commandKey(entry) === command));
|
|
397
|
+
return {
|
|
398
|
+
name: "unrecorded_command_execution",
|
|
399
|
+
result: "pass",
|
|
400
|
+
detail: transitive.length > 0 ? `All commands are whitelisted; transitive_verified_command: ${transitive.join(", ")}` : "All commands in whitelist.",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
45
403
|
export function auditTask(taskId) {
|
|
46
404
|
const config = getConfig();
|
|
47
405
|
const tasksDir = getTasksDir(config);
|
|
@@ -69,12 +427,13 @@ export function auditTask(taskId) {
|
|
|
69
427
|
// ── 1. Task status ──
|
|
70
428
|
const taskStatus = statusData.status || "unknown";
|
|
71
429
|
const failedStatuses = new Set(["failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"]);
|
|
430
|
+
const doneStatuses = new Set(["done", "done_by_agent"]);
|
|
72
431
|
checks.push({
|
|
73
432
|
name: "task_status",
|
|
74
|
-
result: taskStatus
|
|
433
|
+
result: doneStatuses.has(taskStatus) ? "pass" : failedStatuses.has(taskStatus) ? "fail" : "warn",
|
|
75
434
|
detail: `Task status is "${taskStatus}".`,
|
|
76
435
|
});
|
|
77
|
-
if (taskStatus
|
|
436
|
+
if (!doneStatuses.has(taskStatus) && !failedStatuses.has(taskStatus)) {
|
|
78
437
|
addManualVerification(`Task status is "${taskStatus}"; audit evidence may be incomplete until terminal state.`);
|
|
79
438
|
}
|
|
80
439
|
// ── 2. result.md ──
|
|
@@ -129,10 +488,15 @@ export function auditTask(taskId) {
|
|
|
129
488
|
? `${newOutOfScope.length} new out-of-scope change(s) detected during task execution.`
|
|
130
489
|
: "No new out-of-scope changes recorded.",
|
|
131
490
|
});
|
|
491
|
+
// Extract changed_files from change evidence (used by several v0.7.2 checks).
|
|
492
|
+
let changedFiles = [];
|
|
132
493
|
const changedFilesFile = join(taskDir, "changed-files.json");
|
|
133
494
|
if (existsSync(changedFilesFile)) {
|
|
134
495
|
try {
|
|
135
496
|
const changeEvidence = JSON.parse(readFileSync(changedFilesFile, "utf-8"));
|
|
497
|
+
if (Array.isArray(changeEvidence.changed_files)) {
|
|
498
|
+
changedFiles = changeEvidence.changed_files;
|
|
499
|
+
}
|
|
136
500
|
const hygiene = changeEvidence.artifact_hygiene;
|
|
137
501
|
if (hygiene?.counts) {
|
|
138
502
|
const trackedArtifacts = Number(hygiene.counts.tracked_build_artifacts || 0);
|
|
@@ -314,10 +678,18 @@ export function auditTask(taskId) {
|
|
|
314
678
|
addManualVerification("Verify remote npm, GitHub Release, and Git tag claims against authoritative remote services.");
|
|
315
679
|
}
|
|
316
680
|
// ── 9. test.log Exit code check ──
|
|
681
|
+
let testLogContent = null;
|
|
317
682
|
if (hasTestLog) {
|
|
318
|
-
|
|
683
|
+
let logContent;
|
|
684
|
+
try {
|
|
685
|
+
logContent = readFileSync(testLogFile, "utf-8");
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
logContent = "";
|
|
689
|
+
}
|
|
690
|
+
testLogContent = logContent;
|
|
319
691
|
// Check test_command visibility
|
|
320
|
-
if (statusData.test_command &&
|
|
692
|
+
if (statusData.test_command && logContent.includes(statusData.test_command)) {
|
|
321
693
|
checks.push({ name: "test_command_in_log", result: "pass", detail: "test.log contains the configured test command." });
|
|
322
694
|
}
|
|
323
695
|
else if (statusData.test_command) {
|
|
@@ -325,7 +697,7 @@ export function auditTask(taskId) {
|
|
|
325
697
|
addManualVerification("Confirm that test.log belongs to the configured test command.");
|
|
326
698
|
}
|
|
327
699
|
// Extract Exit code
|
|
328
|
-
const exitMatch =
|
|
700
|
+
const exitMatch = logContent.match(/Exit\s*code:\s*(\d+)/i);
|
|
329
701
|
if (exitMatch) {
|
|
330
702
|
const exitCode = parseInt(exitMatch[1]);
|
|
331
703
|
if (exitCode === 0) {
|
|
@@ -343,6 +715,33 @@ export function auditTask(taskId) {
|
|
|
343
715
|
addManualVerification("The configured test command has no recorded exit code; rerun or inspect verification evidence.");
|
|
344
716
|
}
|
|
345
717
|
}
|
|
718
|
+
// ── v0.7.2: New audit checks ──
|
|
719
|
+
// Read result.md content (guarded) for the unrecorded_command_execution check.
|
|
720
|
+
let resultMdContent = null;
|
|
721
|
+
if (hasResult) {
|
|
722
|
+
try {
|
|
723
|
+
resultMdContent = readFileSync(resultFile, "utf-8");
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
resultMdContent = null;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const verifyCommands = Array.isArray(statusData.verify_commands) ? statusData.verify_commands : [];
|
|
730
|
+
const testCommand = typeof statusData.test_command === "string" ? statusData.test_command : null;
|
|
731
|
+
const forbidden = Array.isArray(statusData.forbidden) ? statusData.forbidden : null;
|
|
732
|
+
const doneEvidence = Array.isArray(statusData.done_evidence) ? statusData.done_evidence : null;
|
|
733
|
+
const newChecks = [
|
|
734
|
+
checkForbiddenScope(changedFiles, forbidden),
|
|
735
|
+
checkDoneEvidenceMissing(taskDir, doneEvidence),
|
|
736
|
+
checkReadmeChangelogSync(changedFiles),
|
|
737
|
+
checkPackageManifestConsistency(changedFiles, repoPathSafe),
|
|
738
|
+
checkSensitivePathAccess(changedFiles),
|
|
739
|
+
checkUnrecordedCommandExecution(testLogContent, resultMdContent, verifyCommands, testCommand, repoPathSafe),
|
|
740
|
+
];
|
|
741
|
+
for (const check of newChecks) {
|
|
742
|
+
if (check)
|
|
743
|
+
checks.push(check);
|
|
744
|
+
}
|
|
346
745
|
// ── 10. Summarize ──
|
|
347
746
|
const failCount = checks.filter((c) => c.result === "fail").length;
|
|
348
747
|
const warnCount = checks.filter((c) => c.result === "warn").length;
|
|
@@ -390,9 +789,63 @@ export function auditTask(taskId) {
|
|
|
390
789
|
...actions.map((a) => `- ${a}`),
|
|
391
790
|
].join("\n");
|
|
392
791
|
writeFileSync(join(taskDir, "independent-review.md"), reviewMd, "utf-8");
|
|
792
|
+
// ── v0.7.2: 集成 acceptanceEngine ──
|
|
793
|
+
const releaseClaimsUnverified = allReleaseClaims.length > 0;
|
|
794
|
+
const acceptanceEvidence = {
|
|
795
|
+
task_id: taskId,
|
|
796
|
+
task_status: taskStatus,
|
|
797
|
+
result_md_exists: hasResult,
|
|
798
|
+
result_json_exists: existsSync(resultJsonFile),
|
|
799
|
+
verify_json_exists: existsSync(verifyJsonFile),
|
|
800
|
+
test_log_exists: hasTestLog,
|
|
801
|
+
git_diff_exists: existsSync(join(taskDir, "git.diff")),
|
|
802
|
+
verify_status: (() => {
|
|
803
|
+
if (!existsSync(verifyJsonFile))
|
|
804
|
+
return null;
|
|
805
|
+
try {
|
|
806
|
+
const v = JSON.parse(readFileSync(verifyJsonFile, "utf-8"));
|
|
807
|
+
return v.status === "passed" ? "passed" : v.status === "failed" ? "failed" : "skipped";
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
})(),
|
|
813
|
+
new_out_of_scope_changes: newOutOfScope,
|
|
814
|
+
goal: statusData.goal ?? null,
|
|
815
|
+
scope: Array.isArray(statusData.scope) ? statusData.scope : null,
|
|
816
|
+
forbidden: Array.isArray(statusData.forbidden) ? statusData.forbidden : null,
|
|
817
|
+
verification: Array.isArray(statusData.verification) ? statusData.verification : null,
|
|
818
|
+
done_evidence: Array.isArray(statusData.done_evidence) ? statusData.done_evidence : null,
|
|
819
|
+
artifact_status: statusData.artifact_status ?? null,
|
|
820
|
+
release_claims_unverified: releaseClaimsUnverified,
|
|
821
|
+
checks: checks.map((c) => ({ name: c.name, result: c.result, detail: c.detail })),
|
|
822
|
+
};
|
|
823
|
+
const acceptanceResult = evaluateAcceptance(acceptanceEvidence);
|
|
824
|
+
// 回写 status.json 的 acceptance_status(仅对 done_by_agent 有意义)
|
|
825
|
+
if (taskStatus === "done_by_agent") {
|
|
826
|
+
const updatedStatus = {
|
|
827
|
+
...statusData,
|
|
828
|
+
acceptance_status: acceptanceResult.acceptance_status,
|
|
829
|
+
updated_at: new Date().toISOString(),
|
|
830
|
+
};
|
|
831
|
+
writeFileSync(statusFile, JSON.stringify(updatedStatus, null, 2), "utf-8");
|
|
832
|
+
}
|
|
833
|
+
// 导出 ACCEPTANCE.md
|
|
834
|
+
const acceptanceMd = renderAcceptanceMarkdown(taskId, acceptanceResult, acceptanceEvidence);
|
|
835
|
+
writeFileSync(join(taskDir, "ACCEPTANCE.md"), acceptanceMd, "utf-8");
|
|
393
836
|
return {
|
|
394
837
|
task_id: taskId,
|
|
395
838
|
verdict,
|
|
839
|
+
acceptance: {
|
|
840
|
+
verdict: acceptanceResult.verdict,
|
|
841
|
+
status: acceptanceResult.acceptance_status ?? "null",
|
|
842
|
+
reason: acceptanceResult.reason,
|
|
843
|
+
reasons: acceptanceResult.reasons,
|
|
844
|
+
required_evidence: acceptanceResult.required_evidence,
|
|
845
|
+
next_suggested_task: acceptanceResult.next_suggested_task,
|
|
846
|
+
fail_checks: acceptanceResult.fail_checks,
|
|
847
|
+
warn_checks: acceptanceResult.warn_checks,
|
|
848
|
+
},
|
|
396
849
|
summary,
|
|
397
850
|
checks,
|
|
398
851
|
risks,
|
package/dist/tools/cancelTask.js
CHANGED
|
@@ -15,7 +15,7 @@ export function requestTaskTermination(taskId, force) {
|
|
|
15
15
|
throw new Error(`Task not found: "${taskId}"`);
|
|
16
16
|
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
17
17
|
const currentStatus = data.status;
|
|
18
|
-
if (["done", "failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"].includes(currentStatus)) {
|
|
18
|
+
if (["done", "done_by_agent", "failed", "failed_verification", "failed_scope_violation", "failed_policy_violation", "canceled"].includes(currentStatus)) {
|
|
19
19
|
return {
|
|
20
20
|
task_id: taskId,
|
|
21
21
|
previous_status: currentStatus,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReleaseStage } from "../release/releaseGate.js";
|
|
2
|
+
export interface CheckReleaseGateInput {
|
|
3
|
+
repo_path: string;
|
|
4
|
+
target_stage: ReleaseStage;
|
|
5
|
+
package_name?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
github_repo?: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* MCP tool handler for the release gate.
|
|
12
|
+
*
|
|
13
|
+
* Validates repo_path against the workspace boundary, runs the release-gate
|
|
14
|
+
* stages up to target_stage, and returns the result as a JSON text blob.
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkReleaseGate(input: CheckReleaseGateInput): Promise<{
|
|
17
|
+
content: Array<{
|
|
18
|
+
type: "text";
|
|
19
|
+
text: string;
|
|
20
|
+
}>;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
import { guardWorkspacePath } from "../security/pathGuard.js";
|
|
3
|
+
import { runReleaseGateCheck, } from "../release/releaseGate.js";
|
|
4
|
+
/**
|
|
5
|
+
* MCP tool handler for the release gate.
|
|
6
|
+
*
|
|
7
|
+
* Validates repo_path against the workspace boundary, runs the release-gate
|
|
8
|
+
* stages up to target_stage, and returns the result as a JSON text blob.
|
|
9
|
+
*/
|
|
10
|
+
export async function checkReleaseGate(input) {
|
|
11
|
+
const config = getConfig();
|
|
12
|
+
const repoPath = guardWorkspacePath(input.repo_path, config.workspaceRoot);
|
|
13
|
+
const result = await runReleaseGateCheck(repoPath, input.target_stage, {
|
|
14
|
+
packageName: input.package_name,
|
|
15
|
+
version: input.version,
|
|
16
|
+
githubRepo: input.github_repo,
|
|
17
|
+
branch: input.branch,
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { type ChangePolicy, type TaskTemplateName } from "./taskTemplates.js";
|
|
2
2
|
import { type PendingReason, type WatcherStatusSnapshot } from "../watcherStatus.js";
|
|
3
|
-
export type TaskStatus = "pending" | "running" | "done" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "canceled";
|
|
4
|
-
|
|
3
|
+
export type TaskStatus = "pending" | "running" | "collecting_artifacts" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "done" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "canceled";
|
|
4
|
+
/**
|
|
5
|
+
* v0.7.2 acceptance status — only meaningful for done_by_agent.
|
|
6
|
+
* - "pending": done_by_agent reached but not yet audited/accepted
|
|
7
|
+
* - "accepted": audit_task confirmed all evidence passes
|
|
8
|
+
* - "rejected": audit_task found fail-level issues
|
|
9
|
+
* - "needs_fix": audit_task found warn-level issues requiring fixes
|
|
10
|
+
* - "blocked": audit_task blocked by approval boundary (e.g. release claims)
|
|
11
|
+
* - null: status has no acceptance semantics (running, failed, etc.)
|
|
12
|
+
*/
|
|
13
|
+
export type AcceptanceStatus = "pending" | "accepted" | "rejected" | "needs_fix" | "blocked" | null;
|
|
14
|
+
export type TaskPhase = "queued" | "preparing" | "executing_agent" | "running_tests" | "collecting_artifacts" | "canceling" | "terminating" | "completed" | "failed" | "failed_verification" | "failed_scope_violation" | "failed_policy_violation" | "failed_stale" | "orphaned" | "done_by_agent" | "accepted" | "rejected" | "needs_fix" | "blocked" | "canceled";
|
|
5
15
|
export interface CreateTaskInput {
|
|
6
16
|
plan_id?: string;
|
|
7
17
|
inline_plan?: string;
|
|
@@ -16,6 +26,12 @@ export interface CreateTaskInput {
|
|
|
16
26
|
timeout_seconds?: number;
|
|
17
27
|
execution_mode?: "assess_only" | "execute";
|
|
18
28
|
assessment_id?: string;
|
|
29
|
+
scope?: string[];
|
|
30
|
+
forbidden?: string[];
|
|
31
|
+
verification?: string[];
|
|
32
|
+
done_evidence?: string[];
|
|
33
|
+
goal_id?: string;
|
|
34
|
+
subgoal_id?: string;
|
|
19
35
|
}
|
|
20
36
|
export interface AssessOnlyOutput {
|
|
21
37
|
assessment_id: string;
|
package/dist/tools/createTask.js
CHANGED
|
@@ -17,6 +17,7 @@ import { expandTaskTemplate, TASK_TEMPLATE_NAMES, } from "./taskTemplates.js";
|
|
|
17
17
|
import { PATCHWARDEN_VERSION } from "../version.js";
|
|
18
18
|
import { getLastToolCatalogSnapshot, resolveToolProfile } from "./toolCatalog.js";
|
|
19
19
|
import { derivePendingReason, readWatcherStatus, } from "../watcherStatus.js";
|
|
20
|
+
import { routeAgent } from "../agents/agentRouter.js";
|
|
20
21
|
export function createTask(input) {
|
|
21
22
|
const config = getConfig();
|
|
22
23
|
const tasksDir = getTasksDir(config);
|
|
@@ -33,8 +34,23 @@ export function createTask(input) {
|
|
|
33
34
|
// ── Validate required fields ──
|
|
34
35
|
// agent and repo_path are required unless assessment_id fills them.
|
|
35
36
|
// assess_only always requires them because the risk engine needs to check them.
|
|
37
|
+
// v1.0.0: agent is now optional — if omitted, routeAgent recommends one based on scope/goal/plan.
|
|
38
|
+
let agentSelectionReason;
|
|
36
39
|
if (!effectiveInput.agent || effectiveInput.agent.trim() === "") {
|
|
37
|
-
|
|
40
|
+
const configuredAgents = Object.keys(config.agents);
|
|
41
|
+
const routeResult = routeAgent({
|
|
42
|
+
goal: effectiveInput.goal,
|
|
43
|
+
scope: effectiveInput.scope,
|
|
44
|
+
inline_plan: effectiveInput.inline_plan,
|
|
45
|
+
template: effectiveInput.template,
|
|
46
|
+
configuredAgents,
|
|
47
|
+
});
|
|
48
|
+
effectiveInput.agent = routeResult.recommended_agent;
|
|
49
|
+
agentSelectionReason = {
|
|
50
|
+
recommended_agent: routeResult.recommended_agent,
|
|
51
|
+
reason: routeResult.reason,
|
|
52
|
+
fallback: routeResult.fallback,
|
|
53
|
+
};
|
|
38
54
|
}
|
|
39
55
|
const planSources = [
|
|
40
56
|
effectiveInput.plan_id?.trim() ? "plan_id" : "",
|
|
@@ -344,6 +360,18 @@ export function createTask(input) {
|
|
|
344
360
|
last_heartbeat_at: new Date().toISOString(),
|
|
345
361
|
current_command: null,
|
|
346
362
|
error: null,
|
|
363
|
+
// v0.7.2: 验收标准绑定到任务创建
|
|
364
|
+
goal: effectiveInput.goal || null,
|
|
365
|
+
scope: effectiveInput.scope ?? null,
|
|
366
|
+
forbidden: effectiveInput.forbidden ?? null,
|
|
367
|
+
verification: effectiveInput.verification ?? null,
|
|
368
|
+
done_evidence: effectiveInput.done_evidence ?? null,
|
|
369
|
+
acceptance_status: null,
|
|
370
|
+
// v0.8.0: Goal Session 关联
|
|
371
|
+
goal_id: effectiveInput.goal_id ?? null,
|
|
372
|
+
subgoal_id: effectiveInput.subgoal_id ?? null,
|
|
373
|
+
// v1.0.0: Agent routing decision (only present when agent was auto-routed)
|
|
374
|
+
...(agentSelectionReason ? { agent_selection_reason: agentSelectionReason } : {}),
|
|
347
375
|
};
|
|
348
376
|
writeFileSync(statusFile, JSON.stringify(statusData, null, 2), "utf-8");
|
|
349
377
|
writeTaskProgress(taskDir, "queued", {
|