From d9a8ff1dbcf5aa23e129666d89cfe90206cf0cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Hern=C3=A1ndez?= Date: Wed, 9 Sep 2026 01:28:58 -0400 Subject: [PATCH] Give the agent skill a front door The skill was the only distribution surface with nowhere to land. The web app never mentioned it -- not a page, not a nav link, not a word -- and the README reached it at section 5, past two hundred lines of badges for other people's registries. That is backwards, and the traffic says so. Of every channel this project ships to, the skill is the only one a person installs by choice; the rest are machine-readable indexes, and machines are what came: 450 unique cloners against 77 unique viewers in fourteen days, a ratio unchanged since July. So it gets what the desktop app and the Word add-in already have: - /skill, bilingual, leading with the one line that installs it, then the two modes, then the two things it refuses to do -- inventing a number and naming an author -- because that is what a reader is actually deciding about - a nav link, on every host, since it runs in the reader's editor not in ours - a place in the README's top links, ahead of the Windows download The rule counts on the page are read from the packs this build ships. Every number this project has typed about itself has gone stale at least once. Two defects the source did not show and a render did: the mode placeholders came out as <your draft> because the locale held entities Razor encoded again, and the two-mode list used markup the grid does not style. Both found by serving the page and looking at it. Also guarded, because it is the same defect as #77 in a different file: the locale tests checked that a translation was *current* and never that it was *complete*. A locale this project credits itself for now has to define every key; a community translation may stay partial, since demanding all 508 keys of a first pull request turns a partial translation into no translation. Verified by mutation: removing two Spanish keys fails, naming them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015PEbbiYSNPw7jE3LrPNhyF --- README.md | 7 ++ src/SignsOfAI.UI/Layout/MainLayout.razor | 4 + src/SignsOfAI.UI/Pages/AgentSkill.razor | 85 +++++++++++++++++++ src/SignsOfAI.UI/wwwroot/css/app.css | 11 +++ src/SignsOfAI.UI/wwwroot/i18n/en.json | 26 +++++- src/SignsOfAI.UI/wwwroot/i18n/es.json | 26 +++++- tests/SignsOfAI.Core.Tests/LocaleFileTests.cs | 32 +++++++ 7 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/SignsOfAI.UI/Pages/AgentSkill.razor diff --git a/README.md b/README.md index 968724f..a6ce595 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ **[Try the live demo →](https://peopleworks.github.io/SignsofAI/)** — English & Spanish, runs in your browser. No signup, and the analysis uploads nothing. +**[Use it in your editor →](https://peopleworks.github.io/SignsofAI/skill)** — an agent skill for +Claude Code, Codex, Gemini CLI and Cursor, in one line: `npx skills add peopleworks/SignsofAI -g`. +It edits by the same rules this engine scores by, and it never invents a number. + **[Download the Windows app →](https://github.com/peopleworks/SignsofAI/releases?q=desktop&expanded=true)** — the same tool in a window. Nothing to install alongside it: the .NET runtime is bundled. ![Signs of AI Writing analyzing text live: the score climbs as AI tells accumulate, then every tell is highlighted with a fix](Docs/screenshots/analyze-live.gif) @@ -232,6 +236,9 @@ already generated — copy it into `.vscode/mcp.json` and you're done. ## 5. Use it as an agent skill — `/signs-of-ai` +**[The skill has its own page →](https://peopleworks.github.io/SignsofAI/skill)**, with the two +modes side by side and what it refuses to do. + Prefer to work inside your editor? [`SKILL.md`](SKILL.md) is a drop-in **agent skill** that de-slops a draft — or reports the tells a text carries — in **English and Spanish**. It is a human-readable distillation of the same `rules.en.json` / `rules.es.json` taxonomy, so it edits by the same rules the diff --git a/src/SignsOfAI.UI/Layout/MainLayout.razor b/src/SignsOfAI.UI/Layout/MainLayout.razor index 6984f0e..acb6442 100644 --- a/src/SignsOfAI.UI/Layout/MainLayout.razor +++ b/src/SignsOfAI.UI/Layout/MainLayout.razor @@ -25,6 +25,10 @@ @* No host condition, unlike the two below: somebody using the desktop app writes in Word too, and the add-in is useful to them for the same reasons. *@ @L["nav.word"] + @* Same reasoning as the Word link: every host can use it, because it runs in the + reader's editor rather than in this one. It had no link at all until #98, which is + how the one channel people install by choice stayed the hardest one to find. *@ + @L["nav.skill"] @* Only where there is something to gain by downloading it. The same signal as the folder link above, and for the same reason: this host cannot open a folder, so a better host exists for this machine and hiding that would be the lowest-common- diff --git a/src/SignsOfAI.UI/Pages/AgentSkill.razor b/src/SignsOfAI.UI/Pages/AgentSkill.razor new file mode 100644 index 0000000..02d4e20 --- /dev/null +++ b/src/SignsOfAI.UI/Pages/AgentSkill.razor @@ -0,0 +1,85 @@ +@page "/skill" +@using SignsOfAI.Core.Rules +@inherits LocalizedComponent + +@* + The agent skill's own page, the way the desktop app and the Word add-in have one. + + It had none, and the repository mentioned it at section 5, past two hundred lines of badges for + other people's registries. That is backwards: of every channel this project ships to, the skill + is the only one a person installs by choice rather than a machine indexes, and it was the only + one with nowhere to land. + + The rule counts are read from the packs the build ships, never typed. Everything this project + has typed about itself has gone stale at least once. +*@ + +@L["skill.pagetitle"] + +
+

