@event4u/agent-config 8.9.0 → 8.11.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/marketplace.json +1 -1
- package/CHANGELOG.md +189 -0
- package/README.md +5 -5
- package/dist/agent-src/commands/challenge-me/vision.md +121 -32
- package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
- package/dist/agent-src/commands/commit/in-chunks.md +1 -1
- package/dist/agent-src/commands/commit.md +2 -1
- package/dist/agent-src/commands/cost/report.md +14 -3
- package/dist/agent-src/commands/feature/dev.md +4 -4
- package/dist/agent-src/commands/humanize.md +101 -0
- package/dist/agent-src/commands/jira-ticket.md +2 -2
- package/dist/agent-src/commands/memory/load.md +15 -0
- package/dist/agent-src/commands/optimize/project.md +12 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -11
- package/dist/agent-src/commands/tests/create.md +29 -7
- package/dist/agent-src/commands/work.md +33 -0
- package/dist/agent-src/commands/worktree/cleanup.md +83 -0
- package/dist/agent-src/commands/worktree/create.md +64 -0
- package/dist/agent-src/commands/worktree/status.md +75 -0
- package/dist/agent-src/commands/worktree/verify.md +70 -0
- package/dist/agent-src/commands/worktree.md +85 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +91 -34
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
- package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
- package/dist/agent-src/rules/autonomous-execution.md +7 -37
- package/dist/agent-src/rules/broken-access-control.md +7 -45
- package/dist/agent-src/rules/code-comment-discipline.md +148 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +44 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +16 -2
- package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
- package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
- package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
- package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
- package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
- package/dist/agent-src/skills/design-review/SKILL.md +29 -2
- package/dist/agent-src/skills/design-system-capture/SKILL.md +21 -0
- package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
- package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +29 -0
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
- package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
- package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +43 -14
- package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
- package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
- package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
- package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
- package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
- package/dist/agent-src/skills/iconography/SKILL.md +24 -0
- package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
- package/dist/agent-src/skills/laravel/SKILL.md +14 -1
- package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
- package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
- package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/mcp/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
- package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
- package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
- package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
- package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
- package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
- package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
- package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
- package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
- package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
- package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
- package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +7 -6
- package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
- package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
- package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
- package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +187 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
- package/dist/agent-src/templates/agent-settings.md +3 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
- package/dist/agent-src/templates/roadmaps.md +14 -0
- package/dist/cli/commands/recordTriggerEval.js +4 -14
- package/dist/cli/commands/recordTriggerEval.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +374 -88
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +9 -9
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +30 -18
- package/dist/discovery/trust-report.md +5 -5
- package/dist/discovery/workspaces.json +31 -15
- package/dist/install/install.mjs +12 -7
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
- package/dist/scripts/condense.js +12 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +24 -3
- package/docs/architecture.md +3 -3
- package/docs/benchmark.md +51 -0
- package/docs/catalog.md +23 -9
- package/docs/command-flows.md +7 -1
- package/docs/contracts/CHANGELOG-conventions.md +6 -0
- package/docs/contracts/ai-council-config.md +92 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/design-artifact-lifecycle.md +93 -0
- package/docs/contracts/design-artifact-verification.md +30 -1
- package/docs/contracts/evidence-based-pruning.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +25 -0
- package/docs/contracts/kernel-membership.md +35 -0
- package/docs/contracts/measurement-baseline.md +1 -0
- package/docs/contracts/release-pr-gating.md +26 -0
- package/docs/contracts/release-sizing.md +107 -0
- package/docs/contracts/surface-agent-contracts.md +153 -0
- package/docs/contracts/write-engine.md +38 -1
- package/docs/customization.md +12 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
- package/docs/decisions/ADR-028-root-layout.md +1 -1
- package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
- package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
- package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
- package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
- package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
- package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
- package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
- package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
- package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
- package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
- package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
- package/docs/decisions/INDEX.md +11 -9
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/distribution/consumer-matrix.md +96 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/code-clarity.md +106 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/proof.md +21 -15
- package/docs/release-runbook.md +3 -0
- package/docs/self-review-gate.md +51 -0
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +29 -9
- package/src/scripts/_cli/cmd_doctor.ts +34 -1
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
- package/src/scripts/_lib/knowledge_global.ts +6 -2
- package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
- package/src/scripts/_lib/lexical_index.ts +15 -2
- package/src/scripts/_lib/orchestration_record.ts +27 -0
- package/src/scripts/_lib/orchestration_savings.ts +52 -0
- package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
- package/src/scripts/ai_council/chairman.ts +90 -0
- package/src/scripts/ai_council/clients.ts +18 -3
- package/src/scripts/ai_council/config.ts +157 -0
- package/src/scripts/ai_council/debate_gates.ts +89 -0
- package/src/scripts/ai_council/orchestrator.ts +65 -2
- package/src/scripts/ai_council/prompts.ts +121 -11
- package/src/scripts/ai_council/replay.ts +17 -0
- package/src/scripts/ai_council/stance_tally.ts +184 -0
- package/src/scripts/ai_tells_rules.ts +433 -0
- package/src/scripts/bench_humanizer_eval.ts +240 -0
- package/src/scripts/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +21 -9
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_release_adjacent_health.ts +121 -0
- package/src/scripts/check_rule_invariants.ts +282 -0
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +392 -0
- package/src/scripts/council_cli.ts +147 -0
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/generate_index.ts +3 -2
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +1 -1
- package/src/scripts/lint_changelog_rollback.ts +293 -0
- package/src/scripts/lint_framework_leakage.ts +88 -7
- package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
- package/src/scripts/lint_knowledge_scale.ts +30 -0
- package/src/scripts/lint_originality_shingles.ts +210 -0
- package/src/scripts/orchestration_record.ts +17 -0
- package/src/scripts/orchestration_savings_report.ts +36 -3
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/second_brain_retrieval.ts +44 -2
- package/src/scripts/self_review_gate.test.ts +65 -0
- package/src/scripts/self_review_gate.ts +298 -0
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/trigger_eval_grandfather.json +223 -224
- package/src/scripts/trigger_eval_rotation.ts +344 -0
- package/src/scripts/update_counts.ts +5 -17
- package/src/scripts/worktree_cleanup_check.ts +263 -0
- package/src/server/schemas/settings.ts +8 -3
- package/src/scripts/analysis_freshness.ts +0 -362
- package/src/scripts/export_replay_corpus.ts +0 -208
|
@@ -373,6 +373,13 @@ export interface MemberConfig {
|
|
|
373
373
|
readonly binary: string | null;
|
|
374
374
|
readonly model_ladder: readonly string[];
|
|
375
375
|
readonly participate_low_impact: boolean;
|
|
376
|
+
/**
|
|
377
|
+
* Optional capability rank for chairman-`auto` tie-breaking (Phase 2;
|
|
378
|
+
* council 2026-07-12 — provider-family difference is primary, tier is the
|
|
379
|
+
* tie-break). Higher = stronger. `null` when unset; selection then falls
|
|
380
|
+
* back to deterministic config order.
|
|
381
|
+
*/
|
|
382
|
+
readonly tier: number | null;
|
|
376
383
|
}
|
|
377
384
|
|
|
378
385
|
/**
|
|
@@ -451,6 +458,32 @@ export interface DecisionReplayConfig {
|
|
|
451
458
|
readonly include_member_arguments: boolean;
|
|
452
459
|
}
|
|
453
460
|
|
|
461
|
+
/** Option-level stance tally (road-to-opt-council-deliberation Phase 1). */
|
|
462
|
+
export interface StanceTallyConfig {
|
|
463
|
+
readonly enabled: boolean;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Chairman synthesis (road-to-opt-council-deliberation Phase 2). Default
|
|
467
|
+
* `host` = today's host-synthesis behaviour, byte-identical. */
|
|
468
|
+
export interface ChairmanConfig {
|
|
469
|
+
readonly mode: string; // one of _VALID_CHAIRMAN_MODES
|
|
470
|
+
readonly member: string | null; // required (and validated) when mode === 'member'
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** Debate enforcement gates (road-to-opt-council-deliberation Phase 3).
|
|
474
|
+
* Enables the anti-conformity directive + the deterministic post-round
|
|
475
|
+
* dissent-quota / novelty checks on the debate path. */
|
|
476
|
+
export interface DebateGatesConfig {
|
|
477
|
+
readonly enabled: boolean;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Pre-round-1 restatement pass (road-to-opt-council-deliberation Phase 3). */
|
|
481
|
+
export interface RestateConfig {
|
|
482
|
+
readonly enabled: boolean;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const _VALID_CHAIRMAN_MODES: ReadonlySet<string> = new Set(['host', 'member', 'auto']);
|
|
486
|
+
|
|
454
487
|
/** Routing entry for one impact class (Phase 10). */
|
|
455
488
|
export interface DecisionResolutionEntry {
|
|
456
489
|
readonly mode: string;
|
|
@@ -519,6 +552,10 @@ export interface CouncilConfig {
|
|
|
519
552
|
readonly model_downgrade: ModelDowngradeConfig;
|
|
520
553
|
readonly debate: DebateConfig;
|
|
521
554
|
readonly decision_replay: DecisionReplayConfig;
|
|
555
|
+
readonly stance_tally: StanceTallyConfig;
|
|
556
|
+
readonly chairman: ChairmanConfig;
|
|
557
|
+
readonly debate_gates: DebateGatesConfig;
|
|
558
|
+
readonly restate: RestateConfig;
|
|
522
559
|
readonly decision_resolution: DecisionResolutionConfig;
|
|
523
560
|
readonly routing: RoutingConfig;
|
|
524
561
|
readonly low_impact: LowImpactConfig;
|
|
@@ -748,6 +785,20 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
748
785
|
_asDict(_getOr(raw, 'decision_replay', {})),
|
|
749
786
|
'decision_replay',
|
|
750
787
|
);
|
|
788
|
+
const stance_tally = _build_stance_tally(
|
|
789
|
+
_asDict(_getOr(raw, 'stance_tally', {})),
|
|
790
|
+
'stance_tally',
|
|
791
|
+
);
|
|
792
|
+
const chairman = _build_chairman(
|
|
793
|
+
_asDict(_getOr(raw, 'chairman', {})),
|
|
794
|
+
'chairman',
|
|
795
|
+
members,
|
|
796
|
+
);
|
|
797
|
+
const debate_gates = _build_debate_gates(
|
|
798
|
+
_asDict(_getOr(raw, 'debate_gates', {})),
|
|
799
|
+
'debate_gates',
|
|
800
|
+
);
|
|
801
|
+
const restate = _build_restate(_asDict(_getOr(raw, 'restate', {})), 'restate');
|
|
751
802
|
const decision_resolution = _build_decision_resolution(
|
|
752
803
|
_asDict(_getOr(raw, 'decision_resolution', {})),
|
|
753
804
|
);
|
|
@@ -774,6 +825,10 @@ export function _build_config(raw: Dict, source_path: PathLike): CouncilConfig {
|
|
|
774
825
|
model_downgrade,
|
|
775
826
|
debate,
|
|
776
827
|
decision_replay,
|
|
828
|
+
stance_tally,
|
|
829
|
+
chairman,
|
|
830
|
+
debate_gates,
|
|
831
|
+
restate,
|
|
777
832
|
decision_resolution,
|
|
778
833
|
routing,
|
|
779
834
|
low_impact,
|
|
@@ -927,6 +982,96 @@ function _build_decision_replay(d: Dict, scope: string): DecisionReplayConfig {
|
|
|
927
982
|
};
|
|
928
983
|
}
|
|
929
984
|
|
|
985
|
+
/**
|
|
986
|
+
* `ai_council.stance_tally` — option-level stance tally (Phase 1). Default-off;
|
|
987
|
+
* an absent block yields `{ enabled: false }`, keeping the council path
|
|
988
|
+
* byte-identical. Per-field type validation (mirrors `_build_decision_replay`);
|
|
989
|
+
* a non-bool `enabled` is rejected rather than silently coerced.
|
|
990
|
+
*/
|
|
991
|
+
function _build_stance_tally(d: Dict, scope: string): StanceTallyConfig {
|
|
992
|
+
if (!_isDict(d)) {
|
|
993
|
+
throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
|
|
994
|
+
}
|
|
995
|
+
const enabled = _get(d, 'enabled', false);
|
|
996
|
+
if (!_isBool(enabled)) {
|
|
997
|
+
throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
|
|
998
|
+
}
|
|
999
|
+
return { enabled: Boolean(enabled) };
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* `ai_council.chairman` (Phase 2). Default `{ mode: 'host', member: null }` =
|
|
1004
|
+
* today's host synthesis, byte-identical. Enum-validates `mode`; when
|
|
1005
|
+
* `mode: 'member'` the named member must exist AND be enabled (fail-closed at
|
|
1006
|
+
* load, mirroring the advisor cross-validation). `mode: 'auto'` needs no member.
|
|
1007
|
+
*/
|
|
1008
|
+
function _build_chairman(
|
|
1009
|
+
d: Dict,
|
|
1010
|
+
scope: string,
|
|
1011
|
+
members: ReadonlyMap<string, MemberConfig>,
|
|
1012
|
+
): ChairmanConfig {
|
|
1013
|
+
if (!_isDict(d)) {
|
|
1014
|
+
throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
|
|
1015
|
+
}
|
|
1016
|
+
const mode = _get(d, 'mode', 'host');
|
|
1017
|
+
if (!(_isStr(mode) && _VALID_CHAIRMAN_MODES.has(mode))) {
|
|
1018
|
+
throw new CouncilConfigError(
|
|
1019
|
+
`\`${scope}.mode\`=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_CHAIRMAN_MODES)}.`,
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
let member: string | null = null;
|
|
1023
|
+
const rawMember = _get(d, 'member', null);
|
|
1024
|
+
if (rawMember !== null && rawMember !== undefined) {
|
|
1025
|
+
if (!_isStr(rawMember)) {
|
|
1026
|
+
throw new CouncilConfigError(`\`${scope}.member\` must be a string.`);
|
|
1027
|
+
}
|
|
1028
|
+
member = rawMember;
|
|
1029
|
+
}
|
|
1030
|
+
if (mode === 'member') {
|
|
1031
|
+
if (member === null) {
|
|
1032
|
+
throw new CouncilConfigError(
|
|
1033
|
+
`\`${scope}.mode\` is 'member' but \`${scope}.member\` is unset.`,
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
const bound = members.get(member);
|
|
1037
|
+
if (bound === undefined) {
|
|
1038
|
+
throw new CouncilConfigError(
|
|
1039
|
+
`\`${scope}.member\`=${_pyReprStr(member)}: no such member in the \`members\` block.`,
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
if (!bound.enabled) {
|
|
1043
|
+
throw new CouncilConfigError(
|
|
1044
|
+
`\`${scope}.member\`=${_pyReprStr(member)}: member exists but is disabled.`,
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return { mode, member };
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/** `ai_council.debate_gates` (Phase 3). `{enabled}`, default false. */
|
|
1052
|
+
function _build_debate_gates(d: Dict, scope: string): DebateGatesConfig {
|
|
1053
|
+
if (!_isDict(d)) {
|
|
1054
|
+
throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
|
|
1055
|
+
}
|
|
1056
|
+
const enabled = _get(d, 'enabled', false);
|
|
1057
|
+
if (!_isBool(enabled)) {
|
|
1058
|
+
throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
|
|
1059
|
+
}
|
|
1060
|
+
return { enabled: Boolean(enabled) };
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/** `ai_council.restate` (Phase 3). `{enabled}`, default false. */
|
|
1064
|
+
function _build_restate(d: Dict, scope: string): RestateConfig {
|
|
1065
|
+
if (!_isDict(d)) {
|
|
1066
|
+
throw new CouncilConfigError(`\`${scope}\` must be a mapping.`);
|
|
1067
|
+
}
|
|
1068
|
+
const enabled = _get(d, 'enabled', false);
|
|
1069
|
+
if (!_isBool(enabled)) {
|
|
1070
|
+
throw new CouncilConfigError(`\`${scope}.enabled\` must be a bool.`);
|
|
1071
|
+
}
|
|
1072
|
+
return { enabled: Boolean(enabled) };
|
|
1073
|
+
}
|
|
1074
|
+
|
|
930
1075
|
const _VALID_RESOLUTION_MODES: ReadonlySet<string> = new Set([
|
|
931
1076
|
'agent',
|
|
932
1077
|
'council',
|
|
@@ -1529,6 +1674,17 @@ function _build_member(
|
|
|
1529
1674
|
`(got ${_pyTypeName(participate_raw)}).`,
|
|
1530
1675
|
);
|
|
1531
1676
|
}
|
|
1677
|
+
const tier_raw = _get(cfg, 'tier', null);
|
|
1678
|
+
let tier: number | null = null;
|
|
1679
|
+
if (tier_raw !== null && tier_raw !== undefined) {
|
|
1680
|
+
if (typeof tier_raw !== 'number' || !Number.isInteger(tier_raw) || tier_raw < 1) {
|
|
1681
|
+
throw new CouncilConfigError(
|
|
1682
|
+
`members.${name}.tier must be an integer >= 1 when set ` +
|
|
1683
|
+
`(got ${_pyRepr(tier_raw)}).`,
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
tier = tier_raw;
|
|
1687
|
+
}
|
|
1532
1688
|
return {
|
|
1533
1689
|
name,
|
|
1534
1690
|
enabled: member_enabled,
|
|
@@ -1538,6 +1694,7 @@ function _build_member(
|
|
|
1538
1694
|
binary: (binary as string | null) ?? null,
|
|
1539
1695
|
model_ladder: ladder,
|
|
1540
1696
|
participate_low_impact: participate_raw,
|
|
1697
|
+
tier,
|
|
1541
1698
|
};
|
|
1542
1699
|
}
|
|
1543
1700
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Debate enforcement-gate detectors (road-to-opt-council-deliberation Phase 3).
|
|
2
|
+
//
|
|
3
|
+
// Pure, deterministic checks on the debate path — no LLM call. They decide
|
|
4
|
+
// WHETHER a bounded repair re-prompt is warranted; the re-prompt DISPATCH (one
|
|
5
|
+
// billable call per member per round, under a hard cap) and its auto-fire-vs-
|
|
6
|
+
// confirm POLICY live in the run path and are gated behind
|
|
7
|
+
// `ai_council.debate_gates.enabled` + the /council:design policy decision.
|
|
8
|
+
//
|
|
9
|
+
// The anti-conformity directive (the prompt-level half of the gates) is in
|
|
10
|
+
// `prompts.ts` (`ANTI_CONFORMITY_DIRECTIVE`) and wired into the debate augmenter.
|
|
11
|
+
|
|
12
|
+
import { jaccardSimilarity } from '../_lib/text_similarity.js';
|
|
13
|
+
|
|
14
|
+
/** Near-duplicate bar: reuse the existing shared MERGE-level Jaccard threshold. */
|
|
15
|
+
export const NOVELTY_DUP_THRESHOLD = 0.8;
|
|
16
|
+
|
|
17
|
+
/** Minimum distinct dissenting members for the dissent quota to be satisfied. */
|
|
18
|
+
export const DISSENT_QUOTA = 2;
|
|
19
|
+
|
|
20
|
+
// Objection markers — the engine has no dissent parser (existing "dissent" is
|
|
21
|
+
// LLM-score-derived, not text-marker-derived), so the marker is defined here.
|
|
22
|
+
const _OBJECTION_RE =
|
|
23
|
+
/\b(disagree|disagrees|object|objection|reject|however|but\b|flaw|flawed|wrong|counter|counter-position|dissent|contradict|refute)\b/i;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Novelty gate: a member's round-N reply is a **near-duplicate** of its own
|
|
27
|
+
* round-(N-1) reply when their normalised token similarity meets the threshold.
|
|
28
|
+
* Token-set Jaccard is order-independent and lowercased, so this is the
|
|
29
|
+
* "normalised near-duplicate" the gate targets. A duplicate → the member added
|
|
30
|
+
* nothing this round and warrants one targeted re-prompt.
|
|
31
|
+
*/
|
|
32
|
+
export function is_near_duplicate(
|
|
33
|
+
prevText: string,
|
|
34
|
+
currText: string,
|
|
35
|
+
threshold = NOVELTY_DUP_THRESHOLD,
|
|
36
|
+
): boolean {
|
|
37
|
+
if (prevText.trim().length === 0 || currText.trim().length === 0) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return jaccardSimilarity(prevText, currText) >= threshold;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Dissent quota: at least `quota` members voice an explicit objection (carry an
|
|
45
|
+
* objection marker). Below quota, the round has collapsed toward agreement and
|
|
46
|
+
* warrants one targeted dissent re-prompt to the most-recently-converged member.
|
|
47
|
+
* Empty / error replies do not count.
|
|
48
|
+
*/
|
|
49
|
+
export function count_dissenters(texts: readonly string[]): number {
|
|
50
|
+
let n = 0;
|
|
51
|
+
for (const t of texts) {
|
|
52
|
+
if (t.trim().length > 0 && _OBJECTION_RE.test(t)) {
|
|
53
|
+
n += 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return n;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** True when the dissent quota is satisfied (≥ `quota` members objected). */
|
|
60
|
+
export function dissent_quota_met(texts: readonly string[], quota = DISSENT_QUOTA): boolean {
|
|
61
|
+
return count_dissenters(texts) >= quota;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** What the run path does when a gate warrants a repair re-prompt. */
|
|
65
|
+
export type RepairAction = 'fire' | 'confirm' | 'skip';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Repair-call policy — decided by the contested-design council pass
|
|
69
|
+
* (anthropic/claude-sonnet-4-5 + openai/gpt-4o, 2-round debate, 2026-07-12):
|
|
70
|
+
* a cost estimate is an **upper bound, not a spend commitment**, and repairs
|
|
71
|
+
* are failure-mode responses rather than the mainline path. So:
|
|
72
|
+
*
|
|
73
|
+
* - unattended runs (`--auto-continue`) **auto-fire** under the hard cap
|
|
74
|
+
* (≤ 1 repair per member per round);
|
|
75
|
+
* - interactive runs get a **one-line confirm** before each repair call;
|
|
76
|
+
* - the cap is absolute: a member that already received its repair this round
|
|
77
|
+
* is never re-prompted again (`skip`), regardless of mode;
|
|
78
|
+
* - manual-transport members follow the same policy (deliberation-flow impact,
|
|
79
|
+
* not cost, governs — the repair "call" is one more paste block).
|
|
80
|
+
*/
|
|
81
|
+
export function repair_action(opts: {
|
|
82
|
+
auto_continue: boolean;
|
|
83
|
+
already_repaired: boolean;
|
|
84
|
+
}): RepairAction {
|
|
85
|
+
if (opts.already_repaired) {
|
|
86
|
+
return 'skip';
|
|
87
|
+
}
|
|
88
|
+
return opts.auto_continue ? 'fire' : 'confirm';
|
|
89
|
+
}
|
|
@@ -56,6 +56,8 @@ import type { ProjectContext } from './project_context.js';
|
|
|
56
56
|
import type { AdvisorPlan } from './advisors.js';
|
|
57
57
|
import {
|
|
58
58
|
advisor_system_prompt,
|
|
59
|
+
ANTI_CONFORMITY_DIRECTIVE,
|
|
60
|
+
STANCE_LINE_CONTRACT,
|
|
59
61
|
build_extraction_user_prompt,
|
|
60
62
|
build_peer_review_user_prompt,
|
|
61
63
|
build_scoring_user_prompt,
|
|
@@ -63,6 +65,7 @@ import {
|
|
|
63
65
|
synthesis_template,
|
|
64
66
|
system_prompt_for,
|
|
65
67
|
} from './prompts.js';
|
|
68
|
+
import { render_vote_tally, tally_stances } from './stance_tally.js';
|
|
66
69
|
|
|
67
70
|
// ── Python-format / stdlib parity helpers ────────────────────────────────
|
|
68
71
|
//
|
|
@@ -409,6 +412,13 @@ export interface ConsultOptions {
|
|
|
409
412
|
| ((round_idx: number, responses: CouncilResponse[]) => void)
|
|
410
413
|
| null;
|
|
411
414
|
advisor_plans?: Map<string, AdvisorPlan> | null;
|
|
415
|
+
/**
|
|
416
|
+
* Phase 1 (stance tally): when true, the FINAL round's user prompt appends
|
|
417
|
+
* the mandatory `STANCE:` closing-line contract so `stance_tally.ts` can
|
|
418
|
+
* tally option-level verdicts deterministically. Default `false` → every
|
|
419
|
+
* round's prompt is byte-identical to today.
|
|
420
|
+
*/
|
|
421
|
+
stance_tally?: boolean;
|
|
412
422
|
}
|
|
413
423
|
|
|
414
424
|
/**
|
|
@@ -467,13 +477,23 @@ export function consult(
|
|
|
467
477
|
let last_results: CouncilResponse[] = [];
|
|
468
478
|
let current_user_prompt = question.user_prompt;
|
|
469
479
|
|
|
480
|
+
const stance_tally = opts.stance_tally ?? false;
|
|
470
481
|
for (let round_idx = 0; round_idx < rounds; round_idx++) {
|
|
482
|
+
// Phase 1: the FINAL round carries the mandatory STANCE closing-line
|
|
483
|
+
// contract when the tally is enabled. Off (default) → the original
|
|
484
|
+
// question object flows through untouched, byte-identical to today.
|
|
485
|
+
const is_final = round_idx === rounds - 1;
|
|
486
|
+
const base_prompt = round_idx === 0 ? question.user_prompt : current_user_prompt;
|
|
487
|
+
const prompt_for_round =
|
|
488
|
+
is_final && stance_tally
|
|
489
|
+
? `${base_prompt}\n\n---\n\n${STANCE_LINE_CONTRACT}`
|
|
490
|
+
: base_prompt;
|
|
471
491
|
const round_question =
|
|
472
|
-
round_idx === 0
|
|
492
|
+
round_idx === 0 && prompt_for_round === question.user_prompt
|
|
473
493
|
? question
|
|
474
494
|
: new CouncilQuestion({
|
|
475
495
|
mode: question.mode,
|
|
476
|
-
user_prompt:
|
|
496
|
+
user_prompt: prompt_for_round,
|
|
477
497
|
max_tokens: question.max_tokens,
|
|
478
498
|
});
|
|
479
499
|
last_results = _run_round(members, round_question, resolvedBudget, spent, {
|
|
@@ -838,6 +858,9 @@ function _augment_for_debate_round(
|
|
|
838
858
|
original_prompt: string,
|
|
839
859
|
prior_responses: CouncilResponse[],
|
|
840
860
|
next_round_number: number,
|
|
861
|
+
// Phase 3: when the debate gates are enabled, prepend the anti-conformity
|
|
862
|
+
// directive. Default `false` keeps the prompt byte-identical to today.
|
|
863
|
+
anti_conformity = false,
|
|
841
864
|
): string {
|
|
842
865
|
const blocks: string[] = [];
|
|
843
866
|
let label_idx = 0;
|
|
@@ -853,6 +876,7 @@ function _augment_for_debate_round(
|
|
|
853
876
|
return original_prompt;
|
|
854
877
|
}
|
|
855
878
|
const prior_block = blocks.join('\n\n');
|
|
879
|
+
const anti_conformity_block = anti_conformity ? `${ANTI_CONFORMITY_DIRECTIVE}\n\n` : '';
|
|
856
880
|
return (
|
|
857
881
|
`${original_prompt}\n\n` +
|
|
858
882
|
`---\n\n` +
|
|
@@ -861,6 +885,7 @@ function _augment_for_debate_round(
|
|
|
861
885
|
`debate. Below are anonymised positions from independent\n` +
|
|
862
886
|
`reviewers in the previous round. You do NOT know which model\n` +
|
|
863
887
|
`produced which position.\n\n` +
|
|
888
|
+
anti_conformity_block +
|
|
864
889
|
`Identify the SINGLE strongest opposing position and write a\n` +
|
|
865
890
|
`rebuttal addressed at its strongest steel-manned form. Do NOT\n` +
|
|
866
891
|
`search for common ground — name the load-bearing flaw the\n` +
|
|
@@ -883,6 +908,12 @@ export interface RunDebateOptions {
|
|
|
883
908
|
on_continue?: DebateContinuePrompt | null;
|
|
884
909
|
advisor_plans?: Map<string, AdvisorPlan> | null;
|
|
885
910
|
seed_round_1?: CouncilResponse[] | null;
|
|
911
|
+
/**
|
|
912
|
+
* Phase 3: when true (from `ai_council.debate_gates.enabled`), round-2+
|
|
913
|
+
* prompts carry the anti-conformity directive. Default `false` → the debate
|
|
914
|
+
* prompt is byte-identical to today.
|
|
915
|
+
*/
|
|
916
|
+
debate_gates?: boolean;
|
|
886
917
|
}
|
|
887
918
|
|
|
888
919
|
/**
|
|
@@ -981,6 +1012,7 @@ export function run_debate(
|
|
|
981
1012
|
question.user_prompt,
|
|
982
1013
|
results,
|
|
983
1014
|
round_number + 1,
|
|
1015
|
+
opts.debate_gates ?? false,
|
|
984
1016
|
);
|
|
985
1017
|
// Hard-cap + continue-prompt gating before kicking off N+1.
|
|
986
1018
|
let next_round_usd: number;
|
|
@@ -1433,6 +1465,18 @@ export interface RenderOptions {
|
|
|
1433
1465
|
consensus?: ConsensusResult | null;
|
|
1434
1466
|
peer_review?: PeerReviewResult | null;
|
|
1435
1467
|
explain_confidence?: boolean | null;
|
|
1468
|
+
/**
|
|
1469
|
+
* Phase 1 (stance tally): when true, a `### Vote Tally` block — computed
|
|
1470
|
+
* deterministically from the final-round response texts — is rendered
|
|
1471
|
+
* before the Convergence / Divergence slot. Default `false` → byte-identical.
|
|
1472
|
+
*/
|
|
1473
|
+
stance_tally?: boolean;
|
|
1474
|
+
/**
|
|
1475
|
+
* Phase 2 (chairman): when a chairman-authored synthesis exists (persisted
|
|
1476
|
+
* in the payload), it REPLACES the template in the Convergence/Divergence
|
|
1477
|
+
* slot, prefixed by the visible annotation. Absent/null → byte-identical.
|
|
1478
|
+
*/
|
|
1479
|
+
chairman?: { member: string | null; annotation: string; text: string | null } | null;
|
|
1436
1480
|
}
|
|
1437
1481
|
|
|
1438
1482
|
/**
|
|
@@ -1505,6 +1549,25 @@ export function render(responses: CouncilResponse[], opts: RenderOptions = {}):
|
|
|
1505
1549
|
} else {
|
|
1506
1550
|
body = '*to be summarised by the host agent*';
|
|
1507
1551
|
}
|
|
1552
|
+
const chairman = opts.chairman ?? null;
|
|
1553
|
+
if (chairman !== null && chairman.text !== null && chairman.text.trim() !== '') {
|
|
1554
|
+
// Chairman-authored synthesis replaces the template; the annotation is
|
|
1555
|
+
// always visible (never a silent substitution).
|
|
1556
|
+
body = `_${chairman.annotation}_\n\n${chairman.text.trim()}`;
|
|
1557
|
+
} else if (chairman !== null) {
|
|
1558
|
+
body = `_${chairman.annotation}_\n\n${body}`;
|
|
1559
|
+
}
|
|
1560
|
+
if (opts.stance_tally === true) {
|
|
1561
|
+
// Phase 1: deterministic option-level tally from the final-round
|
|
1562
|
+
// response texts (the STANCE closing lines live IN the texts, so the
|
|
1563
|
+
// render is a pure projection — no config or transport needed here).
|
|
1564
|
+
const tally = tally_stances(
|
|
1565
|
+
responses
|
|
1566
|
+
.filter((r) => !r.error)
|
|
1567
|
+
.map((r) => ({ member: `${r.provider}:${r.model}`, text: r.text })),
|
|
1568
|
+
);
|
|
1569
|
+
blocks.push(render_vote_tally(tally));
|
|
1570
|
+
}
|
|
1508
1571
|
blocks.push(`## Convergence / Divergence\n\n${body}`);
|
|
1509
1572
|
return blocks.join('\n\n---\n\n');
|
|
1510
1573
|
}
|
|
@@ -148,6 +148,28 @@ one of several independent reviewers. Round-specific instructions:
|
|
|
148
148
|
End each round with: a one-line position summary and the single
|
|
149
149
|
piece of evidence that would change your mind.`;
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Mandatory closing-line contract appended to the FINAL round when
|
|
153
|
+
* `ai_council.stance_tally` is enabled (Phase 1). Peers backing the same option
|
|
154
|
+
* MUST reuse the same label so the deterministic tally can canonicalise it;
|
|
155
|
+
* `abstain` is allowed. Parsed by `stance_tally.parse_stance_line` — the tally
|
|
156
|
+
* never infers a stance from the surrounding prose, so an omitted or malformed
|
|
157
|
+
* line becomes a repair-marker, not a guess.
|
|
158
|
+
*/
|
|
159
|
+
export const STANCE_LINE_CONTRACT = `Close your reply with EXACTLY this line, and nothing after it:
|
|
160
|
+
STANCE: <option-label> | CONFIDENCE: high|med|low | DEALBREAKER: yes|no
|
|
161
|
+
Reuse the SAME <option-label> as any peer backing the same option (match their wording); use \`abstain\` only if you genuinely cannot choose. CONFIDENCE is your certainty in the pick; DEALBREAKER is \`yes\` only if you would block on the alternative.`;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Anti-conformity directive appended to round-2+ debate prompts when
|
|
165
|
+
* `ai_council.debate_gates` is enabled (road-to-opt-council-deliberation
|
|
166
|
+
* Phase 3). Counters the round-over-round convergence-to-consensus drift the
|
|
167
|
+
* `rounds:N` path otherwise invites. Byte-identical across the api / cli /
|
|
168
|
+
* manual transports (it becomes part of the shared `user_prompt`, which every
|
|
169
|
+
* client's `ask()` receives verbatim), so no per-transport special-casing.
|
|
170
|
+
*/
|
|
171
|
+
export const ANTI_CONFORMITY_DIRECTIVE = `Anti-conformity rule for this round: defend a position you still believe is correct. Change your position ONLY when a specific, named flaw in it has been identified — and you must name that flaw explicitly to justify the change. Do not soften or converge merely because other reviewers disagree; agreement without a named reason is conformity, not reasoning.`;
|
|
172
|
+
|
|
151
173
|
const _MODE_TABLE: Record<string, string> = {
|
|
152
174
|
prompt: PROMPT_MODE,
|
|
153
175
|
roadmap: ROADMAP_MODE,
|
|
@@ -233,8 +255,15 @@ read it directly from a response.
|
|
|
233
255
|
A single sentence: which course the host agent should advise the
|
|
234
256
|
user to take, grounded in the strongest converged point.
|
|
235
257
|
|
|
236
|
-
###
|
|
237
|
-
|
|
258
|
+
### Kill criteria
|
|
259
|
+
Observable conditions that would falsify this recommendation without
|
|
260
|
+
re-convening the council. Each entry names a threshold or a specific
|
|
261
|
+
event — never a vague "if it goes wrong". Omit only if genuinely none
|
|
262
|
+
exist, and say so explicitly.
|
|
263
|
+
|
|
264
|
+
### Concrete next step
|
|
265
|
+
Exactly one artefact-producing action the user can take in their
|
|
266
|
+
current turn.`;
|
|
238
267
|
|
|
239
268
|
export const PR_SYNTHESIS = `Summarise the council with the PR-review shape below.
|
|
240
269
|
|
|
@@ -251,7 +280,17 @@ and the host agent confirms are load-bearing. Maximum five.
|
|
|
251
280
|
|
|
252
281
|
### Recommendation
|
|
253
282
|
APPROVE / REQUEST_CHANGES / REJECT and a single sentence justifying
|
|
254
|
-
the verdict, anchored on the strongest consensus or must-fix line
|
|
283
|
+
the verdict, anchored on the strongest consensus or must-fix line.
|
|
284
|
+
|
|
285
|
+
### Kill criteria
|
|
286
|
+
Observable conditions that would falsify this verdict without re-convening
|
|
287
|
+
the council (a failing check, a missing test, a regression signal). Each
|
|
288
|
+
entry names a threshold or a specific event. Omit only if genuinely none
|
|
289
|
+
exist, and say so.
|
|
290
|
+
|
|
291
|
+
### Concrete next step
|
|
292
|
+
Exactly one artefact-producing action the author can take in their current
|
|
293
|
+
turn.`;
|
|
255
294
|
|
|
256
295
|
export const ANALYSIS_SYNTHESIS = `Summarise the council with the analysis-lens shape below.
|
|
257
296
|
|
|
@@ -270,19 +309,41 @@ metadata shape as Top-10.
|
|
|
270
309
|
### Outliers
|
|
271
310
|
Single-reviewer findings the others did not engage with. Keep them
|
|
272
311
|
— they are signal for a future deeper analysis pass — but mark each
|
|
273
|
-
as \`unverified-by-council
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
312
|
+
as \`unverified-by-council\`.
|
|
313
|
+
|
|
314
|
+
### Kill criteria
|
|
315
|
+
Observable conditions that would falsify the analysis's leading finding
|
|
316
|
+
without re-convening the council. Each entry names a threshold or event.
|
|
317
|
+
Omit only if genuinely none exist, and say so.
|
|
318
|
+
|
|
319
|
+
### Concrete next step
|
|
320
|
+
Exactly one artefact-producing action the user can take in their current
|
|
321
|
+
turn.`;
|
|
322
|
+
|
|
323
|
+
// Creative lenses — free-form prose synthesis, then the two required
|
|
324
|
+
// verdict-discipline sections. The body stays open-ended (the host agent
|
|
325
|
+
// writes the Convergence / Divergence prose on its own merits); only the
|
|
326
|
+
// falsifiable close is mandated, so every lens carries Kill criteria +
|
|
327
|
+
// Concrete next step (road-to-opt-council-deliberation Phase 0).
|
|
328
|
+
export const CREATIVE_SYNTHESIS = `Write a free-form convergence / divergence
|
|
329
|
+
synthesis of the council in prose — no fixed section shape for the body.
|
|
330
|
+
Then close with exactly these two sections:
|
|
331
|
+
|
|
332
|
+
### Kill criteria
|
|
333
|
+
Observable conditions that would falsify the direction you converged on,
|
|
334
|
+
without re-convening the council. Each entry names a threshold or event.
|
|
335
|
+
Omit only if genuinely none exist, and say so.
|
|
336
|
+
|
|
337
|
+
### Concrete next step
|
|
338
|
+
Exactly one artefact-producing action the user can take in their current
|
|
339
|
+
turn.`;
|
|
279
340
|
|
|
280
341
|
const _SYNTHESIS_TABLE: Record<string, string> = {
|
|
281
342
|
default: DEFAULT_SYNTHESIS,
|
|
282
343
|
pr: PR_SYNTHESIS,
|
|
283
344
|
analysis: ANALYSIS_SYNTHESIS,
|
|
284
|
-
design:
|
|
285
|
-
optimize:
|
|
345
|
+
design: CREATIVE_SYNTHESIS,
|
|
346
|
+
optimize: CREATIVE_SYNTHESIS,
|
|
286
347
|
};
|
|
287
348
|
|
|
288
349
|
// Input modes inherit the `default` decision template. Lens overrides
|
|
@@ -371,6 +432,55 @@ export function all_synthesis_modes(): string[] {
|
|
|
371
432
|
return Object.keys(_SYNTHESIS_TABLE).sort();
|
|
372
433
|
}
|
|
373
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Raised when a completed synthesis is missing a required verdict-discipline
|
|
437
|
+
* section (Kill criteria / Concrete next step) or carries it empty. Named so
|
|
438
|
+
* the render step fails loudly rather than emitting an unfalsifiable verdict.
|
|
439
|
+
* Mirrors the `CouncilConfigError` named-error precedent in `config.ts`.
|
|
440
|
+
*/
|
|
441
|
+
export class SynthesisRenderError extends Error {
|
|
442
|
+
constructor(message: string) {
|
|
443
|
+
super(message);
|
|
444
|
+
this.name = 'SynthesisRenderError';
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** The two sections every lens synthesis must close with (Phase 0). */
|
|
449
|
+
export const REQUIRED_SYNTHESIS_SECTIONS = ['Kill criteria', 'Concrete next step'] as const;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Validate a **completed** synthesis (the host- or chairman-authored text that
|
|
453
|
+
* fills the Convergence / Divergence slot) — not the template prompt. Every
|
|
454
|
+
* lens must close with a non-empty `### Kill criteria` and `### Concrete next
|
|
455
|
+
* step`; a missing or placeholder-empty section throws `SynthesisRenderError`
|
|
456
|
+
* naming the first offender. Called on the synthesis-emit path (the chairman
|
|
457
|
+
* synthesis in Phase 2, and any future synthesis-record step); safe to call on
|
|
458
|
+
* decision and creative lenses alike, since both now require the two sections.
|
|
459
|
+
*/
|
|
460
|
+
export function assert_synthesis_sections(text: string): void {
|
|
461
|
+
const lines = text.split('\n');
|
|
462
|
+
const isHeading = (l: string): boolean => /^#{2,4}\s/.test(l);
|
|
463
|
+
for (const section of REQUIRED_SYNTHESIS_SECTIONS) {
|
|
464
|
+
const headingRe = new RegExp(`^#{2,4}\\s+${section}\\s*$`, 'i');
|
|
465
|
+
const idx = lines.findIndex((l) => headingRe.test(l));
|
|
466
|
+
if (idx === -1) {
|
|
467
|
+
throw new SynthesisRenderError(
|
|
468
|
+
`synthesis is missing the required "### ${section}" section`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
let body = '';
|
|
472
|
+
for (let i = idx + 1; i < lines.length; i++) {
|
|
473
|
+
if (isHeading(lines[i] as string)) break;
|
|
474
|
+
body += lines[i];
|
|
475
|
+
}
|
|
476
|
+
if (body.trim().length === 0) {
|
|
477
|
+
throw new SynthesisRenderError(
|
|
478
|
+
`synthesis "### ${section}" section is empty (placeholder)`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
374
484
|
/**
|
|
375
485
|
* Drop any *whole line* containing a host-agent identity substring.
|
|
376
486
|
*
|
|
@@ -308,6 +308,23 @@ export function render_decision_replay(inputs: DecisionReplayInputs): string {
|
|
|
308
308
|
}
|
|
309
309
|
lines.push(`**Synthesis verdict**: ${verdict} consensus — ${f.source} sourced.\n`);
|
|
310
310
|
}
|
|
311
|
+
// Aggregate render lines (Phase 0 fold): one evidence-label line + one
|
|
312
|
+
// provider-spread line — folded into the trailer, not a new template
|
|
313
|
+
// section (gap-table FOLD row: evidence labels + provider spread → replay).
|
|
314
|
+
const evTally: Record<string, number> = { H: 0, M: 0, L: 0 };
|
|
315
|
+
for (const f of ranked) {
|
|
316
|
+
const em = _metaGet(inputs.metadata, f.id);
|
|
317
|
+
const q = (em?.evidence_quality ?? '').toUpperCase().charAt(0);
|
|
318
|
+
if (q === 'H' || q === 'M' || q === 'L') {
|
|
319
|
+
evTally[q] = (evTally[q] as number) + 1;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const providers = new Set(inputs.deliberation.map((r) => r.provider));
|
|
323
|
+
lines.push(
|
|
324
|
+
`- **Evidence spread**: ${evTally.H} high · ${evTally.M} medium · ` +
|
|
325
|
+
`${evTally.L} low (across ${ranked.length} finding(s))\n` +
|
|
326
|
+
`- **Provider spread**: ${providers.size} distinct provider(s) deliberated\n`,
|
|
327
|
+
);
|
|
311
328
|
const mode_label = inputs.include_member_arguments ? 'full' : 'redacted (counts only)';
|
|
312
329
|
lines.push(`---\n\n_artefact mode: ${mode_label}_\n`);
|
|
313
330
|
return _pyRStrip(lines.join('\n')) + '\n';
|