@andespindola/crow-skills 0.11.0 → 0.11.1

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Skills com escopo explícito, conhecimento especializado preservado e fluxos comuns reutilizáveis.
4
4
 
5
- **Versão 0.11.0** ([mudanças](CHANGELOG.md)): `crow-skills setup --yes` configura de uma vez todos os CLIs de agente da máquina e `crow-skills upgrade --yes` atualiza tudo numa só passada; os hooks que o crow-skills grava já saem habilitados (no Codex, aprovados pelo próprio `codex app-server`, só os do crow-skills; `--no-trust` desliga) e `status`/`doctor` mostram se cada CLI vai rodá-los; a ajuda ficou curta e organizada por tarefa. A 0.10.1 passou a tratar swap e CPU como eixos de pressão no controle de recursos (swap quase cheio e carga alta reduzem agentes, jobs e workers; nenhuma recomendação cresce com a pressão), mostrou a CPU% dos CLIs de agente, trouxe `crow-skills resources --exec-prefix` para rodar jobs pesados em prioridade baixa e as regras da crow-behaviour sob pressão (reduzir já, um job pesado por vez, não repetir verificação, validação ampla no CI); os hooks do Codex vão para o `config.toml` quando o `hooks.json` é symlink. A 0.10.0 trouxe o crow-broadcast, que compartilha, de forma passiva, o que cada sessão e cada CLI (Claude Code, Codex, Gemini CLI, Antigravity, Devin, inclusive dentro do Uniflow) está fazendo nesta máquina: repositório, clone, branch, execução crow em andamento e arquivos em edição; o prompt recebe um resumo só quando outra sessão viva está no mesmo repositório, editar um arquivo que outra sessão tocou ou trocar de branch num clone em uso gera aviso (ou bloqueio, se configurado), e `crow-skills broadcast claim` reserva áreas; a crow-behaviour consulta isso antes de planejar e reserva as áreas que delega ([crow-broadcast](docs/broadcast.md)). A 0.9.0 trouxe o controle de memória da máquina: `crow-skills resources` mede memória, pressão e os CLIs de agente em execução e recomenda quantos agentes, jobs pesados e workers rodar em paralelo; a crow-behaviour consulta essa recomendação antes de cada fan-out (sob `warn` reduz, sob `critical` segue sequencial), limita workers de builds e testes, reutiliza um dev server por projeto e encerra o que abriu, e 15 especialistas que sobem processos pesados ganharam a regra correspondente; o hook de prompt e as status lines só avisam quando a máquina está sob pressão. A 0.8.0 trouxe `crow-skills mode on|off`, que liga e desliga a crow-behaviour em todas as sessões e CLIs (Claude Code, Codex, Gemini CLI, Antigravity, Devin) a partir do próximo prompt, com `/crow on|off` dentro da sessão e o indicador `▸▸ crow on` na status line; a crow-behaviour mostra o progresso como a lista de agentes do host (`● main`, `○ dono atividade`); `crow-skills events` publica um barramento de eventos dos hooks para terminais; `crow-skills autoupdate on` atualiza o CLI e as skills gerenciadas todo dia e no login. A 0.7.1 trouxe as correções da revalidação no Codex às skills especialistas; a 0.7.0 tornou o Uniflow agente de instalação (`--agents uniflow`); a 0.6.1 passou a mostrar o progresso da crow-behaviour na própria conversa; a 0.6.0 trouxe as regras de entrega das 32 skills especialistas, a partir da [validação no Codex](docs/evaluations/2026-10-26-codex.md).
5
+ **Versão 0.11.1** ([mudanças](CHANGELOG.md)): o Codex deixa de mostrar em dobro as skills que o Uniflow já projeta em `~/.codex/skills`. A 0.11.0 trouxe `crow-skills setup --yes`, que configura de uma vez todos os CLIs de agente da máquina, e `crow-skills upgrade --yes`, que atualiza tudo numa só passada; os hooks que o crow-skills grava já saem habilitados (no Codex, aprovados pelo próprio `codex app-server`, só os do crow-skills; `--no-trust` desliga) e `status`/`doctor` mostram se cada CLI vai rodá-los; a ajuda ficou curta e organizada por tarefa. A 0.10.1 passou a tratar swap e CPU como eixos de pressão no controle de recursos (swap quase cheio e carga alta reduzem agentes, jobs e workers; nenhuma recomendação cresce com a pressão), mostrou a CPU% dos CLIs de agente, trouxe `crow-skills resources --exec-prefix` para rodar jobs pesados em prioridade baixa e as regras da crow-behaviour sob pressão (reduzir já, um job pesado por vez, não repetir verificação, validação ampla no CI); os hooks do Codex vão para o `config.toml` quando o `hooks.json` é symlink. A 0.10.0 trouxe o crow-broadcast, que compartilha, de forma passiva, o que cada sessão e cada CLI (Claude Code, Codex, Gemini CLI, Antigravity, Devin, inclusive dentro do Uniflow) está fazendo nesta máquina: repositório, clone, branch, execução crow em andamento e arquivos em edição; o prompt recebe um resumo só quando outra sessão viva está no mesmo repositório, editar um arquivo que outra sessão tocou ou trocar de branch num clone em uso gera aviso (ou bloqueio, se configurado), e `crow-skills broadcast claim` reserva áreas; a crow-behaviour consulta isso antes de planejar e reserva as áreas que delega ([crow-broadcast](docs/broadcast.md)). A 0.9.0 trouxe o controle de memória da máquina: `crow-skills resources` mede memória, pressão e os CLIs de agente em execução e recomenda quantos agentes, jobs pesados e workers rodar em paralelo; a crow-behaviour consulta essa recomendação antes de cada fan-out (sob `warn` reduz, sob `critical` segue sequencial), limita workers de builds e testes, reutiliza um dev server por projeto e encerra o que abriu, e 15 especialistas que sobem processos pesados ganharam a regra correspondente; o hook de prompt e as status lines só avisam quando a máquina está sob pressão. A 0.8.0 trouxe `crow-skills mode on|off`, que liga e desliga a crow-behaviour em todas as sessões e CLIs (Claude Code, Codex, Gemini CLI, Antigravity, Devin) a partir do próximo prompt, com `/crow on|off` dentro da sessão e o indicador `▸▸ crow on` na status line; a crow-behaviour mostra o progresso como a lista de agentes do host (`● main`, `○ dono atividade`); `crow-skills events` publica um barramento de eventos dos hooks para terminais; `crow-skills autoupdate on` atualiza o CLI e as skills gerenciadas todo dia e no login. A 0.7.1 trouxe as correções da revalidação no Codex às skills especialistas; a 0.7.0 tornou o Uniflow agente de instalação (`--agents uniflow`); a 0.6.1 passou a mostrar o progresso da crow-behaviour na própria conversa; a 0.6.0 trouxe as regras de entrega das 32 skills especialistas, a partir da [validação no Codex](docs/evaluations/2026-10-26-codex.md).
6
6
 