@L["skill.h1"]

+

@L["skill.tagline"]

+
+ +@* Installing first: one line, because that is the whole argument against scrolling. *@ +
+

@L["skill.install.title"]

+

@L["skill.install.lede"]

+ + +
npx skills add peopleworks/SignsofAI -g
+

@L.M("skill.install.any.body")

+ + +
/plugin marketplace add peopleworks/SignsofAI
+/plugin install signs-of-ai
+

@L.M("skill.install.plugin.body")

+
+ +
+

@L["skill.use.title"]

+
/signs-of-ai @L["skill.use.edit.arg"]
+/signs-of-ai @L["skill.use.examine.arg"]
+
    +
  • +

    @L["skill.use.edit.name"]

    +

    @L["skill.use.edit.what"]

    +
  • +
  • +

    @L["skill.use.examine.name"]

    +

    @L["skill.use.examine.what"]

    +
  • +
+
+ +@* The differentiator, and the reason this is not just another de-slop prompt: the two things it is + built to refuse. A reader deciding whether to let a tool near coursework is deciding about these. *@ +
+

@L["skill.refuses.title"]

+

@L.M("skill.refuses.score")

+

@L.M("skill.refuses.author")

+
+ +
+

@L["skill.rules.title"]

+

@L.M("skill.rules.body", EnglishRules, SpanishRules)

