@event4u/agent-config 8.10.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.
Files changed (192) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +148 -0
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/challenge-me/vision.md +121 -32
  5. package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
  6. package/dist/agent-src/commands/commit/in-chunks.md +1 -1
  7. package/dist/agent-src/commands/commit.md +2 -1
  8. package/dist/agent-src/commands/cost/report.md +2 -1
  9. package/dist/agent-src/commands/feature/dev.md +4 -4
  10. package/dist/agent-src/commands/humanize.md +101 -0
  11. package/dist/agent-src/commands/jira-ticket.md +2 -2
  12. package/dist/agent-src/commands/memory/load.md +15 -0
  13. package/dist/agent-src/commands/optimize/project.md +12 -0
  14. package/dist/agent-src/commands/roadmap/create.md +12 -11
  15. package/dist/agent-src/commands/tests/create.md +1 -1
  16. package/dist/agent-src/commands/worktree/cleanup.md +22 -17
  17. package/dist/agent-src/commands/worktree/create.md +3 -1
  18. package/dist/agent-src/commands/worktree/status.md +12 -0
  19. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
  20. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
  21. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
  22. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
  23. package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
  24. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
  25. package/dist/agent-src/rules/autonomous-execution.md +7 -37
  26. package/dist/agent-src/rules/broken-access-control.md +7 -45
  27. package/dist/agent-src/rules/code-comment-discipline.md +148 -0
  28. package/dist/agent-src/rules/context-hygiene.md +14 -0
  29. package/dist/agent-src/rules/delegation-policy.md +10 -8
  30. package/dist/agent-src/rules/design-fidelity.md +4 -0
  31. package/dist/agent-src/rules/git-history-discipline.md +4 -79
  32. package/dist/agent-src/rules/legal-safety-floor.md +11 -55
  33. package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
  34. package/dist/agent-src/rules/think-before-action.md +10 -0
  35. package/dist/agent-src/rules/verify-before-complete.md +12 -0
  36. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
  37. package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
  38. package/dist/agent-src/skills/api-design/SKILL.md +7 -5
  39. package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
  40. package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
  41. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  42. package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
  43. package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
  44. package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
  45. package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
  46. package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
  47. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
  48. package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
  49. package/dist/agent-src/skills/design-review/SKILL.md +23 -2
  50. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
  51. package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
  52. package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
  53. package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
  54. package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
  55. package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
  56. package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
  57. package/dist/agent-src/skills/laravel/SKILL.md +14 -1
  58. package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
  59. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
  60. package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
  61. package/dist/agent-src/skills/mcp/SKILL.md +1 -1
  62. package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
  63. package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
  64. package/dist/agent-src/skills/performance/SKILL.md +1 -1
  65. package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
  66. package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
  67. package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
  68. package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
  69. package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
  70. package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
  71. package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
  72. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
  73. package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
  74. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
  75. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
  76. package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
  77. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
  78. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
  79. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
  80. package/dist/agent-src/templates/agent-settings.md +3 -2
  81. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
  82. package/dist/agent-src/templates/roadmaps.md +14 -0
  83. package/dist/cli/commands/recordTriggerEval.js +4 -14
  84. package/dist/cli/commands/recordTriggerEval.js.map +1 -1
  85. package/dist/discovery/deprecation-report.md +1 -1
  86. package/dist/discovery/discovery-manifest.json +147 -78
  87. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  88. package/dist/discovery/discovery-manifest.summary.md +6 -6
  89. package/dist/discovery/orphan-report.md +1 -1
  90. package/dist/discovery/packs.json +13 -10
  91. package/dist/discovery/trust-report.md +3 -3
  92. package/dist/discovery/workspaces.json +14 -9
  93. package/dist/install/install.mjs +12 -7
  94. package/dist/mcp/registry-manifest.json +4 -4
  95. package/dist/router.json +1 -1
  96. package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
  97. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  98. package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
  99. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
  100. package/dist/scripts/condense.js +12 -2
  101. package/dist/scripts/condense.js.map +1 -1
  102. package/dist/server/schemas/settings.js +5 -2
  103. package/dist/server/schemas/settings.js.map +1 -1
  104. package/docs/CLAIMS.md +24 -3
  105. package/docs/architecture.md +3 -3
  106. package/docs/benchmark.md +51 -0
  107. package/docs/catalog.md +8 -5
  108. package/docs/command-flows.md +2 -1
  109. package/docs/contracts/ai-council-config.md +92 -0
  110. package/docs/contracts/command-clusters.md +1 -0
  111. package/docs/contracts/evidence-based-pruning.md +1 -0
  112. package/docs/contracts/hook-architecture-v1.md +25 -0
  113. package/docs/contracts/kernel-membership.md +35 -0
  114. package/docs/contracts/measurement-baseline.md +1 -0
  115. package/docs/contracts/release-sizing.md +22 -0
  116. package/docs/contracts/write-engine.md +38 -1
  117. package/docs/customization.md +12 -0
  118. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
  119. package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
  120. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
  121. package/docs/decisions/ADR-028-root-layout.md +1 -1
  122. package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
  123. package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
  124. package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
  125. package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
  126. package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
  127. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
  128. package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
  129. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
  130. package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
  131. package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
  132. package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
  133. package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
  134. package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
  135. package/docs/decisions/INDEX.md +11 -9
  136. package/docs/design/cross-model-parity-eval.md +82 -0
  137. package/docs/design/length-neutral-judge-rerun.md +92 -0
  138. package/docs/featured-skills.md +2 -2
  139. package/docs/getting-started-by-role.md +1 -1
  140. package/docs/getting-started.md +2 -2
  141. package/docs/governance-advantage.md +1 -1
  142. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
  143. package/docs/guidelines/code-clarity.md +106 -1
  144. package/docs/guidelines/design-antipatterns.md +20 -0
  145. package/docs/guidelines/php/php-coding-patterns.md +12 -2
  146. package/docs/proof.md +20 -14
  147. package/docs/wedge/production-validator/production-validator.md +27 -0
  148. package/package.json +1 -1
  149. package/src/config/agent-settings.template.yml +29 -9
  150. package/src/scripts/_cli/cmd_doctor.ts +34 -1
  151. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
  152. package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
  153. package/src/scripts/_lib/knowledge_global.ts +6 -2
  154. package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
  155. package/src/scripts/_lib/lexical_index.ts +15 -2
  156. package/src/scripts/_lib/orchestration_record.ts +14 -0
  157. package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
  158. package/src/scripts/ai_council/chairman.ts +90 -0
  159. package/src/scripts/ai_council/clients.ts +18 -3
  160. package/src/scripts/ai_council/config.ts +157 -0
  161. package/src/scripts/ai_council/debate_gates.ts +89 -0
  162. package/src/scripts/ai_council/orchestrator.ts +65 -2
  163. package/src/scripts/ai_council/prompts.ts +121 -11
  164. package/src/scripts/ai_council/replay.ts +17 -0
  165. package/src/scripts/ai_council/stance_tally.ts +184 -0
  166. package/src/scripts/ai_tells_rules.ts +433 -0
  167. package/src/scripts/bench_humanizer_eval.ts +240 -0
  168. package/src/scripts/build_discovery_manifest.ts +6 -0
  169. package/src/scripts/build_proof.ts +21 -9
  170. package/src/scripts/check_claims.ts +40 -0
  171. package/src/scripts/check_rule_invariants.ts +10 -3
  172. package/src/scripts/condense.ts +16 -2
  173. package/src/scripts/consumer_matrix.ts +41 -2
  174. package/src/scripts/council_cli.ts +147 -0
  175. package/src/scripts/detect_ai_tells.ts +312 -0
  176. package/src/scripts/install.ts +12 -0
  177. package/src/scripts/knowledge_global_cli.ts +1 -1
  178. package/src/scripts/lint_framework_leakage.ts +88 -7
  179. package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
  180. package/src/scripts/lint_knowledge_scale.ts +30 -0
  181. package/src/scripts/lint_originality_shingles.ts +210 -0
  182. package/src/scripts/orchestration_record.ts +1 -0
  183. package/src/scripts/schemas/agent-settings.schema.json +11 -0
  184. package/src/scripts/self_review_gate.ts +41 -15
  185. package/src/scripts/skill_eval_coverage.ts +71 -3
  186. package/src/scripts/skill_linter.ts +1 -0
  187. package/src/scripts/trigger_eval_rotation.ts +344 -0
  188. package/src/scripts/update_counts.ts +5 -17
  189. package/src/scripts/worktree_cleanup_check.ts +263 -0
  190. package/src/server/schemas/settings.ts +8 -3
  191. package/src/scripts/analysis_freshness.ts +0 -362
  192. package/src/scripts/export_replay_corpus.ts +0 -208
