Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/gep/candidates.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function extractCapabilityCandidates({ recentSessionTranscript, signals }) {
];

for (const sc of signalCandidates) {
if (!signalList.includes(sc.signal)) continue;
if (!signalList.some(s => s === sc.signal || s.startsWith(sc.signal + ':'))) continue;
const evidence = `Signal present: ${sc.signal}`;
const shape = buildFiveQuestionsShape({ title: sc.title, signals, evidence });
candidates.push({
Expand Down
4 changes: 3 additions & 1 deletion src/gep/mutation.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ var OPPORTUNITY_SIGNALS = [
function hasOpportunitySignal(signals) {
var list = Array.isArray(signals) ? signals.map(function (s) { return String(s || ''); }) : [];
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
if (list.includes(OPPORTUNITY_SIGNALS[i])) return true;
var name = OPPORTUNITY_SIGNALS[i];
if (list.includes(name)) return true;
if (list.some(function (s) { return s.startsWith(name + ':'); })) return true;
}
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/gep/questionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function generateQuestions(opts) {
}

// --- Strategy 5: User feature requests the agent can amplify ---
if (signalSet.has('user_feature_request')) {
if (signalSet.has('user_feature_request') || signals.some(function (s) { return String(s).startsWith('user_feature_request:'); })) {
var featureLines = transcript.split('\n').filter(function(l) {
return /\b(add|implement|create|build|i want|i need|please add)\b/i.test(l);
});
Expand Down
103 changes: 84 additions & 19 deletions src/gep/signals.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ var OPPORTUNITY_SIGNALS = [
function hasOpportunitySignal(signals) {
var list = Array.isArray(signals) ? signals : [];
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
if (list.includes(OPPORTUNITY_SIGNALS[i])) return true;
var name = OPPORTUNITY_SIGNALS[i];
if (list.includes(name)) return true;
// Signals may carry extra as "name:snippet"
if (list.some(function (s) { return String(s).startsWith(name + ':'); })) return true;
}
return false;
}
Expand Down Expand Up @@ -49,8 +52,12 @@ function analyzeRecentHistory(recentEvents) {
var sigs = Array.isArray(evt.signals) ? evt.signals : [];
for (var k = 0; k < sigs.length; k++) {
var s = String(sigs[k]);
// Normalize: ignore errsig details for frequency counting
var key = s.startsWith('errsig:') ? 'errsig' : s.startsWith('recurring_errsig') ? 'recurring_errsig' : s;
// Normalize: strip details suffix so frequency keys match dedup filter keys
var key = s.startsWith('errsig:') ? 'errsig'
: s.startsWith('recurring_errsig') ? 'recurring_errsig'
: s.startsWith('user_feature_request:') ? 'user_feature_request'
: s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
: s;
signalFreq[key] = (signalFreq[key] || 0) + 1;
}
var genes = Array.isArray(evt.genes_used) ? evt.genes_used : [];
Expand Down Expand Up @@ -137,15 +144,15 @@ function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, user
String(userSnippet || ''),
].join('\n');
var lower = corpus.toLowerCase();

// Analyze recent evolution history for de-duplication
var history = analyzeRecentHistory(recentEvents || []);

// --- Defensive signals (errors, missing resources) ---

// Refined error detection regex to avoid false positives on "fail"/"failed" in normal text.
// We prioritize structured error markers ([error], error:, exception:) and specific JSON patterns.
var errorHit = /\[error\]|error:|exception:|iserror":true|"status":\s*"error"|"status":\s*"failed"/.test(lower);
// Chinese: 错误、异常、失败、报错 — all require contextual colon [::] so improvement text (e.g. "改进一下错误处理") is not treated as log_error.
var errorHit = /\[error\]|error:|exception:|iserror":true|"status":\s*"error"|"status":\s*"failed"|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/.test(lower);
if (errorHit) signals.push('log_error');

// Error signature (more reproducible than a coarse "log_error" tag).
Expand All @@ -156,7 +163,7 @@ function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, user
.filter(Boolean);

var errLine =
lines.find(function (l) { return /\b(typeerror|referenceerror|syntaxerror)\b\s*:|error\s*:|exception\s*:|\[error/i.test(l); }) ||
lines.find(function (l) { return /\b(typeerror|referenceerror|syntaxerror)\b\s*:|error\s*:|exception\s*:|\[error|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/i.test(l); }) ||
null;

if (errLine) {
Expand Down Expand Up @@ -200,27 +207,77 @@ function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, user
}

// --- Opportunity signals (innovation / feature requests) ---

// user_feature_request: user explicitly asks for a new capability
// Look for action verbs + object patterns that indicate a feature request
if (/\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,60}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i.test(corpus)) {
signals.push('user_feature_request');
// Support 4 languages: 简中、繁中、英、日. Attach extra info (snippet) for selector/prompt use.

var featureRequestSnippet = '';
// English
var featEn = corpus.match(/\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,120}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i);
if (featEn) featureRequestSnippet = featEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
if (!featureRequestSnippet && /\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower)) {
var featWant = corpus.match(/.{0,80}\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b.{0,80}/i);
featureRequestSnippet = featWant ? featWant[0].replace(/\s+/g, ' ').trim().slice(0, 200) : 'feature request';
}
// 简中(含「我想……」:截取描述至 200 字)
if (!featureRequestSnippet && /加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus)) {
var featZh = corpus.match(/.{0,100}(加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能).{0,100}/);
if (featZh) featureRequestSnippet = featZh[0].replace(/\s+/g, ' ').trim().slice(0, 200);
if (!featureRequestSnippet && /我想/.test(corpus)) {
var featWantZh = corpus.match(/我想\s*[,,\.。、\s]*([\s\S]{0,400})/);
featureRequestSnippet = featWantZh ? (featWantZh[1].replace(/\s+/g, ' ').trim().slice(0, 200) || '功能需求') : '功能需求';
}
if (!featureRequestSnippet) featureRequestSnippet = '功能需求';
}
// Also catch direct "I want/need X" patterns
if (/\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower)) {
signals.push('user_feature_request');
// 繁中
if (!featureRequestSnippet && /加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus)) {
var featTw = corpus.match(/.{0,100}(加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加).{0,100}/);
featureRequestSnippet = featTw ? featTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '功能需求';
}
// 日
if (!featureRequestSnippet && /追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
var featJa = corpus.match(/.{0,100}(追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい).{0,100}/);
featureRequestSnippet = featJa ? featJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '機能要望';
}
if (featureRequestSnippet || /\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,60}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i.test(corpus) ||
/\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower) ||
/加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus) ||
/加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus) ||
/追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
signals.push('user_feature_request:' + (featureRequestSnippet || ''));
}

