@maestroagora/agora 1.9.0 → 1.10.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.
@@ -0,0 +1,110 @@
1
+ import { measure } from "./voice/features.mjs";
2
+ import { buildOverlapIndex, phraseOverlap } from "./voice/check.mjs";
3
+ import { segmentParagraphs, segmentSentences, tokenize } from "./voice/pipeline.mjs";
4
+
5
+ export const TASK_VOICE_SKETCH = "TASK_VOICE_SKETCH";
6
+ export const TASK_SAMPLE_FULL_FLOOR = 3;
7
+ export const TASK_SAMPLE_MAXIMUM = 10;
8
+
9
+ const median = (values) => {
10
+ if (!values.length) return null;
11
+ const sorted = [...values].sort((left, right) => left - right);
12
+ const middle = Math.floor(sorted.length / 2);
13
+ return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
14
+ };
15
+
16
+ const range = (values) => values.length
17
+ ? { minimum: Math.min(...values), median: median(values), maximum: Math.max(...values) }
18
+ : { minimum: null, median: null, maximum: null };
19
+
20
+ const normalizeSamples = (samples) => samples.map((sample, index) => {
21
+ if (typeof sample === "string") return { id: `sample-${index + 1}`, text: sample, genre: null };
22
+ if (!sample || typeof sample.text !== "string") throw new Error(`sample ${index + 1} must contain text`);
23
+ return {
24
+ id: sample.id || `sample-${index + 1}`,
25
+ text: sample.text,
26
+ genre: sample.genre || null,
27
+ };
28
+ });
29
+
30
+ const selectSamples = (samples, genre) => {
31
+ if (!genre) return { selected: samples, sameGenre: false };
32
+ const sameGenre = samples.filter((sample) => sample.genre === genre);
33
+ return sameGenre.length ? { selected: sameGenre, sameGenre: true } : { selected: samples, sameGenre: false };
34
+ };
35
+
36
+ export function buildTaskVoiceSketch(inputSamples, { genre = null } = {}) {
37
+ if (!Array.isArray(inputSamples)) throw new Error("samples must be an array");
38
+ if (inputSamples.length > TASK_SAMPLE_MAXIMUM) {
39
+ throw new Error(`task voice sketches accept at most ${TASK_SAMPLE_MAXIMUM} samples`);
40
+ }
41
+ if (inputSamples.length === 0) {
42
+ return {
43
+ kind: TASK_VOICE_SKETCH,
44
+ certified: false,
45
+ persistence: "task-only",
46
+ sample_count: 0,
47
+ confidence: "fallback",
48
+ genre,
49
+ same_genre_samples: false,
50
+ observations: ["No samples supplied. Preserve credible choices in the draft and use plain professional writing."],
51
+ measurements: null,
52
+ limitations: ["No author-sample fit can be assessed."],
53
+ };
54
+ }
55
+
56
+ const normalized = normalizeSamples(inputSamples);
57
+ const { selected, sameGenre } = selectSamples(normalized, genre);
58
+ const text = selected.map((sample) => sample.text).join("\n\n");
59
+ const paragraphs = segmentParagraphs(text);
60
+ const sentences = paragraphs.flatMap((paragraph) => segmentSentences(paragraph));
61
+ const sentenceWords = sentences.map((sentence) => tokenize(sentence).length);
62
+ const paragraphWords = paragraphs.map((paragraph) => tokenize(paragraph).length);
63
+ const measured = measure(text);
64
+ const lowConfidence = selected.length < TASK_SAMPLE_FULL_FLOOR;
65
+
66
+ return {
67
+ kind: TASK_VOICE_SKETCH,
68
+ certified: false,
69
+ persistence: "task-only",
70
+ stores_source_text: false,
71
+ sample_count: selected.length,
72
+ supplied_sample_count: normalized.length,
73
+ confidence: lowConfidence ? "low: cautious local observations only" : "task-local: recurring habits may guide this task",
74
+ genre,
75
+ same_genre_samples: sameGenre,
76
+ observations: [
77
+ `Sentence length in the selected samples ranges from ${range(sentenceWords).minimum} to ${range(sentenceWords).maximum} words, with a median of ${range(sentenceWords).median}.`,
78
+ `Paragraph length ranges from ${range(paragraphWords).minimum} to ${range(paragraphWords).maximum} words, with a median of ${range(paragraphWords).median}.`,
79
+ `First-person singular rate is ${measured.person_and_stance.person.first_singular ?? "unavailable"} per 1000 tokens; second-person rate is ${measured.person_and_stance.person.second ?? "unavailable"}.`,
80
+ `The contraction rate is ${measured.contractions.rate_percent ?? "unavailable at this sample size"}.`,
81
+ ],
82
+ measurements: {
83
+ tokens: measured.counts.tokens,
84
+ sentences: measured.counts.sentences,
85
+ paragraphs: measured.counts.paragraphs,
86
+ sentence_words: range(sentenceWords),
87
+ paragraph_words: range(paragraphWords),
88
+ punctuation_per_1000: measured.punctuation_per_1000,
89
+ person_per_1000: measured.person_and_stance.person,
90
+ contraction_rate_percent: measured.contractions.rate_percent,
91
+ },
92
+ limitations: [
93
+ "This sketch does not establish identity, authorship, statistical matching, personality, or approval.",
94
+ lowConfidence
95
+ ? "One or two samples support only obvious local observations."
96
+ : "Three to ten samples support task-local guidance, not a persistent certified profile.",
97
+ "Measurements describe the samples and never set generation quotas.",
98
+ "Source facts, examples, metaphors, slogans, anecdotes, and distinctive phrases must not transfer.",
99
+ ],
100
+ };
101
+ }
102
+
103
+ export function buildTaskSampleOverlapIndex(inputSamples) {
104
+ const samples = normalizeSamples(inputSamples);
105
+ return buildOverlapIndex(samples.map((sample) => ({ source: sample.id, tokens: tokenize(sample.text) })));
106
+ }
107
+
108
+ export function checkTaskSampleOverlap(draftText, index) {
109
+ return phraseOverlap(draftText, index);
110
+ }
@@ -141,19 +141,21 @@ export const CONTRACTION_PAIRS = [
141
141
  { contracted: ["let's"], expanded: [["let", "us"]] },
142
142
  ];
