chore(catalog): remove plugins that are no longer published
Remove claude-design from the marketplace manifest and its catalog
README entry, and remove the playground-design-system entry from the
README. The catalog now lists 11 plugins.
Add scripts/check-public-links.mjs: fails when README.md or the
manifest links to an open/<name> repository missing from a tracked
PUBLIC_REPOS list. Chosen over the local retired-terms list because
that gate scans every tracked file, and historical docs plus
CONVENTIONS.md (which keep naming these repos on purpose) would hold
it red; a live visibility check could not be red before the repos
change visibility. Red on 5ba4a7a (6 hits), green after (0 of 36).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ba4a7a735
commit
d0bc696da3
5 changed files with 172 additions and 37 deletions
|
|
@ -91,15 +91,6 @@
|
||||||
},
|
},
|
||||||
"description": "Shared Claude Code output style for the ktg-plugin-marketplace. Plain-language tone — explains what and why, hides paths/JSON/stack traces by default, matches the user's language."
|
"description": "Shared Claude Code output style for the ktg-plugin-marketplace. Plain-language tone — explains what and why, hides paths/JSON/stack traces by default, matches the user's language."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "claude-design",
|
|
||||||
"source": {
|
|
||||||
"source": "url",
|
|
||||||
"url": "https://git.fromaitochitta.com/open/claude-design.git",
|
|
||||||
"ref": "v0.2.0"
|
|
||||||
},
|
|
||||||
"description": "End-to-end facilitator for prompting Claude Design (claude.ai/design) — idea to copy-paste-ready prompt with iteration coaching, citing Anthropic primary sources."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "repo-mailbox",
|
"name": "repo-mailbox",
|
||||||
"source": {
|
"source": {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ their own Forgejo repositories under `https://git.fromaitochitta.com/open/`.
|
||||||
- Marketplace conventions: see CONVENTIONS.md.
|
- Marketplace conventions: see CONVENTIONS.md.
|
||||||
- Adding/updating a plugin entry: edit `.claude-plugin/marketplace.json` (external `source: "url"` with
|
- Adding/updating a plugin entry: edit `.claude-plugin/marketplace.json` (external `source: "url"` with
|
||||||
a pinned `ref`) and re-state the plugin in README.md with its verified version.
|
a pinned `ref`) and re-state the plugin in README.md with its verified version.
|
||||||
|
- **Public-link gate:** `node scripts/check-public-links.mjs` fails when README.md or the manifest
|
||||||
|
links to an `open/<name>` repository missing from `PUBLIC_REPOS` in that script — a repository
|
||||||
|
made private or removed turns those links into dead links and a manifest entry into a broken
|
||||||
|
install. Adding a plugin means adding its repository to `PUBLIC_REPOS` in the same commit;
|
||||||
|
removing one means taking it out of both.
|
||||||
- Plugin source, issues, and releases live in each plugin's own repository — not here.
|
- Plugin source, issues, and releases live in each plugin's own repository — not here.
|
||||||
- **Every tag this repo cuts is ANNOTATED — `git tag -a`, never a bare `git tag`.** A lightweight tag
|
- **Every tag this repo cuts is ANNOTATED — `git tag -a`, never a bare `git tag`.** A lightweight tag
|
||||||
is a branch-like ref: it can be moved to another commit with nothing recorded that it ever pointed
|
is a branch-like ref: it can be moved to another commit with nothing recorded that it ever pointed
|
||||||
|
|
@ -226,7 +231,7 @@ their own Forgejo repositories under `https://git.fromaitochitta.com/open/`.
|
||||||
correct-but-split, 1 defect.** Exact: voyage `24 agents` / `7 hooks` / `832 tests`, linkedin-studio
|
correct-but-split, 1 defect.** Exact: voyage `24 agents` / `7 hooks` / `832 tests`, linkedin-studio
|
||||||
`6 skills`, graceful-handoff `1 pipeline` / `48 tests`, ai-psychosis `1 skill` / `1 command`,
|
`6 skills`, graceful-handoff `1 pipeline` / `48 tests`, ai-psychosis `1 skill` / `1 command`,
|
||||||
ms-ai-architect `29 commands` / `5 skills` / `2 hooks`, okr `7 agents` / `3 hooks`, claude-design
|
ms-ai-architect `29 commands` / `5 skills` / `2 hooks`, okr `7 agents` / `3 hooks`, claude-design
|
||||||
`5 tests`. Split: voyage `6 commands (+1 helper)` measures 7 command files, and the split is the
|
`5 tests` (claude-design has since left the catalog, 2026-09-23). Split: voyage `6 commands (+1 helper)` measures 7 command files, and the split is the
|
||||||
plugin's own — its README at `v5.9.1` reads "6 slash commands (…) + trekendsession helper", and that
|
plugin's own — its README at `v5.9.1` reads "6 slash commands (…) + trekendsession helper", and that
|
||||||
file's `description:` calls itself a helper. **Defect: repo-standard `170 tests` measures 243** at
|
file's `description:` calls itself a helper. **Defect: repo-standard `170 tests` measures 243** at
|
||||||
`v0.11.1` (`node --test scripts/*.test.mjs`, the version its `package.json` declares). The catalog is
|
`v0.11.1` (`node --test scripts/*.test.mjs`, the version its `package.json` declares). The catalog is
|
||||||
|
|
@ -269,7 +274,7 @@ their own Forgejo repositories under `https://git.fromaitochitta.com/open/`.
|
||||||
`agents` = `agents/**/*.md` (4/4: llm-security 6, config-audit 7, ms-ai-architect 12,
|
`agents` = `agents/**/*.md` (4/4: llm-security 6, config-audit 7, ms-ai-architect 12,
|
||||||
linkedin-studio 20), `commands` = `commands/**/*.md` (3/3: config-audit 21, okr 16,
|
linkedin-studio 20), `commands` = `commands/**/*.md` (3/3: config-audit 21, okr 16,
|
||||||
linkedin-studio 30), `skills` = `skills/*/SKILL.md` (4/4: repo-mailbox 3, claude-design 1,
|
linkedin-studio 30), `skills` = `skills/*/SKILL.md` (4/4: repo-mailbox 3, claude-design 1,
|
||||||
graceful-handoff 1, repo-standard 1). `tests = ℹ tests` re-confirmed against config-audit
|
graceful-handoff 1, repo-standard 1; 3/3 since claude-design left the catalog 2026-09-23). `tests = ℹ tests` re-confirmed against config-audit
|
||||||
(`ℹ tests 1398` == badge 1398, `pass` 1374 — count `tests`, and note the suite need not be green
|
(`ℹ tests 1398` == badge 1398, `pass` 1374 — count `tests`, and note the suite need not be green
|
||||||
for the census to be valid). **`scanners` is NOT calibrated:** neither files-under-`scanners/`
|
for the census to be valid). **`scanners` is NOT calibrated:** neither files-under-`scanners/`
|
||||||
(llm-security 55, config-audit 61) nor top-level `.mjs` (27 / 32) reproduces the badges (23 / 16),
|
(llm-security 55, config-audit 61) nor top-level `.mjs` (27 / 32) reproduces the badges (23 / 16),
|
||||||
|
|
|
||||||
26
README.md
26
README.md
|
|
@ -209,18 +209,6 @@ Optional and works alongside every other plugin. Activate with `/config` → Out
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### [Claude Design](https://git.fromaitochitta.com/open/claude-design) `v0.2.0`
|
|
||||||
|
|
||||||
End-to-end facilitator for prompting Claude Design (`claude.ai/design`). Walks you from raw idea through prompt drafting, delivery, and iteration coaching. The output is the prompt; the artifact gets built in Claude Design.
|
|
||||||
|
|
||||||
- **Eight-phase flow** — disambiguate → intent preset → audience and destination → DESIGN.md anchor → five-layer prompt → delivery → iteration coaching → ship-readiness
|
|
||||||
- **Evidence-graded references** — six foundation plus eight per-preset references, each carrying an Anthropic-domain citation
|
|
||||||
- **Complements Anthropic's official design plugin** — this covers idea → prompt → iterate; theirs covers critique → handoff, with zero command overlap (enforced by test)
|
|
||||||
|
|
||||||
1 skill · 14 reference files · 5 tests · [Full documentation →](https://git.fromaitochitta.com/open/claude-design)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### [repo-mailbox](https://git.fromaitochitta.com/open/repo-mailbox) `v0.36.0`
|
### [repo-mailbox](https://git.fromaitochitta.com/open/repo-mailbox) `v0.36.0`
|
||||||
|
|
||||||
A local mailbox for coordination between Claude Code sessions in different repositories. Session A in repo X leaves a message for repo Y; the next session in repo Y gets it injected as context at startup. Local, private, no network, no SaaS.
|
A local mailbox for coordination between Claude Code sessions in different repositories. Session A in repo X leaves a message for repo Y; the next session in repo Y gets it injected as context at startup. Local, private, no network, no SaaS.
|
||||||
|
|
@ -250,20 +238,6 @@ Per-repo gate for the open/ presentation standard: README first screen, install
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Shared infrastructure
|
|
||||||
|
|
||||||
### [Playground Design System](https://git.fromaitochitta.com/open/playground-design-system) `v0.6.0`
|
|
||||||
|
|
||||||
Shared design system for plugin Playgrounds — the visual self-service UIs that complement terminal slash-commands. Aksel/Digdir-aligned, WCAG 2.1 AA, light + dark themes, print-ready. Used by `ms-ai-architect`, `okr`, `llm-security`, `voyage`, and `config-audit`.
|
|
||||||
|
|
||||||
- **Tokens and components** — self-hosted fonts (OFL 1.1), a deuteranopia-safe severity ramp, and Tier 1–3 components: radar, matrix-heatmap, findings-browser, AI Act pyramid and timeline, maturity-ladder, and more
|
|
||||||
- **Privacy-first** — all fonts self-hosted, zero CDN requests, works offline and behind air-gapped firewalls
|
|
||||||
- **Vendoring sync** — `scripts/sync-design-system.mjs <plugin>` keeps each plugin standalone; a SHA-256 manifest detects local drift
|
|
||||||
|
|
||||||
[Full documentation →](https://git.fromaitochitta.com/open/playground-design-system) · [Browse showcase](https://git.fromaitochitta.com/open/playground-design-system/src/branch/main/playground-examples/index.html)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
This repository's own gates — version consistency between each plugin entry and the tag it pins, catalog-to-plugin stat mirroring, OKF parity, and the navigation goldens — are covered by tests:
|
This repository's own gates — version consistency between each plugin entry and the tag it pins, catalog-to-plugin stat mirroring, OKF parity, and the navigation goldens — are covered by tests:
|
||||||
|
|
|
||||||
85
scripts/check-public-links.mjs
Normal file
85
scripts/check-public-links.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
// Public-link gate: fails when the catalog's public surface (README.md, the marketplace
|
||||||
|
// manifest) links to an org repository that is not on the tracked list of public repos
|
||||||
|
// below. A repository that is made private or removed turns every link to it into a dead
|
||||||
|
// link — and a manifest entry into a broken install — so the catalog may only point at
|
||||||
|
// repositories that are deliberately listed as public here.
|
||||||
|
//
|
||||||
|
// Usage: node scripts/check-public-links.mjs [repo-root]
|
||||||
|
// exit 0 = checked, 0 hits · exit 1 = hit(s), or 0 links found (verified nothing)
|
||||||
|
//
|
||||||
|
// Adding a plugin to the catalog means adding its repository to PUBLIC_REPOS in the same
|
||||||
|
// commit; removing one means taking it out of both.
|
||||||
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
// Org repositories the catalog may link to — the published plugins plus this catalog.
|
||||||
|
export const PUBLIC_REPOS = [
|
||||||
|
'ai-psychosis',
|
||||||
|
'config-audit',
|
||||||
|
'graceful-handoff',
|
||||||
|
'human-friendly-style',
|
||||||
|
'ktg-plugin-marketplace',
|
||||||
|
'linkedin-studio',
|
||||||
|
'llm-security',
|
||||||
|
'ms-ai-architect',
|
||||||
|
'okr',
|
||||||
|
'repo-mailbox',
|
||||||
|
'repo-standard',
|
||||||
|
'voyage',
|
||||||
|
];
|
||||||
|
|
||||||
|
// The files a reader or an installer reaches from the catalog.
|
||||||
|
export const SURFACE_FILES = ['README.md', '.claude-plugin/marketplace.json'];
|
||||||
|
|
||||||
|
const LINK = /git\.fromaitochitta\.com\/open\/([A-Za-z0-9._-]+)/g;
|
||||||
|
|
||||||
|
// Returns one { line, repo } per org-repo link in `content`; a trailing `.git` is dropped.
|
||||||
|
export function extractRepoLinks(content) {
|
||||||
|
const links = [];
|
||||||
|
content.split('\n').forEach((text, i) => {
|
||||||
|
for (const m of text.matchAll(LINK)) links.push({ line: i + 1, repo: m[1].replace(/\.git$/, '') });
|
||||||
|
});
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
// files: [{ path, content }]. Returns every link plus the ones not on the public list.
|
||||||
|
export function scanFiles(files, publicRepos = PUBLIC_REPOS) {
|
||||||
|
const links = [];
|
||||||
|
for (const { path, content } of files) {
|
||||||
|
for (const l of extractRepoLinks(content)) links.push({ path, ...l });
|
||||||
|
}
|
||||||
|
return { links, findings: links.filter((l) => !publicRepos.includes(l.repo)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runCheck(root) {
|
||||||
|
const files = SURFACE_FILES.filter((p) => existsSync(join(root, p))).map((path) => ({
|
||||||
|
path,
|
||||||
|
content: readFileSync(join(root, path), 'utf8'),
|
||||||
|
}));
|
||||||
|
return { files: files.length, ...scanFiles(files) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(argv) {
|
||||||
|
const root = argv[0] ?? join(HERE, '..');
|
||||||
|
const { files, links, findings } = runCheck(root);
|
||||||
|
for (const f of findings) {
|
||||||
|
console.log(`[ERROR] link to a repository not listed as public — ${f.path}:${f.line}: open/${f.repo}`);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`check-public-links: ${links.length} org-repo links in ${files}/${SURFACE_FILES.length} files, ` +
|
||||||
|
`${PUBLIC_REPOS.length} public repos listed — ${findings.length} hit(s)`,
|
||||||
|
);
|
||||||
|
if (links.length === 0) {
|
||||||
|
console.log('check-public-links: 0 links found — verified nothing');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return findings.length > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||||
|
process.exit(main(process.argv.slice(2)));
|
||||||
|
}
|
||||||
80
scripts/check-public-links.test.mjs
Normal file
80
scripts/check-public-links.test.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Tests for the public-link gate. Unit tests use synthetic content; the last test is the
|
||||||
|
// FERDIG criterion and reads this repository's own README and manifest.
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { PUBLIC_REPOS, extractRepoLinks, scanFiles, runCheck } from './check-public-links.mjs';
|
||||||
|
|
||||||
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO = join(HERE, '..');
|
||||||
|
const CLI = join(HERE, 'check-public-links.mjs');
|
||||||
|
|
||||||
|
test('extractRepoLinks finds every org-repo link with its line, dropping .git', () => {
|
||||||
|
const content = [
|
||||||
|
'# title',
|
||||||
|
'[a](https://git.fromaitochitta.com/open/voyage) and [b](https://git.fromaitochitta.com/open/okr/src/branch/main)',
|
||||||
|
'"url": "https://git.fromaitochitta.com/open/repo-mailbox.git"',
|
||||||
|
'https://example.com/open/not-ours',
|
||||||
|
].join('\n');
|
||||||
|
assert.deepEqual(extractRepoLinks(content), [
|
||||||
|
{ line: 2, repo: 'voyage' },
|
||||||
|
{ line: 2, repo: 'okr' },
|
||||||
|
{ line: 3, repo: 'repo-mailbox' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scanFiles flags only links to repositories off the public list', () => {
|
||||||
|
const { links, findings } = scanFiles(
|
||||||
|
[
|
||||||
|
{ path: 'README.md', content: 'https://git.fromaitochitta.com/open/voyage\nhttps://git.fromaitochitta.com/open/gone-private' },
|
||||||
|
{ path: 'm.json', content: '"https://git.fromaitochitta.com/open/gone-private.git"' },
|
||||||
|
],
|
||||||
|
['voyage'],
|
||||||
|
);
|
||||||
|
assert.equal(links.length, 3);
|
||||||
|
assert.deepEqual(findings.map((f) => `${f.path}:${f.line}:${f.repo}`), ['README.md:2:gone-private', 'm.json:1:gone-private']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the public list is sorted and has no duplicates', () => {
|
||||||
|
assert.deepEqual([...PUBLIC_REPOS].sort(), PUBLIC_REPOS);
|
||||||
|
assert.equal(new Set(PUBLIC_REPOS).size, PUBLIC_REPOS.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
function tmpRoot(files) {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), 'public-links-'));
|
||||||
|
for (const [path, body] of Object.entries(files)) {
|
||||||
|
mkdirSync(dirname(join(root, path)), { recursive: true });
|
||||||
|
writeFileSync(join(root, path), body);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('CLI exits 1 on a hit and 0 on a clean surface, always printing the denominator', () => {
|
||||||
|
const run = (root) => spawnSync(process.execPath, [CLI, root], { encoding: 'utf8' });
|
||||||
|
const dirty = run(tmpRoot({ 'README.md': 'https://git.fromaitochitta.com/open/voyage\nhttps://git.fromaitochitta.com/open/gone-private\n' }));
|
||||||
|
assert.equal(dirty.status, 1);
|
||||||
|
assert.match(dirty.stdout, /\[ERROR\] link to a repository not listed as public — README\.md:2: open\/gone-private/);
|
||||||
|
assert.match(dirty.stdout, /2 org-repo links in 1\/2 files/);
|
||||||
|
const clean = run(tmpRoot({ '.claude-plugin/marketplace.json': '"https://git.fromaitochitta.com/open/voyage.git"' }));
|
||||||
|
assert.equal(clean.status, 0);
|
||||||
|
assert.match(clean.stdout, /1 org-repo links in 1\/2 files, \d+ public repos listed — 0 hit/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CLI fails when it finds no links at all: verified nothing', () => {
|
||||||
|
const r = spawnSync(process.execPath, [CLI, tmpRoot({ 'README.md': 'no links\n' })], { encoding: 'utf8' });
|
||||||
|
assert.equal(r.status, 1);
|
||||||
|
assert.match(r.stdout, /verified nothing/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The FERDIG criterion: the catalog links only to repositories listed as public, and every
|
||||||
|
// plugin in the manifest is one of them.
|
||||||
|
test('this repository: README and manifest link only to public repositories', () => {
|
||||||
|
const { files, links, findings } = runCheck(REPO);
|
||||||
|
assert.equal(files, 2, 'both surface files present');
|
||||||
|
assert.ok(links.length > 0, '0 links found — verified nothing');
|
||||||
|
assert.deepEqual(findings.map((f) => `${f.path}:${f.line}: open/${f.repo}`), []);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue