@event4u/agent-config 8.10.0 → 8.12.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/AGENTS.md +1 -0
- package/CHANGELOG.md +67 -149
- package/README.md +6 -6
- 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 +2 -1
- 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 +1 -1
- package/dist/agent-src/commands/worktree/cleanup.md +22 -17
- package/dist/agent-src/commands/worktree/create.md +3 -1
- package/dist/agent-src/commands/worktree/status.md +12 -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 +47 -3
- package/dist/agent-src/contexts/execution/roadmap-ci-steps-mechanics.md +117 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +111 -85
- 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/architecture.md +6 -51
- 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 +64 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +9 -77
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +4 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/improve-before-implement.md +7 -66
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/no-roadmap-references.md +13 -77
- package/dist/agent-src/rules/persona-governance.md +5 -47
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +6 -32
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +21 -104
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -75
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +96 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
- 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/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/decision-review/SKILL.md +86 -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-review/SKILL.md +23 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -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/laravel/SKILL.md +14 -1
- package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
- 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/mcp/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +58 -1
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -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/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- 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/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/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -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/contexts/knowledge-card.md +12 -0
- 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 +157 -88
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +6 -6
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -10
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +14 -9
- 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 +54 -5
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-8.12.0.md +304 -0
- package/docs/benchmark.md +90 -0
- package/docs/catalog.md +8 -5
- package/docs/command-flows.md +2 -1
- package/docs/contracts/ai-council-config.md +97 -0
- package/docs/contracts/command-clusters.md +1 -0
- 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/persona-schema.md +49 -0
- package/docs/contracts/provider-lifecycle.md +29 -4
- package/docs/contracts/release-sizing.md +22 -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-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-120-council-chairman-mode.md +70 -0
- package/docs/decisions/ADR-121-knowledge-sensitivity-classes.md +90 -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 +13 -9
- package/docs/design/council-vs-solo-baseline.md +92 -0
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/design/utilization-window-criteria.md +51 -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/agent-interaction-and-decision-quality.md +68 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +80 -3
- package/docs/guidelines/code-clarity.md +191 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/maintainers/branch-protection.md +87 -0
- package/docs/maintainers/system-map.md +85 -0
- package/docs/proof.md +32 -15
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +38 -9
- package/src/scripts/_cli/cmd_doctor.ts +129 -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 +50 -2
- package/src/scripts/_lib/knowledge_global_promote.ts +150 -0
- 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 +14 -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 +252 -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/bench_persona_placebo.ts +308 -0
- package/src/scripts/bench_quality_rerun.ts +357 -0
- package/src/scripts/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +30 -9
- package/src/scripts/check_artefact_count_messaging.ts +56 -0
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_knowledge_cards.ts +33 -4
- package/src/scripts/check_portability.ts +1 -0
- package/src/scripts/check_rule_invariants.ts +10 -3
- package/src/scripts/complexity_report.ts +776 -0
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +292 -14
- package/src/scripts/council_cli.ts +203 -1
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/explain_run.ts +593 -0
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +235 -46
- 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 +1 -0
- package/src/scripts/rule_backlinks.ts +0 -0
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/self_review_gate.ts +41 -15
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- 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
|
@@ -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';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Option-level stance tally (road-to-opt-council-deliberation Phase 1).
|
|
2
|
+
//
|
|
3
|
+
// `consensus.ts` scores *findings*; nothing here produces an *option-level*
|
|
4
|
+
// verdict for "A or B?" questions. This module engineers Source G's prose
|
|
5
|
+
// tally deterministically: parse each member's mandatory final-round stance
|
|
6
|
+
// line, weight by confidence, and decide consensus vs. an honest split against
|
|
7
|
+
// a ⅔ threshold. Everything countable lands in TS with tests — never inferred
|
|
8
|
+
// from prose (an unparseable line is a repair-marker, not a guess).
|
|
9
|
+
//
|
|
10
|
+
// Default-off behind `ai_council.stance_tally.enabled`; additive — with the key
|
|
11
|
+
// false the council path is byte-identical to today.
|
|
12
|
+
|
|
13
|
+
/** Confidence tier from a stance line. */
|
|
14
|
+
export type Confidence = 'high' | 'med' | 'low';
|
|
15
|
+
|
|
16
|
+
/** Contribution weight per confidence tier (Source G's factors). */
|
|
17
|
+
export const CONFIDENCE_FACTOR: Record<Confidence, number> = {
|
|
18
|
+
high: 1.0,
|
|
19
|
+
med: 0.75,
|
|
20
|
+
low: 0.5,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Consensus requires an option to clear two-thirds of the base-weight total. */
|
|
24
|
+
export const CONSENSUS_FRACTION = 2 / 3;
|
|
25
|
+
|
|
26
|
+
/** The canonical label for an explicit abstention. */
|
|
27
|
+
export const ABSTAIN_LABEL = 'abstain';
|
|
28
|
+
|
|
29
|
+
/** A parsed stance line bound to its member. */
|
|
30
|
+
export interface StanceLine {
|
|
31
|
+
member: string; // e.g. "openai:gpt-4o"
|
|
32
|
+
label: string; // canonical (casefolded) key; `abstain` is special
|
|
33
|
+
display: string; // first-seen original label text (rendering)
|
|
34
|
+
confidence: Confidence;
|
|
35
|
+
dealbreaker: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Per-option accumulation across members. */
|
|
39
|
+
export interface OptionTally {
|
|
40
|
+
label: string; // display label (first-seen casing)
|
|
41
|
+
weight: number; // sum of confidence factors of backers
|
|
42
|
+
backers: Array<{ member: string; confidence: Confidence }>;
|
|
43
|
+
dealbreakers: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The full tally verdict. `consensus` is null on a split — never a forced winner. */
|
|
47
|
+
export interface StanceTallyResult {
|
|
48
|
+
options: OptionTally[]; // non-abstain options, weight desc
|
|
49
|
+
w_total: number; // base-weight denominator (every voting member, abstain included)
|
|
50
|
+
threshold: number; // CONSENSUS_FRACTION * w_total
|
|
51
|
+
consensus: OptionTally | null; // the option clearing the threshold, or null
|
|
52
|
+
split: boolean; // true when no option clears the threshold
|
|
53
|
+
abstain_count: number;
|
|
54
|
+
needs_repair: string[]; // members whose stance line was missing/unparseable
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Tolerant of whitespace and case; requires the three explicit fields in order.
|
|
58
|
+
// `medium` is accepted as an alias for `med`.
|
|
59
|
+
const _STANCE_RE =
|
|
60
|
+
/STANCE:\s*(.+?)\s*\|\s*CONFIDENCE:\s*(high|med(?:ium)?|low)\s*\|\s*DEALBREAKER:\s*(yes|no)/gi;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse the member's stance line from their reply text. Returns the LAST stance
|
|
64
|
+
* line if several appear (the final, most-authoritative one), or `null` when no
|
|
65
|
+
* well-formed line exists — the caller treats null as a repair-marker and never
|
|
66
|
+
* infers a stance from the surrounding prose.
|
|
67
|
+
*/
|
|
68
|
+
export function parse_stance_line(text: string): Omit<StanceLine, 'member'> | null {
|
|
69
|
+
_STANCE_RE.lastIndex = 0;
|
|
70
|
+
let last: RegExpExecArray | null = null;
|
|
71
|
+
let m: RegExpExecArray | null;
|
|
72
|
+
while ((m = _STANCE_RE.exec(text)) !== null) {
|
|
73
|
+
last = m;
|
|
74
|
+
}
|
|
75
|
+
if (last === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const rawLabel = (last[1] ?? '').trim();
|
|
79
|
+
if (rawLabel.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let conf = (last[2] ?? '').toLowerCase();
|
|
83
|
+
if (conf === 'medium') {
|
|
84
|
+
conf = 'med';
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
label: rawLabel.toLowerCase(),
|
|
88
|
+
display: rawLabel,
|
|
89
|
+
confidence: conf as Confidence,
|
|
90
|
+
dealbreaker: (last[3] ?? '').toLowerCase() === 'yes',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Tally option-level stances across members. Base weight is 1.0 per member;
|
|
96
|
+
* `W_total` sums those base weights over every member with a parseable stance
|
|
97
|
+
* (abstentions included — they raise the bar without backing any option). An
|
|
98
|
+
* option's weight is the sum of its backers' confidence factors. Consensus iff
|
|
99
|
+
* the leading option's weight ≥ ⅔ × W_total; otherwise a structured split is
|
|
100
|
+
* returned — never a forced winner, never an auto-added round.
|
|
101
|
+
*/
|
|
102
|
+
export function tally_stances(
|
|
103
|
+
members: ReadonlyArray<{ member: string; text: string }>,
|
|
104
|
+
): StanceTallyResult {
|
|
105
|
+
const parsed: StanceLine[] = [];
|
|
106
|
+
const needs_repair: string[] = [];
|
|
107
|
+
for (const { member, text } of members) {
|
|
108
|
+
const p = parse_stance_line(text);
|
|
109
|
+
if (p === null) {
|
|
110
|
+
needs_repair.push(member);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
parsed.push({ member, ...p });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const w_total = parsed.length; // base weight 1.0 each, abstain included
|
|
117
|
+
const threshold = CONSENSUS_FRACTION * w_total;
|
|
118
|
+
let abstain_count = 0;
|
|
119
|
+
|
|
120
|
+
const byLabel = new Map<string, OptionTally>();
|
|
121
|
+
for (const p of parsed) {
|
|
122
|
+
if (p.label === ABSTAIN_LABEL) {
|
|
123
|
+
abstain_count += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
let o = byLabel.get(p.label);
|
|
127
|
+
if (o === undefined) {
|
|
128
|
+
o = { label: p.display, weight: 0, backers: [], dealbreakers: 0 };
|
|
129
|
+
byLabel.set(p.label, o);
|
|
130
|
+
}
|
|
131
|
+
o.weight += CONFIDENCE_FACTOR[p.confidence];
|
|
132
|
+
o.backers.push({ member: p.member, confidence: p.confidence });
|
|
133
|
+
if (p.dealbreaker) {
|
|
134
|
+
o.dealbreakers += 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const options = [...byLabel.values()].sort(
|
|
139
|
+
(a, b) => b.weight - a.weight || a.label.localeCompare(b.label),
|
|
140
|
+
);
|
|
141
|
+
const top = options[0] ?? null;
|
|
142
|
+
// Strict-≥ against the threshold; a floating-point epsilon guards the
|
|
143
|
+
// exact-boundary case (e.g. 2.0 ≥ ⅔×3) from representation drift.
|
|
144
|
+
const consensus = top !== null && top.weight + 1e-9 >= threshold ? top : null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
options,
|
|
148
|
+
w_total,
|
|
149
|
+
threshold,
|
|
150
|
+
consensus,
|
|
151
|
+
split: consensus === null,
|
|
152
|
+
abstain_count,
|
|
153
|
+
needs_repair,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Render the **Vote Tally** verdict section: one line per option, the threshold,
|
|
159
|
+
* and a cleared-or-escalated line. Deterministic — pure projection of the tally.
|
|
160
|
+
*/
|
|
161
|
+
export function render_vote_tally(result: StanceTallyResult): string {
|
|
162
|
+
const lines: string[] = ['### Vote Tally'];
|
|
163
|
+
if (result.options.length === 0) {
|
|
164
|
+
lines.push('- (no option-level stances parsed)');
|
|
165
|
+
}
|
|
166
|
+
for (const o of result.options) {
|
|
167
|
+
const backers = o.backers.map((b) => `${b.member} (${b.confidence})`).join(', ');
|
|
168
|
+
const db = o.dealbreakers > 0 ? ` · ${o.dealbreakers} dealbreaker(s)` : '';
|
|
169
|
+
lines.push(`- ${o.label} — ${o.weight.toFixed(2)} (${backers})${db}`);
|
|
170
|
+
}
|
|
171
|
+
if (result.abstain_count > 0) {
|
|
172
|
+
lines.push(`- abstain — ${result.abstain_count} member(s) (raises the bar)`);
|
|
173
|
+
}
|
|
174
|
+
lines.push(`Threshold: ⅔ × ${result.w_total} = ${result.threshold.toFixed(2)}`);
|
|
175
|
+
lines.push(
|
|
176
|
+
result.consensus !== null
|
|
177
|
+
? `Cleared: ${result.consensus.label} (${result.consensus.weight.toFixed(2)})`
|
|
178
|
+
: 'Escalated: no option cleared the threshold — the split is returned to the user, not forced.',
|
|
179
|
+
);
|
|
180
|
+
if (result.needs_repair.length > 0) {
|
|
181
|
+
lines.push(`Unparsed stance (repair needed): ${result.needs_repair.join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|