@evo-dev/core 0.0.1-alpha
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 +19 -0
- package/assets/agents/review/code-reviewer/manifest.json +10 -0
- package/assets/agents/review/code-reviewer/prompt.md +59 -0
- package/assets/agents/review/code-reviewer/verification.md +11 -0
- package/assets/skills/coding/engineering-discipline/SKILL.md +63 -0
- package/assets/skills/coding/engineering-discipline/anti-patterns.md +21 -0
- package/assets/skills/coding/engineering-discipline/examples.md +19 -0
- package/assets/skills/coding/engineering-discipline/manifest.json +10 -0
- package/assets/skills/coding/engineering-discipline/verification.md +11 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +45 -0
- package/assets/workflows/rd-code-review/WORKFLOW.json +45 -0
- package/assets/workflows/rd-docs-update/WORKFLOW.json +45 -0
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +45 -0
- package/assets/workflows/rd-refactor/WORKFLOW.json +45 -0
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +49 -0
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +45 -0
- package/assets/workflows/rd-test-generation/WORKFLOW.json +45 -0
- package/dist/assets/index.js +209 -0
- package/dist/config/index.js +601 -0
- package/dist/index.js +4879 -0
- package/dist/plugins/index.js +265 -0
- package/package.json +30 -0
- package/src/.gitkeep +0 -0
- package/src/agents/index.ts +561 -0
- package/src/assets/errors.ts +21 -0
- package/src/assets/index.ts +18 -0
- package/src/assets/manifest.ts +109 -0
- package/src/assets/scanner.ts +189 -0
- package/src/config/errors.ts +21 -0
- package/src/config/index.ts +26 -0
- package/src/config/paths.ts +43 -0
- package/src/config/registry.ts +84 -0
- package/src/config/settings.ts +212 -0
- package/src/config/state.ts +130 -0
- package/src/config/store.ts +166 -0
- package/src/daemon/index.ts +414 -0
- package/src/hooks/index.ts +1023 -0
- package/src/index.ts +14 -0
- package/src/learning/index.ts +714 -0
- package/src/observability/index.ts +272 -0
- package/src/pack/index.ts +779 -0
- package/src/plugins/capabilities.ts +347 -0
- package/src/plugins/index.ts +41 -0
- package/src/plugins/registry.ts +60 -0
- package/src/plugins/types.ts +123 -0
- package/src/project/index.ts +507 -0
- package/src/protected-zones/index.ts +137 -0
- package/src/sync/index.ts +7 -0
- package/src/sync/orchestrator.ts +298 -0
- package/src/task/index.ts +840 -0
- package/src/workflow/index.ts +137 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export type ObservabilityEventType =
|
|
5
|
+
| "verification.completed"
|
|
6
|
+
| "workflow.step.planned"
|
|
7
|
+
| "test-run.completed"
|
|
8
|
+
| "review.finding";
|
|
9
|
+
|
|
10
|
+
export interface ObservabilityEvent {
|
|
11
|
+
version: 1;
|
|
12
|
+
eventId: string;
|
|
13
|
+
type: ObservabilityEventType;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
scope?: {
|
|
16
|
+
taskId?: string;
|
|
17
|
+
workflowId?: string;
|
|
18
|
+
projectId?: string;
|
|
19
|
+
};
|
|
20
|
+
privacy: {
|
|
21
|
+
classification: "local-private";
|
|
22
|
+
metadataOnly: true;
|
|
23
|
+
rawPayloadStored: false;
|
|
24
|
+
rawOutputStored: false;
|
|
25
|
+
sourceContentStored: false;
|
|
26
|
+
promptStored: false;
|
|
27
|
+
};
|
|
28
|
+
summary: string;
|
|
29
|
+
data: Record<string, string | number | boolean | string[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ObservabilityStorePaths {
|
|
33
|
+
rootDir: string;
|
|
34
|
+
eventsPath: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RetentionDryRunResult {
|
|
38
|
+
candidates: Array<{ path: string; sizeBytes: number; reason: string }>;
|
|
39
|
+
totalBytes: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const EVENT_TYPE_TO_DIR: Record<ObservabilityEventType, string> = {
|
|
43
|
+
"verification.completed": "verification",
|
|
44
|
+
"workflow.step.planned": "workflows",
|
|
45
|
+
"test-run.completed": "tests",
|
|
46
|
+
"review.finding": "reviews",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const FORBIDDEN_RAW_KEYS = new Set([
|
|
50
|
+
"commandoutput",
|
|
51
|
+
"memorybodies",
|
|
52
|
+
"memorybody",
|
|
53
|
+
"promptbody",
|
|
54
|
+
"prompttext",
|
|
55
|
+
"rawcommandoutput",
|
|
56
|
+
"rawoutput",
|
|
57
|
+
"rawpayload",
|
|
58
|
+
"secret",
|
|
59
|
+
"secretvalue",
|
|
60
|
+
"source",
|
|
61
|
+
"sourcebody",
|
|
62
|
+
"sourcecode",
|
|
63
|
+
"sourcecontent",
|
|
64
|
+
"sourcetext",
|
|
65
|
+
"stderr",
|
|
66
|
+
"stdout",
|
|
67
|
+
"transcript",
|
|
68
|
+
"transcriptbody",
|
|
69
|
+
"transcripttext",
|
|
70
|
+
]);
|
|
71
|
+
const SENSITIVE_TEXT_PATTERN =
|
|
72
|
+
/https?:\/\/\S+|\b(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|raw log|raw source|raw prompt)\b/i;
|
|
73
|
+
|
|
74
|
+
export function createObservabilityEvent(input: {
|
|
75
|
+
eventId: string;
|
|
76
|
+
type: ObservabilityEventType;
|
|
77
|
+
summary: string;
|
|
78
|
+
data?: Record<string, string | number | boolean | string[]>;
|
|
79
|
+
timestamp?: string;
|
|
80
|
+
scope?: ObservabilityEvent["scope"];
|
|
81
|
+
}): ObservabilityEvent {
|
|
82
|
+
const event: ObservabilityEvent = {
|
|
83
|
+
version: 1,
|
|
84
|
+
eventId: sanitizeId(input.eventId),
|
|
85
|
+
type: input.type,
|
|
86
|
+
timestamp: input.timestamp ?? new Date().toISOString(),
|
|
87
|
+
scope: input.scope,
|
|
88
|
+
privacy: {
|
|
89
|
+
classification: "local-private",
|
|
90
|
+
metadataOnly: true,
|
|
91
|
+
rawPayloadStored: false,
|
|
92
|
+
rawOutputStored: false,
|
|
93
|
+
sourceContentStored: false,
|
|
94
|
+
promptStored: false,
|
|
95
|
+
},
|
|
96
|
+
summary: sanitizeText(input.summary),
|
|
97
|
+
data: sanitizeData(input.data ?? {}),
|
|
98
|
+
};
|
|
99
|
+
validateObservabilityEvent(event);
|
|
100
|
+
return event;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function validateObservabilityEvent(event: ObservabilityEvent): void {
|
|
104
|
+
if (event.version !== 1) throw new Error("Observability event version must be 1.");
|
|
105
|
+
if (event.privacy.metadataOnly !== true)
|
|
106
|
+
throw new Error("Observability event must be metadata-only.");
|
|
107
|
+
if (
|
|
108
|
+
event.privacy.rawPayloadStored !== false ||
|
|
109
|
+
event.privacy.rawOutputStored !== false ||
|
|
110
|
+
event.privacy.sourceContentStored !== false ||
|
|
111
|
+
event.privacy.promptStored !== false
|
|
112
|
+
) {
|
|
113
|
+
throw new Error("Observability event cannot store raw payload/output/source/prompt.");
|
|
114
|
+
}
|
|
115
|
+
assertNoForbiddenContent(event);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function resolveObservabilityStorePaths(
|
|
119
|
+
homeDir: string,
|
|
120
|
+
type: ObservabilityEventType,
|
|
121
|
+
): ObservabilityStorePaths {
|
|
122
|
+
const rootDir = join(homeDir, ".evodev", "OBSERVABILITY", EVENT_TYPE_TO_DIR[type]);
|
|
123
|
+
return { rootDir, eventsPath: join(rootDir, "events.jsonl") };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function appendObservabilityEvent(
|
|
127
|
+
homeDir: string,
|
|
128
|
+
event: ObservabilityEvent,
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
validateObservabilityEvent(event);
|
|
131
|
+
const paths = resolveObservabilityStorePaths(homeDir, event.type);
|
|
132
|
+
await mkdir(dirname(paths.eventsPath), { recursive: true });
|
|
133
|
+
await writeFile(paths.eventsPath, `${JSON.stringify(event)}\n`, { encoding: "utf8", flag: "a" });
|
|
134
|
+
return paths.eventsPath;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function listObservabilityEvents(
|
|
138
|
+
homeDir: string,
|
|
139
|
+
type?: ObservabilityEventType,
|
|
140
|
+
): Promise<ObservabilityEvent[]> {
|
|
141
|
+
const eventTypes =
|
|
142
|
+
type === undefined ? (Object.keys(EVENT_TYPE_TO_DIR) as ObservabilityEventType[]) : [type];
|
|
143
|
+
const events: ObservabilityEvent[] = [];
|
|
144
|
+
|
|
145
|
+
for (const eventType of eventTypes) {
|
|
146
|
+
const path = resolveObservabilityStorePaths(homeDir, eventType).eventsPath;
|
|
147
|
+
if (!(await pathExists(path))) continue;
|
|
148
|
+
const lines = (await readFile(path, "utf8")).split("\n").filter(Boolean);
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const event = JSON.parse(line) as ObservabilityEvent;
|
|
151
|
+
validateObservabilityEvent(event);
|
|
152
|
+
events.push(event);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return events.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function dryRunObservabilityRetentionCleanup(
|
|
160
|
+
homeDir: string,
|
|
161
|
+
): Promise<RetentionDryRunResult> {
|
|
162
|
+
const root = join(homeDir, ".evodev", "OBSERVABILITY");
|
|
163
|
+
const candidates: RetentionDryRunResult["candidates"] = [];
|
|
164
|
+
if (!(await pathExists(root))) return { candidates, totalBytes: 0 };
|
|
165
|
+
|
|
166
|
+
for (const file of await collectJsonlFiles(root)) {
|
|
167
|
+
const fileStat = await stat(file);
|
|
168
|
+
candidates.push({ path: file, sizeBytes: fileStat.size, reason: "observability-jsonl" });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
candidates,
|
|
173
|
+
totalBytes: candidates.reduce((sum, candidate) => sum + candidate.sizeBytes, 0),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function formatObservabilityEvents(events: ObservabilityEvent[]): string {
|
|
178
|
+
return [
|
|
179
|
+
"EvoDev observability events",
|
|
180
|
+
"",
|
|
181
|
+
...events.map(
|
|
182
|
+
(event) => `- ${event.timestamp} ${event.type} ${event.eventId}: ${event.summary}`,
|
|
183
|
+
),
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function formatRetentionDryRun(result: RetentionDryRunResult): string {
|
|
188
|
+
return [
|
|
189
|
+
"EvoDev observability cleanup dry-run",
|
|
190
|
+
"",
|
|
191
|
+
`Candidates: ${result.candidates.length}`,
|
|
192
|
+
`Total bytes: ${result.totalBytes}`,
|
|
193
|
+
...result.candidates.map(
|
|
194
|
+
(candidate) => `- ${candidate.path} (${candidate.sizeBytes} bytes, ${candidate.reason})`,
|
|
195
|
+
),
|
|
196
|
+
].join("\n");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function sanitizeData(
|
|
200
|
+
data: Record<string, string | number | boolean | string[]>,
|
|
201
|
+
): Record<string, string | number | boolean | string[]> {
|
|
202
|
+
const sanitized: Record<string, string | number | boolean | string[]> = {};
|
|
203
|
+
for (const [key, value] of Object.entries(data)) {
|
|
204
|
+
const normalizedKey = normalizeKey(key);
|
|
205
|
+
if (FORBIDDEN_RAW_KEYS.has(normalizedKey)) {
|
|
206
|
+
throw new Error(`Observability data contains forbidden raw field: ${key}`);
|
|
207
|
+
}
|
|
208
|
+
if (typeof value === "string") sanitized[key] = sanitizeText(value);
|
|
209
|
+
else if (Array.isArray(value)) sanitized[key] = value.map(sanitizeText);
|
|
210
|
+
else sanitized[key] = value;
|
|
211
|
+
}
|
|
212
|
+
return sanitized;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function assertNoForbiddenContent(value: unknown): void {
|
|
216
|
+
if (typeof value === "string") {
|
|
217
|
+
if (value === "local-private") return;
|
|
218
|
+
if (SENSITIVE_TEXT_PATTERN.test(value))
|
|
219
|
+
throw new Error("Observability event contains sensitive content.");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(value)) {
|
|
223
|
+
for (const item of value) assertNoForbiddenContent(item);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (typeof value !== "object" || value === null) return;
|
|
227
|
+
for (const [key, child] of Object.entries(value)) {
|
|
228
|
+
const normalizedKey = normalizeKey(key);
|
|
229
|
+
if (FORBIDDEN_RAW_KEYS.has(normalizedKey)) {
|
|
230
|
+
throw new Error(`Observability event contains forbidden raw field: ${key}`);
|
|
231
|
+
}
|
|
232
|
+
assertNoForbiddenContent(child);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function collectJsonlFiles(root: string): Promise<string[]> {
|
|
237
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
238
|
+
const files: string[] = [];
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
const path = join(root, entry.name);
|
|
241
|
+
if (entry.isDirectory()) files.push(...(await collectJsonlFiles(path)));
|
|
242
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(path);
|
|
243
|
+
}
|
|
244
|
+
return files.sort();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
248
|
+
try {
|
|
249
|
+
await stat(path);
|
|
250
|
+
return true;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (
|
|
253
|
+
error instanceof Error &&
|
|
254
|
+
"code" in error &&
|
|
255
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
256
|
+
)
|
|
257
|
+
return false;
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function sanitizeText(value: string): string {
|
|
263
|
+
return value.replace(SENSITIVE_TEXT_PATTERN, "[redacted]").slice(0, 500);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function sanitizeId(value: string): string {
|
|
267
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 80) || "event";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function normalizeKey(key: string): string {
|
|
271
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
272
|
+
}
|