@evo-dev/core 0.0.1-alpha → 0.0.1-alpha.10
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/assets/agents/review/code-reviewer/examples.md +1 -1
- package/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/assets/agents/review/code-reviewer/verification.md +1 -1
- package/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
- package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/config/index.js +1115 -81
- package/dist/index.js +13796 -2196
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +63 -292
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +7 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +201 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +462 -40
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -0
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +652 -376
- package/src/index.ts +16 -3
- package/src/pack/index.ts +13 -13
- package/src/plugins/capabilities.ts +40 -42
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +4 -0
- package/src/projects/index.ts +453 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +790 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/team/index.ts +3642 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +6 -24
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
package/src/workflow/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFile, readdir } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type { TaskContract } from "../task/index.ts";
|
|
4
3
|
|
|
5
4
|
export type WorkflowMode = "minimal" | "standard" | "rigorous";
|
|
6
5
|
|
|
@@ -13,7 +12,7 @@ export interface WorkflowManifest {
|
|
|
13
12
|
steps: WorkflowStep[];
|
|
14
13
|
requiredEvidence: string[];
|
|
15
14
|
verification: {
|
|
16
|
-
policy: "
|
|
15
|
+
policy: "advisory";
|
|
17
16
|
antiCriteria: string[];
|
|
18
17
|
};
|
|
19
18
|
privacy: {
|
|
@@ -36,12 +35,10 @@ export interface WorkflowStep {
|
|
|
36
35
|
|
|
37
36
|
export interface WorkflowPlan {
|
|
38
37
|
workflow: WorkflowManifest;
|
|
39
|
-
taskId?: string;
|
|
40
|
-
mode: WorkflowMode | null;
|
|
41
38
|
steps: Array<WorkflowStep & { plannedOnly: true }>;
|
|
42
39
|
requiredEvidence: string[];
|
|
43
40
|
warnings: string[];
|
|
44
|
-
|
|
41
|
+
advisories: string[];
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export async function scanWorkflowManifests(workflowsDir: string): Promise<WorkflowManifest[]> {
|
|
@@ -78,26 +75,13 @@ export function parseWorkflowManifest(value: unknown): WorkflowManifest {
|
|
|
78
75
|
return manifest;
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
export function planWorkflow(input: {
|
|
82
|
-
workflow: WorkflowManifest;
|
|
83
|
-
contract?: TaskContract;
|
|
84
|
-
}): WorkflowPlan {
|
|
85
|
-
const mode = input.contract?.route.mode ?? null;
|
|
86
|
-
const warnings: string[] = [];
|
|
87
|
-
const blockers: string[] = [];
|
|
88
|
-
|
|
89
|
-
if (mode !== null && !input.workflow.modes.includes(mode)) {
|
|
90
|
-
blockers.push(`Workflow ${input.workflow.id} does not support task mode ${mode}.`);
|
|
91
|
-
}
|
|
92
|
-
|
|
78
|
+
export function planWorkflow(input: { workflow: WorkflowManifest }): WorkflowPlan {
|
|
93
79
|
return {
|
|
94
80
|
workflow: input.workflow,
|
|
95
|
-
taskId: input.contract?.taskId,
|
|
96
|
-
mode,
|
|
97
81
|
steps: input.workflow.steps.map((step) => ({ ...step, plannedOnly: true })),
|
|
98
82
|
requiredEvidence: input.workflow.requiredEvidence,
|
|
99
|
-
warnings,
|
|
100
|
-
|
|
83
|
+
warnings: [],
|
|
84
|
+
advisories: [],
|
|
101
85
|
};
|
|
102
86
|
}
|
|
103
87
|
|
|
@@ -116,8 +100,6 @@ export function formatWorkflowPlan(plan: WorkflowPlan): string {
|
|
|
116
100
|
"EvoDev workflow dry-run",
|
|
117
101
|
"",
|
|
118
102
|
`Workflow: ${plan.workflow.id}`,
|
|
119
|
-
`Task: ${plan.taskId ?? "none"}`,
|
|
120
|
-
`Mode: ${plan.mode ?? "not routed"}`,
|
|
121
103
|
"",
|
|
122
104
|
"Steps:",
|
|
123
105
|
...plan.steps.map(
|
|
@@ -128,7 +110,7 @@ export function formatWorkflowPlan(plan: WorkflowPlan): string {
|
|
|
128
110
|
"Required evidence:",
|
|
129
111
|
...plan.requiredEvidence.map((item) => ` - ${item}`),
|
|
130
112
|
...plan.warnings.map((warning) => `Warning: ${warning}`),
|
|
131
|
-
...plan.
|
|
113
|
+
...plan.advisories.map((advisory) => `Advisory: ${advisory}`),
|
|
132
114
|
].join("\n");
|
|
133
115
|
}
|
|
134
116
|
|
package/src/project/index.ts
DELETED
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, join, relative } from "node:path";
|
|
3
|
-
|
|
4
|
-
export const PROJECT_CONTEXT_ALLOWED_RELATIVE_PATHS = [
|
|
5
|
-
".evodev/project.json",
|
|
6
|
-
".evodev/profile.md",
|
|
7
|
-
".evodev/index.json",
|
|
8
|
-
".evodev/commands.json",
|
|
9
|
-
".evodev/privacy.json",
|
|
10
|
-
".evodev/decisions/README.md",
|
|
11
|
-
] as const;
|
|
12
|
-
|
|
13
|
-
export type ProjectContextAllowedRelativePath =
|
|
14
|
-
(typeof PROJECT_CONTEXT_ALLOWED_RELATIVE_PATHS)[number];
|
|
15
|
-
|
|
16
|
-
export type ProjectContextPlanAction = "create" | "error";
|
|
17
|
-
|
|
18
|
-
export interface ProjectContextPlanEntry {
|
|
19
|
-
relativePath: ProjectContextAllowedRelativePath;
|
|
20
|
-
absolutePath: string;
|
|
21
|
-
action: ProjectContextPlanAction;
|
|
22
|
-
reason: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ProjectContextPlan {
|
|
26
|
-
projectDir: string;
|
|
27
|
-
projectId: string;
|
|
28
|
-
files: ProjectContextPlanEntry[];
|
|
29
|
-
index: ProjectIndex;
|
|
30
|
-
commands: ProjectCommands;
|
|
31
|
-
privacy: ProjectPrivacy;
|
|
32
|
-
warnings: string[];
|
|
33
|
-
errors: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface ProjectProfile {
|
|
37
|
-
version: 1;
|
|
38
|
-
projectId: string;
|
|
39
|
-
displayName: string;
|
|
40
|
-
root: {
|
|
41
|
-
pathPolicy: "local-only";
|
|
42
|
-
};
|
|
43
|
-
privacy: {
|
|
44
|
-
classification: "local-private";
|
|
45
|
-
metadataOnly: true;
|
|
46
|
-
sourceContentIncluded: false;
|
|
47
|
-
rawCommandsIncluded: false;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface ProjectIndex {
|
|
52
|
-
version: 1;
|
|
53
|
-
mode: "metadata-only";
|
|
54
|
-
projectId: string;
|
|
55
|
-
generatedAt: null;
|
|
56
|
-
rootName: string;
|
|
57
|
-
files: ProjectFileMetadata[];
|
|
58
|
-
docs: {
|
|
59
|
-
entrypoints: string[];
|
|
60
|
-
};
|
|
61
|
-
commands: {
|
|
62
|
-
scripts: ProjectScriptSummary[];
|
|
63
|
-
testCommandCandidates: string[];
|
|
64
|
-
};
|
|
65
|
-
exclusions: string[];
|
|
66
|
-
sourceContentIncluded: false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ProjectFileMetadata {
|
|
70
|
-
path: string;
|
|
71
|
-
kind: "file" | "directory";
|
|
72
|
-
sizeBytes?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ProjectCommands {
|
|
76
|
-
version: 1;
|
|
77
|
-
metadataOnly: true;
|
|
78
|
-
rawCommandsIncluded: false;
|
|
79
|
-
scripts: ProjectScriptSummary[];
|
|
80
|
-
warnings: string[];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface ProjectScriptSummary {
|
|
84
|
-
name: string;
|
|
85
|
-
commandClass: ProjectCommandClass;
|
|
86
|
-
summary: string;
|
|
87
|
-
rawCommandStored: false;
|
|
88
|
-
redacted: boolean;
|
|
89
|
-
warning?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export type ProjectCommandClass =
|
|
93
|
-
| "build"
|
|
94
|
-
| "format"
|
|
95
|
-
| "install"
|
|
96
|
-
| "lint"
|
|
97
|
-
| "other"
|
|
98
|
-
| "release"
|
|
99
|
-
| "test"
|
|
100
|
-
| "typecheck";
|
|
101
|
-
|
|
102
|
-
export interface ProjectPrivacy {
|
|
103
|
-
version: 1;
|
|
104
|
-
classification: "local-private";
|
|
105
|
-
metadataOnly: true;
|
|
106
|
-
sourceContentIndex: false;
|
|
107
|
-
promptHistoryIndex: false;
|
|
108
|
-
shellHistoryIndex: false;
|
|
109
|
-
rawCommandOutputIndex: false;
|
|
110
|
-
externalUpload: false;
|
|
111
|
-
excludedGlobs: string[];
|
|
112
|
-
protectedPatterns: string[];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface WriteProjectContextResult {
|
|
116
|
-
writtenFiles: ProjectContextAllowedRelativePath[];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const EXCLUDED_DIRECTORY_NAMES = new Set([
|
|
120
|
-
".git",
|
|
121
|
-
".claude",
|
|
122
|
-
".codex",
|
|
123
|
-
"backups",
|
|
124
|
-
"build",
|
|
125
|
-
"coverage",
|
|
126
|
-
"dist",
|
|
127
|
-
"node_modules",
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
const EXCLUDED_FILE_PREFIXES = [".env"];
|
|
131
|
-
const EXCLUDED_FILE_PARTS = [
|
|
132
|
-
"api-key",
|
|
133
|
-
"api_key",
|
|
134
|
-
"apikey",
|
|
135
|
-
"credential",
|
|
136
|
-
"credentials",
|
|
137
|
-
"internal",
|
|
138
|
-
"internal-link",
|
|
139
|
-
"password",
|
|
140
|
-
"passwords",
|
|
141
|
-
"passwd",
|
|
142
|
-
"private",
|
|
143
|
-
"private-url",
|
|
144
|
-
"secret",
|
|
145
|
-
"token",
|
|
146
|
-
];
|
|
147
|
-
const EXCLUDED_FILE_EXTENSIONS = [".key", ".pem", ".p12", ".pfx"];
|
|
148
|
-
const DOC_ENTRYPOINTS = new Set(["README.md"]);
|
|
149
|
-
const DEFAULT_EXCLUDED_GLOBS = [
|
|
150
|
-
".git/**",
|
|
151
|
-
"node_modules/**",
|
|
152
|
-
"dist/**",
|
|
153
|
-
"build/**",
|
|
154
|
-
"coverage/**",
|
|
155
|
-
".env*",
|
|
156
|
-
"**/*secret*",
|
|
157
|
-
"**/*token*",
|
|
158
|
-
"**/*password*",
|
|
159
|
-
"**/*passwd*",
|
|
160
|
-
"**/*api-key*",
|
|
161
|
-
"**/*api_key*",
|
|
162
|
-
"**/*apikey*",
|
|
163
|
-
"**/*credential*",
|
|
164
|
-
"**/*private*",
|
|
165
|
-
"**/*internal*",
|
|
166
|
-
"**/*.key",
|
|
167
|
-
"**/*.pem",
|
|
168
|
-
".claude/**",
|
|
169
|
-
".codex/**",
|
|
170
|
-
".evodev/backups/**",
|
|
171
|
-
];
|
|
172
|
-
const PROTECTED_PATTERNS = [
|
|
173
|
-
"secret",
|
|
174
|
-
"token",
|
|
175
|
-
"password",
|
|
176
|
-
"passwd",
|
|
177
|
-
"api-key",
|
|
178
|
-
"credential",
|
|
179
|
-
"private",
|
|
180
|
-
"private-url",
|
|
181
|
-
"internal",
|
|
182
|
-
"internal-link",
|
|
183
|
-
];
|
|
184
|
-
|
|
185
|
-
export async function createProjectContextPlan(projectDir: string): Promise<ProjectContextPlan> {
|
|
186
|
-
const projectDirStat = await stat(projectDir);
|
|
187
|
-
if (!projectDirStat.isDirectory()) {
|
|
188
|
-
throw new Error(`Project dir is not a directory: ${projectDir}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const projectId = createProjectId(projectDir);
|
|
192
|
-
const privacy = createProjectPrivacy();
|
|
193
|
-
const files = await collectProjectFileMetadata(projectDir);
|
|
194
|
-
const commands = await collectProjectCommands(projectDir);
|
|
195
|
-
const docs = files
|
|
196
|
-
.filter((file) => file.kind === "file" && DOC_ENTRYPOINTS.has(basename(file.path)))
|
|
197
|
-
.map((file) => file.path)
|
|
198
|
-
.sort();
|
|
199
|
-
const index: ProjectIndex = {
|
|
200
|
-
version: 1,
|
|
201
|
-
mode: "metadata-only",
|
|
202
|
-
projectId,
|
|
203
|
-
generatedAt: null,
|
|
204
|
-
rootName: basename(projectDir),
|
|
205
|
-
files,
|
|
206
|
-
docs: { entrypoints: docs },
|
|
207
|
-
commands: {
|
|
208
|
-
scripts: commands.scripts,
|
|
209
|
-
testCommandCandidates: commands.scripts
|
|
210
|
-
.filter((script) => script.commandClass === "test")
|
|
211
|
-
.map((script) => script.name),
|
|
212
|
-
},
|
|
213
|
-
exclusions: privacy.excludedGlobs,
|
|
214
|
-
sourceContentIncluded: false,
|
|
215
|
-
};
|
|
216
|
-
const planFiles = await Promise.all(
|
|
217
|
-
PROJECT_CONTEXT_ALLOWED_RELATIVE_PATHS.map(async (relativePath) => {
|
|
218
|
-
const absolutePath = join(projectDir, relativePath);
|
|
219
|
-
const exists = await pathExists(absolutePath);
|
|
220
|
-
return {
|
|
221
|
-
relativePath,
|
|
222
|
-
absolutePath,
|
|
223
|
-
action: exists ? "error" : "create",
|
|
224
|
-
reason: exists
|
|
225
|
-
? "Target already exists; I1 does not overwrite."
|
|
226
|
-
: "Allowed project context file.",
|
|
227
|
-
} satisfies ProjectContextPlanEntry;
|
|
228
|
-
}),
|
|
229
|
-
);
|
|
230
|
-
const errors = planFiles
|
|
231
|
-
.filter((file) => file.action === "error")
|
|
232
|
-
.map((file) => `${file.relativePath}: ${file.reason}`);
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
projectDir,
|
|
236
|
-
projectId,
|
|
237
|
-
files: planFiles,
|
|
238
|
-
index,
|
|
239
|
-
commands,
|
|
240
|
-
privacy,
|
|
241
|
-
warnings: commands.warnings,
|
|
242
|
-
errors,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export async function writeProjectContext(
|
|
247
|
-
plan: ProjectContextPlan,
|
|
248
|
-
): Promise<WriteProjectContextResult> {
|
|
249
|
-
if (plan.errors.length > 0) {
|
|
250
|
-
throw new Error(`Cannot write project context:\n${plan.errors.join("\n")}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const payloads = createProjectContextPayloads(plan);
|
|
254
|
-
const writtenFiles: ProjectContextAllowedRelativePath[] = [];
|
|
255
|
-
|
|
256
|
-
for (const relativePath of PROJECT_CONTEXT_ALLOWED_RELATIVE_PATHS) {
|
|
257
|
-
const content = payloads[relativePath];
|
|
258
|
-
const absolutePath = join(plan.projectDir, relativePath);
|
|
259
|
-
await mkdir(join(absolutePath, ".."), { recursive: true });
|
|
260
|
-
await writeFile(absolutePath, content, { encoding: "utf8", flag: "wx" });
|
|
261
|
-
writtenFiles.push(relativePath);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return { writtenFiles };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export function formatProjectContextPlan(
|
|
268
|
-
plan: ProjectContextPlan,
|
|
269
|
-
mode: "dry-run" | "write",
|
|
270
|
-
): string {
|
|
271
|
-
return [
|
|
272
|
-
"EvoDev project init",
|
|
273
|
-
"",
|
|
274
|
-
`Mode: ${mode}`,
|
|
275
|
-
`Project: ${plan.projectDir}`,
|
|
276
|
-
`Project id: ${plan.projectId}`,
|
|
277
|
-
"",
|
|
278
|
-
"Plan:",
|
|
279
|
-
...plan.files.map((file) => ` - ${file.action}: ${file.relativePath} (${file.reason})`),
|
|
280
|
-
"",
|
|
281
|
-
"Metadata-only index summary:",
|
|
282
|
-
` - files: ${plan.index.files.filter((file) => file.kind === "file").length}`,
|
|
283
|
-
` - directories: ${plan.index.files.filter((file) => file.kind === "directory").length}`,
|
|
284
|
-
` - docs: ${plan.index.docs.entrypoints.length}`,
|
|
285
|
-
` - package scripts: ${plan.commands.scripts.length}`,
|
|
286
|
-
...plan.warnings.map((warning) => `Warning: ${warning}`),
|
|
287
|
-
...plan.errors.map((error) => `Error: ${error}`),
|
|
288
|
-
].join("\n");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function createProjectContextPayloads(
|
|
292
|
-
plan: ProjectContextPlan,
|
|
293
|
-
): Record<ProjectContextAllowedRelativePath, string> {
|
|
294
|
-
const profile: ProjectProfile = {
|
|
295
|
-
version: 1,
|
|
296
|
-
projectId: plan.projectId,
|
|
297
|
-
displayName: basename(plan.projectDir),
|
|
298
|
-
root: { pathPolicy: "local-only" },
|
|
299
|
-
privacy: {
|
|
300
|
-
classification: "local-private",
|
|
301
|
-
metadataOnly: true,
|
|
302
|
-
sourceContentIncluded: false,
|
|
303
|
-
rawCommandsIncluded: false,
|
|
304
|
-
},
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
".evodev/project.json": `${JSON.stringify(profile, null, 2)}\n`,
|
|
309
|
-
".evodev/profile.md": createProfileMarkdown(plan),
|
|
310
|
-
".evodev/index.json": `${JSON.stringify(plan.index, null, 2)}\n`,
|
|
311
|
-
".evodev/commands.json": `${JSON.stringify(plan.commands, null, 2)}\n`,
|
|
312
|
-
".evodev/privacy.json": `${JSON.stringify(plan.privacy, null, 2)}\n`,
|
|
313
|
-
".evodev/decisions/README.md": "# Project Decisions\n\nRecord project decisions here.\n",
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function createProfileMarkdown(plan: ProjectContextPlan): string {
|
|
318
|
-
return [
|
|
319
|
-
`# ${basename(plan.projectDir)} Project Context`,
|
|
320
|
-
"",
|
|
321
|
-
"This project context was generated as metadata-only local state.",
|
|
322
|
-
"",
|
|
323
|
-
`- Project id: ${plan.projectId}`,
|
|
324
|
-
"- Source content included: false",
|
|
325
|
-
"- Raw package script commands included: false",
|
|
326
|
-
"- External upload: false",
|
|
327
|
-
"",
|
|
328
|
-
].join("\n");
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function collectProjectFileMetadata(projectDir: string): Promise<ProjectFileMetadata[]> {
|
|
332
|
-
const files: ProjectFileMetadata[] = [];
|
|
333
|
-
|
|
334
|
-
async function visit(dir: string): Promise<void> {
|
|
335
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
336
|
-
|
|
337
|
-
for (const entry of entries) {
|
|
338
|
-
const absolutePath = join(dir, entry.name);
|
|
339
|
-
const relativePath = relative(projectDir, absolutePath).replaceAll("\\", "/");
|
|
340
|
-
if (shouldExcludePath(relativePath, entry.isDirectory())) {
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (entry.isDirectory()) {
|
|
345
|
-
files.push({ path: relativePath, kind: "directory" });
|
|
346
|
-
await visit(absolutePath);
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (entry.isFile()) {
|
|
351
|
-
const fileStat = await stat(absolutePath);
|
|
352
|
-
files.push({ path: relativePath, kind: "file", sizeBytes: fileStat.size });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
await visit(projectDir);
|
|
358
|
-
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function collectProjectCommands(projectDir: string): Promise<ProjectCommands> {
|
|
362
|
-
const packageJsonPath = join(projectDir, "package.json");
|
|
363
|
-
const warnings: string[] = [];
|
|
364
|
-
|
|
365
|
-
if (!(await pathExists(packageJsonPath))) {
|
|
366
|
-
return { version: 1, metadataOnly: true, rawCommandsIncluded: false, scripts: [], warnings };
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
let parsed: unknown;
|
|
370
|
-
try {
|
|
371
|
-
parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
372
|
-
} catch (error) {
|
|
373
|
-
warnings.push(`package.json scripts skipped: ${describeError(error)}`);
|
|
374
|
-
return { version: 1, metadataOnly: true, rawCommandsIncluded: false, scripts: [], warnings };
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
|
|
378
|
-
const summaries: ProjectScriptSummary[] = [];
|
|
379
|
-
|
|
380
|
-
for (const [name, value] of Object.entries(scripts).sort(([left], [right]) =>
|
|
381
|
-
left.localeCompare(right),
|
|
382
|
-
)) {
|
|
383
|
-
if (typeof value !== "string") {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const commandClass = classifyScript(name, value);
|
|
388
|
-
const sensitiveReason = detectSensitiveScript(`${name} ${value}`);
|
|
389
|
-
if (sensitiveReason !== null) {
|
|
390
|
-
const safeName = `redacted-${commandClass}-script`;
|
|
391
|
-
const warning = `Protected ${commandClass} script name/command omitted because it matched protected pattern: ${sensitiveReason}.`;
|
|
392
|
-
warnings.push(warning);
|
|
393
|
-
summaries.push({
|
|
394
|
-
name: safeName,
|
|
395
|
-
commandClass,
|
|
396
|
-
summary: `Protected ${commandClass} script name/command omitted.`,
|
|
397
|
-
rawCommandStored: false,
|
|
398
|
-
redacted: true,
|
|
399
|
-
warning,
|
|
400
|
-
});
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
summaries.push({
|
|
405
|
-
name,
|
|
406
|
-
commandClass,
|
|
407
|
-
summary: `Safe ${commandClass} script command summary only.`,
|
|
408
|
-
rawCommandStored: false,
|
|
409
|
-
redacted: false,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
version: 1,
|
|
415
|
-
metadataOnly: true,
|
|
416
|
-
rawCommandsIncluded: false,
|
|
417
|
-
scripts: summaries,
|
|
418
|
-
warnings,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function classifyScript(name: string, command: string): ProjectCommandClass {
|
|
423
|
-
const text = `${name} ${command}`.toLowerCase();
|
|
424
|
-
if (text.includes("typecheck") || text.includes("tsc")) return "typecheck";
|
|
425
|
-
if (text.includes("lint") || text.includes("biome") || text.includes("eslint")) return "lint";
|
|
426
|
-
if (text.includes("test")) return "test";
|
|
427
|
-
if (text.includes("build")) return "build";
|
|
428
|
-
if (text.includes("format") || text.includes("prettier")) return "format";
|
|
429
|
-
if (text.includes("install")) return "install";
|
|
430
|
-
if (text.includes("release") || text.includes("publish") || text.includes("pack"))
|
|
431
|
-
return "release";
|
|
432
|
-
return "other";
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function detectSensitiveScript(command: string): string | null {
|
|
436
|
-
const lower = command.toLowerCase();
|
|
437
|
-
if (
|
|
438
|
-
/(^|[^a-z0-9])(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials|private|internal|private-url|internal-link)([^a-z0-9]|$)/.test(
|
|
439
|
-
lower,
|
|
440
|
-
)
|
|
441
|
-
)
|
|
442
|
-
return "protected-name-or-command";
|
|
443
|
-
if (/https?:\/\//i.test(command)) return "url";
|
|
444
|
-
if (/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i.test(command)) return "email-or-internal-id";
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function createProjectPrivacy(): ProjectPrivacy {
|
|
449
|
-
return {
|
|
450
|
-
version: 1,
|
|
451
|
-
classification: "local-private",
|
|
452
|
-
metadataOnly: true,
|
|
453
|
-
sourceContentIndex: false,
|
|
454
|
-
promptHistoryIndex: false,
|
|
455
|
-
shellHistoryIndex: false,
|
|
456
|
-
rawCommandOutputIndex: false,
|
|
457
|
-
externalUpload: false,
|
|
458
|
-
excludedGlobs: DEFAULT_EXCLUDED_GLOBS,
|
|
459
|
-
protectedPatterns: PROTECTED_PATTERNS,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function createProjectId(projectDir: string): string {
|
|
464
|
-
return (
|
|
465
|
-
basename(projectDir)
|
|
466
|
-
.toLowerCase()
|
|
467
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
468
|
-
.replace(/^-+|-+$/g, "") || "project"
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function shouldExcludePath(relativePath: string, isDirectory: boolean): boolean {
|
|
473
|
-
const segments = relativePath.split("/");
|
|
474
|
-
const name = segments.at(-1) ?? relativePath;
|
|
475
|
-
const lowerName = name.toLowerCase();
|
|
476
|
-
|
|
477
|
-
if (isDirectory && EXCLUDED_DIRECTORY_NAMES.has(name)) return true;
|
|
478
|
-
if (segments.includes(".evodev") && !relativePath.startsWith(".evodev/decisions")) return true;
|
|
479
|
-
if (EXCLUDED_FILE_PREFIXES.some((prefix) => name.startsWith(prefix))) return true;
|
|
480
|
-
if (EXCLUDED_FILE_PARTS.some((part) => lowerName.includes(part))) return true;
|
|
481
|
-
if (EXCLUDED_FILE_EXTENSIONS.some((extension) => lowerName.endsWith(extension))) return true;
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async function pathExists(path: string): Promise<boolean> {
|
|
486
|
-
try {
|
|
487
|
-
await stat(path);
|
|
488
|
-
return true;
|
|
489
|
-
} catch (error) {
|
|
490
|
-
if (
|
|
491
|
-
error instanceof Error &&
|
|
492
|
-
"code" in error &&
|
|
493
|
-
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
494
|
-
) {
|
|
495
|
-
return false;
|
|
496
|
-
}
|
|
497
|
-
throw error;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
502
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function describeError(error: unknown): string {
|
|
506
|
-
return error instanceof Error ? error.message : String(error);
|
|
507
|
-
}
|