codeep 2.4.2 → 2.5.1
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.md +1 -0
- package/dist/acp/session.js +3 -1
- package/dist/renderer/agentExecution.js +8 -0
- package/dist/renderer/main.js +8 -0
- package/dist/utils/agent.d.ts +17 -0
- package/dist/utils/agent.js +28 -28
- package/dist/utils/diffPreview.js +3 -1
- package/dist/utils/headlessReview.d.ts +24 -0
- package/dist/utils/headlessReview.js +91 -0
- package/dist/utils/projectIntelligence.js +77 -1
- package/dist/utils/skills.js +5 -1
- package/dist/utils/toolExecution.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1050,6 +1050,7 @@ In `dangerous` mode, configure which tools require confirmation via `/settings`:
|
|
|
1050
1050
|
| `codeep account` | Link CLI to codeep.dev (GitHub OAuth) |
|
|
1051
1051
|
| `codeep account sync` | Pull API keys + personalities + commands from codeep.dev → local |
|
|
1052
1052
|
| `codeep account push` | Push local API keys + personalities + commands → codeep.dev |
|
|
1053
|
+
| `codeep review [files…]` | Offline, deterministic code review for CI — markdown or `--json`, `--fail-on <error\|warning\|info\|none>` sets the exit code. No API key needed. |
|
|
1053
1054
|
|
|
1054
1055
|
### Authentication
|
|
1055
1056
|
|
package/dist/acp/session.js
CHANGED
|
@@ -154,7 +154,9 @@ export async function runAgentSession(opts) {
|
|
|
154
154
|
});
|
|
155
155
|
// result.finalResponse is already emitted via onChunk streaming above;
|
|
156
156
|
// only emit it here if nothing was streamed (e.g. non-streaming fallback path)
|
|
157
|
-
|
|
157
|
+
// — except a paused/interrupted run, whose finalResponse is a fresh "say
|
|
158
|
+
// continue" notice that was never streamed and must always reach the client.
|
|
159
|
+
if (result.finalResponse && (chunksEmitted === 0 || result.interrupted)) {
|
|
158
160
|
opts.onChunk(result.finalResponse);
|
|
159
161
|
}
|
|
160
162
|
// Surface errors as thrown exceptions so the ACP server can handle them correctly.
|
|
@@ -343,6 +343,14 @@ export async function executeAgentTask(task, dryRun, ctx) {
|
|
|
343
343
|
else if (result.aborted) {
|
|
344
344
|
app.addMessage({ role: 'assistant', content: 'Agent stopped by user.' });
|
|
345
345
|
}
|
|
346
|
+
else if (result.interrupted) {
|
|
347
|
+
// Paused at a step/time safety limit — resumable, not a failure. Show the
|
|
348
|
+
// agent's partial summary and nudge the user to resume.
|
|
349
|
+
if (result.finalResponse) {
|
|
350
|
+
app.addMessage({ role: 'assistant', content: result.finalResponse });
|
|
351
|
+
}
|
|
352
|
+
app.notify('Paused at the safety limit — say "continue" to keep going');
|
|
353
|
+
}
|
|
346
354
|
else {
|
|
347
355
|
// Show the agent's summary if available, with error details below
|
|
348
356
|
if (result.finalResponse) {
|
package/dist/renderer/main.js
CHANGED
|
@@ -375,6 +375,13 @@ function showSessionPickerInline() {
|
|
|
375
375
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
376
376
|
async function main() {
|
|
377
377
|
const args = process.argv.slice(2);
|
|
378
|
+
// Headless, deterministic code review for CI (no API key, no TUI). Handled
|
|
379
|
+
// before the global --help/--version checks so `codeep review --help` shows
|
|
380
|
+
// the review usage rather than the top-level help.
|
|
381
|
+
if (args[0] === 'review') {
|
|
382
|
+
const { runHeadlessReview } = await import('../utils/headlessReview.js');
|
|
383
|
+
process.exit(runHeadlessReview(args.slice(1)));
|
|
384
|
+
}
|
|
378
385
|
if (args.includes('--version') || args.includes('-v')) {
|
|
379
386
|
console.log(`Codeep v${getCurrentVersion()}`);
|
|
380
387
|
process.exit(0);
|
|
@@ -389,6 +396,7 @@ Usage:
|
|
|
389
396
|
codeep account sync Pull keys + personalities + commands + profile from codeep.dev
|
|
390
397
|
codeep account push Push local keys + personalities + commands + profile to codeep.dev
|
|
391
398
|
codeep acp Start ACP server (for Zed editor integration)
|
|
399
|
+
codeep review Offline code review for CI (--json, --fail-on <level>)
|
|
392
400
|
codeep --version Show version
|
|
393
401
|
codeep --help Show this help
|
|
394
402
|
|
package/dist/utils/agent.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export interface AgentOptions {
|
|
|
71
71
|
/** Role system-prompt addendum injected for a delegated sub-agent. */
|
|
72
72
|
roleAddendum?: string;
|
|
73
73
|
}
|
|
74
|
+
/** Why a run stopped early at a safety limit — both are resumable, not errors. */
|
|
75
|
+
export type InterruptKind = 'iteration_limit' | 'time_limit';
|
|
74
76
|
export interface AgentResult {
|
|
75
77
|
success: boolean;
|
|
76
78
|
iterations: number;
|
|
@@ -78,7 +80,22 @@ export interface AgentResult {
|
|
|
78
80
|
finalResponse: string;
|
|
79
81
|
error?: string;
|
|
80
82
|
aborted?: boolean;
|
|
83
|
+
/** Set when the run paused at a step/time safety limit. The caller can offer
|
|
84
|
+
* a "continue" affordance instead of treating it as a failure. */
|
|
85
|
+
interrupted?: InterruptKind;
|
|
81
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the result for a run that paused at a safety limit. Pausing is a normal,
|
|
89
|
+
* resumable state — not an error — so the summary tells the user how to resume.
|
|
90
|
+
* Shared by both limit checks in the loop so the wording + `interrupted` signal
|
|
91
|
+
* stay in sync.
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildPausedResult(kind: InterruptKind, ctx: {
|
|
94
|
+
iterations: number;
|
|
95
|
+
actions: ActionLog[];
|
|
96
|
+
maxIterations?: number;
|
|
97
|
+
durationMin?: number;
|
|
98
|
+
}): AgentResult;
|
|
82
99
|
/**
|
|
83
100
|
* Run the agent loop
|
|
84
101
|
*/
|
package/dist/utils/agent.js
CHANGED
|
@@ -103,6 +103,32 @@ function compressMessages(messages, actions) {
|
|
|
103
103
|
debug(`Context compressed: ${totalChars} chars → keeping first + summary + last ${keep} messages`);
|
|
104
104
|
return [firstMessage, summaryMessage, ...recentMessages];
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the result for a run that paused at a safety limit. Pausing is a normal,
|
|
108
|
+
* resumable state — not an error — so the summary tells the user how to resume.
|
|
109
|
+
* Shared by both limit checks in the loop so the wording + `interrupted` signal
|
|
110
|
+
* stay in sync.
|
|
111
|
+
*/
|
|
112
|
+
export function buildPausedResult(kind, ctx) {
|
|
113
|
+
const editedFiles = [...new Set(ctx.actions.filter(a => a.type === 'write' || a.type === 'edit').map(a => a.target))];
|
|
114
|
+
const head = kind === 'time_limit'
|
|
115
|
+
? `⏸ Paused after the ${ctx.durationMin}-minute time limit.`
|
|
116
|
+
: `⏸ Paused after ${ctx.maxIterations} tool steps (the safety limit).`;
|
|
117
|
+
const lines = [head, '', 'This is a safety limit, not an error — say **continue** to pick up where it left off.'];
|
|
118
|
+
if (editedFiles.length > 0) {
|
|
119
|
+
lines.push('', '**Progress so far — files written/edited:**', ...editedFiles.map(f => ` ✓ \`${f}\``));
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
iterations: ctx.iterations,
|
|
124
|
+
actions: ctx.actions,
|
|
125
|
+
finalResponse: lines.join('\n'),
|
|
126
|
+
error: kind === 'time_limit'
|
|
127
|
+
? `Exceeded maximum duration of ${ctx.durationMin} min`
|
|
128
|
+
: `Exceeded maximum of ${ctx.maxIterations} iterations`,
|
|
129
|
+
interrupted: kind,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
106
132
|
const DEFAULT_OPTIONS = {
|
|
107
133
|
// Modern models (GLM-5.1, Claude 4.5, GPT-4.1) complete typical coding tasks in
|
|
108
134
|
// 3–8 iterations. The old cap of 100 mostly let broken loops wander for minutes
|
|
@@ -452,21 +478,8 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
452
478
|
while (iteration < opts.maxIterations) {
|
|
453
479
|
// Check timeout
|
|
454
480
|
if (Date.now() - startTime > opts.maxDuration) {
|
|
455
|
-
const filesDone = actions.filter(a => a.type === 'write' || a.type === 'edit').map(a => a.target);
|
|
456
481
|
const durationMin = Math.round(opts.maxDuration / 60000);
|
|
457
|
-
|
|
458
|
-
if (filesDone.length > 0) {
|
|
459
|
-
partialLines.push(`\n**Partial progress — files written/edited:**`);
|
|
460
|
-
[...new Set(filesDone)].forEach(f => partialLines.push(` ✓ \`${f}\``));
|
|
461
|
-
partialLines.push(`\nYou can continue by running the agent again.`);
|
|
462
|
-
}
|
|
463
|
-
result = {
|
|
464
|
-
success: false,
|
|
465
|
-
iterations: iteration,
|
|
466
|
-
actions,
|
|
467
|
-
finalResponse: partialLines.join('\n'),
|
|
468
|
-
error: `Exceeded maximum duration of ${durationMin} min`,
|
|
469
|
-
};
|
|
482
|
+
result = buildPausedResult('time_limit', { iterations: iteration, actions, durationMin });
|
|
470
483
|
if (!opts.nested)
|
|
471
484
|
writeProgressLog(projectContext.root || '', prompt, result, projectContext.name);
|
|
472
485
|
return result;
|
|
@@ -894,20 +907,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
894
907
|
}
|
|
895
908
|
// Check if we hit max iterations — build partial summary from actions log
|
|
896
909
|
if (iteration >= opts.maxIterations && !finalResponse) {
|
|
897
|
-
|
|
898
|
-
const partialLines = [`Agent reached the iteration limit (${opts.maxIterations} steps).`];
|
|
899
|
-
if (filesDone.length > 0) {
|
|
900
|
-
partialLines.push(`\n**Partial progress — files written/edited:**`);
|
|
901
|
-
[...new Set(filesDone)].forEach(f => partialLines.push(` ✓ \`${f}\``));
|
|
902
|
-
partialLines.push(`\nThe task may be incomplete. You can continue by running the agent again.`);
|
|
903
|
-
}
|
|
904
|
-
result = {
|
|
905
|
-
success: false,
|
|
906
|
-
iterations: iteration,
|
|
907
|
-
actions,
|
|
908
|
-
finalResponse: partialLines.join('\n'),
|
|
909
|
-
error: `Exceeded maximum of ${opts.maxIterations} iterations`,
|
|
910
|
-
};
|
|
910
|
+
result = buildPausedResult('iteration_limit', { iterations: iteration, actions, maxIterations: opts.maxIterations });
|
|
911
911
|
if (!opts.nested)
|
|
912
912
|
writeProgressLog(projectContext.root || '', prompt, result, projectContext.name);
|
|
913
913
|
return result;
|
|
@@ -283,7 +283,9 @@ export function createEditDiff(path, oldText, newText, projectRoot) {
|
|
|
283
283
|
if (!content.includes(oldText)) {
|
|
284
284
|
return null;
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
// Literal replacement ($-safe): a plain-string replace would interpret
|
|
287
|
+
// $&, $1, $$ in newText and mis-render the diff.
|
|
288
|
+
const newContent = content.replace(oldText, () => newText);
|
|
287
289
|
const hunks = generateDiff(content, newContent);
|
|
288
290
|
return {
|
|
289
291
|
path,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ReviewResult } from './codeReview.js';
|
|
2
|
+
export type FailOn = 'error' | 'warning' | 'info' | 'none';
|
|
3
|
+
export interface ReviewArgs {
|
|
4
|
+
files: string[];
|
|
5
|
+
json: boolean;
|
|
6
|
+
failOn: FailOn;
|
|
7
|
+
help: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const REVIEW_HELP = "Usage: codeep review [options] [files...]\n\nRun a deterministic, offline code review (no API key required). With no files,\nreviews your unstaged git changes, falling back to a src/ scan when the tree is\nclean. Pass files (or let your CI pass the PR's changed files) to scope it.\n\nOptions:\n --json Print the result as JSON instead of the markdown report\n --fail-on <level> Exit non-zero when an issue at or above <level> is found:\n error | warning | info | none (default: error)\n -h, --help Show this help\n\nExit code: 0 when nothing at/above --fail-on is found, 1 otherwise.";
|
|
10
|
+
/** Parse `codeep review` argv (everything after the subcommand). Pure. */
|
|
11
|
+
export declare function parseReviewArgs(argv: string[]): ReviewArgs;
|
|
12
|
+
/** Exit code for a result under a fail-on threshold. Pure. */
|
|
13
|
+
export declare function exitCodeForResult(result: ReviewResult, failOn: FailOn): number;
|
|
14
|
+
export interface ReviewDeps {
|
|
15
|
+
/** Run the review over optional specific files. */
|
|
16
|
+
review: (files?: string[]) => ReviewResult;
|
|
17
|
+
/** Sink for the report (one call). */
|
|
18
|
+
write: (text: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Orchestrate a headless review and return the process exit code. Side effects
|
|
22
|
+
* (filesystem, stdout) live behind `deps` so the flow is unit-testable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runHeadlessReview(argv: string[], deps?: ReviewDeps): number;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Headless `codeep review` — a non-interactive entry point around the
|
|
2
|
+
// deterministic reviewer in codeReview.ts. No API key, no TUI: it scans, prints
|
|
3
|
+
// a report (markdown or JSON), and exits non-zero when issues at/above a chosen
|
|
4
|
+
// severity are found, so it drops cleanly into CI (e.g. a GitHub Action).
|
|
5
|
+
import { performCodeReview, formatReviewResult } from './codeReview.js';
|
|
6
|
+
const FAIL_ON_VALUES = ['error', 'warning', 'info', 'none'];
|
|
7
|
+
// Higher = more severe. `suggestion` sits below `info` so `--fail-on info`
|
|
8
|
+
// never trips on a mere suggestion.
|
|
9
|
+
const SEVERITY_RANK = { suggestion: 0, info: 1, warning: 2, error: 3 };
|
|
10
|
+
export const REVIEW_HELP = `Usage: codeep review [options] [files...]
|
|
11
|
+
|
|
12
|
+
Run a deterministic, offline code review (no API key required). With no files,
|
|
13
|
+
reviews your unstaged git changes, falling back to a src/ scan when the tree is
|
|
14
|
+
clean. Pass files (or let your CI pass the PR's changed files) to scope it.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--json Print the result as JSON instead of the markdown report
|
|
18
|
+
--fail-on <level> Exit non-zero when an issue at or above <level> is found:
|
|
19
|
+
error | warning | info | none (default: error)
|
|
20
|
+
-h, --help Show this help
|
|
21
|
+
|
|
22
|
+
Exit code: 0 when nothing at/above --fail-on is found, 1 otherwise.`;
|
|
23
|
+
/** Parse `codeep review` argv (everything after the subcommand). Pure. */
|
|
24
|
+
export function parseReviewArgs(argv) {
|
|
25
|
+
const out = { files: [], json: false, failOn: 'error', help: false };
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const arg = argv[i];
|
|
28
|
+
if (arg === '--json') {
|
|
29
|
+
out.json = true;
|
|
30
|
+
}
|
|
31
|
+
else if (arg === '-h' || arg === '--help') {
|
|
32
|
+
out.help = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '--fail-on') {
|
|
35
|
+
const v = argv[++i];
|
|
36
|
+
if (FAIL_ON_VALUES.includes(v))
|
|
37
|
+
out.failOn = v;
|
|
38
|
+
}
|
|
39
|
+
else if (arg.startsWith('--fail-on=')) {
|
|
40
|
+
const v = arg.slice('--fail-on='.length);
|
|
41
|
+
if (FAIL_ON_VALUES.includes(v))
|
|
42
|
+
out.failOn = v;
|
|
43
|
+
}
|
|
44
|
+
else if (!arg.startsWith('-')) {
|
|
45
|
+
out.files.push(arg);
|
|
46
|
+
}
|
|
47
|
+
// Unknown flags are ignored so a future flag doesn't hard-fail old clients.
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/** Exit code for a result under a fail-on threshold. Pure. */
|
|
52
|
+
export function exitCodeForResult(result, failOn) {
|
|
53
|
+
if (failOn === 'none')
|
|
54
|
+
return 0;
|
|
55
|
+
const threshold = SEVERITY_RANK[failOn];
|
|
56
|
+
const tripped = result.issues.some((i) => (SEVERITY_RANK[i.severity] ?? 0) >= threshold);
|
|
57
|
+
return tripped ? 1 : 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Orchestrate a headless review and return the process exit code. Side effects
|
|
61
|
+
* (filesystem, stdout) live behind `deps` so the flow is unit-testable.
|
|
62
|
+
*/
|
|
63
|
+
export function runHeadlessReview(argv, deps = defaultDeps()) {
|
|
64
|
+
const args = parseReviewArgs(argv);
|
|
65
|
+
if (args.help) {
|
|
66
|
+
deps.write(REVIEW_HELP);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
const result = deps.review(args.files.length ? args.files : undefined);
|
|
70
|
+
deps.write(args.json ? JSON.stringify(result, null, 2) : formatReviewResult(result));
|
|
71
|
+
return exitCodeForResult(result, args.failOn);
|
|
72
|
+
}
|
|
73
|
+
// Only the reviewer's `.root` is read, so a minimal context rooted at cwd is
|
|
74
|
+
// enough — no need for a full (slower) project scan just to lint.
|
|
75
|
+
function minimalContext(root) {
|
|
76
|
+
return {
|
|
77
|
+
root,
|
|
78
|
+
name: root.split('/').pop() ?? 'workspace',
|
|
79
|
+
type: 'Unknown',
|
|
80
|
+
structure: '',
|
|
81
|
+
keyFiles: [],
|
|
82
|
+
fileCount: 0,
|
|
83
|
+
summary: `Workspace at ${root}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function defaultDeps() {
|
|
87
|
+
return {
|
|
88
|
+
review: (files) => performCodeReview(minimalContext(process.cwd()), files),
|
|
89
|
+
write: (text) => process.stdout.write(text + '\n'),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -126,6 +126,74 @@ export function saveProjectIntelligence(projectPath, intelligence) {
|
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Complete default intelligence skeleton — every nested section is present so
|
|
131
|
+
* consumers (e.g. generateContextFromIntelligence) never dereference an
|
|
132
|
+
* undefined section. Used as the merge base when normalizing a loaded file.
|
|
133
|
+
*/
|
|
134
|
+
function createBaseIntelligence(projectPath) {
|
|
135
|
+
return {
|
|
136
|
+
version: INTELLIGENCE_VERSION,
|
|
137
|
+
scannedAt: new Date().toISOString(),
|
|
138
|
+
projectPath,
|
|
139
|
+
name: basename(projectPath),
|
|
140
|
+
type: 'Unknown',
|
|
141
|
+
description: '',
|
|
142
|
+
structure: {
|
|
143
|
+
totalFiles: 0,
|
|
144
|
+
totalDirectories: 0,
|
|
145
|
+
languages: {},
|
|
146
|
+
topDirectories: [],
|
|
147
|
+
},
|
|
148
|
+
dependencies: {
|
|
149
|
+
runtime: [],
|
|
150
|
+
dev: [],
|
|
151
|
+
frameworks: [],
|
|
152
|
+
},
|
|
153
|
+
keyFiles: [],
|
|
154
|
+
entryPoints: [],
|
|
155
|
+
scripts: {},
|
|
156
|
+
architecture: {
|
|
157
|
+
patterns: [],
|
|
158
|
+
mainModules: [],
|
|
159
|
+
ciSystem: null,
|
|
160
|
+
containerization: [],
|
|
161
|
+
monorepoTool: null,
|
|
162
|
+
},
|
|
163
|
+
conventions: {
|
|
164
|
+
indentation: 'spaces',
|
|
165
|
+
quotes: 'single',
|
|
166
|
+
semicolons: true,
|
|
167
|
+
namingStyle: 'camelCase',
|
|
168
|
+
},
|
|
169
|
+
testing: {
|
|
170
|
+
framework: null,
|
|
171
|
+
testDirectory: null,
|
|
172
|
+
hasTests: false,
|
|
173
|
+
},
|
|
174
|
+
notes: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Merge a loaded (possibly partial or older-schema) intelligence object over
|
|
179
|
+
* the complete default skeleton so every nested section is guaranteed present.
|
|
180
|
+
* An interrupted scan or an older CLI can write a file that is missing whole
|
|
181
|
+
* sections (e.g. `conventions`); without this, reading `conventions.indentation`
|
|
182
|
+
* downstream throws "Cannot read properties of undefined". Nested objects are
|
|
183
|
+
* merged per-section so partial sub-objects are also backfilled.
|
|
184
|
+
*/
|
|
185
|
+
function normalizeIntelligence(data, projectPath) {
|
|
186
|
+
const base = createBaseIntelligence(projectPath);
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
...data,
|
|
190
|
+
structure: { ...base.structure, ...data.structure },
|
|
191
|
+
dependencies: { ...base.dependencies, ...data.dependencies },
|
|
192
|
+
architecture: { ...base.architecture, ...data.architecture },
|
|
193
|
+
conventions: { ...base.conventions, ...data.conventions },
|
|
194
|
+
testing: { ...base.testing, ...data.testing },
|
|
195
|
+
};
|
|
196
|
+
}
|
|
129
197
|
/**
|
|
130
198
|
* Load intelligence from .codeep/intelligence.json
|
|
131
199
|
*/
|
|
@@ -135,9 +203,13 @@ export function loadProjectIntelligence(projectPath) {
|
|
|
135
203
|
if (!existsSync(filePath))
|
|
136
204
|
return null;
|
|
137
205
|
const data = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
206
|
+
if (!data || typeof data !== 'object')
|
|
207
|
+
return null;
|
|
138
208
|
if (data.version !== INTELLIGENCE_VERSION)
|
|
139
209
|
return null;
|
|
140
|
-
|
|
210
|
+
// Backfill any missing sections so a partial/interrupted file can't crash
|
|
211
|
+
// downstream consumers that assume a complete shape.
|
|
212
|
+
return normalizeIntelligence(data, projectPath);
|
|
141
213
|
}
|
|
142
214
|
catch {
|
|
143
215
|
return null;
|
|
@@ -159,6 +231,10 @@ export function isIntelligenceFresh(projectPath, maxAgeHours = 24) {
|
|
|
159
231
|
* Generate AI-friendly context from intelligence
|
|
160
232
|
*/
|
|
161
233
|
export function generateContextFromIntelligence(intelligence) {
|
|
234
|
+
// Defensive: backfill any missing sections so a partial object (e.g. from an
|
|
235
|
+
// older/interrupted scan, or an external SDK caller) can't crash the
|
|
236
|
+
// formatter on a nested dereference like `conventions.indentation`.
|
|
237
|
+
intelligence = normalizeIntelligence((intelligence ?? {}), intelligence?.projectPath ?? '');
|
|
162
238
|
const lines = [];
|
|
163
239
|
lines.push(`# Project: ${intelligence.name}`);
|
|
164
240
|
lines.push(`Type: ${intelligence.type}`);
|
package/dist/utils/skills.js
CHANGED
|
@@ -741,7 +741,11 @@ function sanitizeForShell(text) {
|
|
|
741
741
|
export function interpolateParams(content, params) {
|
|
742
742
|
let result = content;
|
|
743
743
|
for (const [key, value] of Object.entries(params)) {
|
|
744
|
-
|
|
744
|
+
// Literal find/replace-all via split+join. Avoids two bugs of the old
|
|
745
|
+
// regex form: a regex-meta char in `key` (e.g. `.`, `(`) breaking the match
|
|
746
|
+
// or throwing, and `$`-sequences in `value` ($&, $$, $1) being interpreted
|
|
747
|
+
// as replacement patterns and corrupting the output.
|
|
748
|
+
result = result.split('${' + key + '}').join(value);
|
|
745
749
|
}
|
|
746
750
|
return result;
|
|
747
751
|
}
|
|
@@ -456,7 +456,10 @@ async function dispatchTool(tool, parameters, projectRoot, fs, toolCall) {
|
|
|
456
456
|
return { success: false, output: '', error: `old_text matches ${matchCount} locations in the file. Provide more surrounding context to make it unique (only 1 match allowed).`, tool, parameters };
|
|
457
457
|
}
|
|
458
458
|
recordEdit(validation.absolutePath);
|
|
459
|
-
|
|
459
|
+
// Function replacer so newText is written literally — a plain-string
|
|
460
|
+
// replacement interprets $&, $1, $$ etc., which silently corrupts any
|
|
461
|
+
// edit whose new_text contains `$` (shell vars, template literals, regex).
|
|
462
|
+
const updated = content.replace(oldText, () => newText);
|
|
460
463
|
if (fs?.writeTextFile) {
|
|
461
464
|
try {
|
|
462
465
|
await fs.writeTextFile(validation.absolutePath, updated);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|