@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +148 -0
- package/README.md +5 -5
- package/dist/agent-src/commands/challenge-me/vision.md +121 -32
- package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
- package/dist/agent-src/commands/commit/in-chunks.md +1 -1
- package/dist/agent-src/commands/commit.md +2 -1
- package/dist/agent-src/commands/cost/report.md +2 -1
- package/dist/agent-src/commands/feature/dev.md +4 -4
- package/dist/agent-src/commands/humanize.md +101 -0
- package/dist/agent-src/commands/jira-ticket.md +2 -2
- package/dist/agent-src/commands/memory/load.md +15 -0
- package/dist/agent-src/commands/optimize/project.md +12 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -11
- package/dist/agent-src/commands/tests/create.md +1 -1
- package/dist/agent-src/commands/worktree/cleanup.md +22 -17
- package/dist/agent-src/commands/worktree/create.md +3 -1
- package/dist/agent-src/commands/worktree/status.md +12 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
- package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
- package/dist/agent-src/rules/autonomous-execution.md +7 -37
- package/dist/agent-src/rules/broken-access-control.md +7 -45
- package/dist/agent-src/rules/code-comment-discipline.md +148 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +4 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
- package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
- package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
- package/dist/agent-src/skills/design-review/SKILL.md +23 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
- package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
- package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
- package/dist/agent-src/skills/laravel/SKILL.md +14 -1
- package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
- package/dist/agent-src/skills/mcp/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
- package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
- package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
- package/dist/agent-src/templates/agent-settings.md +3 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
- package/dist/agent-src/templates/roadmaps.md +14 -0
- package/dist/cli/commands/recordTriggerEval.js +4 -14
- package/dist/cli/commands/recordTriggerEval.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +147 -78
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +6 -6
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -10
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +14 -9
- package/dist/install/install.mjs +12 -7
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
- package/dist/scripts/condense.js +12 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +24 -3
- package/docs/architecture.md +3 -3
- package/docs/benchmark.md +51 -0
- package/docs/catalog.md +8 -5
- package/docs/command-flows.md +2 -1
- package/docs/contracts/ai-council-config.md +92 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/evidence-based-pruning.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +25 -0
- package/docs/contracts/kernel-membership.md +35 -0
- package/docs/contracts/measurement-baseline.md +1 -0
- package/docs/contracts/release-sizing.md +22 -0
- package/docs/contracts/write-engine.md +38 -1
- package/docs/customization.md +12 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
- package/docs/decisions/ADR-028-root-layout.md +1 -1
- package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
- package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
- package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
- package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
- package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
- package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
- package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
- package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
- package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
- package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
- package/docs/decisions/INDEX.md +11 -9
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/code-clarity.md +106 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/proof.md +20 -14
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +29 -9
- package/src/scripts/_cli/cmd_doctor.ts +34 -1
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
- package/src/scripts/_lib/knowledge_global.ts +6 -2
- package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
- package/src/scripts/_lib/lexical_index.ts +15 -2
- package/src/scripts/_lib/orchestration_record.ts +14 -0
- package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
- package/src/scripts/ai_council/chairman.ts +90 -0
- package/src/scripts/ai_council/clients.ts +18 -3
- package/src/scripts/ai_council/config.ts +157 -0
- package/src/scripts/ai_council/debate_gates.ts +89 -0
- package/src/scripts/ai_council/orchestrator.ts +65 -2
- package/src/scripts/ai_council/prompts.ts +121 -11
- package/src/scripts/ai_council/replay.ts +17 -0
- package/src/scripts/ai_council/stance_tally.ts +184 -0
- package/src/scripts/ai_tells_rules.ts +433 -0
- package/src/scripts/bench_humanizer_eval.ts +240 -0
- package/src/scripts/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +21 -9
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_rule_invariants.ts +10 -3
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +41 -2
- package/src/scripts/council_cli.ts +147 -0
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +1 -1
- package/src/scripts/lint_framework_leakage.ts +88 -7
- package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
- package/src/scripts/lint_knowledge_scale.ts +30 -0
- package/src/scripts/lint_originality_shingles.ts +210 -0
- package/src/scripts/orchestration_record.ts +1 -0
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/self_review_gate.ts +41 -15
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/trigger_eval_rotation.ts +344 -0
- package/src/scripts/update_counts.ts +5 -17
- package/src/scripts/worktree_cleanup_check.ts +263 -0
- package/src/server/schemas/settings.ts +8 -3
- package/src/scripts/analysis_freshness.ts +0 -362
- package/src/scripts/export_replay_corpus.ts +0 -208
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Option-level stance tally (road-to-opt-council-deliberation Phase 1).
|
|
2
|
+
//
|
|
3
|
+
// `consensus.ts` scores *findings*; nothing here produces an *option-level*
|
|
4
|
+
// verdict for "A or B?" questions. This module engineers Source G's prose
|
|
5
|
+
// tally deterministically: parse each member's mandatory final-round stance
|
|
6
|
+
// line, weight by confidence, and decide consensus vs. an honest split against
|
|
7
|
+
// a ⅔ threshold. Everything countable lands in TS with tests — never inferred
|
|
8
|
+
// from prose (an unparseable line is a repair-marker, not a guess).
|
|
9
|
+
//
|
|
10
|
+
// Default-off behind `ai_council.stance_tally.enabled`; additive — with the key
|
|
11
|
+
// false the council path is byte-identical to today.
|
|
12
|
+
|
|
13
|
+
/** Confidence tier from a stance line. */
|
|
14
|
+
export type Confidence = 'high' | 'med' | 'low';
|
|
15
|
+
|
|
16
|
+
/** Contribution weight per confidence tier (Source G's factors). */
|
|
17
|
+
export const CONFIDENCE_FACTOR: Record<Confidence, number> = {
|
|
18
|
+
high: 1.0,
|
|
19
|
+
med: 0.75,
|
|
20
|
+
low: 0.5,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Consensus requires an option to clear two-thirds of the base-weight total. */
|
|
24
|
+
export const CONSENSUS_FRACTION = 2 / 3;
|
|
25
|
+
|
|
26
|
+
/** The canonical label for an explicit abstention. */
|
|
27
|
+
export const ABSTAIN_LABEL = 'abstain';
|
|
28
|
+
|
|
29
|
+
/** A parsed stance line bound to its member. */
|
|
30
|
+
export interface StanceLine {
|
|
31
|
+
member: string; // e.g. "openai:gpt-4o"
|
|
32
|
+
label: string; // canonical (casefolded) key; `abstain` is special
|
|
33
|
+
display: string; // first-seen original label text (rendering)
|
|
34
|
+
confidence: Confidence;
|
|
35
|
+
dealbreaker: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Per-option accumulation across members. */
|
|
39
|
+
export interface OptionTally {
|
|
40
|
+
label: string; // display label (first-seen casing)
|
|
41
|
+
weight: number; // sum of confidence factors of backers
|
|
42
|
+
backers: Array<{ member: string; confidence: Confidence }>;
|
|
43
|
+
dealbreakers: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The full tally verdict. `consensus` is null on a split — never a forced winner. */
|
|
47
|
+
export interface StanceTallyResult {
|
|
48
|
+
options: OptionTally[]; // non-abstain options, weight desc
|
|
49
|
+
w_total: number; // base-weight denominator (every voting member, abstain included)
|
|
50
|
+
threshold: number; // CONSENSUS_FRACTION * w_total
|
|
51
|
+
consensus: OptionTally | null; // the option clearing the threshold, or null
|
|
52
|
+
split: boolean; // true when no option clears the threshold
|
|
53
|
+
abstain_count: number;
|
|
54
|
+
needs_repair: string[]; // members whose stance line was missing/unparseable
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Tolerant of whitespace and case; requires the three explicit fields in order.
|
|
58
|
+
// `medium` is accepted as an alias for `med`.
|
|
59
|
+
const _STANCE_RE =
|
|
60
|
+
/STANCE:\s*(.+?)\s*\|\s*CONFIDENCE:\s*(high|med(?:ium)?|low)\s*\|\s*DEALBREAKER:\s*(yes|no)/gi;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse the member's stance line from their reply text. Returns the LAST stance
|
|
64
|
+
* line if several appear (the final, most-authoritative one), or `null` when no
|
|
65
|
+
* well-formed line exists — the caller treats null as a repair-marker and never
|
|
66
|
+
* infers a stance from the surrounding prose.
|
|
67
|
+
*/
|
|
68
|
+
export function parse_stance_line(text: string): Omit<StanceLine, 'member'> | null {
|
|
69
|
+
_STANCE_RE.lastIndex = 0;
|
|
70
|
+
let last: RegExpExecArray | null = null;
|
|
71
|
+
let m: RegExpExecArray | null;
|
|
72
|
+
while ((m = _STANCE_RE.exec(text)) !== null) {
|
|
73
|
+
last = m;
|
|
74
|
+
}
|
|
75
|
+
if (last === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const rawLabel = (last[1] ?? '').trim();
|
|
79
|
+
if (rawLabel.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
let conf = (last[2] ?? '').toLowerCase();
|
|
83
|
+
if (conf === 'medium') {
|
|
84
|
+
conf = 'med';
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
label: rawLabel.toLowerCase(),
|
|
88
|
+
display: rawLabel,
|
|
89
|
+
confidence: conf as Confidence,
|
|
90
|
+
dealbreaker: (last[3] ?? '').toLowerCase() === 'yes',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Tally option-level stances across members. Base weight is 1.0 per member;
|
|
96
|
+
* `W_total` sums those base weights over every member with a parseable stance
|
|
97
|
+
* (abstentions included — they raise the bar without backing any option). An
|
|
98
|
+
* option's weight is the sum of its backers' confidence factors. Consensus iff
|
|
99
|
+
* the leading option's weight ≥ ⅔ × W_total; otherwise a structured split is
|
|
100
|
+
* returned — never a forced winner, never an auto-added round.
|
|
101
|
+
*/
|
|
102
|
+
export function tally_stances(
|
|
103
|
+
members: ReadonlyArray<{ member: string; text: string }>,
|
|
104
|
+
): StanceTallyResult {
|
|
105
|
+
const parsed: StanceLine[] = [];
|
|
106
|
+
const needs_repair: string[] = [];
|
|
107
|
+
for (const { member, text } of members) {
|
|
108
|
+
const p = parse_stance_line(text);
|
|
109
|
+
if (p === null) {
|
|
110
|
+
needs_repair.push(member);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
parsed.push({ member, ...p });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const w_total = parsed.length; // base weight 1.0 each, abstain included
|
|
117
|
+
const threshold = CONSENSUS_FRACTION * w_total;
|
|
118
|
+
let abstain_count = 0;
|
|
119
|
+
|
|
120
|
+
const byLabel = new Map<string, OptionTally>();
|
|
121
|
+
for (const p of parsed) {
|
|
122
|
+
if (p.label === ABSTAIN_LABEL) {
|
|
123
|
+
abstain_count += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
let o = byLabel.get(p.label);
|
|
127
|
+
if (o === undefined) {
|
|
128
|
+
o = { label: p.display, weight: 0, backers: [], dealbreakers: 0 };
|
|
129
|
+
byLabel.set(p.label, o);
|
|
130
|
+
}
|
|
131
|
+
o.weight += CONFIDENCE_FACTOR[p.confidence];
|
|
132
|
+
o.backers.push({ member: p.member, confidence: p.confidence });
|
|
133
|
+
if (p.dealbreaker) {
|
|
134
|
+
o.dealbreakers += 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const options = [...byLabel.values()].sort(
|
|
139
|
+
(a, b) => b.weight - a.weight || a.label.localeCompare(b.label),
|
|
140
|
+
);
|
|
141
|
+
const top = options[0] ?? null;
|
|
142
|
+
// Strict-≥ against the threshold; a floating-point epsilon guards the
|
|
143
|
+
// exact-boundary case (e.g. 2.0 ≥ ⅔×3) from representation drift.
|
|
144
|
+
const consensus = top !== null && top.weight + 1e-9 >= threshold ? top : null;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
options,
|
|
148
|
+
w_total,
|
|
149
|
+
threshold,
|
|
150
|
+
consensus,
|
|
151
|
+
split: consensus === null,
|
|
152
|
+
abstain_count,
|
|
153
|
+
needs_repair,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Render the **Vote Tally** verdict section: one line per option, the threshold,
|
|
159
|
+
* and a cleared-or-escalated line. Deterministic — pure projection of the tally.
|
|
160
|
+
*/
|
|
161
|
+
export function render_vote_tally(result: StanceTallyResult): string {
|
|
162
|
+
const lines: string[] = ['### Vote Tally'];
|
|
163
|
+
if (result.options.length === 0) {
|
|
164
|
+
lines.push('- (no option-level stances parsed)');
|
|
165
|
+
}
|
|
166
|
+
for (const o of result.options) {
|
|
167
|
+
const backers = o.backers.map((b) => `${b.member} (${b.confidence})`).join(', ');
|
|
168
|
+
const db = o.dealbreakers > 0 ? ` · ${o.dealbreakers} dealbreaker(s)` : '';
|
|
169
|
+
lines.push(`- ${o.label} — ${o.weight.toFixed(2)} (${backers})${db}`);
|
|
170
|
+
}
|
|
171
|
+
if (result.abstain_count > 0) {
|
|
172
|
+
lines.push(`- abstain — ${result.abstain_count} member(s) (raises the bar)`);
|
|
173
|
+
}
|
|
174
|
+
lines.push(`Threshold: ⅔ × ${result.w_total} = ${result.threshold.toFixed(2)}`);
|
|
175
|
+
lines.push(
|
|
176
|
+
result.consensus !== null
|
|
177
|
+
? `Cleared: ${result.consensus.label} (${result.consensus.weight.toFixed(2)})`
|
|
178
|
+
: 'Escalated: no option cleared the threshold — the split is returned to the user, not forced.',
|
|
179
|
+
);
|
|
180
|
+
if (result.needs_repair.length > 0) {
|
|
181
|
+
lines.push(`Unparsed stance (repair needed): ${result.needs_repair.join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
return lines.join('\n');
|
|
184
|
+
}
|
|
@@ -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
|
+
}
|