@maestroagora/agora 1.8.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 +52 -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 -330
- 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 +39 -75
- package/skills/agora/references/agora-science.md +34 -5
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestro-agora",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"displayName": "Maestro: Agora",
|
|
5
5
|
"description": "Use /agora for persuasive writing, technical explanation, case studies, investment analysis, spoken or written copy, and explicit publication artifact audits.",
|
|
6
6
|
"author": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maestro-agora",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Maestro: Agora writes clear persuasion, technical explanations, case studies, and investment communication, then audits local publication artifacts when requested.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mark Laursen",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"composerIcon": "./assets/icon.png",
|
|
39
39
|
"defaultPrompt": [
|
|
40
40
|
"/agora sell Write the strongest hero from this brief and destination.",
|
|
41
|
-
"/agora
|
|
41
|
+
"/agora technical Explain this system for the technical reader I specify.",
|
|
42
42
|
"/agora case study Turn this project material into the case study I describe.",
|
|
43
43
|
"/agora invest Build this fundraising asset from my thesis, claims, urgency, and plan.",
|
|
44
44
|
"/agora publication audit Inspect this local file for hidden Unicode, metadata, and provenance."
|
package/README.md
CHANGED
|
@@ -24,10 +24,12 @@ Use it for landing pages, heroes, ads, product copy, sales outreach, investor co
|
|
|
24
24
|
|---|---|
|
|
25
25
|
| Persuasion | Argument, consequence, reason to believe, CTA, channel fit, and rhetorical force |
|
|
26
26
|
| Heroes and short sales copy | Awareness, claim saturation, traffic source, promise grammar, destination fidelity, and the complete first-screen composition |
|
|
27
|
-
| `SCIENCE` |
|
|
27
|
+
| `SCIENCE` | Research findings, methods, statistics, uncertainty, scientific explanation, sources, and limits |
|
|
28
|
+
| `TECHNICAL` | Documented system behavior, architecture, APIs, implementation detail, and technical evaluation |
|
|
28
29
|
| `CASE_STUDY` | Real projects, fictional mocks, and concept portfolios shaped around the story and status you choose |
|
|
29
30
|
| `INVEST` | Fundraising, diligence, and capital-allocation communication shaped around your thesis, claims, urgency, and next decision |
|
|
30
|
-
| `VOICE` | A measured
|
|
31
|
+
| `VOICE` | A measured persistent profile or a task-only sketch from samples supplied with one task |
|
|
32
|
+
| Human-voice editing | One exact canonical standard for output bans, structural cleanup, meaning preservation, genre fit, and detector limits |
|
|
31
33
|
| Written GEO/AEO | Clear entities, self-contained passages, source transparency, and relevant publication checks |
|
|
32
34
|
|
|
33
35
|
Agora returns one ready-to-use result by default. Alternatives, internal routes, and rationale stay out of the final copy unless requested.
|
|
@@ -111,7 +113,7 @@ Choose a primary mode when you want to override inference:
|
|
|
111
113
|
Add modifiers when the subject or asset needs them:
|
|
112
114
|
|
|
113
115
|
```text
|
|
114
|
-
/agora sell
|
|
116
|
+
/agora sell technical Write a product section for engineers with this documented behavior.
|
|
115
117
|
/agora inform science Explain this study for a general audience.
|
|
116
118
|
/agora sell case study Write a customer success case from this approved project record.
|
|
117
119
|
/agora inform science case study Explain this engineering implementation and its measured limits.
|
|
@@ -122,6 +124,23 @@ Add modifiers when the subject or asset needs them:
|
|
|
122
124
|
|
|
123
125
|
In Codex, `$agora` and the skills picker can also select the installed skill. Other hosts may expose skills through a picker or mention syntax. Asking the agent to use the Agora skill remains portable.
|
|
124
126
|
|
|
127
|
+
## Human-voice editing standard
|
|
128
|
+
|
|
129
|
+
Agora ships the attached source unchanged as `human-voice-editing-reference.md`. It is the single canonical authority for banned vocabulary, connectives, templates, significance tails, punctuation, prompt leakage, structural tells, author samples, meaning preservation, genre fit, detector limits, and privacy cautions.
|
|
130
|
+
|
|
131
|
+
The standard removes generated stock vocabulary, connective phrases, templates, significance tails, prompt leakage, repeated structural patterns, raw channel residue, fake human texture, and detector-driven corruption. It preserves the current user's facts, required wording, technical terms, genre, and intended meaning. Measured voice profiles document author habits but do not automatically exempt banned vocabulary.
|
|
132
|
+
|
|
133
|
+
Plain professional writing is the default. AI, software, data, security, engineering, and infrastructure products stay in plain language for ordinary readers. A specialized register activates only when the deliverable needs scientific findings, technical detail, legal wording, or formal review.
|
|
134
|
+
|
|
135
|
+
Run the deterministic style review on a file:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
npx -y -p @maestroagora/agora@latest agora-style-audit ./draft.md
|
|
139
|
+
npx -y -p @maestroagora/agora@latest agora-style-audit ./draft.md --register technical --json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Hard findings cover banned typography, canonical vocabulary and templates, prompt leakage, and significance tails. Review warnings cover plain-register control words, long or joined sentences, repeated openings and paragraph shapes, noun and preposition stacks, false reframes, question fragments, corporate helper phrases, and legalistic leakage. The tool does not rewrite text, validate facts, infer authorship, or promise detector evasion.
|
|
143
|
+
|
|
125
144
|
## Publication privacy and provenance
|
|
126
145
|
|
|
127
146
|
Agora writes copy. It does not create SVG, PNG, JPEG, PDF, DOCX, or PPTX files by itself. Other document, presentation, PDF, site, design, or image tools may place Agora copy into those artifacts.
|
|
@@ -149,7 +168,7 @@ Model-level text watermarks are statistical generation signals, not hidden chara
|
|
|
149
168
|
|
|
150
169
|
## Routing model
|
|
151
170
|
|
|
152
|
-
Agora chooses a primary mode
|
|
171
|
+
Agora resolves the audience, genre, writing register, and voice before it plans the argument. It then chooses a primary mode, publication surface, and any domain or asset modifiers. Voice shapes expression from the first sentence while preserving required facts and wording.
|
|
153
172
|
|
|
154
173
|
### Primary modes
|
|
155
174
|
|
|
@@ -167,11 +186,14 @@ Agora chooses a primary mode first, then the publication surface, then any domai
|
|
|
167
186
|
|
|
168
187
|
| Modifier | Function |
|
|
169
188
|
|---|---|
|
|
170
|
-
| `SCIENCE` | Adds
|
|
189
|
+
| `SCIENCE` | Adds research findings, methods, statistics, uncertainty, and optional scientific review |
|
|
190
|
+
| `TECHNICAL` | Adds documented system behavior, architecture, APIs, implementation detail, and technical evaluation |
|
|
171
191
|
| `CASE_STUDY` | Adds case-study structure, result framing, and optional attribution or permission review |
|
|
172
|
-
| `VOICE` | Applies a measured
|
|
192
|
+
| `VOICE` | Applies a measured persistent profile or a task-only sketch |
|
|
173
193
|
|
|
174
|
-
The modifiers can compose. A technical fundraising case may use `INVEST +
|
|
194
|
+
The modifiers can compose. A technical fundraising case may use `INVEST + TECHNICAL + CASE_STUDY`. A founder-voiced scientific product video may use `SELL + SCIENCE + VOICE + HYBRID`.
|
|
195
|
+
|
|
196
|
+
Subject matter alone does not activate `SCIENCE` or `TECHNICAL`. A nontechnical homepage for an AI or security product remains `SELL + PLAIN`.
|
|
175
197
|
|
|
176
198
|
### Surfaces
|
|
177
199
|
|
|
@@ -221,12 +243,12 @@ Promise grammar matters. `See X`, `Learn how to X`, `We help you X`, `Do X more
|
|
|
221
243
|
|
|
222
244
|
## Scientific and technical communication
|
|
223
245
|
|
|
224
|
-
|
|
246
|
+
Specialized explanation supports three internal routes:
|
|
225
247
|
|
|
226
248
|
| Route | Subject |
|
|
227
249
|
|---|---|
|
|
228
250
|
| `EMPIRICAL` | Studies, experiments, observations, measurements, and findings |
|
|
229
|
-
| `TECHNICAL` | Systems, interfaces, architecture,
|
|
251
|
+
| `TECHNICAL` | Systems, interfaces, architecture, APIs, implementation, dependencies, and failure behavior |
|
|
230
252
|
| `MIXED` | Measured findings plus technical mechanism |
|
|
231
253
|
|
|
232
254
|
When you request scientific claim review, Agora can classify material as observation, established fact or consensus, model, interpretation, implication, recommendation, hypothesis, speculation, or unknown. Without that request, it follows the certainty and framing in your brief.
|
|
@@ -285,7 +307,7 @@ Legal, securities, offering, solicitation, eligibility, and disclosure review is
|
|
|
285
307
|
|
|
286
308
|
## Measured voice profiles
|
|
287
309
|
|
|
288
|
-
`VOICE` modifies another mode rather than replacing it.
|
|
310
|
+
`VOICE` modifies another mode rather than replacing it. Persistent profiles are measured from the corpus you select, not improvised from adjectives.
|
|
289
311
|
|
|
290
312
|
Build and inspect a profile with the shipped engine:
|
|
291
313
|
|
|
@@ -304,6 +326,10 @@ A profile records sentence and paragraph distributions, function words, punctuat
|
|
|
304
326
|
|
|
305
327
|
Profiles live at `~/.agora/voices/`, outside the replaceable skill directory. A default profile can apply across modes. Use `--no-voice`, `neutral`, or `--voice <name>` per request. Your required wording overrides habitual profile tendencies.
|
|
306
328
|
|
|
329
|
+
Task-only voice sketches are separate. When you supply authentic samples with one task, Agora can use three to ten same-genre samples without the 5,000-word persistent-profile floor. One or two samples support only cautious local observations. The sketch records visible habits, stays inside the task, is never certified or stored as a profile, and never claims authorship or statistical identity. Agora does not copy distinctive phrases, facts, examples, metaphors, slogans, or anecdotes from the samples. With no samples, it uses plain professional writing and preserves credible choices from your draft.
|
|
330
|
+
|
|
331
|
+
Sentence and paragraph measurements remain available in `voice check`. They are diagnostics, not generation quotas. Agora does not force a 25-word sentence, alternate sentence lengths, or write toward a target distribution.
|
|
332
|
+
|
|
307
333
|
Agora builds or applies the profile you request. You are responsible for corpus rights, identity use, attribution, endorsements, disclosure, and publication.
|
|
308
334
|
|
|
309
335
|
## User control and responsibility
|
|
@@ -342,6 +368,11 @@ skills/agora/
|
|
|
342
368
|
|-- scripts/
|
|
343
369
|
| `-- publication-audit.mjs
|
|
344
370
|
`-- references/
|
|
371
|
+
|-- agora-writing-runtime.md
|
|
372
|
+
|-- agora-marketing-runtime.md
|
|
373
|
+
|-- agora-conversion-runtime.md
|
|
374
|
+
|-- agora-case-study-runtime.md
|
|
375
|
+
|-- human-voice-editing-reference.md
|
|
345
376
|
|-- agora-case-studies.md
|
|
346
377
|
|-- agora-conversion.md
|
|
347
378
|
|-- agora-craft.md
|
|
@@ -352,9 +383,12 @@ skills/agora/
|
|
|
352
383
|
`-- agora-voice.md
|
|
353
384
|
```
|
|
354
385
|
|
|
355
|
-
`SKILL.md` contains routing
|
|
386
|
+
`SKILL.md` contains routing. Ordinary work loads the compact writing and job runtimes first. Deep research stays available for explicit research, source review, scientific, technical, legal, audit, diligence, and maintainer work.
|
|
356
387
|
|
|
357
|
-
- `
|
|
388
|
+
- `human-voice-editing-reference.md` is the exact canonical human-voice editing authority.
|
|
389
|
+
- `agora-writing-runtime.md` is the small plain-language writing contract loaded for every writing task.
|
|
390
|
+
- `agora-marketing-runtime.md`, `agora-conversion-runtime.md`, and `agora-case-study-runtime.md` keep ordinary drafting out of the research register.
|
|
391
|
+
- `agora-marketing.md` preserves deep doctrine, source material, research grades, and maintainer guidance.
|
|
358
392
|
- `agora-craft.md` adds headlines, heroes, awareness and sophistication, emotion, and prose rhythm.
|
|
359
393
|
- `agora-science.md` adds empirical and technical explanation plus optional scientific claim review.
|
|
360
394
|
- `agora-case-studies.md` adds case structure, results, and optional attribution, permission, and confidentiality review.
|
|
@@ -370,13 +404,13 @@ Research informed Agora, but research custody is separate from distribution.
|
|
|
370
404
|
|
|
371
405
|
The public repository and npm package must not contain raw or corrected transcripts, caption files, supplied PDFs or office documents, audio, video, private source identities, model-output scratch, research working files, local paths, or assigned secrets.
|
|
372
406
|
|
|
373
|
-
`npm run check` runs validation, deterministic tests, the exact package allowlist, and release hygiene. `npm run release:check`
|
|
407
|
+
`npm run check` runs validation, deterministic tests, the exact package allowlist, and release hygiene. `npm run release:check` adds the current frozen blind-evaluation evidence gate. `npm pack` and `npm publish` invoke it through `prepack` and `prepublishOnly`.
|
|
374
408
|
|
|
375
|
-
|
|
409
|
+
`evals/releases/current.json` is the explicit release contract. Its version must match `package.json`, the frozen manifest, schemas, release gates, adjudications, records, custody hashes, and external artifact manifests. Missing or stale human-writing evidence blocks a publishable release. Live generation does not run inside unit tests.
|
|
376
410
|
|
|
377
411
|
The versioned directories under `evals/blind/` are public pairwise-release artifacts, not permanently secret holdouts. `v1.2.0`, `v1.4.0`, `v1.5.0`, and `v1.7.0` are frozen by exact tree hashes in `evals/releases/locks.json`; validation fails on additions, deletions, or edits.
|
|
378
412
|
|
|
379
|
-
`evals/prospective/
|
|
413
|
+
`evals/prospective/human-writing-v1.0.0/` is the development source for the human-writing partition. The frozen current manifest lives under `evals/blind/` after prospective generation and blind pairwise review. Earlier fixture sets remain frozen for regression analysis and historical reproducibility.
|
|
380
414
|
|
|
381
415
|
Deterministic tests verify instruction structure, routing contracts, static invariants, and evaluation-record shape. Versioned blind-evaluation tooling remains available for repeatable development analysis without turning model preference into a universal conversion claim.
|
|
382
416
|
|
|
@@ -390,12 +424,14 @@ npm pack --dry-run --json
|
|
|
390
424
|
npx -y @maestroagora/agora --dry-run
|
|
391
425
|
```
|
|
392
426
|
|
|
393
|
-
The mandatory release gate checks skill structure, routing contracts, user-authority boundaries,
|
|
427
|
+
The mandatory release gate checks skill structure, routing contracts, factual and user-authority boundaries, typography, metadata, reference links, installer parity, exact npm contents, frozen evaluation-tree locks, public-tree hygiene, deterministic tests, and checked current blind evidence. Generation and judging happen before evidence is frozen, not during routine tests.
|
|
394
428
|
|
|
395
429
|
## Change record
|
|
396
430
|
|
|
397
431
|
| Version | What changed |
|
|
398
432
|
|---|---|
|
|
433
|
+
| 1.10.0 | Makes plain professional writing the default, separates compact runtime rules from maintainer research, ships the exact canonical human-voice source, adds task-only voice sketches and a deterministic style audit, routes technical language by job and audience, removes forced rhythm targets, and binds release checks to current human-writing evidence. |
|
|
434
|
+
| 1.9.0 | Makes first-read clarity automatic. Agora now drafts for factual completeness, rewrites for literal clarity, preserves qualifiers across short passages, names concrete actors and observable results, and rejects vague referents, hidden metaphors, noun stacks, and revisions that only rename ambiguity. Adds rendered label-clearance guidance for technical diagrams. |
|
|
399
435
|
| 1.8.0 | Adds opt-in publication privacy and provenance review plus a packaged read-only audit CLI. Reports configured hidden Unicode, document and image metadata, Office review material, and C2PA carrier or validation signals without changing source files. Redacts sensitive values and paths by default, preserves unknown coverage, and makes no watermark-removal or authorship claim. |
|
|
400
436
|
| 1.7.0 | Adds a progressively loaded conversion-context reference with bounded conversion priors, downstream outcome matching, self-serve and enterprise route design, pricing decision contracts, proof placement, and contradiction handling. Tightens closed-world fact preservation and limits written GEO/AEO requirements to indexable public work. |
|
|
401
437
|
| 1.6.0 | Expands user control across every writing mode. User-selected claims, fiction, urgency, attribution, profile use, and publication choices now control the draft. Claim, evidence, permission, disclosure, confidentiality, diligence, and compliance review are opt-in. Adds a user-responsibility disclaimer and an accurate privacy notice. |
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maestroagora/agora",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Install Maestro: Agora for persuasive writing, technical explanation, case studies, investment communication, and publication artifact review.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agora": "scripts/install.mjs",
|
|
8
8
|
"agora-publication-audit": "skills/agora/scripts/publication-audit.mjs",
|
|
9
|
+
"agora-style-audit": "scripts/style-audit.mjs",
|
|
9
10
|
"agora-voice": "scripts/voice-measure.mjs"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
@@ -21,6 +22,9 @@
|
|
|
21
22
|
"assets/icon.png",
|
|
22
23
|
"assets/maestro-agora-banner.png",
|
|
23
24
|
"scripts/install.mjs",
|
|
25
|
+
"scripts/style-audit-core.mjs",
|
|
26
|
+
"scripts/style-audit.mjs",
|
|
27
|
+
"scripts/task-voice-sketch.mjs",
|
|
24
28
|
"scripts/voice-measure.mjs",
|
|
25
29
|
"scripts/voice",
|
|
26
30
|
"skills/agora"
|
|
@@ -32,8 +36,8 @@
|
|
|
32
36
|
"prepack": "npm run release:check",
|
|
33
37
|
"prepublishOnly": "npm run release:check",
|
|
34
38
|
"eval:release": "node scripts/release-evidence-check.mjs",
|
|
35
|
-
"release:check": "npm run check",
|
|
36
|
-
"test": "node --test tests/behavior-contract.test.mjs tests/hero-contract.test.mjs tests/conversion-context-contract.test.mjs tests/science-contract.test.mjs tests/case-study-contract.test.mjs tests/invest-contract.test.mjs tests/publication-audit.test.mjs tests/blind-summary.test.mjs tests/blind-judge-prompt.test.mjs tests/blind-judge-materialize.test.mjs tests/blind-judgment-ingest.test.mjs tests/adjudication-reducer.test.mjs tests/eval-provenance-check.test.mjs tests/release-evidence-check.test.mjs tests/release-hygiene.test.mjs tests/install.test.mjs tests/voice-measure.test.mjs",
|
|
39
|
+
"release:check": "npm run check && npm run eval:release",
|
|
40
|
+
"test": "node --test tests/behavior-contract.test.mjs tests/human-writing-contract.test.mjs tests/style-audit.test.mjs tests/task-voice-sketch.test.mjs tests/release-contract.test.mjs tests/hero-contract.test.mjs tests/conversion-context-contract.test.mjs tests/science-contract.test.mjs tests/case-study-contract.test.mjs tests/invest-contract.test.mjs tests/publication-audit.test.mjs tests/blind-summary.test.mjs tests/blind-judge-prompt.test.mjs tests/blind-judge-materialize.test.mjs tests/blind-judgment-ingest.test.mjs tests/adjudication-reducer.test.mjs tests/eval-provenance-check.test.mjs tests/release-evidence-check.test.mjs tests/release-hygiene.test.mjs tests/install.test.mjs tests/voice-measure.test.mjs",
|
|
37
41
|
"validate": "node scripts/validate.mjs"
|
|
38
42
|
},
|
|
39
43
|
"engines": {
|
package/scripts/install.mjs
CHANGED
|
@@ -252,6 +252,11 @@ async function verifySource() {
|
|
|
252
252
|
"references/agora-craft.md",
|
|
253
253
|
"references/agora-invest.md",
|
|
254
254
|
"references/agora-marketing.md",
|
|
255
|
+
"references/agora-case-study-runtime.md",
|
|
256
|
+
"references/agora-conversion-runtime.md",
|
|
257
|
+
"references/agora-marketing-runtime.md",
|
|
258
|
+
"references/agora-writing-runtime.md",
|
|
259
|
+
"references/human-voice-editing-reference.md",
|
|
255
260
|
"references/agora-publication.md",
|
|
256
261
|
"references/agora-science.md",
|
|
257
262
|
"references/agora-voice.md",
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import { GENERIC_AI_VOCABULARY } from "./voice/lexicon.mjs";
|
|
4
|
+
import { segmentParagraphs, segmentSentences, tokenize } from "./voice/pipeline.mjs";
|
|
5
|
+
|
|
6
|
+
export const REGISTERS = new Set(["plain", "technical", "scientific", "legal", "audit"]);
|
|
7
|
+
|
|
8
|
+
export const CANONICAL_CONNECTIVES = [
|
|
9
|
+
"moreover",
|
|
10
|
+
"furthermore",
|
|
11
|
+
"additionally",
|
|
12
|
+
"in addition",
|
|
13
|
+
"notably",
|
|
14
|
+
"importantly",
|
|
15
|
+
"indeed",
|
|
16
|
+
"in essence",
|
|
17
|
+
"in summary",
|
|
18
|
+
"in conclusion",
|
|
19
|
+
"ultimately",
|
|
20
|
+
"that said",
|
|
21
|
+
"on the other hand",
|
|
22
|
+
"on one hand",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const CANONICAL_TEMPLATE_PATTERNS = [
|
|
26
|
+
["important-note", /\bit(?:'s| is) important to note that\b/giu],
|
|
27
|
+
["worth-noting", /\bit(?:'s| is) worth (?:noting|mentioning) that\b/giu],
|
|
28
|
+
["fast-paced-world", /\bin today(?:'s|s) fast-paced world\b/giu],
|
|
29
|
+
["ever-evolving-landscape", /\bin the ever-evolving landscape of\b/giu],
|
|
30
|
+
["realm", /\bin the realm of\b/giu],
|
|
31
|
+
["testament", /\ba testament to\b/giu],
|
|
32
|
+
["stands-as", /\bstands as a\b/giu],
|
|
33
|
+
["serves-as", /\bserves as a\b/giu],
|
|
34
|
+
["pivotal-role", /\bplays a (?:crucial|pivotal|vital) role in\b/giu],
|
|
35
|
+
["not-only", /\bnot only\b[^.!?\n]{0,180}\bbut also\b/giu],
|
|
36
|
+
["whether-you", /\bwhether you(?:'re| are)\b[^.!?\n]{0,120}\bor\b/giu],
|
|
37
|
+
["from-to", /\bfrom\b[^,!?.\n]{1,80}\bto\b[^,!?.\n]{1,80},[^.!?\n]{0,120}\bhas\b/giu],
|
|
38
|
+
["at-core", /\bat its core\b/giu],
|
|
39
|
+
["when-it-comes", /\bwhen it comes to\b/giu],
|
|
40
|
+
["navigating-complexities", /\bnavigating the complexities of\b/giu],
|
|
41
|
+
["unlocking-potential", /\bunlocking the potential of\b/giu],
|
|
42
|
+
["harnessing-power", /\bharnessing the power of\b/giu],
|
|
43
|
+
["paving-way", /\bpaving the way for\b/giu],
|
|
44
|
+
["setting-stage", /\bsetting the stage for\b/giu],
|
|
45
|
+
["bringing-forefront", /\bbringing\b[^.!?\n]{1,100}\bto the forefront\b/giu],
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export const CANONICAL_SIGNIFICANCE_TAILS = [
|
|
49
|
+
"emphasizing the significance of",
|
|
50
|
+
"reflecting the continued relevance of",
|
|
51
|
+
"underscoring the importance of",
|
|
52
|
+
"highlighting its role in",
|
|
53
|
+
"demonstrating its impact on",
|
|
54
|
+
"marking a turning point in",
|
|
55
|
+
"cementing its place as",
|
|
56
|
+
"solidifying its reputation for",
|
|
57
|
+
"showcasing its commitment to",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const CANONICAL_PROMPT_LEAKAGE = [
|
|
61
|
+
/\bcertainly!\b/giu,
|
|
62
|
+
/\bof course!\b/giu,
|
|
63
|
+
/\babsolutely!\b/giu,
|
|
64
|
+
/\bgreat question!\b/giu,
|
|
65
|
+
/\bhere is your (?:article|blog post|essay) on\b/giu,
|
|
66
|
+
/\bas an ai language model\b/giu,
|
|
67
|
+
/\bup to my last training update\b/giu,
|
|
68
|
+
/\bi hope this helps!\b/giu,
|
|
69
|
+
/\blet me know if you(?:'d| would) like\b/giu,
|
|
70
|
+
/\bcertainly! here(?:'s| is) a\b/giu,
|
|
71
|
+
/\bsure, i can help with that\b/giu,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
export const CONTROL_ROOM_TERMS = [
|
|
75
|
+
"evidence", "evidentiary", "proof", "verified", "verification", "claim",
|
|
76
|
+
"substantiation", "provenance", "methodology", "framework", "mechanism",
|
|
77
|
+
"criterion", "criteria", "qualification", "qualifier", "causal", "entailment",
|
|
78
|
+
"proposition", "decision surface", "route", "artifact", "register", "hierarchy",
|
|
79
|
+
"gate", "audit", "ledger", "boundary", "scope", "operational",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
export const FALSE_REFRAMES = [
|
|
83
|
+
/\bthis is not just\b[^.!?\n]{1,120}[.!?]\s*it is\b/giu,
|
|
84
|
+
/\bthis is not\b[^.!?\n]{1,120}[.!?]\s*it is\b/giu,
|
|
85
|
+
/\b\w[\w -]{0,80} is more than \w/giu,
|
|
86
|
+
/\bthe real issue is not\b[^.!?\n]{1,140}[.!?]\s*it is\b/giu,
|
|
87
|
+
/\bthe question is not whether\b[^.!?\n]{1,160}\bbut how\b/giu,
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
export const QUESTION_FRAGMENT_THEATER = /\b(?:the result|the answer|the problem|the difference|the bottom line)\?/giu;
|
|
91
|
+
export const CONVERSATIONAL_THEATER = /\b(?:here is the thing|let us be honest|think about it|imagine this|picture this|you know the feeling|the bottom line is)\b/giu;
|
|
92
|
+
export const CORPORATE_HELPERS = /\b(?:provides the ability to|is designed to enable|helps to facilitate|allows users to|offers a way to|serves to|works to|aims to|seeks to|has the potential to)\b/giu;
|
|
93
|
+
export const LEGALISTIC_LEAKAGE = /\b(?:with respect to|in relation to|insofar as|pursuant to|herein|therein|whereby|for the avoidance of doubt|where applicable|subject to the foregoing|constitutes|shall)\b/giu;
|
|
94
|
+
export const EMPTY_ANALYTICAL_ENDINGS = /\bthis (?:demonstrates that|indicates the importance of|provides a strong foundation for|creates a clear pathway to|supports the broader objective of|aligns with|reinforces|reflects)\b/giu;
|
|
95
|
+
|
|
96
|
+
const HARD = "hard";
|
|
97
|
+
const WARNING = "warning";
|
|
98
|
+
const EXACT_START = "<!-- agora-style-audit: exact-start -->";
|
|
99
|
+
const EXACT_END = "<!-- agora-style-audit: exact-end -->";
|
|
100
|
+
|
|
101
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102
|
+
|
|
103
|
+
const lineAndColumn = (text, index) => {
|
|
104
|
+
const before = text.slice(0, index);
|
|
105
|
+
const lines = before.split("\n");
|
|
106
|
+
return { line: lines.length, column: lines.at(-1).length + 1 };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const finding = (text, match, severity, category, rule, message) => ({
|
|
110
|
+
severity,
|
|
111
|
+
category,
|
|
112
|
+
rule,
|
|
113
|
+
message,
|
|
114
|
+
excerpt: match[0],
|
|
115
|
+
...lineAndColumn(text, match.index),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const maskRange = (characters, start, end) => {
|
|
119
|
+
for (let index = Math.max(0, start); index < Math.min(characters.length, end); index += 1) {
|
|
120
|
+
if (characters[index] !== "\n") characters[index] = " ";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export function maskExactText(text, exactTextRanges = []) {
|
|
125
|
+
const characters = [...text];
|
|
126
|
+
const patterns = [
|
|
127
|
+
/```[\s\S]*?```/g,
|
|
128
|
+
/~~~[\s\S]*?~~~/g,
|
|
129
|
+
/`[^`\n]+`/g,
|
|
130
|
+
/^\s*>.*$/gm,
|
|
131
|
+
new RegExp(`${escapeRegExp(EXACT_START)}[\\s\\S]*?${escapeRegExp(EXACT_END)}`, "g"),
|
|
132
|
+
];
|
|
133
|
+
for (const pattern of patterns) {
|
|
134
|
+
for (const match of text.matchAll(pattern)) maskRange(characters, match.index, match.index + match[0].length);
|
|
135
|
+
}
|
|
136
|
+
for (const rangeValue of exactTextRanges) maskRange(characters, rangeValue.start, rangeValue.end);
|
|
137
|
+
return characters.join("");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const scanRegex = (text, masked, regex, severity, category, rule, message, findings) => {
|
|
141
|
+
regex.lastIndex = 0;
|
|
142
|
+
for (const match of masked.matchAll(regex)) findings.push(finding(text, match, severity, category, rule, message));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const sentenceEntries = (text) => {
|
|
146
|
+
const entries = [];
|
|
147
|
+
let searchFrom = 0;
|
|
148
|
+
for (const paragraph of segmentParagraphs(text)) {
|
|
149
|
+
for (const sentence of segmentSentences(paragraph)) {
|
|
150
|
+
const index = text.indexOf(sentence, searchFrom);
|
|
151
|
+
entries.push({ sentence, index: index < 0 ? searchFrom : index, tokens: tokenize(sentence) });
|
|
152
|
+
if (index >= 0) searchFrom = index + sentence.length;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return entries;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const sentenceWarnings = (text, masked, findings) => {
|
|
159
|
+
const entries = sentenceEntries(masked);
|
|
160
|
+
const joinWords = new Set(["and", "but", "or", "because", "although", "while", "whereas", "which", "that", "whereby", "if", "when", "since"]);
|
|
161
|
+
const prepositions = new Set(["of", "for", "with", "in", "through"]);
|
|
162
|
+
const nounSuffix = /(?:tion|sion|ment|ness|ity|ance|ence|al|ure|ism|ship)$/u;
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const match = { 0: entry.sentence, index: entry.index };
|
|
165
|
+
if (entry.tokens.length > 28) findings.push(finding(text, match, WARNING, "sentence", "sentence-over-28-words", `Ordinary sentence has ${entry.tokens.length} words; review whether a split preserves meaning.`));
|
|
166
|
+
const joins = entry.tokens.filter((token) => joinWords.has(token)).length;
|
|
167
|
+
if (joins >= 3) findings.push(finding(text, match, WARNING, "sentence", "three-or-more-joined-clauses", `Sentence contains ${joins} clause-join proxies.`));
|
|
168
|
+
const prepCount = entry.tokens.filter((token) => prepositions.has(token)).length;
|
|
169
|
+
if (prepCount >= 5) findings.push(finding(text, match, WARNING, "sentence", "preposition-stack", `Sentence contains ${prepCount} common prepositions.`));
|
|
170
|
+
let nounRun = 0;
|
|
171
|
+
let longest = 0;
|
|
172
|
+
for (const token of entry.tokens) {
|
|
173
|
+
nounRun = nounSuffix.test(token) ? nounRun + 1 : 0;
|
|
174
|
+
longest = Math.max(longest, nounRun);
|
|
175
|
+
}
|
|
176
|
+
if (longest >= 3) findings.push(finding(text, match, WARNING, "sentence", "noun-stack", "Sentence contains a possible abstract noun stack."));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (let index = 2; index < entries.length; index += 1) {
|
|
180
|
+
const openings = entries.slice(index - 2, index + 1).map((entry) => entry.tokens.slice(0, 2).join(" "));
|
|
181
|
+
if (openings[0] && openings.every((opening) => opening === openings[0])) {
|
|
182
|
+
const entry = entries[index - 2];
|
|
183
|
+
findings.push(finding(text, { 0: openings[0], index: entry.index }, WARNING, "structure", "repeated-openings", "Three consecutive sentences share the same two-word opening."));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const paragraphWarnings = (text, masked, findings) => {
|
|
189
|
+
const paragraphs = segmentParagraphs(masked).map((paragraph) => ({
|
|
190
|
+
paragraph,
|
|
191
|
+
words: tokenize(paragraph).length,
|
|
192
|
+
sentences: segmentSentences(paragraph).length,
|
|
193
|
+
}));
|
|
194
|
+
for (let index = 2; index < paragraphs.length; index += 1) {
|
|
195
|
+
const group = paragraphs.slice(index - 2, index + 1);
|
|
196
|
+
const sameSentenceCount = group.every((entry) => entry.sentences === group[0].sentences);
|
|
197
|
+
const smallest = Math.min(...group.map((entry) => entry.words));
|
|
198
|
+
const largest = Math.max(...group.map((entry) => entry.words));
|
|
199
|
+
if (sameSentenceCount && smallest > 0 && largest / smallest <= 1.15) {
|
|
200
|
+
const start = text.indexOf(group[0].paragraph);
|
|
201
|
+
findings.push(finding(text, { 0: group[0].paragraph.slice(0, 80), index: Math.max(0, start) }, WARNING, "structure", "repeated-paragraph-shape", "Three consecutive paragraphs have nearly identical shapes."));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export function auditText(text, { register = "plain", exactTextRanges = [] } = {}) {
|
|
207
|
+
if (!REGISTERS.has(register)) throw new Error(`unknown register: ${register}`);
|
|
208
|
+
const masked = maskExactText(text, exactTextRanges);
|
|
209
|
+
const findings = [];
|
|
210
|
+
|
|
211
|
+
scanRegex(text, masked, /\u2014/gu, HARD, "punctuation", "u+2014", "Unicode U+2014 is forbidden outside exact text.", findings);
|
|
212
|
+
scanRegex(text, masked, /[\u2018\u2019\u201c\u201d]/gu, HARD, "punctuation", "smart-quote", "Generated smart quotes are forbidden outside exact text.", findings);
|
|
213
|
+
|
|
214
|
+
for (const word of GENERIC_AI_VOCABULARY) {
|
|
215
|
+
scanRegex(text, masked, new RegExp(`\\b${escapeRegExp(word)}\\b`, "giu"), HARD, "canonical", `banned-vocabulary:${word}`, "Canonical banned vocabulary requires an explicit exact-text exception.", findings);
|
|
216
|
+
}
|
|
217
|
+
for (const phrase of CANONICAL_CONNECTIVES) {
|
|
218
|
+
scanRegex(text, masked, new RegExp(`\\b${escapeRegExp(phrase)}\\b`, "giu"), HARD, "canonical", `banned-connective:${phrase}`, "Canonical banned connective requires an explicit formal-genre or exact-text exception.", findings);
|
|
219
|
+
}
|
|
220
|
+
for (const [id, pattern] of CANONICAL_TEMPLATE_PATTERNS) {
|
|
221
|
+
scanRegex(text, masked, pattern, HARD, "canonical", `banned-template:${id}`, "Canonical stock template is forbidden.", findings);
|
|
222
|
+
}
|
|
223
|
+
for (const phrase of CANONICAL_SIGNIFICANCE_TAILS) {
|
|
224
|
+
scanRegex(text, masked, new RegExp(`,?\\s*${escapeRegExp(phrase)}\\b`, "giu"), HARD, "canonical", `significance-tail:${phrase}`, "Canonical significance tail is forbidden.", findings);
|
|
225
|
+
}
|
|
226
|
+
for (const [index, pattern] of CANONICAL_PROMPT_LEAKAGE.entries()) {
|
|
227
|
+
scanRegex(text, masked, pattern, HARD, "canonical", `prompt-leakage:${index + 1}`, "Prompt or task meta-commentary is forbidden.", findings);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (register === "plain") {
|
|
231
|
+
for (const term of CONTROL_ROOM_TERMS) {
|
|
232
|
+
scanRegex(text, masked, new RegExp(`\\b${escapeRegExp(term)}\\b`, "giu"), WARNING, "register", `control-room:${term}`, "Review whether the reader needs this internal control term.", findings);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const [index, pattern] of FALSE_REFRAMES.entries()) scanRegex(text, masked, pattern, WARNING, "structure", `false-reframe:${index + 1}`, "Review generic false-reframe construction.", findings);
|
|
237
|
+
scanRegex(text, masked, QUESTION_FRAGMENT_THEATER, WARNING, "structure", "question-fragment-theater", "Review generic question-fragment theater.", findings);
|
|
238
|
+
scanRegex(text, masked, CONVERSATIONAL_THEATER, WARNING, "structure", "conversational-theater", "Review conversational theater against the active voice.", findings);
|
|
239
|
+
scanRegex(text, masked, CORPORATE_HELPERS, WARNING, "wording", "corporate-helper", "Replace a corporate helper phrase with a direct verb when meaning permits.", findings);
|
|
240
|
+
if (register !== "legal" && register !== "audit") scanRegex(text, masked, LEGALISTIC_LEAKAGE, WARNING, "register", "legalistic-leakage", "Review legalistic wording outside a legal or audit register.", findings);
|
|
241
|
+
scanRegex(text, masked, EMPTY_ANALYTICAL_ENDINGS, WARNING, "structure", "empty-analytical-ending", "Replace an empty analytical ending with the fact or needed inference.", findings);
|
|
242
|
+
|
|
243
|
+
sentenceWarnings(text, masked, findings);
|
|
244
|
+
paragraphWarnings(text, masked, findings);
|
|
245
|
+
|
|
246
|
+
findings.sort((left, right) => left.line - right.line || left.column - right.column || left.rule.localeCompare(right.rule));
|
|
247
|
+
const hardFailures = findings.filter((item) => item.severity === HARD);
|
|
248
|
+
const warnings = findings.filter((item) => item.severity === WARNING);
|
|
249
|
+
return {
|
|
250
|
+
schema_version: 1,
|
|
251
|
+
register,
|
|
252
|
+
pass: hardFailures.length === 0,
|
|
253
|
+
hard_failure_count: hardFailures.length,
|
|
254
|
+
warning_count: warnings.length,
|
|
255
|
+
findings,
|
|
256
|
+
limits: {
|
|
257
|
+
authorship: "not assessed",
|
|
258
|
+
detector_evasion: "not promised",
|
|
259
|
+
factual_validation: "outside this lexical and structural tool",
|
|
260
|
+
rewriting: "not performed",
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export async function auditFile(path, options = {}) {
|
|
266
|
+
return auditText(await readFile(path, "utf8"), options);
|
|
267
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { auditFile, REGISTERS } from "./style-audit-core.mjs";
|
|
6
|
+
|
|
7
|
+
export function parseArgs(argv) {
|
|
8
|
+
const options = { file: null, register: "plain", json: false };
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
const value = argv[index];
|
|
11
|
+
if (value === "--json") options.json = true;
|
|
12
|
+
else if (value === "--register") options.register = argv[++index];
|
|
13
|
+
else if (value.startsWith("--register=")) options.register = value.slice("--register=".length);
|
|
14
|
+
else if (value.startsWith("-")) throw new Error(`unknown option: ${value}`);
|
|
15
|
+
else if (options.file) throw new Error("supply exactly one file");
|
|
16
|
+
else options.file = value;
|
|
17
|
+
}
|
|
18
|
+
if (!options.file) throw new Error("missing file");
|
|
19
|
+
if (!REGISTERS.has(options.register)) throw new Error(`unknown register: ${options.register}`);
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function renderHuman(path, report) {
|
|
24
|
+
const status = report.pass ? "PASS" : "FAIL";
|
|
25
|
+
const lines = [
|
|
26
|
+
`${status} ${path}`,
|
|
27
|
+
`Register: ${report.register}`,
|
|
28
|
+
`Hard failures: ${report.hard_failure_count}`,
|
|
29
|
+
`Review warnings: ${report.warning_count}`,
|
|
30
|
+
];
|
|
31
|
+
for (const item of report.findings) {
|
|
32
|
+
lines.push(`${item.severity.toUpperCase()} ${item.line}:${item.column} ${item.rule}: ${item.message}`);
|
|
33
|
+
}
|
|
34
|
+
lines.push("This audit does not rewrite text, validate facts, infer authorship, or promise detector evasion.");
|
|
35
|
+
return `${lines.join("\n")}\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
39
|
+
const options = parseArgs(argv);
|
|
40
|
+
const path = resolve(options.file);
|
|
41
|
+
const report = await auditFile(path, { register: options.register });
|
|
42
|
+
process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : renderHuman(options.file, report));
|
|
43
|
+
if (!report.pass) process.exitCode = 1;
|
|
44
|
+
return report;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (import.meta.url === new URL(`file://${resolve(process.argv[1]).replaceAll("\\", "/")}`).href) {
|
|
48
|
+
try {
|
|
49
|
+
await main();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
process.stderr.write(`agora-style-audit: ${error.message}\n`);
|
|
52
|
+
process.exitCode = 2;
|
|
53
|
+
}
|
|
54
|
+
}
|