@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.
Files changed (226) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -0
  3. package/CHANGELOG.md +67 -149
  4. package/README.md +6 -6
  5. package/dist/agent-src/commands/challenge-me/vision.md +121 -32
  6. package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
  7. package/dist/agent-src/commands/commit/in-chunks.md +1 -1
  8. package/dist/agent-src/commands/commit.md +2 -1
  9. package/dist/agent-src/commands/cost/report.md +2 -1
  10. package/dist/agent-src/commands/feature/dev.md +4 -4
  11. package/dist/agent-src/commands/humanize.md +101 -0
  12. package/dist/agent-src/commands/jira-ticket.md +2 -2
  13. package/dist/agent-src/commands/memory/load.md +15 -0
  14. package/dist/agent-src/commands/optimize/project.md +12 -0
  15. package/dist/agent-src/commands/roadmap/create.md +12 -11
  16. package/dist/agent-src/commands/tests/create.md +1 -1
  17. package/dist/agent-src/commands/worktree/cleanup.md +22 -17
  18. package/dist/agent-src/commands/worktree/create.md +3 -1
  19. package/dist/agent-src/commands/worktree/status.md +12 -0
  20. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
  21. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
  22. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
  23. package/dist/agent-src/contexts/execution/roadmap-ci-steps-mechanics.md +117 -0
  24. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +111 -85
  25. package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
  26. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
  27. package/dist/agent-src/rules/architecture.md +6 -51
  28. package/dist/agent-src/rules/autonomous-execution.md +7 -37
  29. package/dist/agent-src/rules/broken-access-control.md +7 -45
  30. package/dist/agent-src/rules/code-comment-discipline.md +64 -0
  31. package/dist/agent-src/rules/context-hygiene.md +14 -0
  32. package/dist/agent-src/rules/decision-revisit-gate.md +9 -77
  33. package/dist/agent-src/rules/delegation-policy.md +10 -8
  34. package/dist/agent-src/rules/design-fidelity.md +4 -0
  35. package/dist/agent-src/rules/git-history-discipline.md +4 -79
  36. package/dist/agent-src/rules/improve-before-implement.md +7 -66
  37. package/dist/agent-src/rules/legal-safety-floor.md +11 -55
  38. package/dist/agent-src/rules/no-roadmap-references.md +13 -77
  39. package/dist/agent-src/rules/persona-governance.md +5 -47
  40. package/dist/agent-src/rules/provider-lifecycle-discipline.md +6 -32
  41. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +21 -104
  42. package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
  43. package/dist/agent-src/rules/think-before-action.md +10 -0
  44. package/dist/agent-src/rules/untrusted-input-defense.md +3 -75
  45. package/dist/agent-src/rules/verify-before-complete.md +12 -0
  46. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +96 -12
  47. package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
  48. package/dist/agent-src/skills/api-design/SKILL.md +7 -5
  49. package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
  50. package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
  51. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  52. package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
  53. package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
  54. package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
  55. package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
  56. package/dist/agent-src/skills/decision-review/SKILL.md +86 -0
  57. package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
  58. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
  59. package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
  60. package/dist/agent-src/skills/design-review/SKILL.md +23 -2
  61. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
  62. package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
  63. package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
  64. package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
  65. package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
  66. package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
  67. package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
  68. package/dist/agent-src/skills/laravel/SKILL.md +14 -1
  69. package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
  70. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
  71. package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
  72. package/dist/agent-src/skills/mcp/SKILL.md +1 -1
  73. package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
  74. package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
  75. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +58 -1
  76. package/dist/agent-src/skills/performance/SKILL.md +1 -1
  77. package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
  78. package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
  79. package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
  80. package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
  81. package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
  82. package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
  83. package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
  84. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
  85. package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
  86. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
  87. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
  88. package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
  89. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
  90. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
  91. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
  92. package/dist/agent-src/templates/agent-settings.md +3 -2
  93. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
  94. package/dist/agent-src/templates/contexts/knowledge-card.md +12 -0
  95. package/dist/agent-src/templates/roadmaps.md +14 -0
  96. package/dist/cli/commands/recordTriggerEval.js +4 -14
  97. package/dist/cli/commands/recordTriggerEval.js.map +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +157 -88
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +6 -6
  102. package/dist/discovery/orphan-report.md +1 -1
  103. package/dist/discovery/packs.json +13 -10
  104. package/dist/discovery/trust-report.md +3 -3
  105. package/dist/discovery/workspaces.json +14 -9
  106. package/dist/install/install.mjs +12 -7
  107. package/dist/mcp/registry-manifest.json +4 -4
  108. package/dist/router.json +1 -1
  109. package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
  110. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  111. package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
  112. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
  113. package/dist/scripts/condense.js +12 -2
  114. package/dist/scripts/condense.js.map +1 -1
  115. package/dist/server/schemas/settings.js +5 -2
  116. package/dist/server/schemas/settings.js.map +1 -1
  117. package/docs/CLAIMS.md +54 -5
  118. package/docs/architecture.md +4 -4
  119. package/docs/archive/CHANGELOG-pre-8.12.0.md +304 -0
  120. package/docs/benchmark.md +90 -0
  121. package/docs/catalog.md +8 -5
  122. package/docs/command-flows.md +2 -1
  123. package/docs/contracts/ai-council-config.md +97 -0
  124. package/docs/contracts/command-clusters.md +1 -0
  125. package/docs/contracts/evidence-based-pruning.md +1 -0
  126. package/docs/contracts/hook-architecture-v1.md +25 -0
  127. package/docs/contracts/kernel-membership.md +35 -0
  128. package/docs/contracts/measurement-baseline.md +1 -0
  129. package/docs/contracts/persona-schema.md +49 -0
  130. package/docs/contracts/provider-lifecycle.md +29 -4
  131. package/docs/contracts/release-sizing.md +22 -0
  132. package/docs/contracts/write-engine.md +38 -1
  133. package/docs/customization.md +12 -0
  134. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
  135. package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
  136. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
  137. package/docs/decisions/ADR-028-root-layout.md +1 -1
  138. package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
  139. package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
  140. package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
  141. package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
  142. package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
  143. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
  144. package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
  145. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
  146. package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
  147. package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
  148. package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
  149. package/docs/decisions/ADR-120-council-chairman-mode.md +70 -0
  150. package/docs/decisions/ADR-121-knowledge-sensitivity-classes.md +90 -0
  151. package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
  152. package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
  153. package/docs/decisions/INDEX.md +13 -9
  154. package/docs/design/council-vs-solo-baseline.md +92 -0
  155. package/docs/design/cross-model-parity-eval.md +82 -0
  156. package/docs/design/length-neutral-judge-rerun.md +92 -0
  157. package/docs/design/utilization-window-criteria.md +51 -0
  158. package/docs/featured-skills.md +2 -2
  159. package/docs/getting-started-by-role.md +1 -1
  160. package/docs/getting-started.md +2 -2
  161. package/docs/governance-advantage.md +1 -1
  162. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +68 -0
  163. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
  164. package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
  165. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +80 -3
  166. package/docs/guidelines/code-clarity.md +191 -1
  167. package/docs/guidelines/design-antipatterns.md +20 -0
  168. package/docs/guidelines/php/php-coding-patterns.md +12 -2
  169. package/docs/maintainers/branch-protection.md +87 -0
  170. package/docs/maintainers/system-map.md +85 -0
  171. package/docs/proof.md +32 -15
  172. package/docs/wedge/production-validator/production-validator.md +27 -0
  173. package/package.json +1 -1
  174. package/src/config/agent-settings.template.yml +38 -9
  175. package/src/scripts/_cli/cmd_doctor.ts +129 -1
  176. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
  177. package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
  178. package/src/scripts/_lib/knowledge_global.ts +50 -2
  179. package/src/scripts/_lib/knowledge_global_promote.ts +150 -0
  180. package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
  181. package/src/scripts/_lib/lexical_index.ts +15 -2
  182. package/src/scripts/_lib/orchestration_record.ts +14 -0
  183. package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
  184. package/src/scripts/ai_council/chairman.ts +90 -0
  185. package/src/scripts/ai_council/clients.ts +18 -3
  186. package/src/scripts/ai_council/config.ts +157 -0
  187. package/src/scripts/ai_council/debate_gates.ts +89 -0
  188. package/src/scripts/ai_council/orchestrator.ts +252 -2
  189. package/src/scripts/ai_council/prompts.ts +121 -11
  190. package/src/scripts/ai_council/replay.ts +17 -0
  191. package/src/scripts/ai_council/stance_tally.ts +184 -0
  192. package/src/scripts/ai_tells_rules.ts +433 -0
  193. package/src/scripts/bench_humanizer_eval.ts +240 -0
  194. package/src/scripts/bench_persona_placebo.ts +308 -0
  195. package/src/scripts/bench_quality_rerun.ts +357 -0
  196. package/src/scripts/build_discovery_manifest.ts +6 -0
  197. package/src/scripts/build_proof.ts +30 -9
  198. package/src/scripts/check_artefact_count_messaging.ts +56 -0
  199. package/src/scripts/check_claims.ts +40 -0
  200. package/src/scripts/check_knowledge_cards.ts +33 -4
  201. package/src/scripts/check_portability.ts +1 -0
  202. package/src/scripts/check_rule_invariants.ts +10 -3
  203. package/src/scripts/complexity_report.ts +776 -0
  204. package/src/scripts/condense.ts +16 -2
  205. package/src/scripts/consumer_matrix.ts +292 -14
  206. package/src/scripts/council_cli.ts +203 -1
  207. package/src/scripts/detect_ai_tells.ts +312 -0
  208. package/src/scripts/explain_run.ts +593 -0
  209. package/src/scripts/install.ts +12 -0
  210. package/src/scripts/knowledge_global_cli.ts +235 -46
  211. package/src/scripts/lint_framework_leakage.ts +88 -7
  212. package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
  213. package/src/scripts/lint_knowledge_scale.ts +30 -0
  214. package/src/scripts/lint_originality_shingles.ts +210 -0
  215. package/src/scripts/orchestration_record.ts +1 -0
  216. package/src/scripts/rule_backlinks.ts +0 -0
  217. package/src/scripts/schemas/agent-settings.schema.json +11 -0
  218. package/src/scripts/self_review_gate.ts +41 -15
  219. package/src/scripts/skill_eval_coverage.ts +71 -3
  220. package/src/scripts/skill_linter.ts +1 -0
  221. package/src/scripts/trigger_eval_rotation.ts +344 -0
  222. package/src/scripts/update_counts.ts +5 -17
  223. package/src/scripts/worktree_cleanup_check.ts +263 -0
  224. package/src/server/schemas/settings.ts +8 -3
  225. package/src/scripts/analysis_freshness.ts +0 -362
  226. package/src/scripts/export_replay_corpus.ts +0 -208
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Deterministic `parallelizable:` classifier recall + false-positive measurement
3
+ * against the orchestration corpus (road-to-orchestration-scope-decision Phase 2
4
+ * / road-to-subagent-value-realization-followup Phase 1 Step 3).
5
+ *
6
+ * The corpus tasks (`internal/bench/orchestration/corpus/*.md`) each document a
7
+ * "Classification signal" and an expected dispatch mode. This encodes those
8
+ * documented signals as `TaskSignals` and asserts `classifyTask` (the pure v1
9
+ * classifier) fires the expected mode — recall on the modes v1 covers
10
+ * (`do-in-parallel`, `do-in-steps`), FP=0 on the negative control, and the
11
+ * documented scope gap (v1 has no `do-competitively` / verdict mode).
12
+ *
13
+ * This is the durable, reproducible "record actual hit/miss counts" deliverable
14
+ * + a regression guard for the classifier. Deterministic — no spend, no agents.
15
+ */
16
+ import { describe, expect, it } from 'vitest';
17
+
18
+ import { classifyTask, type ActivationInputs, type TaskSignals } from './auto_dispatch.js';
19
+
20
+ const ON: ActivationInputs = { enabled: true, auto: 'on', subagent_spawn: true };
21
+
22
+ /** Corpus signals transcribed from each task's documented "Classification signal". */
23
+ const CORPUS: Array<{
24
+ id: string;
25
+ signals: TaskSignals;
26
+ expectMode: 'do-in-parallel' | 'do-in-steps' | null;
27
+ note: string;
28
+ }> = [
29
+ {
30
+ id: 'orch-01',
31
+ signals: { parallelizable: 'files', independent_slices: 4, size_estimate: 4, ordered_plan: false },
32
+ expectMode: 'do-in-parallel',
33
+ note: 'N independent files, same analysis shape',
34
+ },
35
+ {
36
+ id: 'orch-02',
37
+ signals: { ordered_plan: true, size_estimate: 5, independent_slices: 0 },
38
+ expectMode: 'do-in-steps',
39
+ note: 'explicit ordered numbered plan',
40
+ },
41
+ {
42
+ id: 'orch-03',
43
+ signals: { size_estimate: 3, independent_slices: 0, ordered_plan: false },
44
+ expectMode: null,
45
+ note: 'do-competitively — NOT a v1 DispatchMode (documented scope gap)',
46
+ },
47
+ {
48
+ id: 'pv-01',
49
+ signals: { size_estimate: 2, independent_slices: 0, ordered_plan: false },
50
+ expectMode: null,
51
+ note: 'verdict dispatch — out of the v1 parallel/steps scope',
52
+ },
53
+ {
54
+ id: 'pv-02',
55
+ signals: { size_estimate: 1, independent_slices: 0, ordered_plan: false },
56
+ expectMode: null,
57
+ note: 'negative control — a clean one-file task MUST NOT dispatch',
58
+ },
59
+ ];
60
+
61
+ describe('parallelizable classifier — corpus recall', () => {
62
+ for (const c of CORPUS) {
63
+ it(`${c.id}: ${c.note}`, () => {
64
+ const r = classifyTask(c.signals, ON);
65
+ expect(r.mode).toBe(c.expectMode);
66
+ });
67
+ }
68
+
69
+ it('recall on v1-covered modes is 2/2 (orch-01 parallel, orch-02 steps)', () => {
70
+ const covered = CORPUS.filter((c) => c.expectMode !== null);
71
+ const hits = covered.filter((c) => classifyTask(c.signals, ON).mode === c.expectMode);
72
+ expect(covered).toHaveLength(2);
73
+ expect(hits).toHaveLength(2);
74
+ });
75
+
76
+ it('false-positive rate is 0 — the negative control (pv-02) never dispatches', () => {
77
+ const pv02 = CORPUS.find((c) => c.id === 'pv-02')!;
78
+ expect(classifyTask(pv02.signals, ON).action).not.toBe('dispatch');
79
+ });
80
+
81
+ it('documents the v1 scope gap: do-competitively (orch-03) + verdict (pv-01) are not classified', () => {
82
+ for (const id of ['orch-03', 'pv-01']) {
83
+ const c = CORPUS.find((x) => x.id === id)!;
84
+ expect(classifyTask(c.signals, ON).mode).toBeNull();
85
+ }
86
+ });
87
+ });
@@ -108,8 +108,16 @@ export function build_claude_hook_matrix(manifest_path: string): ClaudeHookMatri
108
108
  if (!concerns || (Array.isArray(concerns) && concerns.length === 0)) continue;
