@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,433 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai_tells_rules — deterministic AI-writing-tell registry for prose deliverables.
4
+ *
5
+ * Data + matchers for the mechanically detectable subset of the AI-writing
6
+ * pattern catalog in `src/skills/humanizer/data/patterns.md` (root source:
7
+ * Wikipedia "Signs of AI writing", WikiProject AI Cleanup). Mirrors the
8
+ * `design_slop_rules.ts` split: this file is the registry, `detect_ai_tells.ts`
9
+ * is the CLI. Scope is GENERATED DELIVERABLE PROSE (ghostwriter drafts, posts,
10
+ * articles) — NEVER repo docs; this repo's own documentation style (em dashes,
11
+ * bold inline headers) is intentional and exempt by scope, not by allowlist.
12
+ *
13
+ * Severity model (encodes the source catalog's false-positive guidance —
14
+ * "clusters are a confession, isolated hits are not"):
15
+ * hard — a single hit counts against `--max-hard` (chat artifacts,
16
+ * knowledge-cutoff disclaimers, emoji headings, ` -- ` asides).
17
+ * cluster — hits contribute `weight` to a per-500-words score gated by
18
+ * `--max-score` (AI vocabulary, filler, hedging, sycophancy, …).
19
+ * Em/en dashes are a DENSITY rule (per-500-words cap, default 2 — CP1
20
+ * parity from design-antipatterns), never hard-zero: council 2026-07-11
21
+ * rejected the zero-dash rule; a captured voice fingerprint that
22
+ * legitimately uses dashes wins (suppression happens at the call site).
23
+ *
24
+ * Dependency-free by design (npx-shipped package) — pattern analysis only.
25
+ */
26
+
27
+ export type TellSeverity = "hard" | "cluster";
28
+ export type TellGroup =
29
+ | "content"
30
+ | "language"
31
+ | "style"
32
+ | "communication"
33
+ | "filler";
34
+ /** "any" = language-agnostic (typography / structure tells). */
35
+ export type TellLanguage = "en" | "de" | "any";
36
+
37
+ export interface TellRule {
38
+ id: string;
39
+ group: TellGroup;
40
+ severity: TellSeverity;
41
+ language: TellLanguage;
42
+ /** cluster weight per hit; ignored for hard rules */
43
+ weight: number;
44
+ description: string;
45
+ patterns: RegExp[];
46
+ }
47
+
48
+ /** Em/en-dash density cap per 500 words (CP1 parity). */
49
+ export const DEFAULT_MAX_DASH_DENSITY = 2;
50
+ /** Weighted cluster-score cap per 500 words. */
51
+ export const DEFAULT_MAX_CLUSTER_SCORE = 3;
52
+ /** Hard-hit cap. */
53
+ export const DEFAULT_MAX_HARD = 0;
54
+
55
+ const w = (
56
+ id: string,
57
+ group: TellGroup,
58
+ severity: TellSeverity,
59
+ language: TellLanguage,
60
+ weight: number,
61
+ description: string,
62
+ patterns: RegExp[],
63
+ ): TellRule => ({ id, group, severity, language, weight, description, patterns });
64
+
65
+ export const TELL_RULES: TellRule[] = [
66
+ // ── hard: communication artifacts pasted into a deliverable ──────────────
67
+ w(
68
+ "tell-chat-artifact",
69
+ "communication",
70
+ "hard",
71
+ "en",
72
+ 0,
73
+ "Chatbot correspondence pasted as content (I hope this helps / let me know / would you like…)",
74
+ [
75
+ /\bI hope this helps\b/i,
76
+ /\blet me know if you('| wou)?l?d? like\b/i,
77
+ /\bwould you like me to\b/i,
78
+ /\bwant me to (give|continue|expand)\b/i,
79
+ /\bshould I continue\b/i,
80
+ /\bhere('s| is) (an? )?(overview|breakdown|summary) of\b/i,
81
+ ],
82
+ ),
83
+ w(
84
+ "tell-knowledge-cutoff",
85
+ "communication",
86
+ "hard",
87
+ "en",
88
+ 0,
89
+ "Knowledge-cutoff disclaimer or speculative gap-filling presented as fact",
90
+ [
91
+ /\bas of my (last|latest) (update|training)\b/i,
92
+ /\bmy knowledge cutoff\b/i,
93
+ /\bup to my last training update\b/i,
94
+ /\bwhile specific details are (limited|scarce|not extensively documented)\b/i,
95
+ /\bbased on (the )?available information\b/i,
96
+ /\b(maintains a low profile|keeps personal details private)\b/i,
97
+ ],
98
+ ),
99
+ w(
100
+ "tell-emoji-heading",
101
+ "style",
102
+ "hard",
103
+ "any",
104
+ 0,
105
+ "Emoji-decorated heading or bold list header",
106
+ [
107
+ /^#{1,6}\s*[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}]/mu,
108
+ /^\s*[-*]?\s*[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}]\s*\*\*/mu,
109
+ ],
110
+ ),
111
+ w(
112
+ "tell-double-hyphen-aside",
113
+ "style",
114
+ "hard",
115
+ "any",
116
+ 0,
117
+ "Spaced double hyphen used as an em-dash substitute",
118
+ [/\s--\s/],
119
+ ),
120
+
121
+ // ── cluster: post-2023 AI vocabulary & copula avoidance ──────────────────
122
+ w(
123
+ "tell-ai-vocabulary",
124
+ "language",
125
+ "cluster",
126
+ "en",
127
+ 1,
128
+ "Overused post-2023 AI vocabulary (delve, tapestry, showcase, pivotal, …)",
129
+ [
130
+ /\bdelve(s|d)?\b/i,
131
+ /\btapestry\b/i,
132
+ /\bshowcas(e|es|ing)\b/i,
133
+ /\btestament to\b/i,
134
+ /\bpivotal\b/i,
135
+ /\bcrucial role\b/i,
136
+ /\bvibrant\b/i,
137
+ /\bintricate\b/i,
138
+ /\binterplay\b/i,
139
+ /\bfostering\b/i,
140
+ /\bgarner(ed|s)?\b/i,
141
+ /\bever-evolving\b/i,
142
+ /\bevolving landscape\b/i,
143
+ /\b(digital|competitive|cultural) landscape\b/i,
144
+ /\bnestled\b/i,
145
+ /\bboasts?\b/i,
146
+ /\bunderscore(s|d)?\b/i,
147
+ /\bgroundbreaking\b/i,
148
+ /\bseamless(ly)?\b/i,
149
+ /\belevate(s|d)? your\b/i,
150
+ ],
151
+ ),
152
+ w(
153
+ "tell-copula-avoidance",
154
+ "language",
155
+ "cluster",
156
+ "en",
157
+ 1,
158
+ "Elaborate constructions replacing a simple 'is' (serves as / stands as / represents a)",
159
+ [
160
+ /\bserves as an?\b/i,
161
+ /\bstands as an?\b/i,
162
+ /\bmarks an? (pivotal|significant|key|major)\b/i,
163
+ /\brepresents an? (shift|milestone|step)\b/i,
164
+ ],
165
+ ),
166
+ w(
167
+ "tell-significance-inflation",
168
+ "content",
169
+ "cluster",
170
+ "en",
171
+ 1,
172
+ "Inflated significance / legacy framing (pivotal moment, broader trends, setting the stage)",
173
+ [
174
+ /\bpivotal moment\b/i,
175
+ /\bbroader (trend|movement|shift)s?\b/i,
176
+ /\bsetting the stage for\b/i,
177
+ /\bindelible mark\b/i,
178
+ /\bkey turning point\b/i,
179
+ /\bdeeply rooted\b/i,
180
+ /\benduring (legacy|spirit|impact)\b/i,
181
+ /\bplays? a (crucial|vital|key|pivotal) role\b/i,
182
+ /\bunderscores? (its|the) (importance|significance)\b/i,
183
+ ],
184
+ ),
185
+ w(
186
+ "tell-negative-parallelism",
187
+ "language",
188
+ "cluster",
189
+ "en",
190
+ 1,
191
+ "Not just X — it's Y constructions and tailing negations",
192
+ [
193
+ /\b(is|it'?s) not (just|only|merely) about\b/i,
194
+ /\bnot (just|only|merely) an?\b[^.!?\n]{3,80}\b(but|it'?s)\b/i,
195
+ /\bisn'?t (just|only|merely)\b/i,
196
+ /,\s*no (guessing|fluff|wasted \w+)[.!]?$/im,
197
+ ],
198
+ ),
199
+ w(
200
+ "tell-false-range",
201
+ "language",
202
+ "cluster",
203
+ "en",
204
+ 0.5,
205
+ "'From X to Y' constructions where X and Y are not on a scale",
206
+ [/\bfrom [^,.\n]{3,45} to [^,.\n]{3,45},\s*(?:and\s+)?from\b/i],
207
+ ),
208
+ w(
209
+ "tell-rule-of-three",
210
+ "language",
211
+ "cluster",
212
+ "any",
213
+ 0.5,
214
+ "Forced triplet groupings (innovation, inspiration, and insights)",
215
+ [/\b\w+(?:ity|ion|ness|ing|s)?, \w+(?:ity|ion|ness|ing|s)?, and \w+/],
216
+ ),
217
+
218
+ // ── cluster: filler, hedging, endings, openers ───────────────────────────
219
+ w(
220
+ "tell-filler-phrase",
221
+ "filler",
222
+ "cluster",
223
+ "en",
224
+ 1,
225
+ "Filler phrases (in order to, due to the fact that, it is important to note)",
226
+ [
227
+ /\bin order to\b/i,
228
+ /\bdue to the fact that\b/i,
229
+ /\bat this point in time\b/i,
230
+ /\bin the event that\b/i,
231
+ /\bhas the ability to\b/i,
232
+ /\bit('s| is) (important|worth) (to note|noting) that\b/i,
233
+ ],
234
+ ),
235
+ w(
236
+ "tell-hedging-stack",
237
+ "filler",
238
+ "cluster",
239
+ "en",
240
+ 1,
241
+ "Stacked hedges (could potentially, might possibly)",
242
+ [
243
+ /\bcould potentially\b/i,
244
+ /\bmight possibly\b/i,
245
+ /\bit could be argued that\b/i,
246
+ /\bperhaps one of the most\b/i,
247
+ ],
248
+ ),
249
+ w(
250
+ "tell-generic-conclusion",
251
+ "filler",
252
+ "cluster",
253
+ "en",
254
+ 1,
255
+ "Generic upbeat endings (the future looks bright, exciting times ahead)",
256
+ [
257
+ /\bthe future (looks|is) bright\b/i,
258
+ /\bexciting times (lie )?ahead\b/i,
259
+ /\bstep in the right direction\b/i,
260
+ /\bjourney (toward|towards)\b/i,
261
+ /\bcontinues? to thrive\b/i,
262
+ /\bpromises? memories\b/i,
263
+ ],
264
+ ),
265
+ w(
266
+ "tell-signposting",
267
+ "filler",
268
+ "cluster",
269
+ "en",
270
+ 1,
271
+ "Announcement-style signposting (let's dive in, here's what you need to know)",
272
+ [
273
+ /\blet'?s dive (in|into)\b/i,
274
+ /\blet'?s explore\b/i,
275
+ /\blet'?s break (this|it) down\b/i,
276
+ /\bwithout further ado\b/i,
277
+ /\bhere'?s what you need to know\b/i,
278
+ ],
279
+ ),
280
+ w(
281
+ "tell-authority-trope",
282
+ "filler",
283
+ "cluster",
284
+ "en",
285
+ 1,
286
+ "Persuasive authority tropes (the real question is, at its core)",
287
+ [
288
+ /\bthe real question is\b/i,
289
+ /\bat its core\b/i,
290
+ /\bwhat really matters is\b/i,
291
+ /\bthe heart of the matter\b/i,
292
+ /\bthe deeper issue\b/i,
293
+ ],
294
+ ),
295
+ w(
296
+ "tell-sycophancy",
297
+ "communication",
298
+ "cluster",
299
+ "en",
300
+ 1,
301
+ "Sycophantic openers (great question, you're absolutely right)",
302
+ [
303
+ /\bgreat question\b/i,
304
+ /\byou'?re absolutely right\b/i,
305
+ /\b(that'?s an? )?excellent point\b/i,
306
+ ],
307
+ ),
308
+ w(
309
+ "tell-vague-attribution",
310
+ "content",
311
+ "cluster",
312
+ "en",
313
+ 1,
314
+ "Weasel-word attributions (experts argue, observers have cited, industry reports)",
315
+ [
316
+ /\bexperts (argue|believe|suggest|agree)\b/i,
317
+ /\bobservers have (cited|noted)\b/i,
318
+ /\bindustry reports\b/i,
319
+ /\bsome critics argue\b/i,
320
+ /\bwidely (regarded|considered) as\b/i,
321
+ ],
322
+ ),
323
+ w(
324
+ "tell-aphorism-formula",
325
+ "filler",
326
+ "cluster",
327
+ "en",
328
+ 1,
329
+ "Aphorism formulas (X is the language/currency/architecture of Z, X becomes a trap)",
330
+ [
331
+ /\bis the (language|currency|architecture|lifeblood) of\b/i,
332
+ /\bbecomes a trap\b/i,
333
+ /\bis not a tool but a mirror\b/i,
334
+ ],
335
+ ),
336
+
337
+ // ── cluster: typography / structure (language-agnostic) ──────────────────
338
+ w(
339
+ "tell-curly-quotes",
340
+ "style",
341
+ "cluster",
342
+ "any",
343
+ 0.25,
344
+ "Curly quotation marks (only meaningful in clusters — editors auto-curl)",
345
+ [/[“”‘’]/],
346
+ ),
347
+ w(
348
+ "tell-bold-header-list",
349
+ "style",
350
+ "cluster",
351
+ "any",
352
+ 1,
353
+ "Vertical list items opening with a bolded header + colon",
354
+ [/^\s*[-*]\s+\*\*[^*\n]{2,60}:?\*\*:?\s/m],
355
+ ),
356
+ w(
357
+ "tell-title-case-heading",
358
+ "style",
359
+ "cluster",
360
+ "en",
361
+ 0.5,
362
+ "Title-Case Headings With Every Word Capitalized",
363
+ [/^#{2,6}\s+(?:[A-Z][a-z]+\s+){3,}[A-Z][a-z]+\s*$/m],
364
+ ),
365
+ ];
366
+
367
+ // ── German subset (language: de) — added under the de-subset-demand gate ────
368
+ export const TELL_RULES_DE: TellRule[] = [
369
+ w(
370
+ "tell-de-filler",
371
+ "filler",
372
+ "cluster",
373
+ "de",
374
+ 1,
375
+ "Deutsche Füllphrasen (es ist wichtig zu beachten, im heutigen digitalen Zeitalter)",
376
+ [
377
+ /\bes ist wichtig(,)? zu (beachten|betonen|erwähnen)\b/i,
378
+ /\bim heutigen digitalen Zeitalter\b/i,
379
+ /\bin der heutigen schnelllebigen (Welt|Zeit)\b/i,
380
+ /\bes sei darauf hingewiesen\b/i,
381
+ ],
382
+ ),
383
+ w(
384
+ "tell-de-connector-stack",
385
+ "language",
386
+ "cluster",
387
+ "de",
388
+ 1,
389
+ "Konnektoren-Stapel (zudem, darüber hinaus, des Weiteren)",
390
+ [
391
+ /\bzudem\b/i,
392
+ /\bdar(ü|ue)ber hinaus\b/i,
393
+ /\bdes Weiteren\b/i,
394
+ /\bnicht zuletzt\b/i,
395
+ ],
396
+ ),
397
+ w(
398
+ "tell-de-significance",
399
+ "content",
400
+ "cluster",
401
+ "de",
402
+ 1,
403
+ "Bedeutungs-Inflation (spielt eine entscheidende Rolle, markiert einen Wendepunkt)",
404
+ [
405
+ /\bspielt eine (entscheidende|zentrale|wichtige|Schl(ü|ue)ssel)rolle\b/i,
406
+ /\bmarkiert einen (Wendepunkt|Meilenstein)\b/i,
407
+ /\bunterstreicht die Bedeutung\b/i,
408
+ /\bein Meilenstein (in|f(ü|ue)r)\b/i,
409
+ /\bnahtlos(e|es|er)?\b/i,
410
+ ],
411
+ ),
412
+ w(
413
+ "tell-de-negative-parallelism",
414
+ "language",
415
+ "cluster",
416
+ "de",
417
+ 1,
418
+ "Nicht nur X, sondern Y",
419
+ [/\bnicht nur\b[^.!?\n]{3,80}\bsondern( auch)?\b/i],
420
+ ),
421
+ ];
422
+
423
+ /** Every registry rule, both languages. */
424
+ export const ALL_TELL_RULES: TellRule[] = [...TELL_RULES, ...TELL_RULES_DE];
425
+
426
+ /** Crude language sniff for `--language auto`: DE stopword density. */
427
+ export function sniffLanguage(text: string): "en" | "de" {
428
+ const deHits =
429
+ text.match(/\b(und|nicht|eine?|der|die|das|mit|für|ist|wird|auch)\b/gi)
430
+ ?.length ?? 0;
431
+ const words = text.split(/\s+/).filter(Boolean).length || 1;
432
+ return deHits / words > 0.04 ? "de" : "en";
433
+ }
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bench_humanizer_eval — paired eval for the humanizer feature.
4
+ *
5
+ * Two metrics over the before/after corpus in `tests/fixtures/ai-tells/`:
6
+ *
7
+ * 1. OBJECTIVE (free, deterministic): AI-tell reduction per pair via
8
+ * `detect_ai_tells.ts` — hard hits, cluster score/500w, dash density/500w.
9
+ * 2. BLIND PREFERENCE (--judge, billable): an LLM judge sees each pair in
10
+ * randomized A/B order (deterministic LCG seed per pair) and picks which
11
+ * text reads more like a skilled human writer. LENGTH-CONTROLLED: pairs
12
+ * whose word counts differ by more than ±25% are excluded from the
13
+ * preference aggregate (the token program's verbosity-bias finding makes
14
+ * uncontrolled pairs unusable) — they still count for the objective metric.
15
+ *
16
+ * Reports follow docs/benchmarks.md naming:
17
+ * internal/bench/reports/humanizer-v1.{json,md} (canonical pointer)
18
+ * internal/bench/reports/<ISO>-humanizer-v1.{json,md} (immutable trail)
19
+ *
20
+ * Claims discipline: this eval backs `claim:humanizer-tell-reduction` in
21
+ * docs/CLAIMS.md. It measures OUR pattern counts and a blind preference —
22
+ * never third-party "AI detector" outcomes; that claim class stays banned.
23
+ *
24
+ * Usage:
25
+ * npx tsx src/scripts/bench_humanizer_eval.ts # objective only (free)
26
+ * npx tsx src/scripts/bench_humanizer_eval.ts --judge --confirm-spend # + blind preference (billable)
27
+ *
28
+ * `--judge` without `--confirm-spend` estimates the billable call count and
29
+ * halts (exit 2) — the spend is opt-in, never fired implicitly.
30
+ */
31
+
32
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
33
+ import { join } from "node:path";
34
+ import { analyzeText, type TellReport } from "./detect_ai_tells.js";
35
+
36
+ const ROOT = process.cwd();
37
+ const FIXTURES = join(ROOT, "tests", "fixtures", "ai-tells");
38
+ const REPORT_DIR = join(ROOT, "internal", "bench", "reports");
39
+ const LENGTH_TOLERANCE = 0.25;
40
+
41
+ interface PairResult {
42
+ name: string;
43
+ language: "en" | "de";
44
+ words_before: number;
45
+ words_after: number;
46
+ length_ratio: number;
47
+ length_controlled: boolean;
48
+ before: Pick<TellReport, "hard_total" | "cluster_score_per_500" | "dash_density_per_500">;
49
+ after: Pick<TellReport, "hard_total" | "cluster_score_per_500" | "dash_density_per_500">;
50
+ judge?: { preferred: "after" | "before"; order: "after-first" | "before-first" };
51
+ }
52
+
53
+ function slim(r: TellReport) {
54
+ return {
55
+ hard_total: r.hard_total,
56
+ cluster_score_per_500: r.cluster_score_per_500,
57
+ dash_density_per_500: r.dash_density_per_500,
58
+ };
59
+ }
60
+
61
+ /** Deterministic per-pair coin flip (LCG) so re-runs keep the same A/B order. */
62
+ function coin(seed: number): boolean {
63
+ return ((seed * 1103515245 + 12345) >>> 16) % 2 === 0;
64
+ }
65
+
66
+ function loadPairs(): Array<{ name: string; language: "en" | "de"; before: string; after: string }> {
67
+ const out: Array<{ name: string; language: "en" | "de"; before: string; after: string }> = [];
68
+ for (const language of ["en", "de"] as const) {
69
+ const dir = join(FIXTURES, language);
70
+ let names: string[] = [];
71
+ try {
72
+ names = readdirSync(dir)
73
+ .filter((f) => f.endsWith(".before.md"))
74
+ .map((f) => f.replace(/\.before\.md$/, ""))
75
+ .sort();
76
+ } catch {
77
+ continue;
78
+ }
79
+ for (const name of names) {
80
+ out.push({
81
+ name,
82
+ language,
83
+ before: readFileSync(join(dir, `${name}.before.md`), "utf8"),
84
+ after: readFileSync(join(dir, `${name}.after.md`), "utf8"),
85
+ });
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+
91
+ async function judgePair(
92
+ pair: { name: string; before: string; after: string },
93
+ index: number,
94
+ ): Promise<{ preferred: "after" | "before"; order: "after-first" | "before-first" }> {
95
+ const { AnthropicClient, load_anthropic_key } = await import("./ai_council/clients.js");
96
+ const client = new AnthropicClient({ api_key: load_anthropic_key() });
97
+ const afterFirst = coin(index + 1);
98
+ const [a, b] = afterFirst ? [pair.after, pair.before] : [pair.before, pair.after];
99
+ const system =
100
+ "You judge writing quality blind. You will see two texts, A and B, covering the same content. " +
101
+ "Pick the one that reads more like it was written by a skilled human writer for a real audience. " +
102
+ "Judge prose quality only — not length, not topic. Reply with exactly one character: A or B.";
103
+ const user = `Text A:\n\n${a}\n\n---\n\nText B:\n\n${b}\n\nWhich reads more like a skilled human writer? Reply A or B.`;
104
+ const resp = client.ask(system, user, 8);
105
+ const text = (resp.text ?? "").trim().toUpperCase();
106
+ const pickedA = text.startsWith("A");
107
+ const preferred = pickedA === afterFirst ? "after" : "before";
108
+ return { preferred, order: afterFirst ? "after-first" : "before-first" };
109
+ }
110
+
111
+ async function main(): Promise<void> {
112
+ const runJudge = process.argv.includes("--judge");
113
+ const confirmSpend = process.argv.includes("--confirm-spend");
114
+ const pairs = loadPairs();
115
+ if (pairs.length < 20) {
116
+ console.error(`corpus too small: ${pairs.length} pairs (< 20)`);
117
+ process.exit(2);
118
+ }
119
+
120
+ // Spend gate: --judge fires one billable API call per length-controlled
121
+ // pair (via the council client's curl transport). Estimate + halt unless
122
+ // --confirm-spend is passed — mirrors council:run's explicit-confirm model,
123
+ // never an interactive prompt. The objective-only default path is free.
124
+ if (runJudge && !confirmSpend) {
125
+ const controlledCount = pairs.filter((p) => {
126
+ const b = analyzeText(p.before, p.language).words;
127
+ const a = analyzeText(p.after, p.language).words;
128
+ return Math.abs(1 - a / b) <= LENGTH_TOLERANCE;
129
+ }).length;
130
+ process.stderr.write(
131
+ `--judge makes ${controlledCount} billable API call(s) (one per length-controlled pair). ` +
132
+ `Re-run with --confirm-spend to authorize the spend. ` +
133
+ `The objective-only run (no --judge) is free.\n`,
134
+ );
135
+ process.exit(2);
136
+ }
137
+
138
+ const results: PairResult[] = [];
139
+ for (let i = 0; i < pairs.length; i++) {
140
+ const p = pairs[i]!;
141
+ const before = analyzeText(p.before, p.language);
142
+ const after = analyzeText(p.after, p.language);
143
+ const ratio = after.words / before.words;
144
+ const controlled = Math.abs(1 - ratio) <= LENGTH_TOLERANCE;
145
+ const row: PairResult = {
146
+ name: p.name,
147
+ language: p.language,
148
+ words_before: before.words,
149
+ words_after: after.words,
150
+ length_ratio: Math.round(ratio * 100) / 100,
151
+ length_controlled: controlled,
152
+ before: slim(before),
153
+ after: slim(after),
154
+ };
155
+ if (runJudge && controlled) {
156
+ row.judge = await judgePair(p, i);
157
+ process.stdout.write(` judged ${p.name}: prefers ${row.judge.preferred} (${row.judge.order})\n`);
158
+ }
159
+ results.push(row);
160
+ }
161
+
162
+ const mean = (xs: number[]) =>
163
+ Math.round((xs.reduce((s, x) => s + x, 0) / Math.max(xs.length, 1)) * 100) / 100;
164
+ const agg = {
165
+ pairs: results.length,
166
+ length_controlled_pairs: results.filter((r) => r.length_controlled).length,
167
+ mean_hard_before: mean(results.map((r) => r.before.hard_total)),
168
+ mean_hard_after: mean(results.map((r) => r.after.hard_total)),
169
+ mean_cluster_before: mean(results.map((r) => r.before.cluster_score_per_500)),
170
+ mean_cluster_after: mean(results.map((r) => r.after.cluster_score_per_500)),
171
+ mean_dash_before: mean(results.map((r) => r.before.dash_density_per_500)),
172
+ mean_dash_after: mean(results.map((r) => r.after.dash_density_per_500)),
173
+ judge_model: undefined as string | undefined,
174
+ judged_pairs: 0,
175
+ prefers_after: 0,
176
+ prefers_before: 0,
177
+ };
178
+ const judged = results.filter((r) => r.judge);
179
+ agg.judged_pairs = judged.length;
180
+ agg.prefers_after = judged.filter((r) => r.judge!.preferred === "after").length;
181
+ agg.prefers_before = judged.filter((r) => r.judge!.preferred === "before").length;
182
+ if (runJudge) {
183
+ const { DEFAULT_ANTHROPIC_MODEL } = await import("./ai_council/clients.js");
184
+ agg.judge_model = DEFAULT_ANTHROPIC_MODEL as string;
185
+ }
186
+
187
+ const iso = new Date().toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
188
+ const payload = { generated: iso, corpus: "tests/fixtures/ai-tells", aggregate: agg, results };
189
+
190
+ const md = [
191
+ "# Humanizer paired eval — v1",
192
+ "",
193
+ `> Generated ${iso} · corpus \`tests/fixtures/ai-tells\` (${agg.pairs} before/after pairs, ` +
194
+ `${agg.length_controlled_pairs} length-controlled ±${LENGTH_TOLERANCE * 100}%).`,
195
+ "",
196
+ "## Objective — AI-tell reduction (deterministic)",
197
+ "",
198
+ "| Metric (mean) | Before | After |",
199
+ "|---|---|---|",
200
+ `| Hard hits | ${agg.mean_hard_before} | ${agg.mean_hard_after} |`,
201
+ `| Cluster score /500w | ${agg.mean_cluster_before} | ${agg.mean_cluster_after} |`,
202
+ `| Dash density /500w | ${agg.mean_dash_before} | ${agg.mean_dash_after} |`,
203
+ "",
204
+ "## Blind preference (length-controlled)",
205
+ "",
206
+ agg.judged_pairs > 0
207
+ ? `Judge ${agg.judge_model}: prefers the humanized text in **${agg.prefers_after}/${agg.judged_pairs}** ` +
208
+ `pairs (randomized A/B order, deterministic seed). An honest null here keeps the detector as a ` +
209
+ `hygiene gate; the claim ledger only carries what this table shows.`
210
+ : "_Not run (objective-only invocation)._",
211
+ "",
212
+ "## Scope note",
213
+ "",
214
+ "This eval measures the package's own pattern counts and a blind prose-quality preference.",
215
+ 'It never measures third-party "AI detector" outcomes — that claim class is banned',
216
+ "(unfalsifiable from our side; see roadmap non-goals).",
217
+ "",
218
+ "## Open question — real-draft lift is unmeasured",
219
+ "",
220
+ "The `before` fixtures were **deliberately tell-seeded**, so a perfect score",
221
+ "measures seeded-tell removal on a self-constructed corpus — NOT that real",
222
+ "ghostwriter drafts get better. Real-world lift stays **unmeasured** until",
223
+ "write-engine step 4b has processed real `/ghostwriter:write` drafts and those",
224
+ "have been paired-evaluated (the `road-to-humanizer-hardening` live-usage",
225
+ "blocker). The claim ledger is scoped to \"on the fixture corpus\" accordingly.",
226
+ "",
227
+ ].join("\n");
228
+
229
+ mkdirSync(REPORT_DIR, { recursive: true });
230
+ writeFileSync(join(REPORT_DIR, "humanizer-v1.json"), JSON.stringify(payload, null, 2));
231
+ writeFileSync(join(REPORT_DIR, "humanizer-v1.md"), md);
232
+ writeFileSync(join(REPORT_DIR, `${iso}-humanizer-v1.json`), JSON.stringify(payload, null, 2));
233
+ writeFileSync(join(REPORT_DIR, `${iso}-humanizer-v1.md`), md);
234
+ process.stdout.write(md + "\n");
235
+ }
236
+
237
+ main().catch((err) => {
238
+ console.error(String(err));
239
+ process.exit(2);
240
+ });