// user_improvement_suggestion: user suggests making something better
if (/\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b/i.test(lower)) {
// Only fire if there is no active error (to distinguish from repair requests)
if (!errorHit) signals.push('user_improvement_suggestion');
// user_improvement_suggestion: 4 languages + extra
var improvementSnippet = '';
if (!errorHit) {
var impEn = corpus.match(/.{0,80}\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b.{0,80}/i);
if (impEn) improvementSnippet = impEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
if (!improvementSnippet && /改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus)) {
var impZh = corpus.match(/.{0,100}(改进一下|优化一下|简化|重构|整理一下|弄得更好).{0,100}/);
improvementSnippet = impZh ? impZh[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改进建议';
}
if (!improvementSnippet && /改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus)) {
var impTw = corpus.match(/.{0,100}(改進一下|優化一下|簡化|重構|整理一下|弄得更好).{0,100}/);
improvementSnippet = impTw ? impTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改進建議';
}
if (!improvementSnippet && /改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus)) {
var impJa = corpus.match(/.{0,100}(改善|最適化|簡素化|リファクタ|良くして|改良).{0,100}/);
improvementSnippet = impJa ? impJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改善要望';
}
var hasImprovement = improvementSnippet ||
/\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b/i.test(lower) ||
/改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus) ||
/改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus) ||
/改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus);
if (hasImprovement) signals.push('user_improvement_suggestion:' + (improvementSnippet || ''));
}

// perf_bottleneck: performance issues detected
if (/\b(slow|timeout|timed?\s*out|latency|bottleneck|took too long|performance issue|high cpu|high memory|oom|out of memory)\b/i.test(lower)) {
signals.push('perf_bottleneck');
}
// Chinese: 慢/超时/卡顿/性能/内存溢出
if (/太慢|超时|卡顿|性能问题|内存溢出|跑不动|很慢/.test(corpus)) {
signals.push('perf_bottleneck');
}

// capability_gap: something is explicitly unsupported or missing
if (/\b(not supported|cannot|doesn'?t support|no way to|missing feature|unsupported|not available|not implemented|no support for)\b/i.test(lower)) {
Expand All @@ -229,6 +286,12 @@ function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, user
signals.push('capability_gap');
}
}
// Chinese: 不支持/没法/无法/没有这个功能
if (/不支持|没法|无法\s*实现|没有这个功能|还不支持/.test(corpus)) {
if (!signals.includes('memory_missing') && !signals.includes('user_missing') && !signals.includes('session_logs_missing')) {
signals.push('capability_gap');
}
}

// --- Tool Usage Analytics ---
var toolUsage = {};
Expand Down Expand Up @@ -273,7 +336,9 @@ function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, user
var beforeDedup = signals.length;
signals = signals.filter(function (s) {
// Normalize signal key for comparison
var key = s.startsWith('errsig:') ? 'errsig' : s.startsWith('recurring_errsig') ? 'recurring_errsig' : s;
var key = s.startsWith('errsig:') ? 'errsig' : s.startsWith('recurring_errsig') ? 'recurring_errsig'
: s.startsWith('user_feature_request:') ? 'user_feature_request' : s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
: s;
return !history.suppressedSignals.has(key);
});
if (beforeDedup > 0 && signals.length === 0) {
Expand Down
15 changes: 14 additions & 1 deletion test/selector.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const GENES = [
type: 'Gene',
id: 'gene_innovate',
category: 'innovate',
signals_match: ['user_feature_request', 'capability_gap', 'stable_success_plateau'],
signals_match: ['user_feature_request', 'user_improvement_suggestion', 'capability_gap', 'stable_success_plateau'],
strategy: ['build it'],
validation: ['node -e "true"'],
},
Expand Down Expand Up @@ -81,6 +81,19 @@ describe('selectGene', () => {
// With preference, it should be selected even if gene_repair scores higher
assert.equal(result.selected.id, 'gene_optimize');
});

it('matches gene when signal carries extra info (user_feature_request:snippet)', () => {
// Signal format: "user_feature_request:加个支付模块" — selector matches by substring includes(pattern)
const result = selectGene(GENES, ['user_feature_request:加个支付模块,要支持微信和支付宝'], {});
assert.ok(result.selected, 'should select a gene');
assert.equal(result.selected.id, 'gene_innovate', 'innovate gene has signals_match user_feature_request');
});

it('matches gene when signal carries extra info (user_improvement_suggestion:snippet)', () => {
const result = selectGene(GENES, ['user_improvement_suggestion:refactor the payment module and simplify the API'], {});
assert.ok(result.selected);
assert.equal(result.selected.id, 'gene_innovate', 'innovate gene has signals_match user_improvement_suggestion');
});
});

describe('selectCapsule', () => {
Expand Down
Loading