109
109
  const native = ac_to_native[ac_event];
110
110
  if (native === undefined) continue;
111
+ // Hook-resilience shim (road-to-opt-subagent-harvest P1.4):
112
+ // local binary → PATH fallback → SILENT exit 0 when neither exists
113
+ // (a hook must never error-spam the loop on an uninstalled repo).
114
+ // The dispatcher's own exit code PROPAGATES when it runs — a
115
+ // fail_closed concern (e.g. block-no-verify) still denies; only the
116
+ // missing-binary case degrades open, which is the honest degrade
117
+ // (an absent dispatcher cannot evaluate anything).
111
118
  matrix[native] =
112
119
  'BIN="$CLAUDE_PROJECT_DIR/agent-config"; [ -x "$BIN" ] || BIN=agent-config; ' +
120
+ 'command -v "$BIN" >/dev/null 2>&1 || exit 0; ' +
113
121
  `"$BIN" dispatch:hook --platform claude --event ${ac_event} ` +
114
122
  `--native-event ${native} --project-dir "$CLAUDE_PROJECT_DIR" ` +
115
123
  `--min-version ${String(hook_spec)}`;
@@ -6,7 +6,9 @@
6
6
  * Python→TS migration). The public API and CLI contract mirror the Python
7
7
  * original EXACTLY — same exported snake_case names, same semantics, same
