Skip to content
Open
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
22 changes: 22 additions & 0 deletions .agents/agents/variants/opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "./agent-variant.schema.json",
"vendor": "opencode",
"destDir": ".opencode/agents",
"modelDefault": "opencode-go/deepseek-v4-flash",
"toolsDefault": [],
"protocolPath": ".agents/skills/_shared/runtime/execution-protocols/opencode.md",
"agents": {
"backend-engineer": {},
"frontend-engineer": {},
"db-engineer": {},
"debug-investigator": {},
"architecture-reviewer": {},
"tf-infra-engineer": {},
"mobile-engineer": {},
"pm-planner": {},
"qa-reviewer": {},
"docs-curator": {},
"refactor-engineer": {},
"research-explorer": {}
}
}
15 changes: 0 additions & 15 deletions .agents/config/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,6 @@ runtime_profiles:
tf-infra: { model: "openai/gpt-5.5", effort: "high" }
explore: { model: "openai/gpt-5.4-mini", effort: "low" }

gemini:
description: "Gemini — Google AI Pro"
agent_defaults:
orchestrator: { model: "google/gemini-3-flash" }
architecture: { model: "google/gemini-3.1-pro-preview", thinking: true }
qa: { model: "google/gemini-3-flash", thinking: true }
pm: { model: "google/gemini-3-flash" }
backend: { model: "google/gemini-3-flash", thinking: true }
frontend: { model: "google/gemini-3-flash", thinking: true }
mobile: { model: "google/gemini-3-flash", thinking: true }
db: { model: "google/gemini-3-flash", thinking: true }
debug: { model: "google/gemini-3-flash", thinking: true }
tf-infra: { model: "google/gemini-3-flash", thinking: true }
explore: { model: "google/gemini-3.1-flash-lite" }

mixed:
description: "Mixed — role-optimal vendors per agent (Claude for orchestration/QA/PM, Codex for impl, Gemini for explore)"
agent_defaults:
Expand Down
58 changes: 57 additions & 1 deletion .agents/hooks/core/agentmemory-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,63 @@ export interface RecalledFact {

interface SearchResult {
score?: number;
timestamp?: unknown;
created_at?: unknown;
observation?: {
narrative?: unknown;
facts?: unknown;
title?: unknown;
type?: unknown;
timestamp?: unknown;
created_at?: unknown;
};
}

function parseSearchResults(body: string, k: number): RecalledFact[] {
/**
* Recall TTL: facts older than this many days are dropped from the snapshot so
* stale, long-resolved decisions stop rehydrating every boundary. Default 30
* days; set `OMA_RECALL_MAX_AGE_DAYS=0` (or a non-positive value) to disable.
* Returns the max age in ms, or null when disabled.
*/
function recallMaxAgeMs(): number | null {
const raw = process.env.OMA_RECALL_MAX_AGE_DAYS;
const days = raw === undefined ? 30 : Number(raw);
if (!Number.isFinite(days) || days <= 0) return null;
return days * 24 * 60 * 60 * 1000;
}

/**
* Best-effort timestamp extraction from a search result. AgentMemory's response
* envelope is not contractually fixed across versions, so several candidate
* field names / locations are probed. Numeric epoch seconds are normalised to
* ms. Returns null when no parseable timestamp is present — callers then keep
* the fact (TTL filtering is fail-open, never dropping facts of unknown age).
*/
function extractTimestampMs(entry: SearchResult): number | null {
const obs = entry.observation ?? {};
const candidates: unknown[] = [
obs.timestamp,
obs.created_at,
entry.timestamp,
entry.created_at,
];
for (const candidate of candidates) {
if (typeof candidate === "number" && Number.isFinite(candidate)) {
return candidate < 1e12 ? candidate * 1000 : candidate;
}
if (typeof candidate === "string" && candidate.trim()) {
const parsed = Date.parse(candidate);
if (Number.isFinite(parsed)) return parsed;
}
}
return null;
}

export function parseSearchResults(
body: string,
k: number,
nowMs: number = Date.now(),
): RecalledFact[] {
let parsed: { results?: unknown };
try {
parsed = JSON.parse(body) as { results?: unknown };
Expand All @@ -164,12 +212,20 @@ function parseSearchResults(body: string, k: number): RecalledFact[] {
return Number.isFinite(raw) ? raw : 1;
})();

const maxAgeMs = recallMaxAgeMs();
const cutoffMs = maxAgeMs === null ? null : nowMs - maxAgeMs;

const facts: RecalledFact[] = [];
for (const entry of parsed.results as SearchResult[]) {
const score = typeof entry.score === "number" ? entry.score : 0;
// Raw `/observe` envelopes score near-zero (~0.006); enriched facts score
// in the single digits. Drop the noise floor so the snapshot stays useful.
if (score < minScore) continue;
// TTL: drop facts older than the cutoff (fail-open on unknown age).
if (cutoffMs !== null) {
const tsMs = extractTimestampMs(entry);
if (tsMs !== null && tsMs < cutoffMs) continue;
}
const obs = entry.observation ?? {};
const narrative =
typeof obs.narrative === "string" && obs.narrative.trim()
Expand Down
48 changes: 48 additions & 0 deletions .agents/hooks/core/triggers.json
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,54 @@
"会议转录"
]
}
},
"oma-refactor": {
"keywords": {
"*": ["oma-refactor"],
"en": [
"refactor this",
"refactoring plan",
"extract class",
"extract method",
"reduce complexity",
"measure complexity",
"code smell",
"technical debt",
"characterization test",
"hotspot analysis",
"split this class",
"clean up this code"
],
"ko": [
"리팩토링",
"리팩터링",
"복잡도 줄여",
"복잡도 측정",
"코드 스멜",
"기술 부채",
"클래스 분리",
"메서드 추출",
"코드 정리해줘"
],
"ja": [
"リファクタリング",
"複雑度を下げ",
"複雑度を測定",
"コードスメル",
"技術的負債",
"クラスを分割",
"メソッド抽出"
],
"zh": [
"重构",
"降低复杂度",
"测量复杂度",
"代码异味",
"技术债",
"拆分类",
"提取方法"
]
}
}
},
"informationalPatterns": {
Expand Down
8 changes: 4 additions & 4 deletions .agents/hooks/variants/claude.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
"UserPromptSubmit": [
{
"hook": "keyword-detector.ts",
"timeout": 5
"timeout": 2
},
{
"hook": "state-boundary.ts",
"timeout": 5
"timeout": 4
},
{
"hook": "skill-injector.ts",
"timeout": 3
"timeout": 2
},
{
"hook": "serena-primer.ts",
"timeout": 3
"timeout": 2
}
],
"PreToolUse": {
Expand Down
Loading