feat: add container manager API, islop router, and container runtime files with vim/bot/d stealth clients
DevPlace CI / test (push) Failing after 38m17s
DevPlace CI / test (push) Failing after 38m17s
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.services.jobs.isslop.badge import badge_html, badge_markdown, render_badge
|
||||
|
||||
|
||||
def test_render_badge_with_grade():
|
||||
svg = render_badge(87.5, "A", "https://devplace.example/tools/isslop/abc/report")
|
||||
assert svg.startswith("<svg")
|
||||
assert "88%" in svg or "87%" in svg
|
||||
assert "authenticity" in svg
|
||||
assert "https://devplace.example/tools/isslop/abc/report" in svg
|
||||
|
||||
|
||||
def test_render_badge_pending():
|
||||
svg = render_badge(None, None, "https://devplace.example/tools/isslop/abc/report")
|
||||
assert "analyzing" in svg
|
||||
|
||||
|
||||
def test_render_badge_escapes_url():
|
||||
svg = render_badge(50.0, "C", 'https://x.example/"><script>')
|
||||
assert "<script>" not in svg
|
||||
|
||||
|
||||
def test_badge_snippets():
|
||||
markdown = badge_markdown("https://x/badge.svg", "https://x/report")
|
||||
assert markdown == "[](https://x/report)"
|
||||
html = badge_html("https://x/badge.svg", "https://x/report")
|
||||
assert '<img src="https://x/badge.svg"' in html
|
||||
@@ -0,0 +1,31 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import json
|
||||
|
||||
from devplacepy.services.jobs.isslop.events import KIND_DONE, WorkerEvent
|
||||
|
||||
|
||||
def test_event_roundtrip():
|
||||
event = WorkerEvent(kind=KIND_DONE, message="Analysis complete", data={"grade": "A"})
|
||||
parsed = WorkerEvent.parse(event.to_json())
|
||||
assert parsed is not None
|
||||
assert parsed.kind == KIND_DONE
|
||||
assert parsed.message == "Analysis complete"
|
||||
assert parsed.data == {"grade": "A"}
|
||||
|
||||
|
||||
def test_parse_rejects_non_json_lines():
|
||||
assert WorkerEvent.parse("") is None
|
||||
assert WorkerEvent.parse("plain log output") is None
|
||||
assert WorkerEvent.parse("{broken json") is None
|
||||
|
||||
|
||||
def test_parse_rejects_missing_fields():
|
||||
assert WorkerEvent.parse(json.dumps({"kind": "log"})) is None
|
||||
assert WorkerEvent.parse(json.dumps({"message": "no kind"})) is None
|
||||
|
||||
|
||||
def test_parse_tolerates_non_dict_data():
|
||||
parsed = WorkerEvent.parse(json.dumps({"kind": "log", "message": "x", "data": [1, 2]}))
|
||||
assert parsed is not None
|
||||
assert parsed.data == {}
|
||||
@@ -0,0 +1,87 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from devplacepy.services.jobs.isslop.analysis.engine import _javascript_alias_prefixes
|
||||
from devplacepy.services.jobs.isslop.analysis.signals import FileContext, RepoContext
|
||||
from devplacepy.services.jobs.isslop.analysis.signals.hallucination import detect_hallucination
|
||||
|
||||
|
||||
def _context(text: str, aliases: frozenset = frozenset(), deps: frozenset = frozenset({"next", "react", "@clerk/nextjs"})):
|
||||
repo = RepoContext(
|
||||
root=Path("/tmp"),
|
||||
python_dependencies=frozenset(),
|
||||
javascript_dependencies=deps,
|
||||
local_python_modules=frozenset(),
|
||||
has_python_manifest=False,
|
||||
has_javascript_manifest=True,
|
||||
javascript_alias_prefixes=aliases,
|
||||
)
|
||||
return FileContext(
|
||||
path=Path("/tmp/x.tsx"),
|
||||
relative="src/x.tsx",
|
||||
language="typescript",
|
||||
text=text,
|
||||
lines=text.splitlines(),
|
||||
comments=[],
|
||||
metrics=None,
|
||||
repo=repo,
|
||||
fingerprint_only=False,
|
||||
)
|
||||
|
||||
|
||||
def test_path_aliases_are_never_unresolved():
|
||||
text = (
|
||||
"import { Sponsors } from '@/components/Sponsors';\n"
|
||||
"import config from '~/config';\n"
|
||||
"import db from '#app/db';\n"
|
||||
"import { page } from '$app/stores';\n"
|
||||
)
|
||||
assert detect_hallucination(_context(text)) == []
|
||||
|
||||
|
||||
def test_tsconfig_alias_prefixes_are_respected():
|
||||
text = "import { helper } from 'src/utils/helper';\n"
|
||||
assert len(detect_hallucination(_context(text))) == 1
|
||||
assert detect_hallucination(_context(text, aliases=frozenset({"src/"}))) == []
|
||||
|
||||
|
||||
def test_declared_package_subpaths_resolve():
|
||||
text = (
|
||||
"import Image from 'next/image';\n"
|
||||
"import { auth } from '@clerk/nextjs/server';\n"
|
||||
"import { useState } from 'react';\n"
|
||||
)
|
||||
assert detect_hallucination(_context(text)) == []
|
||||
|
||||
|
||||
def test_node_builtins_and_schemes_resolve():
|
||||
text = (
|
||||
"import fs from 'node:fs';\n"
|
||||
"import hooks from 'async_hooks';\n"
|
||||
"import content from 'virtual:generated';\n"
|
||||
)
|
||||
assert detect_hallucination(_context(text)) == []
|
||||
|
||||
|
||||
def test_genuinely_missing_package_is_flagged():
|
||||
findings = detect_hallucination(_context("import magic from 'left-pad-ultra';\n"))
|
||||
assert len(findings) == 1
|
||||
assert findings[0].code == "DEP_UNRESOLVED"
|
||||
assert "left-pad-ultra" in findings[0].title
|
||||
|
||||
|
||||
def test_alias_prefix_parser_reads_jsonc(tmp_path):
|
||||
(tmp_path / "tsconfig.json").write_text(
|
||||
"""{
|
||||
// path aliases
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"~lib/*": ["./lib/*"], /* legacy */
|
||||
},
|
||||
},
|
||||
}""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert _javascript_alias_prefixes(tmp_path) == frozenset({"@/", "~lib/"})
|
||||
@@ -0,0 +1,69 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.services.jobs.isslop.agent.classifier import AiVerdict
|
||||
from devplacepy.services.jobs.isslop.agent.reporter import _summary_payload, fallback_report
|
||||
from devplacepy.services.jobs.isslop.analysis.templates import TemplateEvidence
|
||||
from devplacepy.services.jobs.isslop.analysis.scoring import FileScore, aggregate
|
||||
from devplacepy.services.jobs.isslop.pipeline import apply_ai_verdicts
|
||||
|
||||
|
||||
def _file(relative, sloc, origin, quality=10.0):
|
||||
return FileScore(
|
||||
relative=relative,
|
||||
language="python",
|
||||
sloc=sloc,
|
||||
origin_score=origin,
|
||||
quality_deficit=quality,
|
||||
category="human-clean",
|
||||
criticality=1.0,
|
||||
signals=[],
|
||||
)
|
||||
|
||||
|
||||
def _verdict(path, probability):
|
||||
return AiVerdict(
|
||||
path=path,
|
||||
origin_score=probability,
|
||||
quality_deficit=10.0,
|
||||
ai_probability=probability,
|
||||
category="uncertain",
|
||||
reasoning="",
|
||||
notable_signals=[],
|
||||
)
|
||||
|
||||
|
||||
def test_apply_ai_verdicts_only_touches_reviewed_files():
|
||||
scores = [_file("a.py", 500, 20.0), _file("b.py", 500, 20.0)]
|
||||
adjusted = apply_ai_verdicts(scores, [_verdict("a.py", 80.0)])
|
||||
assert adjusted[0].origin_score == round(0.6 * 20.0 + 0.4 * 80.0, 1)
|
||||
assert adjusted[1].origin_score == 20.0
|
||||
|
||||
|
||||
def test_hedging_verdicts_cannot_dominate_a_large_repo():
|
||||
scores = [_file(f"f{i}.py", 200, 15.0) for i in range(100)]
|
||||
verdicts = [_verdict(f"f{i}.py", 40.0) for i in range(12)]
|
||||
static = aggregate(scores)
|
||||
final = aggregate(apply_ai_verdicts(scores, verdicts))
|
||||
assert static.ai_percent == 0.0
|
||||
assert final.ai_percent < 5.0
|
||||
assert final.human_percent > 95.0
|
||||
|
||||
|
||||
def test_strong_verdict_on_heavy_file_moves_the_verdict():
|
||||
scores = [_file("huge.py", 5000, 30.0)] + [_file(f"s{i}.py", 50, 15.0) for i in range(10)]
|
||||
final = aggregate(apply_ai_verdicts(scores, [_verdict("huge.py", 95.0)]))
|
||||
static = aggregate(scores)
|
||||
assert final.ai_percent > static.ai_percent + 30.0
|
||||
|
||||
|
||||
def test_report_payload_grade_matches_final_scores():
|
||||
scores = [_file("a.py", 300, 60.0, 40.0), _file("b.py", 200, 20.0)]
|
||||
verdicts = [_verdict("a.py", 90.0)]
|
||||
adjusted = apply_ai_verdicts(scores, verdicts)
|
||||
final = aggregate(adjusted)
|
||||
payload = _summary_payload("https://x.example/repo", "git", final, adjusted, verdicts, 0, [], {}, TemplateEvidence())
|
||||
assert payload["repo_scores"]["grade"] == final.grade
|
||||
assert payload["repo_scores"]["human_percent"] == final.human_percent
|
||||
markdown = fallback_report(payload)
|
||||
assert f"grade **{final.grade}**" in markdown
|
||||
assert f"**{final.human_percent}% human**" in markdown
|
||||
@@ -0,0 +1,83 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.services.jobs.isslop.analysis.scoring import (
|
||||
CATEGORY_HUMAN_CLEAN,
|
||||
CATEGORY_HUMAN_MESSY,
|
||||
CATEGORY_SLOP,
|
||||
CATEGORY_SOPHISTICATED,
|
||||
CATEGORY_UNCERTAIN,
|
||||
categorize,
|
||||
compose_slop_score,
|
||||
grade_for,
|
||||
)
|
||||
|
||||
|
||||
def test_compose_slop_score_bounds():
|
||||
assert compose_slop_score(0.0, 0.0) == 0.0
|
||||
assert compose_slop_score(100.0, 100.0) == 100.0
|
||||
assert 0.0 <= compose_slop_score(50.0, 50.0) <= 100.0
|
||||
|
||||
|
||||
def test_compose_slop_score_weighs_ai_heavier_than_quality():
|
||||
ai_heavy = compose_slop_score(80.0, 20.0)
|
||||
quality_heavy = compose_slop_score(20.0, 80.0)
|
||||
assert ai_heavy > quality_heavy
|
||||
|
||||
|
||||
def test_grade_for_is_monotonic():
|
||||
grades = [grade_for(value) for value in (0.0, 20.0, 40.0, 60.0, 80.0, 100.0)]
|
||||
order = "ABCDF"
|
||||
positions = [order.index(grade) for grade in grades]
|
||||
assert positions == sorted(positions)
|
||||
assert grades[0] == "A"
|
||||
assert grades[-1] == "F"
|
||||
|
||||
|
||||
def test_categorize_corners():
|
||||
assert categorize(95.0, 95.0) == CATEGORY_SLOP
|
||||
assert categorize(95.0, 5.0) == CATEGORY_SOPHISTICATED
|
||||
assert categorize(5.0, 5.0) == CATEGORY_HUMAN_CLEAN
|
||||
assert categorize(5.0, 95.0) == CATEGORY_HUMAN_MESSY
|
||||
assert categorize(50.0, 50.0) == CATEGORY_UNCERTAIN
|
||||
|
||||
|
||||
def test_ai_fraction_is_continuous_and_monotonic():
|
||||
from devplacepy.services.jobs.isslop.analysis.scoring import ai_fraction
|
||||
|
||||
assert ai_fraction(0.0) == 0.0
|
||||
assert ai_fraction(35.0) == 0.0
|
||||
assert ai_fraction(50.0) == 0.5
|
||||
assert ai_fraction(65.0) == 1.0
|
||||
assert ai_fraction(100.0) == 1.0
|
||||
samples = [ai_fraction(v) for v in range(30, 71)]
|
||||
assert samples == sorted(samples)
|
||||
steps = [b - a for a, b in zip(samples, samples[1:])]
|
||||
assert max(steps) < 0.06
|
||||
|
||||
|
||||
def test_adjust_for_images_recomputes_grade_consistently():
|
||||
from devplacepy.services.jobs.isslop.analysis.scoring import (
|
||||
RepoScores,
|
||||
adjust_for_images,
|
||||
compose_slop_score,
|
||||
grade_for,
|
||||
)
|
||||
|
||||
scores = RepoScores(
|
||||
origin_score=10.0,
|
||||
quality_deficit=10.0,
|
||||
slop_score=compose_slop_score(2.0, 10.0),
|
||||
grade=grade_for(compose_slop_score(2.0, 10.0)),
|
||||
category="human-clean",
|
||||
human_percent=98.0,
|
||||
ai_percent=2.0,
|
||||
confidence="medium",
|
||||
strong_signal_count=0,
|
||||
medium_signal_count=0,
|
||||
files_scored=50,
|
||||
)
|
||||
adjusted = adjust_for_images(scores, 90.0)
|
||||
assert adjusted.ai_percent == round(0.75 * 2.0 + 0.25 * 90.0, 1)
|
||||
assert adjusted.human_percent == round(100.0 - adjusted.ai_percent, 1)
|
||||
assert adjusted.slop_score == compose_slop_score(adjusted.ai_percent, scores.quality_deficit)
|
||||
assert adjusted.grade == grade_for(adjusted.slop_score)
|
||||
@@ -0,0 +1,79 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import json
|
||||
|
||||
from devplacepy.services.jobs.isslop.analysis.scoring import RepoScores, adjust_for_template, aggregate
|
||||
from devplacepy.services.jobs.isslop.analysis.templates import detect_template
|
||||
|
||||
|
||||
def _scores(ai=2.0, quality=20.0):
|
||||
return RepoScores(
|
||||
origin_score=20.0,
|
||||
quality_deficit=quality,
|
||||
slop_score=7.8,
|
||||
grade="A",
|
||||
category="human-clean",
|
||||
human_percent=100.0 - ai,
|
||||
ai_percent=ai,
|
||||
confidence="medium",
|
||||
strong_signal_count=0,
|
||||
medium_signal_count=0,
|
||||
files_scored=50,
|
||||
)
|
||||
|
||||
|
||||
def test_untouched_boilerplate_is_detected(tmp_path):
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "next-js-boilerplate",
|
||||
"author": "Ixartz (https://github.com/ixartz)",
|
||||
}))
|
||||
(tmp_path / "README.md").write_text(
|
||||
"# Boilerplate and Starter for Next.js\n\nSponsors welcome. Demo at https://demo.nextjs-boilerplate.com\n"
|
||||
)
|
||||
for artifact in (".storybook", ".husky", ".devcontainer"):
|
||||
(tmp_path / artifact).mkdir()
|
||||
for artifact in ("commitlint.config.ts", "playwright.config.ts", "vitest.config.ts", "drizzle.config.ts", "crowdin.yml", "CHANGELOG.md"):
|
||||
(tmp_path / artifact).write_text("")
|
||||
evidence = detect_template(tmp_path)
|
||||
assert evidence.score >= 70.0
|
||||
assert any("known template" in marker for marker in evidence.markers)
|
||||
adjusted = adjust_for_template(_scores(), evidence.score)
|
||||
assert adjusted.grade in ("C", "D", "F")
|
||||
assert adjusted.ai_percent >= 55.0
|
||||
assert adjusted.category == "ai-slop"
|
||||
|
||||
|
||||
def test_t3_scaffold_metadata_is_detected(tmp_path):
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "my-cool-product",
|
||||
"ct3aMetadata": {"initVersion": "7.39.0"},
|
||||
}))
|
||||
evidence = detect_template(tmp_path)
|
||||
assert any("create-t3-app" in marker for marker in evidence.markers)
|
||||
|
||||
|
||||
def test_genuine_project_produces_no_evidence(tmp_path):
|
||||
(tmp_path / "package.json").write_text(json.dumps({
|
||||
"name": "acme-billing-portal",
|
||||
"description": "Internal billing portal for Acme",
|
||||
}))
|
||||
(tmp_path / "README.md").write_text("# Acme billing portal\n\nInternal tool, see the wiki for onboarding.\n")
|
||||
(tmp_path / "Dockerfile").write_text("")
|
||||
evidence = detect_template(tmp_path)
|
||||
assert evidence.score < 35.0
|
||||
assert adjust_for_template(_scores(), evidence.score) == _scores()
|
||||
|
||||
|
||||
def test_weak_evidence_below_gate_never_adjusts():
|
||||
base = _scores()
|
||||
assert adjust_for_template(base, 34.9) == base
|
||||
adjusted = adjust_for_template(base, 90.0)
|
||||
assert adjusted.ai_percent == round(0.85 * 90.0, 1)
|
||||
assert adjusted.human_percent == round(100.0 - adjusted.ai_percent, 1)
|
||||
assert adjusted.category == "ai-slop"
|
||||
|
||||
|
||||
def test_confident_but_partial_evidence_caps_category_at_uncertain():
|
||||
adjusted = adjust_for_template(_scores(), 55.0)
|
||||
assert adjusted.category == "uncertain"
|
||||
assert adjusted.grade != "A"
|
||||
@@ -0,0 +1,33 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from devplacepy.routers.tools.isslop import MEDIA_NAME_PATTERN
|
||||
from devplacepy.services.jobs.isslop.agent.vision import make_thumbnail
|
||||
|
||||
|
||||
def test_make_thumbnail_preserves_aspect_ratio(tmp_path):
|
||||
source = tmp_path / "wide.png"
|
||||
Image.new("RGB", (1600, 400), (255, 0, 0)).save(source)
|
||||
name = make_thumbnail(source, tmp_path / "media", "assets/wide.png")
|
||||
assert name is not None
|
||||
assert MEDIA_NAME_PATTERN.match(name)
|
||||
with Image.open(tmp_path / "media" / name) as thumb:
|
||||
assert thumb.format == "WEBP"
|
||||
assert thumb.width <= 480 and thumb.height <= 480
|
||||
assert abs(thumb.width / thumb.height - 4.0) < 0.05
|
||||
|
||||
|
||||
def test_make_thumbnail_is_deterministic_per_path(tmp_path):
|
||||
source = tmp_path / "icon.png"
|
||||
Image.new("RGBA", (256, 256), (0, 0, 255, 128)).save(source)
|
||||
first = make_thumbnail(source, tmp_path / "media", "public/icon.png")
|
||||
second = make_thumbnail(source, tmp_path / "media", "public/icon.png")
|
||||
assert first == second
|
||||
assert make_thumbnail(source, tmp_path / "media", "other/icon.png") != first
|
||||
|
||||
|
||||
def test_make_thumbnail_fails_soft_on_unreadable(tmp_path):
|
||||
source = tmp_path / "broken.png"
|
||||
source.write_bytes(b"not an image")
|
||||
assert make_thumbnail(source, tmp_path / "media", "broken.png") is None
|
||||
@@ -0,0 +1,36 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import pytest
|
||||
|
||||
from devplacepy.services.jobs.isslop.acquisition.workspace import (
|
||||
safe_relative_path,
|
||||
slugify,
|
||||
workspace_for,
|
||||
)
|
||||
|
||||
|
||||
def test_workspace_for_stays_inside_root(tmp_path):
|
||||
workspace = workspace_for(tmp_path, "https://github.com/owner/repo", "abc123")
|
||||
assert workspace.parent == tmp_path.resolve()
|
||||
assert "github.com-owner-repo" in workspace.name
|
||||
assert workspace.name.endswith("abc123")
|
||||
|
||||
|
||||
def test_workspace_for_rejects_escaping_uid(tmp_path):
|
||||
with pytest.raises(ValueError):
|
||||
workspace_for(tmp_path, "https://example.com", "../../../outside/x")
|
||||
|
||||
|
||||
def test_safe_relative_path_rejects_traversal(tmp_path):
|
||||
resolved = safe_relative_path(tmp_path, "../../../etc/passwd")
|
||||
assert resolved.is_relative_to(tmp_path.resolve())
|
||||
|
||||
|
||||
def test_safe_relative_path_keeps_nested_files(tmp_path):
|
||||
resolved = safe_relative_path(tmp_path, "assets/js/app.js")
|
||||
assert resolved == tmp_path.resolve() / "assets" / "js" / "app.js"
|
||||
|
||||
|
||||
def test_slugify_strips_scheme_and_specials():
|
||||
assert slugify("https://Example.com/Owner/Repo.git") == "example.com-owner-repo.git"
|
||||
assert slugify("!!!") == "source"
|
||||
Reference in New Issue
Block a user