143
143
 
144
- // The generic AI-vocabulary list the tell gate bans. A profile's owned list can
145
- // suppress the ban for a measured word, and only for the words it measured.
146
- // Sourced from the vocabulary section of the canonical reference.
144
+ // The priority anti-AI vocabulary list. A profile's owned list records measured
145
+ // author habit but does not suppress this ban automatically. It supplies only
146
+ // candidate evidence for the narrow review described by the skill reference.
147
147
  export const GENERIC_AI_VOCABULARY = [
148
- "bespoke", "bolster", "breathtaking", "comprehensive", "craft", "curated",
149
- "cutting-edge", "delve", "elevate", "embark", "empower", "enhance",
150
- "ecosystem", "essential", "facilitate", "forefront", "forge", "foster",
151
- "game-changer", "groundbreaking", "harness", "holistic", "innovative",
152
- "intricate", "invaluable", "journey", "landscape", "leverage", "meticulous",
153
- "multifaceted", "navigate", "nuanced", "paramount", "pivotal", "powerhouse",
154
- "profound", "realm", "revolutionary", "robust", "seamless", "showcase",
155
- "spearhead", "state-of-the-art", "streamline", "tapestry", "testament",
156
- "trailblazer", "transformative", "underscore", "unleash", "unlock",
148
+ "backbone", "bespoke", "best-in-class", "bolster", "breathtaking",
149
+ "comprehensive", "cornerstone", "craft", "crucial", "curated", "cutting-edge",
150
+ "delve", "drive", "ecosystem", "elevate", "empower", "enhance", "essential",
151
+ "facilitate", "forefront", "forge", "foster", "frontier", "game-changer",
152
+ "groundbreaking", "harness", "holistic", "indispensable", "intricate",
153
+ "invaluable", "journey", "landscape", "leverage", "lifeblood", "meticulous",
154
+ "multifaceted", "navigate", "notable", "noteworthy", "nuanced", "optimize",
155
+ "paramount", "pivotal", "powerhouse", "profound", "realm", "remarkable",
156
+ "revolutionary", "rich", "robust", "seamless", "showcase", "significant",
157
+ "spearhead", "state-of-the-art", "streamline", "stunning", "tapestry",
158
+ "testament", "trailblazer", "transformative", "underscore", "unleash", "unlock",
157
159
  "unparalleled", "unprecedented", "vibrant", "vital", "world-class",
158
160
  ];
