opencode-ultra 0.7.6 → 0.8.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/index.js CHANGED
@@ -28603,6 +28603,72 @@ function formatResult(result) {
28603
28603
  }
28604
28604
 
28605
28605
  // src/tools/evolve-filter.ts
28606
+ var EvolveProposalSchema = exports_external.object({
28607
+ title: exports_external.string().min(1),
28608
+ priority: exports_external.enum(["P0", "P1", "P2"]),
28609
+ effort: exports_external.enum(["Low", "Medium", "High"]),
28610
+ description: exports_external.string().optional(),
28611
+ files: exports_external.array(exports_external.string()).optional(),
28612
+ currentState: exports_external.string().optional(),
28613
+ inspiration: exports_external.string().optional(),
28614
+ current_state: exports_external.string().optional(),
28615
+ why: exports_external.string().optional(),
28616
+ how: exports_external.string().optional()
28617
+ }).transform((v) => {
28618
+ const legacyDesc = [v.why, v.how].filter((s) => typeof s === "string" && s.length > 0);
28619
+ return {
28620
+ title: v.title,
28621
+ priority: v.priority,
28622
+ effort: v.effort,
28623
+ description: v.description ?? (legacyDesc.length > 0 ? legacyDesc.join(`
28624
+ `) : ""),
28625
+ files: v.files,
28626
+ currentState: v.currentState ?? v.current_state,
28627
+ inspiration: v.inspiration
28628
+ };
28629
+ });
28630
+ function validateProposalJsonl(line) {
28631
+ const raw = line.trim();
28632
+ if (!raw)
28633
+ return { ok: false, error: "Empty line" };
28634
+ let parsed;
28635
+ try {
28636
+ parsed = JSON.parse(raw);
28637
+ } catch (err) {
28638
+ const msg = err instanceof Error ? err.message : String(err);
28639
+ return { ok: false, error: `Invalid JSON: ${msg}` };
28640
+ }
28641
+ const result = EvolveProposalSchema.safeParse(parsed);
28642
+ if (!result.success) {
28643
+ const issues = result.error.issues.map((i) => {
28644
+ const path8 = i.path.length > 0 ? i.path.join(".") : "(root)";
28645
+ return `${path8}: ${i.message}`;
28646
+ }).join("; ");
28647
+ return { ok: false, error: `Schema validation failed: ${issues}` };
28648
+ }
28649
+ return { ok: true, proposal: result.data };
28650
+ }
28651
+ function normalizeTitleForDedup(title) {
28652
+ return title.normalize("NFKC").toLowerCase().replace(/[\p{P}\p{S}\s]+/gu, "").trim();
28653
+ }
28654
+ function deduplicateProposals(existing, incoming) {
28655
+ const seen = new Set;
28656
+ const out = [];
28657
+ const add = (p) => {
28658
+ const key = normalizeTitleForDedup(p.title);
28659
+ if (key.length === 0)
28660
+ return;
28661
+ if (seen.has(key))
28662
+ return;
28663
+ seen.add(key);
28664
+ out.push(p);
28665
+ };
28666
+ for (const p of existing)
28667
+ add(p);
28668
+ for (const p of incoming)
28669
+ add(p);
28670
+ return out;
28671
+ }
28606
28672
  var PRIORITY_WEIGHT = {
28607
28673
  P0: 10,
28608
28674
  P1: 5,
@@ -28756,6 +28822,186 @@ evolve_score({ markdown: "## Improvement: Rate Limiting\\n**Priority**: P0\\n**E
28756
28822
  });
28757
28823
  }
28758
28824
 
