agents-city 0.3.0-beta.21
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/.claude-plugin/marketplace.json +16 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.es.md +2021 -0
- package/README.md +1999 -0
- package/benchmarks/committee/README.md +39 -0
- package/benchmarks/committee/metrics.py +51 -0
- package/benchmarks/committee/run.py +64 -0
- package/benchmarks/committee/traces/01-single.json +8 -0
- package/benchmarks/committee/traces/02-mesh.json +16 -0
- package/benchmarks/committee/traces/03-chair.json +15 -0
- package/benchmarks/latency/README.md +35 -0
- package/benchmarks/latency/fake-claude-cli.mjs +119 -0
- package/benchmarks/latency/fake-native-server.mjs +340 -0
- package/benchmarks/latency/fake-tui.py +91 -0
- package/benchmarks/latency/live.py +380 -0
- package/benchmarks/stress/README.md +20 -0
- package/benchmarks/stress/run.py +487 -0
- package/bin/agents +4 -0
- package/bin/agents-city.js +116 -0
- package/bin/benchmark +17 -0
- package/bin/bus +6 -0
- package/bin/cities +3 -0
- package/bin/city +86 -0
- package/bin/committee +6 -0
- package/bin/demo +200 -0
- package/bin/exit +5 -0
- package/bin/hall +73 -0
- package/bin/hall.html +426 -0
- package/bin/logs +3 -0
- package/bin/report.py +14 -0
- package/bin/reset +3 -0
- package/bin/road +3 -0
- package/bin/seat +10 -0
- package/bin/serve.py +1428 -0
- package/bin/setup.py +753 -0
- package/bin/skills +3 -0
- package/bin/test +131 -0
- package/bin/test-adapter.py +183 -0
- package/bin/test-admision.py +99 -0
- package/bin/test-avatar.py +58 -0
- package/bin/test-benchmark.py +40 -0
- package/bin/test-broker.py +334 -0
- package/bin/test-cage.py +206 -0
- package/bin/test-card.py +402 -0
- package/bin/test-channel.py +379 -0
- package/bin/test-cities.py +453 -0
- package/bin/test-claude-runtime.py +252 -0
- package/bin/test-committee.py +340 -0
- package/bin/test-contracts.py +544 -0
- package/bin/test-crecimiento.py +164 -0
- package/bin/test-demo.py +308 -0
- package/bin/test-doctor.py +106 -0
- package/bin/test-domains.py +151 -0
- package/bin/test-evidencia.py +50 -0
- package/bin/test-exit.py +128 -0
- package/bin/test-hall-protocol.py +93 -0
- package/bin/test-launch.py +169 -0
- package/bin/test-live-feed.py +508 -0
- package/bin/test-pairing.py +99 -0
- package/bin/test-parcels.py +283 -0
- package/bin/test-runtime-failures.py +252 -0
- package/bin/test-runtime-ui.py +488 -0
- package/bin/test-runtime.py +268 -0
- package/bin/test-rutas.py +80 -0
- package/bin/test-seat.py +1386 -0
- package/bin/test-security.py +150 -0
- package/bin/test-serve.py +1057 -0
- package/bin/test-stress.py +64 -0
- package/bin/test-widgets.py +143 -0
- package/bin/test-workspace.py +188 -0
- package/bin/testlib.py +95 -0
- package/bin/tokens.py +14 -0
- package/bus/scripts/deploy.sh +60 -0
- package/bus/scripts/mint-token.sh +68 -0
- package/bus/scripts/setup-dev.sh +79 -0
- package/bus/scripts/test-channel.ts +8 -0
- package/bus/scripts/test-hub.ts +266 -0
- package/bus/scripts/test-local.sh +40 -0
- package/bus/scripts/test-queue.ts +132 -0
- package/bus/worker/package-lock.json +1971 -0
- package/bus/worker/package.json +17 -0
- package/bus/worker/src/index.ts +485 -0
- package/bus/worker/tsconfig.json +13 -0
- package/bus/worker/wrangler.toml +32 -0
- package/city/oven/README.md +59 -0
- package/city/oven/collect.py +43 -0
- package/city/oven/oven.html +4 -0
- package/city/scripts/history.py +120 -0
- package/city/scripts/seed.py +442 -0
- package/city/web/assets/modelos/License.txt +28 -0
- package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/assets/modelos/building-a.glb +0 -0
- package/city/web/assets/modelos/building-b.glb +0 -0
- package/city/web/assets/modelos/building-c.glb +0 -0
- package/city/web/assets/modelos/building-d.glb +0 -0
- package/city/web/assets/modelos/building-e.glb +0 -0
- package/city/web/assets/modelos/building-f.glb +0 -0
- package/city/web/assets/modelos/building-g.glb +0 -0
- package/city/web/assets/modelos/building-h.glb +0 -0
- package/city/web/assets/sprites/building-a.png +0 -0
- package/city/web/assets/sprites/building-b.png +0 -0
- package/city/web/assets/sprites/building-c.png +0 -0
- package/city/web/assets/sprites/building-d.png +0 -0
- package/city/web/assets/sprites/building-e.png +0 -0
- package/city/web/assets/sprites/building-f.png +0 -0
- package/city/web/assets/sprites/building-g.png +0 -0
- package/city/web/assets/sprites/building-h.png +0 -0
- package/city/web/assets/sprites/building-i.png +0 -0
- package/city/web/assets/sprites/building-j.png +0 -0
- package/city/web/assets/sprites/building-k.png +0 -0
- package/city/web/assets/sprites/building-l.png +0 -0
- package/city/web/assets/sprites/building-m.png +0 -0
- package/city/web/assets/sprites/building-n.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
- package/city/web/assets/sprites/catalogo.json +47 -0
- package/city/web/assets/sprites/detail-awning-wide.png +0 -0
- package/city/web/assets/sprites/detail-awning.png +0 -0
- package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
- package/city/web/assets/sprites/detail-overhang.png +0 -0
- package/city/web/assets/sprites/detail-parasol-a.png +0 -0
- package/city/web/assets/sprites/detail-parasol-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-c.png +0 -0
- package/city/web/assets/sprites/low-detail-building-d.png +0 -0
- package/city/web/assets/sprites/low-detail-building-e.png +0 -0
- package/city/web/assets/sprites/low-detail-building-f.png +0 -0
- package/city/web/assets/sprites/low-detail-building-g.png +0 -0
- package/city/web/assets/sprites/low-detail-building-h.png +0 -0
- package/city/web/assets/sprites/low-detail-building-i.png +0 -0
- package/city/web/assets/sprites/low-detail-building-j.png +0 -0
- package/city/web/assets/sprites/low-detail-building-k.png +0 -0
- package/city/web/assets/sprites/low-detail-building-l.png +0 -0
- package/city/web/assets/sprites/low-detail-building-m.png +0 -0
- package/city/web/assets/sprites/low-detail-building-n.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/assets/sprites/medidas.json +248 -0
- package/city/web/dist/city.js +939 -0
- package/city/web/dist/index.html +294 -0
- package/city/web/dist/modelos/License.txt +28 -0
- package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/dist/modelos/building-a.glb +0 -0
- package/city/web/dist/modelos/building-b.glb +0 -0
- package/city/web/dist/modelos/building-c.glb +0 -0
- package/city/web/dist/modelos/building-d.glb +0 -0
- package/city/web/dist/modelos/building-e.glb +0 -0
- package/city/web/dist/modelos/building-f.glb +0 -0
- package/city/web/dist/modelos/building-g.glb +0 -0
- package/city/web/dist/modelos/building-h.glb +0 -0
- package/city/web/dist/sprites/building-a.png +0 -0
- package/city/web/dist/sprites/building-b.png +0 -0
- package/city/web/dist/sprites/building-c.png +0 -0
- package/city/web/dist/sprites/building-d.png +0 -0
- package/city/web/dist/sprites/building-e.png +0 -0
- package/city/web/dist/sprites/building-f.png +0 -0
- package/city/web/dist/sprites/building-g.png +0 -0
- package/city/web/dist/sprites/building-h.png +0 -0
- package/city/web/dist/sprites/building-i.png +0 -0
- package/city/web/dist/sprites/building-j.png +0 -0
- package/city/web/dist/sprites/building-k.png +0 -0
- package/city/web/dist/sprites/building-l.png +0 -0
- package/city/web/dist/sprites/building-m.png +0 -0
- package/city/web/dist/sprites/building-n.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
- package/city/web/dist/sprites/catalogo.json +47 -0
- package/city/web/dist/sprites/detail-awning-wide.png +0 -0
- package/city/web/dist/sprites/detail-awning.png +0 -0
- package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
- package/city/web/dist/sprites/detail-overhang.png +0 -0
- package/city/web/dist/sprites/detail-parasol-a.png +0 -0
- package/city/web/dist/sprites/detail-parasol-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-c.png +0 -0
- package/city/web/dist/sprites/low-detail-building-d.png +0 -0
- package/city/web/dist/sprites/low-detail-building-e.png +0 -0
- package/city/web/dist/sprites/low-detail-building-f.png +0 -0
- package/city/web/dist/sprites/low-detail-building-g.png +0 -0
- package/city/web/dist/sprites/low-detail-building-h.png +0 -0
- package/city/web/dist/sprites/low-detail-building-i.png +0 -0
- package/city/web/dist/sprites/low-detail-building-j.png +0 -0
- package/city/web/dist/sprites/low-detail-building-k.png +0 -0
- package/city/web/dist/sprites/low-detail-building-l.png +0 -0
- package/city/web/dist/sprites/low-detail-building-m.png +0 -0
- package/city/web/dist/sprites/low-detail-building-n.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/dist/sprites/medidas.json +248 -0
- package/city/web/dist-hall/hall.js +264 -0
- package/city/web/index.html +294 -0
- package/city/web/package.json +18 -0
- package/city/web/sella.py +21 -0
- package/city/web/src/activity-actors.ts +47 -0
- package/city/web/src/activity.ts +172 -0
- package/city/web/src/ayuntamiento.ts +580 -0
- package/city/web/src/draw.ts +640 -0
- package/city/web/src/game-speech.ts +79 -0
- package/city/web/src/hall.ts +1867 -0
- package/city/web/src/main.ts +2225 -0
- package/city/web/src/oven.ts +124 -0
- package/city/web/src/people.ts +244 -0
- package/city/web/src/presencia.ts +193 -0
- package/city/web/src/puertas.ts +180 -0
- package/city/web/src/repo-roles.ts +59 -0
- package/city/worker/package.json +16 -0
- package/city/worker/schema.sql +117 -0
- package/city/worker/src/index.ts +702 -0
- package/city/worker/src/square.ts +218 -0
- package/city/worker/wrangler.toml +58 -0
- package/demo/ada.md +50 -0
- package/demo/bruno.md +62 -0
- package/demo/camila.md +56 -0
- package/demo/city/ada.md +40 -0
- package/demo/city/city.yml +8 -0
- package/demo/city/parcels.yml +15 -0
- package/demo/city/roads.json +4 -0
- package/demo/city/units.yml +10 -0
- package/demo/clinica/city.yml +8 -0
- package/demo/clinica/parcels.yml +15 -0
- package/demo/clinica/roads.json +4 -0
- package/demo/clinica/units.yml +9 -0
- package/demo/clinica/vera.md +46 -0
- package/demo/dante.md +54 -0
- package/demo/despacho/city.yml +8 -0
- package/demo/despacho/marta.md +46 -0
- package/demo/despacho/parcels.yml +15 -0
- package/demo/despacho/roads.json +4 -0
- package/demo/despacho/units.yml +9 -0
- package/demo/elsa.md +50 -0
- package/demo/farid.md +52 -0
- package/demo/greta.md +50 -0
- package/demo/hugo.md +49 -0
- package/demo/iris.md +49 -0
- package/demo/jonas.md +51 -0
- package/demo/kira.md +51 -0
- package/demo/luca.md +49 -0
- package/demo/parcels.yml +92 -0
- package/demo/seed.py +20 -0
- package/demo/show.py +161 -0
- package/demo/stories.py +824 -0
- package/demo/units.yml +33 -0
- package/docs/agents-first.md +99 -0
- package/docs/glossary.md +55 -0
- package/docs/map-live-layers.md +73 -0
- package/docs/security.md +231 -0
- package/docs/self-host.md +151 -0
- package/docs/testing.md +205 -0
- package/package.json +73 -0
- package/plugin/.claude-plugin/plugin.json +62 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/channel/activity-cli.ts +16 -0
- package/plugin/channel/adapter-prompts.ts +113 -0
- package/plugin/channel/adapter.js +4292 -0
- package/plugin/channel/adapter.ts +133 -0
- package/plugin/channel/bus.js +20188 -0
- package/plugin/channel/bus.ts +131 -0
- package/plugin/channel/city-config.ts +125 -0
- package/plugin/channel/claude-channel.ts +58 -0
- package/plugin/channel/cli-args.ts +75 -0
- package/plugin/channel/client.js +4200 -0
- package/plugin/channel/client.ts +38 -0
- package/plugin/channel/committee/activity.ts +256 -0
- package/plugin/channel/committee/collection.ts +154 -0
- package/plugin/channel/committee/decision.ts +175 -0
- package/plugin/channel/committee/floor.ts +135 -0
- package/plugin/channel/committee/guards.ts +30 -0
- package/plugin/channel/committee/history.ts +38 -0
- package/plugin/channel/committee/render.ts +102 -0
- package/plugin/channel/committee/service.ts +72 -0
- package/plugin/channel/committee/storage.ts +92 -0
- package/plugin/channel/committee/types.ts +163 -0
- package/plugin/channel/committee/view.ts +63 -0
- package/plugin/channel/committee-cli.ts +109 -0
- package/plugin/channel/delivery-metrics.ts +61 -0
- package/plugin/channel/delivery-queue.ts +118 -0
- package/plugin/channel/hub/activity-controller.ts +72 -0
- package/plugin/channel/hub/activity-feed.ts +169 -0
- package/plugin/channel/hub/committee-controller.ts +60 -0
- package/plugin/channel/hub/connections.ts +48 -0
- package/plugin/channel/hub/diagnostics.ts +67 -0
- package/plugin/channel/hub/envelope-validity.ts +109 -0
- package/plugin/channel/hub/envelopes.ts +66 -0
- package/plugin/channel/hub/lifecycle.ts +91 -0
- package/plugin/channel/hub/local-roads.ts +89 -0
- package/plugin/channel/hub/remote-roads.ts +165 -0
- package/plugin/channel/hub/road-controller.ts +99 -0
- package/plugin/channel/hub-client.ts +149 -0
- package/plugin/channel/local-hub.js +5995 -0
- package/plugin/channel/local-hub.ts +349 -0
- package/plugin/channel/map-reporter.ts +48 -0
- package/plugin/channel/package-lock.json +1731 -0
- package/plugin/channel/package.json +19 -0
- package/plugin/channel/protocol.ts +98 -0
- package/plugin/channel/road-cli.ts +18 -0
- package/plugin/channel/run.sh +15 -0
- package/plugin/channel/runtime/claude.ts +458 -0
- package/plugin/channel/runtime/codex-config.ts +77 -0
- package/plugin/channel/runtime/codex.ts +669 -0
- package/plugin/channel/runtime/command.ts +62 -0
- package/plugin/channel/runtime/factory.ts +15 -0
- package/plugin/channel/runtime/json-rpc.ts +154 -0
- package/plugin/channel/runtime/kimi.ts +277 -0
- package/plugin/channel/runtime/opencode.ts +248 -0
- package/plugin/channel/runtime/process.ts +84 -0
- package/plugin/channel/runtime/types.ts +45 -0
- package/plugin/channel/runtime-files.ts +73 -0
- package/plugin/channel/runtime-gateway.js +6396 -0
- package/plugin/channel/runtime-gateway.ts +358 -0
- package/plugin/channel/runtime-metrics.ts +52 -0
- package/plugin/channel/runtime-subscription.ts +180 -0
- package/plugin/channel/terminal-delivery.ts +131 -0
- package/plugin/channel/untrusted.ts +60 -0
- package/plugin/commands/committee.md +31 -0
- package/plugin/commands/exit.md +32 -0
- package/plugin/commands/goals.md +20 -0
- package/plugin/commands/join.md +18 -0
- package/plugin/commands/notice.md +20 -0
- package/plugin/commands/propose.md +19 -0
- package/plugin/commands/round.md +17 -0
- package/plugin/commands/session.md +22 -0
- package/plugin/commands/settings.md +25 -0
- package/plugin/commands/setup.md +21 -0
- package/plugin/commands/team.md +23 -0
- package/plugin/domains/custom.md +27 -0
- package/plugin/domains/finance.md +31 -0
- package/plugin/domains/healthcare.md +34 -0
- package/plugin/domains/legal.md +31 -0
- package/plugin/domains/marketing.md +33 -0
- package/plugin/domains/operations.md +31 -0
- package/plugin/domains/research.md +32 -0
- package/plugin/domains/sales.md +30 -0
- package/plugin/domains/software.md +37 -0
- package/plugin/hooks/activity.sh +6 -0
- package/plugin/hooks/digging.sh +68 -0
- package/plugin/hooks/growth.sh +42 -0
- package/plugin/hooks/hooks.json +106 -0
- package/plugin/hooks/notice-on-pr.sh +35 -0
- package/plugin/hooks/notice-on-stop.sh +80 -0
- package/plugin/hooks/notice-pending.sh +58 -0
- package/plugin/hooks/solo-en-ciudad.sh +23 -0
- package/plugin/hooks/tokens.sh +47 -0
- package/plugin/roles/examples/account-executive.md +25 -0
- package/plugin/roles/examples/ai-manager.md +34 -0
- package/plugin/roles/examples/associate.md +24 -0
- package/plugin/roles/examples/brand-lead.md +20 -0
- package/plugin/roles/examples/cfo.md +20 -0
- package/plugin/roles/examples/city-lead.md +25 -0
- package/plugin/roles/examples/clinical-director.md +26 -0
- package/plugin/roles/examples/clinical-ops.md +25 -0
- package/plugin/roles/examples/clinician.md +25 -0
- package/plugin/roles/examples/compliance.md +27 -0
- package/plugin/roles/examples/content.md +24 -0
- package/plugin/roles/examples/controller.md +27 -0
- package/plugin/roles/examples/cpto.md +41 -0
- package/plugin/roles/examples/customer-success.md +25 -0
- package/plugin/roles/examples/data-engineer.md +37 -0
- package/plugin/roles/examples/data.md +60 -0
- package/plugin/roles/examples/dev.md +37 -0
- package/plugin/roles/examples/devops.md +39 -0
- package/plugin/roles/examples/enablement.md +24 -0
- package/plugin/roles/examples/ethics.md +24 -0
- package/plugin/roles/examples/fin-analytics.md +27 -0
- package/plugin/roles/examples/health-compliance.md +25 -0
- package/plugin/roles/examples/health-data.md +26 -0
- package/plugin/roles/examples/knowledge.md +25 -0
- package/plugin/roles/examples/lifecycle.md +26 -0
- package/plugin/roles/examples/llm-engineer.md +34 -0
- package/plugin/roles/examples/managing-partner.md +20 -0
- package/plugin/roles/examples/methods.md +23 -0
- package/plugin/roles/examples/operations-lead.md +23 -0
- package/plugin/roles/examples/ops.md +24 -0
- package/plugin/roles/examples/patient-safety.md +26 -0
- package/plugin/roles/examples/performance.md +27 -0
- package/plugin/roles/examples/po.md +36 -0
- package/plugin/roles/examples/process-owner.md +23 -0
- package/plugin/roles/examples/product-design.md +39 -0
- package/plugin/roles/examples/program-manager.md +24 -0
- package/plugin/roles/examples/quality.md +25 -0
- package/plugin/roles/examples/research-director.md +24 -0
- package/plugin/roles/examples/research-ops.md +25 -0
- package/plugin/roles/examples/researcher.md +25 -0
- package/plugin/roles/examples/revenue-lead.md +25 -0
- package/plugin/roles/examples/revops.md +26 -0
- package/plugin/roles/examples/seo.md +27 -0
- package/plugin/roles/examples/specialist.md +24 -0
- package/plugin/scripts/admision.py +165 -0
- package/plugin/scripts/apaga.py +232 -0
- package/plugin/scripts/avatar.py +211 -0
- package/plugin/scripts/broker.py +531 -0
- package/plugin/scripts/cage.py +252 -0
- package/plugin/scripts/capabilities.py +197 -0
- package/plugin/scripts/card.py +312 -0
- package/plugin/scripts/cities.py +520 -0
- package/plugin/scripts/city-env.sh +90 -0
- package/plugin/scripts/city-runtime.sh +87 -0
- package/plugin/scripts/city-session.sh +526 -0
- package/plugin/scripts/city_env.py +67 -0
- package/plugin/scripts/crecimiento.py +140 -0
- package/plugin/scripts/deliberations.py +48 -0
- package/plugin/scripts/doctor.py +172 -0
- package/plugin/scripts/domains.py +201 -0
- package/plugin/scripts/evidencia.py +64 -0
- package/plugin/scripts/find-repos.sh +111 -0
- package/plugin/scripts/gh.py +159 -0
- package/plugin/scripts/hall_protocol.py +147 -0
- package/plugin/scripts/hook_activity.py +139 -0
- package/plugin/scripts/launch.py +94 -0
- package/plugin/scripts/logs.py +98 -0
- package/plugin/scripts/pairing.py +185 -0
- package/plugin/scripts/parcels.py +132 -0
- package/plugin/scripts/read-card.py +51 -0
- package/plugin/scripts/report.py +201 -0
- package/plugin/scripts/reset.py +180 -0
- package/plugin/scripts/roads.py +230 -0
- package/plugin/scripts/roles.py +274 -0
- package/plugin/scripts/runtime_log.py +69 -0
- package/plugin/scripts/runtime_processes.py +132 -0
- package/plugin/scripts/rutas.py +83 -0
- package/plugin/scripts/seat.py +1349 -0
- package/plugin/scripts/tokens.py +194 -0
- package/plugin/scripts/trust-repos.py +59 -0
- package/plugin/scripts/ui.py +280 -0
- package/plugin/scripts/units.py +80 -0
- package/plugin/scripts/workspace.py +364 -0
- package/plugin/skills/city/SKILL.md +191 -0
- package/templates/blank.md +39 -0
- package/templates/finance.md +44 -0
- package/templates/legal.md +45 -0
- package/templates/marketing.md +48 -0
- package/templates/product.md +45 -0
package/bin/test-card.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The card reader and writer.
|
|
3
|
+
|
|
4
|
+
./bin/test-card.py
|
|
5
|
+
|
|
6
|
+
A card is the only thing that says who somebody is, what they answer for and what
|
|
7
|
+
they are aiming at — and this repo had five ways to read one and three to write one.
|
|
8
|
+
That is how the tmux session came to ask for a field called `agente` while all
|
|
9
|
+
twelve demo cards carry `agent`: the read returned nothing every time, a default
|
|
10
|
+
covered for it, and every architect sat on the bus as `<user>/dev`.
|
|
11
|
+
|
|
12
|
+
So the happy half of this pins the format, and the unhappy half is the cards people
|
|
13
|
+
actually end up with: hand-written, half-written, written by the other door.
|
|
14
|
+
"""
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
|
|
21
|
+
AQUI = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
RAIZ = os.path.dirname(AQUI)
|
|
23
|
+
sys.path.insert(0, os.path.join(RAIZ, 'plugin', 'scripts'))
|
|
24
|
+
sys.path.insert(0, AQUI)
|
|
25
|
+
import card # noqa: E402
|
|
26
|
+
|
|
27
|
+
from testlib import comprueba, afirma, resumen # noqa: E402
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ficha(cuerpo):
|
|
31
|
+
d = tempfile.mkdtemp()
|
|
32
|
+
r = os.path.join(d, 'x.md')
|
|
33
|
+
open(r, 'w', encoding='utf-8').write(cuerpo)
|
|
34
|
+
return d, r
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ENTERA = '''---
|
|
38
|
+
user: ana
|
|
39
|
+
name: Ana Ruiz
|
|
40
|
+
role: cpto
|
|
41
|
+
agent: ana/lead
|
|
42
|
+
repos: [api, web]
|
|
43
|
+
goals_defined: true
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# ana
|
|
47
|
+
|
|
48
|
+
## Current goals
|
|
49
|
+
|
|
50
|
+
### O1 — Every parcel has an owner
|
|
51
|
+
- **What**: Every parcel has an owner.
|
|
52
|
+
- **How it is measured**: parcels with an owner in .city.yml.
|
|
53
|
+
- **Measure**: `grep -lc owner: */.city.yml`
|
|
54
|
+
- **Baseline**: 0 of 3
|
|
55
|
+
- **Target**: 3 of 3
|
|
56
|
+
- **By when**: end of Q3
|
|
57
|
+
- **State**: in progress
|
|
58
|
+
'''
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ══ the format as written ══════════════════════════════════════════════════
|
|
62
|
+
def formato():
|
|
63
|
+
print(' a card as written')
|
|
64
|
+
d, r = ficha(ENTERA)
|
|
65
|
+
c = card.lee(r)
|
|
66
|
+
for k, v in (('user', 'ana'), ('name', 'Ana Ruiz'), ('role', 'cpto'),
|
|
67
|
+
('agent', 'ana/lead'), ('goals_defined', True)):
|
|
68
|
+
comprueba(f'· {k}', c[k], v)
|
|
69
|
+
comprueba('· repos come back as a list', c['repos'], ['api', 'web'])
|
|
70
|
+
comprueba(
|
|
71
|
+
'· a legacy card invents no repo specialties: both are explicitly blank',
|
|
72
|
+
c['repo_roles'],
|
|
73
|
+
{'api': 'blank', 'web': 'blank'},
|
|
74
|
+
)
|
|
75
|
+
o = c['objetivo']
|
|
76
|
+
comprueba('· the goal title', o['title'], 'Every parcel has an owner')
|
|
77
|
+
comprueba('· the signal, with its trailing full stop dropped',
|
|
78
|
+
o['signal'], 'parcels with an owner in .city.yml')
|
|
79
|
+
comprueba('· the command, with its backticks dropped',
|
|
80
|
+
o['command'], 'grep -lc owner: */.city.yml')
|
|
81
|
+
comprueba('· baseline / target / by',
|
|
82
|
+
(o['baseline'], o['target'], o['by']), ('0 of 3', '3 of 3', 'end of Q3'))
|
|
83
|
+
shutil.rmtree(d)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ══ cards people actually have ══════════════════════════════════════════════
|
|
87
|
+
def fichas_raras():
|
|
88
|
+
print(' cards that are not textbook')
|
|
89
|
+
|
|
90
|
+
comprueba('· a file that is not there reads as empty, not as a crash',
|
|
91
|
+
card.lee('/tmp/no-card-here-at-all.md'), {})
|
|
92
|
+
comprueba('· a directory, likewise', card.lee(tempfile.mkdtemp()), {})
|
|
93
|
+
|
|
94
|
+
d, r = ficha('# just a heading, no frontmatter\n')
|
|
95
|
+
c = card.lee(r)
|
|
96
|
+
comprueba('· no frontmatter: no fields', (c['user'], c['repos']), ('', []))
|
|
97
|
+
comprueba('· and role still has a sane default', c['role'], 'dev')
|
|
98
|
+
shutil.rmtree(d)
|
|
99
|
+
|
|
100
|
+
d, r = ficha('---\nuser: a\n') # opened and never closed
|
|
101
|
+
comprueba('· frontmatter that is never closed reads as none',
|
|
102
|
+
card.lee(r)['user'], '')
|
|
103
|
+
shutil.rmtree(d)
|
|
104
|
+
|
|
105
|
+
# The bug this module exists for.
|
|
106
|
+
d, r = ficha('---\nuser: a\nagente: a/ops\nrepos: [x]\n---\n')
|
|
107
|
+
comprueba('· a hand-written `agente:` answers to `agent`',
|
|
108
|
+
card.lee(r)['agent'], 'a/ops')
|
|
109
|
+
shutil.rmtree(d)
|
|
110
|
+
d, r = ficha('---\nuser: a\nagent: a/lead\nagente: a/WRONG\n---\n')
|
|
111
|
+
comprueba('· and when both are there, `agent` wins',
|
|
112
|
+
card.lee(r)['agent'], 'a/lead')
|
|
113
|
+
shutil.rmtree(d)
|
|
114
|
+
|
|
115
|
+
for cuerpo, espera, por_que in (
|
|
116
|
+
('repos: []', [], 'an empty list'),
|
|
117
|
+
('repos:', [], 'the field with nothing after it'),
|
|
118
|
+
('repos: [ ]', [], 'a list of one space'),
|
|
119
|
+
('repos: ["api", \'web\']', ['api', 'web'], 'mixed quotes'),
|
|
120
|
+
('repos: [api,web]', ['api', 'web'], 'no spaces'),
|
|
121
|
+
('repos: [api, , web]', ['api', 'web'], 'a hole in the middle'),
|
|
122
|
+
('repos: [My_Repo, a.b-c]', ['My_Repo', 'a.b-c'], 'dots, dashes, capitals')):
|
|
123
|
+
d, r = ficha(f'---\nuser: a\n{cuerpo}\n---\n')
|
|
124
|
+
comprueba(f'· repos, {por_que}', card.lee(r)['repos'], espera)
|
|
125
|
+
shutil.rmtree(d)
|
|
126
|
+
|
|
127
|
+
# goals_defined is a flag, and anything that is not exactly true is false.
|
|
128
|
+
for valor, espera in (('true', True), ('false', False), ('True', False), ('', False)):
|
|
129
|
+
d, r = ficha(f'---\nuser: a\ngoals_defined: {valor}\n---\n')
|
|
130
|
+
comprueba(f'· goals_defined: {valor!r}', card.lee(r)['goals_defined'], espera)
|
|
131
|
+
shutil.rmtree(d)
|
|
132
|
+
|
|
133
|
+
# A card claiming a goal in the frontmatter but not carrying one.
|
|
134
|
+
d, r = ficha('---\nuser: a\ngoals_defined: true\n---\n\n## Current goals\n\n> Pending.\n')
|
|
135
|
+
comprueba('· goals_defined lying is not a goal', card.lee(r)['objetivo'], None)
|
|
136
|
+
shutil.rmtree(d)
|
|
137
|
+
|
|
138
|
+
# A goal with no command shows a stand-in to a reader. Reading it back as a
|
|
139
|
+
# command would hand a round a sentence to run.
|
|
140
|
+
obj = {'title': 't', 'signal': 's', 'command': '', 'baseline': 'b',
|
|
141
|
+
'target': 'g', 'by': 'now'}
|
|
142
|
+
lineas = card.bloque_objetivo(obj)
|
|
143
|
+
afirma('· no command shows the manual stand-in',
|
|
144
|
+
any('manual —' in l for l in lineas), str(lineas[:4]))
|
|
145
|
+
texto = '---\nuser: a\n---\n\n' + '\n'.join(lineas)
|
|
146
|
+
comprueba('· and it is not read back as a command',
|
|
147
|
+
card.objetivo(texto)['command'], '')
|
|
148
|
+
|
|
149
|
+
# A qualitative goal: prose judge, no command — first class, both directions.
|
|
150
|
+
cual = {'title': 'AGENTS.md worth reading', 'signal': 'quality, judged', 'command': '',
|
|
151
|
+
'manual': 'the architect reads them on Fridays', 'baseline': '', 'target': '',
|
|
152
|
+
'by': 'Q4'}
|
|
153
|
+
t2 = '---\nuser: a\n---\n\n' + '\n'.join(card.bloque_objetivo(cual))
|
|
154
|
+
v2 = card.objetivo(t2)
|
|
155
|
+
comprueba('· a prose-judged goal keeps its judge', v2['manual'],
|
|
156
|
+
'the architect reads them on Fridays')
|
|
157
|
+
comprueba('· and still hands a round no command to run', v2['command'], '')
|
|
158
|
+
afirma('· the stock stand-in does not masquerade as somebody\'s prose',
|
|
159
|
+
card.objetivo('---\nu: a\n---\n\n'
|
|
160
|
+
+ '\n'.join(card.bloque_objetivo(
|
|
161
|
+
dict(cual, manual=''))))['manual'] == '')
|
|
162
|
+
|
|
163
|
+
comprueba('· no goal at all emits the pending note, not a goal',
|
|
164
|
+
card.objetivo('---\nuser: a\n---\n\n'
|
|
165
|
+
+ '\n'.join(card.bloque_objetivo(None))), None)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ══ writing, and only what was asked ═══════════════════════════════════════
|
|
169
|
+
def escritura():
|
|
170
|
+
print(' writing')
|
|
171
|
+
|
|
172
|
+
d, r = ficha(ENTERA)
|
|
173
|
+
antes = open(r).read()
|
|
174
|
+
afirma('· changing the repos changes the repos', card.cambia_repos(r, ['z', 'a']))
|
|
175
|
+
despues = open(r).read()
|
|
176
|
+
comprueba('· sorted, in the frontmatter', card.lee(r)['repos'], ['a', 'z'])
|
|
177
|
+
afirma('· and nothing else moved — the goal is untouched',
|
|
178
|
+
card.lee(r)['objetivo']['title'] == 'Every parcel has an owner')
|
|
179
|
+
comprueba('· exactly one line differs',
|
|
180
|
+
sum(1 for x, y in zip(antes.splitlines(), despues.splitlines(),
|
|
181
|
+
strict=False) if x != y), 1)
|
|
182
|
+
shutil.rmtree(d)
|
|
183
|
+
|
|
184
|
+
d, r = ficha(
|
|
185
|
+
'---\nuser: a\nrole: cpto\nrepos: [portfolio, warehouse]\n'
|
|
186
|
+
'role.portfolio: seo\nrole.warehouse: data-engineer\n'
|
|
187
|
+
'runs.portfolio: codex\ngoals_defined: false\n---\n'
|
|
188
|
+
)
|
|
189
|
+
comprueba(
|
|
190
|
+
'· each repo role is read independently from the seat role',
|
|
191
|
+
card.lee(r)['repo_roles'],
|
|
192
|
+
{'portfolio': 'seo', 'warehouse': 'data-engineer'},
|
|
193
|
+
)
|
|
194
|
+
afirma(
|
|
195
|
+
'· repo-role surgery writes one explicit role per selected agent',
|
|
196
|
+
card.cambia_roles_repos(r, ['portfolio', 'docs'], {'portfolio': 'po', 'docs': 'blank'}),
|
|
197
|
+
)
|
|
198
|
+
despues = open(r).read()
|
|
199
|
+
comprueba(
|
|
200
|
+
'· changed repo roles round-trip, including the deliberate blank',
|
|
201
|
+
card.roles_repos(despues, ['portfolio', 'docs']),
|
|
202
|
+
{'portfolio': 'po', 'docs': 'blank'},
|
|
203
|
+
)
|
|
204
|
+
afirma(
|
|
205
|
+
'· stale agent roles leave but an unrelated runtime setting survives',
|
|
206
|
+
'role.warehouse:' not in despues and 'runs.portfolio: codex' in despues,
|
|
207
|
+
despues,
|
|
208
|
+
)
|
|
209
|
+
try:
|
|
210
|
+
card.normaliza_roles_repos(['a/b', 'a-b'], {})
|
|
211
|
+
colision = ''
|
|
212
|
+
except ValueError as e:
|
|
213
|
+
colision = str(e)
|
|
214
|
+
afirma(
|
|
215
|
+
'· two repos may not silently become the same bus actor',
|
|
216
|
+
'collide as agent' in colision,
|
|
217
|
+
colision,
|
|
218
|
+
)
|
|
219
|
+
try:
|
|
220
|
+
card.normaliza_roles_repos(['api'], {'api': 'seo; touch /tmp/no'})
|
|
221
|
+
invalido = ''
|
|
222
|
+
except ValueError as e:
|
|
223
|
+
invalido = str(e)
|
|
224
|
+
afirma('· a role is data, never a shell fragment', 'invalid role' in invalido, invalido)
|
|
225
|
+
|
|
226
|
+
lector = os.path.join(RAIZ, 'plugin', 'scripts', 'read-card.py')
|
|
227
|
+
rol_actor = subprocess.run(
|
|
228
|
+
[sys.executable, lector, '--actor-role', r, 'portfolio'],
|
|
229
|
+
capture_output=True,
|
|
230
|
+
text=True,
|
|
231
|
+
).stdout.strip()
|
|
232
|
+
comprueba('· the runtime reads the same safe role for its actor', rol_actor, 'po')
|
|
233
|
+
card.pon_campo(r, 'role.portfolio', '$(touch-bad)')
|
|
234
|
+
rol_actor = subprocess.run(
|
|
235
|
+
[sys.executable, lector, '--actor-role', r, 'portfolio'],
|
|
236
|
+
capture_output=True,
|
|
237
|
+
text=True,
|
|
238
|
+
).stdout.strip()
|
|
239
|
+
comprueba('· a malformed hand edit degrades to blank at runtime', rol_actor, 'blank')
|
|
240
|
+
shutil.rmtree(d)
|
|
241
|
+
|
|
242
|
+
d, r = ficha('---\nuser: a\n---\n')
|
|
243
|
+
comprueba('· a card with no repos field refuses rather than inventing one',
|
|
244
|
+
card.cambia_repos(r, ['x']), False)
|
|
245
|
+
shutil.rmtree(d)
|
|
246
|
+
|
|
247
|
+
# The suffix, including the roles nobody anticipated.
|
|
248
|
+
sys.path.insert(0, AQUI)
|
|
249
|
+
import roles
|
|
250
|
+
for rol, espera in (('cpto', 'lead'), ('managing-partner', 'partner'),
|
|
251
|
+
('controller', 'ctrl'), ('dev', 'dev'),
|
|
252
|
+
('invented-role', 'invented'), ('x', 'x')):
|
|
253
|
+
comprueba(f'· suffix for {rol}', roles.sufijo(rol), espera)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# ══ surgical changes: what must survive them ═══════════════════════════════
|
|
257
|
+
def cirugia():
|
|
258
|
+
print(' changing a card without erasing it')
|
|
259
|
+
# A realistic card: the body line both writers emit, plus a goal.
|
|
260
|
+
d, r = ficha(ENTERA.replace('# ana\n',
|
|
261
|
+
'# ana\n\nRole: **cpto** — in the city, the **architect**. '
|
|
262
|
+
'The domain is in `roles/cpto.md`.\n'))
|
|
263
|
+
# A round left its mark; changing the goal must not erase it.
|
|
264
|
+
t = open(r).read() + '\n## Round history\n\n### 2026-08-20 — round\n- kira: pinned on purpose\n'
|
|
265
|
+
open(r, 'w').write(t)
|
|
266
|
+
afirma('· cambia_objetivo rewrites the goal',
|
|
267
|
+
card.cambia_objetivo(r, {'title': 'Nuevo', 'signal': 'prosa', 'command': '',
|
|
268
|
+
'manual': 'lo juzga el consejo', 'baseline': '',
|
|
269
|
+
'target': '', 'by': 'Q1'}))
|
|
270
|
+
v = card.lee(r)
|
|
271
|
+
comprueba('· the new goal is there, judge included',
|
|
272
|
+
(v['objetivo']['title'], v['objetivo']['manual']),
|
|
273
|
+
('Nuevo', 'lo juzga el consejo'))
|
|
274
|
+
afirma('· and the round history SURVIVES — the template rewrite wiped it',
|
|
275
|
+
'pinned on purpose' in v['texto'])
|
|
276
|
+
afirma('· goals_defined follows', v['goals_defined'])
|
|
277
|
+
afirma('· clearing works the same way', card.cambia_objetivo(r, None))
|
|
278
|
+
v = card.lee(r)
|
|
279
|
+
comprueba('· cleared reads as pending', v['objetivo'], None)
|
|
280
|
+
afirma('· history still there', 'pinned on purpose' in v['texto'])
|
|
281
|
+
afirma('· the role changes all three places it is written',
|
|
282
|
+
card.cambia_rol(r, 'devops', 'ana/ops', 'Surveyor'))
|
|
283
|
+
v = card.lee(r)
|
|
284
|
+
comprueba('· frontmatter role and agent', (v['role'], v['agent']), ('devops', 'ana/ops'))
|
|
285
|
+
afirma('· and the body line agrees',
|
|
286
|
+
'Role: **devops** — in the city, the **surveyor**' in v['texto'])
|
|
287
|
+
afirma('· including the domain pointer, or it sends readers to the old file',
|
|
288
|
+
'`roles/devops.md`' in v['texto'] and '`roles/cpto.md`' not in v['texto'])
|
|
289
|
+
afirma('· a card with no goals section refuses instead of guessing',
|
|
290
|
+
card.cambia_objetivo(os.path.join(d, 'x2.md'), None) is False
|
|
291
|
+
if open(os.path.join(d, 'x2.md'), 'w').write('---\nuser: b\n---\n') or True
|
|
292
|
+
else False)
|
|
293
|
+
shutil.rmtree(d)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ══ the engine fields ═══════════════════════════════════════════════════════
|
|
297
|
+
def motores():
|
|
298
|
+
print(' the engine each agent starts with')
|
|
299
|
+
d, r = ficha(ENTERA)
|
|
300
|
+
afirma('· a default engine can be set', card.pon_campo(r, 'model', 'opus'))
|
|
301
|
+
afirma('· and one window tuned', card.pon_campo(r, 'model.dbt', 'haiku'))
|
|
302
|
+
t = open(r).read()
|
|
303
|
+
comprueba('· both are readable by the exact keys the session script asks for',
|
|
304
|
+
(card.campo(t, 'model'), card.campo(t, 'model.dbt')), ('opus', 'haiku'))
|
|
305
|
+
afirma('· replacing overwrites instead of duplicating',
|
|
306
|
+
card.pon_campo(r, 'model', 'sonnet')
|
|
307
|
+
and open(r).read().count('model:') == 1)
|
|
308
|
+
afirma('· empty removes the key', card.pon_campo(r, 'model.dbt', '')
|
|
309
|
+
and 'model.dbt' not in open(r).read())
|
|
310
|
+
afirma('· a dotted window key never matches the plain default',
|
|
311
|
+
card.campo(open(r).read(), 'model.dbt') == ''
|
|
312
|
+
and card.campo(open(r).read(), 'model') == 'sonnet')
|
|
313
|
+
afirma('· a hostile key is refused, not written',
|
|
314
|
+
card.pon_campo(r, 'model.$(rm -rf)', 'x') is False)
|
|
315
|
+
afirma('· and the goal survived all of it',
|
|
316
|
+
card.lee(r)['objetivo'] is not None)
|
|
317
|
+
# The shell path the session actually uses.
|
|
318
|
+
salida = subprocess.run(['python3',
|
|
319
|
+
os.path.join(RAIZ, 'plugin', 'scripts', 'read-card.py'), r, 'model'],
|
|
320
|
+
capture_output=True, text=True).stdout.strip()
|
|
321
|
+
comprueba('· read-card.py hands the session the same answer', salida, 'sonnet')
|
|
322
|
+
|
|
323
|
+
# The window slug: what a repo is called as a tmux window and, because of
|
|
324
|
+
# that, as an engine-key suffix. Its shell twin is held by the contracts.
|
|
325
|
+
comprueba('· a worktree slugs to a key-safe window name',
|
|
326
|
+
card.ventana('MiApp@feature/X'), 'miapp-feature-x')
|
|
327
|
+
comprueba('· underscores and case fold the same way',
|
|
328
|
+
card.ventana('Data_Pipeline'), 'data-pipeline')
|
|
329
|
+
afirma('· every slug makes a legal engine key, hostile charsets included',
|
|
330
|
+
all(card.pon_campo(r, f'runs.{card.ventana(n)}', 'codex')
|
|
331
|
+
for n in ('API', 'a/b_c@d', 'x')),
|
|
332
|
+
'pon_campo refused a runs.<ventana> key')
|
|
333
|
+
shutil.rmtree(d)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ══ every real card in the repo, and both writers ══════════════════════════
|
|
337
|
+
def de_verdad():
|
|
338
|
+
print(' the cards that exist')
|
|
339
|
+
|
|
340
|
+
import glob
|
|
341
|
+
reales = sorted(glob.glob(os.path.join(RAIZ, 'demo', '*.md')))
|
|
342
|
+
afirma('· the demo has its twelve', len(reales) == 12, f'{len(reales)} found')
|
|
343
|
+
sin_agente = []
|
|
344
|
+
for f in reales:
|
|
345
|
+
c = card.lee(f)
|
|
346
|
+
if not c['agent']:
|
|
347
|
+
sin_agente.append(os.path.basename(f))
|
|
348
|
+
comprueba(f'· {os.path.basename(f)} has a user', bool(c['user']), True)
|
|
349
|
+
comprueba('· and every one of them has an agent — this is the bug that was '
|
|
350
|
+
'invisible', sin_agente, [])
|
|
351
|
+
|
|
352
|
+
# The shell path the tmux session actually uses.
|
|
353
|
+
for f in reales[:3]:
|
|
354
|
+
esperado = card.lee(f)['agent']
|
|
355
|
+
salida = subprocess.run(
|
|
356
|
+
['python3', os.path.join(RAIZ, 'plugin', 'scripts', 'read-card.py'), f, 'agent'],
|
|
357
|
+
capture_output=True, text=True).stdout.strip()
|
|
358
|
+
comprueba(f'· read-card.py agrees for {os.path.basename(f)}', salida, esperado)
|
|
359
|
+
|
|
360
|
+
# Both writers must emit a goal a round can read, in the same shape.
|
|
361
|
+
import importlib.machinery as mach
|
|
362
|
+
import importlib.util as iu
|
|
363
|
+
s = iu.spec_from_loader('seat', mach.SourceFileLoader(
|
|
364
|
+
'seat', os.path.join(RAIZ, 'plugin', 'scripts', 'seat.py')))
|
|
365
|
+
seat = iu.module_from_spec(s)
|
|
366
|
+
s.loader.exec_module(seat)
|
|
367
|
+
import setup as W
|
|
368
|
+
|
|
369
|
+
meta = {'user': 'ana', 'title': 'One goal', 'signal': 'a signal',
|
|
370
|
+
'command': 'echo 1', 'baseline': '0', 'target': '1', 'by': 'Q3'}
|
|
371
|
+
casa = tempfile.mkdtemp()
|
|
372
|
+
porSeat = os.path.join(casa, 'ana.md')
|
|
373
|
+
seat.escribe_ficha(porSeat, 'ana', 'cpto', ['api'], meta)
|
|
374
|
+
|
|
375
|
+
otro = os.path.join(casa, 'wiz')
|
|
376
|
+
os.makedirs(otro + '/roles', exist_ok=True)
|
|
377
|
+
W.escribe({'destino': otro, 'unidades': [{'id': 'u', 'name': 'U', 'color': 'aabbcc'}],
|
|
378
|
+
'roles': ['cpto'], 'repos': [], 'gente': [{'user': 'ana', 'role': 'cpto'}],
|
|
379
|
+
'org': '', 'rutas': {}, 'kind': 'product', 'grow_cmd': '', 'objetivo': meta})
|
|
380
|
+
porWizard = os.path.join(otro, 'ana.md')
|
|
381
|
+
|
|
382
|
+
a, b = card.lee(porSeat), card.lee(porWizard)
|
|
383
|
+
for k in ('user', 'role', 'agent', 'goals_defined'):
|
|
384
|
+
comprueba(f'· both doors write the same {k}', a[k], b[k])
|
|
385
|
+
for k in ('title', 'signal', 'command', 'baseline', 'target', 'by'):
|
|
386
|
+
comprueba(f'· and the same goal {k}', a['objetivo'][k], b['objetivo'][k])
|
|
387
|
+
shutil.rmtree(casa)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def main():
|
|
391
|
+
print()
|
|
392
|
+
formato()
|
|
393
|
+
fichas_raras()
|
|
394
|
+
escritura()
|
|
395
|
+
cirugia()
|
|
396
|
+
motores()
|
|
397
|
+
de_verdad()
|
|
398
|
+
return resumen('card')
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == '__main__':
|
|
402
|
+
sys.exit(main())
|