Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/deploy-cloudflare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ jobs:
# advanced while Test was still running, and passed only by coincidence
# otherwise. `head_sha` alone is the commit that was tested, and it is what
# the checkout below already uses.
#
# `head_branch` alone is not proof of main. A fork's pull request from a
# branch named `main` reports `head_branch == 'main'`, and its Test run
# concludes `success` because every job skips fork code. Only a push to
# this repository may deploy, or that fork's code would build with these
# secrets.
if: >-
(github.event_name == 'workflow_dispatch' && inputs.target != 'harness-proof') ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.head_branch == 'main')
runs-on: [self-hosted, linux, x64, harlan-desktop-deploy]
timeout-minutes: 20
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true

# Every job runs on Harlan's workstation. A pull request from a fork must never
# reach it, because that would run untrusted code on the host. Each job
# therefore runs only for a push or for a pull request from a branch in this
# repository. A maintainer pushes a fork's change to a branch here to test it.
#
# Every job skipped still concludes the run as `success`. Deploy to Cloudflare
# guards on the triggering event and repository for that reason.

jobs:
harness-proof:
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, linux, x64, harlan-desktop-ci]
timeout-minutes: 15
steps:
Expand All @@ -33,6 +42,7 @@ jobs:
pnpm --filter @skilld/skill-harness-worker test:run

harlan-desktop:
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
uses: harlan-zw/harlan-nuxt/.github/workflows/harlan-desktop-site-ci.yml@main
with:
# Ephemeral containers on the workstation. Safe here because the
Expand Down Expand Up @@ -71,7 +81,7 @@ jobs:

build:
# Main builds in the deploy workflow. Keep the build check on pull requests.
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, linux, x64, harlan-desktop-ci]
timeout-minutes: 30
steps:
Expand Down
7 changes: 3 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ New Markdown at the repository root is an error. Identity and filters only.
- **`skills.search` answers are frozen for skilld 3.2.0.** The CLI parses them with
`deny_unknown_fields`, so one new field breaks every released `skilld search`. Add the field to
`skills.get`.
- **`GOOGLE_RECOVERY.md` is in `.gitignore`.** Around ten source comments cite it as the reason a
surface is `noindex` or absent from the sitemap, and nobody cloning this repository can read it.
Treat those comments as pointing at nothing until the file is either committed or the comments
are rewritten to carry their own reason.
- **A comment carries its own reason.** Never cite a gitignored file, such as
`GOOGLE_RECOVERY.md`, or a file on a personal machine as the reason for a decision.
Nobody who clones this repository can read either one.

## Consumers

