@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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/README.md +51 -16
- package/package.json +7 -3
- package/scripts/install.mjs +5 -0
- package/scripts/style-audit-core.mjs +267 -0
- package/scripts/style-audit.mjs +54 -0
- package/scripts/task-voice-sketch.mjs +110 -0
- package/scripts/voice/lexicon.mjs +14 -12
- package/scripts/voice/profile.mjs +6 -6
- package/skills/agora/SKILL.md +113 -311
- package/skills/agora/references/agora-case-study-runtime.md +31 -0
- package/skills/agora/references/agora-conversion-runtime.md +49 -0
- package/skills/agora/references/agora-craft.md +7 -15
- package/skills/agora/references/agora-marketing-runtime.md +85 -0
- package/skills/agora/references/agora-marketing.md +3 -67
- package/skills/agora/references/agora-science.md +21 -3
- package/skills/agora/references/agora-voice.md +43 -17
- package/skills/agora/references/agora-writing-runtime.md +259 -0
- package/skills/agora/references/human-voice-editing-reference.md +717 -0
|
@@ -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
|
|
145
|
-
//
|
|
146
|
-
//
|
|
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
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
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
|
-
//
|
|
83
|
-
//
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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(
|