+ +
+ +@code { + private const string SkillUrl = "https://github.com/peopleworks/SignsofAI/blob/main/SKILL.md"; + + // Counted from the packs this build carries. The number in a sentence about how many rules there + // are is exactly the kind that outlives its truth. + private static int Count(string language) + { + var pack = RulePackLoader.Load(language); + return pack.Lexical.Length + pack.Patterns.Length; + } + + private static int EnglishRules => Count("en"); + private static int SpanishRules => Count("es"); +} diff --git a/src/SignsOfAI.UI/wwwroot/css/app.css b/src/SignsOfAI.UI/wwwroot/css/app.css index b5ce1cd..f9db691 100644 --- a/src/SignsOfAI.UI/wwwroot/css/app.css +++ b/src/SignsOfAI.UI/wwwroot/css/app.css @@ -1106,6 +1106,17 @@ button.ghost.sm { padding: .35rem .7rem; font-size: .82rem; } a sub-heading inside a card, and a pulled quote for the sentence the pane actually prints. */ .wa-h3 { font-size: .94rem; font-weight: 650; margin: 1.1rem 0 .4rem; } +/* The one command that installs the skill. It is the whole argument of that page, so it is set as a + block a reader can select in one gesture rather than as inline code inside a sentence. Wraps + rather than scrolls: a command you cannot see the end of is a command you retype wrong. */ +.skill-cmd { + margin: .5rem 0 .8rem; padding: .7rem .85rem; + background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; + font-size: .86rem; line-height: 1.55; + white-space: pre-wrap; overflow-wrap: anywhere; +} +.skill-cmd code { background: none; padding: 0; font: inherit; } + /* A row of links where one of them is the action. why.html has a .cta of its own — it is a standalone page with its own stylesheet — and copying the markup here without the style is exactly how this shipped as two bare links sitting against each other. */ diff --git a/src/SignsOfAI.UI/wwwroot/i18n/en.json b/src/SignsOfAI.UI/wwwroot/i18n/en.json index c461c63..1cc5d56 100644 --- a/src/SignsOfAI.UI/wwwroot/i18n/en.json +++ b/src/SignsOfAI.UI/wwwroot/i18n/en.json @@ -482,5 +482,29 @@ "home.report.title": "Writing analysis report", "batch.report.hint": "Save the scan as a document you can keep or hand to a colleague. It names your students' files.", "batch.report.title": "Folder scan — reading order", - "dl.word.page": "How it works, and how to install it" + "dl.word.page": "How it works, and how to install it", + "nav.skill": "In your editor", + "skill.pagetitle": "The agent skill — Signs of AI Writing", + "skill.h1": "Use it where you already write", + "skill.tagline": "One line installs it into Claude Code, Codex, Gemini CLI or Cursor. It edits by the same rules this engine scores by, in English and Spanish.", + "skill.install.title": "Installing it", + "skill.install.lede": "Nothing to download and nothing to run. The skill is a document your agent reads.", + "skill.install.any.title": "Any agent that supports skills", + "skill.install.any.body": "Works in Claude Code, Codex, Gemini CLI, Cursor and the rest. The -g installs it once for every project.", + "skill.install.plugin.title": "Or as a Claude Code plugin", + "skill.install.plugin.body": "From the marketplace manifest in the repository itself, so there is no third party between you and the source.", + "skill.use.title": "Two modes, and they are not the same job", + "skill.use.edit.arg": "", + "skill.use.examine.arg": "is this AI slop? ", + "skill.use.edit.name": "Edit", + "skill.use.edit.what": "Rewrites the draft and tells you what it changed, keeping the voice it found.", + "skill.use.examine.name": "Examine", + "skill.use.examine.what": "Quotes the tells the text carries and changes nothing.", + "skill.refuses.title": "The two things it will not do", + "skill.refuses.score": "It never invents a number. A skill reading a document cannot measure a false-positive rate, so it does not pretend to. For a calibrated score it hands off to this engine — the app you are reading, the command line, or the MCP server.", + "skill.refuses.author": "It never says who wrote a text. No tool can, this one included, and a skill that implied otherwise would be the thing this project argues against.", + "skill.rules.title": "The same taxonomy, not a second opinion", + "skill.rules.body": "It is a human-readable distillation of the rule packs this build ships — {0} rules in English and {1} in Spanish — so what it edits and what the engine scores are the same catalogue. The Spanish pack is written from scratch, not machine-translated.", + "skill.cta.read": "Read the skill", + "skill.cta.catalog": "Browse the rules" } diff --git a/src/SignsOfAI.UI/wwwroot/i18n/es.json b/src/SignsOfAI.UI/wwwroot/i18n/es.json index 7174bb1..ec7a861 100644 --- a/src/SignsOfAI.UI/wwwroot/i18n/es.json +++ b/src/SignsOfAI.UI/wwwroot/i18n/es.json @@ -482,5 +482,29 @@ "home.report.title": "Reporte de análisis de escritura", "batch.report.hint": "Guarda el análisis como un documento para conservar o pasar a un colega. Nombra los archivos de tus estudiantes.", "batch.report.title": "Análisis de carpeta — orden de lectura", - "dl.word.page": "Cómo funciona y cómo se instala" + "dl.word.page": "Cómo funciona y cómo se instala", + "nav.skill": "En tu editor", + "skill.pagetitle": "El skill para agentes — Señales de escritura IA", + "skill.h1": "Úsalo donde ya escribes", + "skill.tagline": "Una línea lo instala en Claude Code, Codex, Gemini CLI o Cursor. Edita con las mismas reglas con las que este motor puntúa, en español e inglés.", + "skill.install.title": "Cómo se instala", + "skill.install.lede": "Nada que descargar y nada que ejecutar. El skill es un documento que lee tu agente.", + "skill.install.any.title": "Cualquier agente que admita skills", + "skill.install.any.body": "Funciona en Claude Code, Codex, Gemini CLI, Cursor y los demás. La -g lo instala una vez para todos los proyectos.", + "skill.install.plugin.title": "O como plugin de Claude Code", + "skill.install.plugin.body": "Desde el manifiesto de marketplace del propio repositorio, así que no hay terceros entre tú y la fuente.", + "skill.use.title": "Dos modos, y no son el mismo trabajo", + "skill.use.edit.arg": "", + "skill.use.examine.arg": "¿esto es slop de IA? ", + "skill.use.edit.name": "Editar", + "skill.use.edit.what": "Reescribe el borrador y te dice qué cambió, conservando la voz que encontró.", + "skill.use.examine.name": "Examinar", + "skill.use.examine.what": "Cita las señales que lleva el texto y no cambia nada.", + "skill.refuses.title": "Las dos cosas que no hará", + "skill.refuses.score": "Nunca se inventa un número. Un skill que lee un documento no puede medir una tasa de falsos positivos, así que no finge hacerlo. Para una puntuación calibrada pasa el trabajo a este motor: la aplicación que estás leyendo, la línea de comandos o el servidor MCP.", + "skill.refuses.author": "Nunca dice quién escribió un texto. Ninguna herramienta puede, esta incluida, y un skill que insinuara lo contrario sería justo aquello contra lo que argumenta este proyecto.", + "skill.rules.title": "La misma taxonomía, no una segunda opinión", + "skill.rules.body": "Es una destilación legible de los packs de reglas que trae esta compilación —{0} reglas en inglés y {1} en español—, así que lo que edita y lo que el motor puntúa son el mismo catálogo. El pack español está escrito desde cero, no traducido a máquina.", + "skill.cta.read": "Leer el skill", + "skill.cta.catalog": "Ver las reglas" } diff --git a/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs b/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs index 06b3542..0c4847f 100644 --- a/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs +++ b/tests/SignsOfAI.Core.Tests/LocaleFileTests.cs @@ -154,6 +154,38 @@ public void No_locale_tells_the_reader_a_person_wrote_the_text() "verdict.none. Found:\n " + string.Join("\n ", problems)); } + /// + /// Being current is not the same as being complete, and only the first was guarded. + /// + /// This project claims the interface is bilingual, not that Spanish is a courtesy translation + /// kept roughly up to date. A missing key renders in English inside an otherwise Spanish page, + /// which is the same defect #77 found in the evidence report: three tests watched report.es.json + /// and none of them noticed it carried 39 of 76 blocks. + /// + /// The line is the manifest's own credit. A locale this project puts its own name to has to be + /// complete; a locale contributed by somebody else may be partial, because demanding all 500 + /// keys from a first pull request would turn a partial translation into no translation. + /// + [Fact] + public void A_locale_this_project_credits_itself_for_is_complete() + { + var manifest = ReadManifest(); + var fallback = ReadLocale(manifest.Fallback); + + foreach (var locale in manifest.Locales.Where(l => + l.Code != manifest.Fallback && + string.Equals(l.Credit, "PeopleWorks", StringComparison.OrdinalIgnoreCase))) + { + var strings = ReadLocale(locale.Code); + var missing = fallback.Keys.Where(k => !strings.ContainsKey(k)).OrderBy(k => k).ToList(); + + Assert.True(missing.Count == 0, + $"{locale.Code}.json is missing {missing.Count} of {fallback.Count} keys, so those " + + $"strings appear in {manifest.Fallback} inside a {locale.Code} page: " + + string.Join(", ", missing.Take(20))); + } + } + [Fact] public void Translations_use_only_known_keys() {