feat: enforce hard test-tier requirement across all DevPlace workflow agents and feature-builder docs
DevPlace CI / test (push) Failing after 21m53s

Update the feature-builder agent prompt, test-maintainer agent, and all four workflow JS files (devii-tool, endpoint, feature, job-service) to codify the DevPlace test standard as a non-optional project requirement: one test file per endpoint, directory tree mirroring the URL/source path, split into three tiers (unit, api, e2e). Add explicit Test phases to devii-tool, endpoint, feature, and job-service workflows, and embed tier-specific test instructions (path mapping, fixture choice, coverage scope) directly in each workflow's meta description and TESTS constant.
This commit is contained in:
2026-06-15 12:10:14 +00:00
parent 3c7527988e
commit e1874f9b6a
71 changed files with 2198 additions and 145 deletions
+70 -8
View File
@@ -6,7 +6,8 @@ import hashlib
import logging
import math
import re
from dataclasses import dataclass
import time
from dataclasses import dataclass, field
from devplacepy import stealth
from devplacepy.config import INTERNAL_EMBED_MODEL, INTERNAL_EMBED_URL
@@ -22,6 +23,23 @@ TOKEN_PATTERN = re.compile(r"[a-z0-9]+")
class EmbedResult:
vectors: list[list[float]]
backend: str
latency_ms: int = 0
cache_hits: int = 0
@dataclass
class EmbeddingCache:
store: dict[str, list[float]] = field(default_factory=dict)
def key(self, text: str) -> str:
return hashlib.sha1((text or "").encode("utf-8")).hexdigest()
def get(self, text: str) -> list[float] | None:
return self.store.get(self.key(text))
def put(self, text: str, vector: list[float]) -> None:
if vector:
self.store[self.key(text)] = vector
def _local_vector(text: str) -> list[float]:
@@ -41,19 +59,51 @@ def _local_vector(text: str) -> list[float]:
def local_embed(texts: list[str]) -> EmbedResult:
return EmbedResult(vectors=[_local_vector(text) for text in texts], backend="local")
start = time.monotonic()
vectors = [_local_vector(text) for text in texts]
latency_ms = int((time.monotonic() - start) * 1000)
return EmbedResult(vectors=vectors, backend="local", latency_ms=latency_ms)
async def embed_texts(
texts: list[str], api_key: str, *, gateway_url: str = INTERNAL_EMBED_URL
texts: list[str],
api_key: str,
*,
gateway_url: str = INTERNAL_EMBED_URL,
cache: EmbeddingCache | None = None,
) -> EmbedResult:
if not texts:
return EmbedResult(vectors=[], backend="empty")
cache_hits = 0
pending_index: list[int] = []
pending_text: list[str] = []
resolved: list[list[float] | None] = [None] * len(texts)
if cache is not None:
for index, text in enumerate(texts):
cached_vector = cache.get(text)
if cached_vector is not None:
resolved[index] = cached_vector
cache_hits += 1
else:
pending_index.append(index)
pending_text.append(text)
else:
pending_index = list(range(len(texts)))
pending_text = list(texts)
if not pending_text:
return EmbedResult(
vectors=[vector or [] for vector in resolved],
backend="cache",
latency_ms=0,
cache_hits=cache_hits,
)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {"model": INTERNAL_EMBED_MODEL, "input": texts}
payload = {"model": INTERNAL_EMBED_MODEL, "input": pending_text}
start = time.monotonic()
backend = "gateway"
try:
async with stealth.stealth_async_client(timeout=EMBED_TIMEOUT_SECONDS) as client:
response = await client.post(gateway_url, json=payload, headers=headers)
@@ -61,10 +111,22 @@ async def embed_texts(
raise RuntimeError(f"embed gateway returned {response.status_code}")
data = response.json()
rows = data.get("data") or []
vectors = [row.get("embedding") or [] for row in rows]
if len(vectors) != len(texts) or any(not vector for vector in vectors):
fresh = [row.get("embedding") or [] for row in rows]
if len(fresh) != len(pending_text) or any(not vector for vector in fresh):
raise RuntimeError("embed gateway returned an incomplete response")
return EmbedResult(vectors=vectors, backend="gateway")
except Exception as exc:
logger.warning("deepsearch embedding gateway failed, using local: %s", exc)
return local_embed(texts)
fresh = [_local_vector(text) for text in pending_text]
backend = "local"
latency_ms = int((time.monotonic() - start) * 1000)
for offset, index in enumerate(pending_index):
vector = fresh[offset]
resolved[index] = vector
if cache is not None:
cache.put(pending_text[offset], vector)
return EmbedResult(
vectors=[vector or [] for vector in resolved],
backend=backend,
latency_ms=latency_ms,
cache_hits=cache_hits,
)
+29 -9
View File
@@ -15,8 +15,7 @@ logger = logging.getLogger(__name__)
TOKEN_PATTERN = re.compile(r"[a-z0-9]+")
BM25_K1 = 1.5
BM25_B = 0.75
HYBRID_VECTOR_WEIGHT = 0.6
HYBRID_KEYWORD_WEIGHT = 0.4
RRF_K = 60.0
DEFAULT_TOP_K = 8
CANDIDATE_MULTIPLIER = 4
@@ -182,18 +181,39 @@ class VectorStore:
)
if not candidates:
return []
vector_rank = sorted(
candidates, key=lambda chunk: chunk.score, reverse=True
)
keyword = self.keyword_scores(query, candidates)
vec_max = max((chunk.score for chunk in candidates), default=0.0) or 1.0
kw_max = max(keyword.values(), default=0.0) or 1.0
keyword_rank = sorted(
candidates, key=lambda chunk: keyword.get(chunk.uid, 0.0), reverse=True
)
fused: dict[str, float] = {}
for rank, chunk in enumerate(vector_rank, start=1):
fused[chunk.uid] = fused.get(chunk.uid, 0.0) + 1.0 / (RRF_K + rank)
for rank, chunk in enumerate(keyword_rank, start=1):
fused[chunk.uid] = fused.get(chunk.uid, 0.0) + 1.0 / (RRF_K + rank)
for chunk in candidates:
vec_norm = max(0.0, chunk.score) / vec_max
kw_norm = keyword.get(chunk.uid, 0.0) / kw_max
chunk.score = (
HYBRID_VECTOR_WEIGHT * vec_norm + HYBRID_KEYWORD_WEIGHT * kw_norm
)
chunk.score = fused.get(chunk.uid, 0.0)
candidates.sort(key=lambda chunk: chunk.score, reverse=True)
return candidates[:top_k]
def coverage_analytics(self) -> dict:
chunks = self.all_chunks()
if not chunks:
return {"chunks": 0, "domains": 0, "sources": 0, "avg_chunk_chars": 0}
domains = {chunk.metadata.get("url", "") for chunk in chunks}
domains.discard("")
sources = {chunk.source for chunk in chunks}
sources.discard("")
avg_chars = int(sum(len(chunk.text) for chunk in chunks) / len(chunks))
return {
"chunks": len(chunks),
"domains": len(domains),
"sources": len(sources),
"avg_chunk_chars": avg_chars,
}
def drop(self) -> None:
try:
import chromadb