@@ -28,7 +28,8 @@
28
28
  * Health checks (see `CHECK_IDS`):
29
29
  * scope · global-binary · claude-plugin · stale-orphans · manifest-integrity ·
30
30
  * lockfile-freshness · bridge-drift · mcp-mode · mcp-beta-readiness ·
31
- * offline-readiness · python-runtime · tier-usage-readiness · council-cli ·
31
+ * offline-readiness · python-runtime · humanizer-runtime · tier-usage-readiness ·
32
+ * council-cli ·
32
33
  * unsupported-combos · wizard-state.
33
34
  * Each emits a structured `{id, status, message, remedy}` record with
34
35
  * `status` ∈ `ok` / `warn` / `fail` / `skipped` (rendered
@@ -742,6 +743,7 @@ const CHECK_IDS = [
742
743
  'mcp-beta-readiness',
743
744
  'offline-readiness',
744
745
  'python-runtime',
746
+ 'humanizer-runtime',
745
747
  'tier-usage-readiness',
746
748
  'council-cli',
747
749
  'unsupported-combos',
@@ -762,6 +764,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
762
764
  'mcp-beta-readiness',
763
765
  'offline-readiness',
764
766
  'python-runtime',
767
+ 'humanizer-runtime',
765
768
  'tier-usage-readiness',
766
769
  'council-cli',
767
770
  'wizard-state',
@@ -1505,6 +1508,33 @@ function _check_python_runtime(): Dict {
1505
1508
  };
1506
1509
  }
1507
1510
 
1511
+ function _check_humanizer_runtime(): Dict {
1512
+ // Write-engine step 4b (humanize audit) runs `detect_ai_tells.ts` via a
1513
+ // Node/tsx runtime "when available", degrading to a prose-only audit
1514
+ // otherwise. This check surfaces which path a consumer install gets so the
1515
+ // default-on behavior is not a silent surprise. Either state is healthy —
1516
+ // the fallback is graceful by design — so it never fails the run.
1517
+ const node = shutilWhich('node');
1518
+ if (node === null) {
1519
+ return {
1520
+ id: 'humanizer-runtime',
1521
+ status: 'ok',
1522
+ message:
1523
+ 'no Node runtime on PATH — write-engine step 4b uses the ' +
1524
+ 'prose-only humanize audit (graceful fallback)',
1525
+ remedy:
1526
+ 'install Node to enable the mechanical detect_ai_tells.ts pass; ' +
1527
+ 'the prose audit runs regardless',
1528
+ };
1529
+ }
1530
+ return {
1531
+ id: 'humanizer-runtime',
1532
+ status: 'ok',
1533
+ message: `Node runtime present (${node}) — step 4b runs the mechanical detect_ai_tells.ts pass`,
1534
+ remedy: '',
1535
+ };
1536
+ }
1537
+
1508
1538
  function _check_mcp_beta_readiness(project_root: string): Dict {
1509
1539
  // Maintainer-scoped gate: the six artefacts live in the agent-config
1510
1540
  // SOURCE repo. In a consumer project the paths can never exist, so the
@@ -2130,6 +2160,7 @@ function _run_checks(
2130
2160
  'mcp-beta-readiness': () => _check_mcp_beta_readiness(project_root),
2131
2161
  'offline-readiness': () => _check_offline_readiness(),
2132
2162
  'python-runtime': () => _check_python_runtime(),
2163
+ 'humanizer-runtime': () => _check_humanizer_runtime(),
2133
2164
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2134
2165
  'council-cli': () => _check_council_cli(project_root),
2135
2166
  'unsupported-combos': () => _check_unsupported_combos(manifest),
@@ -2200,6 +2231,7 @@ function _run_checks_no_manifest(
2200
2231
  'mcp-beta-readiness': () => _check_mcp_beta_readiness(project_root),
2201
2232
  'offline-readiness': () => _check_offline_readiness(),
2202
2233
  'python-runtime': () => _check_python_runtime(),
2234
+ 'humanizer-runtime': () => _check_humanizer_runtime(),
2203
2235
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2204
2236
  'council-cli': () => _check_council_cli(project_root),
2205
2237
  'unsupported-combos': () => _skipped_manifest_check('unsupported-combos'),
@@ -2687,6 +2719,7 @@ export {
2687
2719
  _check_offline_readiness,
2688
2720
  _check_stale_orphans,
2689
2721
  _check_python_runtime,
2722
+ _check_humanizer_runtime,
2690
2723
  _check_mcp_beta_readiness,
2691
2724
  _check_tier_usage_readiness,
2692
2725
  _check_council_cli,
@@ -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,10 @@ 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'];
56
60
 
57
61
  /**
58
62
  * Hard defaults applied when the setting block is absent. Default is ON for
@@ -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,