jeo-code 0.1.0 → 0.4.5
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/README.ja.md +160 -0
- package/README.ko.md +160 -0
- package/README.md +115 -297
- package/README.zh.md +160 -0
- package/package.json +11 -6
- package/scripts/install.sh +28 -28
- package/scripts/uninstall.sh +17 -15
- package/src/AGENTS.md +50 -0
- package/src/agent/AGENTS.md +49 -0
- package/src/agent/bash-fixups.ts +103 -0
- package/src/agent/compaction.ts +410 -19
- package/src/agent/config-schema.ts +119 -5
- package/src/agent/context-files.ts +314 -17
- package/src/agent/dev/AGENTS.md +36 -0
- package/src/agent/dev/advanced-analyzer.ts +12 -0
- package/src/agent/dev/evolution-bridge.ts +82 -0
- package/src/agent/dev/evolution-logger.ts +41 -0
- package/src/agent/dev/self-analysis.ts +64 -0
- package/src/agent/dev/self-improve.ts +24 -0
- package/src/agent/dev/spec-automation.ts +49 -0
- package/src/agent/engine.ts +808 -54
- package/src/agent/hooks.ts +273 -0
- package/src/agent/loop.ts +21 -1
- package/src/agent/memory.ts +201 -0
- package/src/agent/model-recency.ts +32 -0
- package/src/agent/output-minimizer.ts +108 -0
- package/src/agent/output-util.ts +64 -0
- package/src/agent/plan.ts +187 -0
- package/src/agent/seed.ts +52 -0
- package/src/agent/session.ts +235 -21
- package/src/agent/state.ts +286 -39
- package/src/agent/step-budget.ts +232 -0
- package/src/agent/subagents.ts +223 -26
- package/src/agent/task-tool.ts +272 -0
- package/src/agent/todo-tool.ts +87 -0
- package/src/agent/tokenizer.ts +117 -0
- package/src/agent/tool-registry.ts +54 -0
- package/src/agent/tools.ts +624 -103
- package/src/agent/web-search.ts +538 -0
- package/src/ai/AGENTS.md +44 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog-compat.ts +3 -1
- package/src/ai/model-catalog.ts +74 -9
- package/src/ai/model-discovery.ts +215 -17
- package/src/ai/model-manager.ts +346 -32
- package/src/ai/model-picker.ts +1 -1
- package/src/ai/model-registry.ts +4 -2
- package/src/ai/pricing.ts +84 -0
- package/src/ai/provider-registry.ts +23 -0
- package/src/ai/provider-status.ts +60 -16
- package/src/ai/providers/AGENTS.md +42 -0
- package/src/ai/providers/anthropic.ts +250 -31
- package/src/ai/providers/antigravity.ts +219 -0
- package/src/ai/providers/errors.ts +15 -1
- package/src/ai/providers/gemini.ts +196 -13
- package/src/ai/providers/ollama.ts +37 -7
- package/src/ai/providers/openai-responses.ts +173 -0
- package/src/ai/providers/openai.ts +64 -12
- package/src/ai/sse.ts +4 -1
- package/src/ai/types.ts +18 -1
- package/src/auth/AGENTS.md +41 -0
- package/src/auth/callback-server.ts +6 -1
- package/src/auth/flows/AGENTS.md +32 -0
- package/src/auth/flows/antigravity.ts +151 -0
- package/src/auth/flows/google-project.ts +190 -0
- package/src/auth/flows/google.ts +39 -18
- package/src/auth/flows/index.ts +15 -5
- package/src/auth/flows/openai.ts +2 -2
- package/src/auth/oauth.ts +8 -0
- package/src/auth/refresh.ts +44 -27
- package/src/auth/storage.ts +149 -26
- package/src/auth/types.ts +1 -1
- package/src/autopilot.ts +362 -0
- package/src/bun-imports.d.ts +4 -0
- package/src/cli/AGENTS.md +39 -0
- package/src/cli/runner.ts +148 -14
- package/src/cli.ts +13 -4
- package/src/commands/AGENTS.md +40 -0
- package/src/commands/approve.ts +62 -3
- package/src/commands/auth.ts +167 -25
- package/src/commands/chat.ts +37 -8
- package/src/commands/deep-interview.ts +633 -175
- package/src/commands/doctor.ts +84 -37
- package/src/commands/evolve-core.ts +18 -0
- package/src/commands/evolve.ts +2 -1
- package/src/commands/export.ts +176 -0
- package/src/commands/gjc.ts +52 -0
- package/src/commands/launch.ts +3549 -240
- package/src/commands/mcp.ts +3 -3
- package/src/commands/ooo-seed.ts +19 -0
- package/src/commands/ralplan.ts +253 -35
- package/src/commands/resume.ts +1 -1
- package/src/commands/session.ts +183 -0
- package/src/commands/setup-helpers.ts +10 -3
- package/src/commands/setup.ts +57 -16
- package/src/commands/skills.ts +78 -18
- package/src/commands/state.ts +198 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/team.ts +340 -212
- package/src/commands/ultragoal.ts +122 -61
- package/src/commands/update.ts +244 -0
- package/src/ledger.ts +270 -0
- package/src/mcp/AGENTS.md +38 -0
- package/src/mcp/server.ts +115 -14
- package/src/mcp/tools.ts +42 -22
- package/src/md-modules.d.ts +4 -0
- package/src/prompts/AGENTS.md +41 -0
- package/src/prompts/agents/AGENTS.md +35 -0
- package/src/prompts/agents/architect.md +35 -0
- package/src/prompts/agents/critic.md +37 -0
- package/src/prompts/agents/executor.md +36 -0
- package/src/prompts/agents/planner.md +37 -0
- package/src/prompts/skills/AGENTS.md +36 -0
- package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
- package/src/prompts/skills/deep-dive/SKILL.md +13 -0
- package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
- package/src/prompts/skills/deep-interview/SKILL.md +12 -0
- package/src/prompts/skills/gjc/AGENTS.md +31 -0
- package/src/prompts/skills/gjc/SKILL.md +15 -0
- package/src/prompts/skills/ralplan/AGENTS.md +31 -0
- package/src/prompts/skills/ralplan/SKILL.md +11 -0
- package/src/prompts/skills/team/AGENTS.md +31 -0
- package/src/prompts/skills/team/SKILL.md +11 -0
- package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
- package/src/prompts/skills/ultragoal/SKILL.md +11 -0
- package/src/skills/AGENTS.md +38 -0
- package/src/skills/catalog.ts +565 -31
- package/src/tui/AGENTS.md +43 -0
- package/src/tui/app.ts +1181 -92
- package/src/tui/components/AGENTS.md +42 -0
- package/src/tui/components/ascii-art.ts +257 -15
- package/src/tui/components/autocomplete.ts +98 -16
- package/src/tui/components/autopilot-status.ts +65 -0
- package/src/tui/components/category-index.ts +49 -0
- package/src/tui/components/code-view.ts +54 -11
- package/src/tui/components/color.ts +171 -2
- package/src/tui/components/config-panel.ts +82 -15
- package/src/tui/components/duration.ts +38 -0
- package/src/tui/components/evolution.ts +3 -3
- package/src/tui/components/footer.ts +91 -42
- package/src/tui/components/forge.ts +426 -31
- package/src/tui/components/hints.ts +54 -0
- package/src/tui/components/hud.ts +73 -0
- package/src/tui/components/index.ts +4 -0
- package/src/tui/components/input-box.ts +150 -0
- package/src/tui/components/layout.ts +11 -3
- package/src/tui/components/live-model-picker.ts +108 -0
- package/src/tui/components/markdown-table.ts +140 -0
- package/src/tui/components/markdown-text.ts +97 -0
- package/src/tui/components/meter.ts +4 -1
- package/src/tui/components/model-picker.ts +3 -2
- package/src/tui/components/provider-picker.ts +3 -2
- package/src/tui/components/section.ts +70 -0
- package/src/tui/components/select-list.ts +40 -10
- package/src/tui/components/skill-picker.ts +25 -0
- package/src/tui/components/slash.ts +244 -21
- package/src/tui/components/status.ts +272 -11
- package/src/tui/components/step-timeline.ts +218 -0
- package/src/tui/components/stream.ts +26 -9
- package/src/tui/components/themes.ts +212 -6
- package/src/tui/components/todo-card.ts +47 -0
- package/src/tui/components/tool-list.ts +58 -12
- package/src/tui/components/transcript.ts +120 -0
- package/src/tui/components/update-box.ts +31 -0
- package/src/tui/components/welcome.ts +162 -0
- package/src/tui/components/width.ts +163 -0
- package/src/tui/monitoring/AGENTS.md +31 -0
- package/src/tui/monitoring/hud-view.ts +55 -0
- package/src/tui/renderer.ts +112 -3
- package/src/tui/terminal.ts +40 -33
- package/src/util/AGENTS.md +39 -0
- package/src/util/clipboard-image.ts +118 -0
- package/src/util/env.ts +12 -0
- package/src/util/provider-error.ts +78 -0
- package/src/util/retry.ts +91 -6
- package/src/util/update-check.ts +64 -0
- package/src/commands/models.ts +0 -104
|
@@ -1,36 +1,333 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { IGNORED_DIRS } from "./tools";
|
|
5
|
+
|
|
6
|
+
interface ContextItem {
|
|
7
|
+
filePath: string;
|
|
8
|
+
displayPath: string;
|
|
9
|
+
type: "cwd" | "nested" | "parent";
|
|
10
|
+
depth?: number;
|
|
11
|
+
distance?: number;
|
|
12
|
+
candidateName: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export interface ProjectContextFile {
|
|
5
16
|
path: string;
|
|
6
17
|
content: string;
|
|
7
18
|
}
|
|
8
19
|
|
|
9
|
-
export const CONTEXT_CANDIDATES = ["JEO.md", "AGENTS.md", ".
|
|
20
|
+
export const CONTEXT_CANDIDATES = ["JEO.md", "AGENTS.md", ".jeo/context.md", "CLAUDE.md"];
|
|
21
|
+
export const AGENT_GUIDANCE_DIRS = [".agents/rules", ".jeo/rules", ".agents/hooks", ".jeo/hooks"] as const;
|
|
22
|
+
const PER_CONTEXT_FILE_CHARS = 16_000;
|
|
23
|
+
const TOTAL_CONTEXT_CHARS = 64_000;
|
|
24
|
+
const BASE_CONTEXT_CHARS = 48_000;
|
|
25
|
+
const GUIDANCE_CONTEXT_CHARS = TOTAL_CONTEXT_CHARS - BASE_CONTEXT_CHARS;
|
|
26
|
+
const MAX_AGENT_GUIDANCE_FILES = 20;
|
|
27
|
+
const AGENT_GUIDANCE_EXTENSIONS = new Set([".md", ".json", ".jsonc", ".yaml", ".yml", ".toml"]);
|
|
28
|
+
|
|
29
|
+
async function readContextFile(filePath: string, displayPath: string, remainingChars: number): Promise<ProjectContextFile | null> {
|
|
30
|
+
if (remainingChars <= 0) return null;
|
|
31
|
+
try {
|
|
32
|
+
const stat = await fs.stat(filePath);
|
|
33
|
+
if (!stat.isFile() || stat.size <= 0) return null;
|
|
34
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
35
|
+
if (content.length <= 0) return null;
|
|
36
|
+
const cap = Math.min(PER_CONTEXT_FILE_CHARS, remainingChars);
|
|
37
|
+
const finalContent = content.length > cap ? content.slice(0, cap) + "\n…(truncated)" : content;
|
|
38
|
+
return { path: displayPath.replace(/\\/g, "/"), content: finalContent };
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function collectTextFiles(rootDir: string, displayPrefix: string, maxDepth: number, out: Array<{ filePath: string; displayPath: string }>): Promise<void> {
|
|
45
|
+
if (out.length >= MAX_AGENT_GUIDANCE_FILES || maxDepth < 0) return;
|
|
46
|
+
let entries: import("node:fs").Dirent[] = [];
|
|
47
|
+
try { entries = await fs.readdir(rootDir, { withFileTypes: true }); } catch { return; }
|
|
48
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (out.length >= MAX_AGENT_GUIDANCE_FILES) return;
|
|
51
|
+
const filePath = path.join(rootDir, entry.name);
|
|
52
|
+
const displayPath = `${displayPrefix}/${entry.name}`.replace(/\\/g, "/");
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
await collectTextFiles(filePath, displayPath, maxDepth - 1, out);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!entry.isFile()) continue;
|
|
58
|
+
if (AGENT_GUIDANCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) out.push({ filePath, displayPath });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface WorkspaceScan {
|
|
63
|
+
// Nested AGENTS.md found by the downward walk (depth 0..3, skipping IGNORED_DIRS).
|
|
64
|
+
nested: Array<{ filePath: string; displayPath: string; depth: number }>;
|
|
65
|
+
// Local (cwd-rooted) guidance files in canonical order: explicit oma-config/triggers
|
|
66
|
+
// first, then `.agents/rules`, `.jeo/rules`, `.agents/hooks` buckets. Pre-dedupe/cap.
|
|
67
|
+
localGuidance: Array<{ filePath: string; displayPath: string }>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Guidance roots relative to cwd, mirroring AGENT_GUIDANCE_DIRS order. Each is the
|
|
71
|
+
// directory collectTextFiles used to traverse (maxDepth 2).
|
|
72
|
+
const GUIDANCE_ROOTS = AGENT_GUIDANCE_DIRS.map((dir) => ({
|
|
73
|
+
segs: dir.split("/"),
|
|
74
|
+
prefix: dir,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
const EXPLICIT_GUIDANCE_FILES = [".agents/oma-config.yaml", ".agents/oma-config.yml", ".agents/hooks/core/triggers.json"];
|
|
78
|
+
|
|
79
|
+
// Module-level cache of the downward scan, keyed by resolved cwd. The scan is
|
|
80
|
+
// independent of $HOME (it only walks the cwd subtree), so caching by cwd is safe.
|
|
81
|
+
const workspaceScanCache = new Map<string, WorkspaceScan>();
|
|
82
|
+
|
|
83
|
+
function segsEqual(a: readonly string[], b: readonly string[]): boolean {
|
|
84
|
+
if (a.length !== b.length) return false;
|
|
85
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isStrictPrefix(prefix: readonly string[], full: readonly string[]): boolean {
|
|
90
|
+
if (prefix.length >= full.length) return false;
|
|
91
|
+
for (let i = 0; i < prefix.length; i++) if (prefix[i] !== full[i]) return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface GuidanceCtx {
|
|
96
|
+
bucket: number;
|
|
97
|
+
prefix: string;
|
|
98
|
+
remaining: number; // mirrors collectTextFiles maxDepth: root=2, then decremented
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Single downward traversal from resolvedCwd that, in ONE readdir per directory,
|
|
102
|
+
// collects BOTH nested AGENTS.md files and the local `.agents`/`.jeo` guidance files.
|
|
103
|
+
// Replaces the previous separate walkDown + per-root collectTextFiles recursions
|
|
104
|
+
// (which re-read the overlapping `.agents/rules` and `.agents/hooks` subtrees).
|
|
105
|
+
async function scanWorkspaceDownwards(resolvedCwd: string): Promise<WorkspaceScan> {
|
|
106
|
+
const nested: WorkspaceScan["nested"] = [];
|
|
107
|
+
const buckets: Array<Array<{ filePath: string; displayPath: string }>> = GUIDANCE_ROOTS.map(() => []);
|
|
108
|
+
|
|
109
|
+
async function walk(dir: string, depth: number, relSegs: string[], nestedActive: boolean, guidance: GuidanceCtx | null): Promise<void> {
|
|
110
|
+
let entries: import("node:fs").Dirent[] = [];
|
|
111
|
+
try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
|
|
112
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
113
|
+
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const name = entry.name;
|
|
116
|
+
const childPath = path.join(dir, name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
const childSegs = [...relSegs, name];
|
|
119
|
+
let childGuidance: GuidanceCtx | null = null;
|
|
120
|
+
const rootIdx = GUIDANCE_ROOTS.findIndex((r) => segsEqual(childSegs, r.segs));
|
|
121
|
+
if (rootIdx >= 0) {
|
|
122
|
+
childGuidance = { bucket: rootIdx, prefix: GUIDANCE_ROOTS[rootIdx].prefix, remaining: 2 };
|
|
123
|
+
} else if (guidance && guidance.remaining >= 1) {
|
|
124
|
+
childGuidance = { bucket: guidance.bucket, prefix: `${guidance.prefix}/${name}`, remaining: guidance.remaining - 1 };
|
|
125
|
+
}
|
|
126
|
+
const childNestedActive = nestedActive && !IGNORED_DIRS.includes(name);
|
|
127
|
+
const childDepth = depth + 1;
|
|
128
|
+
const nestedWantsDescend = childNestedActive && childDepth <= 3;
|
|
129
|
+
const guidanceWantsDescend = childGuidance !== null;
|
|
130
|
+
const ancestorWantsDescend = GUIDANCE_ROOTS.some((r) => isStrictPrefix(childSegs, r.segs));
|
|
131
|
+
if (nestedWantsDescend || guidanceWantsDescend || ancestorWantsDescend) {
|
|
132
|
+
await walk(childPath, childDepth, childSegs, childNestedActive, childGuidance);
|
|
133
|
+
}
|
|
134
|
+
} else if (entry.isFile()) {
|
|
135
|
+
if (nestedActive && depth <= 3 && name === "AGENTS.md") {
|
|
136
|
+
const displayPath = path.relative(resolvedCwd, childPath).replace(/\\/g, "/");
|
|
137
|
+
nested.push({ filePath: childPath, displayPath, depth });
|
|
138
|
+
}
|
|
139
|
+
if (guidance && guidance.remaining >= 0 && AGENT_GUIDANCE_EXTENSIONS.has(path.extname(name).toLowerCase())) {
|
|
140
|
+
buckets[guidance.bucket].push({ filePath: childPath, displayPath: `${guidance.prefix}/${name}`.replace(/\\/g, "/") });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await walk(resolvedCwd, 0, [], true, null);
|
|
147
|
+
|
|
148
|
+
// Explicit cwd-rooted guidance files (kept as cheap stat checks, fixed order, first).
|
|
149
|
+
const explicit: Array<{ filePath: string; displayPath: string }> = [];
|
|
150
|
+
for (const rel of EXPLICIT_GUIDANCE_FILES) {
|
|
151
|
+
const filePath = path.join(resolvedCwd, rel);
|
|
152
|
+
try {
|
|
153
|
+
const st = await fs.stat(filePath);
|
|
154
|
+
if (st.isFile() && st.size > 0) explicit.push({ filePath, displayPath: rel.replace(/\\/g, "/") });
|
|
155
|
+
} catch { /* optional */ }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { nested, localGuidance: [...explicit, ...buckets.flat()] };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function getWorkspaceScan(cwd: string): Promise<WorkspaceScan> {
|
|
162
|
+
const resolvedCwd = path.resolve(cwd);
|
|
163
|
+
const cached = workspaceScanCache.get(resolvedCwd);
|
|
164
|
+
if (cached) return cached;
|
|
165
|
+
const scan = await scanWorkspaceDownwards(resolvedCwd);
|
|
166
|
+
workspaceScanCache.set(resolvedCwd, scan);
|
|
167
|
+
return scan;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Invalidate the cached single-pass workspace scan. Pass a `cwd` to clear just that
|
|
172
|
+
* entry (resolved), or omit to clear the entire cache. Call this after the workspace's
|
|
173
|
+
* AGENTS.md / `.agents` / `.jeo` guidance files change on disk.
|
|
174
|
+
*/
|
|
175
|
+
export function invalidateWorkspaceScan(cwd?: string): void {
|
|
176
|
+
if (cwd === undefined) {
|
|
177
|
+
workspaceScanCache.clear();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
workspaceScanCache.delete(path.resolve(cwd));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function discoverAgentGuidanceFiles(cwd = process.cwd()): Promise<Array<{ filePath: string; displayPath: string }>> {
|
|
184
|
+
const scan = await getWorkspaceScan(cwd);
|
|
185
|
+
const out: Array<{ filePath: string; displayPath: string }> = [...scan.localGuidance];
|
|
186
|
+
const home = path.join(process.env.HOME || "", "");
|
|
187
|
+
const homeRoots = [
|
|
188
|
+
{ rootDir: path.join(home, ".agents", "rules"), displayPrefix: "~/.agents/rules" },
|
|
189
|
+
{ rootDir: path.join(home, ".jeo", "rules"), displayPrefix: "~/.jeo/rules" },
|
|
190
|
+
{ rootDir: path.join(home, ".agents", "hooks"), displayPrefix: "~/.agents/hooks" },
|
|
191
|
+
{ rootDir: path.join(home, ".jeo", "hooks"), displayPrefix: "~/.jeo/hooks" },
|
|
192
|
+
];
|
|
193
|
+
for (const root of homeRoots) {
|
|
194
|
+
await collectTextFiles(root.rootDir, root.displayPrefix, 2, out);
|
|
195
|
+
}
|
|
196
|
+
const seen = new Set<string>();
|
|
197
|
+
return out.filter(entry => {
|
|
198
|
+
const key = entry.displayPath.toLowerCase();
|
|
199
|
+
if (seen.has(key)) return false;
|
|
200
|
+
seen.add(key);
|
|
201
|
+
return true;
|
|
202
|
+
}).slice(0, MAX_AGENT_GUIDANCE_FILES);
|
|
203
|
+
}
|
|
204
|
+
|
|
10
205
|
|
|
11
206
|
export async function loadProjectContext(cwd = process.cwd()): Promise<ProjectContextFile[]> {
|
|
12
207
|
const result: ProjectContextFile[] = [];
|
|
208
|
+
let baseChars = 0;
|
|
209
|
+
let guidanceChars = 0;
|
|
210
|
+
const addGuidanceFile = async (filePath: string, displayPath: string) => {
|
|
211
|
+
const file = await readContextFile(filePath, displayPath, GUIDANCE_CONTEXT_CHARS - guidanceChars);
|
|
212
|
+
if (!file) return;
|
|
213
|
+
result.push(file);
|
|
214
|
+
guidanceChars += file.content.length;
|
|
215
|
+
};
|
|
13
216
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
217
|
+
const resolvedCwd = path.resolve(cwd);
|
|
218
|
+
const home = process.env.HOME ? path.resolve(process.env.HOME) : null;
|
|
219
|
+
const collectedItems: ContextItem[] = [];
|
|
220
|
+
|
|
221
|
+
// 1. CWD 및 부모 walk
|
|
222
|
+
let curr = resolvedCwd;
|
|
223
|
+
let distance = 0;
|
|
224
|
+
while (true) {
|
|
225
|
+
if (home && curr === home) {
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const candidate of CONTEXT_CANDIDATES) {
|
|
230
|
+
const filePath = path.join(curr, candidate);
|
|
231
|
+
try {
|
|
232
|
+
const stat = await fs.stat(filePath);
|
|
233
|
+
if (stat.isFile() && stat.size > 0) {
|
|
234
|
+
const displayPath = path.relative(resolvedCwd, filePath).replace(/\\/g, "/");
|
|
235
|
+
collectedItems.push({
|
|
236
|
+
filePath,
|
|
237
|
+
displayPath: displayPath || candidate,
|
|
238
|
+
type: distance === 0 ? "cwd" : "parent",
|
|
239
|
+
distance: distance === 0 ? undefined : distance,
|
|
240
|
+
candidateName: candidate,
|
|
28
241
|
});
|
|
29
242
|
}
|
|
243
|
+
} catch {
|
|
244
|
+
// 무시
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
let isGitRoot = false;
|
|
249
|
+
try {
|
|
250
|
+
const gitStat = await fs.stat(path.join(curr, ".git"));
|
|
251
|
+
if (gitStat.isDirectory() || gitStat.isFile()) {
|
|
252
|
+
isGitRoot = true;
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// 무시
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (isGitRoot) {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const parent = path.dirname(curr);
|
|
263
|
+
if (parent === curr) {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
curr = parent;
|
|
267
|
+
distance++;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 2. CWD 하위 중첩 AGENTS.md 수집 (depth <= 3) — 캐시된 단일 스캔에서 읽음
|
|
271
|
+
const scan = await getWorkspaceScan(resolvedCwd);
|
|
272
|
+
for (const nf of scan.nested) {
|
|
273
|
+
collectedItems.push({
|
|
274
|
+
filePath: nf.filePath,
|
|
275
|
+
displayPath: nf.displayPath,
|
|
276
|
+
type: "nested",
|
|
277
|
+
depth: nf.depth,
|
|
278
|
+
candidateName: "AGENTS.md",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 3. 우선순위 정렬
|
|
283
|
+
const typeOrder = { cwd: 1, nested: 2, parent: 3 };
|
|
284
|
+
collectedItems.sort((a, b) => {
|
|
285
|
+
if (a.type !== b.type) {
|
|
286
|
+
return typeOrder[a.type] - typeOrder[b.type];
|
|
287
|
+
}
|
|
288
|
+
if (a.type === "cwd") {
|
|
289
|
+
return CONTEXT_CANDIDATES.indexOf(a.candidateName) - CONTEXT_CANDIDATES.indexOf(b.candidateName);
|
|
290
|
+
} else if (a.type === "nested") {
|
|
291
|
+
if (a.depth !== b.depth) {
|
|
292
|
+
return (b.depth ?? 0) - (a.depth ?? 0);
|
|
293
|
+
}
|
|
294
|
+
return a.displayPath.localeCompare(b.displayPath);
|
|
295
|
+
} else {
|
|
296
|
+
if (a.distance !== b.distance) {
|
|
297
|
+
return (a.distance ?? 0) - (b.distance ?? 0);
|
|
30
298
|
}
|
|
31
|
-
|
|
32
|
-
// Skip missing, unreadable, or directories/empty files
|
|
299
|
+
return CONTEXT_CANDIDATES.indexOf(a.candidateName) - CONTEXT_CANDIDATES.indexOf(b.candidateName);
|
|
33
300
|
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// 중복 경로 제거 (Set 사용)
|
|
304
|
+
const seenPaths = new Set<string>();
|
|
305
|
+
const uniqueItems: ContextItem[] = [];
|
|
306
|
+
for (const item of collectedItems) {
|
|
307
|
+
const key = path.resolve(item.filePath);
|
|
308
|
+
if (!seenPaths.has(key)) {
|
|
309
|
+
seenPaths.add(key);
|
|
310
|
+
uniqueItems.push(item);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 4. 예산 내에서 로드
|
|
315
|
+
for (const item of uniqueItems) {
|
|
316
|
+
if (baseChars >= BASE_CONTEXT_CHARS) {
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
const file = await readContextFile(item.filePath, item.displayPath, BASE_CONTEXT_CHARS - baseChars);
|
|
320
|
+
if (!file) continue;
|
|
321
|
+
result.push(file);
|
|
322
|
+
baseChars += file.content.length;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// GJC/OMA parity: skill docs are loaded by `skills/catalog.ts`; hook/rule guidance is
|
|
326
|
+
// separate project policy. Keep a reserved guidance budget so large root context files do
|
|
327
|
+
// not completely crowd out `.agents` / `.jeo` rules and hooks.
|
|
328
|
+
for (const entry of await discoverAgentGuidanceFiles(cwd)) {
|
|
329
|
+
await addGuidanceFile(entry.filePath, entry.displayPath);
|
|
330
|
+
if (guidanceChars >= GUIDANCE_CONTEXT_CHARS) break;
|
|
34
331
|
}
|
|
35
332
|
|
|
36
333
|
return result;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!-- Parent: ../../AGENTS.md -->
|
|
2
|
+
<!-- Generated: 2026-06-11 | Updated: 2026-06-11 -->
|
|
3
|
+
|
|
4
|
+
# dev
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Developer-specific agent tooling and spec automation.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
| File | Description |
|
|
11
|
+
|------|-------------|
|
|
12
|
+
| `spec-automation.ts` | Tools to sync `.specify/` artifacts to ooo seeds |
|
|
13
|
+
|
|
14
|
+
## Subdirectories
|
|
15
|
+
*(None)*
|
|
16
|
+
|
|
17
|
+
## For AI Agents
|
|
18
|
+
|
|
19
|
+
### Working In This Directory
|
|
20
|
+
- Tools here are for internal workflow automation rather than core end-user features.
|
|
21
|
+
|
|
22
|
+
### Testing Requirements
|
|
23
|
+
- Mock filesystem operations.
|
|
24
|
+
|
|
25
|
+
### Common Patterns
|
|
26
|
+
*(None)*
|
|
27
|
+
|
|
28
|
+
## Dependencies
|
|
29
|
+
|
|
30
|
+
### Internal
|
|
31
|
+
- `src/agent/` state management.
|
|
32
|
+
|
|
33
|
+
### External
|
|
34
|
+
*(None)*
|
|
35
|
+
|
|
36
|
+
<!-- MANUAL: -->
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isDevMode } from "../state";
|
|
4
|
+
|
|
5
|
+
export async function runAdvancedAnalysis(cwd: string): Promise<string> {
|
|
6
|
+
if (!isDevMode()) throw new Error("Advanced analysis only available in Dev Mode");
|
|
7
|
+
|
|
8
|
+
console.log("[jeo-Core] Running Advanced Architectural Analysis...");
|
|
9
|
+
|
|
10
|
+
// This will eventually call an LLM to scan the repo
|
|
11
|
+
return "Advanced Analysis Result: Found tight coupling between src/ai/model-manager.ts and src/ai/providers/. Recommend implementing a Provider Registry pattern.";
|
|
12
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { runGjcCommand } from "../../commands/gjc";
|
|
2
|
+
import { runAdvancedAnalysis } from "./advanced-analyzer";
|
|
3
|
+
import { logEvolution } from "./evolution-logger";
|
|
4
|
+
|
|
5
|
+
export async function consultGjcForAdvancedEvolution(cwd: string) {
|
|
6
|
+
const report = await runAdvancedAnalysis(cwd);
|
|
7
|
+
const timestamp = new Date().toISOString();
|
|
8
|
+
|
|
9
|
+
await logEvolution({
|
|
10
|
+
timestamp,
|
|
11
|
+
target: "src/ai/model-manager.ts",
|
|
12
|
+
request: report,
|
|
13
|
+
status: "in_progress"
|
|
14
|
+
}, cwd);
|
|
15
|
+
|
|
16
|
+
console.log();
|
|
17
|
+
console.log("[jeo-Core] Architectural Debt identified: Provider Coupling.");
|
|
18
|
+
|
|
19
|
+
const request = `
|
|
20
|
+
I am jeo, the Core Engine. My Advanced Analyzer identified tight coupling between 'src/ai/model-manager.ts' and specific provider files.
|
|
21
|
+
Report: ${report}
|
|
22
|
+
|
|
23
|
+
As my implementation guide (gjc), please:
|
|
24
|
+
1. Design a 'Provider Registry' pattern in 'src/ai/provider-registry.ts'.
|
|
25
|
+
2. Provide a refactoring plan for 'src/ai/model-manager.ts' to use this registry for dynamic provider loading.
|
|
26
|
+
3. Ensure no breaking changes to the external ModelManager API.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await runGjcCommand([request]);
|
|
31
|
+
|
|
32
|
+
await logEvolution({
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
target: "src/ai/model-manager.ts",
|
|
35
|
+
request,
|
|
36
|
+
status: "success"
|
|
37
|
+
}, cwd);
|
|
38
|
+
console.log("[jeo-Core] Advanced Provider Registry refactor SUCCESSFUL.");
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
await logEvolution({
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
target: "src/ai/model-manager.ts",
|
|
43
|
+
request,
|
|
44
|
+
status: "failed",
|
|
45
|
+
verificationOutput: err.message
|
|
46
|
+
}, cwd);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function consultGjcForEvolution(cwd: string) {
|
|
52
|
+
const { runSelfAnalysis } = await import("./self-analysis");
|
|
53
|
+
const report = await runSelfAnalysis(cwd);
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
|
|
56
|
+
await logEvolution({
|
|
57
|
+
timestamp,
|
|
58
|
+
target: "src/agent/engine.ts",
|
|
59
|
+
request: report,
|
|
60
|
+
status: "in_progress"
|
|
61
|
+
}, cwd);
|
|
62
|
+
|
|
63
|
+
console.log();
|
|
64
|
+
try {
|
|
65
|
+
await runGjcCommand([report]);
|
|
66
|
+
await logEvolution({
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
target: "src/agent/engine.ts",
|
|
69
|
+
request: report,
|
|
70
|
+
status: "success"
|
|
71
|
+
}, cwd);
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
await logEvolution({
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
target: "src/agent/engine.ts",
|
|
76
|
+
request: report,
|
|
77
|
+
status: "failed",
|
|
78
|
+
verificationOutput: err.message
|
|
79
|
+
}, cwd);
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface EvolutionEntry {
|
|
5
|
+
timestamp: string;
|
|
6
|
+
target: string;
|
|
7
|
+
request: string;
|
|
8
|
+
status: "success" | "failed" | "in_progress";
|
|
9
|
+
verificationOutput?: string;
|
|
10
|
+
driftScore?: number;
|
|
11
|
+
logFile?: string;
|
|
12
|
+
stage?: "analysis" | "consultation" | "implementation" | "verification";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function logEvolution(entry: EvolutionEntry, cwd: string = process.cwd()) {
|
|
16
|
+
const logPath = path.join(cwd, "logs", "evolution-log.json");
|
|
17
|
+
await fs.mkdir(path.dirname(logPath), { recursive: true });
|
|
18
|
+
|
|
19
|
+
let logs: EvolutionEntry[] = [];
|
|
20
|
+
try {
|
|
21
|
+
const content = await fs.readFile(logPath, "utf-8");
|
|
22
|
+
logs = JSON.parse(content);
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
const existingIdx = logs.findIndex(l => l.status === "in_progress" && l.target === entry.target);
|
|
26
|
+
if (existingIdx !== -1) {
|
|
27
|
+
logs[existingIdx] = { ...logs[existingIdx], ...entry };
|
|
28
|
+
} else {
|
|
29
|
+
logs.push(entry);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await fs.writeFile(logPath, JSON.stringify(logs, null, 2), "utf-8");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function streamEvolutionLogs(executionId: string, output: string, cwd: string = process.cwd()) {
|
|
36
|
+
const logDir = path.join(cwd, "logs", "evolution");
|
|
37
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
38
|
+
const logFile = path.join(logDir, `${executionId}.log`);
|
|
39
|
+
await fs.appendFile(logFile, output + "\n", "utf-8");
|
|
40
|
+
return logFile;
|
|
41
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isDevMode } from "../state";
|
|
4
|
+
import type { PerfMetric } from "../output-util";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* jeo-Centric Analysis: jeo looks at its own performance and engine.
|
|
8
|
+
*/
|
|
9
|
+
export async function runSelfAnalysis(cwd: string): Promise<string> {
|
|
10
|
+
if (!isDevMode()) throw new Error("Self-analysis only available in Dev Mode");
|
|
11
|
+
|
|
12
|
+
const perfPath = path.join(cwd, ".jeo/state/performance-metrics.json");
|
|
13
|
+
let perfData: PerfMetric[] = [];
|
|
14
|
+
try {
|
|
15
|
+
const content = await fs.readFile(perfPath, "utf-8");
|
|
16
|
+
perfData = JSON.parse(content);
|
|
17
|
+
} catch {
|
|
18
|
+
// If no metrics yet, proceed with engine analysis
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const targetPath = path.join(cwd, "src/agent/engine.ts");
|
|
22
|
+
const content = await fs.readFile(targetPath, "utf-8");
|
|
23
|
+
|
|
24
|
+
const lineCount = content.split("\n").length;
|
|
25
|
+
const hasTooManyResponsibilities = content.includes("runAgentLoop") && content.includes("truncateToolOutput") && content.includes("spillToolResult");
|
|
26
|
+
|
|
27
|
+
let report = "Analysis of src/agent/engine.ts:\n";
|
|
28
|
+
report += "- File length: " + lineCount + " lines.\n";
|
|
29
|
+
if (lineCount > 300) report += "- Issue: The file is becoming monolithic.\n";
|
|
30
|
+
if (hasTooManyResponsibilities) report += "- Issue: runAgentLoop handles tool output truncation and spilling directly, which should be modularized.\n";
|
|
31
|
+
|
|
32
|
+
if (perfData.length > 0) {
|
|
33
|
+
const recent = perfData.slice(-50);
|
|
34
|
+
const avgDuration = recent.reduce((sum, m) => sum + (m as any).duration, 0) / recent.length;
|
|
35
|
+
const failures = recent.filter((m) => !m.success).length;
|
|
36
|
+
|
|
37
|
+
report += "\nPerformance & Error Analysis (last " + recent.length + " tools):\n";
|
|
38
|
+
report += "- Average tool duration: " + avgDuration.toFixed(2) + "ms\n";
|
|
39
|
+
report += "- Success rate: " + (((recent.length - failures) / recent.length) * 100).toFixed(1) + "%\n";
|
|
40
|
+
|
|
41
|
+
const slowTools = recent.filter((m) => (m as any).duration > 2000);
|
|
42
|
+
if (slowTools.length > 0) {
|
|
43
|
+
report += "- Notice: " + slowTools.length + " tools took > 2s. Slowest: " + slowTools.sort((a, b) => (b as any).duration - (a as any).duration)[0].tool + "\n";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const recentErrors = recent.filter(m => !m.success && (m as any).error).map(m => (m as any).error!);
|
|
47
|
+
if (recentErrors.length > 0) {
|
|
48
|
+
report += "- Top Error Patterns:\n";
|
|
49
|
+
const counts: Record<string, number> = {};
|
|
50
|
+
for (const err of recentErrors) {
|
|
51
|
+
const key = err.substring(0, 60);
|
|
52
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
53
|
+
}
|
|
54
|
+
Object.entries(counts)
|
|
55
|
+
.sort((a, b) => b[1] - a[1])
|
|
56
|
+
.slice(0, 3)
|
|
57
|
+
.forEach(([err, count]) => {
|
|
58
|
+
report += " * [" + count + "x] " + err + "...\n";
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return report;
|
|
64
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isDevMode } from "../state";
|
|
2
|
+
import { runSelfAnalysis } from "./self-analysis";
|
|
3
|
+
import { logEvolution } from "./evolution-logger";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logic for the agent to analyze its own performance and suggest improvements.
|
|
7
|
+
* This module is only active when isDevMode() is true.
|
|
8
|
+
*/
|
|
9
|
+
export async function suggestSelfImprovement(cwd: string) {
|
|
10
|
+
if (!isDevMode()) return null;
|
|
11
|
+
|
|
12
|
+
console.log("[DEV] Analyzing jeo for self-improvement...");
|
|
13
|
+
const report = await runSelfAnalysis(cwd);
|
|
14
|
+
|
|
15
|
+
await logEvolution({
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
target: "Self-Improvement Audit",
|
|
18
|
+
request: "Autonomous performance and architectural audit",
|
|
19
|
+
status: "success",
|
|
20
|
+
verificationOutput: report
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return report;
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { logEvolution } from "./evolution-logger";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Level 3: Specification Automation
|
|
7
|
+
* Syncs .specify/ artifacts to ooo seeds.
|
|
8
|
+
*/
|
|
9
|
+
export async function syncSpecificationToSeed(cwd: string) {
|
|
10
|
+
const specPath = path.join(cwd, ".specify", "specification.md");
|
|
11
|
+
const seedPath = path.join(cwd, ".ouroboros", "seeds", "generated-seed.yaml");
|
|
12
|
+
|
|
13
|
+
console.log("[jeo-Core] Syncing specification to ooo seed...");
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const spec = await fs.readFile(specPath, "utf-8");
|
|
17
|
+
|
|
18
|
+
// Simple extraction logic for demo/Phase 3
|
|
19
|
+
const goalsMatch = spec.match(/## Purpose\n([\s\S]+?)\n\n/);
|
|
20
|
+
const goals = goalsMatch ? goalsMatch[1].trim() : "Autonomous Evolution";
|
|
21
|
+
const reqsMatch = spec.match(/## Requirements\n([\s\S]+?)\n\n/);
|
|
22
|
+
const reqs = reqsMatch ? reqsMatch[1].split("\n").map(l => l.replace(/^[*-]\d+./, "").trim()).filter(Boolean) : [];
|
|
23
|
+
|
|
24
|
+
const seedContent = `
|
|
25
|
+
goal: "${goals.replace(/"/g, '\\\\\\\\"')}"
|
|
26
|
+
constraints:
|
|
27
|
+
- "Bun runtime"
|
|
28
|
+
- "Zero native dependencies"
|
|
29
|
+
acceptance_criteria:
|
|
30
|
+
${reqs.map(r => ` - "${r.replace(/"/g, '\\\\\\\\"')}"`).join("\n")}
|
|
31
|
+
- "Mechanical verification (tests) must pass"
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
await fs.mkdir(path.dirname(seedPath), { recursive: true });
|
|
35
|
+
await fs.writeFile(seedPath, seedContent.trim(), "utf-8");
|
|
36
|
+
|
|
37
|
+
await logEvolution({
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
target: ".ouroboros/seeds/generated-seed.yaml",
|
|
40
|
+
request: "Automated seed generation from .specify/specification.md",
|
|
41
|
+
status: "success"
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log("[jeo-Core] Successfully generated seed.");
|
|
45
|
+
} catch (err: any) {
|
|
46
|
+
console.error(`[jeo-Core] Failed to sync specification: ${err.message}`);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|