7
7
  **Reconstrução em andamento.** A primeira versão com 32 contratos genéricos não atende à preservação de capacidades solicitada. Não use esse número nem o mapeamento por categoria como prova de substituição das originais. A revisão passa a abranger cada definição e seus recursos, modos e dependências, sem número fixo de skills ou limite artificial de linhas. Veja [critérios e progresso](docs/reconstruction.md).
8
8
 
@@ -42,7 +42,7 @@ Exemplos:
42
42
  O pacote de cada skill é somente sua pasta em `skills/`. O [CLI crow-skills](docs/cli.md) instala cópias completas e versionadas em agentes/escopos explícitos, com preview, saída JSON, detecção de conflitos e recuperação. Requer Python 3.10+; Git para fontes em clone, Node 18+ para o launcher npm; mutações em macOS/Linux. A distribuição npm privada `@andespindola/crow-skills` inclui um snapshot completo das skills, sem clone ou hooks que as instalem automaticamente. No [Uniflow](docs/cli.md#uniflow), `--agents uniflow --global` grava as skills no store dele (`~/.config/uniflow/skills`), sem tocar no resto da configuração. Em `--global`, instalar a crow-behaviour também grava o guia aditivo do agente (claude, codex, gemini, devin) para usar as skills; `--no-guide` desliga. As integrações de interface (status lines do Claude Code, do Codex e do Antigravity, hooks do modo crow em cada CLI, comando `/crow`, segmento tmux) seguem o mesmo fluxo, preservando a configuração existente; `--no-integrations` desliga. O [barramento de eventos](docs/events.md) e a atualização automática (`crow-skills autoupdate on`) estão no [CLI](docs/cli.md).
43
43
 
44
44
  ```sh
45
- npm install -g @andespindola/crow-skills@0.11.0
45
+ npm install -g @andespindola/crow-skills@0.11.1
46
46
  crow-skills mode
47
47
  crow-skills progress
48
48
  ./bin/crow-skills list
@@ -1,3 +1,3 @@
1
1
  """Portable, local-first installer for the crow-skills catalogue."""
2
2
 
3
- __version__ = "0.11.0"
3
+ __version__ = "0.11.1"
@@ -305,6 +305,60 @@ def preview_self(install, git_pull):
305
305
  return {"kind": "unknown", "result": "skipped", "reason": "instalação não reconhecida (brew, npm ou checkout git)"}
306
306
 
307
307
 
308
+ def broken_projections(agent, home, store, rows):
309
+ """Uniflow links of this CLI whose store entry was edited or lost files: the CLI may not load them, so
310
+ upgrade reports them as needing the user (same as setup), with the command that restores the store."""
311
+ from .guide import broken_fix, store_broken
312
+ names = set()
313
+ if any(r["status"] == "uniflow_link" for r in rows):
314
+ bad = store_broken(home, store)
315
+ names |= {r["name"] for r in rows if r["status"] == "uniflow_link" and r["name"] in bad}
316
+ if agent == "codex":
317
+ from . import projection
318
+ names |= {n for n, kind in projection.links(home, store).items() if kind == "modified"}
319
+ return [{"name": n, "status": "uniflow_link_modified", "fix": broken_fix(n)} for n in sorted(names)]
320
+
321
+
322
+ def reinstall_behaviour(home, entry, dry_run, trust):
323
+ """Codex without a working crow-behaviour (its projection is broken, no native copy): install it natively,
324
+ as setup does, so the guide and hooks keep a coordinator to point to."""
325
+ from .cli import parser, run as cli_run
326
+ argv = ["install", "crow-behaviour", "--agents", "codex", "--global", "--json", "--home", str(home),
327
+ "--dry-run" if dry_run else "--yes"]
328
+ if not trust:
329
+ argv.append("--no-trust")
330
+ data = cli_run(parser().parse_args(argv))
331
+ if any(c["name"] == "crow-behaviour" and c["action"] != "unchanged" for c in data["changes"]):
332
+ entry["updated"].append("crow-behaviour")
333
+ entry["transaction"] = data.get("transaction")
334
+
335
+
336
+ def codex_duplicates(home, store, entry, names, dry_run):
337
+ """Codex sees Uniflow's links in ~/.codex/skills: those names are not updated natively, and intact managed
338
+ native copies of them are removed through uninstall (backup, transaction). Returns the names to update."""
339
+ from . import projection
340
+ from .cli import parser, run as cli_run
341
+ seen = projection.projected("codex", home, store)
342
+ if not seen:
343
+ return names
344
+ dupes = projection.duplicates(home, store)
345
+ entry["via_uniflow"] = sorted(set(entry["via_uniflow"]) | seen)
346
+ entry["kept"] = [k for k in entry["kept"] if k["name"] not in seen]
347
+ entry["kept"] += [{"name": n, "status": "duplicate_modified"} for n in dupes["modified"]]
348
+ entry["kept"] += [{"name": n, "status": "duplicate_shared"} for n in dupes["shared"]]
349
+ if dupes["remove"]:
350
+ argv = ["uninstall", *dupes["remove"], "--agents", "codex", "--global", "--json", "--home", str(home),
351
+ "--dry-run" if dry_run else "--yes", "--no-integrations"]
352
+ try:
353
+ data = cli_run(parser().parse_args(argv))
354
+ entry["removed"] = [{"name": c["name"], "reason": "removida: duplicava a projeção do Uniflow"}
355
+ for c in data["changes"] if c["action"] != "unchanged"]
356
+ entry["removal_transaction"] = data.get("transaction")
357
+ except CrowError as exc:
358
+ entry["error"] = {"code": exc.code, "message": str(exc)}
359
+ return [n for n in names if n not in seen]
360
+
361
+
308
362
  def skills_update(home, trust=True, dry_run=False):
309
363
  """update --global for every agent with managed installs; modified skills are kept and reported.
310
364
 
@@ -334,17 +388,23 @@ def skills_update(home, trust=True, dry_run=False):
334
388
  from .guide import store_managed
335
389
  store_ok = store_managed(home, store) if any(r["status"] == "uniflow_link" for r in rows) else set()
336
390
  projected = sorted(r["name"] for r in rows if r["status"] == "uniflow_link" and r["name"] in store_ok)
391
+ if agent == "codex":
392
+ from . import projection
393
+ projected = sorted(set(projected) | projection.projected("codex", home, store)) # ~/.codex/skills links
394
+ broken = broken_projections(agent, home, store, rows)
337
395
  rows = [r for r in rows if r["name"] and r["revision"]]
338
396
  if not rows:
339
- if projected:
397
+ if projected or broken:
340
398
  # Every crow skill here comes from Uniflow's store (updated as agent uniflow): only the guide,
341
399
  # integrations and hooks of this CLI are refreshed.
342
400
  from . import machine
343
- entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": [], "error": None,
401
+ entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": list(broken), "error": None,
344
402
  "via_uniflow": projected}
345
403
  if dry_run:
346
404
  entry["dry_run"] = True
347
405
  try:
406
+ if agent == "codex" and "crow-behaviour" in [k["name"] for k in broken]:
407
+ reinstall_behaviour(home, entry, dry_run, trust)
348
408
  extras = machine.refresh_extras(agent, home, dry_run, trust)
349
409
  entry["guides"] = (extras.get("guides") or {}).get("transaction")
350
410
  except CrowError as exc:
@@ -362,10 +422,23 @@ def skills_update(home, trust=True, dry_run=False):
362
422
  kept.append({"name": r["name"], "status": "other_origin"}) # update --source or install switches it
363
423
  else:
364
424
  names.append(r["name"])
365
- entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept, "error": None,
366
- "via_uniflow": projected}
425
+ entry = {"agent": agent, "target": target["path"], "updated": [], "unchanged": 0, "kept": kept + list(broken),
426
+ "error": None, "via_uniflow": projected}
367
427
  if dry_run:
368
428
  entry["dry_run"] = True
429
+ if agent == "codex":
430
+ names = codex_duplicates(home, store, entry, names, dry_run)
431
+ if "crow-behaviour" in [k["name"] for k in broken] and "crow-behaviour" not in [r["name"] for r in rows]:
432
+ try:
433
+ reinstall_behaviour(home, entry, dry_run, trust)
434
+ except CrowError as exc:
435
+ entry["error"] = {"code": exc.code, "message": str(exc)}
436
+ if not names and not entry.get("error"):
437
+ from . import machine
438
+ try:
439
+ machine.refresh_extras(agent, home, dry_run, trust)
440
+ except CrowError as exc:
441
+ entry["error"] = {"code": exc.code, "message": str(exc)}
369
442
  if names:
370
443
  try:
371
444
  argv = ["update", *names, "--agents", agent, "--global", "--dry-run" if dry_run else "--yes", "--json",
@@ -14,6 +14,7 @@ from . import guide
14
14
  from . import hook
15
15
  from . import integrations
16
16
  from . import progress
17
+ from . import projection
17
18
  from . import resources
18
19
  from . import machine
19
20
  from . import ui
@@ -332,6 +333,7 @@ def guide_sync(args, home, store, command, targets, plan, source):
332
333
  if agent not in selected and not (record and path in touched):
333
334
  continue
334
335
  added, removed = touched.get(path, (set(), set()))
336
+ removed = removed - projection.projected(agent, home, store) # still provided by Uniflow's projection
335
337
  after = sorted((set(guide.managed_skills(agent, home, store)) | added) - removed)
336
338
  if mech["kind"] == "toml" and guide.tomllib is None:
337
339
  report["skipped"].append({"agent": agent, "reason": "Python 3.11+ (tomllib) é necessário para editar config.toml; guia não alterado."})
@@ -387,6 +389,7 @@ def behaviour_agents(home, store, plan=None):
387
389
  path = os.path.abspath(destinations(agent, home)[0]["path"])
388
390
  added, removed = touched.get(path, (set(), set()))
389
391
  try:
392
+ removed = removed - projection.projected(agent, home, store)
390
393
  managed = set(guide.managed_skills(agent, home, store))
391
394
  except CrowError:
392
395
  # An unreadable skills root (e.g. a dotfiles symlink) holds nothing this CLI manages;
@@ -744,14 +747,43 @@ def pick(source):
744
747
  return names, ",".join(chosen_groups) or None
745
748
 
746
749
 
750
+ def codex_projection_split(home, store, targets, names):
751
+ """The user-scope codex target and which of the names Codex already sees via Uniflow (install skips them)."""
752
+ target = next((t for t in targets if projection.native_target("codex", t, home)), None)
753
+ if target is None or set(target["agents"]) - {"codex"}:
754
+ return None # --agents shared: other hosts read that dir without Uniflow's codex projection
755
+ projected = projection.projected("codex", home, store)
756
+ if not projected:
757
+ return None
758
+ dupes = projection.duplicates(home, store)
759
+ return {"target": target, "skip": set(names) & projected,
760
+ "duplicates": sorted(set(dupes["remove"]) | set(dupes["modified"]))}
761
+
762
+
763
+ def merge_plans(plans):
764
+ merged = {"before": {}, "after": {}, "items": [], "packages": {}, "changes": []}
765
+ for plan in plans:
766
+ merged["before"].update(plan["before"])
767
+ merged["after"].update(plan["after"])
768
+ merged["items"] += plan["items"]
769
+ merged["packages"].update(plan["packages"])
770
+ merged["changes"] += plan["changes"]
771
+ return merged
772
+
773
+
747
774
  def unmanaged_projections(store, home, rows):
748
775
  """A Uniflow link to a store entry crow-skills does not manage is not an install (same rule as setup)."""
749
776
  if not any(r["status"] == "uniflow_link" for r in rows):
750
777
  return rows
751
778
  managed = guide.store_managed(home, store)
752
779
  disabled = machine.store_disabled(home, store)
780
+ broken = guide.store_broken(home, store)
753
781
  for row in rows:
754
- if row["status"] == "uniflow_link" and row["name"] in disabled:
782
+ if row["status"] == "uniflow_link" and row["name"] in broken:
783
+ # The store entry this link resolves to was edited or lost files: not a working install.
784
+ row.update(status="modified", projection="uniflow_link_modified",
785
+ note=guide.BROKEN_REASON[0].upper() + guide.BROKEN_REASON[1:] + ". Para corrigir: " + guide.broken_fix(row["name"]))
786
+ elif row["status"] == "uniflow_link" and row["name"] in disabled:
755
787
  # Managed by crow-skills but disabled by Uniflow: reported as such, never as unmanaged (no --replace).
756
788
  row.update(status="disabled", projection="uniflow_link_disabled",
757
789
  note="Desabilitada no Uniflow (SKILL.md.disabled no store); reabilite no Uniflow para usar.")
@@ -859,6 +891,17 @@ def run(args):
859
891
  result = {"installations": rows, "guides": guides, "integrations": ui, "pending_transactions": pending,
860
892
  "mode": hook.read_mode(home), "activation_verified": False,
861
893
  "reload_hint": "Peça ao host para recarregar/listar skills; reinicie a sessão se necessário."}
894
+ if any(projection.native_target("codex", t, home) for t in targets):
895
+ rows += projection.status_rows(home, store, rows)
896
+ dupes = projection.duplicates(home, store)
897
+ names_ = dupes["remove"] + dupes["modified"] + dupes["shared"]
898
+ if names_:
899
+ # A warning, not unhealthy: both copies are intact and work; Codex just lists them twice.
900
+ result["warnings"] = [{"code": "codex_duplicates", "skills": names_,
901
+ "message": "O Codex vê " + str(len(names_)) + " skill(s) duas vezes (cópia em ~/.agents/skills "
902
+ "e link do Uniflow em " + str(projection.codex_skills_dir(home)) + ").",
903
+ "fix": "crow-skills setup --agents codex --yes"
904
+ + (" --overwrite-modified" if dupes["modified"] else "")}]
862
905
  result["groups"] = group_coverage(rows)
863
906
  try:
864
907
  result["autoupdate"] = autoupdate.status(store, home) if global_view else None
@@ -942,7 +985,17 @@ def run(args):
942
985
  for name in list(names):
943
986
  source.require(name)
944
987
  names.extend(n for n in source.catalog[name]["next"] if n not in names)
945
- plan = store.plan(args.command, names, targets, source, getattr(args, "replace", False), args.overwrite_modified)
988
+ codex_view = None
989
+ if args.command == "install" and not (getattr(args, "project", None) or getattr(args, "dest", None)):
990
+ codex_view = codex_projection_split(home, store, targets, names)
991
+ if codex_view and codex_view["skip"]:
992
+ # Codex already sees these through Uniflow's links in ~/.codex/skills: no native copy for codex.
993
+ plans = [store.plan(args.command, [n for n in names if n not in codex_view["skip"]] if t is codex_view["target"] else names,
994
+ [t], source, getattr(args, "replace", False), args.overwrite_modified)
995
+ for t in targets if t is not codex_view["target"] or set(names) - codex_view["skip"]]
996
+ plan = merge_plans(plans)
997
+ else:
998
+ plan = store.plan(args.command, names, targets, source, getattr(args, "replace", False), args.overwrite_modified)
946
999
  guides, gplan = guide_sync(args, home, store, args.command, targets, plan, source)
947
1000
  ui = integrations_preview(args, home, store, plan)
948
1001
  summary = {"changes": plan["changes"], "revision": source.revision if source else None,
@@ -953,6 +1006,14 @@ def run(args):
953
1006
  notice = uniflow_notice(home, store, targets, plan) if args.command != "uninstall" else None
954
1007
  if notice is not None:
955
1008
  summary["uniflow"] = notice
1009
+ if codex_view and (codex_view["skip"] or codex_view["duplicates"]):
1010
+ summary["codex_projection"] = {
1011
+ "via_uniflow": sorted(codex_view["skip"]), "duplicates": codex_view["duplicates"],
1012
+ "note": "O Codex já vê estas skills pelo link do Uniflow em " + str(projection.codex_skills_dir(home)) +
1013
+ "; nenhuma cópia nativa é criada para o codex. Cópias nativas que duplicam a projeção não são removidas "
1014
+ "por install; remova-as com o comando sugerido ou rode crow-skills setup --yes.",
1015
+ "suggested_command": ("crow-skills uninstall " + " ".join(codex_view["duplicates"]) + " --agents codex --global")
1016
+ if codex_view["duplicates"] else None}
956
1017
  if plan["items"] or (gplan and gplan["items"]) or ui.get("changes"):
957
1018
  authorize(args, interactive, summary)
958
1019
  txid = None
@@ -1023,6 +1084,8 @@ def render_status(data, doctor=False, stream=None):
1023
1084
  ("fail" if h["state"] in machine.BAD_HOOKS else "warn"), stream),
1024
1085
  h["integration"], ui.word(h["state"]),
1025
1086
  ui.clean(h.get("fix") or "")] for h in hooks], stream=stream)
1087
+ for warning in data.get("warnings") or []:
1088
+ lines += ["", ui.mark("warn", stream) + " " + ui.clean(warning["message"]) + "\n Para corrigir: " + warning["fix"]]
1026
1089
  if data.get("pending_transactions"):
1027
1090
  lines += ["", ui.mark("fail", stream) + " Transação pendente: crow-skills recover --yes"]
1028
1091
  if doctor:
@@ -187,17 +187,39 @@ def managed_skills(agent, home, store):
187
187
  target = destinations(agent, home)[0]
188
188
  rows = store.status([target], ())
189
189
  store_ok = store_managed(home, store) if any(r["status"] == "uniflow_link" for r in rows) else set()
190
- return sorted(r["name"] for r in rows if r["status"] in ("installed", "modified")
191
- or (r["status"] == "uniflow_link" and r["name"] in store_ok))
190
+ names = {r["name"] for r in rows if r["status"] in ("installed", "modified")
191
+ or (r["status"] == "uniflow_link" and r["name"] in store_ok)}
192
+ from .projection import projected
193
+ return sorted(names | projected(agent, home, store)) # codex: Uniflow's links in ~/.codex/skills
192
194
 
193
195
 
194
- def store_managed(home, store):
195
- """Names whose Uniflow store copy is a crow-skills install (a record, files installed or edited).
196
- A link to a store entry the user wrote by hand is not a crow-skills install for the CLI."""
196
+ def store_statuses(home, store):
197
+ """{name: status} of the Uniflow store (crow-skills view); {} when it cannot be read."""
197
198
  try:
198
- return {r["name"] for r in store.status(destinations("uniflow", home), ()) if r["status"] in ("installed", "modified")}
199
+ return {r["name"]: r["status"] for r in store.status(destinations("uniflow", home), ()) if r["name"]}
199
200
  except (CrowError, OSError):
200
- return set()
201
+ return {}
202
+
203
+
204
+ def store_managed(home, store):
205
+ """Names whose Uniflow store copy is an intact crow-skills install. A link to a store entry the user wrote
206
+ by hand, or to a managed entry that was edited or lost files (a CLI may fail to load it), does not count
207
+ as an install for the CLI."""
208
+ return {n for n, s in store_statuses(home, store).items() if s == "installed"}
209
+
210
+
211
+ def store_broken(home, store):
212
+ """Managed store entries that were edited or lost files (status modified/missing)."""
213
+ return {n for n, s in store_statuses(home, store).items() if s in ("modified", "missing")}
214
+
215
+
216
+ BROKEN_REASON = ("a entrada do store do Uniflow foi editada ou perdeu arquivos; o CLI pode não carregá-la, então não conta "
217
+ "como instalada")
218
+
219
+
220
+ def broken_fix(name):
221
+ return ("revise o store e, para restaurá-lo com backup: crow-skills update " + name +
222
+ " --agents uniflow --global --overwrite-modified --yes")
201
223
 
202
224
 
203
225
  def present_skills(agent, home, store, catalog):
@@ -173,6 +173,7 @@ def classify(store, target, names, agent, replace, overwrite, store_names=()):
173
173
  pending = set(store_names) if projected else set()
174
174
  store_ok = guide.store_managed(store.home, store) if projected else set()
175
175
  disabled = store_disabled(store.home, store) if projected else set()
176
+ broken = guide.store_broken(store.home, store) if projected else set()
176
177
  for name in names:
177
178
  row = rows.get(name) or {"status": "absent"}
178
179
  status = row["status"]
@@ -181,6 +182,9 @@ def classify(store, target, names, agent, replace, overwrite, store_names=()):
181
182
  elif status == "uniflow_link" and name in disabled:
182
183
  kept.append({"name": name, "status": "uniflow_link_disabled", "reason": DISABLED_REASON,
183
184
  "fix": "reabilite a skill no Uniflow; o crow-skills mantém a desabilitação"})
185
+ elif status == "uniflow_link" and name in broken:
186
+ kept.append({"name": name, "status": "uniflow_link_modified", "reason": guide.BROKEN_REASON,
187
+ "fix": guide.broken_fix(name)})
184
188
  elif status == "uniflow_link":
185
189
  kept.append({"name": name, "status": "uniflow_link_unmanaged",
186
190
  "reason": "link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada",
@@ -240,6 +244,8 @@ def configure_agent(args, home, store, agent, names, dry_run, store_names=()):
240
244
  try:
241
245
  install, entry["kept"], entry["via_uniflow"], waiting = classify(store, target, names, agent, replace, overwrite,
242
246
  store_names if agent != "uniflow" else ())
247
+ if agent == "codex":
248
+ install = codex_projection_step(args, home, store, entry, names, install, waiting, store_names, dry_run)
243
249
  if waiting:
244
250
  entry["via_uniflow_pending"] = waiting
245
251
  if not install:
@@ -272,6 +278,60 @@ def configure_agent(args, home, store, agent, names, dry_run, store_names=()):
272
278
  return entry
273
279
 
274
280
 
281
+ DUPLICATE = "removida: duplicava a projeção do Uniflow"
282
+
283
+
284
+ def codex_projection_step(args, home, store, entry, names, install, waiting, store_names, dry_run):
285
+ """Codex sees ~/.codex/skills (Uniflow's links to its store) and ~/.agents/skills (our copies) and lists a
286
+ same-name skill twice. A name Codex sees via a managed projection gets no native copy, and an intact
287
+ managed native copy of it is removed through uninstall (backup, transaction, rollback). Returns the
288
+ names still to install natively."""
289
+ from . import projection
290
+ for name, kind in sorted(projection.links(home, store).items()):
291
+ if kind == "modified" and name in names:
292
+ entry["kept"].append({"name": name, "status": "uniflow_link_modified",
293
+ "reason": guide.BROKEN_REASON + "; a cópia nativa em ~/.agents/skills continua valendo",
294
+ "fix": guide.broken_fix(name)})
295
+ projected = projection.projected("codex", home, store)
296
+ if not projected:
297
+ return install
298
+ selected = set(names)
299
+ # Names on their way into the store reach Codex by the same projection on its next start.
300
+ pending = (set(store_names) - projected - {"crow-behaviour"}) & set(install)
301
+ pending = {n for n in pending if n not in entry["unchanged"]}
302
+ native = {r["name"]: r["status"] for r in store.status([destinations("codex", home)[0]], sorted(pending)) if r["name"]}
303
+ pending = {n for n in pending if native.get(n, "absent") == "absent"}
304
+ entry["via_uniflow"] = sorted(set(entry["via_uniflow"]) | (projected & selected) | pending)
305
+ waiting.extend(sorted(pending))
306
+ dupes = projection.duplicates(home, store)
307
+ overwrite = bool(getattr(args, "overwrite_modified", False))
308
+ remove = [n for n in dupes["remove"] + (dupes["modified"] if overwrite else []) if n in selected]
309
+ entry["kept"] = [k for k in entry["kept"] if k["name"] not in projected]
310
+ for name in dupes["modified"]:
311
+ if name in selected and not overwrite:
312
+ entry["kept"].append({"name": name, "status": "duplicate_modified",
313
+ "reason": "cópia nativa editada que duplica a projeção do Uniflow no Codex",
314
+ "fix": "revise e, para remover com backup: crow-skills setup --agents codex --overwrite-modified --yes"})
315
+ for name in dupes["shared"]:
316
+ if name in selected:
317
+ entry["kept"].append({"name": name, "status": "duplicate_shared",
318
+ "reason": "cópia nativa também registrada para shared (diretório lido por outros hosts); "
319
+ "mantida, e o Codex a vê duas vezes",
320
+ "fix": "se nenhum outro host precisa dela: crow-skills uninstall " + name +
321
+ " --agents codex,shared --global --yes"})
322
+ if remove:
323
+ cli = _cli()
324
+ argv = ["uninstall", *remove, "--agents", "codex", "--global", "--json", "--home", str(home),
325
+ "--dry-run" if dry_run else "--yes"]
326
+ if overwrite:
327
+ argv.append("--overwrite-modified")
328
+ argv.append("--no-integrations") # hooks/trust follow in the install/refresh step, with the run's --no-trust
329
+ data = cli.run(cli.parser().parse_args(argv))
330
+ entry["removed"] = [{"name": c["name"], "reason": DUPLICATE} for c in data["changes"] if c["action"] != "unchanged"]
331
+ entry["removal_transaction"] = data.get("transaction")
332
+ return [n for n in install if n not in projected and n not in pending]
333
+
334
+
275
335
  def machine_steps(args, home, store, dry_run):
276
336
  steps = {}
277
337
  # Crow mode: written as on only when the user never chose (no mode file).
@@ -409,6 +469,8 @@ def summarize(detected, entries, store, home, probe, dry_run, trust=True):
409
469
  parts.append(str(unchanged) + " já em dia")
410
470
  if entry.get("via_uniflow"):
411
471
  parts.append(str(len(entry["via_uniflow"])) + " via uniflow")
472
+ if entry.get("removed"):
473
+ parts.append(str(len(entry["removed"])) + (" a remover" if dry_run else " removidas") + " (duplicavam o Uniflow)")
412
474
  if kept:
413
475
  parts.append(str(len(kept)) + " mantidas")
414
476
  item["skills"] = ", ".join(parts) or "—"
@@ -466,7 +528,7 @@ def summarize(detected, entries, store, home, probe, dry_run, trust=True):
466
528
  elif kept or state in ("warn", "absent"):
467
529
  item["state"] = "warn"
468
530
  else:
469
- item["state"] = "plan" if dry_run and (entry["changed"] or real_changes(entry.get("integrations"))) else "ok"
531
+ item["state"] = "plan" if dry_run and (entry["changed"] or entry.get("removed") or real_changes(entry.get("integrations"))) else "ok"
470
532
  summary.append(item)
471
533
  return summary
472
534
 
@@ -497,7 +559,7 @@ def setup(args, home, store, interactive):
497
559
  entries, steps = build(True)
498
560
  result = {"version": __version__, "revision": source.revision, "skills": names, "detected": detected, "agents": agents,
499
561
  "absent": [r["id"] for r in detected if not r["detected"]], "trust": not getattr(args, "no_trust", False)}
500
- pending = any(e["changed"] or real_changes(e.get("integrations")) or real_changes(e.get("guides"))
562
+ pending = any(e["changed"] or e.get("removed") or real_changes(e.get("integrations")) or real_changes(e.get("guides"))
501
563
  for e in entries) or any(s["state"] == "plan" for s in steps.values())
502
564
  if dry_run or not agents:
503
565
  summary = summarize(detected, entries, store, home, False, True)
@@ -564,11 +626,21 @@ def upgrade_summary(skills, dry_run):
564
626
  agent = s["agent"]
565
627
  item = {"agent": agent, "label": AGENT_LABELS.get(agent, agent), "changed": s.get("updated") or [],
566
628
  "unchanged": s.get("unchanged", 0), "kept": s.get("kept") or [], "via_uniflow": len(s.get("via_uniflow") or []),
629
+ "removed": [r["name"] for r in s.get("removed") or []],
567
630
  "needs_user": []}
568
631
  for k in item["kept"]:
569
632
  if k["status"] in ("modified", "missing"):
570
633
  item["needs_user"].append(k["name"] + " editada localmente; para substituir com backup: crow-skills update " +
571
634
  k["name"] + " --agents " + agent + " --global --overwrite-modified --yes")
635
+ elif k["status"] == "duplicate_modified":
636
+ item["needs_user"].append(k["name"] + " (cópia nativa editada) duplica a projeção do Uniflow no Codex; para remover "
637
+ "com backup: crow-skills setup --agents codex --overwrite-modified --yes")
638
+ elif k["status"] == "uniflow_link_modified":
639
+ item["needs_user"].append(k["name"] + ": a entrada do store do Uniflow foi editada ou perdeu arquivos; para "
640
+ "restaurá-la com backup: " + k.get("fix", "crow-skills update " + k["name"] +
641
+ " --agents uniflow --global --overwrite-modified --yes").split(": ", 1)[-1])
642
+ elif k["status"] == "duplicate_shared":
643
+ item["needs_user"].append(k["name"] + " duplica a projeção do Uniflow no Codex, mas também é de shared; mantida")
572
644
  elif k["status"] == "other_origin":
573
645
  item["needs_user"].append(k["name"] + " instalada de outra origem; para trocar: crow-skills install " +
574
646
  k["name"] + " --agents " + agent + " --global --yes")
@@ -576,7 +648,7 @@ def upgrade_summary(skills, dry_run):
576
648
  item["needs_user"].append(k["name"] + ": " + k["status"])
577
649
  if s.get("error"):
578
650
  item["needs_user"].insert(0, s["error"]["message"])
579
- item["state"] = "fail" if s.get("error") else ("warn" if item["needs_user"] else ("plan" if dry_run and item["changed"] else "ok"))
651
+ item["state"] = "fail" if s.get("error") else ("warn" if item["needs_user"] else ("plan" if dry_run and (item["changed"] or item["removed"]) else "ok"))
580
652
  rows.append(item)
581
653
  return rows
582
654
 
@@ -698,8 +770,11 @@ def render_upgrade(data, stream=None):
698
770
  rows = []
699
771
  for item in data.get("summary", []):
700
772
  changed = ", ".join(item["changed"]) if len(item["changed"]) <= 3 else str(len(item["changed"])) + " skills"
773
+ removed = ((" · " if changed else "") + str(len(item["removed"])) + (" a remover" if dry else " removidas") +
774
+ " (duplicavam o Uniflow)") if item.get("removed") else ""
701
775
  rows.append([ui.mark(STATE_MARK[item["state"]], stream), item["label"],
702
- (("a atualizar: " if dry else "atualizadas: ") + changed) if changed else "sem mudanças",
776
+ ((("a atualizar: " if dry else "atualizadas: ") + changed) if changed else ("" if removed else "sem mudanças"))
777
+ + removed,
703
778
  str(item["unchanged"]), str(item.get("via_uniflow", 0)), str(len(item["kept"]))])
704
779
  if rows:
705
780
  lines.append("")
@@ -0,0 +1,128 @@
1
+ """Uniflow's projection of its store into Codex's own skills dir ($CODEX_HOME/skills, default ~/.codex/skills).
2
+
3
+ Codex scans both $CODEX_HOME/skills and ~/.agents/skills (where crow-skills installs for codex), follows
4
+ symlinks and lists a same-name skill twice when the two entries resolve to different directories. Uniflow
5
+ links ~/.codex/skills/crow-x -> <store>/crow-x, so a native copy in ~/.agents/skills duplicates it.
6
+
7
+ A link counts as the codex install of a skill ("via uniflow") only when it resolves to <store>/<name> and
8
+ that store entry is crow-skills-managed (guide.store_managed); a store entry Uniflow disabled is reported as
9
+ disabled; a link to anything else is ignored. A link ~/.codex/skills/crow-x -> ~/.agents/skills/crow-x is an
10
+ alias Codex deduplicates by canonical path: harmless, never touched.
11
+ """
12
+ import os
13
+ from pathlib import Path
14
+
15
+ from .agents import destinations, real_home, uniflow_store
16
+ from .source import CrowError
17
+
18
+
19
+ def codex_home(home):
20
+ """$CODEX_HOME for the real profile (as Codex resolves it); an isolated --home uses <home>/.codex."""
21
+ home = Path(home)
22
+ if real_home(home) and os.environ.get("CODEX_HOME"):
23
+ return Path(os.path.abspath(Path(os.environ["CODEX_HOME"]).expanduser()))
24
+ return Path(os.path.abspath(home / ".codex"))
25
+
26
+
27
+ def codex_skills_dir(home):
28
+ return codex_home(home) / "skills"
29
+
30
+
31
+ def links(home, store):
32
+ """{name: "managed" | "disabled" | "modified" | "unmanaged" | "alias" | "other"} for crow-* symlinks in Codex's own
33
+ skills dir: to an intact managed / Uniflow-disabled / edited-or-incomplete managed / hand-written store entry, to our
34
+ own copy, or elsewhere. Only "managed" counts as the codex install and allows removing a native duplicate."""
35
+ directory = codex_skills_dir(home)
36
+ try:
37
+ if directory.is_symlink() or not directory.is_dir():
38
+ return {}
39
+ entries = [p for p in directory.iterdir() if p.name.startswith("crow-") and p.is_symlink()]
40
+ except OSError:
41
+ return {}
42
+ if not entries:
43
+ return {}
44
+ store_dir = uniflow_store(home)
45
+ native = Path(destinations("codex", home)[0]["path"])
46
+ from .guide import store_statuses
47
+ statuses = store_statuses(home, store) # only an intact ("installed") store entry is a working codex install
48
+ result = {}
49
+ for link in entries:
50
+ name = link.name
51
+ resolved = os.path.realpath(link)
52
+ if resolved == os.path.realpath(native / name):
53
+ result[name] = "alias"
54
+ elif (resolved == os.path.realpath(store_dir / name) and (store_dir / name).is_dir()
55
+ and not (store_dir / name).is_symlink()):
56
+ state = statuses.get(name)
57
+ result[name] = {"installed": "managed", "disabled": "disabled", "modified": "modified",
58
+ "missing": "modified"}.get(state, "unmanaged")
59
+ else:
60
+ result[name] = "other"
61
+ return result
62
+
63
+
64
+ def status_rows(home, store, rows):
65
+ """Rows for `status`/`doctor`: Codex's view through Uniflow's links, as a destination of its own."""
66
+ directory = str(codex_skills_dir(home))
67
+ extra = []
68
+ for name, kind in sorted(links(home, store).items()):
69
+ if kind == "managed":
70
+ extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "uniflow_link", "revision": None,
71
+ "activation_verified": False, "projection": "codex_home",
72
+ "note": "Link do Uniflow em " + directory + " para o store; é a instalação do codex (via uniflow)."})
73
+ elif kind == "disabled":
74
+ extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "disabled", "revision": None,
75
+ "activation_verified": False, "projection": "uniflow_link_disabled",
76
+ "note": "Desabilitada no Uniflow (SKILL.md.disabled no store); reabilite no Uniflow para usar."})
77
+ elif kind == "modified":
78
+ from .guide import BROKEN_REASON, broken_fix
79
+ extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "modified", "revision": None,
80
+ "activation_verified": False, "projection": "uniflow_link_modified",
81
+ "note": BROKEN_REASON[0].upper() + BROKEN_REASON[1:] + ". Para corrigir: " + broken_fix(name)})
82
+ elif kind == "unmanaged":
83
+ extra.append({"name": name, "target": directory, "agents": ["codex"], "status": "unmanaged", "revision": None,
84
+ "activation_verified": False, "projection": "uniflow_link_unmanaged",
85
+ "note": "Link do Uniflow para um pacote do store que o crow-skills não gerencia; não conta como instalada. "
86
+ "Revise o store e, para gerenciá-lo com backup: crow-skills setup --agents uniflow --replace --yes"})
87
+ return extra
88
+
89
+
90
+ def projected(agent, home, store):
91
+ """Names Codex already sees through a managed Uniflow projection (empty for other agents)."""
92
+ if agent != "codex":
93
+ return set()
94
+ return {n for n, kind in links(home, store).items() if kind == "managed"}
95
+
96
+
97
+ def native_target(agent, target, home):
98
+ """target is the user-scope codex install dir (not --project/--dest)."""
99
+ return agent == "codex" and os.path.abspath(target["path"]) == os.path.abspath(destinations("codex", home)[0]["path"])
100
+
101
+
102
+ def duplicates(home, store):
103
+ """Native copies in ~/.agents/skills that duplicate a managed projection, by what can be done with them.
104
+
105
+ Returns {"remove": [...], "modified": [...], "shared": [...]}: "remove" are crow-skills-managed and intact;
106
+ "modified" were edited locally (kept unless --overwrite-modified); "shared" are also recorded for another
107
+ agent of that dir (shared), which may rely on them."""
108
+ names = projected("codex", home, store)
109
+ result = {"remove": [], "modified": [], "shared": []}
110
+ if not names:
111
+ return result
112
+ target = destinations("codex", home)[0]
113
+ try:
114
+ rows = {r["name"]: r for r in store.status([target], sorted(names)) if r["name"]}
115
+ records = store.state(target["path"])["installed"]
116
+ except (CrowError, OSError):
117
+ return result
118
+ for name in sorted(names):
119
+ row = rows.get(name)
120
+ if not row or row["status"] not in ("installed", "modified", "missing"):
121
+ continue # absent, unmanaged or unreadable: nothing of ours to remove
122
+ if set(records.get(name, {}).get("agents") or ["codex"]) - {"codex"}:
123
+ result["shared"].append(name)
124
+ elif row["status"] == "installed":
125
+ result["remove"].append(name)
126
+ else:
127
+ result["modified"].append(name)
128
+ return result
package/docs/cli.md CHANGED
@@ -1,4 +1,4 @@
1
- # crow-skills CLI 0.11.0
1
+ # crow-skills CLI 0.11.1
2
2
 
3
3
  CLI local-first para humanos e agentes. Python 3.10+, sem bibliotecas Python externas; Git é necessário para fontes em clone, não para o snapshot npm. O launcher npm requer Node 18+. Mutações suportadas em macOS/Linux (locks POSIX). Não instala o agente, não executa scripts das skills e não muda modelos, prompts base ou permissões. Instruções de uso das skills (o guia do agente, abaixo) são gravadas pelo comando `guide` e, por padrão, por `install`/`update`/`uninstall` em `--global` quando a crow-behaviour fica instalada para um agente com mecanismo verificado; `--no-guide` desliga isso. Os mesmos comandos também aplicam as [integrações de interface](#integrações-de-interface-integrations) (status lines do Claude Code, do Codex e do Antigravity, hooks do modo crow em cada CLI, comando `/crow`, segmento tmux); `--no-integrations` desliga. `crow-skills mode on|off` liga e desliga a crow-behaviour em [todas as sessões e CLIs](#modo-crow-mode); `crow-skills progress` mostra o [painel de progresso](#progresso-progress) da execução ativa; `crow-skills events` expõe o [barramento de eventos](#eventos-events) dos hooks; `crow-skills autoupdate on` liga a [atualização automática](#atualização-automática-autoupdate).
4
4
 
@@ -81,7 +81,7 @@ Para testar/distribuir o arquivo local sem registry:
81
81
 
82
82
  ```sh
83
83
  npm pack
84
- npm install -g ./andespindola-crow-skills-0.11.0.tgz --ignore-scripts
84
+ npm install -g ./andespindola-crow-skills-0.11.1.tgz --ignore-scripts
85
85
  ```
86
86
 
87
87
  O pacote contém o CLI Python e todas as skills do commit, com `release.json` de hashes. Não requer acesso ao GitHub ou clone para list/install. O launcher Node encaminha argumentos sem shell para Python em modo isolado, evitando imports pelo diretório do chamador/PYTHONPATH. `CROW_SKILLS_PYTHON` pode apontar para outro executável Python 3.10+. Não há dependências npm nem hooks de instalação que instalem skills automaticamente.
@@ -217,6 +217,18 @@ O Uniflow guarda as skills num store próprio e cria, ao subir cada CLI, links d
217
217
  - Cópias nativas duplicadas: se claude, gemini ou devin já têm um pacote crow de mesmo nome, o Uniflow não cria o link para ele e a cópia nativa continua valendo ali, possivelmente em outra revisão (`effect: skipped`). No codex é pior: o crow-skills instala em `~/.agents/skills`, o Uniflow liga o store em `~/.codex/skills` e o Codex lê os dois, vendo duas skills de mesmo nome (`effect: both`). `install`/`update` com `uniflow` listam essas cópias em `uniflow.native_duplicates` (diretórios que não puderam ser lidos, como um `~/.claude` symlink de dotfiles, vão para `uniflow.uninspected` sem derrubar o comando) e sugerem em `uniflow.suggested_command` o `uninstall` explícito; nada é removido sem esse comando. Remover a cópia nativa da crow-behaviour remove também o guia daquele agente, que deixa de valer fora do Uniflow.
218
218
  - Links projetados: em `status`/`doctor`, um link do Uniflow para o store num diretório de agente aparece como `uniflow_link`, e não deixa o doctor `healthy:false`.
219
219
 
220
+ #### Codex e a projeção do Uniflow
221
+
222
+ O Codex lê duas raízes de skills do usuário: `$CODEX_HOME/skills` (padrão `~/.codex/skills`, onde o Uniflow liga o store) e `~/.agents/skills` (onde o crow-skills instala para o codex). Ele segue symlinks e lista duas vezes uma skill de mesmo nome quando as duas entradas apontam para diretórios diferentes. Por isso:
223
+
224
+ - Um link `~/.codex/skills/crow-x` → `<store>/crow-x`, com a entrada do store gerenciada pelo crow-skills, conta como a instalação do codex (`via uniflow`). Entrada desabilitada no Uniflow aparece como `disabled`; entrada escrita à mão, como não gerenciada (com o mesmo motivo e correção do resto do Uniflow). `CODEX_HOME` vale para o perfil real; `--home` isolado usa `<home>/.codex`.
225
+ - `setup` e `upgrade` não criam cópia nativa em `~/.agents/skills` para esses nomes e removem, pelo `uninstall` normal (backup, transação, `rollback`), as cópias nativas gerenciadas e intactas que duplicam a projeção (`removida: duplicava a projeção do Uniflow`). Uma cópia editada fica, com aviso e a dica `--overwrite-modified`. Uma cópia também registrada para `shared` (`--agents codex,shared`: o diretório é lido por outros hosts) fica, porque outro host pode depender dela; o motivo e o comando aparecem no resumo.
226
+ - `install --agents codex` não cria cópia nativa de um nome projetado (`codex_projection.via_uniflow` no resumo) e nunca remove nada: as duplicatas existentes aparecem em `codex_projection.duplicates`, com `suggested_command`. Com `--agents codex,shared` instala como antes.
227
+ - A crow-behaviour continua nativa onde o Uniflow não a projeta, e o guia, as integrações e os hooks do Codex seguem funcionando; quando ela é projetada, o guia conta a projeção.
228
+ - Um link `~/.codex/skills/crow-x` → `~/.agents/skills/crow-x` é só um apelido (o Codex o deduplica pelo caminho canônico): nunca é alterado nem removido.
229
+ - `status`/`doctor` mostram `~/.codex/skills` como um destino do codex (`N skills (N via uniflow)`). Duplicatas geram o aviso `codex_duplicates` (`warnings`) com o comando que resolve; o doctor continua `healthy:true`, porque as duas cópias estão íntegras e funcionam (o Codex só as lista duas vezes).
230
+
231
+
220
232
  Fontes primárias consultadas em 2026-10-20: [Codex](https://learn.chatgpt.com/docs/build-skills#where-codex-loads-local-skills), [Claude](https://code.claude.com/docs/en/skills), [Gemini](https://geminicli.com/docs/cli/using-agent-skills/), [Devin](https://docs.devin.ai/cli/extensibility/skills/overview).
221
233
 
222
234
  ## Instruções para o agente (guide)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/crow-skills",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Versioned, complementary agent skills with a protected multi-agent installer",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
package/release.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "README.md": {
4
4
  "executable": false,
5
5
  "kind": "file",
6
- "sha256": "fe7ae8fa72f3edc6ed2845ed276aeab3e56cd4f8e4a4dea13e0d1adf578107ca"
6
+ "sha256": "2f357dd4b258621fb3801702907a33b371ec0ff3350f6c9107b60d5c98a736b1"
7
7
  },
8
8
  "bin/npm-crow-skills.cjs": {
9
9
  "executable": true,
@@ -18,7 +18,7 @@
18
18
  "crow_skills/__init__.py": {
19
19
  "executable": false,
20
20
  "kind": "file",
21
- "sha256": "eda470f1bc8d44a9fcee33f2506a91d677b853668c17afc696f2f87b37b8cc3c"
21
+ "sha256": "e2a03185ed4cda7560a68844180c6b1dd684ff3314befe8f0ba978f3e45daf49"
22
22
  },
23
23
  "crow_skills/__main__.py": {
24
24
  "executable": false,
@@ -33,7 +33,7 @@
33
33
  "crow_skills/autoupdate.py": {
34
34
  "executable": false,
35
35
  "kind": "file",
36
- "sha256": "b994bd426a3aa3091bfc49bedc3f63a4df632e431c3f193bbe1873fbe42d6d67"
36
+ "sha256": "bbe3c38f93d8f6e70c0ec428e3d383d7248c61c07c774420c4c47e27c7e9def5"
37
37
  },
38
38
  "crow_skills/broadcast.py": {
39
39
  "executable": false,
@@ -43,7 +43,7 @@
43
43
  "crow_skills/cli.py": {
44
44
  "executable": false,
45
45
  "kind": "file",
46
- "sha256": "9839fabde672833229a589ae027a3a23e42673a4117419e640622c411048ee94"
46
+ "sha256": "1d0730dcfc0bfd2d11747aef884f4bbca9eb7768ce0dd321f0cb7e5506419ab5"
47
47
  },
48
48
  "crow_skills/events.py": {
49
49
  "executable": false,
@@ -58,7 +58,7 @@
58
58
  "crow_skills/guide.py": {
59
59
  "executable": false,
60
60
  "kind": "file",
61
- "sha256": "12fe2d42175a971ff9369571186d6a46b9f94da257ab286cecbc0e213ba8b4a8"
61
+ "sha256": "5b2767b58439c55d1f1d439bc04a9769c54aba2e59a1680aa00a97ba4f5efd0d"
62
62
  },
63
63
  "crow_skills/hook.py": {
64
64
  "executable": false,
@@ -73,7 +73,7 @@
73
73
  "crow_skills/machine.py": {
74
74
  "executable": false,
75
75
  "kind": "file",
76
- "sha256": "f8ea1f41256cd7df9579d658cd78087a094e8d31342394fd4b4a911cc8011616"
76
+ "sha256": "f48bb54b7816ce0fc62e68694dd970f699d355e60db2fb2eab651291294c7012"
77
77
  },
78
78
  "crow_skills/npm_entry.py": {
79
79
  "executable": false,
@@ -85,6 +85,11 @@
85
85
  "kind": "file",
86
86
  "sha256": "b4c34e8abf29656d38771b0d0ea87664babaeab63507df8c50ac5f248cbe0dc3"
87
87
  },
88
+ "crow_skills/projection.py": {
89
+ "executable": false,
90
+ "kind": "file",
91
+ "sha256": "0f6a7c788e58cc54752bbf293f107a2a8e731cacefa2dcd95d3f4998f2866d4a"
92
+ },
88
93
  "crow_skills/resources.py": {
89
94
  "executable": false,
90
95
  "kind": "file",
@@ -118,12 +123,12 @@
118
123
  "docs/cli.md": {
119
124
  "executable": false,
120
125
  "kind": "file",
121
- "sha256": "918d426b725899b2140df76dac40e147af987f8d4c0164b0c4a290df7f67ed50"
126
+ "sha256": "4b39fe944290967c083cd79af886a96653ab334a7dbde6d4ad0a5c7059d38503"
122
127
  },
123
128
  "package.json": {
124
129
  "executable": false,
125
130
  "kind": "file",
126
- "sha256": "c4d7a50a924470c4e4ee4a27b1ce796ca0db0296d8b3f48e13ea7badc72e1ea3"
131
+ "sha256": "2a4fda62a127bf8ddbd35323f698e2886dda6360d41100126cdb8ba05b2a4c32"
127
132
  },
128
133
  "skills/crow-accessibility/SKILL.md": {
129
134
  "executable": false,
@@ -686,7 +691,7 @@
686
691
  "sha256": "e37132ef8b060548191fbf14a5a18a9dd3acd84ea9a35e802174455b9e374d3d"
687
692
  }
688
693
  },
689
- "revision": "e06686f7886a5c816ea91a11ff01197c61cc8fe0",
694
+ "revision": "06d4a9e1e39dad6c977807ee67f00fa5ed311dd1",
690
695
  "schema_version": 1,
691
- "version": "0.11.0"
696
+ "version": "0.11.1"
692
697
  }