opencode-ultra 0.3.0 → 0.4.0
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/dist/config.d.ts +13 -0
- package/dist/hooks/fragment-injector.d.ts +9 -0
- package/dist/hooks/prompt-renderer.d.ts +13 -0
- package/dist/index.js +370 -15
- package/dist/tools/ast-search.d.ts +3 -0
- package/dist/tools/batch-read.d.ts +2 -0
- package/dist/tools/continuity-ledger.d.ts +4 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -34,6 +34,19 @@ declare const PluginConfigSchema: z.ZodObject<{
|
|
|
34
34
|
model: z.ZodOptional<z.ZodString>;
|
|
35
35
|
variant: z.ZodOptional<z.ZodString>;
|
|
36
36
|
}, z.core.$loose>>>;
|
|
37
|
+
fragments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
38
|
+
prompt_renderer: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
40
|
+
markdown: "markdown";
|
|
41
|
+
xml: "xml";
|
|
42
|
+
json: "json";
|
|
43
|
+
}>>;
|
|
44
|
+
model_overrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
45
|
+
markdown: "markdown";
|
|
46
|
+
xml: "xml";
|
|
47
|
+
json: "json";
|
|
48
|
+
}>>>;
|
|
49
|
+
}, z.core.$strip>>;
|
|
37
50
|
disabled_agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
51
|
disabled_hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
52
|
disabled_tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type FragmentsConfig = Record<string, string[]>;
|
|
2
|
+
export declare function createFragmentInjector(ctx: {
|
|
3
|
+
directory: string;
|
|
4
|
+
}, internalSessions: Set<string>, fragments?: FragmentsConfig): (input: {
|
|
5
|
+
sessionID?: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
}, output: {
|
|
8
|
+
system: string[];
|
|
9
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type PromptFormat = "markdown" | "xml" | "json";
|
|
2
|
+
export type PromptRendererConfig = {
|
|
3
|
+
default: PromptFormat;
|
|
4
|
+
model_overrides: Record<string, PromptFormat>;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolvePromptFormat(inputModel: any, config?: Partial<PromptRendererConfig>): PromptFormat;
|
|
7
|
+
export declare function formatSystemSection(format: PromptFormat, content: string): string;
|
|
8
|
+
export declare function createPromptRendererHook(internalSessions: Set<string>, config?: Partial<PromptRendererConfig>): (input: {
|
|
9
|
+
sessionID?: string;
|
|
10
|
+
model?: unknown;
|
|
11
|
+
}, output: {
|
|
12
|
+
system: string[];
|
|
13
|
+
}) => void;
|
package/dist/index.js
CHANGED
|
@@ -14396,6 +14396,11 @@ var CategoryConfigSchema = exports_external.object({
|
|
|
14396
14396
|
var PluginConfigSchema = exports_external.object({
|
|
14397
14397
|
agents: AgentOverridesSchema.optional(),
|
|
14398
14398
|
categories: exports_external.record(exports_external.string(), CategoryConfigSchema).optional(),
|
|
14399
|
+
fragments: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).optional(),
|
|
14400
|
+
prompt_renderer: exports_external.object({
|
|
14401
|
+
default: exports_external.enum(["markdown", "xml", "json"]).optional(),
|
|
14402
|
+
model_overrides: exports_external.record(exports_external.string(), exports_external.enum(["markdown", "xml", "json"])).optional()
|
|
14403
|
+
}).optional(),
|
|
14399
14404
|
disabled_agents: exports_external.array(exports_external.string()).optional(),
|
|
14400
14405
|
disabled_hooks: exports_external.array(exports_external.string()).optional(),
|
|
14401
14406
|
disabled_tools: exports_external.array(exports_external.string()).optional(),
|
|
@@ -14874,6 +14879,94 @@ function loadCodeStyle(projectDir) {
|
|
|
14874
14879
|
return hit.content;
|
|
14875
14880
|
}
|
|
14876
14881
|
|
|
14882
|
+
// src/hooks/fragment-injector.ts
|
|
14883
|
+
import * as fs3 from "fs";
|
|
14884
|
+
import * as path3 from "path";
|
|
14885
|
+
var cache2 = new Map;
|
|
14886
|
+
async function readCached(absPath) {
|
|
14887
|
+
try {
|
|
14888
|
+
const st = await fs3.promises.stat(absPath);
|
|
14889
|
+
if (!st.isFile())
|
|
14890
|
+
return null;
|
|
14891
|
+
const prev = cache2.get(absPath);
|
|
14892
|
+
if (prev && prev.mtimeMs === st.mtimeMs)
|
|
14893
|
+
return prev.content;
|
|
14894
|
+
const content = await fs3.promises.readFile(absPath, "utf-8");
|
|
14895
|
+
cache2.set(absPath, { mtimeMs: st.mtimeMs, content });
|
|
14896
|
+
return content;
|
|
14897
|
+
} catch (err) {
|
|
14898
|
+
if (err?.code !== "ENOENT") {
|
|
14899
|
+
log("fragment read failed", { path: absPath, error: String(err) });
|
|
14900
|
+
}
|
|
14901
|
+
return null;
|
|
14902
|
+
}
|
|
14903
|
+
}
|
|
14904
|
+
function createFragmentInjector(ctx, internalSessions, fragments) {
|
|
14905
|
+
return async (input, output) => {
|
|
14906
|
+
if (input.sessionID && internalSessions.has(input.sessionID))
|
|
14907
|
+
return;
|
|
14908
|
+
const agent = input.agent;
|
|
14909
|
+
if (!agent || !fragments)
|
|
14910
|
+
return;
|
|
14911
|
+
const fragmentPaths = fragments[agent];
|
|
14912
|
+
if (!Array.isArray(fragmentPaths) || fragmentPaths.length === 0)
|
|
14913
|
+
return;
|
|
14914
|
+
for (const rel of fragmentPaths) {
|
|
14915
|
+
const abs = path3.resolve(ctx.directory, rel);
|
|
14916
|
+
const content = await readCached(abs);
|
|
14917
|
+
if (content === null) {
|
|
14918
|
+
log("fragment file not found; skipping", { agent, path: rel });
|
|
14919
|
+
continue;
|
|
14920
|
+
}
|
|
14921
|
+
output.system.push(content);
|
|
14922
|
+
}
|
|
14923
|
+
};
|
|
14924
|
+
}
|
|
14925
|
+
|
|
14926
|
+
// src/hooks/prompt-renderer.ts
|
|
14927
|
+
function escapeXml(s) {
|
|
14928
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """).replace(/'/g, "'");
|
|
14929
|
+
}
|
|
14930
|
+
function resolvePromptFormat(inputModel, config2) {
|
|
14931
|
+
const def = config2?.default ?? "markdown";
|
|
14932
|
+
const overrides = config2?.model_overrides ?? {};
|
|
14933
|
+
const providerID = inputModel?.providerID;
|
|
14934
|
+
const modelID = inputModel?.modelID;
|
|
14935
|
+
const key = typeof providerID === "string" && typeof modelID === "string" ? `${providerID}/${modelID}` : undefined;
|
|
14936
|
+
if (key && overrides[key])
|
|
14937
|
+
return overrides[key];
|
|
14938
|
+
if (typeof modelID === "string" && overrides[modelID])
|
|
14939
|
+
return overrides[modelID];
|
|
14940
|
+
return def;
|
|
14941
|
+
}
|
|
14942
|
+
function formatSystemSection(format2, content) {
|
|
14943
|
+
if (format2 === "markdown")
|
|
14944
|
+
return content;
|
|
14945
|
+
if (format2 === "xml") {
|
|
14946
|
+
return `<section name="Rules">${escapeXml(content)}</section>`;
|
|
14947
|
+
}
|
|
14948
|
+
if (format2 === "json") {
|
|
14949
|
+
return JSON.stringify({ section: "Rules", content });
|
|
14950
|
+
}
|
|
14951
|
+
return content;
|
|
14952
|
+
}
|
|
14953
|
+
function createPromptRendererHook(internalSessions, config2) {
|
|
14954
|
+
return (input, output) => {
|
|
14955
|
+
if (input.sessionID && internalSessions.has(input.sessionID))
|
|
14956
|
+
return;
|
|
14957
|
+
if (!Array.isArray(output.system) || output.system.length === 0)
|
|
14958
|
+
return;
|
|
14959
|
+
const format2 = resolvePromptFormat(input.model, config2);
|
|
14960
|
+
if (format2 === "markdown")
|
|
14961
|
+
return;
|
|
14962
|
+
try {
|
|
14963
|
+
output.system = output.system.map((s) => formatSystemSection(format2, s));
|
|
14964
|
+
} catch (err) {
|
|
14965
|
+
log("prompt renderer failed", { error: String(err) });
|
|
14966
|
+
}
|
|
14967
|
+
};
|
|
14968
|
+
}
|
|
14969
|
+
|
|
14877
14970
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
14878
14971
|
var exports_external2 = {};
|
|
14879
14972
|
__export(exports_external2, {
|
|
@@ -15603,15 +15696,15 @@ function mergeDefs2(...defs) {
|
|
|
15603
15696
|
function cloneDef2(schema) {
|
|
15604
15697
|
return mergeDefs2(schema._zod.def);
|
|
15605
15698
|
}
|
|
15606
|
-
function getElementAtPath2(obj,
|
|
15607
|
-
if (!
|
|
15699
|
+
function getElementAtPath2(obj, path4) {
|
|
15700
|
+
if (!path4)
|
|
15608
15701
|
return obj;
|
|
15609
|
-
return
|
|
15702
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
15610
15703
|
}
|
|
15611
15704
|
function promiseAllObject2(promisesObj) {
|
|
15612
15705
|
const keys = Object.keys(promisesObj);
|
|
15613
|
-
const
|
|
15614
|
-
return Promise.all(
|
|
15706
|
+
const promises2 = keys.map((key) => promisesObj[key]);
|
|
15707
|
+
return Promise.all(promises2).then((results) => {
|
|
15615
15708
|
const resolvedObj = {};
|
|
15616
15709
|
for (let i = 0;i < keys.length; i++) {
|
|
15617
15710
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -15965,11 +16058,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
15965
16058
|
}
|
|
15966
16059
|
return false;
|
|
15967
16060
|
}
|
|
15968
|
-
function prefixIssues2(
|
|
16061
|
+
function prefixIssues2(path4, issues) {
|
|
15969
16062
|
return issues.map((iss) => {
|
|
15970
16063
|
var _a2;
|
|
15971
16064
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
15972
|
-
iss.path.unshift(
|
|
16065
|
+
iss.path.unshift(path4);
|
|
15973
16066
|
return iss;
|
|
15974
16067
|
});
|
|
15975
16068
|
}
|
|
@@ -16137,7 +16230,7 @@ function treeifyError2(error48, _mapper) {
|
|
|
16137
16230
|
return issue3.message;
|
|
16138
16231
|
};
|
|
16139
16232
|
const result = { errors: [] };
|
|
16140
|
-
const processError = (error49,
|
|
16233
|
+
const processError = (error49, path4 = []) => {
|
|
16141
16234
|
var _a2, _b;
|
|
16142
16235
|
for (const issue3 of error49.issues) {
|
|
16143
16236
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -16147,7 +16240,7 @@ function treeifyError2(error48, _mapper) {
|
|
|
16147
16240
|
} else if (issue3.code === "invalid_element") {
|
|
16148
16241
|
processError({ issues: issue3.issues }, issue3.path);
|
|
16149
16242
|
} else {
|
|
16150
|
-
const fullpath = [...
|
|
16243
|
+
const fullpath = [...path4, ...issue3.path];
|
|
16151
16244
|
if (fullpath.length === 0) {
|
|
16152
16245
|
result.errors.push(mapper(issue3));
|
|
16153
16246
|
continue;
|
|
@@ -16179,8 +16272,8 @@ function treeifyError2(error48, _mapper) {
|
|
|
16179
16272
|
}
|
|
16180
16273
|
function toDotPath2(_path) {
|
|
16181
16274
|
const segs = [];
|
|
16182
|
-
const
|
|
16183
|
-
for (const seg of
|
|
16275
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16276
|
+
for (const seg of path4) {
|
|
16184
16277
|
if (typeof seg === "number")
|
|
16185
16278
|
segs.push(`[${seg}]`);
|
|
16186
16279
|
else if (typeof seg === "symbol")
|
|
@@ -27519,6 +27612,243 @@ Original task:
|
|
|
27519
27612
|
${original}`;
|
|
27520
27613
|
}
|
|
27521
27614
|
|
|
27615
|
+
// src/tools/batch-read.ts
|
|
27616
|
+
import * as fs4 from "fs";
|
|
27617
|
+
import * as path4 from "path";
|
|
27618
|
+
var MAX_PATHS = 20;
|
|
27619
|
+
function resolvePath(projectDir, inputPath) {
|
|
27620
|
+
return path4.isAbsolute(inputPath) ? inputPath : path4.join(projectDir, inputPath);
|
|
27621
|
+
}
|
|
27622
|
+
function isProbablyBinary(buf) {
|
|
27623
|
+
const sample = buf.subarray(0, Math.min(buf.length, 8192));
|
|
27624
|
+
for (const b of sample) {
|
|
27625
|
+
if (b === 0)
|
|
27626
|
+
return true;
|
|
27627
|
+
}
|
|
27628
|
+
return false;
|
|
27629
|
+
}
|
|
27630
|
+
async function readOne(projectDir, inputPath) {
|
|
27631
|
+
const abs = resolvePath(projectDir, inputPath);
|
|
27632
|
+
try {
|
|
27633
|
+
const stat = await fs4.promises.stat(abs);
|
|
27634
|
+
if (!stat.isFile()) {
|
|
27635
|
+
return `=== ${inputPath} ===
|
|
27636
|
+
[File not found]
|
|
27637
|
+
`;
|
|
27638
|
+
}
|
|
27639
|
+
const buf = await fs4.promises.readFile(abs);
|
|
27640
|
+
if (isProbablyBinary(buf)) {
|
|
27641
|
+
return `=== ${inputPath} ===
|
|
27642
|
+
[Binary file]
|
|
27643
|
+
`;
|
|
27644
|
+
}
|
|
27645
|
+
const content = buf.toString("utf-8");
|
|
27646
|
+
return `=== ${inputPath} ===
|
|
27647
|
+
${content}
|
|
27648
|
+
`;
|
|
27649
|
+
} catch (err) {
|
|
27650
|
+
if (err?.code === "ENOENT") {
|
|
27651
|
+
return `=== ${inputPath} ===
|
|
27652
|
+
[File not found]
|
|
27653
|
+
`;
|
|
27654
|
+
}
|
|
27655
|
+
log("batch_read failed to read file", { path: abs, error: String(err) });
|
|
27656
|
+
return `=== ${inputPath} ===
|
|
27657
|
+
[File not found]
|
|
27658
|
+
`;
|
|
27659
|
+
}
|
|
27660
|
+
}
|
|
27661
|
+
function createBatchReadTool(ctx) {
|
|
27662
|
+
return tool({
|
|
27663
|
+
description: "Read multiple files in parallel and concatenate contents.",
|
|
27664
|
+
args: {
|
|
27665
|
+
paths: tool.schema.array(tool.schema.string()).describe("File paths (max 20).")
|
|
27666
|
+
},
|
|
27667
|
+
execute: async ({ paths }) => {
|
|
27668
|
+
if (!Array.isArray(paths)) {
|
|
27669
|
+
return "Error: paths must be an array of strings";
|
|
27670
|
+
}
|
|
27671
|
+
if (paths.length > MAX_PATHS) {
|
|
27672
|
+
return `Error: maximum ${MAX_PATHS} paths allowed`;
|
|
27673
|
+
}
|
|
27674
|
+
const results = await Promise.all(paths.map((p) => readOne(ctx.directory, p)));
|
|
27675
|
+
return results.join("");
|
|
27676
|
+
}
|
|
27677
|
+
});
|
|
27678
|
+
}
|
|
27679
|
+
|
|
27680
|
+
// src/tools/continuity-ledger.ts
|
|
27681
|
+
import * as fs5 from "fs";
|
|
27682
|
+
import * as path5 from "path";
|
|
27683
|
+
function normalizeLedgerName(name) {
|
|
27684
|
+
const trimmed = (name ?? "").trim().replace(/\.md$/i, "");
|
|
27685
|
+
const spaced = trimmed.replace(/\s+/g, "-");
|
|
27686
|
+
const cleaned = spaced.replace(/[^A-Za-z0-9._-]/g, "");
|
|
27687
|
+
return cleaned.length > 0 ? cleaned : "ledger";
|
|
27688
|
+
}
|
|
27689
|
+
function ledgerDir(projectDir) {
|
|
27690
|
+
return path5.join(projectDir, ".opencode", "ledgers");
|
|
27691
|
+
}
|
|
27692
|
+
function ledgerPath(projectDir, name) {
|
|
27693
|
+
return path5.join(ledgerDir(projectDir), `${normalizeLedgerName(name)}.md`);
|
|
27694
|
+
}
|
|
27695
|
+
function createLedgerSaveTool(ctx) {
|
|
27696
|
+
return tool({
|
|
27697
|
+
description: "Save a continuity ledger markdown document to .opencode/ledgers/.",
|
|
27698
|
+
args: {
|
|
27699
|
+
name: tool.schema.string().describe('Ledger name (e.g. "auth-refactor").'),
|
|
27700
|
+
content: tool.schema.string().describe("Markdown content.")
|
|
27701
|
+
},
|
|
27702
|
+
execute: async ({ name, content }) => {
|
|
27703
|
+
const dir = ledgerDir(ctx.directory);
|
|
27704
|
+
const outPath = ledgerPath(ctx.directory, name);
|
|
27705
|
+
try {
|
|
27706
|
+
await fs5.promises.mkdir(dir, { recursive: true });
|
|
27707
|
+
await fs5.promises.writeFile(outPath, content, "utf-8");
|
|
27708
|
+
return `Saved to .opencode/ledgers/${path5.basename(outPath)}`;
|
|
27709
|
+
} catch (err) {
|
|
27710
|
+
log("ledger_save failed", { error: String(err) });
|
|
27711
|
+
return `Error: failed to save ledger`;
|
|
27712
|
+
}
|
|
27713
|
+
}
|
|
27714
|
+
});
|
|
27715
|
+
}
|
|
27716
|
+
function createLedgerLoadTool(ctx) {
|
|
27717
|
+
return tool({
|
|
27718
|
+
description: "Load a continuity ledger markdown document from .opencode/ledgers/.",
|
|
27719
|
+
args: {
|
|
27720
|
+
name: tool.schema.string().optional().describe("Ledger name (defaults to most recent).")
|
|
27721
|
+
},
|
|
27722
|
+
execute: async ({ name }) => {
|
|
27723
|
+
const dir = ledgerDir(ctx.directory);
|
|
27724
|
+
try {
|
|
27725
|
+
if (name && name.trim().length > 0) {
|
|
27726
|
+
const p = ledgerPath(ctx.directory, name);
|
|
27727
|
+
if (!fs5.existsSync(p))
|
|
27728
|
+
return "No ledgers found";
|
|
27729
|
+
return await fs5.promises.readFile(p, "utf-8");
|
|
27730
|
+
}
|
|
27731
|
+
if (!fs5.existsSync(dir))
|
|
27732
|
+
return "No ledgers found";
|
|
27733
|
+
const entries = await fs5.promises.readdir(dir);
|
|
27734
|
+
const files = entries.filter((e) => e.toLowerCase().endsWith(".md")).map((e) => path5.join(dir, e));
|
|
27735
|
+
if (files.length === 0)
|
|
27736
|
+
return "No ledgers found";
|
|
27737
|
+
let latest = files[0];
|
|
27738
|
+
let latestMtime = -1;
|
|
27739
|
+
for (const f of files) {
|
|
27740
|
+
try {
|
|
27741
|
+
const st = await fs5.promises.stat(f);
|
|
27742
|
+
if (st.mtimeMs > latestMtime) {
|
|
27743
|
+
latestMtime = st.mtimeMs;
|
|
27744
|
+
latest = f;
|
|
27745
|
+
}
|
|
27746
|
+
} catch {}
|
|
27747
|
+
}
|
|
27748
|
+
return await fs5.promises.readFile(latest, "utf-8");
|
|
27749
|
+
} catch (err) {
|
|
27750
|
+
log("ledger_load failed", { error: String(err) });
|
|
27751
|
+
return "No ledgers found";
|
|
27752
|
+
}
|
|
27753
|
+
}
|
|
27754
|
+
});
|
|
27755
|
+
}
|
|
27756
|
+
|
|
27757
|
+
// src/tools/ast-search.ts
|
|
27758
|
+
import * as fs6 from "fs";
|
|
27759
|
+
import * as path6 from "path";
|
|
27760
|
+
import { spawnSync } from "child_process";
|
|
27761
|
+
var MAX_MATCHES = 50;
|
|
27762
|
+
function stripAnsi(s) {
|
|
27763
|
+
return s.replace(/\u001b\[[0-9;]*m/g, "");
|
|
27764
|
+
}
|
|
27765
|
+
function resolveSearchPath(projectDir, p) {
|
|
27766
|
+
return path6.isAbsolute(p) ? p : path6.join(projectDir, p);
|
|
27767
|
+
}
|
|
27768
|
+
function candidatesFor(bin) {
|
|
27769
|
+
const out = [bin];
|
|
27770
|
+
if (process.platform === "win32") {
|
|
27771
|
+
out.push(`${bin}.exe`, `${bin}.cmd`, `${bin}.bat`);
|
|
27772
|
+
}
|
|
27773
|
+
return out;
|
|
27774
|
+
}
|
|
27775
|
+
function findAstGrepBinary() {
|
|
27776
|
+
const pathEnv = process.env.PATH ?? "";
|
|
27777
|
+
const dirs = pathEnv.split(path6.delimiter).filter(Boolean);
|
|
27778
|
+
const bins = ["sg", "ast-grep"];
|
|
27779
|
+
for (const dir of dirs) {
|
|
27780
|
+
for (const b of bins) {
|
|
27781
|
+
for (const cand of candidatesFor(b)) {
|
|
27782
|
+
const full = path6.join(dir, cand);
|
|
27783
|
+
try {
|
|
27784
|
+
const st = fs6.statSync(full);
|
|
27785
|
+
if (!st.isFile())
|
|
27786
|
+
continue;
|
|
27787
|
+
fs6.accessSync(full, fs6.constants.X_OK);
|
|
27788
|
+
return full;
|
|
27789
|
+
} catch {}
|
|
27790
|
+
}
|
|
27791
|
+
}
|
|
27792
|
+
}
|
|
27793
|
+
return null;
|
|
27794
|
+
}
|
|
27795
|
+
function normalizeOutput(stdout) {
|
|
27796
|
+
const lines = stripAnsi(stdout).split(`
|
|
27797
|
+
`).map((l) => l.trimEnd()).filter((l) => l.length > 0);
|
|
27798
|
+
const out = [];
|
|
27799
|
+
for (const line of lines) {
|
|
27800
|
+
const m3 = line.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
27801
|
+
if (m3) {
|
|
27802
|
+
out.push(`${m3[1]}:${m3[2]}: ${m3[4].trim()}`);
|
|
27803
|
+
continue;
|
|
27804
|
+
}
|
|
27805
|
+
const m2 = line.match(/^(.*?):(\d+):(.*)$/);
|
|
27806
|
+
if (m2) {
|
|
27807
|
+
out.push(`${m2[1]}:${m2[2]}: ${m2[3].trim()}`);
|
|
27808
|
+
continue;
|
|
27809
|
+
}
|
|
27810
|
+
out.push(line);
|
|
27811
|
+
}
|
|
27812
|
+
return out.slice(0, MAX_MATCHES).join(`
|
|
27813
|
+
`);
|
|
27814
|
+
}
|
|
27815
|
+
function createAstSearchTool(ctx, binaryPath) {
|
|
27816
|
+
return tool({
|
|
27817
|
+
description: "AST-aware code search using ast-grep (sg).",
|
|
27818
|
+
args: {
|
|
27819
|
+
pattern: tool.schema.string().describe('ast-grep pattern (e.g. "console.log($$$)").'),
|
|
27820
|
+
path: tool.schema.string().optional().describe("Directory to search (default: project root)."),
|
|
27821
|
+
lang: tool.schema.string().optional().describe('Language (e.g. "typescript", "python").')
|
|
27822
|
+
},
|
|
27823
|
+
execute: async ({ pattern, path: searchPath, lang }) => {
|
|
27824
|
+
const target = resolveSearchPath(ctx.directory, searchPath ?? ".");
|
|
27825
|
+
const args = ["--pattern", pattern, target];
|
|
27826
|
+
if (lang && lang.trim().length > 0) {
|
|
27827
|
+
args.push("--lang", lang);
|
|
27828
|
+
}
|
|
27829
|
+
const res = spawnSync(binaryPath, args, {
|
|
27830
|
+
cwd: ctx.directory,
|
|
27831
|
+
encoding: "utf-8"
|
|
27832
|
+
});
|
|
27833
|
+
if (res.error) {
|
|
27834
|
+
log("ast_search spawn failed", { error: String(res.error) });
|
|
27835
|
+
return `Error: ${String(res.error)}`;
|
|
27836
|
+
}
|
|
27837
|
+
const stdout = res.stdout ?? "";
|
|
27838
|
+
const stderr = res.stderr ?? "";
|
|
27839
|
+
if ((res.status ?? 0) === 1 && stdout.trim().length === 0) {
|
|
27840
|
+
return "No matches";
|
|
27841
|
+
}
|
|
27842
|
+
if ((res.status ?? 0) !== 0 && (res.status ?? 0) !== 1) {
|
|
27843
|
+
log("ast_search failed", { status: res.status, stderr });
|
|
27844
|
+
return stderr.trim().length > 0 ? stderr.trim() : "Error: ast_search failed";
|
|
27845
|
+
}
|
|
27846
|
+
const normalized = normalizeOutput(stdout);
|
|
27847
|
+
return normalized.length > 0 ? normalized : "No matches";
|
|
27848
|
+
}
|
|
27849
|
+
});
|
|
27850
|
+
}
|
|
27851
|
+
|
|
27522
27852
|
// src/hooks/todo-enforcer.ts
|
|
27523
27853
|
var DEFAULT_MAX_ENFORCEMENTS = 5;
|
|
27524
27854
|
var sessionState = new Map;
|
|
@@ -27601,7 +27931,7 @@ ${unfinished.map((t) => `- [ ] ${t.text}`).join(`
|
|
|
27601
27931
|
}
|
|
27602
27932
|
|
|
27603
27933
|
// src/hooks/comment-checker.ts
|
|
27604
|
-
import * as
|
|
27934
|
+
import * as fs7 from "fs";
|
|
27605
27935
|
var AI_SLOP_PATTERNS = [
|
|
27606
27936
|
/\/\/ .{80,}/,
|
|
27607
27937
|
/\/\/ (This|The|We|Here|Note:)/i,
|
|
@@ -27680,7 +28010,7 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
|
|
|
27680
28010
|
if (!filePath || !isCodeFile(filePath))
|
|
27681
28011
|
return;
|
|
27682
28012
|
try {
|
|
27683
|
-
const content =
|
|
28013
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
27684
28014
|
const result = checkComments(content, filePath, maxRatio, slopThreshold);
|
|
27685
28015
|
if (result.shouldWarn) {
|
|
27686
28016
|
output.output += `
|
|
@@ -27852,10 +28182,10 @@ class Semaphore {
|
|
|
27852
28182
|
this.active++;
|
|
27853
28183
|
return;
|
|
27854
28184
|
}
|
|
27855
|
-
return new Promise((
|
|
28185
|
+
return new Promise((resolve2) => {
|
|
27856
28186
|
this.queue.push(() => {
|
|
27857
28187
|
this.active++;
|
|
27858
|
-
|
|
28188
|
+
resolve2();
|
|
27859
28189
|
});
|
|
27860
28190
|
});
|
|
27861
28191
|
}
|
|
@@ -27975,10 +28305,17 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27975
28305
|
resolveAgentModel
|
|
27976
28306
|
});
|
|
27977
28307
|
const ralphTools = createRalphLoopTools(ctx, internalSessions);
|
|
28308
|
+
const batchRead = createBatchReadTool(ctx);
|
|
28309
|
+
const ledgerSave = createLedgerSaveTool(ctx);
|
|
28310
|
+
const ledgerLoad = createLedgerLoadTool(ctx);
|
|
28311
|
+
const astGrepBin = findAstGrepBinary();
|
|
28312
|
+
const astSearch = astGrepBin ? createAstSearchTool(ctx, astGrepBin) : null;
|
|
27978
28313
|
const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
|
|
27979
28314
|
const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
|
|
27980
28315
|
const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
|
|
27981
28316
|
const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
|
|
28317
|
+
const fragmentInjector = createFragmentInjector(ctx, internalSessions, pluginConfig.fragments);
|
|
28318
|
+
const promptRendererHook = createPromptRendererHook(internalSessions, pluginConfig.prompt_renderer);
|
|
27982
28319
|
const pendingKeywords = new Map;
|
|
27983
28320
|
log("Config loaded", {
|
|
27984
28321
|
agentCount: Object.keys(agents).length,
|
|
@@ -27999,6 +28336,18 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27999
28336
|
if (!disabledTools.has("cancel_ralph")) {
|
|
28000
28337
|
toolRegistry.cancel_ralph = ralphTools.cancel_ralph;
|
|
28001
28338
|
}
|
|
28339
|
+
if (!disabledTools.has("batch_read")) {
|
|
28340
|
+
toolRegistry.batch_read = batchRead;
|
|
28341
|
+
}
|
|
28342
|
+
if (!disabledTools.has("ledger_save")) {
|
|
28343
|
+
toolRegistry.ledger_save = ledgerSave;
|
|
28344
|
+
}
|
|
28345
|
+
if (!disabledTools.has("ledger_load")) {
|
|
28346
|
+
toolRegistry.ledger_load = ledgerLoad;
|
|
28347
|
+
}
|
|
28348
|
+
if (!disabledTools.has("ast_search") && astSearch) {
|
|
28349
|
+
toolRegistry.ast_search = astSearch;
|
|
28350
|
+
}
|
|
28002
28351
|
return {
|
|
28003
28352
|
tool: toolRegistry,
|
|
28004
28353
|
config: async (config3) => {
|
|
@@ -28107,6 +28456,12 @@ ${arch}`);
|
|
|
28107
28456
|
${style}`);
|
|
28108
28457
|
}
|
|
28109
28458
|
}
|
|
28459
|
+
if (!disabledHooks.has("fragment-injector")) {
|
|
28460
|
+
await fragmentInjector(input, output);
|
|
28461
|
+
}
|
|
28462
|
+
if (!disabledHooks.has("prompt-renderer")) {
|
|
28463
|
+
promptRendererHook(input, output);
|
|
28464
|
+
}
|
|
28110
28465
|
},
|
|
28111
28466
|
"experimental.session.compacting": async (input, output) => {
|
|
28112
28467
|
if (disabledHooks.has("session-compaction"))
|
package/package.json
CHANGED