Expand Down
4 changes: 2 additions & 2 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ const heroVerbs = [
* `diagrams` was added and `research` was un-retired, both on these numbers.
* See the header of clusters.ts.
*
* Re-measure before trusting this order past October. Method and numbers:
* ~/scratch/notes/skilld-track-demand-2026-09-04.md
* Re-measure before trusting this order past October. The percentages
* below are each track's share of the weighted demand.
*/
const TRACK_DEMAND_ORDER = [
'design', // 16.5%
Expand Down
6 changes: 3 additions & 3 deletions layers/marketing/app/pages/learn/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ useSeoMeta({
ogTitle: () => data.value?.title ?? '',
ogDescription: () => data.value?.description ?? '',
ogUrl: canonicalUrl,
// The /learn index is a 55-word card list (GOOGLE_RECOVERY.md thin-page
// audit). Articles stay indexable; the index earns index back when it
// carries real editorial content.
// The /learn index is a 55-word card list, which the 2026-08-22 indexing
// audit flagged as a thin page. Articles stay indexable; the index earns
// index back when it carries real editorial content.
robots: () => slug.value ? pageRobots(`/learn/${slug.value}`) : 'noindex,follow',
})

Expand Down
3 changes: 2 additions & 1 deletion layers/marketing/app/pages/skills/tag/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ useSeoMeta({
description,
ogTitle: title,
ogDescription: description,
// Tag pages are noindex by default sitewide (GOOGLE_RECOVERY.md, 2026-08-22).
// Tag pages are noindex by default sitewide since 2026-08-22. They were
// auto-generated lists with no editorial text, and 16% of the sitemap.
// Indexable only for a vocab tag with an editorial keep=1 decision.
robots: () => (data.value?.indexable ? 'index,follow' : 'noindex,follow'),
})
Expand Down
5 changes: 2 additions & 3 deletions layers/registry/app/components/SkillDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1045,9 +1045,8 @@ if (import.meta.server && pageState.value.status) {

// 2026-08-22: the shared "A Claude Code skill for Cursor, Codex, and other
// agents." suffix is gone. Identical boilerplate across 1,300+ meta
// descriptions was the last scaled-content signature in Google snippets
// (GOOGLE_RECOVERY.md). Provenance stays: "From owner/repo." carries the
// unique part.
// descriptions was the last scaled-content signature in Google snippets.
// Provenance stays: "From owner/repo." carries the unique part.
function withSeoContext(text: string, owner: string, repo: string, max = 200): string {
const suffix = ` From ${owner}/${repo}.`
const collapsed = text.replace(/\s+/g, ' ').trim()
Expand Down
4 changes: 2 additions & 2 deletions layers/registry/server/data/clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
* demand and costs a URL, while a homepage tile answers trending demand and
* costs the scarcest space on the site. The grid caps at twelve, so the
* subtraction happens there. `seo` and `coding` keep their pages and lose
* their tiles. Method and numbers:
* ~/scratch/notes/skilld-track-demand-2026-09-04.md
* their tiles. The method and the shares sit on TRACK_DEMAND_ORDER in
* app/pages/index.vue.
*
* 2026-08-25: `anti-slop-coding` joined as the coding counterpart. Its seven
* pinned skills preserve the shared ranking and keep the page focused. It has
Expand Down
11 changes: 5 additions & 6 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export default defineNuxtConfig({
'/_nuxt/v2/',
)
},
// 2026-08-22 agent lane (GOOGLE_RECOVERY.md): list the agent-only sitemap
// in llms.txt. The module's `notes` config exists but never renders, so
// 2026-08-22 agent lane, where agents discover pages without Google's
// sitemap: list the agent-only sitemap in llms.txt. The module's `notes` config exists but never renders, so
// this pushes a link into the first section instead.
'ai-ready:llms-txt': (payload: { sections?: { links?: { title: string, href: string, description?: string }[] }[], notes: string[] }) => {
const section = payload.sections?.[0]
Expand Down Expand Up @@ -298,7 +298,6 @@ export default defineNuxtConfig({
runtimeSync: true,
// llms.txt is an index, not a dump: each entry links the page's .md.
// The 28.5 MB llms-full.txt inline dump is retired below (routeRules).
// GOOGLE_RECOVERY.md, agent lane rework.
llmsTxt: {
markdownLinks: true,
},
Expand Down Expand Up @@ -811,9 +810,9 @@ export default defineNuxtConfig({
},

sitemap: {
// `/learn` index is a 55-word card list, noindex since 2026-08-22
// (GOOGLE_RECOVERY.md). Articles stay indexable and sitemap-listed; only
// the bare index leaves. Global so no child sitemap can re-adopt it.
// `/learn` index is a 55-word card list, noindex since 2026-08-22 as a
// thin page. Articles stay indexable and sitemap-listed; only the bare
// index leaves. Global so no child sitemap can re-adopt it.
exclude: ['/learn'],
sitemaps: {
pages: {
Expand Down
3 changes: 1 addition & 2 deletions server/middleware/trailing-slash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// 2026-08-22: canonical trailing-slash policy (GOOGLE_RECOVERY.md technical
// audit). Nuxt served 200 on both `/gh/x/y` and `/gh/x/y/`, and canonicals
// 2026-08-22: canonical trailing-slash policy, from a technical SEO audit. Nuxt served 200 on both `/gh/x/y` and `/gh/x/y/`, and canonicals
// point at the no-slash form, so Google registered duplicate URLs (37 pages
// in "Duplicate, Google chose different canonical"). One URL per page:
// strip the trailing slash with a 301.
Expand Down
2 changes: 1 addition & 1 deletion server/routes/ai-sitemap.xml.get.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="@cloudflare/workers-types" />

// 2026-08-22 agent lane (GOOGLE_RECOVERY.md): an agent-only sitemap listing
// 2026-08-22 agent lane: an agent-only sitemap listing
// every AI-ready page, including pages that are noindex for Google. Referenced
// from llms.txt only, never robots.txt, so it cannot couple agent discovery to
// the Google recovery surface. Google officially ignores llms.txt, and even if
Expand Down