8
8
  * tier-classification, provenance footer, and config-merge behavior, same
9
- * exit codes and byte-identical stdout. No behaviour changes.
9
+ * exit codes and byte-identical stdout. No behaviour changes at port time;
10
+ * one deliberate post-port change: DEFAULT_ALLOWED_TIERS narrowed to
11
+ * ['public'] per ADR-119 (2026-07-12).
10
12
  *
11
13
  * Structure-grounding v2 (ADR-100 / road-to-structure-grounding-v2). Promotes
12
14
  * *expensive* (remote) project-local knowledge cards to a per-user, file-first
@@ -51,8 +53,33 @@ export const TIERS: readonly string[] = ['public', 'vendor', 'proprietary'];
51
53
  /**
52
54
  * Default tiers eligible to auto-cross a project boundary under default-on.
53
55
  * `proprietary` is excluded by design — manual-only regardless of `enabled`.
56
+ * Narrowed to `public` only per ADR-119 (2026-07-12, council-validated
57
+ * flip): `vendor` is a deliberate opt-in widening, not the shipped default.
54
58
  */
55
- export const DEFAULT_ALLOWED_TIERS: readonly string[] = ['public', 'vendor'];
59
+ export const DEFAULT_ALLOWED_TIERS: readonly string[] = ['public'];
60
+
61
+ /**
62
+ * Sensitivity axis (Phase 1, road-to-feedback-8.11 / successor note to
63
+ * ADR-119) — orthogonal to `TIERS` (origin) and layered on top of the
64
+ * existing tier + redaction gate. `tier` classifies WHERE a card came from;
65
+ * `sensitivity` classifies WHETHER it may ever leave the project at all.
66
+ *
67
+ * * `prohibited` — contains redaction-class content; never leaves the
68
+ * repo. Machine-derived from the redaction scan (a card is never
69
+ * hand-labelled into this class — a positive hit forces it).
70
+ * * `project` — DEFAULT. Stays project-local; promotion is refused
71
+ * unless a human explicitly reclassifies the card to `shareable`.
72
+ * * `shareable` — eligible for the global store; must pass redaction
73
+ * AND carry full promotion provenance (see the G5 footer fields).
74
+ *
75
+ * Deliberately kept to three machine-anchored classes — a wider
76
+ * team/organization taxonomy is phantom for a single-maintainer install and
77
+ * would over-fit; revisit only once a real multi-tenant consumer exists.
78
+ */
79
+ export const SENSITIVITIES: readonly string[] = ['prohibited', 'project', 'shareable'];
80
+
81
+ /** Default sensitivity for a card with no explicit `sensitivity:` field. */
82
+ export const DEFAULT_SENSITIVITY = 'project';
56
83
 
