From 5092b34216c782e21b7669a6fe05b938e74bc478 Mon Sep 17 00:00:00 2001 From: Benjamin ACH Date: Thu, 26 Mar 2026 23:02:08 +0100 Subject: [PATCH] feat(docs): add `llms.txt` page --- .../scalingo-generate-description/SKILL.md | 103 ++++++++++++++++++ .../agents/openai.yaml | 4 + config.ru | 19 ++++ .../2000-01-01-setup-ssh-windows.md | 2 +- src/llms.txt | 21 ++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 .agents/skills/scalingo-generate-description/SKILL.md create mode 100644 .agents/skills/scalingo-generate-description/agents/openai.yaml create mode 100644 src/llms.txt diff --git a/.agents/skills/scalingo-generate-description/SKILL.md b/.agents/skills/scalingo-generate-description/SKILL.md new file mode 100644 index 000000000..89e125681 --- /dev/null +++ b/.agents/skills/scalingo-generate-description/SKILL.md @@ -0,0 +1,103 @@ +--- +name: scalingo-generate-description +description: Generate or rewrite a Jekyll Markdown front matter `description` line that is short, actionable, and optimized for LLM page routing. Use when the user asks to create/update doc descriptions for llms.txt or metadata quality. +--- + +# Frontmatter Description + +Generate a single-line Jekyll front matter description for documentation pages. + +## When to use + +Use this skill when the user asks to: +- create a page `description` field +- rewrite a `description` to be shorter, clearer, or more actionable +- optimize descriptions for LLM retrieval/routing (for example "find the page about app logs") + +## Output contract + +When no confirmation is required, return exactly one line: + +`description: ""` + +Rules: +- keep a valid YAML front matter line +- always wrap the description value in double quotes +- if the text contains `"`, escape it as `\"` + +When interactive confirmation is required before edits: +- show the proposed line in a clearly separated block +- do not append inline confirmation text on the same paragraph +- use the interaction policy below + +## Safety and edit policy + +- Default behavior: propose text first, do not write immediately. +- Ask for explicit confirmation before file edits. +- If a `description` already exists, propose a replacement and wait for confirmation before overwriting. +- Only write directly without confirmation when the user explicitly asks to apply immediately. + +### Interaction policy + +If collaboration mode is `Plan`: +- Use `request_user_input` with one question and 2 options: + - `Apply (Recommended)`: write the proposed `description` into the target file. + - `Skip`: keep file unchanged. +- After the answer, either apply edit or continue without editing. + +If collaboration mode is `Default`: +- Ask using a dedicated action block, never inline with the description. +- Required prompt format: + - `ACTION REQUISE` + - `Reponds: apply pour ecrire dans le fichier, ou skip pour ne rien changer.` +- Accept only `apply` or `skip` (case-insensitive). If ambiguous, ask again. +- If no target file is provided, ask for the file path before asking `apply` or `skip`. +- If `apply` is selected, write the new `description` immediately. +- After applying, confirm with a short status line and show the final written line. +- If `skip` is selected, confirm that no file was modified. + +Recommended presentation template: + +`Description proposee:` +`description: ""` +`ACTION REQUISE` +`Reponds: apply pour ecrire dans le fichier, ou skip pour ne rien changer.` + +## Content rules + +- base the description only on explicit information in the page text +- do not infer, generalize, or add facts that are not stated on the page +- if a detail is not supported by the page, omit it +- hard limit: 256 characters max +- target: 120 to 180 characters when possible +- sequence: page scope first, actionable outcomes second +- use concrete verbs: enable, disable, configure, deploy, retrieve, inspect, reset, monitor, troubleshoot +- include retrieval-critical nouns when relevant: logs, dashboard, CLI, API, environment variables, metrics, alerts +- avoid fluff and marketing language +- avoid repeating the title verbatim unless needed for disambiguation + +## Writing process + +1. Read title + section headings + first paragraphs. +2. Identify the top user intent this page solves. +3. Keep only high-signal terms that help routing. +4. Draft one concise line and trim aggressively. +5. Validate against checks below. + +## Validation checks + +- Is it <= 256 chars? +- Can every claim be traced back to the page text? +- Would it help route a query like "how to get app logs" or "how to reset 2FA"? +- Is each word useful for either scope or actionability? +- Is it specific enough versus nearby pages? + +## Good pattern + +`description: ". ."` + +## Examples + +`description: "Retrieve and inspect Scalingo app logs from dashboard or CLI, understand log streams, and troubleshoot common runtime errors using actionable filtering and investigation steps."` + +`description: "Enable, disable, or recover 2FA for a Scalingo account with dashboard steps, TOTP validation, recovery code handling, and support escalation for authenticator loss."` diff --git a/.agents/skills/scalingo-generate-description/agents/openai.yaml b/.agents/skills/scalingo-generate-description/agents/openai.yaml new file mode 100644 index 000000000..c88055785 --- /dev/null +++ b/.agents/skills/scalingo-generate-description/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Scalingo - Generate Description" + short_description: "Generate concise actionable doc descriptions" + default_prompt: "Use $scalingo-generate-description to create or rewrite a Jekyll front matter description line under 256 characters." diff --git a/config.ru b/config.ru index 677579ab4..e92b4a26c 100644 --- a/config.ru +++ b/config.ru @@ -15,6 +15,23 @@ class Object end end +class ForcePlainTextUtf8 + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + content_type = headers["Content-Type"] + + if content_type&.start_with?("text/plain") && !content_type.match?(/;\s*charset=/i) + headers["Content-Type"] = "#{content_type}; charset=utf-8" + end + + [status, headers, body] + end +end + use Rack::Rewrite do r301 %r{.*}, "https://#{ENV["CANONICAL_HOST"]}/samples$&", if: proc { |rack_env| ["samples.scalingo.com"].include?(rack_env["SERVER_NAME"]) @@ -52,6 +69,8 @@ use Rack::Rewrite do } end +use ForcePlainTextUtf8 + use Rack::Cors do if ENV["ALLOWED_CHANGELOG_FEED_CONSUMERS"].present? allowed_origins = ENV["ALLOWED_CHANGELOG_FEED_CONSUMERS"].split(",") diff --git a/src/_posts/platform/getting-started/2000-01-01-setup-ssh-windows.md b/src/_posts/platform/getting-started/2000-01-01-setup-ssh-windows.md index 2c3c865f9..2e5e0a034 100644 --- a/src/_posts/platform/getting-started/2000-01-01-setup-ssh-windows.md +++ b/src/_posts/platform/getting-started/2000-01-01-setup-ssh-windows.md @@ -78,7 +78,7 @@ It should display the following output: You've successfully authenticated on Scalingo, but there is no shell access ``` -If it doesn't, something has been done wrong. +If it doesn't, something has been done wrong. Ensure your key is loaded in the SSH agent by running: ```bash $ ssh-add.exe -l diff --git a/src/llms.txt b/src/llms.txt new file mode 100644 index 000000000..ed002faff --- /dev/null +++ b/src/llms.txt @@ -0,0 +1,21 @@ +--- +layout: null +--- + +# Scalingo Doc + +> Scalingo is a European Platform-as-a-Service for deploying, running, and operating applications, databases, and supporting services in the cloud. This documentation covers the main platform workflows and reference material, including application deployment, environment configuration, databases, add-ons, networking, observability, troubleshooting, and operational guidance. + +_Generated: {{ site.time | date_to_rfc822 }}_ + +## Top docs +The links below prioritize key documentation pages. +{% assign top_urls = site.highlighted.first_steps | concat: site.highlighted.popular | uniq %}{% for top_url in top_urls -%}{% assign top_post = site.posts | where: "url", top_url | first %} +- [{% if top_post %}{{ top_post.title }}{% else %}{{ top_url }}{% endif %}]({{ top_url | prepend: site.baseurl | prepend: site.url }}){% if top_post and top_post.description %}: {{ top_post.description | strip_html | normalize_whitespace | truncate: 256 }}{% endif %} +{% endfor %} + +## All posts +The links below point to the published documentation pages, ordered by modification date (newest first). +{% assign posts = site.posts | sort: 'modified_at' | reverse %}{% for post in posts %}{% unless post.categories contains 'changelog' or post.llm_exclude == true or post.layout == 'dir' -%}{% assign llm_description = post.description | strip_html | normalize_whitespace | truncate: 256 %} +- [{{ post.title }}]({{ post.url | prepend: site.baseurl | prepend: site.url }}){% if post.description and post.description != "" %}: {{ llm_description }}{% endif %} +{% endunless -%}{% endfor %}