28825
+ // src/tools/evolve-scan.ts
28826
+ import * as fs8 from "fs";
28827
+ import * as path8 from "path";
28828
+ function splitJsonl(text) {
28829
+ return text.split(/\r?\n/);
28830
+ }
28831
+ function parseJsonl(text) {
28832
+ const proposals = [];
28833
+ const errors5 = [];
28834
+ const lines = splitJsonl(text);
28835
+ for (let i = 0;i < lines.length; i++) {
28836
+ const raw = lines[i] ?? "";
28837
+ if (!raw.trim())
28838
+ continue;
28839
+ const v = validateProposalJsonl(raw);
28840
+ if (v.ok === false) {
28841
+ errors5.push({ line: i + 1, raw, error: v.error });
28842
+ continue;
28843
+ }
28844
+ proposals.push(v.proposal);
28845
+ }
28846
+ return { proposals, errors: errors5 };
28847
+ }
28848
+ function proposalToJson(p) {
28849
+ const out = {
28850
+ title: p.title,
28851
+ priority: p.priority,
28852
+ effort: p.effort,
28853
+ description: p.description
28854
+ };
28855
+ if (p.files && p.files.length > 0)
28856
+ out.files = p.files;
28857
+ if (p.currentState)
28858
+ out.currentState = p.currentState;
28859
+ if (p.inspiration)
28860
+ out.inspiration = p.inspiration;
28861
+ return out;
28862
+ }
28863
+ function formatMaybeCode(s) {
28864
+ return s.replace(/\s+/g, " ").trim();
28865
+ }
28866
+ function markdownEscape(s) {
28867
+ return s.replaceAll("|", "\\|").replaceAll(`
28868
+ `, " ");
28869
+ }
28870
+ function buildReport(args) {
28871
+ const lines = [];
28872
+ lines.push("# Evolve Scan Report");
28873
+ lines.push("");
28874
+ lines.push("## Metadata");
28875
+ lines.push(`- Generated: ${args.generatedAt}`);
28876
+ lines.push(`- Search query: ${args.query && args.query.trim() ? args.query.trim() : "(not provided)"}`);
28877
+ lines.push(`- Threshold: minScore=${args.minScore}, maxProposals=${args.maxProposals}`);
28878
+ lines.push(`- Proposals: existing=${args.existingCount}, incoming=${args.incomingCount}, merged=${args.mergedCount}, accepted=${args.acceptedCount}, rejected=${args.rejectedCount}`);
28879
+ lines.push(`- Invalid JSONL lines skipped: ${args.existingErrors.length + args.incomingErrors.length}`);
28880
+ lines.push("");
28881
+ lines.push("## Feature Matrix");
28882
+ lines.push("");
28883
+ if (args.featureMatrix && args.featureMatrix.trim()) {
28884
+ lines.push(args.featureMatrix.trim());
28885
+ } else {
28886
+ lines.push("_(not provided)_");
28887
+ }
28888
+ lines.push("");
28889
+ lines.push("## Scored Proposals");
28890
+ lines.push("");
28891
+ lines.push(`### Accepted (${args.accepted.length})`);
28892
+ lines.push("");
28893
+ if (args.accepted.length === 0) {
28894
+ lines.push("_(none)_");
28895
+ } else {
28896
+ lines.push("| Title | Score | Priority | Effort | Description | Inspiration | Current state |");
28897
+ lines.push("|---|---:|---|---|---|---|---|");
28898
+ for (const p of args.accepted) {
28899
+ lines.push(`| ${markdownEscape(p.title)} | ${p.score} | ${p.priority} | ${p.effort} | ${markdownEscape(formatMaybeCode(p.description))} | ${markdownEscape(p.inspiration ? formatMaybeCode(p.inspiration) : "")} | ${markdownEscape(p.currentState ? formatMaybeCode(p.currentState) : "")} |`);
28900
+ }
28901
+ }
28902
+ lines.push("");
28903
+ lines.push(`### Rejected (${args.rejected.length})`);
28904
+ lines.push("");
28905
+ if (args.rejected.length === 0) {
28906
+ lines.push("_(none)_");
28907
+ } else {
28908
+ lines.push("| Title | Score | Priority | Effort | Reason |");
28909
+ lines.push("|---|---:|---|---|---|");
28910
+ for (const p of args.rejected) {
28911
+ lines.push(`| ${markdownEscape(p.title)} | ${p.score} | ${p.priority} | ${p.effort} | ${markdownEscape(p.reason ? formatMaybeCode(p.reason) : "")} |`);
28912
+ }
28913
+ }
28914
+ lines.push("");
28915
+ const allErrors = [
28916
+ ...args.existingErrors.map((e) => ({ ...e, source: "existing" })),
28917
+ ...args.incomingErrors.map((e) => ({ ...e, source: "incoming" }))
28918
+ ];
28919
+ if (allErrors.length > 0) {
28920
+ lines.push("## Skipped JSONL Lines");
28921
+ lines.push("");
28922
+ for (const e of allErrors) {
28923
+ lines.push(`- ${e.source} line ${e.line}: ${e.error}`);
28924
+ }
28925
+ lines.push("");
28926
+ }
28927
+ lines.push("## Score Reference");
28928
+ lines.push("");
28929
+ lines.push("P0+Low=30 | P0+Med=20 | P0+High=10 | P1+Low=15 | P1+Med=10 | P1+High=5 | P2+Low=3 | P2+Med=2 | P2+High=1");
28930
+ lines.push("");
28931
+ return lines.join(`
28932
+ `);
28933
+ }
28934
+ function createEvolveScanTool() {
28935
+ return tool({
28936
+ description: `Scan/validate evolve proposals JSONL, merge with existing proposals, score them, and generate a report.
28937
+
28938
+ Writes outputs to:
28939
+ - .opencode/evolve-proposals.jsonl (validated, deduplicated)
28940
+ - .opencode/evolve-report.md (Feature Matrix + Scored Proposals + metadata)
28941
+
28942
+ This tool only writes into .opencode/ and does not modify source code.`,
28943
+ args: {
28944
+ proposalsJsonl: tool.schema.string().optional().describe("Incoming proposals as raw JSONL text (optional; merged into existing file)"),
28945
+ featureMatrix: tool.schema.string().optional().describe("Feature matrix markdown (Phase 2 output) to embed into the report"),
28946
+ query: tool.schema.string().optional().describe("Search query used during research (for report metadata)"),
28947
+ minScore: tool.schema.number().optional().describe("Minimum score to accept (default: 5)"),
28948
+ maxProposals: tool.schema.number().optional().describe("Max proposals to accept (default: 3)")
28949
+ },
28950
+ execute: async (args) => {
28951
+ const generatedAt = new Date().toISOString();
28952
+ const minScore = args.minScore ?? 5;
28953
+ const maxProposals = args.maxProposals ?? 3;
28954
+ const outDir = path8.join(process.cwd(), ".opencode");
28955
+ const proposalsPath = path8.join(outDir, "evolve-proposals.jsonl");
28956
+ const reportPath = path8.join(outDir, "evolve-report.md");
28957
+ fs8.mkdirSync(outDir, { recursive: true });
28958
+ const existingText = fs8.existsSync(proposalsPath) ? fs8.readFileSync(proposalsPath, "utf-8") : "";
28959
+ const existingParsed = existingText ? parseJsonl(existingText) : { proposals: [], errors: [] };
28960
+ const incomingParsed = args.proposalsJsonl ? parseJsonl(args.proposalsJsonl) : { proposals: [], errors: [] };
28961
+ const existingDeduped = deduplicateProposals([], existingParsed.proposals);
28962
+ const merged = deduplicateProposals(existingDeduped, incomingParsed.proposals);
28963
+ const addedCount = merged.length - existingDeduped.length;
28964
+ const existingDupesRemoved = existingParsed.proposals.length - existingDeduped.length;
28965
+ const incomingDupesSkipped = incomingParsed.proposals.length - addedCount;
28966
+ const filtered = filterProposals(merged, { minScore, maxProposals });
28967
+ const accepted = filtered.filter((p) => p.accepted).map((p) => ({ ...p, score: p.score }));
28968
+ const rejected = filtered.filter((p) => !p.accepted).map((p) => ({ ...p, score: p.score, reason: p.reason }));
28969
+ const jsonlOut = merged.map((p) => JSON.stringify(proposalToJson(p))).join(`
28970
+ `) + (merged.length > 0 ? `
28971
+ ` : "");
28972
+ fs8.writeFileSync(proposalsPath, jsonlOut, "utf-8");
28973
+ const report = buildReport({
28974
+ generatedAt,
28975
+ query: args.query,
28976
+ minScore,
28977
+ maxProposals,
28978
+ featureMatrix: args.featureMatrix,
28979
+ existingCount: existingParsed.proposals.length,
28980
+ incomingCount: incomingParsed.proposals.length,
28981
+ mergedCount: merged.length,
28982
+ acceptedCount: accepted.length,
28983
+ rejectedCount: rejected.length,
28984
+ existingErrors: existingParsed.errors,
28985
+ incomingErrors: incomingParsed.errors,
28986
+ accepted: accepted.map((p) => ({ ...p, score: scoreProposal(p) })),
28987
+ rejected: rejected.map((p) => ({ ...p, score: scoreProposal(p) }))
28988
+ });
28989
+ fs8.writeFileSync(reportPath, report, "utf-8");
28990
+ const skippedInvalid = existingParsed.errors.length + incomingParsed.errors.length;
28991
+ return [
28992
+ "## evolve_scan complete",
28993
+ `- Wrote: ${proposalsPath}`,
28994
+ `- Wrote: ${reportPath}`,
28995
+ `- Existing proposals: ${existingParsed.proposals.length} (dedup removed ${existingDupesRemoved})`,
28996
+ `- Incoming proposals: ${incomingParsed.proposals.length} (dedup skipped ${incomingDupesSkipped}, invalid skipped ${incomingParsed.errors.length})`,
28997
+ `- Merged proposals: ${merged.length} (accepted ${accepted.length}, rejected ${rejected.length})`,
28998
+ `- Invalid JSONL lines skipped (total): ${skippedInvalid}`
28999
+ ].join(`
29000
+ `);
29001
+ }
29002
+ });
29003
+ }
29004
+
28759
29005
  // src/hooks/todo-enforcer.ts
