wicked-garden 12.27.0
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/archetypes.json +166 -0
- package/.claude-plugin/components.json +75 -0
- package/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +174 -0
- package/.claude-plugin/specialist.json +48 -0
- package/.claude-plugin/stack-registry.json +96 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +712 -0
- package/CONTRIBUTING.md +99 -0
- package/ETHOS.md +83 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/WICKED_GARDEN_BUS_EVENTS.md +149 -0
- package/agents/agentic/architect.md +449 -0
- package/agents/agentic/performance-analyst.md +702 -0
- package/agents/agentic/safety-reviewer.md +662 -0
- package/agents/crew/implementer.md +163 -0
- package/agents/crew/researcher.md +94 -0
- package/agents/crew/reviewer.md +184 -0
- package/agents/data/data-engineer.md +227 -0
- package/agents/engineering/api-documentarian.md +440 -0
- package/agents/engineering/migration-engineer.md +301 -0
- package/agents/engineering/solution-architect.md +208 -0
- package/agents/jam/brainstorm-facilitator.md +297 -0
- package/agents/jam/council.md +322 -0
- package/agents/jam/quick-facilitator.md +85 -0
- package/agents/persona/persona-agent.md +121 -0
- package/agents/platform/compliance-officer.md +266 -0
- package/agents/platform/privacy-expert.md +496 -0
- package/agents/platform/security-engineer.md +287 -0
- package/agents/product/a11y-expert.md +323 -0
- package/agents/product/requirements-analyst.md +238 -0
- package/agents/product/ui-reviewer.md +180 -0
- package/agents/product/ux-designer.md +199 -0
- package/agents/product/value-strategist.md +269 -0
- package/agents/qe/semantic-reviewer.md +78 -0
- package/commands/agentic/audit.md +24 -0
- package/commands/agentic/design.md +24 -0
- package/commands/agentic/frameworks.md +25 -0
- package/commands/agentic/review.md +27 -0
- package/commands/archetype/build.md +12 -0
- package/commands/archetype/decide.md +12 -0
- package/commands/archetype/explore.md +12 -0
- package/commands/archetype/incident.md +12 -0
- package/commands/archetype/migrate.md +12 -0
- package/commands/archetype/modernize.md +12 -0
- package/commands/archetype/review.md +14 -0
- package/commands/archetype/ship.md +12 -0
- package/commands/archetype/specify.md +12 -0
- package/commands/archetype/triage.md +12 -0
- package/commands/compile.md +37 -0
- package/commands/data/analyze.md +18 -0
- package/commands/data/data.md +21 -0
- package/commands/data/ml.md +18 -0
- package/commands/data/ontology.md +22 -0
- package/commands/data/pipeline.md +17 -0
- package/commands/deliberate.md +181 -0
- package/commands/engineering/add-field.md +77 -0
- package/commands/engineering/apply.md +58 -0
- package/commands/engineering/arch.md +26 -0
- package/commands/engineering/debug.md +27 -0
- package/commands/engineering/docs.md +27 -0
- package/commands/engineering/new-generator.md +257 -0
- package/commands/engineering/patch-plan.md +59 -0
- package/commands/engineering/plan.md +29 -0
- package/commands/engineering/remove.md +48 -0
- package/commands/engineering/rename.md +44 -0
- package/commands/engineering/review.md +32 -0
- package/commands/help.md +128 -0
- package/commands/install.md +118 -0
- package/commands/intent.md +54 -0
- package/commands/jam/brainstorm.md +37 -0
- package/commands/jam/council.md +49 -0
- package/commands/jam/quick.md +21 -0
- package/commands/jam/revisit.md +79 -0
- package/commands/persona/as.md +136 -0
- package/commands/persona/define.md +24 -0
- package/commands/persona/list.md +118 -0
- package/commands/platform/actions.md +20 -0
- package/commands/platform/assert.md +18 -0
- package/commands/platform/audit.md +23 -0
- package/commands/platform/compliance.md +20 -0
- package/commands/platform/gh.md +22 -0
- package/commands/platform/health.md +22 -0
- package/commands/platform/incident.md +23 -0
- package/commands/platform/infra.md +24 -0
- package/commands/platform/peer-health.md +103 -0
- package/commands/platform/plugin-health.md +58 -0
- package/commands/platform/security.md +38 -0
- package/commands/platform/toolchain.md +96 -0
- package/commands/platform/traces.md +20 -0
- package/commands/product/a11y.md +19 -0
- package/commands/product/acceptance.md +19 -0
- package/commands/product/align.md +20 -0
- package/commands/product/analyze.md +23 -0
- package/commands/product/elicit.md +19 -0
- package/commands/product/listen.md +24 -0
- package/commands/product/mockup.md +20 -0
- package/commands/product/screenshot.md +19 -0
- package/commands/product/strategy.md +40 -0
- package/commands/product/synthesize.md +23 -0
- package/commands/product/ux-review.md +42 -0
- package/commands/product/ux.md +20 -0
- package/commands/product/visual-direction.md +34 -0
- package/commands/prove.md +47 -0
- package/commands/report-issue.md +243 -0
- package/commands/reset.md +144 -0
- package/commands/search/blast-radius.md +58 -0
- package/commands/search/hotspots.md +55 -0
- package/commands/search/index.md +38 -0
- package/commands/search/lineage.md +47 -0
- package/commands/search/service-map.md +79 -0
- package/commands/setup.md +363 -0
- package/commands/smaht/briefing.md +200 -0
- package/commands/smaht/events-import.md +95 -0
- package/commands/smaht/state.md +19 -0
- package/commands/where-am-i.md +58 -0
- package/docs/README.md +32 -0
- package/docs/adr/0001-code-relationship-graph-engine.md +116 -0
- package/docs/adr/0002-major-cleanup-keep-cut.md +91 -0
- package/docs/adr/0003-peers-gate-required-layers-optin.md +41 -0
- package/docs/adr/0004-code-graph-moves-to-wicked-brain.md +47 -0
- package/docs/brain-chunk-format.md +135 -0
- package/docs/compiler.md +95 -0
- package/docs/daemon-api.md +70 -0
- package/docs/domains.md +197 -0
- package/docs/getting-started.md +208 -0
- package/docs/plans/2026-06-08-wicked-loom-conduct.md +1337 -0
- package/docs/plans/2026-06-08-wicked-loom-contract.md +638 -0
- package/docs/plans/2026-06-08-wicked-loom-cutover.md +1300 -0
- package/docs/plans/2026-06-08-wicked-loom-expand-compose.md +843 -0
- package/docs/plans/2026-06-08-wicked-loom-flow-cutover.md +403 -0
- package/docs/plans/2026-06-10-codegraph-to-brain-phase1a-graph-core.md +838 -0
- package/docs/plans/2026-06-10-codegraph-to-brain-phase1b-injected-edges.md +44 -0
- package/docs/required-peers.md +60 -0
- package/docs/specs/2026-06-08-wicked-loom-extraction.md +225 -0
- package/docs/specs/2026-06-10-codegraph-to-brain-migration.md +168 -0
- package/docs/v11/archetypes.md +231 -0
- package/hooks/hooks.json +275 -0
- package/hooks/scripts/bootstrap.py +1797 -0
- package/hooks/scripts/invoke.py +62 -0
- package/hooks/scripts/notification.py +166 -0
- package/hooks/scripts/permission_request.py +223 -0
- package/hooks/scripts/post_tool.py +1374 -0
- package/hooks/scripts/pre_compact.py +272 -0
- package/hooks/scripts/pre_tool.py +1278 -0
- package/hooks/scripts/prompt_submit.py +1109 -0
- package/hooks/scripts/session_end.py +95 -0
- package/hooks/scripts/stop.py +624 -0
- package/hooks/scripts/subagent_lifecycle.py +464 -0
- package/hooks/scripts/subscribers/on_gate_decided.py +84 -0
- package/hooks/scripts/task_completed.py +322 -0
- package/hooks/scripts/worktree_guard.py +148 -0
- package/hooks/subscriptions/on_gate_decided.json +9 -0
- package/install.mjs +128 -0
- package/package.json +41 -0
- package/pyproject.toml +59 -0
- package/schemas/README.md +32 -0
- package/schemas/assert_contracts.json +37 -0
- package/schemas/health_probe.json +46 -0
- package/schemas/plugin_status.json +37 -0
- package/scripts/_adapters/__init__.py +166 -0
- package/scripts/_adapters/jira_adapter.py +18 -0
- package/scripts/_adapters/linear_adapter.py +18 -0
- package/scripts/_adapters/miro_adapter.py +18 -0
- package/scripts/_adapters/notion_adapter.py +18 -0
- package/scripts/_agents.py +357 -0
- package/scripts/_brain_ingest/__init__.py +2 -0
- package/scripts/_brain_ingest/session_fact_extractor.py +341 -0
- package/scripts/_brain_port.py +163 -0
- package/scripts/_bus.py +1081 -0
- package/scripts/_bus_catalog_gen.py +93 -0
- package/scripts/_bus_consumers.json +46 -0
- package/scripts/_bus_emit.py +43 -0
- package/scripts/_capability_registry.py +334 -0
- package/scripts/_capability_resolver.py +322 -0
- package/scripts/_domain_store.py +559 -0
- package/scripts/_event_log_reader.py +154 -0
- package/scripts/_event_schema.py +305 -0
- package/scripts/_event_store.py +397 -0
- package/scripts/_heavy_cadence.py +324 -0
- package/scripts/_integration_resolver.py +321 -0
- package/scripts/_logger.py +151 -0
- package/scripts/_loom.py +482 -0
- package/scripts/_paths.py +133 -0
- package/scripts/_python.sh +22 -0
- package/scripts/_run.py +137 -0
- package/scripts/_session.py +502 -0
- package/scripts/_validate_registry.py +1042 -0
- package/scripts/_wicked_testing_probe.py +277 -0
- package/scripts/agentic/analyze_agents.py +391 -0
- package/scripts/agentic/detect_framework.py +420 -0
- package/scripts/agentic/issue_taxonomy.py +357 -0
- package/scripts/agentic/pattern_scorer.py +434 -0
- package/scripts/classify/persist.py +169 -0
- package/scripts/compiler/compile.py +552 -0
- package/scripts/compiler/phase0/VERDICT.md +60 -0
- package/scripts/compiler/phase0/detect.py +292 -0
- package/scripts/compiler/phase0/emit.py +228 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.bindings.json +30 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/.wg/evidence/honest-tests.txt +2 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/Enterprise-AISDLC.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/command_iq.bindings.json +45 -0
- package/scripts/compiler/phase0/evidence/command_iq.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/.projects/evidence-domain/phase-1-clarify/evidence/honest-tests.txt +3 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/command_iq.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/memos.bindings.json +28 -0
- package/scripts/compiler/phase0/evidence/memos.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/memos.stage/.wg/evidence/honest-tests.txt +2 -0
- package/scripts/compiler/phase0/evidence/memos.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/memos.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/verdict.json +104 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.bindings.json +29 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.lint-output.txt +17 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/failing-tests.txt +4 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/coverage/honest-tests.txt +3 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-fail.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/fab-missing.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/honest.build.md +8 -0
- package/scripts/compiler/phase0/evidence/wicked-bus.stage/wg_check_claims.py +174 -0
- package/scripts/compiler/phase0/evidence/wire-vault-transcript.txt +60 -0
- package/scripts/compiler/phase0/run_probe.py +234 -0
- package/scripts/compiler/phase0/wire_vault.py +310 -0
- package/scripts/crew/__init__.py +0 -0
- package/scripts/crew/_task_audit_writer.py +243 -0
- package/scripts/crew/_task_reader.py +389 -0
- package/scripts/crew/archetypes_v11.py +348 -0
- package/scripts/crew/detectors/__init__.py +12 -0
- package/scripts/crew/flow_compiler.py +121 -0
- package/scripts/crew/phase_manager.py +703 -0
- package/scripts/crew/scope_delta.py +264 -0
- package/scripts/crew/specialist_resolver.py +218 -0
- package/scripts/crew/stack_registry.py +189 -0
- package/scripts/data/data_profiler.py +271 -0
- package/scripts/data/ontology_recommender.py +231 -0
- package/scripts/data/schema_validator.py +267 -0
- package/scripts/delivery/drift.py +739 -0
- package/scripts/delivery/telemetry.py +614 -0
- package/scripts/engineering/patch/codegraph_db.py +125 -0
- package/scripts/engineering/patch/generators/__init__.py +56 -0
- package/scripts/engineering/patch/generators/base.py +373 -0
- package/scripts/engineering/patch/generators/csharp_generator.py +494 -0
- package/scripts/engineering/patch/generators/go_generator.py +430 -0
- package/scripts/engineering/patch/generators/java_generator.py +928 -0
- package/scripts/engineering/patch/generators/jsp_generator.py +454 -0
- package/scripts/engineering/patch/generators/kotlin_generator.py +561 -0
- package/scripts/engineering/patch/generators/perl_generator.py +510 -0
- package/scripts/engineering/patch/generators/php_generator.py +644 -0
- package/scripts/engineering/patch/generators/propagation_engine.py +929 -0
- package/scripts/engineering/patch/generators/python_generator.py +487 -0
- package/scripts/engineering/patch/generators/ruby_generator.py +463 -0
- package/scripts/engineering/patch/generators/rust_generator.py +400 -0
- package/scripts/engineering/patch/generators/sql_generator.py +594 -0
- package/scripts/engineering/patch/generators/typescript_generator.py +439 -0
- package/scripts/engineering/patch/patch.py +800 -0
- package/scripts/engineering/patch/safety.py +429 -0
- package/scripts/jam/__init__.py +0 -0
- package/scripts/jam/_bus_consumers.py +271 -0
- package/scripts/jam/agentic_cli_registry.py +703 -0
- package/scripts/jam/consensus.py +737 -0
- package/scripts/jam/detect_clis.py +409 -0
- package/scripts/jam/jam.py +283 -0
- package/scripts/jam/save_transcript.py +171 -0
- package/scripts/loom/__init__.py +22 -0
- package/scripts/loom/cli.py +170 -0
- package/scripts/loom/compose.py +180 -0
- package/scripts/loom/gate.py +120 -0
- package/scripts/loom/manifest.py +140 -0
- package/scripts/loom/resolve.py +59 -0
- package/scripts/persona/__init__.py +0 -0
- package/scripts/persona/registry.py +688 -0
- package/scripts/platform/guard_pipeline.py +1214 -0
- package/scripts/platform/guard_profiles.py +216 -0
- package/scripts/platform/observability/assert_contracts.py +434 -0
- package/scripts/platform/observability/health_probe.py +915 -0
- package/scripts/platform/observability/ops_log_viewer.py +209 -0
- package/scripts/platform/prereq_doctor.py +567 -0
- package/scripts/qe/conditions_manifest.py +260 -0
- package/scripts/qe/content_sanitizer.py +160 -0
- package/scripts/qe/coverage_tracker.py +535 -0
- package/scripts/qe/discover_scenarios.py +243 -0
- package/scripts/qe/evidence_tracker.py +232 -0
- package/scripts/qe/prove.py +196 -0
- package/scripts/qe/registry_lookup.py +80 -0
- package/scripts/qe/registry_store.py +176 -0
- package/scripts/qe/semantic_review.py +1005 -0
- package/scripts/qe/vault_gate.py +382 -0
- package/scripts/qe/verdict_audit.py +175 -0
- package/scripts/qe/verdict_schema.py +335 -0
- package/scripts/reset.py +220 -0
- package/scripts/resolve_path.py +22 -0
- package/scripts/sentinel/invariants.py +402 -0
- package/scripts/sentinel/pre_push.py +73 -0
- package/scripts/setup/__init__.py +1 -0
- package/scripts/setup/detect_state.py +114 -0
- package/scripts/setup/migrate_legacy.py +67 -0
- package/scripts/setup/migrate_v6_projects.py +232 -0
- package/scripts/setup/onboarding.py +114 -0
- package/scripts/smaht/_bus_consumers.py +98 -0
- package/scripts/smaht/adapters/__init__.py +83 -0
- package/scripts/smaht/adapters/brain_adapter.py +254 -0
- package/scripts/smaht/adapters/context7_adapter.py +438 -0
- package/scripts/smaht/adapters/delegation_adapter.py +104 -0
- package/scripts/smaht/adapters/domain_adapter.py +206 -0
- package/scripts/smaht/adapters/events_adapter.py +231 -0
- package/scripts/smaht/adapters/tools_adapter.py +49 -0
- package/scripts/smaht/cheatsheet_store.py +236 -0
- package/scripts/smaht/context_package.py +386 -0
- package/scripts/smaht/knowledge_graph.py +395 -0
- package/scripts/smaht/propose_skills.py +856 -0
- package/scripts/smaht/test_cheatsheet_store.py +428 -0
- package/scripts/statusline.py +151 -0
- package/scripts/where_am_i.py +236 -0
- package/skills/agentic/agentic-patterns/SKILL.md +91 -0
- package/skills/agentic/agentic-patterns/refs/anti-patterns-design.md +283 -0
- package/skills/agentic/agentic-patterns/refs/anti-patterns-operational.md +216 -0
- package/skills/agentic/agentic-patterns/refs/pattern-catalog.md +329 -0
- package/skills/agentic/context-engineering/SKILL.md +142 -0
- package/skills/agentic/context-engineering/refs/caching-and-optimization.md +251 -0
- package/skills/agentic/context-engineering/refs/compression-techniques.md +171 -0
- package/skills/agentic/context-engineering/refs/cost-calculation-budget.md +250 -0
- package/skills/agentic/context-engineering/refs/cost-optimization-reporting.md +300 -0
- package/skills/agentic/context-engineering/refs/selective-loading.md +179 -0
- package/skills/agentic/frameworks/SKILL.md +191 -0
- package/skills/agentic/frameworks/refs/framework-profiles-1.md +209 -0
- package/skills/agentic/frameworks/refs/framework-profiles-2.md +214 -0
- package/skills/agentic/frameworks/refs/migration-patterns-paths.md +267 -0
- package/skills/agentic/frameworks/refs/migration-patterns-testing.md +208 -0
- package/skills/agentic/refs/audit.md +134 -0
- package/skills/agentic/refs/design.md +137 -0
- package/skills/agentic/refs/frameworks.md +102 -0
- package/skills/agentic/refs/review.md +160 -0
- package/skills/agentic/review-methodology/SKILL.md +186 -0
- package/skills/agentic/review-methodology/refs/deliverable-templates.md +246 -0
- package/skills/agentic/review-methodology/refs/issue-taxonomy-quality-testing.md +277 -0
- package/skills/agentic/review-methodology/refs/issue-taxonomy-reliability-safety.md +253 -0
- package/skills/agentic/trust-and-safety/SKILL.md +177 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-actions.md +246 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-input-output.md +238 -0
- package/skills/agentic/trust-and-safety/refs/guardrails-resources.md +209 -0
- package/skills/agentic/trust-and-safety/refs/safety-checklist-advanced.md +209 -0
- package/skills/agentic/trust-and-safety/refs/safety-checklist-core.md +215 -0
- package/skills/archetype/SKILL.md +104 -0
- package/skills/archetype/refs/build.md +142 -0
- package/skills/archetype/refs/decide.md +117 -0
- package/skills/archetype/refs/explore.md +66 -0
- package/skills/archetype/refs/incident.md +149 -0
- package/skills/archetype/refs/migrate.md +170 -0
- package/skills/archetype/refs/modernize.md +187 -0
- package/skills/archetype/refs/review.md +145 -0
- package/skills/archetype/refs/ship.md +117 -0
- package/skills/archetype/refs/specify.md +108 -0
- package/skills/archetype/refs/triage.md +62 -0
- package/skills/classify/SKILL.md +157 -0
- package/skills/data/analysis/SKILL.md +125 -0
- package/skills/data/analysis/refs/templates.md +170 -0
- package/skills/data/data/SKILL.md +118 -0
- package/skills/data/data/refs/examples.md +235 -0
- package/skills/data/ml/SKILL.md +125 -0
- package/skills/data/ml/refs/templates.md +172 -0
- package/skills/data/pipeline/SKILL.md +136 -0
- package/skills/data/pipeline/refs/templates.md +175 -0
- package/skills/data/refs/analyze.md +123 -0
- package/skills/data/refs/data.md +95 -0
- package/skills/data/refs/ml.md +118 -0
- package/skills/data/refs/pipeline.md +92 -0
- package/skills/deliberate/SKILL.md +125 -0
- package/skills/deliberate/refs/architecture-lens.md +37 -0
- package/skills/deliberate/refs/assumptions-lens.md +43 -0
- package/skills/deliberate/refs/code-lens.md +38 -0
- package/skills/deliberate/refs/content-lens.md +37 -0
- package/skills/deliberate/refs/data-lens.md +43 -0
- package/skills/deliberate/refs/design-lens.md +37 -0
- package/skills/deliberate/refs/opportunity-patterns.md +107 -0
- package/skills/deliberate/refs/ops-lens.md +43 -0
- package/skills/deliberate/refs/product-lens.md +46 -0
- package/skills/deliberate/refs/rethink-framework.md +77 -0
- package/skills/deliberate/refs/security-lens.md +47 -0
- package/skills/deliberate/refs/testing-lens.md +45 -0
- package/skills/deliberate/refs/timing-lens.md +42 -0
- package/skills/engineering/architecture/SKILL.md +177 -0
- package/skills/engineering/architecture/refs/adr-template.md +268 -0
- package/skills/engineering/architecture/refs/architecture-template-deployment.md +176 -0
- package/skills/engineering/architecture/refs/architecture-template-design.md +300 -0
- package/skills/engineering/architecture/refs/examples-ecommerce-analytics.md +299 -0
- package/skills/engineering/architecture/refs/examples-saas-trading.md +291 -0
- package/skills/engineering/architecture/refs/patterns-advanced.md +238 -0
- package/skills/engineering/architecture/refs/patterns-structural.md +303 -0
- package/skills/engineering/backend/SKILL.md +102 -0
- package/skills/engineering/backend/refs/checklists.md +146 -0
- package/skills/engineering/backend/refs/patterns.md +181 -0
- package/skills/engineering/debugging/SKILL.md +99 -0
- package/skills/engineering/debugging/refs/patterns.md +248 -0
- package/skills/engineering/debugging/refs/process.md +203 -0
- package/skills/engineering/docs/SKILL.md +97 -0
- package/skills/engineering/engineering/SKILL.md +88 -0
- package/skills/engineering/engineering/refs/checklists.md +91 -0
- package/skills/engineering/engineering/refs/templates.md +71 -0
- package/skills/engineering/frontend/SKILL.md +92 -0
- package/skills/engineering/frontend/refs/checklists.md +96 -0
- package/skills/engineering/frontend/refs/patterns.md +167 -0
- package/skills/engineering/integration/SKILL.md +183 -0
- package/skills/engineering/integration/refs/error-handling-best-practices.md +139 -0
- package/skills/engineering/integration/refs/error-handling-error-classes-typescript.md +212 -0
- package/skills/engineering/integration/refs/error-handling-error-response-format.md +301 -0
- package/skills/engineering/integration/refs/error-handling-retry-strategies.md +190 -0
- package/skills/engineering/integration/refs/event-schemas-best-practices.md +225 -0
- package/skills/engineering/integration/refs/event-schemas-event-naming-conventions.md +303 -0
- package/skills/engineering/integration/refs/event-schemas-event-structure.md +288 -0
- package/skills/engineering/integration/refs/graphql-best-practices.md +138 -0
- package/skills/engineering/integration/refs/graphql-error-handling.md +198 -0
- package/skills/engineering/integration/refs/graphql-graphql-basics.md +197 -0
- package/skills/engineering/integration/refs/graphql-query-examples.md +226 -0
- package/skills/engineering/integration/refs/graphql-schema-design-patterns.md +280 -0
- package/skills/engineering/integration/refs/grpc-grpc-overview.md +246 -0
- package/skills/engineering/integration/refs/grpc-implementation-examples.md +269 -0
- package/skills/engineering/integration/refs/grpc-metadata-headers.md +304 -0
- package/skills/engineering/integration/refs/grpc-testing.md +49 -0
- package/skills/engineering/integration/refs/openapi-template.yaml +682 -0
- package/skills/engineering/integration/refs/resilience-best-practices.md +109 -0
- package/skills/engineering/integration/refs/resilience-circuit-breaker-pattern.md +302 -0
- package/skills/engineering/integration/refs/resilience-rate-limiting.md +245 -0
- package/skills/engineering/integration/refs/resilience-timeout-pattern.md +212 -0
- package/skills/engineering/integration/refs/rest-api-fundamentals.md +299 -0
- package/skills/engineering/integration/refs/rest-api-responses-security.md +302 -0
- package/skills/engineering/integration/refs/versioning-breaking-changes.md +237 -0
- package/skills/engineering/integration/refs/versioning-migration-strategies.md +257 -0
- package/skills/engineering/integration/refs/versioning-versioning-strategies.md +296 -0
- package/skills/engineering/large-scale-migration/SKILL.md +65 -0
- package/skills/engineering/patch/SKILL.md +184 -0
- package/skills/engineering/refs/arch.md +126 -0
- package/skills/engineering/refs/debug.md +142 -0
- package/skills/engineering/refs/docs.md +190 -0
- package/skills/engineering/refs/plan.md +109 -0
- package/skills/engineering/refs/review.md +138 -0
- package/skills/engineering/system-design/SKILL.md +154 -0
- package/skills/engineering/system-design/refs/anti-patterns-big-ball-of-mud.md +254 -0
- package/skills/engineering/system-design/refs/anti-patterns-circular-dependencies.md +241 -0
- package/skills/engineering/system-design/refs/anti-patterns-feature-envy.md +168 -0
- package/skills/engineering/system-design/refs/anti-patterns-god-object.md +247 -0
- package/skills/engineering/system-design/refs/component-template-lifecycle.md +210 -0
- package/skills/engineering/system-design/refs/component-template-maintenance.md +219 -0
- package/skills/engineering/system-design/refs/component-template-structure.md +276 -0
- package/skills/engineering/system-design/refs/dependencies-best-practices.md +260 -0
- package/skills/engineering/system-design/refs/dependencies-dependency-principles.md +295 -0
- package/skills/engineering/system-design/refs/dependencies-dependency-tracking.md +261 -0
- package/skills/engineering/system-design/refs/interface-template-lifecycle.md +211 -0
- package/skills/engineering/system-design/refs/interface-template-maintenance.md +212 -0
- package/skills/engineering/system-design/refs/interface-template-structure.md +269 -0
- package/skills/engineering/system-design/refs/patterns-dependency-injection.md +151 -0
- package/skills/engineering/system-design/refs/patterns-facade-pattern.md +257 -0
- package/skills/engineering/system-design/refs/patterns-layered-pattern.md +204 -0
- package/skills/engineering/system-design/refs/patterns-plugin-architecture.md +229 -0
- package/skills/engineering/unit-test-quality/SKILL.md +194 -0
- package/skills/ground/SKILL.md +130 -0
- package/skills/integration-discovery/SKILL.md +168 -0
- package/skills/integration-discovery/refs/capabilities.md +282 -0
- package/skills/integration-discovery/refs/cli-detection.md +295 -0
- package/skills/integration-discovery/refs/discovery-sources.md +233 -0
- package/skills/integration-discovery/refs/task-patterns.md +206 -0
- package/skills/jam/SKILL.md +45 -0
- package/skills/jam/refs/facilitation-patterns.md +125 -0
- package/skills/jam/refs/quick.md +64 -0
- package/skills/jam/refs/synthesis-patterns.md +114 -0
- package/skills/multi-model/SKILL.md +168 -0
- package/skills/multi-model/refs/aichat.md +119 -0
- package/skills/multi-model/refs/aider.md +100 -0
- package/skills/multi-model/refs/auditability.md +238 -0
- package/skills/multi-model/refs/codex.md +169 -0
- package/skills/multi-model/refs/context.md +306 -0
- package/skills/multi-model/refs/copilot.md +161 -0
- package/skills/multi-model/refs/examples.md +224 -0
- package/skills/multi-model/refs/gemini.md +185 -0
- package/skills/multi-model/refs/goose.md +112 -0
- package/skills/multi-model/refs/llm.md +124 -0
- package/skills/multi-model/refs/opencode.md +196 -0
- package/skills/multi-model/refs/orchestration.md +188 -0
- package/skills/multi-model/refs/pi.md +208 -0
- package/skills/persona/SKILL.md +144 -0
- package/skills/persona/refs/define.md +133 -0
- package/skills/platform/actions/refs/actions.md +114 -0
- package/skills/platform/audit/SKILL.md +174 -0
- package/skills/platform/audit/refs/audit.md +145 -0
- package/skills/platform/audit/refs/checklists-evidence-operations.md +144 -0
- package/skills/platform/audit/refs/checklists-gdpr-pci-evidence.md +213 -0
- package/skills/platform/audit/refs/checklists-soc2-hipaa.md +120 -0
- package/skills/platform/audit/refs/frameworks-gdpr-pci.md +225 -0
- package/skills/platform/audit/refs/frameworks-soc2-hipaa.md +228 -0
- package/skills/platform/compliance/SKILL.md +169 -0
- package/skills/platform/compliance/refs/checklists.md +303 -0
- package/skills/platform/compliance/refs/compliance.md +117 -0
- package/skills/platform/compliance/refs/frameworks.md +282 -0
- package/skills/platform/errors/SKILL.md +183 -0
- package/skills/platform/errors/refs/patterns.md +271 -0
- package/skills/platform/errors/refs/severity.md +264 -0
- package/skills/platform/gate-benchmark-rebaseline/SKILL.md +108 -0
- package/skills/platform/gh/refs/gh.md +105 -0
- package/skills/platform/gh-cli/SKILL.md +120 -0
- package/skills/platform/gh-cli/refs/automation.md +63 -0
- package/skills/platform/gh-cli/refs/gh-ops.md +103 -0
- package/skills/platform/gh-cli/refs/patterns.md +132 -0
- package/skills/platform/gh-cli/scripts/gh_ops.py +454 -0
- package/skills/platform/github-actions/SKILL.md +159 -0
- package/skills/platform/github-actions/refs/security.md +150 -0
- package/skills/platform/github-actions/refs/templates.md +230 -0
- package/skills/platform/github-actions/refs/troubleshooting.md +174 -0
- package/skills/platform/github-actions/scripts/workflow_gen.py +215 -0
- package/skills/platform/gitlab-ci/SKILL.md +140 -0
- package/skills/platform/gitlab-ci/refs/templates.md +223 -0
- package/skills/platform/gitlab-ci/refs/troubleshooting.md +213 -0
- package/skills/platform/glab-cli/SKILL.md +133 -0
- package/skills/platform/glab-cli/refs/ci-debugging.md +95 -0
- package/skills/platform/glab-cli/refs/glab-ops.md +78 -0
- package/skills/platform/glab-cli/refs/patterns.md +131 -0
- package/skills/platform/glab-cli/scripts/glab_ops.py +313 -0
- package/skills/platform/health/SKILL.md +177 -0
- package/skills/platform/health/refs/health.md +72 -0
- package/skills/platform/health/refs/sources.md +302 -0
- package/skills/platform/incident/refs/incident.md +136 -0
- package/skills/platform/infra/refs/infra.md +104 -0
- package/skills/platform/observability/SKILL.md +128 -0
- package/skills/platform/observability/refs/toolchain-discovery.md +243 -0
- package/skills/platform/prereq-doctor/SKILL.md +57 -0
- package/skills/platform/prereq-doctor/refs/tool-registry.md +58 -0
- package/skills/platform/traces/refs/traces.md +101 -0
- package/skills/product/acceptance-criteria/SKILL.md +193 -0
- package/skills/product/acceptance-criteria/refs/ac-anti-patterns.md +291 -0
- package/skills/product/acceptance-criteria/refs/ac-examples.md +277 -0
- package/skills/product/accessibility/SKILL.md +197 -0
- package/skills/product/accessibility/refs/aria-patterns-basics.md +250 -0
- package/skills/product/accessibility/refs/aria-patterns-dynamic.md +187 -0
- package/skills/product/accessibility/refs/aria-patterns-interactive.md +246 -0
- package/skills/product/accessibility/refs/audit-report-findings.md +248 -0
- package/skills/product/accessibility/refs/audit-report-results.md +276 -0
- package/skills/product/accessibility/refs/common-violations-html-css.md +270 -0
- package/skills/product/accessibility/refs/common-violations-interactive.md +303 -0
- package/skills/product/accessibility/refs/keyboard-testing-basics.md +222 -0
- package/skills/product/accessibility/refs/keyboard-testing-patterns.md +173 -0
- package/skills/product/accessibility/refs/screen-reader-testing-advanced.md +294 -0
- package/skills/product/accessibility/refs/screen-reader-testing-commands.md +255 -0
- package/skills/product/accessibility/refs/wcag-checklist.md +262 -0
- package/skills/product/analyze/SKILL.md +164 -0
- package/skills/product/analyze/refs/algorithms.md +238 -0
- package/skills/product/analyze/refs/sentiment-patterns.md +180 -0
- package/skills/product/imagery/SKILL.md +97 -0
- package/skills/product/imagery/alter/SKILL.md +105 -0
- package/skills/product/imagery/alter/refs/editing-reference.md +146 -0
- package/skills/product/imagery/create/SKILL.md +109 -0
- package/skills/product/imagery/create/refs/prompt-engineering.md +112 -0
- package/skills/product/imagery/create/refs/provider-reference.md +219 -0
- package/skills/product/imagery/review/SKILL.md +101 -0
- package/skills/product/imagery/review/refs/analysis-lenses.md +136 -0
- package/skills/product/imagery/review/refs/quality-gates.md +153 -0
- package/skills/product/imagery/scripts/analyze.sh +23 -0
- package/skills/product/imagery/scripts/edit.sh +14 -0
- package/skills/product/imagery/scripts/generate.sh +13 -0
- package/skills/product/imagery/scripts/provider.py +787 -0
- package/skills/product/listen/SKILL.md +175 -0
- package/skills/product/listen/refs/channels.md +220 -0
- package/skills/product/mockup/SKILL.md +106 -0
- package/skills/product/mockup/refs/templates.md +95 -0
- package/skills/product/refs/a11y.md +109 -0
- package/skills/product/refs/acceptance.md +79 -0
- package/skills/product/refs/align.md +67 -0
- package/skills/product/refs/analyze.md +77 -0
- package/skills/product/refs/elicit.md +67 -0
- package/skills/product/refs/listen.md +58 -0
- package/skills/product/refs/mockup.md +77 -0
- package/skills/product/refs/screenshot.md +59 -0
- package/skills/product/refs/strategy.md +77 -0
- package/skills/product/refs/synthesize.md +54 -0
- package/skills/product/refs/ux-review.md +68 -0
- package/skills/product/refs/ux.md +101 -0
- package/skills/product/requirements-analysis/SKILL.md +136 -0
- package/skills/product/requirements-analysis/refs/requirements-example-auth.md +264 -0
- package/skills/product/requirements-analysis/refs/requirements-example-export-integration.md +88 -0
- package/skills/product/requirements-analysis/refs/requirements-output-format-template.md +247 -0
- package/skills/product/requirements-analysis/refs/user-story-guide.md +296 -0
- package/skills/product/requirements-graph/SKILL.md +155 -0
- package/skills/product/requirements-graph/refs/examples.md +223 -0
- package/skills/product/requirements-graph/refs/schema.md +162 -0
- package/skills/product/requirements-migrate/SKILL.md +90 -0
- package/skills/product/requirements-navigate/SKILL.md +141 -0
- package/skills/product/requirements-navigate/refs/meta-templates.md +141 -0
- package/skills/product/screenshot/SKILL.md +147 -0
- package/skills/product/strategy/SKILL.md +198 -0
- package/skills/product/strategy/refs/frameworks.md +295 -0
- package/skills/product/strategy/refs/templates-competitive-market.md +299 -0
- package/skills/product/strategy/refs/templates-roi-value.md +207 -0
- package/skills/product/synthesize/SKILL.md +116 -0
- package/skills/product/synthesize/refs/journey-mapping.md +212 -0
- package/skills/product/synthesize/refs/prioritization.md +193 -0
- package/skills/product/ux-review/SKILL.md +164 -0
- package/skills/product/visual-direction/SKILL.md +137 -0
- package/skills/product/visual-review/SKILL.md +131 -0
- package/skills/runtime-exec/SKILL.md +138 -0
- package/skills/search/codebase-narrator/SKILL.md +131 -0
- package/skills/search/codebase-narrator/refs/output-template.md +100 -0
- package/skills/smaht/SKILL.md +72 -0
- package/skills/smaht/discovery/SKILL.md +65 -0
- package/skills/smaht/intent/SKILL.md +89 -0
- package/skills/smaht/propose-skills/SKILL.md +142 -0
- package/skills/smaht/refs/state.md +76 -0
- package/skills/swarm/SKILL.md +132 -0
- package/skills/swarm/refs/fan-out.md +104 -0
- package/skills/swarm/refs/independent-verification.md +86 -0
- package/skills/swarm/refs/receipts-and-evidence.md +69 -0
- package/skills/swarm/refs/recon-synthesis.md +87 -0
- package/skills/swarm/refs/ship-discipline.md +90 -0
- package/skills/wickedizer/SKILL.md +147 -0
- package/skills/wickedizer/refs/artifact-types.md +238 -0
- package/skills/wickedizer/refs/complexity.md +278 -0
- package/skills/wickedizer/refs/de-ai-pass.md +140 -0
- package/skills/wickedizer/refs/failure-modes.md +249 -0
- package/skills/wickedizer/refs/patterns.md +282 -0
- package/skills/wickedizer/refs/review.md +221 -0
- package/skills/wickedizer/refs/structure.md +282 -0
- package/skills/wickedizer/refs/voice-profile.md +125 -0
- package/skills/workflow/SKILL.md +132 -0
- package/skills/workflow/refs/archetype-adjustments.md +205 -0
- package/skills/workflow/refs/evidence.md +58 -0
- package/skills/workflow/refs/integration.md +130 -0
- package/skills/workflow/refs/risk-dimension-signals.md +260 -0
- package/skills/workflow/refs/scoring-rubric.md +49 -0
- package/skills/workflow/refs/signal-keywords.md +176 -0
- package/skills/workflow/refs/specialist-routing-rules.md +159 -0
- package/skills/worktrees/SKILL.md +87 -0
- package/skills/worktrees/refs/recipes.md +96 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
consensus.py — Structured consensus scoring, dissent tracking, and synthesis
|
|
4
|
+
for jam council sessions.
|
|
5
|
+
|
|
6
|
+
Stdlib-only. Cross-platform.
|
|
7
|
+
|
|
8
|
+
CLI:
|
|
9
|
+
consensus.py synthesize --proposals proposals.json [--reviews reviews.json] [--question "..."]
|
|
10
|
+
consensus.py score --proposals proposals.json
|
|
11
|
+
consensus.py format --result result.json [--show-dissent]
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
from dataclasses import asdict, dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Mapping
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Council raw-vote surfacing (Issue #584)
|
|
26
|
+
#
|
|
27
|
+
# The council agent produces a synthesised verdict from per-model responses,
|
|
28
|
+
# but the individual per-model votes + one-line rationales are never surfaced
|
|
29
|
+
# to the caller. Even on a unanimous verdict, each model may flag a different
|
|
30
|
+
# concern the caller would want to see. ``build_council_output`` assembles an
|
|
31
|
+
# envelope carrying both layers, switched by ``WG_COUNCIL_OUTPUT``.
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
#: Maximum characters kept when deriving a per-model rationale from the
|
|
35
|
+
#: model's raw response. Named constant so the value cannot drift between
|
|
36
|
+
#: the extractor and the tests (R3 — no magic values).
|
|
37
|
+
_RATIONALE_MAX_CHARS: int = 240
|
|
38
|
+
|
|
39
|
+
#: Env var controlling which layers ``build_council_output`` emits. Values are
|
|
40
|
+
#: normalised lower-case; unknown values fall back to ``both`` so we never
|
|
41
|
+
#: starve the caller of data because of a typo (graceful degradation).
|
|
42
|
+
ENV_COUNCIL_OUTPUT: str = "WG_COUNCIL_OUTPUT"
|
|
43
|
+
COUNCIL_OUTPUT_BOTH: str = "both"
|
|
44
|
+
COUNCIL_OUTPUT_SYNTH: str = "synth"
|
|
45
|
+
COUNCIL_OUTPUT_RAW: str = "raw"
|
|
46
|
+
_VALID_COUNCIL_OUTPUTS: frozenset[str] = frozenset(
|
|
47
|
+
{COUNCIL_OUTPUT_BOTH, COUNCIL_OUTPUT_SYNTH, COUNCIL_OUTPUT_RAW}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
#: Label line prefixes a model may emit to mark its own terse summary. When
|
|
51
|
+
#: present, the text following the label wins over the raw-response prefix
|
|
52
|
+
#: so the caller sees the model's own one-liner rather than our slice.
|
|
53
|
+
_SUMMARY_LABEL_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
54
|
+
re.compile(r"^\s*(?:summary|tl;dr|verdict|recommendation)\s*[:\-]\s*(.+)$", re.IGNORECASE),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Import siblings (scripts/ root)
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
61
|
+
|
|
62
|
+
from _domain_store import DomainStore # noqa: E402
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Data Models
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class Proposal:
|
|
71
|
+
persona: str
|
|
72
|
+
proposal: str
|
|
73
|
+
rationale: str
|
|
74
|
+
confidence: float # 0.0 to 1.0
|
|
75
|
+
concerns: list[str] = field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class CrossReview:
|
|
80
|
+
reviewer: str
|
|
81
|
+
target_persona: str
|
|
82
|
+
agreements: list[str] = field(default_factory=list)
|
|
83
|
+
disagreements: list[dict] = field(default_factory=list) # [{"point": ..., "counter": ...}]
|
|
84
|
+
questions: list[str] = field(default_factory=list)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class DissentingView:
|
|
89
|
+
persona: str
|
|
90
|
+
view: str
|
|
91
|
+
strength: str # "strong", "moderate", "mild"
|
|
92
|
+
acknowledged: bool = False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class ConsensusResult:
|
|
97
|
+
decision: str
|
|
98
|
+
confidence: float
|
|
99
|
+
consensus_points: list[dict] = field(default_factory=list) # [{"point": ..., "agreement": N, "of": M}]
|
|
100
|
+
dissenting_views: list[DissentingView] = field(default_factory=list)
|
|
101
|
+
open_questions: list[str] = field(default_factory=list)
|
|
102
|
+
rounds: int = 1
|
|
103
|
+
participants: int = 0
|
|
104
|
+
proposals: list[Proposal] = field(default_factory=list)
|
|
105
|
+
cross_reviews: list[CrossReview] = field(default_factory=list)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Text helpers
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
_MIN_WORD_LEN = 4 # words shorter than this are treated as stop words
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _meaningful_words(text: str) -> set[str]:
|
|
116
|
+
"""Extract meaningful words (length >= 4) from text, lowercased."""
|
|
117
|
+
return {
|
|
118
|
+
w for w in re.findall(r"[a-z0-9]+", text.lower())
|
|
119
|
+
if len(w) >= _MIN_WORD_LEN
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _similarity(a: str, b: str) -> float:
|
|
124
|
+
"""Word-overlap similarity between two strings (Jaccard on meaningful words)."""
|
|
125
|
+
wa = _meaningful_words(a)
|
|
126
|
+
wb = _meaningful_words(b)
|
|
127
|
+
if not wa or not wb:
|
|
128
|
+
return 0.0
|
|
129
|
+
return len(wa & wb) / len(wa | wb)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _extract_phrases(text: str) -> list[str]:
|
|
133
|
+
"""Split text into key phrases (sentences or bullet points)."""
|
|
134
|
+
# Split on sentence-ending punctuation or newlines with bullets
|
|
135
|
+
parts = re.split(r"(?<=[.!?])\s+|\n+", text.strip())
|
|
136
|
+
# Clean and filter
|
|
137
|
+
phrases = []
|
|
138
|
+
for p in parts:
|
|
139
|
+
cleaned = re.sub(r"^[\s\-*#>]+", "", p).strip()
|
|
140
|
+
if len(cleaned) > 10: # skip trivially short fragments
|
|
141
|
+
phrases.append(cleaned)
|
|
142
|
+
return phrases
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Core Functions
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
def _find_consensus_points(
|
|
150
|
+
proposals: list[Proposal],
|
|
151
|
+
) -> tuple[list[dict], list[dict]]:
|
|
152
|
+
"""Find consensus and divergent points across proposals.
|
|
153
|
+
|
|
154
|
+
Returns (consensus_points, divergent_points) where each entry is:
|
|
155
|
+
{"point": str, "agreement": int, "of": int}
|
|
156
|
+
"""
|
|
157
|
+
n = len(proposals)
|
|
158
|
+
if n == 0:
|
|
159
|
+
return [], []
|
|
160
|
+
|
|
161
|
+
# Gather all phrases from all proposals
|
|
162
|
+
all_phrases: list[tuple[str, int]] = [] # (phrase, proposal_index)
|
|
163
|
+
for i, prop in enumerate(proposals):
|
|
164
|
+
for phrase in _extract_phrases(prop.proposal):
|
|
165
|
+
all_phrases.append((phrase, i))
|
|
166
|
+
|
|
167
|
+
# Deduplicate similar phrases, keeping the longest representative
|
|
168
|
+
clusters: list[dict] = [] # {"representative": str, "sources": set[int]}
|
|
169
|
+
for phrase, idx in all_phrases:
|
|
170
|
+
merged = False
|
|
171
|
+
for cluster in clusters:
|
|
172
|
+
if _similarity(phrase, cluster["representative"]) > 0.35:
|
|
173
|
+
cluster["sources"].add(idx)
|
|
174
|
+
# Keep longer phrase as representative
|
|
175
|
+
if len(phrase) > len(cluster["representative"]):
|
|
176
|
+
cluster["representative"] = phrase
|
|
177
|
+
merged = True
|
|
178
|
+
break
|
|
179
|
+
if not merged:
|
|
180
|
+
clusters.append({"representative": phrase, "sources": {idx}})
|
|
181
|
+
|
|
182
|
+
consensus = []
|
|
183
|
+
divergent = []
|
|
184
|
+
for cluster in clusters:
|
|
185
|
+
count = len(cluster["sources"])
|
|
186
|
+
entry = {
|
|
187
|
+
"point": cluster["representative"],
|
|
188
|
+
"agreement": count,
|
|
189
|
+
"of": n,
|
|
190
|
+
}
|
|
191
|
+
ratio = count / n
|
|
192
|
+
if ratio >= 0.6:
|
|
193
|
+
consensus.append(entry)
|
|
194
|
+
elif ratio < 0.4:
|
|
195
|
+
divergent.append(entry)
|
|
196
|
+
|
|
197
|
+
# Sort by agreement descending
|
|
198
|
+
consensus.sort(key=lambda x: x["agreement"], reverse=True)
|
|
199
|
+
divergent.sort(key=lambda x: x["agreement"])
|
|
200
|
+
return consensus, divergent
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def score_consensus(proposals: list[Proposal]) -> dict:
|
|
204
|
+
"""Calculate consensus metrics from independent proposals.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
{
|
|
208
|
+
"consensus_points": [...],
|
|
209
|
+
"confidence": float,
|
|
210
|
+
"agreement_ratio": float,
|
|
211
|
+
"divergent_points": [...],
|
|
212
|
+
}
|
|
213
|
+
"""
|
|
214
|
+
consensus_pts, divergent_pts = _find_consensus_points(proposals)
|
|
215
|
+
|
|
216
|
+
# Average confidence across proposals
|
|
217
|
+
avg_confidence = (
|
|
218
|
+
sum(p.confidence for p in proposals) / len(proposals)
|
|
219
|
+
if proposals else 0.0
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Agreement ratio: proportion of points that are consensus vs total
|
|
223
|
+
total_pts = len(consensus_pts) + len(divergent_pts)
|
|
224
|
+
agreement_ratio = (
|
|
225
|
+
len(consensus_pts) / total_pts if total_pts > 0 else 0.0
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
"consensus_points": consensus_pts,
|
|
230
|
+
"confidence": round(avg_confidence, 3),
|
|
231
|
+
"agreement_ratio": round(agreement_ratio, 3),
|
|
232
|
+
"divergent_points": divergent_pts,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def extract_dissent(
|
|
237
|
+
proposals: list[Proposal],
|
|
238
|
+
cross_reviews: list[CrossReview] | None = None,
|
|
239
|
+
) -> list[DissentingView]:
|
|
240
|
+
"""Extract dissenting views from proposals and cross-reviews.
|
|
241
|
+
|
|
242
|
+
Strength classification:
|
|
243
|
+
- "strong": raised by 2+ personas or has detailed counter-rationale
|
|
244
|
+
- "moderate": raised by 1 persona with rationale
|
|
245
|
+
- "mild": noted as concern without strong objection
|
|
246
|
+
"""
|
|
247
|
+
dissents: list[DissentingView] = []
|
|
248
|
+
concern_tracker: dict[str, list[str]] = {} # concern_text -> [personas]
|
|
249
|
+
|
|
250
|
+
# Stage 1: Gather concerns from proposals
|
|
251
|
+
for prop in proposals:
|
|
252
|
+
for concern in prop.concerns:
|
|
253
|
+
key = concern.strip().lower()
|
|
254
|
+
if key not in concern_tracker:
|
|
255
|
+
concern_tracker[key] = []
|
|
256
|
+
concern_tracker[key].append(prop.persona)
|
|
257
|
+
|
|
258
|
+
for concern_text, personas in concern_tracker.items():
|
|
259
|
+
# Find the original-case version
|
|
260
|
+
original = concern_text
|
|
261
|
+
for prop in proposals:
|
|
262
|
+
for c in prop.concerns:
|
|
263
|
+
if c.strip().lower() == concern_text:
|
|
264
|
+
original = c.strip()
|
|
265
|
+
break
|
|
266
|
+
|
|
267
|
+
if len(personas) >= 2:
|
|
268
|
+
strength = "strong"
|
|
269
|
+
else:
|
|
270
|
+
strength = "moderate"
|
|
271
|
+
|
|
272
|
+
dissents.append(DissentingView(
|
|
273
|
+
persona=", ".join(sorted(set(personas))),
|
|
274
|
+
view=original,
|
|
275
|
+
strength=strength,
|
|
276
|
+
acknowledged=False,
|
|
277
|
+
))
|
|
278
|
+
|
|
279
|
+
# Stage 2: Extract from cross-reviews
|
|
280
|
+
if cross_reviews:
|
|
281
|
+
for review in cross_reviews:
|
|
282
|
+
for disagreement in review.disagreements:
|
|
283
|
+
point = disagreement.get("point", "")
|
|
284
|
+
counter = disagreement.get("counter", "")
|
|
285
|
+
if not point:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# Check if already captured as a concern
|
|
289
|
+
already_captured = any(
|
|
290
|
+
_similarity(point, d.view) > 0.5 for d in dissents
|
|
291
|
+
)
|
|
292
|
+
if already_captured:
|
|
293
|
+
# Upgrade to strong if it appears in cross-review too
|
|
294
|
+
for d in dissents:
|
|
295
|
+
if _similarity(point, d.view) > 0.5:
|
|
296
|
+
d.strength = "strong"
|
|
297
|
+
break
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# Detailed counter-rationale makes it strong
|
|
301
|
+
strength = "strong" if len(counter) > 50 else "moderate"
|
|
302
|
+
dissents.append(DissentingView(
|
|
303
|
+
persona=review.reviewer,
|
|
304
|
+
view=point,
|
|
305
|
+
strength=strength,
|
|
306
|
+
acknowledged=False,
|
|
307
|
+
))
|
|
308
|
+
|
|
309
|
+
# Unresolved questions are mild dissent
|
|
310
|
+
for question in review.questions:
|
|
311
|
+
if len(question.strip()) > 10:
|
|
312
|
+
dissents.append(DissentingView(
|
|
313
|
+
persona=review.reviewer,
|
|
314
|
+
view=question.strip(),
|
|
315
|
+
strength="mild",
|
|
316
|
+
acknowledged=False,
|
|
317
|
+
))
|
|
318
|
+
|
|
319
|
+
return dissents
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def synthesize(
|
|
323
|
+
proposals: list[Proposal],
|
|
324
|
+
cross_reviews: list[CrossReview] | None = None,
|
|
325
|
+
question: str = "",
|
|
326
|
+
) -> ConsensusResult:
|
|
327
|
+
"""Run the full 3-stage consensus protocol.
|
|
328
|
+
|
|
329
|
+
Stage 1: Analyze independent proposals
|
|
330
|
+
Stage 2: Process cross-reviews (if provided)
|
|
331
|
+
Stage 3: Produce structured synthesis
|
|
332
|
+
"""
|
|
333
|
+
# Stage 1: Score consensus from proposals
|
|
334
|
+
scores = score_consensus(proposals)
|
|
335
|
+
|
|
336
|
+
# Stage 2: Extract dissent (includes cross-review processing)
|
|
337
|
+
dissents = extract_dissent(proposals, cross_reviews)
|
|
338
|
+
|
|
339
|
+
# Stage 3: Synthesize decision
|
|
340
|
+
consensus_pts = scores["consensus_points"]
|
|
341
|
+
|
|
342
|
+
# Build decision summary from top consensus points
|
|
343
|
+
if consensus_pts:
|
|
344
|
+
top_points = [cp["point"] for cp in consensus_pts[:5]]
|
|
345
|
+
decision = ". ".join(top_points)
|
|
346
|
+
if not decision.endswith("."):
|
|
347
|
+
decision += "."
|
|
348
|
+
elif proposals:
|
|
349
|
+
# No clear consensus — use highest-confidence proposal
|
|
350
|
+
best = max(proposals, key=lambda p: p.confidence)
|
|
351
|
+
decision = best.proposal
|
|
352
|
+
else:
|
|
353
|
+
decision = "No proposals provided."
|
|
354
|
+
|
|
355
|
+
# Collect open questions from cross-reviews
|
|
356
|
+
open_questions: list[str] = []
|
|
357
|
+
if cross_reviews:
|
|
358
|
+
for review in cross_reviews:
|
|
359
|
+
open_questions.extend(review.questions)
|
|
360
|
+
# Deduplicate
|
|
361
|
+
seen: set[str] = set()
|
|
362
|
+
unique_questions: list[str] = []
|
|
363
|
+
for q in open_questions:
|
|
364
|
+
q_lower = q.strip().lower()
|
|
365
|
+
if q_lower and q_lower not in seen:
|
|
366
|
+
seen.add(q_lower)
|
|
367
|
+
unique_questions.append(q.strip())
|
|
368
|
+
|
|
369
|
+
return ConsensusResult(
|
|
370
|
+
decision=decision,
|
|
371
|
+
confidence=scores["confidence"],
|
|
372
|
+
consensus_points=consensus_pts,
|
|
373
|
+
dissenting_views=dissents,
|
|
374
|
+
open_questions=unique_questions,
|
|
375
|
+
rounds=1 if not cross_reviews else 2,
|
|
376
|
+
participants=len(proposals),
|
|
377
|
+
proposals=proposals,
|
|
378
|
+
cross_reviews=cross_reviews or [],
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
# Formatting
|
|
384
|
+
# ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
def format_for_memory(result: ConsensusResult) -> dict:
|
|
387
|
+
"""Format consensus result for storage in wicked-brain:memory.
|
|
388
|
+
|
|
389
|
+
Returns dict suitable for memory.create() with:
|
|
390
|
+
- content: decision summary
|
|
391
|
+
- type: "decision"
|
|
392
|
+
- metadata: consensus details including dissent
|
|
393
|
+
"""
|
|
394
|
+
strong_dissents = [
|
|
395
|
+
d for d in result.dissenting_views if d.strength == "strong"
|
|
396
|
+
]
|
|
397
|
+
return {
|
|
398
|
+
"content": result.decision,
|
|
399
|
+
"type": "decision",
|
|
400
|
+
"metadata": {
|
|
401
|
+
"confidence": result.confidence,
|
|
402
|
+
"participants": result.participants,
|
|
403
|
+
"rounds": result.rounds,
|
|
404
|
+
"consensus_point_count": len(result.consensus_points),
|
|
405
|
+
"dissent_count": len(result.dissenting_views),
|
|
406
|
+
"strong_dissent_count": len(strong_dissents),
|
|
407
|
+
"strong_dissents": [
|
|
408
|
+
{"persona": d.persona, "view": d.view}
|
|
409
|
+
for d in strong_dissents
|
|
410
|
+
],
|
|
411
|
+
"open_question_count": len(result.open_questions),
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def format_for_display(
|
|
417
|
+
result: ConsensusResult, show_dissent: bool = False,
|
|
418
|
+
) -> str:
|
|
419
|
+
"""Format consensus result as human-readable markdown."""
|
|
420
|
+
lines: list[str] = []
|
|
421
|
+
lines.append("## Council Consensus")
|
|
422
|
+
lines.append("")
|
|
423
|
+
lines.append(f"**Confidence:** {result.confidence:.0%}")
|
|
424
|
+
lines.append(f"**Participants:** {result.participants}")
|
|
425
|
+
lines.append(f"**Rounds:** {result.rounds}")
|
|
426
|
+
lines.append("")
|
|
427
|
+
|
|
428
|
+
lines.append("### Decision")
|
|
429
|
+
lines.append("")
|
|
430
|
+
lines.append(result.decision)
|
|
431
|
+
lines.append("")
|
|
432
|
+
|
|
433
|
+
if result.consensus_points:
|
|
434
|
+
lines.append("### Consensus Points")
|
|
435
|
+
lines.append("")
|
|
436
|
+
for cp in result.consensus_points:
|
|
437
|
+
lines.append(
|
|
438
|
+
f"- ({cp['agreement']}/{cp['of']}) {cp['point']}"
|
|
439
|
+
)
|
|
440
|
+
lines.append("")
|
|
441
|
+
|
|
442
|
+
if show_dissent and result.dissenting_views:
|
|
443
|
+
lines.append("### Dissenting Views")
|
|
444
|
+
lines.append("")
|
|
445
|
+
for dv in result.dissenting_views:
|
|
446
|
+
icon = {"strong": "!!", "moderate": "!", "mild": "~"}.get(
|
|
447
|
+
dv.strength, "~"
|
|
448
|
+
)
|
|
449
|
+
lines.append(f"- [{icon}] **{dv.persona}**: {dv.view}")
|
|
450
|
+
lines.append("")
|
|
451
|
+
|
|
452
|
+
if result.open_questions:
|
|
453
|
+
lines.append("### Open Questions")
|
|
454
|
+
lines.append("")
|
|
455
|
+
for q in result.open_questions:
|
|
456
|
+
lines.append(f"- {q}")
|
|
457
|
+
lines.append("")
|
|
458
|
+
|
|
459
|
+
return "\n".join(lines)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
# ---------------------------------------------------------------------------
|
|
463
|
+
# Storage
|
|
464
|
+
# ---------------------------------------------------------------------------
|
|
465
|
+
|
|
466
|
+
def store_council_result(result: ConsensusResult, session_id: str) -> dict:
|
|
467
|
+
"""Store council result via DomainStore("wicked-jam").
|
|
468
|
+
|
|
469
|
+
Stores under source "council-results" with full consensus data.
|
|
470
|
+
"""
|
|
471
|
+
ds = DomainStore("wicked-jam")
|
|
472
|
+
payload = {
|
|
473
|
+
"session_id": session_id,
|
|
474
|
+
"decision": result.decision,
|
|
475
|
+
"confidence": result.confidence,
|
|
476
|
+
"consensus_points": result.consensus_points,
|
|
477
|
+
"dissenting_views": [asdict(d) for d in result.dissenting_views],
|
|
478
|
+
"open_questions": result.open_questions,
|
|
479
|
+
"rounds": result.rounds,
|
|
480
|
+
"participants": result.participants,
|
|
481
|
+
"proposals": [asdict(p) for p in result.proposals],
|
|
482
|
+
"cross_reviews": [asdict(r) for r in result.cross_reviews],
|
|
483
|
+
}
|
|
484
|
+
return ds.create("council-results", payload)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ---------------------------------------------------------------------------
|
|
488
|
+
# Deserialization helpers
|
|
489
|
+
# ---------------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
def _proposals_from_json(data: list[dict]) -> list[Proposal]:
|
|
492
|
+
return [
|
|
493
|
+
Proposal(
|
|
494
|
+
persona=d["persona"],
|
|
495
|
+
proposal=d["proposal"],
|
|
496
|
+
rationale=d.get("rationale", ""),
|
|
497
|
+
confidence=float(d.get("confidence", 0.5)),
|
|
498
|
+
concerns=d.get("concerns", []),
|
|
499
|
+
)
|
|
500
|
+
for d in data
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _reviews_from_json(data: list[dict]) -> list[CrossReview]:
|
|
505
|
+
return [
|
|
506
|
+
CrossReview(
|
|
507
|
+
reviewer=d["reviewer"],
|
|
508
|
+
target_persona=d["target_persona"],
|
|
509
|
+
agreements=d.get("agreements", []),
|
|
510
|
+
disagreements=d.get("disagreements", []),
|
|
511
|
+
questions=d.get("questions", []),
|
|
512
|
+
)
|
|
513
|
+
for d in data
|
|
514
|
+
]
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _result_from_json(data: dict) -> ConsensusResult:
|
|
518
|
+
return ConsensusResult(
|
|
519
|
+
decision=data["decision"],
|
|
520
|
+
confidence=float(data["confidence"]),
|
|
521
|
+
consensus_points=data.get("consensus_points", []),
|
|
522
|
+
dissenting_views=[
|
|
523
|
+
DissentingView(**dv)
|
|
524
|
+
for dv in data.get("dissenting_views", [])
|
|
525
|
+
],
|
|
526
|
+
open_questions=data.get("open_questions", []),
|
|
527
|
+
rounds=data.get("rounds", 1),
|
|
528
|
+
participants=data.get("participants", 0),
|
|
529
|
+
proposals=_proposals_from_json(data.get("proposals", [])),
|
|
530
|
+
cross_reviews=_reviews_from_json(data.get("cross_reviews", [])),
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# ---------------------------------------------------------------------------
|
|
535
|
+
# Council raw-vote surfacing (Issue #584)
|
|
536
|
+
# ---------------------------------------------------------------------------
|
|
537
|
+
|
|
538
|
+
def _extract_rationale(
|
|
539
|
+
raw_text: str,
|
|
540
|
+
max_chars: int = _RATIONALE_MAX_CHARS,
|
|
541
|
+
) -> str:
|
|
542
|
+
"""Derive a one-line rationale from a model's raw response (Issue #584).
|
|
543
|
+
|
|
544
|
+
Selection rules (deterministic — same input always yields same output):
|
|
545
|
+
|
|
546
|
+
1. If the response contains a ``summary:`` / ``tl;dr:`` / ``verdict:`` /
|
|
547
|
+
``recommendation:`` label line, take the text after the first such
|
|
548
|
+
label. Models frequently emit their own terse one-liner we should
|
|
549
|
+
prefer over our own slice.
|
|
550
|
+
2. Otherwise take the first ``max_chars`` characters of the trimmed
|
|
551
|
+
response, collapsing internal whitespace to single spaces so the
|
|
552
|
+
rationale fits on one line.
|
|
553
|
+
|
|
554
|
+
The returned string is always ``<= max_chars`` characters.
|
|
555
|
+
"""
|
|
556
|
+
if not raw_text:
|
|
557
|
+
return ""
|
|
558
|
+
|
|
559
|
+
text = raw_text.strip()
|
|
560
|
+
# Rule 1: look for a model-authored summary label on any line.
|
|
561
|
+
for line in text.splitlines():
|
|
562
|
+
for pattern in _SUMMARY_LABEL_PATTERNS:
|
|
563
|
+
match = pattern.match(line)
|
|
564
|
+
if match:
|
|
565
|
+
candidate = re.sub(r"\s+", " ", match.group(1)).strip()
|
|
566
|
+
if candidate:
|
|
567
|
+
return candidate[:max_chars]
|
|
568
|
+
|
|
569
|
+
# Rule 2: fall back to a prefix of the flattened response.
|
|
570
|
+
flattened = re.sub(r"\s+", " ", text).strip()
|
|
571
|
+
return flattened[:max_chars]
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _normalise_confidence(value: Any) -> float | None:
|
|
575
|
+
"""Return a float confidence in [0.0, 1.0] or ``None``.
|
|
576
|
+
|
|
577
|
+
``None``, missing keys, empty strings, and unparseable values all map to
|
|
578
|
+
``None`` — critically, they must NOT collapse to ``0.0`` because that
|
|
579
|
+
would be indistinguishable from a model that explicitly voted
|
|
580
|
+
low-confidence (Issue #584 — missing != zero).
|
|
581
|
+
"""
|
|
582
|
+
if value is None:
|
|
583
|
+
return None
|
|
584
|
+
if isinstance(value, bool):
|
|
585
|
+
# bool is a subclass of int in Python; reject it to avoid True -> 1.0.
|
|
586
|
+
return None
|
|
587
|
+
try:
|
|
588
|
+
conf = float(value)
|
|
589
|
+
except (TypeError, ValueError):
|
|
590
|
+
return None
|
|
591
|
+
# Clamp into [0.0, 1.0] — models occasionally emit 0-100 scales; treating
|
|
592
|
+
# out-of-range as None keeps the output honest rather than silently wrong.
|
|
593
|
+
if conf < 0.0 or conf > 1.0:
|
|
594
|
+
return None
|
|
595
|
+
return conf
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _build_raw_vote(vote: Mapping[str, Any]) -> dict[str, Any]:
|
|
599
|
+
"""Build a single raw-vote entry from an input vote record.
|
|
600
|
+
|
|
601
|
+
Input schema (tolerant — any of these fields may be present):
|
|
602
|
+
{
|
|
603
|
+
"model": str, # required
|
|
604
|
+
"verdict": str, # required (passthrough)
|
|
605
|
+
"confidence": float | None, # optional
|
|
606
|
+
"rationale": str, # optional (caller-supplied)
|
|
607
|
+
"raw_text": str, # optional (model response)
|
|
608
|
+
"response": str, # optional alias for raw_text
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
Output schema (stable — this is the public surface per Issue #584):
|
|
612
|
+
{"model", "verdict", "confidence", "rationale"}
|
|
613
|
+
"""
|
|
614
|
+
# Model + verdict are passed through as strings; ``None`` is preserved as
|
|
615
|
+
# ``None`` so downstream consumers can distinguish "no vote" from "empty".
|
|
616
|
+
model = vote.get("model")
|
|
617
|
+
verdict = vote.get("verdict")
|
|
618
|
+
|
|
619
|
+
# Rationale precedence: caller-supplied > model-extracted > empty string.
|
|
620
|
+
supplied = vote.get("rationale")
|
|
621
|
+
if isinstance(supplied, str) and supplied.strip():
|
|
622
|
+
rationale = re.sub(r"\s+", " ", supplied.strip())[:_RATIONALE_MAX_CHARS]
|
|
623
|
+
else:
|
|
624
|
+
raw_text = vote.get("raw_text") or vote.get("response") or ""
|
|
625
|
+
rationale = _extract_rationale(str(raw_text))
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
"model": model,
|
|
629
|
+
"verdict": verdict,
|
|
630
|
+
"confidence": _normalise_confidence(vote.get("confidence")),
|
|
631
|
+
"rationale": rationale,
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def _read_council_output_mode(env: Mapping[str, str] | None) -> str:
|
|
636
|
+
"""Read ``WG_COUNCIL_OUTPUT`` and return a validated mode string."""
|
|
637
|
+
source = env if env is not None else os.environ
|
|
638
|
+
raw = (source.get(ENV_COUNCIL_OUTPUT) or COUNCIL_OUTPUT_BOTH).strip().lower()
|
|
639
|
+
if raw not in _VALID_COUNCIL_OUTPUTS:
|
|
640
|
+
return COUNCIL_OUTPUT_BOTH
|
|
641
|
+
return raw
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def build_council_output(
|
|
645
|
+
votes: list[Mapping[str, Any]],
|
|
646
|
+
synthesized: Mapping[str, Any] | None,
|
|
647
|
+
*,
|
|
648
|
+
env: Mapping[str, str] | None = None,
|
|
649
|
+
) -> dict[str, Any]:
|
|
650
|
+
"""Assemble the council output envelope (Issue #584).
|
|
651
|
+
|
|
652
|
+
Emits the synthesised verdict and/or the raw per-model votes based on the
|
|
653
|
+
``WG_COUNCIL_OUTPUT`` env var (default ``both``):
|
|
654
|
+
|
|
655
|
+
* ``both`` — top-level ``synthesized`` + ``raw_votes``
|
|
656
|
+
* ``synth`` — ``synthesized`` only (legacy shape; backward-compatible)
|
|
657
|
+
* ``raw`` — ``raw_votes`` only (unvarnished per-model layer)
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
votes: One dict per model that voted. Each dict carries at least a
|
|
661
|
+
``model`` and ``verdict``; ``confidence``, ``rationale``, and
|
|
662
|
+
``raw_text`` are optional. See :func:`_build_raw_vote`.
|
|
663
|
+
synthesized: The synthesised verdict shape already produced by the
|
|
664
|
+
council agent. Passed through unchanged; this function does not
|
|
665
|
+
rewrite which models were called or how votes were counted.
|
|
666
|
+
env: Optional env mapping (testing seam; defaults to ``os.environ``).
|
|
667
|
+
|
|
668
|
+
Returns:
|
|
669
|
+
Dict with the requested top-level keys. Callers depending on the
|
|
670
|
+
legacy single-key shape should set ``WG_COUNCIL_OUTPUT=synth``.
|
|
671
|
+
"""
|
|
672
|
+
mode = _read_council_output_mode(env)
|
|
673
|
+
raw_votes = [_build_raw_vote(v) for v in votes]
|
|
674
|
+
synth_payload: dict[str, Any] = dict(synthesized) if synthesized else {}
|
|
675
|
+
|
|
676
|
+
output: dict[str, Any] = {}
|
|
677
|
+
if mode in (COUNCIL_OUTPUT_BOTH, COUNCIL_OUTPUT_SYNTH):
|
|
678
|
+
output["synthesized"] = synth_payload
|
|
679
|
+
if mode in (COUNCIL_OUTPUT_BOTH, COUNCIL_OUTPUT_RAW):
|
|
680
|
+
output["raw_votes"] = raw_votes
|
|
681
|
+
return output
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
# ---------------------------------------------------------------------------
|
|
685
|
+
# CLI
|
|
686
|
+
# ---------------------------------------------------------------------------
|
|
687
|
+
|
|
688
|
+
def main() -> None:
|
|
689
|
+
parser = argparse.ArgumentParser(
|
|
690
|
+
description="Jam council consensus scoring and synthesis",
|
|
691
|
+
)
|
|
692
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
693
|
+
|
|
694
|
+
# synthesize
|
|
695
|
+
syn = sub.add_parser("synthesize", help="Run full consensus protocol")
|
|
696
|
+
syn.add_argument("--proposals", required=True, help="Path to proposals JSON")
|
|
697
|
+
syn.add_argument("--reviews", default=None, help="Path to cross-reviews JSON")
|
|
698
|
+
syn.add_argument("--question", default="", help="Original question")
|
|
699
|
+
|
|
700
|
+
# score
|
|
701
|
+
sc = sub.add_parser("score", help="Score consensus from proposals")
|
|
702
|
+
sc.add_argument("--proposals", required=True, help="Path to proposals JSON")
|
|
703
|
+
|
|
704
|
+
# format
|
|
705
|
+
fmt = sub.add_parser("format", help="Format a consensus result")
|
|
706
|
+
fmt.add_argument("--result", required=True, help="Path to result JSON")
|
|
707
|
+
fmt.add_argument("--show-dissent", action="store_true", help="Show dissenting views")
|
|
708
|
+
|
|
709
|
+
args = parser.parse_args()
|
|
710
|
+
|
|
711
|
+
if args.command == "synthesize":
|
|
712
|
+
proposals = _proposals_from_json(
|
|
713
|
+
json.loads(Path(args.proposals).read_text(encoding="utf-8"))
|
|
714
|
+
)
|
|
715
|
+
reviews = None
|
|
716
|
+
if args.reviews:
|
|
717
|
+
reviews = _reviews_from_json(
|
|
718
|
+
json.loads(Path(args.reviews).read_text(encoding="utf-8"))
|
|
719
|
+
)
|
|
720
|
+
result = synthesize(proposals, reviews, args.question)
|
|
721
|
+
sys.stdout.write(json.dumps(asdict(result), indent=2))
|
|
722
|
+
|
|
723
|
+
elif args.command == "score":
|
|
724
|
+
proposals = _proposals_from_json(
|
|
725
|
+
json.loads(Path(args.proposals).read_text(encoding="utf-8"))
|
|
726
|
+
)
|
|
727
|
+
scores = score_consensus(proposals)
|
|
728
|
+
sys.stdout.write(json.dumps(scores, indent=2))
|
|
729
|
+
|
|
730
|
+
elif args.command == "format":
|
|
731
|
+
data = json.loads(Path(args.result).read_text(encoding="utf-8"))
|
|
732
|
+
result = _result_from_json(data)
|
|
733
|
+
sys.stdout.write(format_for_display(result, show_dissent=args.show_dissent))
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
if __name__ == "__main__":
|
|
737
|
+
main()
|