opencode-ultra 0.6.5 → 0.7.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/dist/config.d.ts +5 -0
- package/dist/hooks/keyword-detector.d.ts +11 -1
- package/dist/index.js +528 -56
- package/dist/safety/index.d.ts +1 -1
- package/dist/safety/trust-score.d.ts +11 -5
- package/dist/tools/evolve-filter.d.ts +40 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -73,6 +73,11 @@ declare const PluginConfigSchema: z.ZodObject<{
|
|
|
73
73
|
maxTotalSpawned: z.ZodOptional<z.ZodNumber>;
|
|
74
74
|
agentTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
75
75
|
}, z.core.$strip>>;
|
|
76
|
+
evolve_auto: z.ZodOptional<z.ZodObject<{
|
|
77
|
+
minScore: z.ZodOptional<z.ZodNumber>;
|
|
78
|
+
maxProposals: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
skipReview: z.ZodOptional<z.ZodBoolean>;
|
|
80
|
+
}, z.core.$strip>>;
|
|
76
81
|
}, z.core.$loose>;
|
|
77
82
|
export type PluginConfig = z.infer<typeof PluginConfigSchema>;
|
|
78
83
|
export declare function parsePluginConfig(raw: unknown): PluginConfig;
|
|
@@ -7,4 +7,14 @@ export declare function extractPromptText(parts: Array<{
|
|
|
7
7
|
type: string;
|
|
8
8
|
text?: string;
|
|
9
9
|
}>): string;
|
|
10
|
-
|
|
10
|
+
/** Context for generating dynamic evolve message */
|
|
11
|
+
export interface EvolveContext {
|
|
12
|
+
agents: Record<string, {
|
|
13
|
+
model: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}>;
|
|
16
|
+
tools: string[];
|
|
17
|
+
hooks: string[];
|
|
18
|
+
features: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function detectKeywords(text: string, evolveCtx?: EvolveContext): DetectedKeyword[];
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
set: (newValue) => all[name] = () => newValue
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
|
+
var __require = import.meta.require;
|
|
12
13
|
|
|
13
14
|
// node_modules/zod/v4/classic/external.js
|
|
14
15
|
var exports_external = {};
|
|
@@ -14472,6 +14473,11 @@ var PluginConfigSchema = exports_external.object({
|
|
|
14472
14473
|
safety: exports_external.object({
|
|
14473
14474
|
maxTotalSpawned: exports_external.number().min(1).optional(),
|
|
14474
14475
|
agentTimeoutMs: exports_external.number().min(1000).optional()
|
|
14476
|
+
}).optional(),
|
|
14477
|
+
evolve_auto: exports_external.object({
|
|
14478
|
+
minScore: exports_external.number().min(0).max(30).optional(),
|
|
14479
|
+
maxProposals: exports_external.number().min(1).max(10).optional(),
|
|
14480
|
+
skipReview: exports_external.boolean().optional()
|
|
14475
14481
|
}).optional()
|
|
14476
14482
|
}).passthrough();
|
|
14477
14483
|
function parsePluginConfig(raw) {
|
|
@@ -14530,7 +14536,8 @@ function mergeConfigs(base, override) {
|
|
|
14530
14536
|
])],
|
|
14531
14537
|
background_task: override.background_task ?? base.background_task,
|
|
14532
14538
|
comment_checker: override.comment_checker ?? base.comment_checker,
|
|
14533
|
-
todo_enforcer: override.todo_enforcer ?? base.todo_enforcer
|
|
14539
|
+
todo_enforcer: override.todo_enforcer ?? base.todo_enforcer,
|
|
14540
|
+
evolve_auto: override.evolve_auto ?? base.evolve_auto
|
|
14534
14541
|
};
|
|
14535
14542
|
}
|
|
14536
14543
|
function detectConfigFile(basePath) {
|
|
@@ -14980,6 +14987,15 @@ ledger_save({
|
|
|
14980
14987
|
})
|
|
14981
14988
|
\`\`\`
|
|
14982
14989
|
|
|
14990
|
+
## AUTOMATED MODE
|
|
14991
|
+
|
|
14992
|
+
The \`evolve_auto\` tool is available for autonomous execution:
|
|
14993
|
+
- \`evolve_auto({dryRun: true})\` \u2014 Scan + filter only (preview proposals with scores)
|
|
14994
|
+
- \`evolve_auto({})\` \u2014 Full autonomous cycle (scan \u2192 filter \u2192 implement \u2192 review \u2192 save)
|
|
14995
|
+
|
|
14996
|
+
Use \`evolve_auto\` when the user requests autonomous self-improvement.
|
|
14997
|
+
Manual exploration (the phases above) is still the default for human-guided evolve.
|
|
14998
|
+
|
|
14983
14999
|
## RULES
|
|
14984
15000
|
- Use the capability inventory above as ground truth. No file reading needed.
|
|
14985
15001
|
- Proposals must cite specific tools/hooks from the inventory for "Current state".
|
|
@@ -27570,6 +27586,7 @@ function sanitizeSpawnResult(result) {
|
|
|
27570
27586
|
` + text;
|
|
27571
27587
|
}
|
|
27572
27588
|
// src/safety/trust-score.ts
|
|
27589
|
+
var MAX_TRUST_SCORE = 108;
|
|
27573
27590
|
var KNOWN_SCOPES = [
|
|
27574
27591
|
"@opencode-ai/",
|
|
27575
27592
|
"opencode-",
|
|
@@ -27588,13 +27605,17 @@ var TYPOSQUAT_PATTERNS = [
|
|
|
27588
27605
|
/^opencode-.{1,3}$/i
|
|
27589
27606
|
];
|
|
27590
27607
|
function computeTrustScore(meta3) {
|
|
27591
|
-
const factors = [
|
|
27592
|
-
|
|
27593
|
-
|
|
27594
|
-
|
|
27595
|
-
|
|
27596
|
-
|
|
27597
|
-
|
|
27608
|
+
const factors = [
|
|
27609
|
+
scoreRecency(meta3),
|
|
27610
|
+
scorePopularity(meta3),
|
|
27611
|
+
scoreQuality(meta3),
|
|
27612
|
+
scoreRepository(meta3),
|
|
27613
|
+
scoreSafety(meta3),
|
|
27614
|
+
scoreProvenance(meta3),
|
|
27615
|
+
scoreDependencyRisk(meta3)
|
|
27616
|
+
];
|
|
27617
|
+
const raw = factors.reduce((sum, f) => sum + f.score, 0);
|
|
27618
|
+
const totalScore = clamp(raw, 0, MAX_TRUST_SCORE);
|
|
27598
27619
|
const level = classifyLevel(totalScore);
|
|
27599
27620
|
const summary = buildSummary(meta3.name, totalScore, level, factors);
|
|
27600
27621
|
log("Trust score computed", { package: meta3.name, score: totalScore, level });
|
|
@@ -27701,49 +27722,107 @@ function scoreRepository(meta3) {
|
|
|
27701
27722
|
}
|
|
27702
27723
|
function scoreSafety(meta3) {
|
|
27703
27724
|
const maxScore = 15;
|
|
27704
|
-
let score =
|
|
27725
|
+
let score = 13;
|
|
27705
27726
|
const details = [];
|
|
27706
27727
|
if (isTyposquatSuspect(meta3.name)) {
|
|
27707
|
-
score
|
|
27728
|
+
score = 0;
|
|
27708
27729
|
details.push("TYPOSQUAT SUSPECT");
|
|
27709
27730
|
} else {
|
|
27710
27731
|
details.push("Name OK");
|
|
27732
|
+
if (KNOWN_SCOPES.some((s) => meta3.name.startsWith(s))) {
|
|
27733
|
+
score = Math.min(maxScore, score + 2);
|
|
27734
|
+
details.push("Known scope");
|
|
27735
|
+
}
|
|
27711
27736
|
}
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
|
|
27737
|
+
return { name: "safety", score: Math.max(0, score), maxScore, detail: details.join(", ") };
|
|
27738
|
+
}
|
|
27739
|
+
function scoreProvenance(meta3) {
|
|
27740
|
+
const maxScore = 8;
|
|
27741
|
+
if (meta3.hasProvenance) {
|
|
27742
|
+
return { name: "provenance", score: 8, maxScore, detail: "Has npm provenance/attestation" };
|
|
27715
27743
|
}
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
27719
|
-
|
|
27720
|
-
|
|
27721
|
-
|
|
27722
|
-
|
|
27744
|
+
return { name: "provenance", score: 0, maxScore, detail: "No npm provenance" };
|
|
27745
|
+
}
|
|
27746
|
+
function scoreDependencyRisk(meta3) {
|
|
27747
|
+
const maxScore = 5;
|
|
27748
|
+
const deps = meta3.dependencyCount;
|
|
27749
|
+
if (deps === undefined) {
|
|
27750
|
+
return { name: "dependency_risk", score: 0, maxScore, detail: "No dependency info" };
|
|
27723
27751
|
}
|
|
27724
|
-
|
|
27752
|
+
if (deps > 20) {
|
|
27753
|
+
return { name: "dependency_risk", score: -5, maxScore, detail: `${deps} deps > 20 \u2014 supply-chain risk` };
|
|
27754
|
+
}
|
|
27755
|
+
return { name: "dependency_risk", score: 0, maxScore, detail: `${deps} deps` };
|
|
27725
27756
|
}
|
|
27726
27757
|
function isTyposquatSuspect(name) {
|
|
27727
|
-
|
|
27758
|
+
if (TYPOSQUAT_PATTERNS.some((p) => p.test(name)))
|
|
27759
|
+
return true;
|
|
27760
|
+
const base = unscopedName(name).toLowerCase();
|
|
27761
|
+
const target = "opencode-ultra";
|
|
27762
|
+
if (base && base !== target && levenshteinDistance(base, target) <= 2)
|
|
27763
|
+
return true;
|
|
27764
|
+
return false;
|
|
27765
|
+
}
|
|
27766
|
+
function isHighPrivilege(name) {
|
|
27767
|
+
return /\b(pty|exec|shell|sudo)\b/i.test(name);
|
|
27768
|
+
}
|
|
27769
|
+
function unscopedName(name) {
|
|
27770
|
+
if (!name)
|
|
27771
|
+
return "";
|
|
27772
|
+
if (name.startsWith("@")) {
|
|
27773
|
+
const parts = name.split("/");
|
|
27774
|
+
return parts[1] ?? name;
|
|
27775
|
+
}
|
|
27776
|
+
return name;
|
|
27777
|
+
}
|
|
27778
|
+
function levenshteinDistance(a, b) {
|
|
27779
|
+
if (a === b)
|
|
27780
|
+
return 0;
|
|
27781
|
+
if (a.length === 0)
|
|
27782
|
+
return b.length;
|
|
27783
|
+
if (b.length === 0)
|
|
27784
|
+
return a.length;
|
|
27785
|
+
const m = a.length;
|
|
27786
|
+
const n = b.length;
|
|
27787
|
+
const dp = new Array(n + 1);
|
|
27788
|
+
for (let j = 0;j <= n; j++)
|
|
27789
|
+
dp[j] = j;
|
|
27790
|
+
for (let i = 1;i <= m; i++) {
|
|
27791
|
+
let prev = dp[0];
|
|
27792
|
+
dp[0] = i;
|
|
27793
|
+
for (let j = 1;j <= n; j++) {
|
|
27794
|
+
const tmp = dp[j];
|
|
27795
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
27796
|
+
dp[j] = Math.min(dp[j] + 1, dp[j - 1] + 1, prev + cost);
|
|
27797
|
+
prev = tmp;
|
|
27798
|
+
}
|
|
27799
|
+
}
|
|
27800
|
+
return dp[n];
|
|
27728
27801
|
}
|
|
27729
27802
|
function classifyLevel(score) {
|
|
27730
|
-
|
|
27803
|
+
const pct = score / MAX_TRUST_SCORE;
|
|
27804
|
+
if (pct >= 0.9)
|
|
27731
27805
|
return "high";
|
|
27732
|
-
if (
|
|
27806
|
+
if (pct >= 0.7)
|
|
27733
27807
|
return "medium";
|
|
27734
|
-
if (
|
|
27808
|
+
if (pct >= 0.4)
|
|
27735
27809
|
return "low";
|
|
27736
27810
|
return "risky";
|
|
27737
27811
|
}
|
|
27738
27812
|
function daysSince(isoDate) {
|
|
27739
27813
|
const then = new Date(isoDate).getTime();
|
|
27814
|
+
if (!Number.isFinite(then))
|
|
27815
|
+
return 9999;
|
|
27740
27816
|
const now = Date.now();
|
|
27741
|
-
return Math.floor((now - then) / (1000 * 60 * 60 * 24));
|
|
27817
|
+
return Math.max(0, Math.floor((now - then) / (1000 * 60 * 60 * 24)));
|
|
27818
|
+
}
|
|
27819
|
+
function clamp(n, min, max) {
|
|
27820
|
+
return Math.max(min, Math.min(max, n));
|
|
27742
27821
|
}
|
|
27743
27822
|
function buildSummary(name, score, level, factors) {
|
|
27744
27823
|
const icon = level === "high" ? "V" : level === "medium" ? "~" : level === "low" ? "!" : "X";
|
|
27745
27824
|
const weakest = [...factors].sort((a, b) => a.score / a.maxScore - b.score / b.maxScore)[0];
|
|
27746
|
-
return `[${icon}] ${name}: ${score}
|
|
27825
|
+
return `[${icon}] ${name}: ${score}/${MAX_TRUST_SCORE} (${level}) \u2014 weakest: ${weakest.name} (${weakest.score}/${weakest.maxScore}: ${weakest.detail})`;
|
|
27747
27826
|
}
|
|
27748
27827
|
// src/tools/spawn-agent.ts
|
|
27749
27828
|
var DEFAULT_MAX_TOTAL_SPAWNED = 15;
|
|
@@ -28444,17 +28523,19 @@ evolve_apply({
|
|
|
28444
28523
|
}
|
|
28445
28524
|
const meta3 = rec.metadata ?? { name: rec.name };
|
|
28446
28525
|
const trustResult = computeTrustScore(meta3);
|
|
28447
|
-
const
|
|
28448
|
-
|
|
28526
|
+
const effectiveMinScore = isHighPrivilege(rec.name) ? minScore + 20 : minScore;
|
|
28527
|
+
const highPrivTag = isHighPrivilege(rec.name) ? " [HIGH-PRIV +20]" : "";
|
|
28528
|
+
const scoreTag = `[${trustResult.score}/108 ${trustResult.level}]`;
|
|
28529
|
+
trustLines.push(` ${scoreTag} ${rec.name}${highPrivTag} \u2014 ${rec.reason}`);
|
|
28449
28530
|
for (const f of trustResult.factors) {
|
|
28450
28531
|
trustLines.push(` ${f.name}: ${f.score}/${f.maxScore} (${f.detail})`);
|
|
28451
28532
|
}
|
|
28452
|
-
if (trustResult.score <
|
|
28533
|
+
if (trustResult.score < effectiveMinScore) {
|
|
28453
28534
|
skipped.push({
|
|
28454
28535
|
name: rec.name,
|
|
28455
|
-
reason: `Trust score ${trustResult.score} < ${
|
|
28536
|
+
reason: `Trust score ${trustResult.score} < ${effectiveMinScore} (${trustResult.level})${highPrivTag}`
|
|
28456
28537
|
});
|
|
28457
|
-
trustLines.push(` -> REJECTED (below threshold ${
|
|
28538
|
+
trustLines.push(` -> REJECTED (below threshold ${effectiveMinScore})`);
|
|
28458
28539
|
continue;
|
|
28459
28540
|
}
|
|
28460
28541
|
added.push(rec.name);
|
|
@@ -28535,6 +28616,388 @@ function formatResult(result) {
|
|
|
28535
28616
|
`);
|
|
28536
28617
|
}
|
|
28537
28618
|
|
|
28619
|
+
// src/tools/evolve-filter.ts
|
|
28620
|
+
var PRIORITY_WEIGHT = {
|
|
28621
|
+
P0: 10,
|
|
28622
|
+
P1: 5,
|
|
28623
|
+
P2: 1
|
|
28624
|
+
};
|
|
28625
|
+
var EFFORT_WEIGHT = {
|
|
28626
|
+
Low: 3,
|
|
28627
|
+
Medium: 2,
|
|
28628
|
+
High: 1
|
|
28629
|
+
};
|
|
28630
|
+
var DEFAULT_MIN_SCORE = 5;
|
|
28631
|
+
var DEFAULT_MAX_PROPOSALS = 3;
|
|
28632
|
+
function scoreProposal(p) {
|
|
28633
|
+
const pw = PRIORITY_WEIGHT[p.priority] ?? 1;
|
|
28634
|
+
const ew = EFFORT_WEIGHT[p.effort] ?? 1;
|
|
28635
|
+
return pw * ew;
|
|
28636
|
+
}
|
|
28637
|
+
function filterProposals(proposals, config3) {
|
|
28638
|
+
const minScore = config3?.minScore ?? DEFAULT_MIN_SCORE;
|
|
28639
|
+
const maxProposals = config3?.maxProposals ?? DEFAULT_MAX_PROPOSALS;
|
|
28640
|
+
const scored = proposals.map((p) => {
|
|
28641
|
+
const score = scoreProposal(p);
|
|
28642
|
+
const accepted2 = score >= minScore;
|
|
28643
|
+
return {
|
|
28644
|
+
...p,
|
|
28645
|
+
score,
|
|
28646
|
+
accepted: accepted2,
|
|
28647
|
+
reason: accepted2 ? undefined : `Score ${score} below threshold ${minScore}`
|
|
28648
|
+
};
|
|
28649
|
+
});
|
|
28650
|
+
const accepted = scored.filter((p) => p.accepted).sort((a, b) => b.score - a.score).slice(0, maxProposals);
|
|
28651
|
+
const rejected = scored.filter((p) => !p.accepted);
|
|
28652
|
+
const acceptedSet = new Set(accepted);
|
|
28653
|
+
const overflow = scored.filter((p) => p.accepted && !acceptedSet.has(p)).map((p) => ({ ...p, accepted: false, reason: `Exceeded maxProposals (${maxProposals})` }));
|
|
28654
|
+
return [...accepted, ...overflow, ...rejected];
|
|
28655
|
+
}
|
|
28656
|
+
function parseProposalsFromMarkdown(markdown) {
|
|
28657
|
+
const proposals = [];
|
|
28658
|
+
const sections = markdown.split(/^##\s+Improvement:\s*/m);
|
|
28659
|
+
for (let i = 1;i < sections.length; i++) {
|
|
28660
|
+
const section = sections[i];
|
|
28661
|
+
const lines = section.trim();
|
|
28662
|
+
const titleMatch = lines.match(/^(.+?)(?:\n|$)/);
|
|
28663
|
+
const title = titleMatch?.[1]?.trim() ?? "Untitled";
|
|
28664
|
+
const priority = extractField(lines, "Priority");
|
|
28665
|
+
const effort = extractField(lines, "Effort");
|
|
28666
|
+
const description = extractField(lines, "Why") || extractField(lines, "How") || "";
|
|
28667
|
+
const currentState = extractField(lines, "Current state") || undefined;
|
|
28668
|
+
const inspiration = extractField(lines, "Inspiration") || undefined;
|
|
28669
|
+
const normalizedPriority = normalizePriority(priority);
|
|
28670
|
+
const normalizedEffort = normalizeEffort(effort);
|
|
28671
|
+
if (normalizedPriority && normalizedEffort) {
|
|
28672
|
+
proposals.push({
|
|
28673
|
+
title,
|
|
28674
|
+
priority: normalizedPriority,
|
|
28675
|
+
effort: normalizedEffort,
|
|
28676
|
+
description,
|
|
28677
|
+
currentState,
|
|
28678
|
+
inspiration
|
|
28679
|
+
});
|
|
28680
|
+
}
|
|
28681
|
+
}
|
|
28682
|
+
return proposals;
|
|
28683
|
+
}
|
|
28684
|
+
function extractField(text, fieldName) {
|
|
28685
|
+
const pattern = new RegExp(`\\*\\*${fieldName}\\*\\*\\s*:\\s*(.+?)(?:\\n|$)`, "i");
|
|
28686
|
+
const match = text.match(pattern);
|
|
28687
|
+
return match?.[1]?.trim() ?? "";
|
|
28688
|
+
}
|
|
28689
|
+
function normalizePriority(raw) {
|
|
28690
|
+
const upper = raw.toUpperCase().trim();
|
|
28691
|
+
if (upper.startsWith("P0"))
|
|
28692
|
+
return "P0";
|
|
28693
|
+
if (upper.startsWith("P1"))
|
|
28694
|
+
return "P1";
|
|
28695
|
+
if (upper.startsWith("P2"))
|
|
28696
|
+
return "P2";
|
|
28697
|
+
return null;
|
|
28698
|
+
}
|
|
28699
|
+
function normalizeEffort(raw) {
|
|
28700
|
+
const lower = raw.toLowerCase().trim();
|
|
28701
|
+
if (lower.startsWith("low"))
|
|
28702
|
+
return "Low";
|
|
28703
|
+
if (lower.startsWith("medium") || lower.startsWith("med"))
|
|
28704
|
+
return "Medium";
|
|
28705
|
+
if (lower.startsWith("high"))
|
|
28706
|
+
return "High";
|
|
28707
|
+
return null;
|
|
28708
|
+
}
|
|
28709
|
+
|
|
28710
|
+
// src/tools/evolve-auto.ts
|
|
28711
|
+
var DEFAULT_AGENT_TIMEOUT_MS2 = 300000;
|
|
28712
|
+
var DEFAULT_MAX_SPAWNED = 15;
|
|
28713
|
+
async function withTimeout3(promise3, ms, label) {
|
|
28714
|
+
let timer;
|
|
28715
|
+
const timeout = new Promise((_, reject) => {
|
|
28716
|
+
timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
|
|
28717
|
+
});
|
|
28718
|
+
try {
|
|
28719
|
+
return await Promise.race([promise3, timeout]);
|
|
28720
|
+
} finally {
|
|
28721
|
+
clearTimeout(timer);
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
async function runEphemeralAgent(ctx, agentName, prompt, internalSessions, timeoutMs) {
|
|
28725
|
+
const sessionResp = await ctx.client.session.create({
|
|
28726
|
+
body: {},
|
|
28727
|
+
query: { directory: ctx.directory }
|
|
28728
|
+
});
|
|
28729
|
+
const sessionID = sessionResp.data?.id;
|
|
28730
|
+
if (!sessionID)
|
|
28731
|
+
throw new Error("Failed to create session");
|
|
28732
|
+
internalSessions.add(sessionID);
|
|
28733
|
+
try {
|
|
28734
|
+
await withTimeout3(ctx.client.session.prompt({
|
|
28735
|
+
path: { id: sessionID },
|
|
28736
|
+
body: {
|
|
28737
|
+
parts: [{ type: "text", text: prompt }],
|
|
28738
|
+
agent: agentName
|
|
28739
|
+
},
|
|
28740
|
+
query: { directory: ctx.directory }
|
|
28741
|
+
}), timeoutMs, `${agentName}`);
|
|
28742
|
+
const messagesResp = await ctx.client.session.messages({
|
|
28743
|
+
path: { id: sessionID },
|
|
28744
|
+
query: { directory: ctx.directory }
|
|
28745
|
+
});
|
|
28746
|
+
const messages = messagesResp.data ?? [];
|
|
28747
|
+
const lastAssistant = messages.filter((m) => m.info?.role === "assistant").pop();
|
|
28748
|
+
const rawResult = lastAssistant?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
28749
|
+
`) ?? "(No response)";
|
|
28750
|
+
return sanitizeSpawnResult(rawResult);
|
|
28751
|
+
} finally {
|
|
28752
|
+
internalSessions.delete(sessionID);
|
|
28753
|
+
await ctx.client.session.delete({ path: { id: sessionID }, query: { directory: ctx.directory } }).catch(() => {});
|
|
28754
|
+
}
|
|
28755
|
+
}
|
|
28756
|
+
function buildEvolvePrompt(evolveCtx) {
|
|
28757
|
+
const inventory = evolveCtx ? buildInventorySection(evolveCtx) : "(No capability data)";
|
|
28758
|
+
return `You are performing an automated self-improvement scan for the opencode-ultra plugin.
|
|
28759
|
+
|
|
28760
|
+
## Current Capabilities
|
|
28761
|
+
${inventory}
|
|
28762
|
+
|
|
28763
|
+
## Task
|
|
28764
|
+
1. Research the OpenCode plugin ecosystem (npm, GitHub) for features that opencode-ultra lacks
|
|
28765
|
+
2. Compare capabilities and identify gaps
|
|
28766
|
+
3. Propose concrete improvements in this EXACT format for EACH proposal:
|
|
28767
|
+
|
|
28768
|
+
## Improvement: [Feature Name]
|
|
28769
|
+
**Inspiration**: [Plugin name] \u2014 [what it does]
|
|
28770
|
+
**Current state**: [what opencode-ultra has now]
|
|
28771
|
+
**Why**: [concrete benefit]
|
|
28772
|
+
**How**: [which file to modify, what to add]
|
|
28773
|
+
**Effort**: Low / Medium / High
|
|
28774
|
+
**Priority**: P0 / P1 / P2
|
|
28775
|
+
|
|
28776
|
+
Sort by Priority then Effort. Be specific and actionable.`;
|
|
28777
|
+
}
|
|
28778
|
+
function buildInventorySection(ctx) {
|
|
28779
|
+
const lines = [];
|
|
28780
|
+
lines.push("### Tools: " + ctx.tools.join(", "));
|
|
28781
|
+
lines.push("### Hooks: " + ctx.hooks.join(", "));
|
|
28782
|
+
lines.push("### Agents");
|
|
28783
|
+
for (const [name, def] of Object.entries(ctx.agents)) {
|
|
28784
|
+
lines.push(`- ${name} (${def.model}) \u2014 ${def.description}`);
|
|
28785
|
+
}
|
|
28786
|
+
lines.push("### Features: " + ctx.features.join(", "));
|
|
28787
|
+
return lines.join(`
|
|
28788
|
+
`);
|
|
28789
|
+
}
|
|
28790
|
+
function buildImplementPrompt(proposal) {
|
|
28791
|
+
return `Implement the following improvement for opencode-ultra:
|
|
28792
|
+
|
|
28793
|
+
## ${proposal.title}
|
|
28794
|
+
- **Priority**: ${proposal.priority}
|
|
28795
|
+
- **Effort**: ${proposal.effort}
|
|
28796
|
+
- **Description**: ${proposal.description}
|
|
28797
|
+
${proposal.currentState ? `- **Current state**: ${proposal.currentState}` : ""}
|
|
28798
|
+
${proposal.inspiration ? `- **Inspiration**: ${proposal.inspiration}` : ""}
|
|
28799
|
+
${proposal.files ? `- **Files to modify**: ${proposal.files.join(", ")}` : ""}
|
|
28800
|
+
|
|
28801
|
+
Implement this change. Be precise and minimal. Only modify what is necessary.`;
|
|
28802
|
+
}
|
|
28803
|
+
function buildReviewPrompt(implementations) {
|
|
28804
|
+
return `Review the following implementation results from an automated evolve cycle:
|
|
28805
|
+
|
|
28806
|
+
${implementations.map((impl, i) => `### Implementation ${i + 1}
|
|
28807
|
+
${impl}`).join(`
|
|
28808
|
+
|
|
28809
|
+
---
|
|
28810
|
+
|
|
28811
|
+
`)}
|
|
28812
|
+
|
|
28813
|
+
Check for:
|
|
28814
|
+
1. Breaking changes
|
|
28815
|
+
2. Missing imports or type errors
|
|
28816
|
+
3. Logic bugs
|
|
28817
|
+
4. Security concerns
|
|
28818
|
+
5. Inconsistencies with existing code patterns
|
|
28819
|
+
|
|
28820
|
+
Report any blocking issues. If everything looks good, confirm with "LGTM".`;
|
|
28821
|
+
}
|
|
28822
|
+
function formatFinalReport(result) {
|
|
28823
|
+
const lines = [];
|
|
28824
|
+
lines.push("# Evolve Auto Results");
|
|
28825
|
+
lines.push("");
|
|
28826
|
+
if (result.proposals) {
|
|
28827
|
+
const accepted = result.proposals.filter((p) => p.accepted);
|
|
28828
|
+
const rejected = result.proposals.filter((p) => !p.accepted);
|
|
28829
|
+
lines.push("## Proposals");
|
|
28830
|
+
lines.push("");
|
|
28831
|
+
if (accepted.length > 0) {
|
|
28832
|
+
lines.push("### Accepted");
|
|
28833
|
+
for (const p of accepted) {
|
|
28834
|
+
lines.push(`- **${p.title}** [${p.priority}/${p.effort}] score=${p.score}`);
|
|
28835
|
+
}
|
|
28836
|
+
lines.push("");
|
|
28837
|
+
}
|
|
28838
|
+
if (rejected.length > 0) {
|
|
28839
|
+
lines.push("### Rejected");
|
|
28840
|
+
for (const p of rejected) {
|
|
28841
|
+
lines.push(`- **${p.title}** [${p.priority}/${p.effort}] score=${p.score} \u2014 ${p.reason}`);
|
|
28842
|
+
}
|
|
28843
|
+
lines.push("");
|
|
28844
|
+
}
|
|
28845
|
+
}
|
|
28846
|
+
if (result.implementations && result.implementations.length > 0) {
|
|
28847
|
+
lines.push("## Implementations");
|
|
28848
|
+
lines.push("");
|
|
28849
|
+
for (let i = 0;i < result.implementations.length; i++) {
|
|
28850
|
+
lines.push(`### Implementation ${i + 1}`);
|
|
28851
|
+
lines.push(result.implementations[i]);
|
|
28852
|
+
lines.push("");
|
|
28853
|
+
}
|
|
28854
|
+
}
|
|
28855
|
+
if (result.reviewOutput) {
|
|
28856
|
+
lines.push("## Review");
|
|
28857
|
+
lines.push(result.reviewOutput);
|
|
28858
|
+
lines.push("");
|
|
28859
|
+
}
|
|
28860
|
+
if (result.savedKey) {
|
|
28861
|
+
lines.push(`## Saved as \`${result.savedKey}\``);
|
|
28862
|
+
}
|
|
28863
|
+
return lines.join(`
|
|
28864
|
+
`);
|
|
28865
|
+
}
|
|
28866
|
+
function createEvolveAutoTool(ctx, internalSessions, deps) {
|
|
28867
|
+
const timeoutMs = deps.agentTimeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS2;
|
|
28868
|
+
return tool({
|
|
28869
|
+
description: `Autonomous self-improvement cycle for opencode-ultra.
|
|
28870
|
+
|
|
28871
|
+
Runs a 5-phase loop: SCAN \u2192 FILTER \u2192 IMPLEMENT \u2192 REVIEW \u2192 SAVE
|
|
28872
|
+
|
|
28873
|
+
Phases:
|
|
28874
|
+
1. SCAN \u2014 Sisyphus scans ecosystem and proposes improvements
|
|
28875
|
+
2. FILTER \u2014 Score and filter proposals by priority \xD7 effort
|
|
28876
|
+
3. IMPLEMENT \u2014 Hephaestus implements each accepted proposal sequentially
|
|
28877
|
+
4. REVIEW \u2014 Momus reviews all implementations
|
|
28878
|
+
5. SAVE \u2014 Results saved to continuity ledger
|
|
28879
|
+
|
|
28880
|
+
Use dryRun: true to run SCAN + FILTER only (preview proposals without implementing).`,
|
|
28881
|
+
args: {
|
|
28882
|
+
dryRun: tool.schema.boolean().optional().describe("SCAN+FILTER only, no implementation (default: false)"),
|
|
28883
|
+
maxProposals: tool.schema.number().optional().describe("Max proposals to implement per cycle (default: 3)"),
|
|
28884
|
+
minScore: tool.schema.number().optional().describe("Minimum score threshold (default: 5)"),
|
|
28885
|
+
skipReview: tool.schema.boolean().optional().describe("Skip momus review phase (default: false)")
|
|
28886
|
+
},
|
|
28887
|
+
execute: async (args, toolCtx) => {
|
|
28888
|
+
const dryRun = args.dryRun ?? false;
|
|
28889
|
+
const minScore = args.minScore ?? deps.evolveAutoConfig?.minScore ?? 5;
|
|
28890
|
+
const maxProposals = args.maxProposals ?? deps.evolveAutoConfig?.maxProposals ?? 3;
|
|
28891
|
+
const skipReview = args.skipReview ?? deps.evolveAutoConfig?.skipReview ?? false;
|
|
28892
|
+
const result = { phase: "INIT" };
|
|
28893
|
+
if (internalSessions.size >= DEFAULT_MAX_SPAWNED - 1) {
|
|
28894
|
+
return "Error: Too many concurrent sessions. Wait for active sessions to complete.";
|
|
28895
|
+
}
|
|
28896
|
+
result.phase = "SCAN";
|
|
28897
|
+
toolCtx.metadata({ title: "evolve_auto: SCAN \u2014 scanning ecosystem..." });
|
|
28898
|
+
log("evolve_auto: Phase 1 SCAN starting");
|
|
28899
|
+
let scanOutput;
|
|
28900
|
+
try {
|
|
28901
|
+
scanOutput = await runEphemeralAgent(ctx, "sisyphus", buildEvolvePrompt(deps.evolveCtx), internalSessions, timeoutMs);
|
|
28902
|
+
result.scanOutput = scanOutput;
|
|
28903
|
+
} catch (err) {
|
|
28904
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28905
|
+
log("evolve_auto: SCAN failed", { error: msg });
|
|
28906
|
+
return `## evolve_auto: SCAN Failed
|
|
28907
|
+
|
|
28908
|
+
**Error**: ${msg}`;
|
|
28909
|
+
}
|
|
28910
|
+
result.phase = "FILTER";
|
|
28911
|
+
toolCtx.metadata({ title: "evolve_auto: FILTER \u2014 scoring proposals..." });
|
|
28912
|
+
log("evolve_auto: Phase 2 PARSE+FILTER");
|
|
28913
|
+
const parsed = parseProposalsFromMarkdown(scanOutput);
|
|
28914
|
+
if (parsed.length === 0) {
|
|
28915
|
+
log("evolve_auto: no proposals parsed from scan output");
|
|
28916
|
+
return `## evolve_auto: No Proposals
|
|
28917
|
+
|
|
28918
|
+
Sisyphus scan completed but no improvement proposals were found in the output.
|
|
28919
|
+
|
|
28920
|
+
### Raw Scan Output
|
|
28921
|
+
${scanOutput}`;
|
|
28922
|
+
}
|
|
28923
|
+
const filtered = filterProposals(parsed, { minScore, maxProposals });
|
|
28924
|
+
result.proposals = filtered;
|
|
28925
|
+
const accepted = filtered.filter((p) => p.accepted);
|
|
28926
|
+
log("evolve_auto: filtered", {
|
|
28927
|
+
total: parsed.length,
|
|
28928
|
+
accepted: accepted.length,
|
|
28929
|
+
rejected: filtered.length - accepted.length
|
|
28930
|
+
});
|
|
28931
|
+
if (accepted.length === 0) {
|
|
28932
|
+
return `## evolve_auto: All Proposals Rejected
|
|
28933
|
+
|
|
28934
|
+
Parsed ${parsed.length} proposals, but none met the threshold (minScore=${minScore}).
|
|
28935
|
+
|
|
28936
|
+
${formatFinalReport(result)}`;
|
|
28937
|
+
}
|
|
28938
|
+
if (dryRun) {
|
|
28939
|
+
toolCtx.metadata({ title: `evolve_auto: DRY RUN \u2014 ${accepted.length} proposals accepted` });
|
|
28940
|
+
return `## evolve_auto: Dry Run Complete
|
|
28941
|
+
|
|
28942
|
+
${formatFinalReport(result)}
|
|
28943
|
+
|
|
28944
|
+
> Use \`evolve_auto({})\` for full execution.`;
|
|
28945
|
+
}
|
|
28946
|
+
result.phase = "IMPLEMENT";
|
|
28947
|
+
result.implementations = [];
|
|
28948
|
+
log("evolve_auto: Phase 3 IMPLEMENT", { count: accepted.length });
|
|
28949
|
+
for (let i = 0;i < accepted.length; i++) {
|
|
28950
|
+
const proposal = accepted[i];
|
|
28951
|
+
toolCtx.metadata({
|
|
28952
|
+
title: `evolve_auto: IMPLEMENT [${i + 1}/${accepted.length}] \u2014 ${proposal.title}`
|
|
28953
|
+
});
|
|
28954
|
+
try {
|
|
28955
|
+
const implResult = await runEphemeralAgent(ctx, "hephaestus", buildImplementPrompt(proposal), internalSessions, timeoutMs);
|
|
28956
|
+
result.implementations.push(implResult);
|
|
28957
|
+
log(`evolve_auto: implemented ${i + 1}/${accepted.length}`, { title: proposal.title });
|
|
28958
|
+
} catch (err) {
|
|
28959
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28960
|
+
log(`evolve_auto: IMPLEMENT failed for "${proposal.title}"`, { error: msg });
|
|
28961
|
+
result.implementations.push(`**FAILED**: ${proposal.title} \u2014 ${msg}`);
|
|
28962
|
+
break;
|
|
28963
|
+
}
|
|
28964
|
+
}
|
|
28965
|
+
if (!skipReview && result.implementations.length > 0) {
|
|
28966
|
+
result.phase = "REVIEW";
|
|
28967
|
+
toolCtx.metadata({ title: "evolve_auto: REVIEW \u2014 momus reviewing..." });
|
|
28968
|
+
log("evolve_auto: Phase 4 REVIEW");
|
|
28969
|
+
try {
|
|
28970
|
+
const reviewResult = await runEphemeralAgent(ctx, "momus", buildReviewPrompt(result.implementations), internalSessions, timeoutMs);
|
|
28971
|
+
result.reviewOutput = reviewResult;
|
|
28972
|
+
} catch (err) {
|
|
28973
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28974
|
+
log("evolve_auto: REVIEW failed", { error: msg });
|
|
28975
|
+
result.reviewOutput = `Review failed: ${msg}`;
|
|
28976
|
+
}
|
|
28977
|
+
}
|
|
28978
|
+
result.phase = "SAVE";
|
|
28979
|
+
toolCtx.metadata({ title: "evolve_auto: SAVE \u2014 saving results..." });
|
|
28980
|
+
log("evolve_auto: Phase 5 SAVE");
|
|
28981
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
28982
|
+
const savedKey = `evolve-auto-${dateStr}`;
|
|
28983
|
+
result.savedKey = savedKey;
|
|
28984
|
+
const report = formatFinalReport(result);
|
|
28985
|
+
const fs8 = await import("fs");
|
|
28986
|
+
const path8 = await import("path");
|
|
28987
|
+
const ledgerDir2 = path8.join(ctx.directory, ".opencode", "ledgers");
|
|
28988
|
+
try {
|
|
28989
|
+
await fs8.promises.mkdir(ledgerDir2, { recursive: true });
|
|
28990
|
+
await fs8.promises.writeFile(path8.join(ledgerDir2, `${savedKey}.md`), report, "utf-8");
|
|
28991
|
+
log("evolve_auto: saved to ledger", { key: savedKey });
|
|
28992
|
+
} catch (err) {
|
|
28993
|
+
log("evolve_auto: failed to save ledger", { error: String(err) });
|
|
28994
|
+
}
|
|
28995
|
+
toolCtx.metadata({ title: `evolve_auto: DONE \u2014 ${accepted.length} improvements` });
|
|
28996
|
+
return report;
|
|
28997
|
+
}
|
|
28998
|
+
});
|
|
28999
|
+
}
|
|
29000
|
+
|
|
28538
29001
|
// src/hooks/todo-enforcer.ts
|
|
28539
29002
|
var DEFAULT_MAX_ENFORCEMENTS = 5;
|
|
28540
29003
|
var sessionState = new Map;
|
|
@@ -29005,6 +29468,30 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
29005
29468
|
const astGrepBin = findAstGrepBinary();
|
|
29006
29469
|
const astSearch = astGrepBin ? createAstSearchTool(ctx, astGrepBin) : null;
|
|
29007
29470
|
const evolveApply = createEvolveApplyTool(ctx);
|
|
29471
|
+
const buildEvolveCtx = (toolNames) => ({
|
|
29472
|
+
agents: Object.fromEntries(Object.entries(agents).map(([name, def]) => [name, { model: def.model, description: def.description }])),
|
|
29473
|
+
tools: toolNames,
|
|
29474
|
+
hooks: [
|
|
29475
|
+
...!disabledHooks.has("keyword-detector") ? ["keyword-detector (ultrawork/search/analyze/think/evolve detection)"] : [],
|
|
29476
|
+
...!disabledHooks.has("rules-injector") ? ["rules-injector (architecture.md/codestyle.md/rules.md injection)"] : [],
|
|
29477
|
+
...!disabledHooks.has("fragment-injector") ? ["fragment-injector (conditional context fragments)"] : [],
|
|
29478
|
+
...!disabledHooks.has("prompt-renderer") ? ["prompt-renderer (template-based prompt rendering)"] : [],
|
|
29479
|
+
...!disabledHooks.has("todo-enforcer") ? ["todo-enforcer (force completion of unfinished TODOs)"] : [],
|
|
29480
|
+
...!disabledHooks.has("comment-checker") ? ["comment-checker (detect AI slop comments in code)"] : [],
|
|
29481
|
+
...!disabledHooks.has("token-truncation") ? ["token-truncation (auto-truncate large tool outputs)"] : [],
|
|
29482
|
+
...!disabledHooks.has("session-compaction") ? ["session-compaction (auto-compact long sessions)"] : []
|
|
29483
|
+
],
|
|
29484
|
+
features: [
|
|
29485
|
+
pool ? "concurrency control (semaphore-based per-provider/model limits)" : "no concurrency limits",
|
|
29486
|
+
"prompt injection sanitizer (17 patterns, 6 categories)",
|
|
29487
|
+
"trust score system (5-factor npm package evaluation)",
|
|
29488
|
+
"spawn limit (max concurrent sub-agents)",
|
|
29489
|
+
"agent timeout (per-agent execution timeout)",
|
|
29490
|
+
pluginConfig.categories ? "categories (model/variant routing per task type)" : "no categories configured",
|
|
29491
|
+
"continuity ledger (persistent key-value store across sessions)",
|
|
29492
|
+
"multi-language keyword detection (EN/JP/CN)"
|
|
29493
|
+
]
|
|
29494
|
+
});
|
|
29008
29495
|
const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
|
|
29009
29496
|
const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
|
|
29010
29497
|
const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
|
|
@@ -29046,6 +29533,14 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
29046
29533
|
if (!disabledTools.has("evolve_apply")) {
|
|
29047
29534
|
toolRegistry.evolve_apply = evolveApply;
|
|
29048
29535
|
}
|
|
29536
|
+
if (!disabledTools.has("evolve_auto")) {
|
|
29537
|
+
const evolveAuto = createEvolveAutoTool(ctx, internalSessions, {
|
|
29538
|
+
agentTimeoutMs: safetyConfig.agentTimeoutMs,
|
|
29539
|
+
evolveCtx: buildEvolveCtx(Object.keys(toolRegistry)),
|
|
29540
|
+
evolveAutoConfig: pluginConfig.evolve_auto
|
|
29541
|
+
});
|
|
29542
|
+
toolRegistry.evolve_auto = evolveAuto;
|
|
29543
|
+
}
|
|
29049
29544
|
return {
|
|
29050
29545
|
tool: toolRegistry,
|
|
29051
29546
|
config: async (config3) => {
|
|
@@ -29087,30 +29582,7 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
29087
29582
|
}
|
|
29088
29583
|
if (!disabledHooks.has("keyword-detector")) {
|
|
29089
29584
|
const promptText = extractPromptText(output.parts);
|
|
29090
|
-
const evolveCtx =
|
|
29091
|
-
agents: Object.fromEntries(Object.entries(agents).map(([name, def]) => [name, { model: def.model, description: def.description }])),
|
|
29092
|
-
tools: Object.keys(toolRegistry),
|
|
29093
|
-
hooks: [
|
|
29094
|
-
...!disabledHooks.has("keyword-detector") ? ["keyword-detector (ultrawork/search/analyze/think/evolve detection)"] : [],
|
|
29095
|
-
...!disabledHooks.has("rules-injector") ? ["rules-injector (architecture.md/codestyle.md/rules.md injection)"] : [],
|
|
29096
|
-
...!disabledHooks.has("fragment-injector") ? ["fragment-injector (conditional context fragments)"] : [],
|
|
29097
|
-
...!disabledHooks.has("prompt-renderer") ? ["prompt-renderer (template-based prompt rendering)"] : [],
|
|
29098
|
-
...!disabledHooks.has("todo-enforcer") ? ["todo-enforcer (force completion of unfinished TODOs)"] : [],
|
|
29099
|
-
...!disabledHooks.has("comment-checker") ? ["comment-checker (detect AI slop comments in code)"] : [],
|
|
29100
|
-
...!disabledHooks.has("token-truncation") ? ["token-truncation (auto-truncate large tool outputs)"] : [],
|
|
29101
|
-
...!disabledHooks.has("session-compaction") ? ["session-compaction (auto-compact long sessions)"] : []
|
|
29102
|
-
],
|
|
29103
|
-
features: [
|
|
29104
|
-
pool ? "concurrency control (semaphore-based per-provider/model limits)" : "no concurrency limits",
|
|
29105
|
-
"prompt injection sanitizer (17 patterns, 6 categories)",
|
|
29106
|
-
"trust score system (5-factor npm package evaluation)",
|
|
29107
|
-
"spawn limit (max concurrent sub-agents)",
|
|
29108
|
-
"agent timeout (per-agent execution timeout)",
|
|
29109
|
-
pluginConfig.categories ? "categories (model/variant routing per task type)" : "no categories configured",
|
|
29110
|
-
"continuity ledger (persistent key-value store across sessions)",
|
|
29111
|
-
"multi-language keyword detection (EN/JP/CN)"
|
|
29112
|
-
]
|
|
29113
|
-
};
|
|
29585
|
+
const evolveCtx = buildEvolveCtx(Object.keys(toolRegistry));
|
|
29114
29586
|
const detected = detectKeywords(promptText, evolveCtx);
|
|
29115
29587
|
if (detected.length > 0) {
|
|
29116
29588
|
const hasUltrawork = detected.some((k) => k.type === "ultrawork");
|
package/dist/safety/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { sanitizeAgentOutput, sanitizeSpawnResult, type SanitizeResult } from "./sanitizer";
|
|
2
|
-
export { computeTrustScore, isTyposquatSuspect, formatTrustTable, type PackageMetadata, type TrustScoreResult, type TrustFactor, } from "./trust-score";
|
|
2
|
+
export { computeTrustScore, isTyposquatSuspect, isHighPrivilege, formatTrustTable, MAX_TRUST_SCORE, type PackageMetadata, type TrustScoreResult, type TrustFactor, } from "./trust-score";
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
* Trust Score — evaluates npm packages for reliability and safety.
|
|
3
3
|
* Used by evolve mode to rank plugin recommendations.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Factors (max 108):
|
|
6
|
+
* recency (25), popularity (25), quality (20), repository (15),
|
|
7
|
+
* safety (15), provenance (8), dependency_risk (0 or -5 penalty)
|
|
8
|
+
*
|
|
9
|
+
* Levels (by percentage of 108):
|
|
10
|
+
* 90%+ HIGH | 70-89% MEDIUM | 40-69% LOW | <40% RISKY
|
|
10
11
|
*/
|
|
12
|
+
export declare const MAX_TRUST_SCORE = 108;
|
|
11
13
|
export interface PackageMetadata {
|
|
12
14
|
name: string;
|
|
13
15
|
version?: string;
|
|
14
16
|
description?: string;
|
|
15
17
|
license?: string;
|
|
18
|
+
/** Whether npm provenance/attestation is available */
|
|
19
|
+
hasProvenance?: boolean;
|
|
16
20
|
/** ISO date string of last publish */
|
|
17
21
|
lastPublished?: string;
|
|
18
22
|
/** Weekly npm downloads */
|
|
@@ -42,6 +46,8 @@ export interface TrustFactor {
|
|
|
42
46
|
}
|
|
43
47
|
export declare function computeTrustScore(meta: PackageMetadata): TrustScoreResult;
|
|
44
48
|
export declare function isTyposquatSuspect(name: string): boolean;
|
|
49
|
+
/** Check if package name suggests high-privilege access (PTY, exec, shell, sudo) */
|
|
50
|
+
export declare function isHighPrivilege(name: string): boolean;
|
|
45
51
|
/**
|
|
46
52
|
* Format trust scores as a markdown table for evolve output.
|
|
47
53
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evolve-filter — Pure scoring and filtering logic for evolve proposals.
|
|
3
|
+
*
|
|
4
|
+
* No side effects. All functions are deterministic.
|
|
5
|
+
*/
|
|
6
|
+
export interface EvolveProposal {
|
|
7
|
+
title: string;
|
|
8
|
+
priority: "P0" | "P1" | "P2";
|
|
9
|
+
effort: "Low" | "Medium" | "High";
|
|
10
|
+
description: string;
|
|
11
|
+
files?: string[];
|
|
12
|
+
currentState?: string;
|
|
13
|
+
inspiration?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FilteredProposal extends EvolveProposal {
|
|
16
|
+
score: number;
|
|
17
|
+
accepted: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface FilterConfig {
|
|
21
|
+
minScore?: number;
|
|
22
|
+
maxProposals?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function scoreProposal(p: EvolveProposal): number;
|
|
25
|
+
export declare function filterProposals(proposals: EvolveProposal[], config?: FilterConfig): FilteredProposal[];
|
|
26
|
+
/**
|
|
27
|
+
* Parse Sisyphus evolve output into structured proposals.
|
|
28
|
+
*
|
|
29
|
+
* Expected format (from keyword-detector EVOLVE_MESSAGE Phase 3):
|
|
30
|
+
* ```
|
|
31
|
+
* ## Improvement: [Feature Name]
|
|
32
|
+
* **Inspiration**: [Plugin name] — [what it does]
|
|
33
|
+
* **Current state**: [what opencode-ultra has now]
|
|
34
|
+
* **Why**: [concrete benefit]
|
|
35
|
+
* **How**: [which file to modify, what to add]
|
|
36
|
+
* **Effort**: Low / Medium / High
|
|
37
|
+
* **Priority**: P0 / P1 / P2
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseProposalsFromMarkdown(markdown: string): EvolveProposal[];
|