57
84
  /**
58
85
  * Hard defaults applied when the setting block is absent. Default is ON for
@@ -380,6 +407,14 @@ const _PROVENANCE_FIELDS: readonly string[] = [
380
407
  'last_verified',
381
408
  'tier',
382
409
  'seen_in',
410
+ // G5 promotion audit-trail fields (Phase 1, road-to-feedback-8.11):
411
+ // the more consistent home for these than frontmatter, since they sit
412
+ // alongside the other promotion-time facts above, not the card's own
413
+ // origin-tier/sensitivity classification (which lives in frontmatter).
414
+ 'source_repo',
415
+ 'owner',
416
+ 'review_after',
417
+ 'promotion_reason',
383
418
  ];
384
419
 
385
420
  /** Render the global-card provenance footer (the unversioned-store audit trail). */
@@ -390,6 +425,10 @@ export function render_provenance_footer(args: {
390
425
  last_verified: string;
391
426
  tier: string;
392
427
  seen_in: string[];
428
+ source_repo: string;
429
+ owner: string;
430
+ review_after: string;
431
+ promotion_reason: string;
393
432
  }): string {
394
433
  return [
395
434
  PROVENANCE_START,
@@ -399,11 +438,20 @@ export function render_provenance_footer(args: {
399
438
  `- last_verified: ${args.last_verified}`,
400
439
  `- tier: ${args.tier}`,
401
440
  `- seen_in: ${args.seen_in.join(', ')}`,
441
+ `- source_repo: ${args.source_repo}`,
442
+ `- owner: ${args.owner}`,
443
+ `- review_after: ${args.review_after}`,
444
+ `- promotion_reason: ${_singleLine(args.promotion_reason)}`,
402
445
  PROVENANCE_END,
403
446
  '',
404
447
  ].join('\n');
405
448
  }
406
449
 
450
+ /** Collapse a human-entered reason to one line — footer fields are line-based. */
451
+ function _singleLine(s: string): string {
452
+ return s.replace(/\r\n|\r|\n/g, ' ').trim();
453
+ }
454
+
407
455
  /** Extract the provenance footer fields as a flat dict (empty if absent). */
408
456
  export function parse_provenance_footer(text: string): Record<string, string> {
409
457
  if (!text.includes(PROVENANCE_START) || !text.includes(PROVENANCE_END)) {
@@ -187,6 +187,156 @@ export function should_suggest(
187
187
  return len >= options.threshold;
188
188
  }
189
189
 
190
+ // ---------------------------------------------------------------------------
191
+ // Sensitivity gate (Phase 1, road-to-feedback-8.11 — successor note to
192
+ // ADR-119). Layered ON TOP of the existing tier/redaction gate in
193
+ // `knowledge_global_redaction.ts`: a card must clear tier + redaction FIRST,
194
+ // then clear this gate before its write is allowed to proceed.
195
+ // ---------------------------------------------------------------------------
196
+
197
+ /** Outcome of the sensitivity gate for one promotion attempt. */
198
+ export interface SensitivityGateResult {
199
+ readonly eligible: boolean;
200
+ readonly reason: string;
201
+ /** The EFFECTIVE sensitivity used for the decision (post auto-derivation). */
202
+ readonly sensitivity: string;
203
+ }
204
+
205
+ /**
206
+ * Resolve the effective sensitivity used for a promotion decision.
207
+ *
208
+ * A redaction hit always forces `prohibited`, overriding any declared value —
209
+ * this is independent of the tier gate's own `halt_on_trigger` setting, so a
210
+ * `prohibited` override never depends on that unrelated config flag. An
211
+ * unset / invalid declared value defaults to `project` (never `shareable` —
212
+ * `shareable` is never auto-assigned, only explicitly declared by a human).
213
+ */
214
+ export function resolve_effective_sensitivity(declared: string, violations_present: boolean): string {
215
+ if (violations_present) {
216
+ return 'prohibited';
217
+ }
218
+ const d = (declared ?? '').trim();
219
+ return knowledge_global.SENSITIVITIES.includes(d) ? d : knowledge_global.DEFAULT_SENSITIVITY;
220
+ }
221
+
222
+ /**
223
+ * Gate a card for promotion on the sensitivity axis.
224
+ *
225
+ * Refuses anything whose effective sensitivity is not `shareable` (including
226
+ * the machine-derived `prohibited` override), and refuses a `shareable` card
227
+ * that has no human-entered `promotion_reason` — the auto-promote
228
+ * *suggestion* only ever leads to a write once a human states why the card
229
+ * is safe to share.
230
+ */
231
+ export function gate_sensitivity_for_promotion(
232
+ declared_sensitivity: string,
233
+ options: { violations_present?: boolean; promotion_reason?: string } = {},
234
+ ): SensitivityGateResult {
235
+ const violations_present = options.violations_present ?? false;
236
+ const effective = resolve_effective_sensitivity(declared_sensitivity, violations_present);
237
+
238
+ if (effective === 'prohibited') {
239
+ return {
240
+ eligible: false,
241
+ sensitivity: effective,
242
+ reason:
243
+ 'sensitivity: prohibited — redaction-class content, never leaves the repo',
244
+ };
245
+ }
246
+ if (effective !== 'shareable') {
247
+ return {
248
+ eligible: false,
249
+ sensitivity: effective,
250
+ reason:
251
+ `sensitivity '${effective}' — promotion refused unless a human explicitly ` +
252
+ "reclassifies the card to 'shareable'",
253
+ };
254
+ }
255
+ const reason = (options.promotion_reason ?? '').trim();
256
+ if (!reason) {
257
+ return {
258
+ eligible: false,
259
+ sensitivity: effective,
260
+ reason:
261
+ "sensitivity 'shareable' but no promotion_reason given — a human must state " +
262
+ 'why this card is safe to share before it may be promoted',
263
+ };
264
+ }
265
+ return { eligible: true, sensitivity: effective, reason: 'sensitivity gate passed' };
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Revocation ledger — append-only tombstone trail (Phase 1, road-to-feedback-8.11)
270
+ // ---------------------------------------------------------------------------
271
+
272
+ export const REVOCATIONS_FILENAME = '.revocations.jsonl';
273
+
274
+ /** One append-only tombstone line: what was revoked, when, and why. */
275
+ export interface RevocationEntry {
276
+ readonly revoked_at: string;
277
+ readonly card_id: string;
278
+ readonly reason: string;
279
+ }
280
+
281
+ function _revocations_path(env: knowledge_global.EnvMapType): string {
282
+ return path.join(knowledge_global.global_store_dir(env, { create: true }), REVOCATIONS_FILENAME);
283
+ }
284
+
285
+ /**
286
+ * Append one tombstone line to the revocation ledger — the caller MUST call
287
+ * this BEFORE deleting the card/usage entry it documents. Append-only (a
288
+ * single `fs.appendFileSync`, never rewritten): no `forget` / `forget --tier`
289
+ * / `purge` call ever removes a prior tombstone, so the ledger stays a
290
+ * durable audit trail across every deletion path — including `purge`, which
291
+ * deliberately spares this one file while wiping everything else.
292
+ */
293
+ export function append_tombstone(
294
+ card_id: string,
295
+ reason: string,
296
+ options: { today?: string; env?: knowledge_global.EnvMapType } = {},
297
+ ): RevocationEntry {
298
+ const entry: RevocationEntry = {
299
+ revoked_at: options.today ?? '',
300
+ card_id,
301
+ reason: reason || 'no reason given',
302
+ };
303
+ fs.appendFileSync(_revocations_path(options.env ?? null), JSON.stringify(entry) + '\n', 'utf-8');
304
+ return entry;
305
+ }
306
+
307
+ /** Read the revocation ledger. Tolerant: missing file → []; malformed lines are skipped. */
308
+ export function load_tombstones(env: knowledge_global.EnvMapType = null): RevocationEntry[] {
309
+ let text: string;
310
+ try {
311
+ text = fs.readFileSync(
312
+ path.join(knowledge_global.global_store_dir(env), REVOCATIONS_FILENAME),
313
+ 'utf-8',
314
+ );
315
+ } catch {
316
+ return [];
317
+ }
318
+ const out: RevocationEntry[] = [];
319
+ for (const raw of text.split(/\r\n|\r|\n/)) {
320
+ const line = raw.trim();
321
+ if (!line) {
322
+ continue;
323
+ }
324
+ try {
325
+ const obj: unknown = JSON.parse(line);
326
+ if (_isPlainObject(obj) && typeof obj['card_id'] === 'string') {
327
+ out.push({
328
+ revoked_at: String(obj['revoked_at'] ?? ''),
329
+ card_id: obj['card_id'],
330
+ reason: String(obj['reason'] ?? ''),
331
+ });
332
+ }
333
+ } catch {
334
+ // corrupt line — skip; never let one bad line crash the ledger read
335
+ }
336
+ }
337
+ return out;
338
+ }
339
+
190
340
  /**
191
341
  * List cards that warrant a promotion suggestion under the current config.
192
342
  *
@@ -7,7 +7,10 @@
7
7
  * the tier gate are matched byte-for-byte against the retired Python implementation. The
8
8
  * public API and CLI contract mirror Python exactly — same exported
9
9
  * snake_case names, same `GateResult` shape + `summary()` text, same exit
10
- * codes and stdout. No behaviour changes.
10
+ * codes and stdout. No behaviour changes at port time; one ADDITIVE
11
+ * hardening landed after the port (2026-07-12, ADR-103 flip validation):
12
+ * hidden-unicode detection + strip-then-rescan in `redaction_scan` — inputs
13
+ * without invisible characters are unaffected.
11
14
  *
12
15
  * Structure-grounding v2, Phase 1 — the privacy crux (ADR-100 /
13
16
  * road-to-structure-grounding-v2). Before any project-local card may cross a
@@ -121,11 +124,26 @@ function _compileIgnoreCase(pattern: string): RegExp {
121
124
  return new RegExp(pattern, 'i');
122
125
  }
123
126
 
127
+ // Invisible / control characters usable to smuggle an identifier past the
128
+ // pattern scan (zero-width class, bidi controls, soft hyphen, BOM, Unicode
129
+ // Tag block) — the encoding-smuggling attack class from the ADR-103
130
+ // council re-evaluation (2026-07-11). Presence is itself a violation:
131
+ // a knowledge card has no legitimate need for invisible characters, and
132
+ // halt-and-surface (never silent-rewrite) is this gate's contract.
133
+ const _HIDDEN_UNICODE_RE =
134
+ /[­​-‏‪-‮⁠-⁤⁦-⁩\u{E0000}-\u{E007F}]/gu;
135
+
124
136
  /**
125
137
  * Combined privacy-floor + source-confidentiality scan over `text`.
126
138
  *
127
139
  * Returns the list of violations (empty = clean). Reused by the Phase-4
128
140
  * linter as the CI net that redaction actually fired on committed cards.
141
+ *
142
+ * Hidden-unicode hardening (additive, 2026-07-12): invisible characters are
143
+ * flagged as their own violation class AND the pattern scan runs on the
144
+ * stripped text, so a zero-width-broken key/email is still caught by its
145
+ * own category. Inputs without invisible characters behave byte-identically
146
+ * to the pre-hardening scan.
129
147
  */
130
148
  export function redaction_scan(
131
149
  text: string,
@@ -136,16 +154,36 @@ export function redaction_scan(
136
154
  sql_identifiers?: readonly string[];
137
155
  } = {},
138
156
  ): RedactionViolation[] {
139
- const floor = redact_low_impact_entry(text, {
157
+ const violations: RedactionViolation[] = [];
158
+ _HIDDEN_UNICODE_RE.lastIndex = 0;
159
+ const hidden = text.match(_HIDDEN_UNICODE_RE);
160
+ let scanText = text;
161
+ if (hidden !== null && hidden.length > 0) {
162
+ const counts = new Map<string, number>();
163
+ for (const ch of hidden) {
164
+ const cp = `U+${(ch.codePointAt(0) as number).toString(16).toUpperCase().padStart(4, '0')}`;
165
+ counts.set(cp, (counts.get(cp) ?? 0) + 1);
166
+ }
167
+ const summary = [...counts.entries()]
168
+ .map(([cp, n]) => (n > 1 ? `${cp}×${n}` : cp))
169
+ .join(' ');
170
+ violations.push({
171
+ category: 'hidden_unicode',
172
+ snippet: _pySliceHead(summary, 40),
173
+ note: 'invisible characters — possible identifier smuggling; pattern scan ran on the stripped text',
174
+ });
175
+ scanText = text.replace(_HIDDEN_UNICODE_RE, '');
176
+ }
177
+ const floor = redact_low_impact_entry(scanText, {
140
178
  repoRoot: options.repo_root ?? null,
141
179
  privateDomains: options.private_domains ?? [],
142
180
  customerNames: options.customer_names ?? [],
143
181
  sqlIdentifiers: options.sql_identifiers ?? [],
144
182
  });
145
- const violations: RedactionViolation[] = [...floor.violations];
183
+ violations.push(...floor.violations);
146
184
  for (const rx of _load_denylist_patterns()) {
147
185
  rx.lastIndex = 0;
148
- const m = rx.exec(text);
186
+ const m = rx.exec(scanText);
149
187
  if (m) {
150
188
  violations.push({
151
189
  category: 'external_source',
@@ -90,11 +90,20 @@ export class LexicalIndex {
90
90
  return Math.max(0, Math.log(1 + (this.n - df + 0.5) / (df + 0.5)));
91
91
  }
92
92
 
93
- /** BM25 score of one document against the tokenised query terms. */
93
+ /**
94
+ * BM25 score of one document against the tokenised query terms, weighted by
95
+ * **term coverage**: the raw BM25 sum is multiplied by `coverage²`, where
96
+ * `coverage = distinct-query-terms-matched / distinct-query-terms`. This
97
+ * stops a single generic, high-TF term match from outranking a document that
98
+ * matches several rarer query terms once each (Phase 1 refinement). A
99
+ * single-term query has `coverage == 1`, so its score is unchanged — the
100
+ * weighting only re-orders genuinely multi-term queries.
101
+ */
94
102
  score(queryTerms: readonly string[], docId: string): number {
95
103
  const doc = this.docs.get(docId);
96
104
  if (doc === undefined || doc.len === 0) return 0;
97
105
  let s = 0;
106
+ const matched = new Set<string>();
98
107
  for (const term of queryTerms) {
99
108
  const tf = doc.tf.get(term);
100
109
  if (tf === undefined) continue;
@@ -102,8 +111,12 @@ export class LexicalIndex {
102
111
  if (idf === 0) continue;
103
112
  const denom = tf + BM25_K1 * (1 - BM25_B + (BM25_B * doc.len) / (this.avgdl || 1));
104
113
  s += (idf * (tf * (BM25_K1 + 1))) / denom;
114
+ matched.add(term);
105
115
  }
106
- return s;
116
+ if (s === 0) return 0;
117
+ const distinctQuery = new Set(queryTerms).size;
118
+ const coverage = distinctQuery > 0 ? matched.size / distinctQuery : 1;
119
+ return s * coverage * coverage;
107
120
  }
108
121
 
109
122
  /**
@@ -24,6 +24,17 @@ export type TierSource = 'static' | 'inferred' | 'inherit';
24
24
  export type Band = 'low' | 'medium' | 'high';
25
25
  export type LinePhase = 'refine' | 'memory' | 'analyze' | 'plan' | 'implement' | 'test' | 'verify' | 'report';
26
26
  export type LineOutcome = 'success' | 'blocked' | 'skipped' | 'error';
27
+ /** The orchestration form the form-gate selected (road-to-opt-subagent-harvest P2). */
28
+ export type DispatchModeId =
29
+ | 'do-and-judge'
30
+ | 'do-and-judge-two-stage'
31
+ | 'do-in-steps'
32
+ | 'do-in-parallel'
33
+ | 'do-competitively'
34
+ | 'judge-with-debate'
35
+ | 'do-in-worktrees'
36
+ | 'do-with-live-app-judge'
37
+ | 'none';
27
38
 
28
39
  /** Inputs the agent supplies about one auto-dispatch. */
29
40
  export interface RecordInput {
@@ -37,6 +48,8 @@ export interface RecordInput {
37
48
  tier_chosen?: TierChosen | null | undefined;
38
49
  tier_source?: TierSource | null | undefined;
39
50
  task_class?: string | null | undefined;
51
+ /** Form-gate outcome: which of the seven modes (or 'none') the gate selected. */
52
+ dispatch_mode?: DispatchModeId | null | undefined;
40
53
  task_size_estimate?: number | undefined;
41
54
  wall_clock_ms?: number | undefined;
42
55
  /** Absolute measured tokens the dispatched slice consumed (feeds the modeled cost-%). */
@@ -153,6 +166,7 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
153
166
  outcome: dOutcome,
154
167
  verify_mode: vMode,
155
168
  task_class: input.task_class ?? null,
169
+ dispatch_mode: input.dispatch_mode ?? null,
156
170
  tier_chosen: input.tier_chosen ?? null,
157
171
  tier_source: input.tier_source ?? null,
158
172
  dispatch_tokens: input.dispatch_tokens ?? null,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared trigger-eval precision/recall floors — single source of truth.
3
+ *
4
+ * Extracted from `src/cli/commands/recordTriggerEval.ts` so the weekly
5
+ * canary rotation (`src/scripts/trigger_eval_rotation.ts`, ADR-118 §4) and
6
+ * the `eval:record` provenance recorder enforce the SAME per-domain floors.
7
+ * Values are unchanged from the recorder's originals (Phase D council
8
+ * verdict — tuned to the trigger pattern each skill shows, not a single
9
+ * global pair): a reference task wants near-perfect precision; a judgment
10
+ * task tolerates a looser one.
11
+ */
12
+
13
+ export interface TriggerEvalFloor {
14
+ minRecall: number;
15
+ minPrecision: number;
16
+ }
17
+
18
+ /** Universal fallback floor when a skill carries no domain-specific entry. */
19
+ export const DEFAULT_FLOOR: TriggerEvalFloor = { minRecall: 1.0, minPrecision: 0.8 };
20
+
21
+ /** Domain-specific floors; skills absent here fall back to {@link DEFAULT_FLOOR}. */
22
+ export const DOMAIN_FLOORS: Readonly<Record<string, TriggerEvalFloor>> = {
23
+ 'image-generation': { minRecall: 1.0, minPrecision: 0.85 },
24
+ iconography: { minRecall: 1.0, minPrecision: 0.9 },
25
+ 'brand-strategy': { minRecall: 0.9, minPrecision: 0.7 },
26
+ };
27
+
28
+ /** Resolve the effective floor for a skill (domain entry, else default). */
29
+ export function floor_for(skill: string | null | undefined): TriggerEvalFloor {
30
+ return (skill !== null && skill !== undefined && DOMAIN_FLOORS[skill]) || DEFAULT_FLOOR;
31
+ }
@@ -0,0 +1,90 @@
1
+ // Chairman selection (road-to-opt-council-deliberation Phase 2).
2
+ //
3
+ // The council's Iron Law argues the host, having framed the artefact, cannot
4
+ // independently judge it — so synthesis by a *non-deliberating* member removes
5
+ // that bias. This module is the pure SELECTION decision (who chairs, and the
6
+ // visible verdict annotation); the billable dispatch of the chairman's synthesis
7
+ // call lives in the CLI run path, gated behind the same config.
8
+ //
9
+ // `auto` policy — decided by the contested-design council pass (anthropic/
10
+ // claude-sonnet-4-5 + openai/gpt-4o, 2-round debate, 2026-07-12):
11
+ // **provider-family difference is primary** — independence from the
12
+ // deliberators' priors is the binding constraint for a judge, and provider
13
+ // diversity is the one structural guarantee available without an LLM call.
14
+ // The **tie-break is the capability signal**: an explicit optional
15
+ // `members.<name>.tier` when present (higher = stronger), else deterministic
16
+ // config order. In this engine each member IS one provider (the
17
+ // one-advisor-per-provider invariant), so every enabled member that did not
18
+ // deliberate is provider-different from every deliberator by construction.
19
+
20
+ export interface ChairmanCandidate {
21
+ /** Member name (== provider key in this engine). */
22
+ name: string;
23
+ /** Optional capability rank from `members.<name>.tier`; higher = stronger. */
24
+ tier: number | null;
25
+ }
26
+
27
+ export interface ChairmanSelection {
28
+ /** The chosen chairman member name, or `null` → host synthesis (today's path). */
29
+ member: string | null;
30
+ /** Visible annotation for the synthesis verdict — never a silent substitution. */
31
+ annotation: string;
32
+ }
33
+
34
+ /**
35
+ * Pure chairman selection.
36
+ *
37
+ * @param mode one of `host` | `member` | `auto` (validated at config load)
38
+ * @param member the configured member name when `mode === 'member'`
39
+ * @param deliberated member names that produced a real (non-error, non-empty) response this session
40
+ * @param candidates enabled members in **config order** (the engine's only trusted ordering)
41
+ */
42
+ export function select_chairman(
43
+ mode: string,
44
+ member: string | null,
45
+ deliberated: ReadonlySet<string>,
46
+ candidates: readonly ChairmanCandidate[],
47
+ ): ChairmanSelection {
48
+ if (mode === 'host') {
49
+ return { member: null, annotation: 'Chairman: host' };
50
+ }
51
+ const enabledNames = new Set(candidates.map((c) => c.name));
52
+ if (mode === 'member') {
53
+ if (member === null || !enabledNames.has(member)) {
54
+ return {
55
+ member: null,
56
+ annotation: `Chairman: host (member ${_q(member)} unavailable — host fallback)`,
57
+ };
58
+ }
59
+ if (deliberated.has(member)) {
60
+ // A member that argued in the debate cannot independently judge it.
61
+ return {
62
+ member: null,
63
+ annotation: `Chairman: host (member ${_q(member)} deliberated — cannot self-judge, host fallback)`,
64
+ };
65
+ }
66
+ return { member, annotation: `Chairman: ${member}` };
67
+ }
68
+ // mode === 'auto' — provider-family difference primary (non-deliberating ⇒
69
+ // provider-different by construction here); tier tie-break; config-order final.
70
+ const pool = candidates.filter((c) => !deliberated.has(c.name));
71
+ if (pool.length === 0) {
72
+ return {
73
+ member: null,
74
+ annotation: 'Chairman: host (no non-panel member available)',
75
+ };
76
+ }
77
+ let best = pool[0] as ChairmanCandidate;
78
+ for (const c of pool.slice(1)) {
79
+ const bestTier = best.tier ?? Number.NEGATIVE_INFINITY;
80
+ const cTier = c.tier ?? Number.NEGATIVE_INFINITY;
81
+ if (cTier > bestTier) {
82
+ best = c; // strictly higher tier wins; equal/lower keeps config order
83
+ }
84
+ }
85
+ return { member: best.name, annotation: `Chairman: ${best.name} (auto)` };
86
+ }
87
+
88
+ function _q(v: string | null): string {
89
+ return v === null ? 'null' : `'${v}'`;
90
+ }
@@ -71,6 +71,21 @@ export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
71
71
  export const DEFAULT_XAI_MODEL = 'grok-4';
72
72
  export const DEFAULT_PERPLEXITY_MODEL = 'sonar-pro';
73
73
 
74
+ // Vendor CLI (subscription transport) default models. Omitting `model` in a
75
+ // CLI client's options pins THESE values — a pin, not "latest"; the vendor
76
+ // CLI's own default may be newer. Bump deliberately; never assume drift.
77
+ //
78
+ // These are SEPARATE from the API-transport `DEFAULT_*_MODEL` constants above
79
+ // because API and CLI defaults can legitimately diverge — openai proves it
80
+ // (API `gpt-4o` vs CLI `gpt-5`). The anthropic/gemini CLI values currently
81
+ // coincide with their API defaults, but stay pinned independently so a future
82
+ // divergence is a one-line change, not a hunt through inline literals.
83
+ // (`xai` / `perplexity` CLI reuse their API constants: their CLIs are
84
+ // community wrappers around the same paid API, so the values do not diverge.)
85
+ export const DEFAULT_OPENAI_CLI_MODEL = 'gpt-5';
86
+ export const DEFAULT_ANTHROPIC_CLI_MODEL = 'claude-sonnet-4-5';
87
+ export const DEFAULT_GEMINI_CLI_MODEL = 'gemini-2.5-pro';
88
+
74
89
  // OpenAI-API-compatible endpoints. xAI and Perplexity both expose the
75
90
  // `/v1/chat/completions` shape, so their clients reuse the `openai` SDK with a
76
91
  // custom `base_url`. Gemini has its own SDK (`google-genai`).
@@ -1171,7 +1186,7 @@ export class AnthropicCliClient extends CliClient {
1171
1186
 
1172
1187
  constructor(opts: CliClientOptions = {}) {
1173
1188
  super({
1174
- model: opts.model ?? 'claude-sonnet-4-5',
1189
+ model: opts.model ?? DEFAULT_ANTHROPIC_CLI_MODEL,
1175
1190
  binary: opts.binary,
1176
1191
  timeout_seconds: opts.timeout_seconds,
1177
1192
  max_calls_per_day: opts.max_calls_per_day,
@@ -1271,7 +1286,7 @@ export class OpenAICliClient extends CliClient {
1271
1286
 
1272
1287
  constructor(opts: CliClientOptions = {}) {
1273
1288
  super({
1274
- model: opts.model ?? 'gpt-5',
1289
+ model: opts.model ?? DEFAULT_OPENAI_CLI_MODEL,
1275
1290
  binary: opts.binary,
1276
1291
  timeout_seconds: opts.timeout_seconds,
1277
1292
  max_calls_per_day: opts.max_calls_per_day,
@@ -1394,7 +1409,7 @@ export class GeminiCliClient extends CliClient {
1394
1409
 
1395
1410
  constructor(opts: CliClientOptions = {}) {
1396
1411
  super({
1397
- model: opts.model ?? 'gemini-2.5-pro',
1412
+ model: opts.model ?? DEFAULT_GEMINI_CLI_MODEL,
1398
1413
  binary: opts.binary,
1399
1414
  timeout_seconds: opts.timeout_seconds,
1400
1415
  max_calls_per_day: opts.max_calls_per_day,