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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegation Assessment Types
|
|
3
|
+
*
|
|
4
|
+
* Types for consistent delegation assessment for large refactors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Scope estimation for a refactoring task
|
|
9
|
+
*/
|
|
10
|
+
export interface ScopeFacts {
|
|
11
|
+
/** Estimated number of relevant files affected */
|
|
12
|
+
estimatedFiles: number;
|
|
13
|
+
/** Actual observed number of files (after scanning) */
|
|
14
|
+
observedFiles: number;
|
|
15
|
+
/** Estimated number of call sites/changes */
|
|
16
|
+
estimatedCallSites: number;
|
|
17
|
+
/** Actual observed call sites */
|
|
18
|
+
observedCallSites: number;
|
|
19
|
+
/** Number of independent components/groups */
|
|
20
|
+
independentComponents: number;
|
|
21
|
+
/** Whether changes are tightly coupled (shared APIs) */
|
|
22
|
+
tightlyCoupled: boolean;
|
|
23
|
+
/** Whether transformation is mechanical (safe for scripts) */
|
|
24
|
+
isMechanical: boolean;
|
|
25
|
+
/** Whether transformation is complex (needs reasoning) */
|
|
26
|
+
isComplex: boolean;
|
|
27
|
+
/** File type breakdown */
|
|
28
|
+
fileTypes: Record<string, number>;
|
|
29
|
+
/** Scope fingerprint for change detection */
|
|
30
|
+
fingerprint: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Delegation strategy decision
|
|
35
|
+
*/
|
|
36
|
+
export type DelegationStrategy =
|
|
37
|
+
| "delegate" // Delegate independent work groups
|
|
38
|
+
| "script_then_delegate" // Script simple parts, delegate complex
|
|
39
|
+
| "script_only" // Script approach is best (mechanical + reliable)
|
|
40
|
+
| "single_agent" // Single agent handles all (small scope)
|
|
41
|
+
| "unavailable"; // Extension not available, use fallback
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Work group for delegation
|
|
45
|
+
*/
|
|
46
|
+
export interface WorkGroup {
|
|
47
|
+
/** Unique group ID */
|
|
48
|
+
groupId: string;
|
|
49
|
+
/** Human-readable description */
|
|
50
|
+
description: string;
|
|
51
|
+
/** Files owned by this group */
|
|
52
|
+
ownedPaths: string[];
|
|
53
|
+
/** Whether this group is mechanical (script-friendly) */
|
|
54
|
+
isMechanical: boolean;
|
|
55
|
+
/** Assigned child agent ID (if delegated) */
|
|
56
|
+
childId?: string;
|
|
57
|
+
/** Status of this group */
|
|
58
|
+
status: "pending" | "dispatched" | "running" | "completed" | "failed" | "cancelled";
|
|
59
|
+
/** Validation evidence */
|
|
60
|
+
validation?: {
|
|
61
|
+
passed: boolean;
|
|
62
|
+
details: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Delegation decision from Jev or fallback
|
|
68
|
+
*/
|
|
69
|
+
export interface DelegationDecision {
|
|
70
|
+
/** Strategy selected */
|
|
71
|
+
strategy: DelegationStrategy;
|
|
72
|
+
/** Reason for the decision */
|
|
73
|
+
reason: string;
|
|
74
|
+
/** Proposed work groups (for delegate strategies) */
|
|
75
|
+
workGroups: WorkGroup[];
|
|
76
|
+
/** Validation expectations */
|
|
77
|
+
validationExpectations: string;
|
|
78
|
+
/** Confidence level */
|
|
79
|
+
confidence: "high" | "medium" | "low";
|
|
80
|
+
/** Whether Jev was used */
|
|
81
|
+
jevUsed: boolean;
|
|
82
|
+
/** Reassessment triggers */
|
|
83
|
+
reassessmentTriggers: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Delegation receipt for persistence
|
|
88
|
+
*/
|
|
89
|
+
export interface DelegationReceipt {
|
|
90
|
+
/** Version for schema compatibility */
|
|
91
|
+
version: number;
|
|
92
|
+
/** Workspace canonical path */
|
|
93
|
+
workspacePath: string;
|
|
94
|
+
/** Session ID */
|
|
95
|
+
sessionId: string;
|
|
96
|
+
/** Parent task ID */
|
|
97
|
+
parentTaskId: string;
|
|
98
|
+
/** Scope fingerprint */
|
|
99
|
+
scopeFingerprint: string;
|
|
100
|
+
/** Decision made */
|
|
101
|
+
decision: DelegationDecision;
|
|
102
|
+
/** Created timestamp */
|
|
103
|
+
createdAt: number;
|
|
104
|
+
/** Last updated timestamp */
|
|
105
|
+
updatedAt: number;
|
|
106
|
+
/** Status */
|
|
107
|
+
status: "assessing" | "in_progress" | "completed" | "failed" | "cancelled";
|
|
108
|
+
/** Active child IDs */
|
|
109
|
+
childIds: string[];
|
|
110
|
+
/** Outstanding validation items */
|
|
111
|
+
pendingValidation: string[];
|
|
112
|
+
/** User constraints (e.g., no delegation) */
|
|
113
|
+
userConstraints: string[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Delegation assessment trigger config
|
|
118
|
+
*/
|
|
119
|
+
export interface DelegationTriggerConfig {
|
|
120
|
+
/** Minimum files to trigger assessment */
|
|
121
|
+
minFiles: number;
|
|
122
|
+
/** Minimum call sites to trigger assessment */
|
|
123
|
+
minCallSites: number;
|
|
124
|
+
/** Minimum independent components to suggest delegation */
|
|
125
|
+
minIndependentComponents: number;
|
|
126
|
+
/** Whether to assess on scope discovery vs mutation */
|
|
127
|
+
assessOnDiscovery: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Default trigger config
|
|
132
|
+
*/
|
|
133
|
+
export const DEFAULT_DELEGATION_TRIGGERS: DelegationTriggerConfig = {
|
|
134
|
+
minFiles: 10,
|
|
135
|
+
minCallSites: 50,
|
|
136
|
+
minIndependentComponents: 2,
|
|
137
|
+
assessOnDiscovery: true,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extension capability for delegation
|
|
142
|
+
*/
|
|
143
|
+
export interface ExtensionCapability {
|
|
144
|
+
/** Extension name */
|
|
145
|
+
name: string;
|
|
146
|
+
/** Whether it supports sub-agents */
|
|
147
|
+
supportsSubagents: boolean;
|
|
148
|
+
/** Whether it supports work isolation */
|
|
149
|
+
supportsWorkIsolation: boolean;
|
|
150
|
+
/** Whether it supports cancellation */
|
|
151
|
+
supportsCancellation: boolean;
|
|
152
|
+
/** Supported tool names */
|
|
153
|
+
availableTools: string[];
|
|
154
|
+
/** Verified working */
|
|
155
|
+
verified: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if delegation should be triggered
|
|
160
|
+
*/
|
|
161
|
+
export function shouldTriggerAssessment(facts: ScopeFacts, config: DelegationTriggerConfig = DEFAULT_DELEGATION_TRIGGERS): boolean {
|
|
162
|
+
// Check file count
|
|
163
|
+
if (facts.observedFiles >= config.minFiles) return true;
|
|
164
|
+
if (facts.estimatedFiles >= config.minFiles) return true;
|
|
165
|
+
|
|
166
|
+
// Check call sites
|
|
167
|
+
if (facts.observedCallSites >= config.minCallSites) return true;
|
|
168
|
+
if (facts.estimatedCallSites >= config.minCallSites) return true;
|
|
169
|
+
|
|
170
|
+
// Check independent components
|
|
171
|
+
if (facts.independentComponents >= config.minIndependentComponents) return true;
|
|
172
|
+
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers and estimates the scope of refactoring work.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, readdirSync } from "fs";
|
|
8
|
+
import { join, extname, relative } from "path";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* File pattern matcher
|
|
13
|
+
*/
|
|
14
|
+
export interface FilePattern {
|
|
15
|
+
/** Glob pattern or exact extension */
|
|
16
|
+
pattern: string;
|
|
17
|
+
/** Whether to recurse into directories */
|
|
18
|
+
recursive?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Scope discovery configuration
|
|
23
|
+
*/
|
|
24
|
+
export interface ScopeDiscoveryConfig {
|
|
25
|
+
/** File extensions to consider relevant */
|
|
26
|
+
relevantExtensions: string[];
|
|
27
|
+
/** Directories to exclude (vendored, generated, etc.) */
|
|
28
|
+
excludeDirs: string[];
|
|
29
|
+
/** Files to exclude by name */
|
|
30
|
+
excludeFiles: string[];
|
|
31
|
+
/** Whether to scan for call sites */
|
|
32
|
+
scanCallSites: boolean;
|
|
33
|
+
/** Call site patterns to count */
|
|
34
|
+
callSitePatterns: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default scope discovery config
|
|
39
|
+
*/
|
|
40
|
+
export const DEFAULT_SCOPE_CONFIG: ScopeDiscoveryConfig = {
|
|
41
|
+
relevantExtensions: [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"],
|
|
42
|
+
excludeDirs: [
|
|
43
|
+
"node_modules",
|
|
44
|
+
".git",
|
|
45
|
+
"dist",
|
|
46
|
+
"build",
|
|
47
|
+
".next",
|
|
48
|
+
"__pycache__",
|
|
49
|
+
"vendor",
|
|
50
|
+
"generated",
|
|
51
|
+
".venv",
|
|
52
|
+
"coverage",
|
|
53
|
+
],
|
|
54
|
+
excludeFiles: [
|
|
55
|
+
"package-lock.json",
|
|
56
|
+
"yarn.lock",
|
|
57
|
+
"pnpm-lock.yaml",
|
|
58
|
+
"bun.lockb",
|
|
59
|
+
"*.min.js",
|
|
60
|
+
"*.bundle.js",
|
|
61
|
+
],
|
|
62
|
+
scanCallSites: true,
|
|
63
|
+
callSitePatterns: [
|
|
64
|
+
"TODO:",
|
|
65
|
+
"FIXME:",
|
|
66
|
+
"console.log",
|
|
67
|
+
"throw new",
|
|
68
|
+
"await this",
|
|
69
|
+
"// @ts-ignore",
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Discovered scope result
|
|
75
|
+
*/
|
|
76
|
+
export interface DiscoveredScope {
|
|
77
|
+
/** Total relevant files found */
|
|
78
|
+
totalFiles: number;
|
|
79
|
+
/** Files matching the refactoring pattern */
|
|
80
|
+
matchingFiles: number;
|
|
81
|
+
/** Estimated call sites */
|
|
82
|
+
callSites: number;
|
|
83
|
+
/** Independent components found */
|
|
84
|
+
components: string[];
|
|
85
|
+
/** File breakdown by type */
|
|
86
|
+
fileTypes: Record<string, number>;
|
|
87
|
+
/** Whether shared APIs were detected */
|
|
88
|
+
hasSharedApis: boolean;
|
|
89
|
+
/** Whether transformation appears mechanical */
|
|
90
|
+
isMechanical: boolean;
|
|
91
|
+
/** Scope fingerprint for change detection */
|
|
92
|
+
fingerprint: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Discover scope by scanning files
|
|
97
|
+
*/
|
|
98
|
+
export function discoverScope(
|
|
99
|
+
workspacePath: string,
|
|
100
|
+
searchPattern: string,
|
|
101
|
+
config: Partial<ScopeDiscoveryConfig> = {}
|
|
102
|
+
): DiscoveredScope {
|
|
103
|
+
const cfg = { ...DEFAULT_SCOPE_CONFIG, ...config };
|
|
104
|
+
const files: string[] = [];
|
|
105
|
+
const fileTypes: Record<string, number> = {};
|
|
106
|
+
const callSites: string[] = [];
|
|
107
|
+
const components: Set<string> = new Set();
|
|
108
|
+
|
|
109
|
+
// Scan directory recursively
|
|
110
|
+
scanDirectory(workspacePath, files, fileTypes, cfg, 0);
|
|
111
|
+
|
|
112
|
+
// Filter to relevant files
|
|
113
|
+
const relevantFiles = files.filter((f) =>
|
|
114
|
+
cfg.relevantExtensions.includes(extname(f))
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Count matching files and call sites
|
|
118
|
+
let matchingCount = 0;
|
|
119
|
+
for (const file of relevantFiles) {
|
|
120
|
+
const content = readFileSync(file, "utf8").slice(0, 10000); // First 10KB
|
|
121
|
+
|
|
122
|
+
// Check if file matches search pattern
|
|
123
|
+
if (content.toLowerCase().includes(searchPattern.toLowerCase())) {
|
|
124
|
+
matchingCount++;
|
|
125
|
+
|
|
126
|
+
// Count call sites
|
|
127
|
+
if (cfg.scanCallSites) {
|
|
128
|
+
for (const pattern of cfg.callSitePatterns) {
|
|
129
|
+
const matches = content.match(new RegExp(pattern, "gi"));
|
|
130
|
+
if (matches) {
|
|
131
|
+
callSites.push(...matches.map((m) => `${file}:${m}`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Identify component (directory)
|
|
137
|
+
const relPath = relative(workspacePath, file);
|
|
138
|
+
const parts = relPath.split("/");
|
|
139
|
+
if (parts.length > 1) {
|
|
140
|
+
components.add(parts.slice(0, -1).join("/"));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Detect shared APIs (files that are imported by multiple components)
|
|
146
|
+
const hasSharedApis = detectSharedApis(relevantFiles);
|
|
147
|
+
|
|
148
|
+
// Detect if transformation is mechanical (simple replacements)
|
|
149
|
+
const isMechanical = detectMechanicalPattern(relevantFiles, searchPattern);
|
|
150
|
+
|
|
151
|
+
// Generate fingerprint
|
|
152
|
+
const fingerprint = generateFingerprint(matchingCount, callSites.length, components.size);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
totalFiles: relevantFiles.length,
|
|
156
|
+
matchingFiles: matchingCount,
|
|
157
|
+
callSites: callSites.slice(0, 100).length,
|
|
158
|
+
components: Array.from(components),
|
|
159
|
+
fileTypes,
|
|
160
|
+
hasSharedApis,
|
|
161
|
+
isMechanical,
|
|
162
|
+
fingerprint,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Recursively scan directory
|
|
168
|
+
*/
|
|
169
|
+
function scanDirectory(
|
|
170
|
+
dir: string,
|
|
171
|
+
files: string[],
|
|
172
|
+
fileTypes: Record<string, number>,
|
|
173
|
+
config: ScopeDiscoveryConfig,
|
|
174
|
+
depth: number
|
|
175
|
+
): void {
|
|
176
|
+
if (depth > 10) return; // Max depth guard
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
180
|
+
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const fullPath = join(dir, entry.name);
|
|
183
|
+
|
|
184
|
+
// Skip excluded
|
|
185
|
+
if (config.excludeDirs.includes(entry.name)) continue;
|
|
186
|
+
if (config.excludeFiles.some((f) => entry.name.includes(f))) continue;
|
|
187
|
+
|
|
188
|
+
if (entry.isDirectory()) {
|
|
189
|
+
scanDirectory(fullPath, files, fileTypes, config, depth + 1);
|
|
190
|
+
} else if (entry.isFile()) {
|
|
191
|
+
files.push(fullPath);
|
|
192
|
+
const ext = extname(entry.name);
|
|
193
|
+
fileTypes[ext] = (fileTypes[ext] || 0) + 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Skip inaccessible directories
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Detect if files have shared API patterns
|
|
203
|
+
*/
|
|
204
|
+
function detectSharedApis(files: string[]): boolean {
|
|
205
|
+
// Look for common export patterns
|
|
206
|
+
const exportCounts = new Map<string, number>();
|
|
207
|
+
|
|
208
|
+
for (const file of files.slice(0, 50)) {
|
|
209
|
+
// Sample first 50 files
|
|
210
|
+
try {
|
|
211
|
+
const content = readFileSync(file, "utf8").slice(0, 5000);
|
|
212
|
+
|
|
213
|
+
// Count exports
|
|
214
|
+
const exportMatches = content.match(/export\s+(const|function|class|type|interface)/g);
|
|
215
|
+
if (exportMatches) {
|
|
216
|
+
exportCounts.set(file, (exportCounts.get(file) || 0) + exportMatches.length);
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// Skip unreadable files
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Multiple files with exports suggests shared APIs
|
|
224
|
+
let filesWithExports = 0;
|
|
225
|
+
for (const count of exportCounts.values()) {
|
|
226
|
+
if (count > 0) filesWithExports++;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return filesWithExports > 3;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Detect if transformation is mechanical (simple replacements)
|
|
234
|
+
*/
|
|
235
|
+
function detectMechanicalPattern(files: string[], searchPattern: string): boolean {
|
|
236
|
+
// Mechanical = simple string replacement, no complex logic
|
|
237
|
+
// Look for consistent patterns
|
|
238
|
+
|
|
239
|
+
let mechanicalCount = 0;
|
|
240
|
+
let totalMatches = 0;
|
|
241
|
+
|
|
242
|
+
for (const file of files.slice(0, 20)) {
|
|
243
|
+
try {
|
|
244
|
+
const content = readFileSync(file, "utf8");
|
|
245
|
+
const matches = content.match(
|
|
246
|
+
new RegExp(searchPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi")
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (matches) {
|
|
250
|
+
totalMatches += matches.length;
|
|
251
|
+
|
|
252
|
+
// Check if each match is a simple replacement (same context)
|
|
253
|
+
// Mechanical if: same indentation, same surrounding code pattern
|
|
254
|
+
const uniqueContexts = new Set<string>();
|
|
255
|
+
|
|
256
|
+
for (const match of matches.slice(0, 5)) {
|
|
257
|
+
const idx = content.indexOf(match);
|
|
258
|
+
const before = content.slice(Math.max(0, idx - 20), idx);
|
|
259
|
+
const after = content.slice(idx + match.length, idx + match.length + 20);
|
|
260
|
+
uniqueContexts.add(before.trim() + "|" + after.trim());
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Single context = mechanical
|
|
264
|
+
if (uniqueContexts.size === 1) {
|
|
265
|
+
mechanicalCount++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// Skip unreadable files
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// More than half mechanical = likely mechanical
|
|
274
|
+
return totalMatches > 0 && mechanicalCount / totalMatches > 0.5;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generate scope fingerprint
|
|
279
|
+
*/
|
|
280
|
+
function generateFingerprint(
|
|
281
|
+
fileCount: number,
|
|
282
|
+
callSiteCount: number,
|
|
283
|
+
componentCount: number
|
|
284
|
+
): string {
|
|
285
|
+
// Simple fingerprint based on counts
|
|
286
|
+
const data = `${fileCount}:${callSiteCount}:${componentCount}`;
|
|
287
|
+
let hash = 0;
|
|
288
|
+
for (let i = 0; i < data.length; i++) {
|
|
289
|
+
const char = data.charCodeAt(i);
|
|
290
|
+
hash = (hash << 5) - hash + char;
|
|
291
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
292
|
+
}
|
|
293
|
+
return Math.abs(hash).toString(36);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Quick scope estimate using git grep
|
|
298
|
+
*/
|
|
299
|
+
export function quickScopeEstimate(
|
|
300
|
+
workspacePath: string,
|
|
301
|
+
pattern: string
|
|
302
|
+
): { fileCount: number; lineCount: number } {
|
|
303
|
+
// Validate inputs to prevent command injection
|
|
304
|
+
const safePath = validatePath(workspacePath);
|
|
305
|
+
if (!safePath) return { fileCount: 0, lineCount: 0 };
|
|
306
|
+
|
|
307
|
+
const safePattern = escapeForGrep(pattern);
|
|
308
|
+
if (!safePattern) return { fileCount: 0, lineCount: 0 };
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const result = execSync(
|
|
312
|
+
`grep -r "${safePattern}" "${safePath}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -l 2>/dev/null | wc -l`,
|
|
313
|
+
{ encoding: "utf8", timeout: 10000 }
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const fileCount = parseInt(result.trim(), 10) || 0;
|
|
317
|
+
|
|
318
|
+
const lineResult = execSync(
|
|
319
|
+
`grep -r "${safePattern}" "${safePath}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 2>/dev/null | wc -l`,
|
|
320
|
+
{ encoding: "utf8", timeout: 10000 }
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const lineCount = parseInt(lineResult.trim(), 10) || 0;
|
|
324
|
+
|
|
325
|
+
return { fileCount, lineCount };
|
|
326
|
+
} catch {
|
|
327
|
+
return { fileCount: 0, lineCount: 0 };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Validate and sanitize a file path
|
|
333
|
+
*/
|
|
334
|
+
function validatePath(path: string): string | null {
|
|
335
|
+
// Only allow alphanumeric, hyphen, underscore, dot, and forward slash
|
|
336
|
+
if (!/^[\w./-]+$/.test(path)) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
// Prevent path traversal
|
|
340
|
+
if (path.includes("..")) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return path;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Escape a string for safe use in grep pattern
|
|
348
|
+
*/
|
|
349
|
+
function escapeForGrep(pattern: string): string {
|
|
350
|
+
// Remove any shell metacharacters
|
|
351
|
+
return pattern.replace(/[|;&$`\\"'<>\(\)\[\]\{\}]/g, "");
|
|
352
|
+
}
|