28760
29006
  var DEFAULT_MAX_ENFORCEMENTS = 5;
28761
29007
  var sessionState = new Map;
@@ -28838,7 +29084,7 @@ ${unfinished.map((t) => `- [ ] ${t.text}`).join(`
28838
29084
  }
28839
29085
 
28840
29086
  // src/hooks/comment-checker.ts
28841
- import * as fs8 from "fs";
29087
+ import * as fs9 from "fs";
28842
29088
  var AI_SLOP_PATTERNS = [
28843
29089
  /\/\/ .{80,}/,
28844
29090
  /\/\/ (This|The|We|Here|Note:)/i,
@@ -28917,7 +29163,7 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
28917
29163
  if (!filePath || !isCodeFile(filePath))
28918
29164
  return;
28919
29165
  try {
28920
- const content = fs8.readFileSync(filePath, "utf-8");
29166
+ const content = fs9.readFileSync(filePath, "utf-8");
28921
29167
  const result = checkComments(content, filePath, maxRatio, slopThreshold);
28922
29168
  if (result.shouldWarn) {
28923
29169
  output.output += `
@@ -29291,6 +29537,9 @@ var OpenCodeUltra = async (ctx) => {
29291
29537
  if (!disabledTools.has("evolve_apply")) {
29292
29538
  toolRegistry.evolve_apply = evolveApply;
29293
29539
  }
29540
+ if (!disabledTools.has("evolve_scan")) {
29541
+ toolRegistry.evolve_scan = createEvolveScanTool();
29542
+ }
29294
29543
  if (!disabledTools.has("evolve_score")) {
29295
29544
  toolRegistry.evolve_score = createEvolveScoreTool();
29296
29545
  }
@@ -29355,6 +29604,20 @@ var OpenCodeUltra = async (ctx) => {
29355
29604
  })?.catch?.((err) => log("Toast failed", { error: err }));
29356
29605
  }
29357
29606
  pendingKeywords.set(input.sessionID, detected);
29607
+ const hasEvolve = detected.some((k) => k.type === "evolve");
29608
+ if (hasEvolve) {
29609
+ const anchor = `
29610
+
29611
+ [EVOLVE MODE ACTIVATED \u2014 MANDATORY TOOL USAGE]
29612
+ You MUST follow the 5-phase evolve workflow injected in the system prompt.
29613
+ Phase 1: RESEARCH \u2014 Use Glob to scan node_modules for OpenCode plugins, or spawn_agent with scout/librarian.
29614
+ Phase 2: COMPARE \u2014 Build a feature matrix (opencode-ultra column = Yes/No/Partial from inventory).
29615
+ Phase 3: PROPOSE \u2014 Write EACH proposal as JSONL to .opencode/evolve-proposals.jsonl using the Write tool.
29616
+ Phase 4: SCORE \u2014 Call evolve_score({ markdown: "## Improvement: ...\\n**Priority**: ...\\n**Effort**: ..." }) for EVERY proposal.
29617
+ Phase 5: SAVE \u2014 Call ledger_save({ name: "evolve-scan-YYYY-MM-DD", content: "..." }).
29618
+ DO NOT skip tools. Text-only output is FORBIDDEN. Every phase requires tool calls.`;
29619
+ output.parts.push({ type: "text", text: anchor });
29620
+ }
29358
29621
  if (hasUltrawork) {
29359
29622
  for (const part of output.parts) {
29360
29623
  if (part.type === "text" && part.text) {
@@ -2,7 +2,10 @@
2
2
  * evolve-filter — Pure scoring and filtering logic for evolve proposals.
3
3
  *
4
4
  * No side effects. All functions are deterministic.
5
+ * Also exports createEvolveScoreTool() for LLM-facing tool registration.
5
6
  */
7
+ import { tool } from "@opencode-ai/plugin";
8
+ import { z } from "zod";
6
9
  export interface EvolveProposal {
7
10
  title: string;
8
11
  priority: "P0" | "P1" | "P2";
@@ -12,6 +15,32 @@ export interface EvolveProposal {
12
15
  currentState?: string;
13
16
  inspiration?: string;
14
17
  }
18
+ /**
19
+ * JSONL schema for evolve proposals.
20
+ *
21
+ * Accepts both canonical keys (description/currentState) and legacy keys
22
+ * (why/how/current_state) used by older evolve prompts.
23
+ */
24
+ export declare const EvolveProposalSchema: z.ZodType<EvolveProposal>;
25
+ export type ProposalJsonlValidation = {
26
+ ok: true;
27
+ proposal: EvolveProposal;
28
+ } | {
29
+ ok: false;
30
+ error: string;
31
+ };
32
+ /**
33
+ * Parse and validate a single JSONL line.
34
+ *
35
+ * Returns {ok:false,error} for invalid lines so callers can skip with reason.
36
+ */
37
+ export declare function validateProposalJsonl(line: string): ProposalJsonlValidation;
38
+ /**
39
+ * Merge existing + incoming proposals, skipping duplicates by normalized title.
40
+ *
41
+ * Normalization: lowercase + punctuation/symbol/whitespace removed.
42
+ */
43
+ export declare function deduplicateProposals(existing: EvolveProposal[], incoming: EvolveProposal[]): EvolveProposal[];
15
44
  export interface FilteredProposal extends EvolveProposal {
16
45
  score: number;
17
46
  accepted: boolean;
@@ -38,3 +67,10 @@ export declare function filterProposals(proposals: EvolveProposal[], config?: Fi
38
67
  * ```
39
68
  */
40
69
  export declare function parseProposalsFromMarkdown(markdown: string): EvolveProposal[];
70
+ /**
71
+ * Tool: evolve_score
72
+ *
73
+ * Takes evolve proposal markdown, parses it, scores each proposal,
74
+ * and returns a prioritized list. Pure computation, no agent spawning.
75
+ */
76
+ export declare function createEvolveScoreTool(): ReturnType<typeof tool>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * evolve_scan — validate + merge + score evolve proposals.
3
+ *
4
+ * This tool is scan/report oriented and only writes into `.opencode/`.
5
+ */
6
+ import { tool } from "@opencode-ai/plugin";
7
+ export declare function createEvolveScanTool(): ReturnType<typeof tool>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ultra",
3
- "version": "0.7.6",
3
+ "version": "0.8.0",
4
4
  "description": "Lightweight OpenCode 1.2.x plugin — ultrawork mode, multi-agent orchestration, rules injection",
5
5
  "keywords": [
6
6
  "opencode",