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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The one reader and writer of a person's card.
|
|
3
|
+
|
|
4
|
+
A card is markdown with frontmatter. It is the only thing that says who somebody
|
|
5
|
+
is, what they answer for and what they are aiming at, and it is read by the tmux
|
|
6
|
+
session, the seat, the wizard, the repo picker, the seeder and every round.
|
|
7
|
+
|
|
8
|
+
There were five ways to read it and three to write it, in one repo:
|
|
9
|
+
|
|
10
|
+
* `read-card.py`, a CLI, one field at a time, by subprocess
|
|
11
|
+
* a repo picker with its own frontmatter parse, for `repos` only — which nothing
|
|
12
|
+
ever called, and which is gone
|
|
13
|
+
* `bin/seat`, all three of the above plus two loose regexes of its own
|
|
14
|
+
* `bin/setup.py` and `bin/seat` each writing the whole card their own way
|
|
15
|
+
|
|
16
|
+
That is how the seat window ended up reading a field called `agente` while every
|
|
17
|
+
card in the repo carries `agent`, and how a card written by one door came out
|
|
18
|
+
different from the same card written by the other. One file, so there is one answer.
|
|
19
|
+
|
|
20
|
+
Here, and not next to the wizard, for the same reason as `parcels.py`: this
|
|
21
|
+
directory is what gets installed on everybody's machine.
|
|
22
|
+
"""
|
|
23
|
+
import re
|
|
24
|
+
|
|
25
|
+
# The frontmatter fields, in the order they are written. Order is not cosmetic — a
|
|
26
|
+
# card is read by people, and a stable order means a diff shows what changed rather
|
|
27
|
+
# than that something moved.
|
|
28
|
+
CAMPOS = ('user', 'name', 'role', 'agent', 'repos', 'goals_defined')
|
|
29
|
+
ROLE_ID = re.compile(r'^[a-z0-9][a-z0-9-]{0,63}$')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def frontmatter(texto):
|
|
33
|
+
"""The frontmatter block of a card, or '' if it has none."""
|
|
34
|
+
if not texto.startswith('---'):
|
|
35
|
+
return ''
|
|
36
|
+
partes = texto.split('---')
|
|
37
|
+
return partes[1] if len(partes) > 2 else ''
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def lista(valor):
|
|
41
|
+
"""`[a, "b", 'c']` -> ['a', 'b', 'c']. Quotes are optional in these files."""
|
|
42
|
+
return [x.strip().strip('"\'') for x in valor.strip().strip('[]').split(',') if x.strip()]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def campo(texto, clave, defecto=''):
|
|
46
|
+
"""One frontmatter field, by name.
|
|
47
|
+
|
|
48
|
+
`agent` also answers to `agente`. Every card in this repo says `agent`, but a
|
|
49
|
+
hand-written one may not, and the version of this that only accepted `agente`
|
|
50
|
+
silently returned nothing for all twelve demo cards.
|
|
51
|
+
"""
|
|
52
|
+
fm = frontmatter(texto)
|
|
53
|
+
if not fm:
|
|
54
|
+
return defecto
|
|
55
|
+
for nombre in ((clave, 'agente') if clave == 'agent' else (clave,)):
|
|
56
|
+
m = re.search(rf'^{re.escape(nombre)}:[ \t]*(.*)$', fm, re.M)
|
|
57
|
+
if m and m.group(1).strip():
|
|
58
|
+
return m.group(1).strip()
|
|
59
|
+
return defecto
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def lee(ruta):
|
|
63
|
+
"""A whole card as a dict, including its goal. Missing file reads as empty."""
|
|
64
|
+
try:
|
|
65
|
+
with open(ruta, encoding='utf-8') as f:
|
|
66
|
+
texto = f.read()
|
|
67
|
+
except (OSError, IsADirectoryError):
|
|
68
|
+
return {}
|
|
69
|
+
usuario = campo(texto, 'user')
|
|
70
|
+
repos = lista(campo(texto, 'repos'))
|
|
71
|
+
return {
|
|
72
|
+
'user': usuario,
|
|
73
|
+
'name': campo(texto, 'name', usuario),
|
|
74
|
+
'role': campo(texto, 'role', 'dev'),
|
|
75
|
+
'agent': campo(texto, 'agent'),
|
|
76
|
+
'repos': repos,
|
|
77
|
+
'repo_roles': roles_repos(texto, repos),
|
|
78
|
+
'goals_defined': campo(texto, 'goals_defined') == 'true',
|
|
79
|
+
'objetivo': objetivo(texto, usuario),
|
|
80
|
+
'texto': texto,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def rol_seguro(valor, defecto='blank'):
|
|
85
|
+
"""One inert role id suitable for a card key, environment and prompt.
|
|
86
|
+
|
|
87
|
+
Role text is configuration, never a shell fragment. A hand-edited malformed
|
|
88
|
+
value therefore degrades to the explicit blank role rather than crossing the
|
|
89
|
+
runtime boundary verbatim.
|
|
90
|
+
"""
|
|
91
|
+
valor = str(valor or '').strip().lower()
|
|
92
|
+
return valor if ROLE_ID.fullmatch(valor) else defecto
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def roles_repos(texto, repos=None):
|
|
96
|
+
"""``{original repo name: operating role}`` from dynamic ``role.<actor>`` keys.
|
|
97
|
+
|
|
98
|
+
Old cards have no such keys. Returning ``blank`` is intentional: it preserves
|
|
99
|
+
the old behaviour without pretending that Agents City knew why that repo was
|
|
100
|
+
present. The key suffix uses the same canonical actor/window slug as tmux and
|
|
101
|
+
the bus.
|
|
102
|
+
"""
|
|
103
|
+
repos = list(repos if repos is not None else lista(campo(texto, 'repos')))
|
|
104
|
+
return {repo: rol_seguro(campo(texto, f'role.{ventana(repo)}')) for repo in repos}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def normaliza_roles_repos(repos, asignados=None):
|
|
108
|
+
"""Validate one explicit role per selected repo and reject actor collisions."""
|
|
109
|
+
asignados = dict(asignados or {})
|
|
110
|
+
fuera, actores = {}, {}
|
|
111
|
+
limpios = [str(r).strip() for r in repos if str(r).strip()]
|
|
112
|
+
for repo in sorted(dict.fromkeys(limpios)):
|
|
113
|
+
actor = ventana(repo)
|
|
114
|
+
if actor in actores and actores[actor] != repo:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"repo names {actores[actor]!r} and {repo!r} collide as agent {actor!r}"
|
|
117
|
+
)
|
|
118
|
+
actores[actor] = repo
|
|
119
|
+
crudo = asignados.get(repo, asignados.get(actor, 'blank'))
|
|
120
|
+
rol = str(crudo or 'blank').strip().lower()
|
|
121
|
+
if not ROLE_ID.fullmatch(rol):
|
|
122
|
+
raise ValueError(f"invalid role {crudo!r} for repo {repo!r}")
|
|
123
|
+
fuera[repo] = rol
|
|
124
|
+
return fuera
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def linea_rol(rol, oficio=''):
|
|
128
|
+
"""The one human-readable role line shared by new cards and surgery."""
|
|
129
|
+
if rol == 'blank':
|
|
130
|
+
return 'Role: **blank** — no predefined seat responsibility or role knowledge.'
|
|
131
|
+
nombre = (oficio or rol.replace('-', ' ')).lower()
|
|
132
|
+
return (
|
|
133
|
+
f'Role: **{rol}** — in the city, the **{nombre}**. '
|
|
134
|
+
f'Role knowledge, when present: `roles/{rol}.md`.'
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def objetivo(texto, usuario=''):
|
|
139
|
+
"""The first goal off a card, in the shape both writers emit."""
|
|
140
|
+
if '### O1 —' not in texto:
|
|
141
|
+
return None
|
|
142
|
+
m = re.search(r'^### O1 — (.+)$', texto, re.M)
|
|
143
|
+
titulo = m.group(1).strip() if m else ''
|
|
144
|
+
if not titulo:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
def linea(etiqueta, defecto=''):
|
|
148
|
+
m = re.search(rf'^- \*\*{etiqueta}\*\*:\s*`?(.+?)`?\.?$', texto, re.M)
|
|
149
|
+
return m.group(1).strip() if m else defecto
|
|
150
|
+
|
|
151
|
+
medida = linea('Measure')
|
|
152
|
+
# A measure is one of two honest things: a command that returns a number, or a
|
|
153
|
+
# person's judgement written down — "the architect reads the AGENTS.md files on
|
|
154
|
+
# Fridays". The second is not a lesser goal; plenty of quality is prose. It
|
|
155
|
+
# round-trips as `manual`, never as a command a round might try to execute.
|
|
156
|
+
manual = ''
|
|
157
|
+
if medida.startswith('manual —'):
|
|
158
|
+
manual = medida[len('manual —'):].strip()
|
|
159
|
+
if manual == 'who looks at what, and how often':
|
|
160
|
+
manual = ''
|
|
161
|
+
medida = ''
|
|
162
|
+
return {'user': usuario, 'title': titulo,
|
|
163
|
+
'signal': linea('How it is measured'),
|
|
164
|
+
'command': medida, 'manual': manual,
|
|
165
|
+
'baseline': linea('Baseline'), 'target': linea('Target'),
|
|
166
|
+
'by': linea('By when', 'this quarter')}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def bloque_objetivo(obj):
|
|
170
|
+
"""The goal, as the lines that go on a card. One emitter, so a round reads the
|
|
171
|
+
same shape whichever door wrote it."""
|
|
172
|
+
if not obj:
|
|
173
|
+
return ['> **Pending.** A round works without them, but it cannot contrast '
|
|
174
|
+
'the work against anything.', '',
|
|
175
|
+
'> To set one: `./bin/seat --goal`, or `/city:goals`.', '']
|
|
176
|
+
medida = obj.get('command') or ('manual — ' + (
|
|
177
|
+
obj.get('manual') or 'who looks at what, and how often'))
|
|
178
|
+
return [f"### O1 — {obj['title']}",
|
|
179
|
+
f"- **What**: {obj['title']}.",
|
|
180
|
+
f"- **How it is measured**: {obj.get('signal') or 'to be defined'}.",
|
|
181
|
+
f"- **Measure**: `{medida}`",
|
|
182
|
+
f"- **Baseline**: {obj.get('baseline') or 'not measured yet'}",
|
|
183
|
+
f"- **Target**: {obj.get('target') or 'to be defined'}",
|
|
184
|
+
f"- **By when**: {obj.get('by') or 'this quarter'}",
|
|
185
|
+
'- **State**: in progress', '']
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _cambia_seccion(texto, titulo, lineas_nuevas):
|
|
189
|
+
"""Replace one `##` section's body, touching nothing else on the card."""
|
|
190
|
+
patron = re.compile(rf'(^## {re.escape(titulo)}\n)(.*?)(?=^## |\Z)', re.M | re.S)
|
|
191
|
+
if not patron.search(texto):
|
|
192
|
+
return None
|
|
193
|
+
cuerpo = '\n' + '\n'.join(lineas_nuevas).rstrip('\n') + '\n\n'
|
|
194
|
+
return patron.sub(lambda m: m.group(1) + cuerpo, texto, count=1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def cambia_objetivo(ruta, obj):
|
|
198
|
+
"""Rewrite only the goals section — and the `goals_defined` flag with it.
|
|
199
|
+
|
|
200
|
+
Surgical on purpose: the card also carries the round history, and the first
|
|
201
|
+
version of "change your goal" rewrote the whole card from a template, wiping
|
|
202
|
+
every round summary anyone had left there. Changing what you aim at must not
|
|
203
|
+
erase what already happened.
|
|
204
|
+
|
|
205
|
+
`obj` of None clears the goal back to pending. Returns False when the card has
|
|
206
|
+
no goals section to replace.
|
|
207
|
+
"""
|
|
208
|
+
with open(ruta, encoding='utf-8') as f:
|
|
209
|
+
texto = f.read()
|
|
210
|
+
nuevo = _cambia_seccion(texto, 'Current goals', bloque_objetivo(obj))
|
|
211
|
+
if nuevo is None:
|
|
212
|
+
return False
|
|
213
|
+
bandera = 'true' if obj else 'false'
|
|
214
|
+
nuevo = re.sub(r'^goals_defined:.*$', f'goals_defined: {bandera}', nuevo,
|
|
215
|
+
count=1, flags=re.M)
|
|
216
|
+
with open(ruta, 'w', encoding='utf-8') as f:
|
|
217
|
+
f.write(nuevo)
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def cambia_rol(ruta, rol, agente, oficio=''):
|
|
222
|
+
"""Rewrite the role — frontmatter, agent name, and the one body line that says
|
|
223
|
+
it out loud, so the card does not end up claiming two different roles."""
|
|
224
|
+
with open(ruta, encoding='utf-8') as f:
|
|
225
|
+
texto = f.read()
|
|
226
|
+
nuevo, n = re.subn(r'^role:.*$', f'role: {rol}', texto, count=1, flags=re.M)
|
|
227
|
+
if n == 0:
|
|
228
|
+
return False
|
|
229
|
+
nuevo = re.sub(r'^agent:.*$', f'agent: {agente}', nuevo, count=1, flags=re.M)
|
|
230
|
+
if oficio or rol == 'blank':
|
|
231
|
+
nuevo = re.sub(r'^Role:.*$', linea_rol(rol, oficio), nuevo, count=1, flags=re.M)
|
|
232
|
+
with open(ruta, 'w', encoding='utf-8') as f:
|
|
233
|
+
f.write(nuevo)
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def ventana(repo):
|
|
238
|
+
"""The tmux window a repo becomes — which doubles as the engine-key suffix
|
|
239
|
+
on the card (`model.<window>`, `runs.<window>`), so the charset stays
|
|
240
|
+
key-safe and valid as a bus actor. A worktree `MiApp@feature/X` becomes
|
|
241
|
+
`miapp-feature-x`. Shell callers delegate here through read-card.py."""
|
|
242
|
+
limpio = re.sub(r'[^a-z0-9-]+', '-', str(repo).lower()).strip('-')
|
|
243
|
+
return (limpio or 'repo')[:80]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def pon_campo(ruta, clave, valor):
|
|
247
|
+
"""Set one frontmatter field, surgically: replace it if it exists, add it just
|
|
248
|
+
before the closing --- if not, remove it when the value is empty.
|
|
249
|
+
|
|
250
|
+
This is how a card carries the engine its agents start with — `model:` and
|
|
251
|
+
`effort:` as the person's default, `model.<window>:` / `effort.<window>:` to
|
|
252
|
+
tune one window ("the docs repo runs on haiku, the monorepo on opus"). The
|
|
253
|
+
session script resolves specific-then-default, and an absent key means
|
|
254
|
+
"whatever this person's Claude defaults to", which is the right silence.
|
|
255
|
+
"""
|
|
256
|
+
if not re.match(r'^[a-z][a-z0-9._-]*$', clave):
|
|
257
|
+
return False
|
|
258
|
+
with open(ruta, encoding='utf-8') as f:
|
|
259
|
+
texto = f.read()
|
|
260
|
+
if not texto.startswith('---'):
|
|
261
|
+
return False
|
|
262
|
+
patron = re.compile(rf'^{re.escape(clave)}:.*\n', re.M)
|
|
263
|
+
fin = texto.index('---', 3)
|
|
264
|
+
if valor == '' or valor is None:
|
|
265
|
+
nuevo = patron.sub('', texto[:fin], count=1) + texto[fin:]
|
|
266
|
+
elif patron.search(texto[:fin]):
|
|
267
|
+
nuevo = patron.sub(f'{clave}: {valor}\n', texto[:fin], count=1) + texto[fin:]
|
|
268
|
+
else:
|
|
269
|
+
nuevo = texto[:fin] + f'{clave}: {valor}\n' + texto[fin:]
|
|
270
|
+
with open(ruta, 'w', encoding='utf-8') as f:
|
|
271
|
+
f.write(nuevo)
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def cambia_repos(ruta, elegidos):
|
|
276
|
+
"""Rewrite only the `repos` field of an existing card, leaving the rest alone.
|
|
277
|
+
|
|
278
|
+
Used by the repo picker, which has no business rewriting somebody's goals to
|
|
279
|
+
change which folders they answer for.
|
|
280
|
+
"""
|
|
281
|
+
with open(ruta, encoding='utf-8') as f:
|
|
282
|
+
texto = f.read()
|
|
283
|
+
linea = 'repos: [' + ', '.join(sorted(elegidos)) + ']'
|
|
284
|
+
nuevo, n = re.subn(r'^repos:.*$', linea, texto, count=1, flags=re.M)
|
|
285
|
+
if n == 0:
|
|
286
|
+
return False
|
|
287
|
+
with open(ruta, 'w', encoding='utf-8') as f:
|
|
288
|
+
f.write(nuevo)
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def cambia_roles_repos(ruta, elegidos, asignados):
|
|
293
|
+
"""Atomically persist one explicit operating role per selected repo.
|
|
294
|
+
|
|
295
|
+
Stale ``role.<actor>`` keys are removed when a repo leaves the city. Other
|
|
296
|
+
per-window settings (model, effort and runtime) remain untouched.
|
|
297
|
+
"""
|
|
298
|
+
roles = normaliza_roles_repos(elegidos, asignados)
|
|
299
|
+
with open(ruta, encoding='utf-8') as f:
|
|
300
|
+
texto = f.read()
|
|
301
|
+
if not texto.startswith('---'):
|
|
302
|
+
return False
|
|
303
|
+
fin = texto.find('---', 3)
|
|
304
|
+
if fin < 0:
|
|
305
|
+
return False
|
|
306
|
+
cabeza = re.sub(r'^role\.[a-z0-9._-]+:.*\n', '', texto[:fin], flags=re.M)
|
|
307
|
+
lineas = ''.join(f'role.{ventana(repo)}: {rol}\n' for repo, rol in roles.items())
|
|
308
|
+
if lineas:
|
|
309
|
+
cabeza += lineas
|
|
310
|
+
with open(ruta, 'w', encoding='utf-8') as f:
|
|
311
|
+
f.write(cabeza + texto[fin:])
|
|
312
|
+
return True
|