159
161
 
@@ -79,8 +79,8 @@ export function ownedVocabulary(documents, certified) {
79
79
 
80
80
  return {
81
81
  withheld: false,
82
- // The allowlist is the intersection with the generic ban list. Only a word
83
- // the tell gate would otherwise strip needs an exception written down.
82
+ // Keep the public field name for profile-schema compatibility. The entries
83
+ // are candidate exceptions that require a separate load-bearing review.
84
84
  allowlist: qualifying.filter((entry) => AI_VOCABULARY.has(entry.token)),
85
85
  distinctive: qualifying
86
86
  .filter((entry) => !AI_VOCABULARY.has(entry.token) && !ALL_FUNCTION_WORDS.has(entry.token))
@@ -386,7 +386,7 @@ function notCaptured(measured, gates, corpus) {
386
386
  );
387
387
  if (curly > 0) {
388
388
  lines.push(
389
- `- The corpus contains ${curly} curly quote characters. The curly-quote ban is not vocabulary, so the owned-vocabulary exception does not reach it and final copy uses straight quotes.`,
389
+ `- The corpus contains ${curly} curly quote characters. The priority anti-AI standard treats typography separately from vocabulary, so final copy uses straight quotes.`,
390
390
  );
391
391
  }
392
392
 
@@ -444,16 +444,16 @@ export function renderProfile({ name, measured, gates, corpus, pipeline, now })
444
444
  const excerpts = selectExcerpts(corpus.documents, measured);
445
445
 
446
446
  const vocabulary = [
447
- "**Owned.** Words measured as recurring across independent documents in this corpus. The list is an allowlist against the generic AI-vocabulary ban and reaches those words only. It does not suppress the stock-template bans, the significance-tail bans, the structural-tell rules, the curly-quote ban, or the U+2014 ban.",
447
+ "**Owned.** Words measured as recurring across independent documents in this corpus. Entries that also appear on the priority anti-AI vocabulary list are candidate exceptions, not an automatic allowlist. Retain one only when it is load-bearing, exact, technically required, part of a verified proper name or immutable text, or explicitly required by the current user or house style. Nothing here suppresses the stock-template bans, significance-tail bans, structural-tell rules, curly-quote ban, or U+2014 ban.",
448
448
  "",
449
449
  ];
450
450
  if (owned.withheld) {
451
451
  vocabulary.push(
452
- "No allowlist is issued. The corpus is below the certification floor, and a word cannot be shown to recur across genres in a corpus that has one. An allowlist assembled from a thin corpus is a licence built on noise.",
452
+ "No candidate exception list is issued. The corpus is below the certification floor, and a word cannot be shown to recur across genres in a corpus that has one. A preference claim assembled from a thin corpus is built on noise.",
453
453
  );
454
454
  } else if (owned.allowlist.length === 0) {
455
455
  vocabulary.push(
456
- "No word on the generic AI-vocabulary ban list met the measurement bar in this corpus, so no exception is issued. The generic ban applies in full.",
456
+ "No word on the priority anti-AI vocabulary list met the measurement bar in this corpus. The vocabulary ban applies in full.",
457
457
  );
458
458
  } else {
459
459
  vocabulary.push(