simplicio-cli 0.2.0__py3-none-any.whl

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.
simplicio/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
simplicio/bench.py ADDED
@@ -0,0 +1,51 @@
1
+ """
2
+ bench.py — compara COM vs SEM o pipeline. Numeros REAIS, nada inventado.
3
+
4
+ Cada caso = {objetivo, alvo, criterios, test_cmd}.
5
+ SEM: manda so o objetivo cru pro LLM (baseline).
6
+ COM: pipeline completo (precedent + skill + camadas + verify).
7
+ Mede: passou no teste de primeira? quantas tentativas?
8
+
9
+ Uso: simplicio bench --cases bench/cases.json --stack angular
10
+ Preenche bench/results.md com a tabela real.
11
+ """
12
+ import os, json, time, subprocess
13
+ from .prompt import montar
14
+ from .providers import gerar
15
+
16
+ def _testa(saida, root, test_cmd):
17
+ os.makedirs(os.path.join(root, ".simplicio"), exist_ok=True)
18
+ open(os.path.join(root, ".simplicio/bench_out.txt"), "w").write(saida or "")
19
+ p = subprocess.run(test_cmd, shell=True, cwd=root, capture_output=True, text=True)
20
+ return p.returncode == 0
21
+
22
+ def _sem(caso, root):
23
+ # baseline: objetivo cru, zero contexto montado
24
+ return gerar(caso["objetivo"])
25
+
26
+ def _com(caso, root, stack):
27
+ return gerar(montar(root, stack, caso["objetivo"], caso["alvo"],
28
+ caso.get("criterios","- estado verdadeiro\n- estado falso"),
29
+ caso.get("restricoes","- build passa")))
30
+
31
+ def run_bench(root, stack, cases_path):
32
+ casos = json.load(open(cases_path))
33
+ linhas, n = [], len(casos)
34
+ acerto = {"sem": 0, "com": 0}
35
+ for c in casos:
36
+ tc = c["test_cmd"]
37
+ ok_sem = _testa(_sem(c, root), root, tc); acerto["sem"] += ok_sem
38
+ ok_com = _testa(_com(c, root, stack), root, tc); acerto["com"] += ok_com
39
+ linhas.append(f"| {c['objetivo'][:40]} | {'✅' if ok_sem else '❌'} | {'✅' if ok_com else '❌'} |")
40
+ tabela = ("| Tarefa | Sem simplicio | Com simplicio |\n|---|---|---|\n"
41
+ + "\n".join(linhas)
42
+ + f"\n\n**Acerto de primeira:** sem = {acerto['sem']}/{n} "
43
+ f"({100*acerto['sem']//n}%) · com = {acerto['com']}/{n} "
44
+ f"({100*acerto['com']//n}%)")
45
+ out = os.path.join(root, "bench", "results.md")
46
+ os.makedirs(os.path.dirname(out), exist_ok=True)
47
+ open(out, "w").write(f"# Benchmark (gerado por `simplicio bench`)\n\n"
48
+ f"Provider: {os.environ.get('SIMPLICIO_PROVIDER','claude')} · "
49
+ f"casos: {n} · data: {time.strftime('%Y-%m-%d')}\n\n{tabela}\n")
50
+ print(tabela)
51
+ print(f"\n-> {out}")
simplicio/cache.py ADDED
@@ -0,0 +1,59 @@
1
+ """
2
+ cache.py — cache de embeddings keyed por HASH do conteudo.
3
+
4
+ Por que por hash e nao por arquivo: se o bloco de codigo nao mudou, o hash
5
+ e o mesmo -> reusa o vetor. Arquivo muda mas o trecho relevante nao? Ainda
6
+ acerta o cache. Trecho muda -> hash novo -> re-embedda SO ele. Granular.
7
+
8
+ Persistido em .simplicio/emb_cache.npz (vetores) + .json (indice hash->linha).
9
+ """
10
+
11
+ import os, json, hashlib
12
+ import numpy as np
13
+
14
+ class EmbeddingCache:
15
+ def __init__(self, root):
16
+ self.dir = os.path.join(root, ".simplicio")
17
+ os.makedirs(self.dir, exist_ok=True)
18
+ self.vec_path = os.path.join(self.dir, "emb_cache.npz")
19
+ self.idx_path = os.path.join(self.dir, "emb_index.json")
20
+ self.index = {} # hash -> posicao na matriz
21
+ self.vectors = None # np.ndarray [N, dim]
22
+ self._load()
23
+
24
+ @staticmethod
25
+ def h(texto):
26
+ return hashlib.sha1(texto.encode("utf-8")).hexdigest()
27
+
28
+ def _load(self):
29
+ if os.path.exists(self.idx_path) and os.path.exists(self.vec_path):
30
+ self.index = json.load(open(self.idx_path))
31
+ self.vectors = np.load(self.vec_path)["v"]
32
+
33
+ def save(self):
34
+ json.dump(self.index, open(self.idx_path, "w"))
35
+ if self.vectors is not None:
36
+ np.savez_compressed(self.vec_path, v=self.vectors)
37
+
38
+ def get_missing(self, textos):
39
+ """Retorna os textos que NAO estao no cache (precisam embeddar)."""
40
+ return [t for t in textos if self.h(t) not in self.index]
41
+
42
+ def add(self, textos, vetores):
43
+ """Adiciona novos textos+vetores ao cache."""
44
+ if not textos:
45
+ return
46
+ vetores = np.asarray(vetores)
47
+ base = 0 if self.vectors is None else self.vectors.shape[0]
48
+ self.vectors = vetores if self.vectors is None else np.vstack([self.vectors, vetores])
49
+ for i, t in enumerate(textos):
50
+ self.index[self.h(t)] = base + i
51
+
52
+ def lookup(self, textos):
53
+ """Devolve matriz de vetores na ordem dos textos (todos ja no cache)."""
54
+ rows = [self.index[self.h(t)] for t in textos]
55
+ return self.vectors[rows]
56
+
57
+ def stats(self):
58
+ return {"cached_blocks": len(self.index),
59
+ "dim": 0 if self.vectors is None else int(self.vectors.shape[1])}
simplicio/cli.py ADDED
@@ -0,0 +1,43 @@
1
+ """cli.py — comandos: index (cacheia repo) e task (roda o pipeline)."""
2
+ import argparse
3
+ from .precedent import index_repo
4
+ from .pipeline import run
5
+ from .bench import run_bench
6
+ from .providers import gerar, info
7
+
8
+ def main():
9
+ ap = argparse.ArgumentParser(prog="simplicio")
10
+ sub = ap.add_subparsers(dest="cmd", required=True)
11
+
12
+ pi = sub.add_parser("index", help="indexa/cacheia o repo (1x ou apos mudancas)")
13
+ pi.add_argument("--root", default="."); pi.add_argument("--stack", default="angular")
14
+
15
+ pt = sub.add_parser("task", help="executa uma tarefa")
16
+ pt.add_argument("objetivo")
17
+ pt.add_argument("--root", default="."); pt.add_argument("--stack", default="angular")
18
+ pt.add_argument("--alvo", required=True)
19
+ pt.add_argument("--criterios", default="- estado verdadeiro\n- estado falso")
20
+ pt.add_argument("--restricoes", default="- build passa")
21
+
22
+
23
+ pb = sub.add_parser("bench", help="compara com vs sem (numeros reais)")
24
+ pb.add_argument("--root", default="."); pb.add_argument("--stack", default="angular")
25
+ pb.add_argument("--cases", default="bench/cases.json")
26
+
27
+
28
+ sub.add_parser("smoke", help="1 chamada de prova: conecta+gera (precisa SIMPLICIO_MODEL+KEY)")
29
+
30
+ a = ap.parse_args()
31
+ if a.cmd == "index":
32
+ index_repo(a.root, a.stack)
33
+ elif a.cmd == "smoke":
34
+ print("provider:", info())
35
+ out = gerar("Responda exatamente: OK simplicio conectado.")
36
+ print("resposta do modelo:", out.strip()[:200])
37
+ elif a.cmd == "bench":
38
+ run_bench(a.root, a.stack, a.cases)
39
+ else:
40
+ run(a.root, a.stack, a.objetivo, a.alvo, a.criterios, a.restricoes)
41
+
42
+ if __name__ == "__main__":
43
+ main()
simplicio/pipeline.py ADDED
@@ -0,0 +1,28 @@
1
+ """pipeline.py — monta -> gera -> aplica -> testa -> corrige (loop)."""
2
+ import os, subprocess
3
+ from .prompt import montar
4
+ from .providers import gerar
5
+
6
+ MAX_TENTATIVAS = 3
7
+
8
+ def _aplicar_e_testar(saida, root):
9
+ os.makedirs(os.path.join(root, ".simplicio"), exist_ok=True)
10
+ open(os.path.join(root, ".simplicio/ultima_saida.txt"), "w").write(saida)
11
+ # PLUGUE: extrair diff -> git apply; extrair teste. Aqui roda o cmd de teste.
12
+ cmd = os.environ.get("SIMPLICIO_TEST_CMD", "echo 'configure SIMPLICIO_TEST_CMD'")
13
+ p = subprocess.run(cmd, shell=True, cwd=root, capture_output=True, text=True)
14
+ return p.returncode == 0, (p.stdout + p.stderr)[-2000:]
15
+
16
+ def run(root, stack, objetivo, alvo, criterios, restricoes):
17
+ prompt = montar(root, stack, objetivo, alvo, criterios, restricoes)
18
+ feedback = None
19
+ for t in range(1, MAX_TENTATIVAS + 1):
20
+ print(f"--- tentativa {t} (provider={os.environ.get('SIMPLICIO_PROVIDER','claude')}) ---")
21
+ saida = gerar(prompt, feedback)
22
+ ok, log = _aplicar_e_testar(saida, root)
23
+ if ok:
24
+ print("PASSOU no contrato. PRONTO.")
25
+ return saida
26
+ print("falhou:", log[:300]); feedback = log
27
+ print("esgotou tentativas — revisar manual.")
28
+ return None
simplicio/precedent.py ADDED
@@ -0,0 +1,83 @@
1
+ """
2
+ precedent.py — acha PRECEDENTE usando o cache (so embedda bloco novo).
3
+ """
4
+ import re, glob, os
5
+ import numpy as np
6
+ from .cache import EmbeddingCache
7
+
8
+ _emb = None
9
+ def _embedder():
10
+ global _emb
11
+ if _emb is None:
12
+ from sentence_transformers import SentenceTransformer
13
+ _emb = SentenceTransformer("all-MiniLM-L6-v2")
14
+ return _emb
15
+
16
+ PATTERNS = {
17
+ "angular": [r"\*ngIf", r"\[hidden\]", r"\[disabled\]", r"hasPerm",
18
+ r"ngxPermission", r"canActivate"],
19
+ "react": [r"&&\s*<", r"\?\s*<[A-Z]", r"usePermission", r"\bcan\(",
20
+ r"hasRole", r"<Protected"],
21
+ "dotnet": [r"\[Authorize", r"HasPermission", r"User\.IsInRole",
22
+ r"\[HasPolicy", r"RequireClaim"],
23
+ }
24
+ EXT = {"angular": (".html", ".ts"), "react": (".tsx", ".jsx", ".ts"),
25
+ "dotnet": (".cs", ".cshtml", ".razor")}
26
+ SKIP = ("node_modules", "/.git/", "/dist/", "/bin/", "/obj/", "/.angular/", "/.simplicio/")
27
+
28
+
29
+ def grep_candidatos(root, stack, janela=1):
30
+ pats = [re.compile(p) for p in PATTERNS[stack]]
31
+ exts = EXT[stack]
32
+ cands = []
33
+ for fp in glob.glob(f"{root}/**/*", recursive=True):
34
+ if not os.path.isfile(fp) or not fp.endswith(exts): continue
35
+ if any(s in fp for s in SKIP): continue
36
+ try:
37
+ linhas = open(fp, encoding="utf-8", errors="ignore").read().splitlines()
38
+ except Exception:
39
+ continue
40
+ for i, ln in enumerate(linhas):
41
+ if any(p.search(ln) for p in pats):
42
+ bloco = "\n".join(linhas[max(0, i - janela): i + janela + 1])
43
+ cands.append({"file": fp, "line": i + 1, "code": bloco})
44
+ return cands
45
+
46
+
47
+ def index_repo(root, stack, verbose=True):
48
+ """Indexa: embedda SO os blocos novos, salva cache. Retorna stats."""
49
+ cache = EmbeddingCache(root)
50
+ cands = grep_candidatos(root, stack)
51
+ textos = list({c["code"] for c in cands}) # dedup
52
+ faltam = cache.get_missing(textos)
53
+ if faltam:
54
+ vetores = _embedder().encode(faltam, show_progress_bar=False)
55
+ cache.add(faltam, vetores)
56
+ cache.save()
57
+ if verbose:
58
+ print(f"[index] candidatos={len(cands)} novos_embeddados={len(faltam)} "
59
+ f"cache_total={cache.stats()['cached_blocks']}")
60
+ return cache, cands
61
+
62
+
63
+ def montar_bloco_precedente(root, stack, tarefa, k=2):
64
+ cache, cands = index_repo(root, stack, verbose=False)
65
+ if not cands:
66
+ return "[PRECEDENTE]\n(nenhum padrao similar no repo — gere do zero pela convencao da stack)"
67
+ textos = [c["code"] for c in cands]
68
+ vc = cache.lookup(textos) # do cache, sem re-embeddar
69
+ vt = _embedder().encode([tarefa])[0] # so a tarefa (curta)
70
+ for c, v in zip(cands, vc):
71
+ c["score"] = float(np.dot(vt, v) / (np.linalg.norm(vt) * np.linalg.norm(v)))
72
+ seen, out = set(), []
73
+ for c in sorted(cands, key=lambda x: x["score"], reverse=True):
74
+ if c["code"] in seen: continue
75
+ seen.add(c["code"]); out.append(c)
76
+ tops = out[:k]
77
+ linhas = ["[PRECEDENTE]",
78
+ "Este projeto JA faz algo parecido. Siga ESTA convencao, nao invente:"]
79
+ for c in tops:
80
+ rel = os.path.relpath(c["file"], root)
81
+ linhas.append(f"\n# {rel}:{c['line']} (similaridade {c['score']:.2f})")
82
+ linhas.append(c["code"])
83
+ return "\n".join(linhas)
simplicio/prompt.py ADDED
@@ -0,0 +1,25 @@
1
+ """prompt.py — empilha as 6 camadas."""
2
+ import os, re
3
+ from .precedent import montar_bloco_precedente
4
+ from .skill_router import montar_bloco_skill
5
+
6
+ def _mapper(root, alvo):
7
+ try:
8
+ txt = open(os.path.join(root, alvo), encoding="utf-8", errors="ignore").read()
9
+ except Exception:
10
+ return "(mapper: alvo nao lido)"
11
+ deps = [l for l in txt.splitlines()
12
+ if l.strip().startswith(("import", "using", "from"))][:15]
13
+ return f"Arquivo: {alvo}\nDependencias:\n" + "\n".join(deps)
14
+
15
+ def montar(root, stack, objetivo, alvo, criterios, restricoes):
16
+ tpl_path = os.path.join(os.path.dirname(__file__), "templates", "simplicio_prompt.md")
17
+ tpl = open(tpl_path, encoding="utf-8").read()
18
+ prec = montar_bloco_precedente(root, stack, objetivo, k=2)
19
+ skill = montar_bloco_skill(root, objetivo)
20
+ alvo_bloco = f"{alvo}\n\nContexto do alvo:\n{_mapper(root, alvo)}"
21
+ for s, v in {"{{STACK}}": stack, "{{OBJETIVO}}": objetivo,
22
+ "{{ALVO}}": alvo_bloco, "{{PRECEDENTE}}": prec, "{{SKILL}}": skill,
23
+ "{{CRITERIOS}}": criterios, "{{RESTRICOES}}": restricoes}.items():
24
+ tpl = tpl.replace(s, v)
25
+ return re.sub(r"\{#.*?#\}", "", tpl, flags=re.DOTALL).strip()
simplicio/providers.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ providers.py — agnostico de provider. NAO lista modelos especificos.
3
+
4
+ Voce define por env (ou flag --model/--base):
5
+ SIMPLICIO_MODEL id do modelo, exatamente como o provider espera
6
+ ex: "anthropic/claude-opus-4", "openai/gpt-4.1",
7
+ "z-ai/glm-4.6", "deepseek/deepseek-chat", "qualquer/coisa"
8
+ SIMPLICIO_BASE_URL endpoint OpenAI-compativel
9
+ ex OpenRouter: https://openrouter.ai/api/v1
10
+ ex GLM: https://api.z.ai/api/paas/v4
11
+ ex local: http://localhost:11434/v1
12
+ SIMPLICIO_API_KEY a chave (qualquer provider)
13
+
14
+ Caminho nativo Anthropic (sem base_url): se SIMPLICIO_BASE_URL estiver vazio
15
+ E a key for ANTHROPIC_API_KEY, usa o SDK anthropic. Senao, usa cliente
16
+ OpenAI-compativel apontando pro base_url -> serve QUALQUER provider OAI-like.
17
+ """
18
+ import os
19
+
20
+ def _cfg():
21
+ return {
22
+ "model": os.environ.get("SIMPLICIO_MODEL"),
23
+ "base": os.environ.get("SIMPLICIO_BASE_URL"),
24
+ "key": os.environ.get("SIMPLICIO_API_KEY")
25
+ or os.environ.get("OPENROUTER_API_KEY")
26
+ or os.environ.get("ANTHROPIC_API_KEY"),
27
+ }
28
+
29
+ def _msgs(prompt, feedback):
30
+ m = [{"role": "user", "content": prompt}]
31
+ if feedback:
32
+ m.append({"role": "user",
33
+ "content": f"O teste FALHOU:\n{feedback}\nCorrija. Mesmo formato."})
34
+ return m
35
+
36
+ def gerar(prompt, feedback=None, max_tokens=4000):
37
+ c = _cfg()
38
+ if not c["model"]:
39
+ raise SystemExit("defina SIMPLICIO_MODEL (id do modelo do seu provider)")
40
+ if not c["key"]:
41
+ raise SystemExit("defina SIMPLICIO_API_KEY (ou OPENROUTER_/ANTHROPIC_API_KEY)")
42
+
43
+ # caminho nativo Anthropic: sem base_url
44
+ if not c["base"]:
45
+ import anthropic
46
+ cli = anthropic.Anthropic(api_key=c["key"])
47
+ r = cli.messages.create(model=c["model"], max_tokens=max_tokens,
48
+ messages=_msgs(prompt, feedback))
49
+ return next((b.text for b in r.content if b.type == "text"), "")
50
+
51
+ # qualquer endpoint OpenAI-compativel (OpenRouter, GLM, DeepSeek, local...)
52
+ from openai import OpenAI
53
+ cli = OpenAI(base_url=c["base"], api_key=c["key"])
54
+ r = cli.chat.completions.create(model=c["model"], max_tokens=max_tokens,
55
+ messages=_msgs(prompt, feedback))
56
+ return r.choices[0].message.content
57
+
58
+ def info():
59
+ c = _cfg()
60
+ return (f"model={c['model'] or '(nao definido)'} "
61
+ f"base={c['base'] or 'anthropic-nativo'} "
62
+ f"key={'set' if c['key'] else 'FALTA'}")
@@ -0,0 +1,48 @@
1
+ """
2
+ skill_router.py — CAMADA 4.5. Seleciona A skill que casa com a tarefa.
3
+ NAO injeta todas (ruido). Ranqueia por sentido, pega top-1.
4
+
5
+ Fonte das skills: por padrao varre <root>/.mapper/skills/*.md ou
6
+ SIMPLICIO_SKILLS_DIR. Cada skill = arquivo md com 1a linha = descricao.
7
+ Reusa o mesmo cache de embeddings.
8
+ """
9
+ import os, glob
10
+ import numpy as np
11
+ from .cache import EmbeddingCache
12
+
13
+ def _skills_dir(root):
14
+ return os.environ.get("SIMPLICIO_SKILLS_DIR",
15
+ os.path.join(root, ".mapper", "skills"))
16
+
17
+ def _carregar_skills(root):
18
+ d = _skills_dir(root)
19
+ out = []
20
+ for fp in glob.glob(os.path.join(d, "*.md")):
21
+ try:
22
+ txt = open(fp, encoding="utf-8", errors="ignore").read()
23
+ except Exception:
24
+ continue
25
+ desc = next((l.strip("# ").strip() for l in txt.splitlines() if l.strip()), "")
26
+ out.append({"nome": os.path.basename(fp), "desc": desc, "corpo": txt})
27
+ return out
28
+
29
+ def montar_bloco_skill(root, tarefa, limiar=0.15):
30
+ skills = _carregar_skills(root)
31
+ if not skills:
32
+ return "" # sem skills -> camada some, sem ruido
33
+ from .precedent import _embedder
34
+ cache = EmbeddingCache(root)
35
+ descs = [s["desc"] for s in skills]
36
+ faltam = cache.get_missing(descs)
37
+ if faltam:
38
+ cache.add(faltam, _embedder().encode(faltam, show_progress_bar=False))
39
+ cache.save()
40
+ vd = cache.lookup(descs)
41
+ vt = _embedder().encode([tarefa])[0]
42
+ scores = [float(np.dot(vt, v)/(np.linalg.norm(vt)*np.linalg.norm(v))) for v in vd]
43
+ i = int(np.argmax(scores))
44
+ if scores[i] < limiar:
45
+ return "" # nada casa o suficiente -> nao força skill irrelevante
46
+ s = skills[i]
47
+ return (f"[SKILL RELEVANTE]\nO mapper tem um metodo que casa com esta tarefa "
48
+ f"(match {scores[i]:.2f}). Siga-o:\n# {s['nome']}\n{s['corpo'][:1200]}")
@@ -0,0 +1,43 @@
1
+ {# ============================================================
2
+ SIMPLICIO-PROMPT — 6 camadas. Ordem: fixo (embaixo/cacheável)
3
+ -> variável (em cima). Preenchido por run_task.py.
4
+ {{...}} = slots que a toolchain injeta automaticamente.
5
+ ============================================================ #}
6
+
7
+ {# ---------- CAMADA 1: PAPEL + STACK (fixo, cacheia) ---------- #}
8
+ Voce e um engenheiro senior trabalhando NESTE projeto.
9
+ Stack: {{STACK}}.
10
+ Convencoes deste projeto sao LEI. Nao traga padrao generico da internet.
11
+ Nao invente arquivo, lib ou abstracao que o projeto nao usa.
12
+
13
+ {# ---------- CAMADA 2: OBJETIVO (1 linha, zero ambiguidade) ---------- #}
14
+ [OBJETIVO]
15
+ {{OBJETIVO}}
16
+
17
+ {# ---------- CAMADA 3: ALVO (so os arquivos que se toca) ---------- #}
18
+ [ALVO]
19
+ Toque SOMENTE nestes arquivos:
20
+ {{ALVO}}
21
+
22
+ {# ---------- CAMADA 4: PRECEDENTE (o ouro — vem do precedent.py) ---------- #}
23
+ {{PRECEDENTE}}
24
+
25
+ {{SKILL}}
26
+
27
+ {# ---------- CAMADA 5: CONTRATO (estados testaveis + o que nao quebrar) ---------- #}
28
+ [CONTRATO]
29
+ Pronto QUANDO, e somente quando, TODOS os estados abaixo forem verdade:
30
+ {{CRITERIOS}}
31
+
32
+ Restricoes (nao quebrar):
33
+ {{RESTRICOES}}
34
+
35
+ {# ---------- CAMADA 6: SAIDA (formato exato) ---------- #}
36
+ [SAIDA]
37
+ Devolva EXATAMENTE neste formato, nada mais:
38
+ 1. DIFF unificado, so dos arquivos do [ALVO].
39
+ 2. TESTE: codigo de teste que verifica cada estado do [CONTRATO]
40
+ (um caso por criterio — estado verdadeiro E falso).
41
+ 3. EVIDENCIA: script Playwright que captura print dos estados de UI,
42
+ se a tarefa for visual. Senao, escreva "N/A".
43
+ Sem explicacao, sem preambulo.
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: simplicio-cli
3
+ Version: 0.2.0
4
+ Summary: Portable task-to-code pipeline that works with any LLM. Turn a one-line task into a verified code change — diff + test + verify loop. +54 pts on a 156-check benchmark vs raw prompting.
5
+ Author-email: Wesley Simplicio <wesleybob4@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wesleysimplicio/simplicio-cli
8
+ Project-URL: Repository, https://github.com/wesleysimplicio/simplicio-cli
9
+ Project-URL: Issues, https://github.com/wesleysimplicio/simplicio-cli/issues
10
+ Project-URL: Changelog, https://github.com/wesleysimplicio/simplicio-cli/releases
11
+ Keywords: llm,ai,agent,code-generation,prompt-engineering,openrouter,openai,anthropic,claude,developer-tools,cli,rag,embeddings,verify-loop,task-automation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Software Development
24
+ Classifier: Topic :: Software Development :: Code Generators
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: sentence-transformers>=2.2
31
+ Requires-Dist: numpy>=1.23
32
+ Requires-Dist: anthropic>=0.30
33
+ Requires-Dist: openai>=1.30
34
+ Dynamic: license-file
35
+
36
+ # simplicio-cli
37
+
38
+ **Turn a one-line task into a verified code change — with any LLM.**
39
+
40
+ [![PyPI](https://img.shields.io/pypi/v/simplicio-cli.svg)](https://pypi.org/project/simplicio-cli/)
41
+ [![Python](https://img.shields.io/pypi/pyversions/simplicio-cli.svg)](https://pypi.org/project/simplicio-cli/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
43
+
44
+ > *"hide the Delete button for non-admins"* → diff + test + applied + verified.
45
+ > Works with **OpenRouter, OpenAI, Anthropic, GLM, DeepSeek, Ollama** — one env var.
46
+
47
+ ```bash
48
+ pip install simplicio-cli
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Why it works — the numbers
54
+
55
+ Same model. Same task. Only the prompt changes. **Measured, reproducible, deterministic.**
56
+
57
+ | Model | Without simplicio | With simplicio | Gain |
58
+ |---|---|---|---|
59
+ | **Llama 3.1 8B Instruct** | 34% | **98%** | **+64 pts** |
60
+ | **Gemma 3 12B IT** | 38% | **94%** | **+56 pts** |
61
+ | **Qwen 2.5 7B Instruct** | 38% | **80%** | **+42 pts** |
62
+ | **Average across 3 models · 10 cases · 156 checks** | **37%** | **91%** | **+54 pts (+145%)** |
63
+
64
+ ### Output-quality signals (rate across all 30 runs)
65
+
66
+ | Signal | Raw prompt | With simplicio |
67
+ |---|---|---|
68
+ | **DIFF block present** | 0% | **100%** |
69
+ | Target file mentioned | 3% | **96%** |
70
+ | TEST block present | 86% | **93%** |
71
+
72
+ > A 7B-parameter open model wrapped in simplicio's 6-layer contract outperforms
73
+ > the same model with raw prompting **by 42 to 64 points**. Without changing the
74
+ > model. Without fine-tuning. Without extra tokens at runtime worth mentioning.
75
+
76
+ Full report: [`bench/results.md`](bench/results.md) · [`bench/results.pdf`](bench/results.pdf) · raw outputs under `.simplicio/bench_runs/`.
77
+
78
+ ---
79
+
80
+ ## How it works
81
+
82
+ ```
83
+ mapper WHERE project structure + latest state
84
+ precedent HOW-1 the real snippet in THIS repo that already does it
85
+ skill-router HOW-2 the ONE mapper skill that matches (ranked, not all)
86
+ simplicio BUILD stacks the 6 layers into one prompt (cache-friendly)
87
+ test JUDGE contract written as testable states
88
+ verify PROOF ran it — did it actually pass? loop-fix up to 3x
89
+ ```
90
+
91
+ **The idea in one line: don't ask the model to guess — hand it the path.**
92
+ Each layer terminates one decision the model would otherwise hallucinate.
93
+ Relevant > complete — inject the *right* context, never *all* of it.
94
+
95
+ ---
96
+
97
+ ## Install
98
+
99
+ ```bash
100
+ pip install simplicio-cli # from PyPI
101
+ # or
102
+ pip install -e . # from this repo
103
+ ```
104
+
105
+ ## Configure — any LLM, nothing hardcoded
106
+
107
+ | Provider | SIMPLICIO_MODEL | SIMPLICIO_BASE_URL |
108
+ |---|---|---|
109
+ | OpenRouter | `anthropic/claude-opus-4` | `https://openrouter.ai/api/v1` |
110
+ | GLM (z.ai) | `glm-4.6` | `https://api.z.ai/api/paas/v4` |
111
+ | DeepSeek | `deepseek-chat` | `https://api.deepseek.com` |
112
+ | OpenAI | `gpt-4.1` | `https://api.openai.com/v1` |
113
+ | Local (Ollama) | `llama3` | `http://localhost:11434/v1` |
114
+ | Anthropic native | `claude-opus-4-7` | *(leave unset)* |
115
+
116
+ If `SIMPLICIO_BASE_URL` is unset and the key is `ANTHROPIC_API_KEY`, it uses the
117
+ native Anthropic SDK. Otherwise it uses an OpenAI-compatible client pointed at
118
+ your `base_url` — so **any** OpenAI-like provider works without code changes.
119
+
120
+ ```bash
121
+ simplicio smoke # prints provider config + one test call
122
+ ```
123
+
124
+ ## Use
125
+
126
+ ```bash
127
+ # index once (caches embeddings; re-run after big changes)
128
+ simplicio index --stack angular
129
+
130
+ # run a task
131
+ simplicio task "hide Delete button for non-admins" \
132
+ --stack angular \
133
+ --alvo src/app/screen/screen.component.html \
134
+ --criterios "- no admin perm: button absent from DOM
135
+ - with admin perm: button present" \
136
+ --restricoes "- don't touch save flow
137
+ - build passes"
138
+ ```
139
+
140
+ Each `task`: precedent (from cache) → skill match → 6 layers → LLM generates
141
+ (diff + test + Playwright) → apply → run `SIMPLICIO_TEST_CMD` → pass? **done** :
142
+ send the error back → fix → retry (up to 3x).
143
+
144
+ ---
145
+
146
+ ## Cache — why it doesn't re-map every time
147
+
148
+ Embeddings are keyed by **content hash**, stored in `.simplicio/`. Unchanged
149
+ code block → vector reused. Change one file → only that block re-embeds.
150
+
151
+ | Run | Blocks embedded | Time |
152
+ |---|---|---|
153
+ | 1st (cold cache) | 3 | ~baseline |
154
+ | 2nd (no change) | **0** | **~instant** |
155
+ | after editing 1 file | **1** | partial |
156
+
157
+ ---
158
+
159
+ ## Benchmark — reproduce in 30 seconds
160
+
161
+ ```bash
162
+ OPENROUTER_API_KEY=… \
163
+ BENCH_MODELS="qwen/qwen-2.5-7b-instruct,meta-llama/llama-3.1-8b-instruct,google/gemma-3-12b-it" \
164
+ python3 bench/run_offline.py
165
+ ```
166
+
167
+ No project required, stdlib only, deterministic regex scoring — no LLM judges
168
+ the LLM. Each case runs twice on the **same** model: raw one-line objective vs
169
+ simplicio's 6-layer contract. Outputs scored on target-file mention, DIFF
170
+ block, TEST block, contract-state words. Full numbers in [`bench/results.md`](bench/results.md).
171
+
172
+ ### Full harness (your real project, your real tests)
173
+
174
+ ```bash
175
+ simplicio bench --cases bench/cases.json --stack angular
176
+ ```
177
+
178
+ Runs each case two ways and runs **your real test command** (e.g. `ng test
179
+ --watch=false`) on each output. Writes the true pass-rate to
180
+ [`bench/results.md`](bench/results.md).
181
+
182
+ ---
183
+
184
+ ## Plug points (stubs marked in code)
185
+
186
+ | File | Replace with |
187
+ |---|---|
188
+ | `prompt.py::_mapper` | your real **llm-project-mapper** |
189
+ | `pipeline.py::_aplicar_e_testar` | extract diff → `git apply` → parse test result |
190
+ | `skill_router.py` | point `SIMPLICIO_SKILLS_DIR` at your mapper's skills |
191
+
192
+ ## Layout
193
+
194
+ ```
195
+ simplicio/
196
+ cli.py # index | task | bench | smoke
197
+ cache.py # content-hash embedding cache
198
+ precedent.py # grep + semantic rank (uses cache)
199
+ skill_router.py # picks the ONE matching skill
200
+ prompt.py # stacks the 6 layers
201
+ providers.py # any OpenAI-compatible endpoint + Anthropic native
202
+ pipeline.py # generate → test → fix loop
203
+ bench.py # with-vs-without harness
204
+ templates/simplicio_prompt.md
205
+ bench/
206
+ run_offline.py # stdlib-only multi-model benchmark
207
+ cases.json # your benchmark tasks
208
+ cases_offline.json
209
+ results.md # filled by `simplicio bench` / `run_offline.py`
210
+ charts/ # SVG: overall, delta, by_case, by_stack
211
+ ```
212
+
213
+ ## License
214
+ MIT
@@ -0,0 +1,16 @@
1
+ simplicio/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ simplicio/bench.py,sha256=_2zvGRGXtfhv7H1iVLcspxAKz6ibIaLqZU5fhdBGCf4,2334
3
+ simplicio/cache.py,sha256=bUdJerB7myC_CesagzNDBfSk_U4FRrmK_eiI1XTmoRw,2267
4
+ simplicio/cli.py,sha256=fs7o-1Llej_TZXZFjN2ADI-OPhR9-BbadqszV7cgveQ,1675
5
+ simplicio/pipeline.py,sha256=9rZSI-7E9OmCB0fScV8qXXXK25SopMzbt92Us48XfvE,1256
6
+ simplicio/precedent.py,sha256=yQ24gMkmIaAja0DT5HRdR3o8gzQRkw3BdNAnnXF0oaM,3340
7
+ simplicio/prompt.py,sha256=6IyKWNvUe_r7ORatNKZk-U1gVhVuc0icKzr-QiY1aM4,1226
8
+ simplicio/providers.py,sha256=Ngq39yKUVKxGyhhR5fSHoOYY7bM5y_G180EUngKGIvw,2598
9
+ simplicio/skill_router.py,sha256=_91_b74BGu3dRFK_7uoApHLIoY1eVqrRjW27bp80_i8,1858
10
+ simplicio/templates/simplicio_prompt.md,sha256=8i6M_BWJHKkjA-6pQsw8c4eDuxSlNPWDmGQlX7txlIQ,1576
11
+ simplicio_cli-0.2.0.dist-info/licenses/LICENSE,sha256=i0oySk8jwjV1P3a3uskDYmAGpU8DOaIGdO1yEp0UUTQ,816
12
+ simplicio_cli-0.2.0.dist-info/METADATA,sha256=J6_oX6TEmmzH5u_kNGNfswU0IIu_vHe1A-am1RsbHLk,7951
13
+ simplicio_cli-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
+ simplicio_cli-0.2.0.dist-info/entry_points.txt,sha256=e-QHNyRGjMY8wCdmEcUmKmzKXZ3oxTDToUQTKlghQsE,49
15
+ simplicio_cli-0.2.0.dist-info/top_level.txt,sha256=qDCXq49sOcz28KN7xHwTKhhsXuqZGXrnJ40bjA2Oy1c,10
16
+ simplicio_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ simplicio = simplicio.cli:main
@@ -0,0 +1,17 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wesley Simplicio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
17
+ OTHER LIABILITY ARISING FROM THE SOFTWARE OR ITS USE.
@@ -0,0 +1 @@
1
+ simplicio