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
package/dist/tools/safeStatus.js
CHANGED
|
@@ -5,6 +5,7 @@ import { guardReadPath } from "../security/pathGuard.js";
|
|
|
5
5
|
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
6
6
|
import { readTaskRuntime } from "../taskRuntime.js";
|
|
7
7
|
import { readWatcherStatus } from "../watcherStatus.js";
|
|
8
|
+
import { diagnoseTask } from "./diagnoseTask.js";
|
|
8
9
|
export function safeStatus(taskId, config) {
|
|
9
10
|
const cfg = config || getConfig();
|
|
10
11
|
const tasksDir = getTasksDir(cfg);
|
|
@@ -16,6 +17,8 @@ export function safeStatus(taskId, config) {
|
|
|
16
17
|
return {
|
|
17
18
|
task_id: taskId,
|
|
18
19
|
status: "not_found",
|
|
20
|
+
legacy_status: null,
|
|
21
|
+
acceptance_status: null,
|
|
19
22
|
phase: null,
|
|
20
23
|
created_at: null,
|
|
21
24
|
started_at: null,
|
|
@@ -28,6 +31,8 @@ export function safeStatus(taskId, config) {
|
|
|
28
31
|
watcher_state: null,
|
|
29
32
|
error_code: null,
|
|
30
33
|
error_summary: null,
|
|
34
|
+
stale_seconds: null,
|
|
35
|
+
diagnosis: null,
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
38
|
guardReadPath(statusFile, cfg.workspaceRoot, cfg.tasksDir);
|
|
@@ -53,9 +58,44 @@ export function safeStatus(taskId, config) {
|
|
|
53
58
|
errorCode = status.status;
|
|
54
59
|
errorSummary = `Task ended with status: ${status.status}`;
|
|
55
60
|
}
|
|
61
|
+
// v0.7.2: derive legacy_status and acceptance_status
|
|
62
|
+
const statusStr = status.status || "not_found";
|
|
63
|
+
const legacyStatus = (statusStr === "done_by_agent" || statusStr === "accepted") ? "done" : null;
|
|
64
|
+
const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
65
|
+
const acceptanceStatus = statusStr === "done_by_agent"
|
|
66
|
+
? (typeof status.acceptance_status === "string" && VALID_ACCEPTANCE.includes(status.acceptance_status)
|
|
67
|
+
? status.acceptance_status
|
|
68
|
+
: "pending") // default for done_by_agent when field missing
|
|
69
|
+
: null;
|
|
70
|
+
// v0.7.0: compute stale_seconds from the most recent heartbeat
|
|
71
|
+
const heartbeatStr = (typeof runtime.last_heartbeat_at === "string" && runtime.last_heartbeat_at) ||
|
|
72
|
+
(typeof status.last_heartbeat_at === "string" && status.last_heartbeat_at) ||
|
|
73
|
+
(typeof status.updated_at === "string" && status.updated_at) ||
|
|
74
|
+
null;
|
|
75
|
+
let staleSeconds = null;
|
|
76
|
+
if (heartbeatStr) {
|
|
77
|
+
const heartbeatMs = Date.parse(heartbeatStr);
|
|
78
|
+
if (Number.isFinite(heartbeatMs)) {
|
|
79
|
+
staleSeconds = Math.max(0, Math.round((Date.now() - heartbeatMs) / 1000));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// v0.7.0: lightweight diagnosis snapshot — only for non-terminal tasks.
|
|
83
|
+
// Wrap in try/catch so a diagnosis failure never breaks safe_status.
|
|
84
|
+
let diagnosis = null;
|
|
85
|
+
if (statusStr === "running" || statusStr === "collecting_artifacts" || statusStr === "pending") {
|
|
86
|
+
try {
|
|
87
|
+
const result = diagnoseTask({ task_id: taskId }, cfg);
|
|
88
|
+
diagnosis = { type: result.diagnosis, confidence: result.confidence };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
diagnosis = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
56
94
|
return {
|
|
57
95
|
task_id: taskId,
|
|
58
|
-
status:
|
|
96
|
+
status: statusStr,
|
|
97
|
+
legacy_status: legacyStatus,
|
|
98
|
+
acceptance_status: acceptanceStatus,
|
|
59
99
|
phase,
|
|
60
100
|
created_at: typeof status.created_at === "string" ? status.created_at : null,
|
|
61
101
|
started_at: typeof status.started_at === "string" ? status.started_at : null,
|
|
@@ -68,5 +108,7 @@ export function safeStatus(taskId, config) {
|
|
|
68
108
|
watcher_state: watcher.status,
|
|
69
109
|
error_code: errorCode,
|
|
70
110
|
error_summary: errorSummary,
|
|
111
|
+
stale_seconds: staleSeconds,
|
|
112
|
+
diagnosis,
|
|
71
113
|
};
|
|
72
114
|
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export interface SafeViewOptions {
|
|
2
|
+
max_items?: number;
|
|
3
|
+
}
|
|
4
|
+
export declare function safeResult(taskId: string, options?: SafeViewOptions): {
|
|
5
|
+
task_id: string;
|
|
6
|
+
status: string;
|
|
7
|
+
terminal: boolean;
|
|
8
|
+
acceptance_status: "failed" | "pending" | "ready_for_review" | "needs_review";
|
|
9
|
+
phase: string;
|
|
10
|
+
repo_path: string;
|
|
11
|
+
changed_files_total: number;
|
|
12
|
+
out_of_scope_changes_total: number;
|
|
13
|
+
artifact_hygiene_counts: Record<string, number>;
|
|
14
|
+
release_artifacts_count: number;
|
|
15
|
+
artifact_status: string | null;
|
|
16
|
+
verification: {
|
|
17
|
+
status: string;
|
|
18
|
+
command_count: number;
|
|
19
|
+
passed_commands: number;
|
|
20
|
+
failed_commands: number;
|
|
21
|
+
skipped_commands: number;
|
|
22
|
+
headline: string;
|
|
23
|
+
};
|
|
24
|
+
warnings: string[];
|
|
25
|
+
errors: string[];
|
|
26
|
+
failure_reason: string | null;
|
|
27
|
+
failed_command: string | null;
|
|
28
|
+
next_action: string;
|
|
29
|
+
execution_blocked: boolean;
|
|
30
|
+
pending_reason: string | null;
|
|
31
|
+
redacted: boolean;
|
|
32
|
+
redaction_categories: string[];
|
|
33
|
+
};
|
|
34
|
+
export declare function safeTestSummary(taskId: string): {
|
|
35
|
+
task_id: string;
|
|
36
|
+
status: string;
|
|
37
|
+
command_count: number;
|
|
38
|
+
passed_commands: number;
|
|
39
|
+
failed_commands: number;
|
|
40
|
+
skipped_commands: number;
|
|
41
|
+
commands: {
|
|
42
|
+
command: string;
|
|
43
|
+
status: string;
|
|
44
|
+
exit_code: any;
|
|
45
|
+
duration_ms: number;
|
|
46
|
+
}[];
|
|
47
|
+
headline: string;
|
|
48
|
+
};
|
|
49
|
+
export declare function safeDiffSummary(taskId: string, options?: SafeViewOptions): {
|
|
50
|
+
task_id: string;
|
|
51
|
+
changed_files_total: number;
|
|
52
|
+
additions: number;
|
|
53
|
+
deletions: number;
|
|
54
|
+
diff_available: boolean;
|
|
55
|
+
diff_truncated: boolean;
|
|
56
|
+
patch_mode: any;
|
|
57
|
+
files: {
|
|
58
|
+
path: string;
|
|
59
|
+
old_path: string | null;
|
|
60
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
61
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
62
|
+
tracked: boolean;
|
|
63
|
+
ignored: boolean;
|
|
64
|
+
}[];
|
|
65
|
+
artifact_hygiene_counts: Record<string, any>;
|
|
66
|
+
large_diff_omitted: boolean;
|
|
67
|
+
truncated: boolean;
|
|
68
|
+
};
|
|
69
|
+
export declare function safeAudit(taskId: string, options?: SafeViewOptions): {
|
|
70
|
+
task_id: string;
|
|
71
|
+
verdict: "pass" | "warn" | "fail";
|
|
72
|
+
acceptance: {
|
|
73
|
+
verdict: string;
|
|
74
|
+
status: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
fail_checks: string[];
|
|
77
|
+
warn_checks: string[];
|
|
78
|
+
next_suggested_task: string;
|
|
79
|
+
};
|
|
80
|
+
check_counts: {
|
|
81
|
+
pass: number;
|
|
82
|
+
warn: number;
|
|
83
|
+
fail: number;
|
|
84
|
+
};
|
|
85
|
+
checks: {
|
|
86
|
+
name: string;
|
|
87
|
+
result: "pass" | "warn" | "fail";
|
|
88
|
+
detail: string;
|
|
89
|
+
}[];
|
|
90
|
+
fail_checks: {
|
|
91
|
+
name: string;
|
|
92
|
+
result: "pass" | "warn" | "fail";
|
|
93
|
+
detail: string;
|
|
94
|
+
}[];
|
|
95
|
+
warn_checks: {
|
|
96
|
+
name: string;
|
|
97
|
+
result: "pass" | "warn" | "fail";
|
|
98
|
+
detail: string;
|
|
99
|
+
}[];
|
|
100
|
+
possible_false_positives: {
|
|
101
|
+
check: string;
|
|
102
|
+
reason: string;
|
|
103
|
+
}[];
|
|
104
|
+
manual_verification_required: boolean;
|
|
105
|
+
manual_verification_items: string[];
|
|
106
|
+
recommended_next_actions: string[];
|
|
107
|
+
};
|
|
108
|
+
export declare function safeDirectSummary(sessionId: string, options?: SafeViewOptions): {
|
|
109
|
+
view: "summary" | "finalize";
|
|
110
|
+
session_id: string;
|
|
111
|
+
title: string;
|
|
112
|
+
repo_path: string;
|
|
113
|
+
created_at: string;
|
|
114
|
+
expires_at: string;
|
|
115
|
+
finalized: boolean;
|
|
116
|
+
finalized_at: string | null;
|
|
117
|
+
audited: boolean;
|
|
118
|
+
changed_files_total: number;
|
|
119
|
+
files: {
|
|
120
|
+
path: string;
|
|
121
|
+
old_path: string | null;
|
|
122
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
123
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
124
|
+
tracked: boolean;
|
|
125
|
+
ignored: boolean;
|
|
126
|
+
}[];
|
|
127
|
+
artifact_hygiene_counts: {};
|
|
128
|
+
source_changes: {
|
|
129
|
+
path: string;
|
|
130
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
131
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
132
|
+
tracked: boolean;
|
|
133
|
+
ignored: boolean;
|
|
134
|
+
reason: string;
|
|
135
|
+
}[];
|
|
136
|
+
tracked_build_artifacts: {
|
|
137
|
+
path: string;
|
|
138
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
139
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
140
|
+
tracked: boolean;
|
|
141
|
+
ignored: boolean;
|
|
142
|
+
reason: string;
|
|
143
|
+
}[];
|
|
144
|
+
runtime_generated_files: {
|
|
145
|
+
path: string;
|
|
146
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
147
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
148
|
+
tracked: boolean;
|
|
149
|
+
ignored: boolean;
|
|
150
|
+
reason: string;
|
|
151
|
+
}[];
|
|
152
|
+
suspicious_changes: {
|
|
153
|
+
path: string;
|
|
154
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
155
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
156
|
+
tracked: boolean;
|
|
157
|
+
ignored: boolean;
|
|
158
|
+
reason: string;
|
|
159
|
+
}[];
|
|
160
|
+
verification: {
|
|
161
|
+
command: string;
|
|
162
|
+
exit_code: number | null;
|
|
163
|
+
passed: boolean;
|
|
164
|
+
timed_out: boolean;
|
|
165
|
+
started_at: string;
|
|
166
|
+
finished_at: string;
|
|
167
|
+
}[];
|
|
168
|
+
large_diff_omitted: boolean;
|
|
169
|
+
truncated: boolean;
|
|
170
|
+
};
|
|
171
|
+
export declare function safeFinalizeDirectSession(sessionId: string, options?: SafeViewOptions): {
|
|
172
|
+
finalized: boolean;
|
|
173
|
+
next_action: string;
|
|
174
|
+
view: "summary" | "finalize";
|
|
175
|
+
session_id: string;
|
|
176
|
+
title: string;
|
|
177
|
+
repo_path: string;
|
|
178
|
+
created_at: string;
|
|
179
|
+
expires_at: string;
|
|
180
|
+
finalized_at: string | null;
|
|
181
|
+
audited: boolean;
|
|
182
|
+
changed_files_total: number;
|
|
183
|
+
files: {
|
|
184
|
+
path: string;
|
|
185
|
+
old_path: string | null;
|
|
186
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
187
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
188
|
+
tracked: boolean;
|
|
189
|
+
ignored: boolean;
|
|
190
|
+
}[];
|
|
191
|
+
artifact_hygiene_counts: {};
|
|
192
|
+
source_changes: {
|
|
193
|
+
path: string;
|
|
194
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
195
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
196
|
+
tracked: boolean;
|
|
197
|
+
ignored: boolean;
|
|
198
|
+
reason: string;
|
|
199
|
+
}[];
|
|
200
|
+
tracked_build_artifacts: {
|
|
201
|
+
path: string;
|
|
202
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
203
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
204
|
+
tracked: boolean;
|
|
205
|
+
ignored: boolean;
|
|
206
|
+
reason: string;
|
|
207
|
+
}[];
|
|
208
|
+
runtime_generated_files: {
|
|
209
|
+
path: string;
|
|
210
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
211
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
212
|
+
tracked: boolean;
|
|
213
|
+
ignored: boolean;
|
|
214
|
+
reason: string;
|
|
215
|
+
}[];
|
|
216
|
+
suspicious_changes: {
|
|
217
|
+
path: string;
|
|
218
|
+
change: "added" | "modified" | "deleted" | "renamed";
|
|
219
|
+
kind: "source" | "build_artifact" | "runtime_generated";
|
|
220
|
+
tracked: boolean;
|
|
221
|
+
ignored: boolean;
|
|
222
|
+
reason: string;
|
|
223
|
+
}[];
|
|
224
|
+
verification: {
|
|
225
|
+
command: string;
|
|
226
|
+
exit_code: number | null;
|
|
227
|
+
passed: boolean;
|
|
228
|
+
timed_out: boolean;
|
|
229
|
+
started_at: string;
|
|
230
|
+
finished_at: string;
|
|
231
|
+
}[];
|
|
232
|
+
large_diff_omitted: boolean;
|
|
233
|
+
truncated: boolean;
|
|
234
|
+
};
|
|
235
|
+
export declare function safeAuditDirectSession(sessionId: string, options?: SafeViewOptions): {
|
|
236
|
+
session_id: string;
|
|
237
|
+
decision: "pass" | "warn" | "fail";
|
|
238
|
+
reason_codes: string[];
|
|
239
|
+
blocking_findings: string[];
|
|
240
|
+
warnings: string[];
|
|
241
|
+
evidence: {
|
|
242
|
+
changed_files_total: number;
|
|
243
|
+
verification_runs: {
|
|
244
|
+
command: string;
|
|
245
|
+
exit_code: number | null;
|
|
246
|
+
passed: boolean;
|
|
247
|
+
timed_out: boolean;
|
|
248
|
+
started_at: string;
|
|
249
|
+
finished_at: string;
|
|
250
|
+
}[];
|
|
251
|
+
diff_available: boolean;
|
|
252
|
+
summary_available: boolean;
|
|
253
|
+
audit_available: boolean;
|
|
254
|
+
};
|
|
255
|
+
next_action: string;
|
|
256
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { getConfig, getTasksDir } from "../config.js";
|
|
4
|
+
import { auditSession } from "./auditSession.js";
|
|
5
|
+
import { auditTask } from "./auditTask.js";
|
|
6
|
+
import { finalizeDirectSession } from "./finalizeDirectSession.js";
|
|
7
|
+
import { getTaskSummary } from "./getTaskSummary.js";
|
|
8
|
+
import { guardReadPath } from "../security/pathGuard.js";
|
|
9
|
+
import { redactSensitiveValue } from "../security/contentRedaction.js";
|
|
10
|
+
import { readDirectSession } from "../direct/directSessionStore.js";
|
|
11
|
+
export function safeResult(taskId, options = {}) {
|
|
12
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
13
|
+
const summary = getTaskSummary(taskId, { view: "compact", max_items: maxItems });
|
|
14
|
+
return redact({
|
|
15
|
+
task_id: summary.task_id,
|
|
16
|
+
status: summary.status,
|
|
17
|
+
terminal: summary.terminal,
|
|
18
|
+
acceptance_status: summary.acceptance_status,
|
|
19
|
+
phase: summary.phase,
|
|
20
|
+
repo_path: summary.repo_path,
|
|
21
|
+
changed_files_total: summary.changed_files_total,
|
|
22
|
+
out_of_scope_changes_total: summary.out_of_scope_changes_total,
|
|
23
|
+
artifact_hygiene_counts: summary.artifact_hygiene.counts,
|
|
24
|
+
release_artifacts_count: summary.release_artifacts_count,
|
|
25
|
+
artifact_status: summary.artifact_status,
|
|
26
|
+
verification: summary.verification_summary,
|
|
27
|
+
warnings: limitStrings(summary.warnings, maxItems),
|
|
28
|
+
errors: limitStrings(summary.errors, maxItems),
|
|
29
|
+
failure_reason: summary.failure_reason,
|
|
30
|
+
failed_command: summary.failed_command,
|
|
31
|
+
next_action: summary.suggested_next_action,
|
|
32
|
+
execution_blocked: summary.execution_blocked,
|
|
33
|
+
pending_reason: summary.pending_reason,
|
|
34
|
+
redacted: summary.redacted,
|
|
35
|
+
redaction_categories: summary.redaction_categories,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function safeTestSummary(taskId) {
|
|
39
|
+
const { taskDir, config } = getTaskDir(taskId);
|
|
40
|
+
const verify = readJson(join(taskDir, "verify.json"), config);
|
|
41
|
+
const summary = getTaskSummary(taskId, { view: "compact", max_items: 8 });
|
|
42
|
+
const commands = asArray(verify.commands).map((entry) => ({
|
|
43
|
+
command: String(entry.command || ""),
|
|
44
|
+
status: String(entry.status || "unknown"),
|
|
45
|
+
exit_code: entry.exit_code ?? null,
|
|
46
|
+
duration_ms: Number(entry.duration_ms || 0),
|
|
47
|
+
}));
|
|
48
|
+
return redact({
|
|
49
|
+
task_id: taskId,
|
|
50
|
+
status: String(verify.status || summary.verification_summary.status || "not_available"),
|
|
51
|
+
command_count: commands.length,
|
|
52
|
+
passed_commands: commands.filter((entry) => entry.status === "passed").length,
|
|
53
|
+
failed_commands: commands.filter((entry) => ["failed", "timed_out", "canceled"].includes(entry.status)).length,
|
|
54
|
+
skipped_commands: commands.filter((entry) => entry.status === "skipped").length,
|
|
55
|
+
commands,
|
|
56
|
+
headline: summary.verification_summary.headline,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function safeDiffSummary(taskId, options = {}) {
|
|
60
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
61
|
+
const { taskDir, config } = getTaskDir(taskId);
|
|
62
|
+
const changes = readJson(join(taskDir, "changed-files.json"), config);
|
|
63
|
+
const changedFiles = asArray(changes.changed_files);
|
|
64
|
+
const hygiene = asRecord(changes.artifact_hygiene);
|
|
65
|
+
return redact({
|
|
66
|
+
task_id: taskId,
|
|
67
|
+
changed_files_total: changedFiles.length,
|
|
68
|
+
additions: Number(changes.additions || 0),
|
|
69
|
+
deletions: Number(changes.deletions || 0),
|
|
70
|
+
diff_available: Boolean(changes.diff_available),
|
|
71
|
+
diff_truncated: Boolean(changes.diff_truncated),
|
|
72
|
+
patch_mode: changes.patch_mode || null,
|
|
73
|
+
files: changedFiles.slice(0, maxItems).map((file) => ({
|
|
74
|
+
path: file.path,
|
|
75
|
+
old_path: file.old_path || null,
|
|
76
|
+
change: file.change,
|
|
77
|
+
kind: file.kind,
|
|
78
|
+
tracked: Boolean(file.tracked),
|
|
79
|
+
ignored: Boolean(file.ignored),
|
|
80
|
+
})),
|
|
81
|
+
artifact_hygiene_counts: asRecord(hygiene.counts),
|
|
82
|
+
large_diff_omitted: true,
|
|
83
|
+
truncated: changedFiles.length > maxItems,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export function safeAudit(taskId, options = {}) {
|
|
87
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
88
|
+
return auditToSafe(auditTask(taskId), maxItems);
|
|
89
|
+
}
|
|
90
|
+
export function safeDirectSummary(sessionId, options = {}) {
|
|
91
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
92
|
+
const session = readDirectSession(sessionId);
|
|
93
|
+
return directSessionToSafe(session, maxItems, "summary");
|
|
94
|
+
}
|
|
95
|
+
export function safeFinalizeDirectSession(sessionId, options = {}) {
|
|
96
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
97
|
+
const finalized = finalizeDirectSession({ session_id: sessionId });
|
|
98
|
+
const session = readDirectSession(sessionId);
|
|
99
|
+
return redact({
|
|
100
|
+
...directSessionToSafe(session, maxItems, "finalize"),
|
|
101
|
+
finalized: finalized.finalized,
|
|
102
|
+
next_action: finalized.next_action,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function safeAuditDirectSession(sessionId, options = {}) {
|
|
106
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
107
|
+
const audit = auditSession({ session_id: sessionId });
|
|
108
|
+
return redact({
|
|
109
|
+
session_id: audit.session_id,
|
|
110
|
+
decision: audit.decision,
|
|
111
|
+
reason_codes: audit.reason_codes.slice(0, maxItems),
|
|
112
|
+
blocking_findings: limitStrings(audit.blocking_findings, maxItems),
|
|
113
|
+
warnings: limitStrings(audit.warnings, maxItems),
|
|
114
|
+
evidence: {
|
|
115
|
+
changed_files_total: audit.evidence.changed_files_total,
|
|
116
|
+
verification_runs: summarizeVerificationRuns(audit.evidence.verification_runs),
|
|
117
|
+
diff_available: Boolean(audit.evidence.diff_path),
|
|
118
|
+
summary_available: Boolean(audit.evidence.summary_path),
|
|
119
|
+
audit_available: Boolean(audit.evidence.audit_path),
|
|
120
|
+
},
|
|
121
|
+
next_action: audit.next_action,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function auditToSafe(audit, maxItems) {
|
|
125
|
+
const checks = audit.checks.map((check) => ({
|
|
126
|
+
name: check.name,
|
|
127
|
+
result: check.result,
|
|
128
|
+
detail: truncate(check.detail, 240),
|
|
129
|
+
}));
|
|
130
|
+
return redact({
|
|
131
|
+
task_id: audit.task_id,
|
|
132
|
+
verdict: audit.verdict,
|
|
133
|
+
acceptance: {
|
|
134
|
+
verdict: audit.acceptance.verdict,
|
|
135
|
+
status: audit.acceptance.status,
|
|
136
|
+
reason: truncate(audit.acceptance.reason, 240),
|
|
137
|
+
fail_checks: audit.acceptance.fail_checks.slice(0, maxItems).map((check) => check.name),
|
|
138
|
+
warn_checks: audit.acceptance.warn_checks.slice(0, maxItems).map((check) => check.name),
|
|
139
|
+
next_suggested_task: truncate(audit.acceptance.next_suggested_task, 240),
|
|
140
|
+
},
|
|
141
|
+
check_counts: {
|
|
142
|
+
pass: checks.filter((check) => check.result === "pass").length,
|
|
143
|
+
warn: checks.filter((check) => check.result === "warn").length,
|
|
144
|
+
fail: checks.filter((check) => check.result === "fail").length,
|
|
145
|
+
},
|
|
146
|
+
checks: checks.slice(0, maxItems),
|
|
147
|
+
fail_checks: checks.filter((check) => check.result === "fail").slice(0, maxItems),
|
|
148
|
+
warn_checks: checks.filter((check) => check.result === "warn").slice(0, maxItems),
|
|
149
|
+
possible_false_positives: audit.possible_false_positives.slice(0, maxItems).map((item) => ({
|
|
150
|
+
check: item.check,
|
|
151
|
+
reason: truncate(item.reason, 240),
|
|
152
|
+
})),
|
|
153
|
+
manual_verification_required: audit.manual_verification_required,
|
|
154
|
+
manual_verification_items: limitStrings(audit.manual_verification_items, maxItems),
|
|
155
|
+
recommended_next_actions: limitStrings(audit.recommended_next_actions, maxItems),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function directSessionToSafe(session, maxItems, view) {
|
|
159
|
+
const artifacts = session.change_artifacts;
|
|
160
|
+
const changedFiles = artifacts?.changed_files || [];
|
|
161
|
+
return redact({
|
|
162
|
+
view,
|
|
163
|
+
session_id: session.session_id,
|
|
164
|
+
title: session.title || "",
|
|
165
|
+
repo_path: session.repo_path,
|
|
166
|
+
created_at: session.created_at,
|
|
167
|
+
expires_at: session.expires_at,
|
|
168
|
+
finalized: session.finalized,
|
|
169
|
+
finalized_at: session.finalized_at,
|
|
170
|
+
audited: session.audited,
|
|
171
|
+
changed_files_total: changedFiles.length,
|
|
172
|
+
files: changedFiles.slice(0, maxItems).map((file) => ({
|
|
173
|
+
path: file.path,
|
|
174
|
+
old_path: file.old_path || null,
|
|
175
|
+
change: file.change,
|
|
176
|
+
kind: file.kind,
|
|
177
|
+
tracked: Boolean(file.tracked),
|
|
178
|
+
ignored: Boolean(file.ignored),
|
|
179
|
+
})),
|
|
180
|
+
artifact_hygiene_counts: artifacts?.artifact_hygiene.counts || {},
|
|
181
|
+
source_changes: limitClassified(artifacts?.artifact_hygiene.source_changes, maxItems),
|
|
182
|
+
tracked_build_artifacts: limitClassified(artifacts?.artifact_hygiene.tracked_build_artifacts, maxItems),
|
|
183
|
+
runtime_generated_files: limitClassified(artifacts?.artifact_hygiene.runtime_generated_files, maxItems),
|
|
184
|
+
suspicious_changes: limitClassified(artifacts?.artifact_hygiene.suspicious_changes, maxItems),
|
|
185
|
+
verification: summarizeVerificationRuns(session.verification_runs),
|
|
186
|
+
large_diff_omitted: true,
|
|
187
|
+
truncated: changedFiles.length > maxItems,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function summarizeVerificationRuns(runs) {
|
|
191
|
+
return runs.map((run) => ({
|
|
192
|
+
command: run.command,
|
|
193
|
+
exit_code: run.exit_code,
|
|
194
|
+
passed: run.passed,
|
|
195
|
+
timed_out: run.timed_out,
|
|
196
|
+
started_at: run.started_at,
|
|
197
|
+
finished_at: run.finished_at,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
function limitClassified(value, maxItems) {
|
|
201
|
+
return (value || []).slice(0, maxItems).map((entry) => ({
|
|
202
|
+
path: entry.path,
|
|
203
|
+
change: entry.change,
|
|
204
|
+
kind: entry.kind,
|
|
205
|
+
tracked: entry.tracked,
|
|
206
|
+
ignored: entry.ignored,
|
|
207
|
+
reason: truncate(entry.reason, 160),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
function getTaskDir(taskId) {
|
|
211
|
+
const config = getConfig();
|
|
212
|
+
const taskDir = resolve(getTasksDir(config), taskId);
|
|
213
|
+
guardReadPath(join(taskDir, "status.json"), config.workspaceRoot, config.tasksDir);
|
|
214
|
+
return { config, taskDir };
|
|
215
|
+
}
|
|
216
|
+
function readJson(path, config) {
|
|
217
|
+
guardReadPath(path, config.workspaceRoot, config.tasksDir);
|
|
218
|
+
if (!existsSync(path))
|
|
219
|
+
return {};
|
|
220
|
+
try {
|
|
221
|
+
const raw = readFileSync(path, "utf-8").replace(/^\uFEFF/, "");
|
|
222
|
+
return asRecord(JSON.parse(raw));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function normalizeMaxItems(value) {
|
|
229
|
+
if (value === undefined)
|
|
230
|
+
return 8;
|
|
231
|
+
if (!Number.isInteger(value) || value < 1 || value > 50) {
|
|
232
|
+
throw new Error("max_items must be an integer from 1 to 50.");
|
|
233
|
+
}
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
function asArray(value) {
|
|
237
|
+
return Array.isArray(value) ? value : [];
|
|
238
|
+
}
|
|
239
|
+
function asRecord(value) {
|
|
240
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
241
|
+
}
|
|
242
|
+
function limitStrings(values, maxItems) {
|
|
243
|
+
return values.slice(0, maxItems).map((value) => truncate(value, 240));
|
|
244
|
+
}
|
|
245
|
+
function truncate(value, maxChars) {
|
|
246
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
|
|
247
|
+
}
|
|
248
|
+
function redact(value) {
|
|
249
|
+
return redactSensitiveValue(value).value;
|
|
250
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.9.0: Schema 漂移检测模块。
|
|
3
|
+
*
|
|
4
|
+
* 检测 registry 元数据与实际工具 schema 是否漂移,
|
|
5
|
+
* 并聚合为可被 doctor 自检消费的 SchemaDriftResult。
|
|
6
|
+
*
|
|
7
|
+
* 本模块只做检测和报告,不做运行时阻断。
|
|
8
|
+
*/
|
|
9
|
+
import type { PatchWardenToolMeta } from "./toolRegistry.js";
|
|
10
|
+
export interface SchemaDriftResult {
|
|
11
|
+
/** true 表示无 drift,false 表示有 drift */
|
|
12
|
+
ok: boolean;
|
|
13
|
+
/** drift 描述列表(每个 drift 一条警告消息) */
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}
|
|
16
|
+
/** toolDefs Map 的值类型:至少包含 inputSchema。 */
|
|
17
|
+
export interface ToolDefLike {
|
|
18
|
+
inputSchema: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 检查 registry 中记录的 inputSchemaDigest 与实际 toolDef 的 inputSchema digest 是否一致。
|
|
22
|
+
* toolDefs 中找不到的工具会被跳过(不报 drift)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkRegistrySchemaDigest(registry: PatchWardenToolMeta[], toolDefs: Map<string, ToolDefLike>): SchemaDriftResult;
|
|
25
|
+
/**
|
|
26
|
+
* 校验 chatgpt_core profile 的工具 manifest 是否稳定。
|
|
27
|
+
*
|
|
28
|
+
* toolCatalog.ts 未导出独立的 manifest hash 计算函数(stableJson 为私有),
|
|
29
|
+
* 因此采用 fallback 方案:校验 CHATGPT_CORE_TOOL_NAMES 中的工具名
|
|
30
|
+
* 全部存在于 toolDefs 中(集合比较,不比较顺序——Map 迭代顺序取决于构造
|
|
31
|
+
* 方式,不一定与 chatgpt_core profile 顺序一致)。
|
|
32
|
+
*
|
|
33
|
+
* 真正的顺序稳定性由 CHATGPT_CORE_TOOL_NAMES 常量本身保证(顺序固定),
|
|
34
|
+
* 如果常量被修改,这里的名称集合比较也会检测到变化。
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkChatgptCoreManifestStable(toolDefs: Map<string, ToolDefLike>): SchemaDriftResult;
|
|
37
|
+
/**
|
|
38
|
+
* 校验 chatgpt_core profile 的工具数仍与 CHATGPT_CORE_TOOL_NAMES 一致。
|
|
39
|
+
* 用于检测是否有新工具被意外追加到 chatgpt_core profile。
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkNewToolsProfileAppend(registry: PatchWardenToolMeta[]): SchemaDriftResult;
|
|
42
|
+
/**
|
|
43
|
+
* 聚合执行所有 schema drift 检查,合并 warnings。
|
|
44
|
+
* ok = warnings.length === 0
|
|
45
|
+
*/
|
|
46
|
+
export declare function runAllSchemaDriftChecks(registry: PatchWardenToolMeta[], toolDefs: Map<string, ToolDefLike>): SchemaDriftResult;
|