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
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run check
- run: npm run pack:dry
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.7
- run: bun install --no-save --ignore-scripts
- run: bun run check
- run: bun run pack:dry
Comment thread
coderabbitai[bot] marked this conversation as resolved.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

### Added

- share documentation workflows
- add public Herdr fleet workflow
- add plugin packaging and question TUI (#26) [#26]
- share documentation workflows (#30) [#30]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- add skill-inspector plugin + verdict-aware renderer
- Add skill-inspector skill: skillspector wrapper with readable reports (#28) [#28]
- add ship-issue Claude Code plugin (#27) [#27]
Expand All @@ -28,6 +30,8 @@

### Changed

- bump ws from 8.20.0 to 8.21.1 in the npm_and_yarn group across 1 directory (#31) [#31]
- bump undici in the npm_and_yarn group across 1 directory (#29) [#29]
- bump shell-quote (#16) [#16]
- bump the npm_and_yarn group across 2 directories with 1 update (#12) [#12]
- Port claude-md-improver skill to Claude Code plugin (#13) [#13]
Expand All @@ -36,6 +40,10 @@
- bump the npm_and_yarn group across 1 directory with 2 updates (#5) [#5]
- Initial commit

### Fixed

- harden Herdr fleet orchestration

### Security

- bump protobufjs to 7.5.6 (#6) [#6]
Expand Down
1 change: 1 addition & 0 deletions docs/catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Keep this as the human-readable record of what lives in the package.
| `grill-with-docs` | Skill | `skills/grill-with-docs/SKILL.md` | active | Stress-tests plans against project domain language, updates `CONTEXT.md`/ADRs as decisions crystallize, and uses recommendation-first `AskUserQuestion` decision prompts. |
| `harness-audit` | Skill | `skills/harness-audit/SKILL.md` | active | Audits repo harness readiness and fix gaps; copy into harness-specific inventories for daily use when isolation matters. |
| `harness-worktrees` | Skill | `skills/harness-worktrees/SKILL.md` | active | Manages Pi/Superconductor worktree refreshes and resets after PR merges. |
| `herdr-fleet` | Skill | `skills/herdr-fleet/SKILL.md` | active | Orchestrates user-confirmed, project-scoped Herdr worker rosters from one control pane. |
| `scaffold-notes` | Skill | `skills/scaffold-notes/SKILL.md` | active | Maintenance skill for adding resources to this repo consistently. |
| `skill-inspector` | Skill | `skills/skill-inspector/SKILL.md` | active | Global-first skill (symlinked into `~/.claude/skills/skill-inspector`); security-scans agent skills with the `skillspector` CLI and renders a plain-English verdict report (safe/caution/do-not-install, threat breakdown, top findings) for chat. |
| `critical-bug-hunt.prompt` | Prompt | `prompts/critical-bug-hunt.prompt.md` | active | Recent-commit audit prompt for high-severity correctness bugs and minimal fixes. |
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@
]
},
"scripts": {
"check": "npm run lint && npm run typecheck && npm test && npm run validate:plugins && node scripts/validate-package-contents.mjs && npm run validate:skills && node scripts/list-resources.mjs",
"list": "node scripts/list-resources.mjs",
"validate:plugins": "node scripts/validate-plugin-ports.mjs && node scripts/validate-codex-plugins.mjs",
"check": "bun run lint && bun run typecheck && bun run test && bun run validate:plugins && bun scripts/validate-package-contents.mjs && bun run validate:skills && bun scripts/list-resources.mjs",
"list": "bun scripts/list-resources.mjs",
"validate:plugins": "bun scripts/validate-plugin-ports.mjs && bun scripts/validate-codex-plugins.mjs",
"install:bash-guard:global": "node scripts/install-bash-guard-global.mjs",
"new:extension": "node scripts/new-extension.mjs",
"new:skill": "node scripts/new-skill.mjs",
"pack:dry": "npm pack --dry-run",
"pack:dry": "bun pm pack --dry-run",
"format": "biome format --write .",
"lint": "biome lint .",
"lint:fix": "biome check --write .",
"prepare": "husky",
"test": "node extensions/autopilot/v2-smoke-test.mjs && node extensions/question/question-smoke-test.mjs && node extensions/conditional-hooks/smoke-test.mjs && node scripts/lib/frontmatter-test.mjs && node scripts/lib/bundle-refs-test.mjs && node scripts/cli-entrypoint-test.mjs && python3 skills/diataxis-docs-site/tests/test_create_site.py",
"test": "node extensions/autopilot/v2-smoke-test.mjs && node extensions/question/question-smoke-test.mjs && node extensions/conditional-hooks/smoke-test.mjs && node scripts/lib/frontmatter-test.mjs && node scripts/lib/bundle-refs-test.mjs && node scripts/cli-entrypoint-test.mjs && python3 skills/diataxis-docs-site/tests/test_create_site.py && bun test scripts/lib/package-links.test.mjs && bun run test:herdr-fleet",
"test:herdr-fleet": "bun skills/herdr-fleet/scripts/resolve-project-key.mjs --self-test && bun skills/herdr-fleet/scripts/watch-fleet.mjs --self-test && bun skills/herdr-fleet/scripts/consume-events.mjs --self-test && bun test skills/herdr-fleet/scripts/fleet-state.test.mjs skills/herdr-fleet/scripts/review-thread-gate.test.mjs",
"typecheck": "tsc --noEmit",
"validate:skills": "node scripts/validate-agent-skills.mjs"
"validate:skills": "bun scripts/validate-agent-skills.mjs"
},
"peerDependencies": {
"@mariozechner/pi-ai": "*",
Expand Down
38 changes: 38 additions & 0 deletions scripts/lib/package-links.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import path from "node:path";

function headingAnchors(content) {
return new Set(
content
.split("\n")
.map((line) => line.match(/^#{1,6}\s+(.+)$/)?.[1])
.filter(Boolean)
.map((heading) =>
heading
.toLowerCase()
.replace(/[`*_~]/g, "")
.replace(/[^\p{L}\p{N}\s-]/gu, "")
.trim()
.replace(/\s+/g, "-"),
),
);
}

export function findBrokenPackedLinks({ sourcePath, content, packedPathSet, readTarget }) {
const broken = [];
for (const match of content.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
const target = match[1].trim().split(/\s+/, 1)[0];
if (/^[a-z][a-z0-9+.-]*:/i.test(target)) continue;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const [relativeTarget, anchor] = target.split("#", 2);
const targetPath = relativeTarget
? path.posix.normalize(path.posix.join(path.posix.dirname(sourcePath), relativeTarget))
: sourcePath;
if (!packedPathSet.has(targetPath)) {
broken.push(`${sourcePath} -> ${target}`);
continue;
}
if (anchor && !headingAnchors(readTarget(targetPath)).has(anchor)) {
broken.push(`${sourcePath} -> #${anchor}`);
}
}
return broken;
}
45 changes: 45 additions & 0 deletions scripts/lib/package-links.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test } from "bun:test";
import assert from "node:assert/strict";
import { findBrokenPackedLinks } from "./package-links.mjs";

const readTarget = () => "# Included target\n";

test("checkout-only targets fail packed-link validation", () => {
const broken = findBrokenPackedLinks({
sourcePath: "skills/herdr-fleet/README.md",
content: "[private](private.md)",
packedPathSet: new Set(["skills/herdr-fleet/README.md"]),
readTarget,
});
assert.deepEqual(broken, ["skills/herdr-fleet/README.md -> private.md"]);
});

test("packed targets with valid anchors pass", () => {
const broken = findBrokenPackedLinks({
sourcePath: "skills/herdr-fleet/README.md",
content: "[included](included.md#included-target)",
packedPathSet: new Set(["skills/herdr-fleet/README.md", "skills/herdr-fleet/included.md"]),
readTarget,
});
assert.deepEqual(broken, []);
});

test("link destinations ignore surrounding whitespace and optional titles", () => {
const broken = findBrokenPackedLinks({
sourcePath: "skills/herdr-fleet/README.md",
content: '[included]( included.md#included-target "Title" ) [external]( https://example.com "Title" )',
packedPathSet: new Set(["skills/herdr-fleet/README.md", "skills/herdr-fleet/included.md"]),
readTarget,
});
assert.deepEqual(broken, []);
});

test("whitespace-prefixed missing targets remain broken after title removal", () => {
const broken = findBrokenPackedLinks({
sourcePath: "skills/herdr-fleet/README.md",
content: '[missing]( missing.md "Title" )',
packedPathSet: new Set(["skills/herdr-fleet/README.md"]),
readTarget,
});
assert.deepEqual(broken, ["skills/herdr-fleet/README.md -> missing.md"]);
});
67 changes: 60 additions & 7 deletions scripts/validate-package-contents.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { spawnSync } from "node:child_process";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { findBrokenPackedLinks } from "./lib/package-links.mjs";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const result = spawnSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], { cwd: root, encoding: "utf8" });
const result = spawnSync("bun", ["pm", "pack", "--dry-run", "--ignore-scripts"], {
cwd: root,
encoding: "utf8",
});

if (result.status !== 0) {
process.stderr.write(result.stderr || result.stdout);
process.exit(result.status ?? 1);
}

const [pack] = JSON.parse(result.stdout);
const packedPaths = pack.files.map((file) => file.path);
const packedPaths = result.stdout
.split("\n")
.map((line) => line.match(/^packed\s+\S+\s+(.+)$/)?.[1])
.filter(Boolean);
const packedPathSet = new Set(packedPaths);
const requiredPaths = [
"skills/diataxis-docs-site/SKILL.md",
Expand All @@ -20,16 +27,62 @@ const requiredPaths = [
"skills/diataxis-docs-site/scripts/create_site.py",
"skills/github-wiki/SKILL.md",
"skills/github-wiki/agents/openai.yaml",
"skills/herdr-fleet/SKILL.md",
"skills/herdr-fleet/README.md",
"skills/herdr-fleet/launch-fleet.md",
"skills/herdr-fleet/protocols.md",
"skills/herdr-fleet/fixtures/compaction-failures.json",
"skills/herdr-fleet/fixtures/fleet-state-race.json",
"skills/herdr-fleet/fixtures/project-key-collision.json",
"skills/herdr-fleet/fixtures/review-thread-pages.json",
"skills/herdr-fleet/fixtures/watcher-identity.json",
"skills/herdr-fleet/scripts/consume-events.mjs",
"skills/herdr-fleet/scripts/fleet-labels.mjs",
"skills/herdr-fleet/scripts/fleet-state.mjs",
"skills/herdr-fleet/scripts/fleet-state.test.mjs",
"skills/herdr-fleet/scripts/resolve-project-key.mjs",
"skills/herdr-fleet/scripts/review-thread-gate.mjs",
"skills/herdr-fleet/scripts/review-thread-gate.test.mjs",
"skills/herdr-fleet/scripts/watch-fleet.mjs",
"scripts/lib/package-links.mjs",
"scripts/lib/package-links.test.mjs",
];
const missing = requiredPaths.filter((requiredPath) => !packedPathSet.has(requiredPath));
const forbidden = packedPaths.filter(
(packedPath) => /(?:^|\/)__pycache__(?:\/|$)/.test(packedPath) || /\.py[cod]$/.test(packedPath),
);

if (missing.length > 0 || forbidden.length > 0) {
if (missing.length > 0) console.error(`Missing required package files:\n${missing.join("\n")}`);
if (forbidden.length > 0) console.error(`Forbidden generated artifacts in package:\n${forbidden.join("\n")}`);
function markdownFiles(directory) {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) return markdownFiles(entryPath);
return entry.isFile() && entry.name.endsWith(".md") ? [entryPath] : [];
});
}

const brokenLinks = [];
for (const markdownPath of markdownFiles(path.join(root, "skills/herdr-fleet"))) {
const sourcePath = path.relative(root, markdownPath).split(path.sep).join(path.posix.sep);
if (!packedPathSet.has(sourcePath)) {
brokenLinks.push(`${sourcePath} is not packed`);
continue;
}
brokenLinks.push(
...findBrokenPackedLinks({
sourcePath,
content: readFileSync(markdownPath, "utf8"),
packedPathSet,
readTarget: (targetPath) => readFileSync(path.join(root, targetPath), "utf8"),
}),
);
}

if (missing.length > 0 || forbidden.length > 0 || brokenLinks.length > 0) {
if (missing.length > 0) process.stderr.write(`Missing required package files:\n${missing.join("\n")}\n`);
if (forbidden.length > 0)
process.stderr.write(`Forbidden generated artifacts in package:\n${forbidden.join("\n")}\n`);
if (brokenLinks.length > 0) process.stderr.write(`Broken herdr-fleet links:\n${brokenLinks.join("\n")}\n`);
process.exit(1);
}

console.log("✓ required skill package files are included");
process.stdout.write("✓ package contents and herdr-fleet links are valid\n");
5 changes: 3 additions & 2 deletions skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ This directory is the repo's generic skills lane. The skills CLI also discovers
| `grill-with-docs` | [`grill-with-docs/`](grill-with-docs/) | Stress-tests plans against project domain language and records resolved terms/ADRs as decisions crystallize. | Generic Agent Skill. |
| `harness-audit` | [`harness-audit/`](harness-audit/) | Audits repos for autonomous-agent harness readiness and unattended ticket execution gaps. | Generic Agent Skill; also available as a Claude Code plugin. |
| `harness-worktrees` | [`harness-worktrees/`](harness-worktrees/) | Manages Pi/Superconductor worktree refresh and reset workflows after PR merges. | Generic Agent Skill; also available as a Claude Code plugin. |
| `herdr-fleet` | [`herdr-fleet/`](herdr-fleet/) | Launches and reconciles user-confirmed, project-scoped Herdr worker fleets from one control pane. | Requires `HERDR_ENV=1`; defaults to report-only merge policy. |
| `scaffold-notes` | [`scaffold-notes/`](scaffold-notes/) | Maintains this repo's Pi package resources and docs when adding or refactoring skills/extensions/prompts/themes. | Repo maintenance skill. |

## Validate

```bash
npm run validate:skills
npx skills add . --list
bun run validate:skills
bunx skills add . --list
```
36 changes: 36 additions & 0 deletions skills/herdr-fleet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Herdr Fleet

`herdr-fleet` helps one control pane launch or rebuild a project-scoped Herdr fleet. It asks the user for the roster, previews the pane map, reuses ownership-proven workers, and creates only confirmed missing panes. There is no default worker count or fixed worker roster.

See [SKILL.md](SKILL.md) for the agent contract, [launch-fleet.md](launch-fleet.md) for intake and reconciliation, and [protocols.md](protocols.md) for the live event loop.

## Roster intake

Each worker is user-selected:

- label;
- launch command;
- role (`implementer`, `reviewer`, or another role);
- optional assignment or lane constraint;
- pane placement.

The current control pane is fixed and is not included in the worker count. Launch and rebuild require an `AskUserQuestion` roster preview and confirmation before worker-pane mutation.

## Launcher menu

These are launcher options, not a default roster:

| Command | Routed model | Effort |
| --- | --- | --- |
| `claudex` | GPT-5.6 Sol | xhigh |
| `claudex --model fable` | GPT-5.6 Sol | xhigh |
| `claudex --model opus` | GPT-5.6 Sol | high |
| `claudex --model sonnet` | Grok 4.5 | high |
| `claudex --model haiku` | GPT-5.6 Terra | medium |
| `claude` | Native Claude Code | native model selection |

Comment thread
coderabbitai[bot] marked this conversation as resolved.
The roster can also use `pi`, `codex`, or any user-provided command.

Claudex keeps the Claude Code harness—its tools, permissions, skills, hooks, context management, and interface—while routing model traffic through CLIProxyAPI. Ossie's setup article, [The Fable Effect](https://aojdevstudio.me/blog/the-fable-effect/#how-i-ran-gpt-56-sol-inside-the-claude-code-harness), is the canonical setup reference. The article is currently a draft, so the URL may return 404 until publication.

This package intentionally does not duplicate proxy configuration, credentials, local paths, or setup secrets.
Loading
Loading