@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
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Consumer-proof release matrix — road-to-proof-under-real-conditions Phase 1.
|
|
4
|
+
*
|
|
5
|
+
* Exercises the PUBLISHED TARBALL the way a consumer does: pack → fresh
|
|
6
|
+
* global install (isolated prefix) → `init` into a fresh project → doctor →
|
|
7
|
+
* conformance → MCP stdio handshake → hook health → projection presence →
|
|
8
|
+
* uninstall. Optional upgrade leg: install the last published minor from the
|
|
9
|
+
* registry, then upgrade to the packed tarball and assert doctor stays green.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists: the release-PR gate skips source-level matrices (soundly —
|
|
12
|
+
* a version-bump diff cannot regress source behaviour), but nothing between
|
|
13
|
+
* merge and tag exercised the tarball shape. Two published minors shipped
|
|
14
|
+
* without `src/install/` while published code imported from it; `tsx` was
|
|
15
|
+
* missing from the published package; each manifested only at publish time.
|
|
16
|
+
* Every leg here asserts exit code + an expected artifact, no LLM calls.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* npx tsx src/scripts/consumer_matrix.ts [--tarball <path>] [--only leg1,leg2]
|
|
20
|
+
* [--skip-registry] [--keep-tmp] [--json]
|
|
21
|
+
*
|
|
22
|
+
* Without --tarball the script runs `npm pack` itself (requires a built
|
|
23
|
+
* tree — `npm ci && npm run build` first; CI does this, local carve-out
|
|
24
|
+
* runs do it once).
|
|
25
|
+
*
|
|
26
|
+
* Exit codes: 0 all requested legs green · 1 leg failure · 2 usage/env.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawnSync, spawn } from 'node:child_process';
|
|
30
|
+
import * as fs from 'node:fs';
|
|
31
|
+
import * as os from 'node:os';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
|
|
35
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
36
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
37
|
+
const PKG_NAME = '@event4u/agent-config';
|
|
38
|
+
|
|
39
|
+
interface Ctx {
|
|
40
|
+
tmpRoot: string;
|
|
41
|
+
prefix: string; // isolated npm global prefix
|
|
42
|
+
projectDir: string; // fresh consumer project
|
|
43
|
+
tarball: string;
|
|
44
|
+
bin: string; // installed agent-config binary
|
|
45
|
+
keepTmp: boolean;
|
|
46
|
+
skipRegistry: boolean;
|
|
47
|
+
results: LegResult[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface LegResult {
|
|
51
|
+
leg: string;
|
|
52
|
+
ok: boolean;
|
|
53
|
+
detail: string;
|
|
54
|
+
durationMs: number;
|
|
55
|
+
/** fn ran to completion (a declared skip or an after-failure skip is NOT executed). */
|
|
56
|
+
executed: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function log(msg: string): void {
|
|
60
|
+
process.stderr.write(`${msg}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function run(
|
|
64
|
+
cmd: string,
|
|
65
|
+
args: string[],
|
|
66
|
+
opts: { cwd?: string; env?: NodeJS.ProcessEnv; timeoutMs?: number } = {},
|
|
67
|
+
): { status: number; stdout: string; stderr: string } {
|
|
68
|
+
const r = spawnSync(cmd, args, {
|
|
69
|
+
cwd: opts.cwd ?? REPO_ROOT,
|
|
70
|
+
env: { ...process.env, ...opts.env },
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
timeout: opts.timeoutMs ?? 600_000,
|
|
73
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
74
|
+
});
|
|
75
|
+
return { status: r.status ?? 1, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function fail(msg: string): never {
|
|
79
|
+
log(`❌ consumer-matrix: ${msg}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Env for running the installed binary against the isolated prefix.
|
|
85
|
+
* HOME is redirected into the tmp root so the simulation is a FRESH
|
|
86
|
+
* MACHINE even on a maintainer host: without this, an existing global
|
|
87
|
+
* install (~/.event4u, ~/.claude, …) trips the cross-scope drift guard
|
|
88
|
+
* inside `init` and the leg fails on developer machines while passing
|
|
89
|
+
* on clean CI runners — the opposite of what a consumer gate is for.
|
|
90
|
+
*/
|
|
91
|
+
function binEnv(ctx: Ctx): NodeJS.ProcessEnv {
|
|
92
|
+
const home = path.join(ctx.tmpRoot, 'home');
|
|
93
|
+
fs.mkdirSync(home, { recursive: true });
|
|
94
|
+
return {
|
|
95
|
+
PATH: `${path.join(ctx.prefix, 'bin')}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
96
|
+
npm_config_prefix: ctx.prefix,
|
|
97
|
+
npm_config_cache: path.join(ctx.tmpRoot, 'npm-cache'),
|
|
98
|
+
HOME: home,
|
|
99
|
+
// Never let a maintainer checkout leak into the consumer simulation.
|
|
100
|
+
AGENT_CONFIG_DEV_MODE: '',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── legs ────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
function legPack(ctx: Ctx): string {
|
|
107
|
+
if (ctx.tarball) {
|
|
108
|
+
if (!fs.existsSync(ctx.tarball)) fail(`--tarball not found: ${ctx.tarball}`);
|
|
109
|
+
return `reused ${path.basename(ctx.tarball)}`;
|
|
110
|
+
}
|
|
111
|
+
const r = run('npm', ['pack', '--ignore-scripts', '--silent', '--pack-destination', ctx.tmpRoot]);
|
|
112
|
+
if (r.status !== 0) throw new Error(`npm pack failed: ${r.stderr.slice(-400)}`);
|
|
113
|
+
const name = r.stdout.trim().split('\n').pop() ?? '';
|
|
114
|
+
ctx.tarball = path.join(ctx.tmpRoot, name);
|
|
115
|
+
if (!fs.existsSync(ctx.tarball)) throw new Error(`packed tarball missing: ${ctx.tarball}`);
|
|
116
|
+
return path.basename(ctx.tarball);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function legFreshInstall(ctx: Ctx): string {
|
|
120
|
+
const r = run('npm', ['install', '-g', '--prefix', ctx.prefix, ctx.tarball], { timeoutMs: 900_000 });
|
|
121
|
+
if (r.status !== 0) throw new Error(`global install failed: ${r.stderr.slice(-600)}`);
|
|
122
|
+
ctx.bin = path.join(ctx.prefix, 'bin', 'agent-config');
|
|
123
|
+
if (!fs.existsSync(ctx.bin)) throw new Error(`installed binary missing at ${ctx.bin}`);
|
|
124
|
+
const v = run(ctx.bin, ['--version'], { env: binEnv(ctx) });
|
|
125
|
+
if (v.status !== 0) throw new Error(`--version failed (missing runtime dep in tarball?): ${v.stderr.slice(-400)}`);
|
|
126
|
+
return `bin ok, version ${v.stdout.trim()}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function legInit(ctx: Ctx): string {
|
|
130
|
+
fs.mkdirSync(ctx.projectDir, { recursive: true });
|
|
131
|
+
run('git', ['init', '-q', '.'], { cwd: ctx.projectDir });
|
|
132
|
+
// --scope=auto is the working headless consumer invocation. The bare
|
|
133
|
+
// default ('project default for backward compatibility') hard-fails on
|
|
134
|
+
// a fresh machine with '--scope=project is reserved for maintainers'
|
|
135
|
+
// (ADR-020) — a real trap this matrix documents; see
|
|
136
|
+
// docs/distribution/consumer-matrix.md § Headless default-scope trap.
|
|
137
|
+
const r = run(ctx.bin, ['init', '--tools=claude-code,cursor,windsurf', '--scope=auto', '--yes'], {
|
|
138
|
+
cwd: ctx.projectDir,
|
|
139
|
+
env: binEnv(ctx),
|
|
140
|
+
timeoutMs: 600_000,
|
|
141
|
+
});
|
|
142
|
+
if (r.status !== 0) throw new Error(`init failed: ${(r.stderr + r.stdout).slice(-600)}`);
|
|
143
|
+
// ADR-020: consumer installs are GLOBAL-only — assert the global trees.
|
|
144
|
+
const home = path.join(ctx.tmpRoot, 'home');
|
|
145
|
+
for (const p of ['.claude', '.cursor', '.codeium/windsurf', '.event4u']) {
|
|
146
|
+
if (!fs.existsSync(path.join(home, p))) throw new Error(`init did not write ~/${p}`);
|
|
147
|
+
}
|
|
148
|
+
return 'init --scope=auto wrote global ~/.claude, ~/.cursor, ~/.codeium/windsurf, ~/.event4u';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function legConformance(ctx: Ctx): string {
|
|
152
|
+
const r = run(ctx.bin, ['conformance'], { cwd: ctx.projectDir, env: binEnv(ctx) });
|
|
153
|
+
if (r.status !== 0) throw new Error(`conformance exit ${r.status}: ${(r.stdout + r.stderr).slice(-600)}`);
|
|
154
|
+
return 'conformance green (embeds doctor --ci + installed-and-firing checks)';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function legMcpHandshake(ctx: Ctx): Promise<string> {
|
|
158
|
+
// Newline-delimited JSON-RPC per src/cli/mcp/stdio.ts.
|
|
159
|
+
return await new Promise<string>((resolve, reject) => {
|
|
160
|
+
const child = spawn(ctx.bin, ['mcp-server'], {
|
|
161
|
+
cwd: ctx.projectDir,
|
|
162
|
+
env: { ...process.env, ...binEnv(ctx) },
|
|
163
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
164
|
+
});
|
|
165
|
+
let out = '';
|
|
166
|
+
const timer = setTimeout(() => {
|
|
167
|
+
child.kill();
|
|
168
|
+
reject(new Error(`no initialize response within 20s; stdout so far: ${out.slice(0, 300)}`));
|
|
169
|
+
}, 20_000);
|
|
170
|
+
child.stdout.on('data', (d: Buffer) => {
|
|
171
|
+
out += d.toString();
|
|
172
|
+
const line = out.split('\n').find((l) => l.trim().startsWith('{'));
|
|
173
|
+
if (line) {
|
|
174
|
+
try {
|
|
175
|
+
const msg = JSON.parse(line) as { id?: number; result?: unknown; error?: unknown };
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
child.kill();
|
|
178
|
+
if (msg.id === 1 && msg.result && !msg.error) {
|
|
179
|
+
resolve('initialize handshake ok');
|
|
180
|
+
} else {
|
|
181
|
+
reject(new Error(`unexpected first message: ${line.slice(0, 300)}`));
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
/* partial line — keep buffering */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
child.on('error', (e) => {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
reject(e);
|
|
191
|
+
});
|
|
192
|
+
child.on('exit', (code) => {
|
|
193
|
+
if (out === '') {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
reject(new Error(`mcp-server exited (${code}) before responding`));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
child.stdin.write(
|
|
199
|
+
`${JSON.stringify({
|
|
200
|
+
jsonrpc: '2.0',
|
|
201
|
+
id: 1,
|
|
202
|
+
method: 'initialize',
|
|
203
|
+
params: {
|
|
204
|
+
protocolVersion: '2025-03-26',
|
|
205
|
+
capabilities: {},
|
|
206
|
+
clientInfo: { name: 'consumer-matrix', version: '0' },
|
|
207
|
+
},
|
|
208
|
+
})}\n`,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function legHooks(ctx: Ctx): string {
|
|
214
|
+
const r = run(ctx.bin, ['hooks:doctor'], { cwd: ctx.projectDir, env: binEnv(ctx) });
|
|
215
|
+
if (r.status !== 0) throw new Error(`hooks:doctor exit ${r.status}: ${(r.stdout + r.stderr).slice(-600)}`);
|
|
216
|
+
return 'hooks:doctor green';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function legProjections(ctx: Ctx): string {
|
|
220
|
+
const home = path.join(ctx.tmpRoot, 'home');
|
|
221
|
+
const missing = ['.cursor/rules', '.codeium/windsurf', '.claude/skills']
|
|
222
|
+
.filter((p) => !fs.existsSync(path.join(home, p)))
|
|
223
|
+
.join(', ');
|
|
224
|
+
if (missing) throw new Error(`global projection trees missing: ${missing}`);
|
|
225
|
+
return 'cursor + windsurf + claude projection trees present in global scope';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function legUninstall(ctx: Ctx): string {
|
|
229
|
+
const r = run(ctx.bin, ['uninstall', '--global', '--tools=claude-code,cursor,windsurf', '--force'], {
|
|
230
|
+
cwd: ctx.projectDir,
|
|
231
|
+
env: binEnv(ctx),
|
|
232
|
+
});
|
|
233
|
+
if (r.status !== 0) throw new Error(`uninstall exit ${r.status}: ${(r.stdout + r.stderr).slice(-600)}`);
|
|
234
|
+
const lock = path.join(ctx.tmpRoot, 'home', '.event4u', 'agent-config', 'installed.lock');
|
|
235
|
+
if (fs.existsSync(lock)) throw new Error(`global lockfile still present after uninstall: ${lock}`);
|
|
236
|
+
return 'uninstall --global green, lockfile removed';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function legUpgrade(ctx: Ctx): string {
|
|
240
|
+
if (ctx.skipRegistry) return 'skipped (--skip-registry)';
|
|
241
|
+
// Last published version from the registry (network).
|
|
242
|
+
const view = run('npm', ['view', PKG_NAME, 'version'], { timeoutMs: 120_000 });
|
|
243
|
+
if (view.status !== 0) throw new Error(`npm view failed: ${view.stderr.slice(-300)}`);
|
|
244
|
+
const published = view.stdout.trim();
|
|
245
|
+
const upPrefix = path.join(ctx.tmpRoot, 'prefix-upgrade');
|
|
246
|
+
const upProject = path.join(ctx.tmpRoot, 'project-upgrade');
|
|
247
|
+
fs.mkdirSync(upProject, { recursive: true });
|
|
248
|
+
const upHome = path.join(ctx.tmpRoot, 'home-upgrade');
|
|
249
|
+
fs.mkdirSync(upHome, { recursive: true });
|
|
250
|
+
const env = {
|
|
251
|
+
PATH: `${path.join(upPrefix, 'bin')}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
252
|
+
npm_config_prefix: upPrefix,
|
|
253
|
+
npm_config_cache: path.join(ctx.tmpRoot, 'npm-cache'),
|
|
254
|
+
HOME: upHome,
|
|
255
|
+
AGENT_CONFIG_DEV_MODE: '',
|
|
256
|
+
};
|
|
257
|
+
let r = run('npm', ['install', '-g', '--prefix', upPrefix, `${PKG_NAME}@${published}`], { timeoutMs: 900_000 });
|
|
258
|
+
if (r.status !== 0) throw new Error(`registry install of ${published} failed: ${r.stderr.slice(-400)}`);
|
|
259
|
+
const bin = path.join(upPrefix, 'bin', 'agent-config');
|
|
260
|
+
r = run(bin, ['init', '--tools=claude-code', '--scope=auto', '--yes'], { cwd: upProject, env, timeoutMs: 600_000 });
|
|
261
|
+
if (r.status !== 0) throw new Error(`init on ${published} failed: ${(r.stderr + r.stdout).slice(-400)}`);
|
|
262
|
+
// Upgrade: overwrite the global install with the packed tarball.
|
|
263
|
+
r = run('npm', ['install', '-g', '--prefix', upPrefix, ctx.tarball], { timeoutMs: 900_000 });
|
|
264
|
+
if (r.status !== 0) throw new Error(`tarball upgrade failed: ${r.stderr.slice(-400)}`);
|
|
265
|
+
r = run(bin, ['conformance'], { cwd: upProject, env });
|
|
266
|
+
if (r.status !== 0) {
|
|
267
|
+
throw new Error(`conformance red after ${published} → tarball upgrade: ${(r.stdout + r.stderr).slice(-600)}`);
|
|
268
|
+
}
|
|
269
|
+
return `upgrade ${published} → packed tarball, conformance green`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── main ────────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
const LEGS: Array<{ name: string; fn: (ctx: Ctx) => string | Promise<string> }> = [
|
|
275
|
+
{ name: 'pack', fn: legPack },
|
|
276
|
+
{ name: 'fresh-install', fn: legFreshInstall },
|
|
277
|
+
{ name: 'init', fn: legInit },
|
|
278
|
+
{ name: 'conformance', fn: legConformance },
|
|
279
|
+
{ name: 'mcp-handshake', fn: legMcpHandshake },
|
|
280
|
+
{ name: 'hooks', fn: legHooks },
|
|
281
|
+
{ name: 'projections', fn: legProjections },
|
|
282
|
+
{ name: 'uninstall', fn: legUninstall },
|
|
283
|
+
{ name: 'upgrade', fn: legUpgrade },
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
async function main(): Promise<number> {
|
|
287
|
+
const argv = process.argv.slice(2);
|
|
288
|
+
const getOpt = (flag: string): string | undefined => {
|
|
289
|
+
const i = argv.indexOf(flag);
|
|
290
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
291
|
+
};
|
|
292
|
+
const only = getOpt('--only')?.split(',').map((s) => s.trim());
|
|
293
|
+
const unknownLeg = only?.find((l) => !LEGS.some((x) => x.name === l));
|
|
294
|
+
if (unknownLeg) {
|
|
295
|
+
log(`❌ unknown leg '${unknownLeg}'. Legs: ${LEGS.map((l) => l.name).join(', ')}`);
|
|
296
|
+
return 2;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'consumer-matrix-'));
|
|
300
|
+
const ctx: Ctx = {
|
|
301
|
+
tmpRoot,
|
|
302
|
+
prefix: path.join(tmpRoot, 'prefix'),
|
|
303
|
+
projectDir: path.join(tmpRoot, 'project'),
|
|
304
|
+
tarball: getOpt('--tarball') ?? '',
|
|
305
|
+
bin: '',
|
|
306
|
+
keepTmp: argv.includes('--keep-tmp'),
|
|
307
|
+
skipRegistry: argv.includes('--skip-registry'),
|
|
308
|
+
results: [],
|
|
309
|
+
};
|
|
310
|
+
fs.mkdirSync(ctx.prefix, { recursive: true });
|
|
311
|
+
log(`consumer-matrix · tmp ${tmpRoot} · node ${process.version}`);
|
|
312
|
+
|
|
313
|
+
let failed = false;
|
|
314
|
+
for (const leg of LEGS) {
|
|
315
|
+
if (only && !only.includes(leg.name)) continue;
|
|
316
|
+
// Legs after a hard dependency failure cannot run meaningfully.
|
|
317
|
+
if (failed) {
|
|
318
|
+
ctx.results.push({
|
|
319
|
+
leg: leg.name,
|
|
320
|
+
ok: false,
|
|
321
|
+
detail: 'skipped (earlier leg failed)',
|
|
322
|
+
durationMs: 0,
|
|
323
|
+
executed: false,
|
|
324
|
+
});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const t0 = Date.now();
|
|
328
|
+
try {
|
|
329
|
+
const detail = await leg.fn(ctx);
|
|
330
|
+
const executed = !detail.startsWith('skipped');
|
|
331
|
+
ctx.results.push({ leg: leg.name, ok: true, detail, durationMs: Date.now() - t0, executed });
|
|
332
|
+
log(` ✅ ${leg.name} — ${detail}`);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
ctx.results.push({
|
|
335
|
+
leg: leg.name,
|
|
336
|
+
ok: false,
|
|
337
|
+
detail: (err as Error).message,
|
|
338
|
+
durationMs: Date.now() - t0,
|
|
339
|
+
executed: true,
|
|
340
|
+
});
|
|
341
|
+
log(` ❌ ${leg.name} — ${(err as Error).message}`);
|
|
342
|
+
failed = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Leg-completeness gate (road-to-fable-feedback-5 Phase 6a): a full run
|
|
347
|
+
// (no --only) must have EXECUTED every expected leg — a silently-skipped
|
|
348
|
+
// leg can no longer ride a green workflow. The only declared skip is
|
|
349
|
+
// `upgrade` under an explicit --skip-registry flag.
|
|
350
|
+
if (!only && !failed) {
|
|
351
|
+
const declaredSkips = new Set(ctx.skipRegistry ? ['upgrade'] : []);
|
|
352
|
+
const missing = LEGS.filter((l) => {
|
|
353
|
+
const r = ctx.results.find((x) => x.leg === l.name);
|
|
354
|
+
return r === undefined || (!r.executed && !declaredSkips.has(l.name));
|
|
355
|
+
}).map((l) => l.name);
|
|
356
|
+
if (missing.length > 0) {
|
|
357
|
+
log(`❌ leg-completeness gate: expected leg(s) did not execute: ${missing.join(', ')}`);
|
|
358
|
+
failed = true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const manifestPath = getOpt('--manifest');
|
|
363
|
+
if (manifestPath) {
|
|
364
|
+
const manifest = {
|
|
365
|
+
ok: !failed,
|
|
366
|
+
node: process.version,
|
|
367
|
+
skipRegistry: ctx.skipRegistry,
|
|
368
|
+
expected: LEGS.map((l) => l.name),
|
|
369
|
+
results: ctx.results,
|
|
370
|
+
};
|
|
371
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
372
|
+
log(`leg manifest written: ${manifestPath}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (argv.includes('--json')) {
|
|
376
|
+
process.stdout.write(`${JSON.stringify({ ok: !failed, results: ctx.results }, null, 2)}\n`);
|
|
377
|
+
}
|
|
378
|
+
if (!ctx.keepTmp) {
|
|
379
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
380
|
+
} else {
|
|
381
|
+
log(`kept tmp: ${tmpRoot}`);
|
|
382
|
+
}
|
|
383
|
+
return failed ? 1 : 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
main().then(
|
|
387
|
+
(code) => process.exit(code),
|
|
388
|
+
(err) => {
|
|
389
|
+
log(`❌ consumer-matrix crashed: ${(err as Error).stack ?? err}`);
|
|
390
|
+
process.exit(2);
|
|
391
|
+
},
|
|
392
|
+
);
|
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
bundle_prompt,
|
|
25
25
|
bundle_roadmap,
|
|
26
26
|
} from './ai_council/bundler.js';
|
|
27
|
+
import { select_chairman } from './ai_council/chairman.js';
|
|
28
|
+
import type { ChairmanCandidate } from './ai_council/chairman.js';
|
|
29
|
+
import { synthesis_template } from './ai_council/prompts.js';
|
|
27
30
|
import type {
|
|
28
31
|
ExternalAIClient} from './ai_council/clients.js';
|
|
29
32
|
import {
|
|
@@ -873,6 +876,8 @@ function format_estimate_table(
|
|
|
873
876
|
consensus_extra_calls?: number;
|
|
874
877
|
peer_review_delta_usd?: number;
|
|
875
878
|
peer_review_extra_calls?: number;
|
|
879
|
+
chairman_delta_usd?: number;
|
|
880
|
+
chairman_extra_calls?: number;
|
|
876
881
|
} = {},
|
|
877
882
|
): string {
|
|
878
883
|
const consensus_delta_usd = opts.consensus_delta_usd ?? 0.0;
|
|
@@ -904,6 +909,12 @@ function format_estimate_table(
|
|
|
904
909
|
);
|
|
905
910
|
total += peer_review_delta_usd;
|
|
906
911
|
}
|
|
912
|
+
if ((opts.chairman_extra_calls ?? 0) > 0) {
|
|
913
|
+
rows.push(
|
|
914
|
+
` +chairman synthesis: +${opts.chairman_extra_calls} call (~+$${_pyFixed(opts.chairman_delta_usd ?? 0, 4)})`,
|
|
915
|
+
);
|
|
916
|
+
total += opts.chairman_delta_usd ?? 0;
|
|
917
|
+
}
|
|
907
918
|
rows.push(` TOTAL: $${_pyFixed(total, 4)}`);
|
|
908
919
|
return rows.join('\n');
|
|
909
920
|
}
|
|
@@ -1054,6 +1065,21 @@ function _peer_review_active(ai_cfg: Dict, args: Args): boolean {
|
|
|
1054
1065
|
return Boolean(_pyBool(pr_cfg['enabled']));
|
|
1055
1066
|
}
|
|
1056
1067
|
|
|
1068
|
+
/** Chairman synthesis delta — one extra call when chairman.mode != host. */
|
|
1069
|
+
function _chairman_cost_delta(
|
|
1070
|
+
ai_cfg: Dict,
|
|
1071
|
+
estimates: CostEstimateLike[],
|
|
1072
|
+
): [number, number] {
|
|
1073
|
+
const ch = (_isDict(ai_cfg) ? (ai_cfg['chairman'] as Dict) : null) || {};
|
|
1074
|
+
const mode = typeof ch['mode'] === 'string' ? (ch['mode'] as string) : 'host';
|
|
1075
|
+
if (mode === 'host' || estimates.length === 0) {
|
|
1076
|
+
return [0, 0.0];
|
|
1077
|
+
}
|
|
1078
|
+
// Worst-case single call: the most expensive member estimate.
|
|
1079
|
+
const maxUsd = Math.max(...estimates.map((e) => _total_usd(e)));
|
|
1080
|
+
return [1, maxUsd];
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1057
1083
|
function _peer_review_cost_delta(
|
|
1058
1084
|
ai_cfg: Dict,
|
|
1059
1085
|
args: Args,
|
|
@@ -1071,6 +1097,94 @@ function _peer_review_cost_delta(
|
|
|
1071
1097
|
return [extra_calls, extra_usd];
|
|
1072
1098
|
}
|
|
1073
1099
|
|
|
1100
|
+
|
|
1101
|
+
interface ChairmanResult {
|
|
1102
|
+
member: string | null;
|
|
1103
|
+
annotation: string;
|
|
1104
|
+
text: string | null;
|
|
1105
|
+
response: CouncilResponse | null;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/** Phase 2 chairman dispatch — optional extra billable pass after consult.
|
|
1109
|
+
* Gated on `ai_council.chairman.mode != host`; selection per the 2026-07-12
|
|
1110
|
+
* council decision (provider-difference primary, tier tie-break) via
|
|
1111
|
+
* `select_chairman`; the synthesis call reuses consult([client]) so the
|
|
1112
|
+
* projected-spend gate, ledger, and metadata stamping apply unchanged.
|
|
1113
|
+
* Never a silent substitution: fallbacks carry a visible annotation. */
|
|
1114
|
+
function _maybe_run_chairman(
|
|
1115
|
+
ai_cfg: Dict,
|
|
1116
|
+
question: CouncilQuestion,
|
|
1117
|
+
members: ExternalAIClient[],
|
|
1118
|
+
responses: CouncilResponse[],
|
|
1119
|
+
budget: CostBudget,
|
|
1120
|
+
table: PriceTable,
|
|
1121
|
+
project: unknown,
|
|
1122
|
+
args: Args,
|
|
1123
|
+
): ChairmanResult | null {
|
|
1124
|
+
const ch = (_isDict(ai_cfg) ? (ai_cfg['chairman'] as Dict) : null) || {};
|
|
1125
|
+
const mode = typeof ch['mode'] === 'string' ? (ch['mode'] as string) : 'host';
|
|
1126
|
+
if (mode === 'host') {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
const cfg_member = typeof ch['member'] === 'string' ? (ch['member'] as string) : null;
|
|
1130
|
+
const membersRaw = (_isDict(ai_cfg) ? (ai_cfg['members'] as Dict) : null) || {};
|
|
1131
|
+
const candidates: ChairmanCandidate[] = [];
|
|
1132
|
+
for (const [name, raw] of Object.entries(membersRaw)) {
|
|
1133
|
+
const md = (raw as Dict) || {};
|
|
1134
|
+
if (md['enabled'] !== true) continue;
|
|
1135
|
+
candidates.push({
|
|
1136
|
+
name,
|
|
1137
|
+
tier: typeof md['tier'] === 'number' ? (md['tier'] as number) : null,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
const deliberated = new Set(
|
|
1141
|
+
responses.filter((r) => r.error === null && r.text.trim() !== '').map((r) => r.provider),
|
|
1142
|
+
);
|
|
1143
|
+
const sel = select_chairman(mode, cfg_member, deliberated, candidates);
|
|
1144
|
+
if (sel.member === null) {
|
|
1145
|
+
return { member: null, annotation: sel.annotation, text: null, response: null };
|
|
1146
|
+
}
|
|
1147
|
+
const client = members.find((m) => m.name === sel.member);
|
|
1148
|
+
if (client === undefined) {
|
|
1149
|
+
return {
|
|
1150
|
+
member: null,
|
|
1151
|
+
annotation: `Chairman: host (member '${sel.member}' has no constructed client — host fallback)`,
|
|
1152
|
+
text: null,
|
|
1153
|
+
response: null,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
// Transcript WITH identities (the chairman judges attributed positions) +
|
|
1157
|
+
// the lens synthesis template as the authoring instruction.
|
|
1158
|
+
const transcript = responses
|
|
1159
|
+
.filter((r) => r.error === null && r.text.trim() !== '')
|
|
1160
|
+
.map((r) => `## ${r.provider} - ${r.model}\n\n${r.text.trim()}`)
|
|
1161
|
+
.join('\n\n---\n\n');
|
|
1162
|
+
const synth_prompt =
|
|
1163
|
+
`You are the council CHAIRMAN. You did not deliberate. Author the synthesis ` +
|
|
1164
|
+
`of the attributed member positions below, following the template.\n\n` +
|
|
1165
|
+
`${synthesis_template(question.mode)}\n\n---\n\n${transcript}`;
|
|
1166
|
+
const synthQ = new CouncilQuestion({
|
|
1167
|
+
mode: question.mode,
|
|
1168
|
+
user_prompt: synth_prompt,
|
|
1169
|
+
max_tokens: question.max_tokens,
|
|
1170
|
+
});
|
|
1171
|
+
const out = consult([client], synthQ, budget, {
|
|
1172
|
+
table,
|
|
1173
|
+
project: project as never,
|
|
1174
|
+
original_ask: args.original_ask,
|
|
1175
|
+
});
|
|
1176
|
+
const r = out[0] ?? null;
|
|
1177
|
+
if (r === null || r.error !== null || r.text.trim() === '') {
|
|
1178
|
+
return {
|
|
1179
|
+
member: null,
|
|
1180
|
+
annotation: `Chairman: ${sel.member} (FAILED - host fallback)`,
|
|
1181
|
+
text: null,
|
|
1182
|
+
response: r,
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return { member: sel.member, annotation: sel.annotation, text: r.text, response: r };
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1074
1188
|
function _maybe_run_peer_review(
|
|
1075
1189
|
ai_cfg: Dict,
|
|
1076
1190
|
args: Args,
|
|
@@ -1204,6 +1318,7 @@ function cmd_estimate(
|
|
|
1204
1318
|
return _emit_debate_estimate(args, ai_cfg, members, billable, estimates, advisor_plans, { skipped });
|
|
1205
1319
|
}
|
|
1206
1320
|
const [extra_calls, extra_usd] = _consensus_cost_delta(ai_cfg, question.mode, estimates, billable.length);
|
|
1321
|
+
const [ch_calls, ch_usd] = _chairman_cost_delta(ai_cfg, estimates);
|
|
1207
1322
|
const [pr_extra_calls, pr_extra_usd] = _peer_review_cost_delta(ai_cfg, args, estimates, billable.length);
|
|
1208
1323
|
_stdout(
|
|
1209
1324
|
`council:estimate · mode=${question.mode} · members=${members.length} ` +
|
|
@@ -1222,6 +1337,8 @@ function cmd_estimate(
|
|
|
1222
1337
|
consensus_extra_calls: extra_calls,
|
|
1223
1338
|
peer_review_delta_usd: pr_extra_usd,
|
|
1224
1339
|
peer_review_extra_calls: pr_extra_calls,
|
|
1340
|
+
chairman_delta_usd: ch_usd,
|
|
1341
|
+
chairman_extra_calls: ch_calls,
|
|
1225
1342
|
}) + '\n',
|
|
1226
1343
|
);
|
|
1227
1344
|
return 0;
|
|
@@ -1720,6 +1837,7 @@ function cmd_run(
|
|
|
1720
1837
|
advisor_plans,
|
|
1721
1838
|
});
|
|
1722
1839
|
const [extra_calls, extra_usd] = _consensus_cost_delta(ai_cfg, question.mode, estimates, billable.length);
|
|
1840
|
+
const [ch_calls, ch_usd] = _chairman_cost_delta(ai_cfg, estimates);
|
|
1723
1841
|
const [pr_extra_calls, pr_extra_usd] = _peer_review_cost_delta(ai_cfg, args, estimates, billable.length);
|
|
1724
1842
|
_stdout(
|
|
1725
1843
|
`council:run · mode=${question.mode} · members=${members.length} ` +
|
|
@@ -1738,6 +1856,8 @@ function cmd_run(
|
|
|
1738
1856
|
consensus_extra_calls: extra_calls,
|
|
1739
1857
|
peer_review_delta_usd: pr_extra_usd,
|
|
1740
1858
|
peer_review_extra_calls: pr_extra_calls,
|
|
1859
|
+
chairman_delta_usd: ch_usd,
|
|
1860
|
+
chairman_extra_calls: ch_calls,
|
|
1741
1861
|
}) + '\n',
|
|
1742
1862
|
);
|
|
1743
1863
|
|
|
@@ -1781,12 +1901,18 @@ function cmd_run(
|
|
|
1781
1901
|
max_total_usd: _pyFloat(cost_cfg['max_total_usd'] ?? 0.0, 0.0) || 0.0,
|
|
1782
1902
|
});
|
|
1783
1903
|
const rounds = _resolve_rounds(args, ai_cfg);
|
|
1904
|
+
// Phase 1: stance tally — defensive read; malformed/absent block reads as off.
|
|
1905
|
+
const stance_tally_on =
|
|
1906
|
+
_isDict(ai_cfg) &&
|
|
1907
|
+
_isDict(ai_cfg['stance_tally']) &&
|
|
1908
|
+
(ai_cfg['stance_tally'] as Dict)['enabled'] === true;
|
|
1784
1909
|
const responses = consult(members, question, budget, {
|
|
1785
1910
|
table,
|
|
1786
1911
|
project,
|
|
1787
1912
|
original_ask: args.original_ask,
|
|
1788
1913
|
rounds,
|
|
1789
1914
|
advisor_plans,
|
|
1915
|
+
stance_tally: stance_tally_on,
|
|
1790
1916
|
});
|
|
1791
1917
|
const persona_labels = build_persona_labels(advisor_plans, billable);
|
|
1792
1918
|
const peer_review = _maybe_run_peer_review(
|
|
@@ -1801,6 +1927,7 @@ function cmd_run(
|
|
|
1801
1927
|
{ persona_labels },
|
|
1802
1928
|
);
|
|
1803
1929
|
const consensus = _maybe_run_consensus(ai_cfg, question, members, responses, budget, table, project, args);
|
|
1930
|
+
const chairman = _maybe_run_chairman(ai_cfg, question, members, responses, budget, table, project, args);
|
|
1804
1931
|
const estimated_total = estimates.reduce((acc, e) => acc + _total_usd(e), 0.0);
|
|
1805
1932
|
let actual_total = 0.0;
|
|
1806
1933
|
const all_responses: CouncilResponse[] = [...responses];
|
|
@@ -1811,6 +1938,9 @@ function cmd_run(
|
|
|
1811
1938
|
all_responses.push(...consensus.extraction_responses);
|
|
1812
1939
|
all_responses.push(...consensus.scoring_responses);
|
|
1813
1940
|
}
|
|
1941
|
+
if (chairman !== null && chairman.response !== null) {
|
|
1942
|
+
all_responses.push(chairman.response);
|
|
1943
|
+
}
|
|
1814
1944
|
for (const r of all_responses) {
|
|
1815
1945
|
if (r.error) {
|
|
1816
1946
|
continue;
|
|
@@ -1830,6 +1960,7 @@ function cmd_run(
|
|
|
1830
1960
|
rounds,
|
|
1831
1961
|
cost_usd_estimated: _pyRound(estimated_total, 6),
|
|
1832
1962
|
cost_usd_actual: _pyRound(actual_total, 6),
|
|
1963
|
+
stance_tally: stance_tally_on,
|
|
1833
1964
|
responses: _serialise_responses(responses),
|
|
1834
1965
|
};
|
|
1835
1966
|
if (peer_review !== null) {
|
|
@@ -1838,6 +1969,9 @@ function cmd_run(
|
|
|
1838
1969
|
if (consensus !== null) {
|
|
1839
1970
|
payload['consensus'] = _serialise_consensus(consensus);
|
|
1840
1971
|
}
|
|
1972
|
+
if (chairman !== null) {
|
|
1973
|
+
payload['chairman'] = { member: chairman.member, annotation: chairman.annotation, text: chairman.text };
|
|
1974
|
+
}
|
|
1841
1975
|
const out_path = _validate_council_output_path(args.output as string, {
|
|
1842
1976
|
kind: 'responses',
|
|
1843
1977
|
subcommand: 'run',
|
|
@@ -2129,6 +2263,14 @@ function cmd_debate(
|
|
|
2129
2263
|
auto_continue: Boolean(_getattr(args, 'auto_continue', false)),
|
|
2130
2264
|
});
|
|
2131
2265
|
|
|
2266
|
+
// Phase 3: debate gates (anti-conformity directive on round 2+). Read
|
|
2267
|
+
// defensively from the raw ai_council block — a malformed/absent
|
|
2268
|
+
// `debate_gates` reads as off, keeping the debate prompt byte-identical.
|
|
2269
|
+
const debate_gates_on =
|
|
2270
|
+
_isDict(ai_cfg) &&
|
|
2271
|
+
_isDict(ai_cfg['debate_gates']) &&
|
|
2272
|
+
(ai_cfg['debate_gates'] as Dict)['enabled'] === true;
|
|
2273
|
+
|
|
2132
2274
|
let all_rounds: CouncilResponse[][];
|
|
2133
2275
|
try {
|
|
2134
2276
|
all_rounds = run_debate(members, question, {
|
|
@@ -2139,6 +2281,7 @@ function cmd_debate(
|
|
|
2139
2281
|
max_rounds: rounds,
|
|
2140
2282
|
on_round_complete: _on_round_complete,
|
|
2141
2283
|
on_continue,
|
|
2284
|
+
debate_gates: debate_gates_on,
|
|
2142
2285
|
advisor_plans,
|
|
2143
2286
|
seed_round_1: seed,
|
|
2144
2287
|
});
|
|
@@ -2190,6 +2333,10 @@ function cmd_render(args: Args): number {
|
|
|
2190
2333
|
prose_synthesis: prose,
|
|
2191
2334
|
consensus,
|
|
2192
2335
|
peer_review,
|
|
2336
|
+
stance_tally: payload['stance_tally'] === true,
|
|
2337
|
+
chairman: _isDict(payload['chairman'])
|
|
2338
|
+
? (payload['chairman'] as { member: string | null; annotation: string; text: string | null })
|
|
2339
|
+
: null,
|
|
2193
2340
|
});
|
|
2194
2341
|
if (_getattr<string | null>(args, 'output', null)) {
|
|
2195
2342
|
const out_path = _validate_council_output_path(args.output as string, {
|