pi-harness-runtime 1.1.348 → 1.1.350
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/index.ts +12 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/delegation-assessor.d.ts +27 -0
- package/packages/contracts/dist/delegation-assessor.js +210 -0
- package/packages/contracts/dist/delegation.d.ts +142 -0
- package/packages/contracts/dist/delegation.js +33 -0
- package/packages/contracts/dist/index.d.ts +3 -0
- package/packages/contracts/dist/index.js +3 -0
- package/packages/contracts/dist/scope-discovery.d.ts +65 -0
- package/packages/contracts/dist/scope-discovery.js +247 -0
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/delegation-assessor.ts +268 -0
- package/packages/contracts/src/delegation.ts +174 -0
- package/packages/contracts/src/index.ts +3 -0
- package/packages/contracts/src/scope-discovery.ts +352 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers and estimates the scope of refactoring work.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* File pattern matcher
|
|
8
|
+
*/
|
|
9
|
+
export interface FilePattern {
|
|
10
|
+
/** Glob pattern or exact extension */
|
|
11
|
+
pattern: string;
|
|
12
|
+
/** Whether to recurse into directories */
|
|
13
|
+
recursive?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Scope discovery configuration
|
|
17
|
+
*/
|
|
18
|
+
export interface ScopeDiscoveryConfig {
|
|
19
|
+
/** File extensions to consider relevant */
|
|
20
|
+
relevantExtensions: string[];
|
|
21
|
+
/** Directories to exclude (vendored, generated, etc.) */
|
|
22
|
+
excludeDirs: string[];
|
|
23
|
+
/** Files to exclude by name */
|
|
24
|
+
excludeFiles: string[];
|
|
25
|
+
/** Whether to scan for call sites */
|
|
26
|
+
scanCallSites: boolean;
|
|
27
|
+
/** Call site patterns to count */
|
|
28
|
+
callSitePatterns: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default scope discovery config
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_SCOPE_CONFIG: ScopeDiscoveryConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Discovered scope result
|
|
36
|
+
*/
|
|
37
|
+
export interface DiscoveredScope {
|
|
38
|
+
/** Total relevant files found */
|
|
39
|
+
totalFiles: number;
|
|
40
|
+
/** Files matching the refactoring pattern */
|
|
41
|
+
matchingFiles: number;
|
|
42
|
+
/** Estimated call sites */
|
|
43
|
+
callSites: number;
|
|
44
|
+
/** Independent components found */
|
|
45
|
+
components: string[];
|
|
46
|
+
/** File breakdown by type */
|
|
47
|
+
fileTypes: Record<string, number>;
|
|
48
|
+
/** Whether shared APIs were detected */
|
|
49
|
+
hasSharedApis: boolean;
|
|
50
|
+
/** Whether transformation appears mechanical */
|
|
51
|
+
isMechanical: boolean;
|
|
52
|
+
/** Scope fingerprint for change detection */
|
|
53
|
+
fingerprint: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Discover scope by scanning files
|
|
57
|
+
*/
|
|
58
|
+
export declare function discoverScope(workspacePath: string, searchPattern: string, config?: Partial<ScopeDiscoveryConfig>): DiscoveredScope;
|
|
59
|
+
/**
|
|
60
|
+
* Quick scope estimate using git grep
|
|
61
|
+
*/
|
|
62
|
+
export declare function quickScopeEstimate(workspacePath: string, pattern: string): {
|
|
63
|
+
fileCount: number;
|
|
64
|
+
lineCount: number;
|
|
65
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers and estimates the scope of refactoring work.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, readdirSync } from "fs";
|
|
7
|
+
import { join, extname, relative } from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
/**
|
|
10
|
+
* Default scope discovery config
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_SCOPE_CONFIG = {
|
|
13
|
+
relevantExtensions: [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"],
|
|
14
|
+
excludeDirs: [
|
|
15
|
+
"node_modules",
|
|
16
|
+
".git",
|
|
17
|
+
"dist",
|
|
18
|
+
"build",
|
|
19
|
+
".next",
|
|
20
|
+
"__pycache__",
|
|
21
|
+
"vendor",
|
|
22
|
+
"generated",
|
|
23
|
+
".venv",
|
|
24
|
+
"coverage",
|
|
25
|
+
],
|
|
26
|
+
excludeFiles: [
|
|
27
|
+
"package-lock.json",
|
|
28
|
+
"yarn.lock",
|
|
29
|
+
"pnpm-lock.yaml",
|
|
30
|
+
"bun.lockb",
|
|
31
|
+
"*.min.js",
|
|
32
|
+
"*.bundle.js",
|
|
33
|
+
],
|
|
34
|
+
scanCallSites: true,
|
|
35
|
+
callSitePatterns: [
|
|
36
|
+
"TODO:",
|
|
37
|
+
"FIXME:",
|
|
38
|
+
"console.log",
|
|
39
|
+
"throw new",
|
|
40
|
+
"await this",
|
|
41
|
+
"// @ts-ignore",
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Discover scope by scanning files
|
|
46
|
+
*/
|
|
47
|
+
export function discoverScope(workspacePath, searchPattern, config = {}) {
|
|
48
|
+
const cfg = { ...DEFAULT_SCOPE_CONFIG, ...config };
|
|
49
|
+
const files = [];
|
|
50
|
+
const fileTypes = {};
|
|
51
|
+
const callSites = [];
|
|
52
|
+
const components = new Set();
|
|
53
|
+
// Scan directory recursively
|
|
54
|
+
scanDirectory(workspacePath, files, fileTypes, cfg, 0);
|
|
55
|
+
// Filter to relevant files
|
|
56
|
+
const relevantFiles = files.filter((f) => cfg.relevantExtensions.includes(extname(f)));
|
|
57
|
+
// Count matching files and call sites
|
|
58
|
+
let matchingCount = 0;
|
|
59
|
+
for (const file of relevantFiles) {
|
|
60
|
+
const content = readFileSync(file, "utf8").slice(0, 10000); // First 10KB
|
|
61
|
+
// Check if file matches search pattern
|
|
62
|
+
if (content.toLowerCase().includes(searchPattern.toLowerCase())) {
|
|
63
|
+
matchingCount++;
|
|
64
|
+
// Count call sites
|
|
65
|
+
if (cfg.scanCallSites) {
|
|
66
|
+
for (const pattern of cfg.callSitePatterns) {
|
|
67
|
+
const matches = content.match(new RegExp(pattern, "gi"));
|
|
68
|
+
if (matches) {
|
|
69
|
+
callSites.push(...matches.map((m) => `${file}:${m}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Identify component (directory)
|
|
74
|
+
const relPath = relative(workspacePath, file);
|
|
75
|
+
const parts = relPath.split("/");
|
|
76
|
+
if (parts.length > 1) {
|
|
77
|
+
components.add(parts.slice(0, -1).join("/"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Detect shared APIs (files that are imported by multiple components)
|
|
82
|
+
const hasSharedApis = detectSharedApis(relevantFiles);
|
|
83
|
+
// Detect if transformation is mechanical (simple replacements)
|
|
84
|
+
const isMechanical = detectMechanicalPattern(relevantFiles, searchPattern);
|
|
85
|
+
// Generate fingerprint
|
|
86
|
+
const fingerprint = generateFingerprint(matchingCount, callSites.length, components.size);
|
|
87
|
+
return {
|
|
88
|
+
totalFiles: relevantFiles.length,
|
|
89
|
+
matchingFiles: matchingCount,
|
|
90
|
+
callSites: callSites.slice(0, 100).length,
|
|
91
|
+
components: Array.from(components),
|
|
92
|
+
fileTypes,
|
|
93
|
+
hasSharedApis,
|
|
94
|
+
isMechanical,
|
|
95
|
+
fingerprint,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Recursively scan directory
|
|
100
|
+
*/
|
|
101
|
+
function scanDirectory(dir, files, fileTypes, config, depth) {
|
|
102
|
+
if (depth > 10)
|
|
103
|
+
return; // Max depth guard
|
|
104
|
+
try {
|
|
105
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
const fullPath = join(dir, entry.name);
|
|
108
|
+
// Skip excluded
|
|
109
|
+
if (config.excludeDirs.includes(entry.name))
|
|
110
|
+
continue;
|
|
111
|
+
if (config.excludeFiles.some((f) => entry.name.includes(f)))
|
|
112
|
+
continue;
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
scanDirectory(fullPath, files, fileTypes, config, depth + 1);
|
|
115
|
+
}
|
|
116
|
+
else if (entry.isFile()) {
|
|
117
|
+
files.push(fullPath);
|
|
118
|
+
const ext = extname(entry.name);
|
|
119
|
+
fileTypes[ext] = (fileTypes[ext] || 0) + 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Skip inaccessible directories
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Detect if files have shared API patterns
|
|
129
|
+
*/
|
|
130
|
+
function detectSharedApis(files) {
|
|
131
|
+
// Look for common export patterns
|
|
132
|
+
const exportCounts = new Map();
|
|
133
|
+
for (const file of files.slice(0, 50)) {
|
|
134
|
+
// Sample first 50 files
|
|
135
|
+
try {
|
|
136
|
+
const content = readFileSync(file, "utf8").slice(0, 5000);
|
|
137
|
+
// Count exports
|
|
138
|
+
const exportMatches = content.match(/export\s+(const|function|class|type|interface)/g);
|
|
139
|
+
if (exportMatches) {
|
|
140
|
+
exportCounts.set(file, (exportCounts.get(file) || 0) + exportMatches.length);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Skip unreadable files
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Multiple files with exports suggests shared APIs
|
|
148
|
+
let filesWithExports = 0;
|
|
149
|
+
for (const count of exportCounts.values()) {
|
|
150
|
+
if (count > 0)
|
|
151
|
+
filesWithExports++;
|
|
152
|
+
}
|
|
153
|
+
return filesWithExports > 3;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Detect if transformation is mechanical (simple replacements)
|
|
157
|
+
*/
|
|
158
|
+
function detectMechanicalPattern(files, searchPattern) {
|
|
159
|
+
// Mechanical = simple string replacement, no complex logic
|
|
160
|
+
// Look for consistent patterns
|
|
161
|
+
let mechanicalCount = 0;
|
|
162
|
+
let totalMatches = 0;
|
|
163
|
+
for (const file of files.slice(0, 20)) {
|
|
164
|
+
try {
|
|
165
|
+
const content = readFileSync(file, "utf8");
|
|
166
|
+
const matches = content.match(new RegExp(searchPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"));
|
|
167
|
+
if (matches) {
|
|
168
|
+
totalMatches += matches.length;
|
|
169
|
+
// Check if each match is a simple replacement (same context)
|
|
170
|
+
// Mechanical if: same indentation, same surrounding code pattern
|
|
171
|
+
const uniqueContexts = new Set();
|
|
172
|
+
for (const match of matches.slice(0, 5)) {
|
|
173
|
+
const idx = content.indexOf(match);
|
|
174
|
+
const before = content.slice(Math.max(0, idx - 20), idx);
|
|
175
|
+
const after = content.slice(idx + match.length, idx + match.length + 20);
|
|
176
|
+
uniqueContexts.add(before.trim() + "|" + after.trim());
|
|
177
|
+
}
|
|
178
|
+
// Single context = mechanical
|
|
179
|
+
if (uniqueContexts.size === 1) {
|
|
180
|
+
mechanicalCount++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Skip unreadable files
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// More than half mechanical = likely mechanical
|
|
189
|
+
return totalMatches > 0 && mechanicalCount / totalMatches > 0.5;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Generate scope fingerprint
|
|
193
|
+
*/
|
|
194
|
+
function generateFingerprint(fileCount, callSiteCount, componentCount) {
|
|
195
|
+
// Simple fingerprint based on counts
|
|
196
|
+
const data = `${fileCount}:${callSiteCount}:${componentCount}`;
|
|
197
|
+
let hash = 0;
|
|
198
|
+
for (let i = 0; i < data.length; i++) {
|
|
199
|
+
const char = data.charCodeAt(i);
|
|
200
|
+
hash = (hash << 5) - hash + char;
|
|
201
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
202
|
+
}
|
|
203
|
+
return Math.abs(hash).toString(36);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Quick scope estimate using git grep
|
|
207
|
+
*/
|
|
208
|
+
export function quickScopeEstimate(workspacePath, pattern) {
|
|
209
|
+
// Validate inputs to prevent command injection
|
|
210
|
+
const safePath = validatePath(workspacePath);
|
|
211
|
+
if (!safePath)
|
|
212
|
+
return { fileCount: 0, lineCount: 0 };
|
|
213
|
+
const safePattern = escapeForGrep(pattern);
|
|
214
|
+
if (!safePattern)
|
|
215
|
+
return { fileCount: 0, lineCount: 0 };
|
|
216
|
+
try {
|
|
217
|
+
const result = execSync(`grep -r "${safePattern}" "${safePath}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -l 2>/dev/null | wc -l`, { encoding: "utf8", timeout: 10000 });
|
|
218
|
+
const fileCount = parseInt(result.trim(), 10) || 0;
|
|
219
|
+
const lineResult = execSync(`grep -r "${safePattern}" "${safePath}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | wc -l`, { encoding: "utf8", timeout: 10000 });
|
|
220
|
+
const lineCount = parseInt(lineResult.trim(), 10) || 0;
|
|
221
|
+
return { fileCount, lineCount };
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return { fileCount: 0, lineCount: 0 };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Validate and sanitize a file path
|
|
229
|
+
*/
|
|
230
|
+
function validatePath(path) {
|
|
231
|
+
// Only allow alphanumeric, hyphen, underscore, dot, and forward slash
|
|
232
|
+
if (!/^[\w./-]+$/.test(path)) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
// Prevent path traversal
|
|
236
|
+
if (path.includes("..")) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return path;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Escape a string for safe use in grep pattern
|
|
243
|
+
*/
|
|
244
|
+
function escapeForGrep(pattern) {
|
|
245
|
+
// Remove any shell metacharacters
|
|
246
|
+
return pattern.replace(/[|;&$`\\"'<>\(\)\[\]\{\}]/g, "");
|
|
247
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Assessor
|
|
3
|
+
*
|
|
4
|
+
* Assesses whether work should be delegated to sub-agents based on scope.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import type {
|
|
9
|
+
ScopeFacts,
|
|
10
|
+
DelegationDecision,
|
|
11
|
+
DelegationStrategy,
|
|
12
|
+
WorkGroup,
|
|
13
|
+
DelegationTriggerConfig,
|
|
14
|
+
ExtensionCapability,
|
|
15
|
+
DEFAULT_DELEGATION_TRIGGERS,
|
|
16
|
+
} from "./delegation.js";
|
|
17
|
+
import { shouldTriggerAssessment } from "./delegation.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sub-agent extension names to check for
|
|
21
|
+
*/
|
|
22
|
+
const SUBAGENT_EXTENSIONS = [
|
|
23
|
+
"pi-subagents",
|
|
24
|
+
"@earendil-works/pi-subagents",
|
|
25
|
+
"subagent",
|
|
26
|
+
"spawn",
|
|
27
|
+
"delegate",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tool names that indicate sub-agent capability
|
|
32
|
+
*/
|
|
33
|
+
const SUBAGENT_TOOLS = [
|
|
34
|
+
"subagent",
|
|
35
|
+
"spawn",
|
|
36
|
+
"delegate",
|
|
37
|
+
"run_subagent",
|
|
38
|
+
"run_agent",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Discover extension capabilities for delegation
|
|
43
|
+
*/
|
|
44
|
+
export async function discoverDelegationCapability(
|
|
45
|
+
pi: ExtensionAPI
|
|
46
|
+
): Promise<ExtensionCapability | null> {
|
|
47
|
+
// Get all available tools
|
|
48
|
+
const allTools = pi.getAllTools();
|
|
49
|
+
const toolNames = allTools.map((t) => t.name);
|
|
50
|
+
const activeTools = pi.getActiveTools();
|
|
51
|
+
|
|
52
|
+
// Check for sub-agent tools
|
|
53
|
+
const hasSubagentTool = SUBAGENT_TOOLS.some(
|
|
54
|
+
(tool) => toolNames.includes(tool) || activeTools.includes(tool)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Check for subagent extension registration
|
|
58
|
+
// The extension might register tools under different names
|
|
59
|
+
const possibleCapabilities: ExtensionCapability[] = [];
|
|
60
|
+
|
|
61
|
+
// Verify by attempting a capability check
|
|
62
|
+
// If subagent tools exist, assume capability
|
|
63
|
+
if (hasSubagentTool) {
|
|
64
|
+
return {
|
|
65
|
+
name: "subagent-extension",
|
|
66
|
+
supportsSubagents: true,
|
|
67
|
+
supportsWorkIsolation: true, // Assume based on tool presence
|
|
68
|
+
supportsCancellation: true,
|
|
69
|
+
availableTools: activeTools.filter((t) =>
|
|
70
|
+
SUBAGENT_TOOLS.some((st) => t.includes(st))
|
|
71
|
+
),
|
|
72
|
+
verified: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check if pi-subagents skill is mentioned in available tools
|
|
77
|
+
const subagentMentioned = toolNames.some(
|
|
78
|
+
(t) => t.includes("subagent") || t.includes("spawn")
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (subagentMentioned) {
|
|
82
|
+
return {
|
|
83
|
+
name: "pi-subagents",
|
|
84
|
+
supportsSubagents: true,
|
|
85
|
+
supportsWorkIsolation: true,
|
|
86
|
+
supportsCancellation: true,
|
|
87
|
+
availableTools: toolNames.filter((t) => t.includes("subagent")),
|
|
88
|
+
verified: false, // Not verified - just mentioned
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Estimate scope from file analysis
|
|
97
|
+
*/
|
|
98
|
+
export async function estimateScope(
|
|
99
|
+
_pi: ExtensionAPI,
|
|
100
|
+
patterns: string[]
|
|
101
|
+
): Promise<ScopeFacts> {
|
|
102
|
+
// TODO: Implement actual file discovery
|
|
103
|
+
// For now, return placeholder
|
|
104
|
+
return {
|
|
105
|
+
estimatedFiles: patterns.length,
|
|
106
|
+
observedFiles: 0,
|
|
107
|
+
estimatedCallSites: 0,
|
|
108
|
+
observedCallSites: 0,
|
|
109
|
+
independentComponents: 1,
|
|
110
|
+
tightlyCoupled: false,
|
|
111
|
+
isMechanical: true,
|
|
112
|
+
isComplex: false,
|
|
113
|
+
fileTypes: {},
|
|
114
|
+
fingerprint: "",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Make deterministic assessment without Jev
|
|
120
|
+
*/
|
|
121
|
+
export function makeDeterministicAssessment(
|
|
122
|
+
facts: ScopeFacts,
|
|
123
|
+
capability: ExtensionCapability | null
|
|
124
|
+
): DelegationDecision {
|
|
125
|
+
// No capability - must use single agent
|
|
126
|
+
if (!capability?.verified) {
|
|
127
|
+
return {
|
|
128
|
+
strategy: "unavailable",
|
|
129
|
+
reason: "No verified sub-agent capability available",
|
|
130
|
+
workGroups: [],
|
|
131
|
+
validationExpectations: "Parent handles all validation",
|
|
132
|
+
confidence: "high",
|
|
133
|
+
jevUsed: false,
|
|
134
|
+
reassessmentTriggers: ["Extension becomes available"],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Small scope - single agent
|
|
139
|
+
if (facts.observedFiles < 5 && facts.observedCallSites < 20) {
|
|
140
|
+
return {
|
|
141
|
+
strategy: "single_agent",
|
|
142
|
+
reason: "Small scope - delegation overhead not justified",
|
|
143
|
+
workGroups: [],
|
|
144
|
+
validationExpectations: "Standard parent validation",
|
|
145
|
+
confidence: "high",
|
|
146
|
+
jevUsed: false,
|
|
147
|
+
reassessmentTriggers: [],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Mechanical transformation with reliable script
|
|
152
|
+
if (facts.isMechanical && !facts.isComplex) {
|
|
153
|
+
return {
|
|
154
|
+
strategy: "script_only",
|
|
155
|
+
reason: "Mechanical transformation - script approach is reliable",
|
|
156
|
+
workGroups: [],
|
|
157
|
+
validationExpectations: "Script output validation + test run",
|
|
158
|
+
confidence: "high",
|
|
159
|
+
jevUsed: false,
|
|
160
|
+
reassessmentTriggers: ["Transformation proves unreliable"],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Multiple independent components
|
|
165
|
+
if (facts.independentComponents >= 2 && !facts.tightlyCoupled) {
|
|
166
|
+
const groups = partitionIntoGroups(facts);
|
|
167
|
+
return {
|
|
168
|
+
strategy: "delegate",
|
|
169
|
+
reason: `${facts.independentComponents} independent components - delegation recommended`,
|
|
170
|
+
workGroups: groups,
|
|
171
|
+
validationExpectations: "Each group validated independently, then integrated",
|
|
172
|
+
confidence: "medium",
|
|
173
|
+
jevUsed: false,
|
|
174
|
+
reassessmentTriggers: ["Scope expands significantly", "Component coupling discovered"],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Tightly coupled - sequential or single agent
|
|
179
|
+
if (facts.tightlyCoupled) {
|
|
180
|
+
return {
|
|
181
|
+
strategy: "script_then_delegate",
|
|
182
|
+
reason: "Coupled components - script shared parts, delegate isolated changes",
|
|
183
|
+
workGroups: [],
|
|
184
|
+
validationExpectations: "Script validates shared API, delegate validates components",
|
|
185
|
+
confidence: "medium",
|
|
186
|
+
jevUsed: false,
|
|
187
|
+
reassessmentTriggers: ["Coupling reduced after refactoring"],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Default - single agent with monitoring
|
|
192
|
+
return {
|
|
193
|
+
strategy: "single_agent",
|
|
194
|
+
reason: "Default to single agent for bounded scope",
|
|
195
|
+
workGroups: [],
|
|
196
|
+
validationExpectations: "Parent validates all changes",
|
|
197
|
+
confidence: "low",
|
|
198
|
+
jevUsed: false,
|
|
199
|
+
reassessmentTriggers: ["Scope exceeds threshold"],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Partition work into groups
|
|
205
|
+
*/
|
|
206
|
+
function partitionIntoGroups(facts: ScopeFacts): WorkGroup[] {
|
|
207
|
+
const groups: WorkGroup[] = [];
|
|
208
|
+
const filesPerGroup = Math.ceil(facts.observedFiles / facts.independentComponents);
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < facts.independentComponents; i++) {
|
|
211
|
+
groups.push({
|
|
212
|
+
groupId: `group-${i + 1}`,
|
|
213
|
+
description: `Component ${i + 1} of ${facts.independentComponents}`,
|
|
214
|
+
ownedPaths: [], // TODO: Populate with actual files
|
|
215
|
+
isMechanical: facts.isMechanical,
|
|
216
|
+
status: "pending",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return groups;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Assess delegation need
|
|
225
|
+
*/
|
|
226
|
+
export async function assessDelegation(
|
|
227
|
+
pi: ExtensionAPI,
|
|
228
|
+
facts: ScopeFacts,
|
|
229
|
+
config: DelegationTriggerConfig = { minFiles: 10, minCallSites: 50, minIndependentComponents: 2, assessOnDiscovery: true }
|
|
230
|
+
): Promise<DelegationDecision | null> {
|
|
231
|
+
// Check if assessment should trigger
|
|
232
|
+
if (!shouldTriggerAssessment(facts, config)) {
|
|
233
|
+
return null; // No assessment needed
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Discover capability
|
|
237
|
+
const capability = await discoverDelegationCapability(pi);
|
|
238
|
+
|
|
239
|
+
// Make deterministic assessment
|
|
240
|
+
return makeDeterministicAssessment(facts, capability);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Validate assessment is still valid
|
|
245
|
+
*/
|
|
246
|
+
export function isAssessmentStale(
|
|
247
|
+
decision: DelegationDecision,
|
|
248
|
+
currentFacts: ScopeFacts,
|
|
249
|
+
originalFacts: ScopeFacts
|
|
250
|
+
): boolean {
|
|
251
|
+
// Scope significantly expanded
|
|
252
|
+
if (currentFacts.observedFiles > originalFacts.observedFiles * 1.5) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// New coupling discovered
|
|
257
|
+
if (currentFacts.tightlyCoupled && !originalFacts.tightlyCoupled) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Reassessment triggers
|
|
262
|
+
return decision.reassessmentTriggers.some((trigger) => {
|
|
263
|
+
if (trigger.includes("scope expands")) {
|
|
264
|
+
return currentFacts.observedFiles > originalFacts.observedFiles * 1.5;
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
});
|
|
268
|
+
}
|