@pushpalsdev/cli 1.0.18 → 1.0.20
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/dist/pushpals-cli.js +291 -44
- package/package.json +1 -1
- package/runtime/configs/backend.toml +1 -1
- package/runtime/configs/default.toml +1 -1
- package/runtime/sandbox/apps/workerpals/.python-version +1 -0
- package/runtime/sandbox/apps/workerpals/Dockerfile.sandbox +71 -0
- package/runtime/sandbox/apps/workerpals/package.json +25 -0
- package/runtime/sandbox/apps/workerpals/pyproject.toml +8 -0
- package/runtime/sandbox/apps/workerpals/src/backends/backend_config.ts +119 -0
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +2029 -0
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe_backend.ts +48 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +1259 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +110 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +67 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +563 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_backend.ts +161 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +536 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +746 -0
- package/runtime/sandbox/apps/workerpals/src/backends/shared/test_settings_resolver.py +60 -0
- package/runtime/sandbox/apps/workerpals/src/backends/task_execute_registry.ts +21 -0
- package/runtime/sandbox/apps/workerpals/src/backends/types.ts +52 -0
- package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +149 -0
- package/runtime/sandbox/apps/workerpals/src/common/executor_backend.ts +15 -0
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +210 -0
- package/runtime/sandbox/apps/workerpals/src/common/logger.ts +65 -0
- package/runtime/sandbox/apps/workerpals/src/common/types.ts +9 -0
- package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +66 -0
- package/runtime/sandbox/apps/workerpals/src/context_manager.ts +45 -0
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +1842 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +3063 -0
- package/runtime/sandbox/apps/workerpals/src/job_runner.ts +194 -0
- package/runtime/sandbox/apps/workerpals/src/shell_manager.ts +210 -0
- package/runtime/sandbox/apps/workerpals/src/timeout_policy.ts +24 -0
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +1436 -0
- package/runtime/sandbox/apps/workerpals/tsconfig.json +15 -0
- package/runtime/sandbox/apps/workerpals/uv.lock +2014 -0
- package/runtime/sandbox/bun.lock +2591 -0
- package/runtime/sandbox/configs/backend.toml +79 -0
- package/runtime/sandbox/configs/default.toml +260 -0
- package/runtime/sandbox/configs/dev.toml +2 -0
- package/runtime/sandbox/configs/local.example.toml +129 -0
- package/runtime/sandbox/package.json +65 -0
- package/runtime/sandbox/packages/protocol/README.md +168 -0
- package/runtime/sandbox/packages/protocol/package.json +37 -0
- package/runtime/sandbox/packages/protocol/scripts/copy-schemas.js +17 -0
- package/runtime/sandbox/packages/protocol/src/a2a/README.md +52 -0
- package/runtime/sandbox/packages/protocol/src/a2a/mapping.ts +55 -0
- package/runtime/sandbox/packages/protocol/src/index.browser.ts +25 -0
- package/runtime/sandbox/packages/protocol/src/index.ts +25 -0
- package/runtime/sandbox/packages/protocol/src/schemas/approvals.schema.json +6 -0
- package/runtime/sandbox/packages/protocol/src/schemas/envelope.schema.json +96 -0
- package/runtime/sandbox/packages/protocol/src/schemas/events.schema.json +679 -0
- package/runtime/sandbox/packages/protocol/src/schemas/http.schema.json +50 -0
- package/runtime/sandbox/packages/protocol/src/types.ts +267 -0
- package/runtime/sandbox/packages/protocol/src/validate.browser.ts +154 -0
- package/runtime/sandbox/packages/protocol/src/validate.ts +233 -0
- package/runtime/sandbox/packages/protocol/src/version.ts +1 -0
- package/runtime/sandbox/packages/protocol/tsconfig.json +20 -0
- package/runtime/sandbox/packages/shared/package.json +19 -0
- package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +400 -0
- package/runtime/sandbox/packages/shared/src/client_preflight.ts +286 -0
- package/runtime/sandbox/packages/shared/src/communication.ts +313 -0
- package/runtime/sandbox/packages/shared/src/config.ts +2180 -0
- package/runtime/sandbox/packages/shared/src/config_template_parity.ts +70 -0
- package/runtime/sandbox/packages/shared/src/git_backend.ts +205 -0
- package/runtime/sandbox/packages/shared/src/index.ts +101 -0
- package/runtime/sandbox/packages/shared/src/local_network.ts +101 -0
- package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +314 -0
- package/runtime/sandbox/packages/shared/src/prompts.ts +64 -0
- package/runtime/sandbox/packages/shared/src/repo.ts +134 -0
- package/runtime/sandbox/packages/shared/src/session_event_visibility.ts +25 -0
- package/runtime/sandbox/packages/shared/src/vision.ts +247 -0
- package/runtime/sandbox/packages/shared/tsconfig.json +16 -0
- package/runtime/sandbox/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
- package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +36 -0
- package/runtime/sandbox/prompts/workerpals/commit_message_user_prompt.md +7 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_timeout_note.md +1 -0
- package/runtime/sandbox/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
- package/runtime/sandbox/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
- package/runtime/sandbox/prompts/workerpals/openhands_task_user_prompt.md +6 -0
- package/runtime/sandbox/prompts/workerpals/openhands_timeout_note.md +1 -0
- package/runtime/sandbox/prompts/workerpals/pr_description.md +42 -0
- package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
- package/runtime/sandbox/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
- package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +115 -0
- package/runtime/sandbox/protocol/schemas/approvals.schema.json +6 -0
- package/runtime/sandbox/protocol/schemas/envelope.schema.json +96 -0
- package/runtime/sandbox/protocol/schemas/events.schema.json +679 -0
- package/runtime/sandbox/protocol/schemas/http.schema.json +50 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"types": ["node"],
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"noEmit": false
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"tsc": "tsc",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"protocol": "workspace:*"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "~5.9.2",
|
|
16
|
+
"@types/bun": "latest"
|
|
17
|
+
},
|
|
18
|
+
"private": true
|
|
19
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
|
|
3
|
+
// Deterministic autonomy policy primitives shared across server/remotebuddy/worker.
|
|
4
|
+
export type AutonomyObjectiveType =
|
|
5
|
+
| "flaky_test"
|
|
6
|
+
| "lint_fix"
|
|
7
|
+
| "type_fix"
|
|
8
|
+
| "small_refactor"
|
|
9
|
+
| "feature_small"
|
|
10
|
+
| "feature_medium"
|
|
11
|
+
| "feature_large"
|
|
12
|
+
| "docs"
|
|
13
|
+
| "dep_bump";
|
|
14
|
+
|
|
15
|
+
export type AutonomyRiskLevel = "low" | "medium" | "high";
|
|
16
|
+
export type AutonomyGlobBreadth = "narrow" | "medium" | "broad";
|
|
17
|
+
export type AutonomyComponentArea = string;
|
|
18
|
+
|
|
19
|
+
export type AutonomyPenaltyKind =
|
|
20
|
+
| "duplicate_pattern"
|
|
21
|
+
| "cooldown_active"
|
|
22
|
+
| "budget_exceeded"
|
|
23
|
+
| "scope_violation"
|
|
24
|
+
| "policy_violation"
|
|
25
|
+
| "preflight_blocked"
|
|
26
|
+
| "low_confidence";
|
|
27
|
+
|
|
28
|
+
export interface AutonomyPenalty {
|
|
29
|
+
kind: AutonomyPenaltyKind;
|
|
30
|
+
weight: number;
|
|
31
|
+
reason: string;
|
|
32
|
+
evidence_ids: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ScopeValidationResult {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
componentArea: string | null;
|
|
38
|
+
normalizedTargetPaths: string[];
|
|
39
|
+
normalizedWriteGlobs: string[];
|
|
40
|
+
breadth: AutonomyGlobBreadth;
|
|
41
|
+
errors: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const GLOB_META_RE = /[*?\[\]{}()!]/;
|
|
45
|
+
const PATH_META_RE = /[*?\[\]{}()!]/;
|
|
46
|
+
const DRIVE_RE = /^[A-Za-z]:\//;
|
|
47
|
+
const SLASH_RE = /\/+/g;
|
|
48
|
+
|
|
49
|
+
function parentPath(path: string): string {
|
|
50
|
+
const idx = path.lastIndexOf("/");
|
|
51
|
+
if (idx <= 0) return path;
|
|
52
|
+
return path.slice(0, idx);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isProbablyFilePath(path: string): boolean {
|
|
56
|
+
const lastSegment = path.split("/").at(-1) ?? "";
|
|
57
|
+
return lastSegment.includes(".");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scopeSeedPath(path: string): string {
|
|
61
|
+
return isProbablyFilePath(path) ? parentPath(path) : path;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function commonRepoAncestor(paths: string[]): string | null {
|
|
65
|
+
const normalized = paths
|
|
66
|
+
.map((entry) => normalizeRepoRelativePath(entry))
|
|
67
|
+
.filter((entry): entry is string => Boolean(entry));
|
|
68
|
+
if (normalized.length === 0) return null;
|
|
69
|
+
const segments = normalized.map((entry) => entry.split("/"));
|
|
70
|
+
const shared: string[] = [];
|
|
71
|
+
const first = segments[0] ?? [];
|
|
72
|
+
for (let idx = 0; idx < first.length; idx += 1) {
|
|
73
|
+
const segment = first[idx];
|
|
74
|
+
if (!segment) break;
|
|
75
|
+
if (segments.every((parts) => parts[idx] === segment)) {
|
|
76
|
+
shared.push(segment);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (shared.length === 0) return normalized[0] ?? null;
|
|
82
|
+
return shared.join("/");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function normalizeAutonomyComponentArea(value: unknown): string | null {
|
|
86
|
+
const normalized = normalizeRepoRelativePath(value);
|
|
87
|
+
if (!normalized) return null;
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function deriveAutonomyComponentArea(
|
|
92
|
+
targetPathsInput: unknown[],
|
|
93
|
+
writeGlobsInput?: unknown[],
|
|
94
|
+
): string | null {
|
|
95
|
+
const writePrefixes = Array.isArray(writeGlobsInput)
|
|
96
|
+
? writeGlobsInput
|
|
97
|
+
.map((entry) => normalizeWriteGlob(entry))
|
|
98
|
+
.filter((entry): entry is string => Boolean(entry))
|
|
99
|
+
.map((entry) => literalPrefix(entry))
|
|
100
|
+
.map((entry) => scopeSeedPath(entry))
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
: [];
|
|
103
|
+
if (writePrefixes.length > 0) {
|
|
104
|
+
return commonRepoAncestor(writePrefixes);
|
|
105
|
+
}
|
|
106
|
+
const targetSeeds = Array.isArray(targetPathsInput)
|
|
107
|
+
? targetPathsInput
|
|
108
|
+
.map((entry) => normalizeTargetPath(entry))
|
|
109
|
+
.filter((entry): entry is string => Boolean(entry))
|
|
110
|
+
.map((entry) => scopeSeedPath(entry))
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
: [];
|
|
113
|
+
if (targetSeeds.length === 0) return null;
|
|
114
|
+
return commonRepoAncestor(targetSeeds);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function componentRootPrefix(area: AutonomyComponentArea): string {
|
|
118
|
+
const normalized = normalizeAutonomyComponentArea(area);
|
|
119
|
+
if (!normalized) return "";
|
|
120
|
+
return `${normalized}/`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function containsGlobMeta(value: string): boolean {
|
|
124
|
+
return GLOB_META_RE.test(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function normalizeRepoRelativePath(value: unknown): string | null {
|
|
128
|
+
if (typeof value !== "string") return null;
|
|
129
|
+
let path = value.trim();
|
|
130
|
+
if (!path) return null;
|
|
131
|
+
path = path.normalize("NFC").replace(/\\/g, "/");
|
|
132
|
+
if (path.startsWith("/")) return null;
|
|
133
|
+
if (DRIVE_RE.test(path)) return null;
|
|
134
|
+
path = path.replace(SLASH_RE, "/");
|
|
135
|
+
|
|
136
|
+
const out: string[] = [];
|
|
137
|
+
for (const rawSegment of path.split("/")) {
|
|
138
|
+
const segment = rawSegment.trim();
|
|
139
|
+
if (!segment || segment === ".") continue;
|
|
140
|
+
if (segment === "..") return null;
|
|
141
|
+
out.push(segment);
|
|
142
|
+
}
|
|
143
|
+
if (out.length === 0) return null;
|
|
144
|
+
return out.join("/");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function normalizeTargetPath(value: unknown): string | null {
|
|
148
|
+
const normalized = normalizeRepoRelativePath(value);
|
|
149
|
+
if (!normalized) return null;
|
|
150
|
+
if (PATH_META_RE.test(normalized)) return null;
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isSupportedGlobSyntax(glob: string): boolean {
|
|
155
|
+
if (!glob) return false;
|
|
156
|
+
if (glob.includes("\\")) return false;
|
|
157
|
+
if (/[{}\[\]()!]/.test(glob)) return false;
|
|
158
|
+
const segments = glob.split("/");
|
|
159
|
+
for (const segment of segments) {
|
|
160
|
+
if (!segment || segment === ".") return false;
|
|
161
|
+
if (segment === "..") return false;
|
|
162
|
+
const idx = segment.indexOf("**");
|
|
163
|
+
if (idx >= 0 && segment !== "**") return false;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function normalizeWriteGlob(value: unknown): string | null {
|
|
169
|
+
if (typeof value !== "string") return null;
|
|
170
|
+
let glob = value.trim();
|
|
171
|
+
if (!glob) return null;
|
|
172
|
+
glob = glob.normalize("NFC").replace(/\\/g, "/");
|
|
173
|
+
if (glob.startsWith("/")) return null;
|
|
174
|
+
if (DRIVE_RE.test(glob)) return null;
|
|
175
|
+
while (glob.startsWith("./")) glob = glob.slice(2);
|
|
176
|
+
glob = glob.replace(SLASH_RE, "/").replace(/\/+$/, "");
|
|
177
|
+
if (!glob) return null;
|
|
178
|
+
if (!isSupportedGlobSyntax(glob)) return null;
|
|
179
|
+
return glob;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function literalPrefix(glob: string): string {
|
|
183
|
+
const segments = glob.split("/");
|
|
184
|
+
const out: string[] = [];
|
|
185
|
+
for (const segment of segments) {
|
|
186
|
+
if (segment === "**" || segment.includes("*") || segment.includes("?")) break;
|
|
187
|
+
out.push(segment);
|
|
188
|
+
}
|
|
189
|
+
return out.join("/");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function escapeRegex(literal: string): string {
|
|
193
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function matchesSegment(pathSegment: string, globSegment: string): boolean {
|
|
197
|
+
const regexSource = `^${escapeRegex(globSegment).replace(/\\\*/g, ".*").replace(/\\\?/g, ".")}$`;
|
|
198
|
+
return new RegExp(regexSource).test(pathSegment);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function matchesGlob(path: string, glob: string): boolean {
|
|
202
|
+
const pathSegs = path.split("/");
|
|
203
|
+
const globSegs = glob.split("/");
|
|
204
|
+
|
|
205
|
+
const walk = (pi: number, gi: number): boolean => {
|
|
206
|
+
if (gi >= globSegs.length) return pi >= pathSegs.length;
|
|
207
|
+
const g = globSegs[gi];
|
|
208
|
+
if (g === "**") {
|
|
209
|
+
if (gi === globSegs.length - 1) return true;
|
|
210
|
+
for (let k = pi; k <= pathSegs.length; k++) {
|
|
211
|
+
if (walk(k, gi + 1)) return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (pi >= pathSegs.length) return false;
|
|
216
|
+
if (!matchesSegment(pathSegs[pi], g)) return false;
|
|
217
|
+
return walk(pi + 1, gi + 1);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return walk(0, 0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clamp01(value: number): number {
|
|
224
|
+
if (!Number.isFinite(value)) return 0;
|
|
225
|
+
if (value < 0) return 0;
|
|
226
|
+
if (value > 1) return 1;
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function normalizePenalties(values: AutonomyPenalty[]): AutonomyPenalty[] {
|
|
231
|
+
const map = new Map<string, AutonomyPenalty>();
|
|
232
|
+
for (const value of values) {
|
|
233
|
+
const reason = String(value.reason ?? "").trim();
|
|
234
|
+
const kind = value.kind;
|
|
235
|
+
if (!kind || !reason) continue;
|
|
236
|
+
const key = `${kind}\u241f${reason}`;
|
|
237
|
+
if (map.has(key)) continue;
|
|
238
|
+
map.set(key, {
|
|
239
|
+
kind,
|
|
240
|
+
reason,
|
|
241
|
+
weight: clamp01(Number(value.weight)),
|
|
242
|
+
evidence_ids: Array.isArray(value.evidence_ids)
|
|
243
|
+
? value.evidence_ids
|
|
244
|
+
.map((entry) => String(entry ?? "").trim())
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.slice(0, 24)
|
|
247
|
+
: [],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return [...map.values()].sort((a, b) => {
|
|
251
|
+
if (a.kind === b.kind) return a.reason.localeCompare(b.reason);
|
|
252
|
+
return a.kind.localeCompare(b.kind);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function penaltyTotal(values: AutonomyPenalty[]): number {
|
|
257
|
+
return normalizePenalties(values).reduce((sum, value) => sum + clamp01(value.weight), 0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function globBreadthScore(glob: string): number {
|
|
261
|
+
const hasGlobStar = glob.includes("**") ? 1 : 0;
|
|
262
|
+
const wildcardCount = (glob.match(/[\*\?]/g) ?? []).length;
|
|
263
|
+
const rootWide = /^[\*]/.test(glob) || glob.startsWith("**/") ? 1 : 0;
|
|
264
|
+
const literalSegments = glob
|
|
265
|
+
.split("/")
|
|
266
|
+
.filter((segment) => segment.length > 0 && !segment.includes("*") && !segment.includes("?"))
|
|
267
|
+
.length;
|
|
268
|
+
const shallowPenalty = Math.max(0, 2 - Math.min(literalSegments, 2));
|
|
269
|
+
return 4 * hasGlobStar + 2 * rootWide + Math.min(4, wildcardCount) + shallowPenalty;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function classifyGlobBreadth(writeGlobs: string[]): AutonomyGlobBreadth {
|
|
273
|
+
const scores = writeGlobs.map(globBreadthScore);
|
|
274
|
+
const total = scores.reduce((sum, score) => sum + score, 0);
|
|
275
|
+
const max = Math.max(...scores, 0);
|
|
276
|
+
if (max <= 3 && total <= 6 && writeGlobs.length <= 3) return "narrow";
|
|
277
|
+
if (max <= 6 && total <= 12 && writeGlobs.length <= 5) return "medium";
|
|
278
|
+
return "broad";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function underRoot(path: string, rootPrefix: string): boolean {
|
|
282
|
+
if (path.startsWith(rootPrefix)) return true;
|
|
283
|
+
return rootPrefix.endsWith("/") && path === rootPrefix.slice(0, -1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function hasForbiddenBroadGlob(glob: string): boolean {
|
|
287
|
+
if (glob === "." || glob === "**") return true;
|
|
288
|
+
if (glob === "*" || glob === "*/**") return true;
|
|
289
|
+
if (glob === "**/*" || glob === "**/**") return true;
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function validateScopeInvariants(
|
|
294
|
+
componentArea: AutonomyComponentArea | null | undefined,
|
|
295
|
+
targetPathsInput: unknown[],
|
|
296
|
+
writeGlobsInput: unknown[],
|
|
297
|
+
options?: { requireWriteGlobs?: boolean },
|
|
298
|
+
): ScopeValidationResult {
|
|
299
|
+
const errors: string[] = [];
|
|
300
|
+
const normalizedComponentArea =
|
|
301
|
+
normalizeAutonomyComponentArea(componentArea) ??
|
|
302
|
+
deriveAutonomyComponentArea(targetPathsInput, writeGlobsInput);
|
|
303
|
+
const rootPrefix = normalizedComponentArea ? componentRootPrefix(normalizedComponentArea) : "";
|
|
304
|
+
const normalizedTargetPaths: string[] = [];
|
|
305
|
+
const targetSeen = new Set<string>();
|
|
306
|
+
for (const raw of targetPathsInput) {
|
|
307
|
+
const normalized = normalizeTargetPath(raw);
|
|
308
|
+
if (!normalized) {
|
|
309
|
+
errors.push(`invalid target_path: ${String(raw ?? "")}`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (rootPrefix && !underRoot(normalized, rootPrefix)) {
|
|
313
|
+
errors.push(`target_path outside component root: ${normalized}`);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (targetSeen.has(normalized)) continue;
|
|
317
|
+
targetSeen.add(normalized);
|
|
318
|
+
normalizedTargetPaths.push(normalized);
|
|
319
|
+
}
|
|
320
|
+
normalizedTargetPaths.sort();
|
|
321
|
+
if (normalizedTargetPaths.length === 0) {
|
|
322
|
+
errors.push("target_paths must contain at least one literal path");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const normalizedWriteGlobs: string[] = [];
|
|
326
|
+
const writeSeen = new Set<string>();
|
|
327
|
+
for (const raw of writeGlobsInput) {
|
|
328
|
+
const normalized = normalizeWriteGlob(raw);
|
|
329
|
+
if (!normalized) {
|
|
330
|
+
errors.push(`invalid write_glob: ${String(raw ?? "")}`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (hasForbiddenBroadGlob(normalized)) {
|
|
334
|
+
errors.push(`forbidden broad write_glob: ${normalized}`);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const prefix = literalPrefix(normalized);
|
|
338
|
+
if (!prefix) {
|
|
339
|
+
errors.push(`write_glob literal prefix cannot be empty: ${normalized}`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (rootPrefix && !underRoot(prefix, rootPrefix)) {
|
|
343
|
+
errors.push(`write_glob outside component root: ${normalized}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (!normalizedTargetPaths.some((targetPath) => targetPath === prefix || targetPath.startsWith(`${prefix}/`))) {
|
|
347
|
+
errors.push(`write_glob prefix does not align with target_paths: ${normalized}`);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (writeSeen.has(normalized)) continue;
|
|
351
|
+
writeSeen.add(normalized);
|
|
352
|
+
normalizedWriteGlobs.push(normalized);
|
|
353
|
+
}
|
|
354
|
+
normalizedWriteGlobs.sort();
|
|
355
|
+
|
|
356
|
+
if ((options?.requireWriteGlobs ?? true) && normalizedWriteGlobs.length === 0) {
|
|
357
|
+
errors.push("write_globs must be provided and non-empty");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
|
|
361
|
+
for (const targetPath of normalizedTargetPaths) {
|
|
362
|
+
const covered = normalizedWriteGlobs.some((glob) => matchesGlob(targetPath, glob));
|
|
363
|
+
if (!covered) errors.push(`target_path not covered by write_globs: ${targetPath}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!normalizedComponentArea) {
|
|
367
|
+
errors.push("component_area could not be derived from scope");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const breadth = classifyGlobBreadth(normalizedWriteGlobs);
|
|
371
|
+
return {
|
|
372
|
+
ok: errors.length === 0,
|
|
373
|
+
componentArea: normalizedComponentArea,
|
|
374
|
+
normalizedTargetPaths,
|
|
375
|
+
normalizedWriteGlobs,
|
|
376
|
+
breadth,
|
|
377
|
+
errors,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function makePatternKey(
|
|
382
|
+
objectiveType: string,
|
|
383
|
+
targetPaths: string[],
|
|
384
|
+
triggerType: string,
|
|
385
|
+
componentArea: string,
|
|
386
|
+
): string {
|
|
387
|
+
const normalizedTargets = [...targetPaths]
|
|
388
|
+
.map((entry) => normalizeTargetPath(entry))
|
|
389
|
+
.filter((entry): entry is string => Boolean(entry))
|
|
390
|
+
.filter((entry, index, array) => array.indexOf(entry) === index)
|
|
391
|
+
.sort();
|
|
392
|
+
const payload = [
|
|
393
|
+
String(objectiveType ?? "").trim(),
|
|
394
|
+
normalizedTargets.join(","),
|
|
395
|
+
String(triggerType ?? "").trim(),
|
|
396
|
+
String(componentArea ?? "").trim(),
|
|
397
|
+
].join("|");
|
|
398
|
+
const digest = createHash("sha256").update(payload).digest("hex");
|
|
399
|
+
return `pk_${digest}`;
|
|
400
|
+
}
|