Commit Graph
114 Commits
Author SHA1 Message Date
Michael Sitarzewski ea3e7e5322 Merge branch 'rudy-897' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 731efe6c9b Merge branch 'rudy-896' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 611d0448e7 Merge branch 'rudy-894' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 3cc60d0e3f Merge branch 'rudy-893' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 545b569e30 Merge branch 'rudy-892' into integ/rudy-series 2026-09-29 10:24:42 -05:00
Michael Sitarzewski 3c195668b0 Merge branch 'rudy-891' into integ/rudy-series 2026-09-29 10:24:41 -05:00
Michael Sitarzewski 747ad1576d Merge branch 'rudy-899' into integ/rudy-series 2026-09-29 10:24:41 -05:00
Michael Sitarzewski 71753b1b3f Merge branch 'rudy-903' into integ/rudy-series 2026-09-29 10:24:41 -05:00
fruitandClaude Opus 5.5 68f01534ef fix(install): Hermes plugin actually gets enabled on real Hermes configs (#881)
The Hermes installer printed "[OK] enabled plugin" and "Done!" while leaving agency-agents-router disabled or absent. On current Hermes this hit every user: the default plugins: block contains a column-0 "# ====" banner, and `hermes plugins enable/disable` writes enabled:/disabled: below it.

- Bound the enabled: sub-block at the first sibling key; sweep stale entries out of disabled: (Hermes: "an explicit disable wins"); handle inline [], [a,b], disabled: above enabled:, trailing comments, corrupted-scalar recovery; bail on shapes that can't be edited line-wise. (@guozi-lab)
- A column-0 comment no longer ends the plugins: block, and the rewrite's exit status propagates (`|| return 1`) so a bail warns instead of reporting success. (maintainer follow-up)
- check-hermes-config-rewrite.py: 16 cases, including Hermes' real shape.

Verified in real Hermes (official installer, 9a0a162) on configs Hermes wrote itself, with a live turn on local qwen3.8-27b: main -> "agency_agents_delegate does not exist"; this change -> delegated: true, real subagent, real result.

Closes #879

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 07:03:44 -05:00
Rudy Celekli 4e2d91d687 fix(installer): protect unrelated Hermes destinations 2026-09-28 01:05:47 -04:00
Rudy Celekli fda3beeaba fix(convert): refuse duplicate agent slugs before writing 2026-09-28 00:32:16 -04:00
Rudy Celekli 435cf1d232 fix(ci): reject empty required agent metadata 2026-09-28 00:16:58 -04:00
Rudy Celekli 0249ecd37f fix(ci): select direct agent files for lint and originality 2026-09-28 00:05:15 -04:00
Rudy Celekli 77a898401a fix(eval): preserve manifest when output validation fails 2026-09-27 23:47:25 -04:00
Rudy Celekli 2bc53022ef fix(installer): honor --path for Aider roster 2026-09-27 23:39:04 -04:00
Rudy Celekli 3e7dda0ae7 fix(installer): honor single Copilot destination override 2026-09-27 23:32:32 -04:00
Rudy Celekli a47212e9e5 fix(convert): preserve Markdown separators in agent bodies 2026-09-27 23:24:27 -04:00
Roberto SolanoandClaude Opus 5.5 479193dcce feat(integration): add DeepSeek Harness (dsh) skills support (#846)
Adds DeepSeek Harness as an integration target: 279 agency-* SKILL.md skills installed to ${DSH_HOME:-~/.dsh}/skills (or DSH_SKILLS_DIR for project scope).

Verified in DeepSeek Harness itself (@deepseek-ai/dsh 0.1.7-rc.2, installed via the documented npx path on Ubuntu 26.04 arm64): the repo installer placed 279 skills, and dsh's own skill-filesystem parser accepted all 279 with zero warnings (a planted invalid name and a missing description were both rejected with dsh's own messages).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 18:45:36 -05:00
Michael SitarzewskiandClaude Opus 5 652277fba1 fix(install): tell aider users when their CONVENTIONS.md is our stale roster (#875)
#871 shrank the aider integration from 3.8M characters of concatenated agents
to a 97K roster index, because aider keeps a conventions file in context for a
whole session. The fix only reached new installs.

install_aider refuses to overwrite an existing CONVENTIONS.md, which is right —
it is aider's own user-authored file and the one on disk may be the reader's.
But "already exists (remove to reinstall)" says nothing about which file it is,
so anyone holding the pre-index roster re-ran the installer, read that, and
kept the broken file. The people the fix was written for were the ones it could
not reach.

Our generated file has always opened with "# The Agency — AI Agent
Conventions", so the installer can now tell its own stale copy from someone
else's conventions and say which it is. Neither branch writes anything.

Verified in a container, all three cases:
  stale Agency roster   -> names it, reports 3800039 bytes, says to delete and re-run
  user's own file       -> "leaving your file alone", file intact
  no file               -> installs, 97065 bytes

Windsurf has the same guard and the same gap; #870 already handles it there, so
this leaves that path alone rather than colliding with it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 20:44:43 -05:00
Michael SitarzewskiandClaude Opus 5 87f8301cad chore(eval): regenerate the convert-outputs manifest for #871 and #872
Both PRs change generators, so every agent line moves: #871 rewrites the aider
CONVENTIONS.md as a roster index rather than 3.8M characters of concatenated
agents, and #872 teaches resolve_opencode_color() the `slate` and `navy` names
that were silently falling through to grey.

Neither PR carried the manifest, so main was left in drift.

Regenerated from a clean detached worktree at origin/main rather than the
managed clone: `--update` walks the working tree, and an untracked agent file
in that clone would have added a 280th line for a file no one else has.

Verified: 29 passed, 0 failed (279 roster agents x 14 tools).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 18:39:14 -05:00
Hotragn Pettugani 1f92396556 fix(convert): two color names fall through to grey, and nothing checked (#872)
* fix(convert): two color names fall through to grey, and nothing checked

resolve_opencode_color() maps a name it does not recognise to #6B7280 and
says nothing about it. Four agents ask for a name it has never known:

  engineering/engineering-minimal-change-engineer.md   slate
  specialized/operations-manager.md                    slate
  gis/gis-technical-consultant.md                      navy
  specialized/chief-financial-officer.md               navy

All four render grey in the OpenCode integration, which reads as a
deliberate grey rather than a miss — and some agents do choose grey, so
there was nothing to notice.

`slate` and `navy` are now in the map. Values follow the CSS named color
where one exists (navy -> #000080, matching how teal already works) and
Tailwind's 500 shade otherwise (slate -> #64748B, matching gray).

Adding two names does not stop the next one, so two checks now cover it:

  - lint-agents.sh rejects a color that is neither #RRGGBB nor a name the
    converter knows, and prints the names it knows. The list is read out
    of resolve_opencode_color() rather than copied, so it cannot drift
    from the map that does the work. This runs on changed files in agent
    PRs, which is where a new color arrives.

  - test-convert-outputs.sh fails when an opencode output is #6B7280 and
    the source did not ask for grey. Grey stays a legitimate choice; the
    check is "grey only when the source said so".

CONTRIBUTING now says which color values actually work, since the template
just said `colorname or "#hexcode"`.

Verified: all 279 agents pass the new lint rule, and no opencode output
falls through to grey by accident.

* ci(lint): run the whole roster when the linter itself changes

The lint job scopes itself to the agent files a PR touched, which is right
for an agent PR and wrong for a rule change. A new rule lands without ever
having run against the other 278 agents — it either breaks a division
nobody edited or quietly does nothing, and either way CI is silent.

The colour check in the previous commit is the case in point: it changes
scripts/, so the lint workflow's path filter did not even fire, and the
rule would have merged without CI looking at a single agent.

Adds a lint-all job that runs the full-roster lint when scripts/
lint-agents.sh, convert.sh, or lib.sh is part of the diff, and says why it
is skipping when they are not. Those three paths are in the workflow's
trigger list now so the job can fire at all.
2026-09-20 18:35:13 -05:00
Hotragn Pettugani d3a3f573e3 fix(aider): CONVENTIONS.md is a roster index, not 3.8 million characters of agents (#871)
Aider loads a conventions file into context and keeps it there for the whole
session — that is what the file is for, and the docs say to load it with
--read so prompt caching can hold it. This integration concatenated every
agent body into it. At 279 agents that is 3,816,372 characters, roughly a
million tokens. No model takes that. Anyone who ran

    ./scripts/install.sh --tool aider

got a CONVENTIONS.md that either blows the context window on the first turn
or bills for a million tokens trying.

CONVENTIONS.md is now the roster index it was described as: one entry per
agent with the name, the description, the division, and the path to the
agent file. 96,823 characters, down from 3.8 million. The header explains
how to pull a single agent's full instructions into the session:

    /read-only /path/to/agency-agents/engineering/engineering-frontend-developer.md

Naming an agent in a prompt still works the way it did — the description is
what the model needed for that, and it is still there.

test-convert-outputs.sh now holds the index to being an index: it fails if
CONVENTIONS.md grows past 250,000 characters, if it does not list exactly
one path per roster agent, or if any path it prints does not resolve. The
existing round-trip check on the accumulated file still covers the names and
descriptions.
2026-09-20 18:35:10 -05:00
ad9264e309 fix(convert): track code fences when splitting OpenClaw sections, and pin it with an invariant (#855)
convert_openclaw() split on `## ` without tracking fenced code blocks, tearing six fenced examples across SOUL.md/AGENTS.md in five agents and leaving dangling fences in each. Implementation from #854 (@guozi-lab): fence helpers in lib.sh used by convert.sh and lint-agents.sh, CommonMark indent aware. Regression test from #853 (@dajiaohuang): every source fenced block must land whole in exactly one output file — verified to fail on the unfixed converter.

Reported by @AmineOzil. Closes #849. Supersedes #853 and #854.

Co-Authored-By: fruit <200041037+guozi-lab@users.noreply.github.com>
Co-Authored-By: Wu Shuwen <108231307+dajiaohuang@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:57:20 -05:00
6d29a9b087 feat(agents): land six specialists — China Network, Platform, Focus Music, PDF Engine, ATS Validator, Universal Document Compiler (#845)
Consolidated landing of #768, #811, #808, #822, #842, #843 — agent files as submitted, README roster rows, regenerated Hermes README, manifest v2 (+6 agent lines). Verified together: lint, originality, guards, converted-frontmatter, outputs eval 26/26 (279 agents x 14 tools), installer 36/0, agent-selection, Hermes checks; PR CI green on all 7.

Closes #768. Closes #811. Closes #808. Closes #822. Closes #842. Closes #843.

Co-Authored-By: Sagarika Sultana <283121436+madebysaira@users.noreply.github.com>
Co-Authored-By: Sunil Kumar <24809771+sunilkumarvalmiki@users.noreply.github.com>
Co-Authored-By: augustoheiss <240949329+augustoheiss@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 19:10:22 -05:00
Michael SitarzewskiandClaude Fable 5.1 449e0c71b1 test(convert): manifest v2 — one line per agent, platform-neutral hashes, advisory drift on PRs (#844)
See the PR for the why/what/proof. Contributors no longer touch scripts/convert-outputs.sha256; drift is advisory on pull requests and strict on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:59:03 -05:00
647c8baa42 fix(install): preserve list-item indent when adding the Hermes plugin; regression test + CI (#714)
ensure_hermes_plugin_enabled() hardcoded a 2-space indent for the inserted `- agency-agents-router` line. Hermes writes plugins.enabled items at 4 spaces, so the new line and the next existing item collapsed into one plain scalar ("agency-agents-router - disk-cleanup") and every previously enabled plugin silently dropped (#839, and #689 diagnosed the same in July). The inserter now matches the existing item indent (default 4), appends after existing entries so order is preserved, and is idempotent on re-run. scripts/check-hermes-config-rewrite.{sh,py} runs seven regression cases; a small workflow runs it on every PR.

Reproduced on main with a 4-space config and verified fixed with this patch; installer suite 36/0; the regression script passes 7/7.

Fixes #839. Closes #689 (same fix, proposed first by @harshsinghmp).

Co-Authored-By: Harsh Singh <32476777+harshsinghmp@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 15:47:19 -05:00
Michael SitarzewskiandClaude Fable 5.1 1454492577 chore(eval): update the convert-outputs manifest for the Hermes builder change (#803)
#803 changed scripts/build-hermes-plugin.py, so the generated Hermes plugin legitimately
changed and the drift manifest's hermes line went stale — main's "Validate converted outputs"
step failed on drift alone (25/26 passed; every invariant held). Regenerated with
scripts/test-convert-outputs.sh --update on a clean checkout of main; the suite is 26/26.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 12:40:16 -05:00
wnwhermesandClaude Fable 5.1 80b338fea8 fix(hermes): delegate through Hermes' public subagent lifecycle instead of nested delegate_task (#803)
agency_agents_delegate previously nested delegate_task through ctx.dispatch_tool(), which returns error JSON as a string instead of raising — so the router reported delegated: true with {"error": "delegate_task requires a parent agent context."} and never delegated (#802, #838). It now launches the specialist through ctx.subagent_lifecycle (upstream hermes-agent PR #72501): bounded wait, cooperative cancel on timeout, honest delegated: false + fallback prompt on launch/terminal failure, 32,000-char context cap, toolsets option removed. Kept in scripts/build-hermes-plugin.py so regeneration preserves it; six behavior tests in scripts/test-hermes-plugin.py.

Verified live in an isolated Hermes git-main (v0.21.0) box on local Qwen: main plugin -> delegated: true with the error string in 0.01 s; this plugin -> real child session, real result.

Fixes #802. Fixes #838.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 12:32:07 -05:00
af128a9288 fix(install): honor CLAUDE_CONFIG_DIR as config root, not agents dir (#834)
resolve_dest() for claude-code now appends /agents to CLAUDE_CONFIG_DIR (the config root that replaces ~/.claude); a value already ending in /agents is used verbatim and a trailing slash is stripped. detect_claude_code() honors CLAUDE_CONFIG_DIR so relocated configs are detected. Three regression cases in scripts/test-install.sh.

Fixes #578. The diagnosis and the same fix were first proposed by @halindrome in #579 (June); this lands the current-main version with tests.

Co-Authored-By: Shane McCarron <520688+halindrome@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 13:06:36 -05:00
Michael SitarzewskiandClaude Fable 5.1 04eadbd3e5 test(convert): regression eval for generated outputs + app contracts; fix two get_field bugs it found (#829)
Adds scripts/test-convert-outputs.sh, the output half of the regression eval
(the install half landed in #828), and wires it plus the previously
un-wired test-agent-selection.sh (#779) into CI.

Why an eval at all: every converter bug so far passed lint and the existing
tests while the installed product was broken. #778 shipped a double-wrapped
description that was valid YAML, so a wrapper check passed; #817 dropped a
whole tool from --parallel and every remaining tool looked fine. Those are
invariant violations, not syntax errors.

Layer A (no history needed), for every agent x every converted tool:
  round-trip   parsed(generated).description == source description
  strict-parse every generated frontmatter / TOML / YAML parses with a real
               parser (kimi/vibe carry only an identifier: id == slug and the
               prose file exists; aider/windsurf: "## Name" + description line)
  count        every tool emits exactly one output per roster agent
  source       every SOURCE frontmatter strict-parses and carries no leaked
               quote — the desktop app reads sources with js-yaml (#473)
Layer B: scripts/convert-outputs.sha256, one aggregate hash per tool plus
divisions.json / tools.json / runbooks.json. A flipped line means outputs or a
contract changed; --update regenerates deliberately so review sees the blast
radius. Date-stable (no generated file embeds a date).

The expected side is derived by an INDEPENDENT strict parse of each source,
never by lib.sh's get_field: the generator uses get_field, so an expected
value derived the same way would move with a get_field bug and hide it —
which is exactly how #778 stayed invisible. That independence found two
shipping defects on the first green run:

- get_field returned only the first line of a multi-line plain scalar.
  Three healthcare agents write their description as an indented
  continuation; every generated output for them shipped it truncated
  mid-sentence while the app showed the whole thing. get_field now folds
  continuation lines the way YAML does (newline -> single space).
- get_field stripped only "field: " (one space). The same three files use
  column-aligned frontmatter (name:        X), so their generated names
  carried leading whitespace in every tool's output. Plain-scalar padding
  is now trimmed.

After both fixes get_field agrees with PyYAML on name and description for
all 273 sources. Acceptance: re-introducing #778's double-wrap, a dropped
tool, a divisions.json change, and an unquoted source each fail the eval
(the double-wrap via Layer A round-trip, not only the manifest).

Refs #778 #817 #473 #810 #826 #828 #779

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:58:13 -05:00
128565a828 fix(install): refuse --path only for colliding tools; re-land installer test suite (#772) (#828)
* Reapply "test(install): add a regression suite for install.sh + CI on Linux and macOS (#772)" (#827)

This reverts commit 4bab3cf4a2.

* fix(install): refuse --path only for tools that would overwrite each other; re-land installer test suite (#772)

Re-lands the install.sh regression suite from #772 (reverted in #827) together
with the guard change that makes it pass, so CI goes green in one step.

Background. #825 made --path refuse more than one --tool, on #819's report that
several tools sharing one destination clobber each other. That was right in
spirit and over-broad in practice, and it was implemented without verifying
the premise. Measured by installing one agent with every tool into a sandbox
and comparing what landed:

  <division>-<slug>.md  (raw copy)   claude-code, copilot
  <slug>.md             (converted)  gemini-cli, opencode, qwen, zcode
  agency-<slug>/SKILL.md             antigravity, osaurus

Tools in the same group write identical filenames and silently overwrite each
other (qwen + gemini-cli lose a file while both print [OK]). Tools in different
groups coexist (claude-code + codex, claude-code + qwen). Every other tool's
output is distinct.

The guard now refuses --path only for a colliding pair, naming both tools and
the reason, and allows the rest. path_collision_group() holds the measured
table; re-measure if a converter's naming changes.

The suite's two-tool --path cases used claude-code + copilot, which collide:
the "installs exactly one agent" count of 1 was passing because copilot had
overwritten claude-code's identical file. They now use claude-code + codex and
assert that BOTH outputs survive, which a single count cannot show; a new case
asserts the colliding pair is refused. codex has no committed output, so those
cases convert (only the two raw-copiers work under --no-convert in a fresh
checkout).

Verified under bash 3.2 (the macOS CI leg): 29 passed, 0 failed, 1 xfail.
Guard spot-checked against all three measured groups plus a cross-group pair.

Suite, workflow and CONTRIBUTING note by @SergiorCode (#772). Refs #772 #819
#825 #827.

Co-Authored-By: SergiorCode <SergiorCode@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(install): auto-convert never fired in a fresh checkout — README.md read as generated output

ensure_converted decided a converted tool's output was present if its
integrations/<tool>/ directory contained any file. Every one of those
directories ships a committed README.md, so in a fresh checkout the check
always found a file, skipped convert.sh, and the installer then hard-failed
"integrations/<tool> missing. Run convert.sh first." — the exact flow #426's
auto-convert was added to prevent. This affected every converted tool; it was
masked locally by generated outputs left behind in the working tree, and
surfaced only when the test suite ran in a clean checkout.

Only files other than README.md now count as generated output. Verified in a
clean worktree under bash 3.2: the suite's serial-control case (claude-code +
codex into one --path) now auto-converts codex and both outputs land;
29 passed, 0 failed, 1 xfail.

Refs #426

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: SergiorCode <SergiorCode@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:15:08 -05:00
Michael Sitarzewski 4bab3cf4a2 Revert "test(install): add a regression suite for install.sh + CI on Linux and macOS (#772)" (#827)
This reverts commit 1e2d1b940e.
2026-09-02 20:51:43 -05:00
Sergio Romero 1e2d1b940e test(install): add a regression suite for install.sh + CI on Linux and macOS (#772)
* test(install): add a regression suite for install.sh + CI on Linux and macOS

install.sh is the largest script in the repo and has no tests. Every install
bug so far has been a silent one — agents copied to the wrong directory, a
path with a space split in two, a filter that installed everything — and the
only signal was a user noticing later.

scripts/test-install.sh pins the installer's observable contract:

  * destinations: default $HOME/.claude/agents, --path override, tool env var
    override, and --path winning over the env var
  * selection: --division, --agent, --agents-file (comments/blank lines)
  * --dry-run writes nothing; unknown --tool exits non-zero
  * --link produces symlinks; a second run installs the same set, not dupes
  * a destination containing spaces stays one directory

Expected counts are derived from divisions.json + lib.sh at runtime, so the
suite doesn't need updating when agents are added. Every case runs with HOME
pointed at a throwaway sandbox, so a broken default path can never write into
the real config. bash 3.2 + BSD userland, no new dependencies.

Verified it fails on the regressions it claims to catch: unquoting install_file
fails only the spaces case, neutering slug_allowed fails the four selection
cases, un-short-circuiting --dry-run fails the dry-run case, and ignoring the
env var in resolve_dest fails the env-override case.

CI runs it on ubuntu-latest and macos-latest (macOS ships bash 3.2, Linux
ships bash 5) plus bash -n over every script in scripts/.

* test(install): pin the parallel worker argument regression (#755)

Review feedback: the existing "paths with spaces" case selects a single tool,
so it stays on the serial path and never reaches the worker spawn where #755's
bug lives. Adds a case that does.

  --tool claude-code,copilot --parallel --jobs 1 --agents-file <spaced path>
  --path "<home>/My [Agents]/dest dir"

with a serial control immediately before it (same two tools, same spaced and
globbed --path, no --parallel) so a failure is attributable to the worker
hand-off rather than to the selection filter.

Marked xfail rather than a hard assertion: it fails on main today and passes
with #755 applied, and encoding a known-broken case as a hard failure would
turn CI red for reasons unrelated to whatever PR is being reviewed. xfail
never fails the suite; when the case starts passing it prints a note to
promote it to assert_eq (one-word edit). Measured on macOS bash 3.2.57:
main -> 25 passed / 1 xfail, #755 applied -> 26 passed / 0 failed, both
deterministic over repeated runs.

Note on --jobs 1: workers are still spawned through the same xargs/sh
hand-off, so argument propagation is exercised in full. Serializing them
keeps a second, unrelated defect out of this case — with two workers running
concurrently against one shared --path, the parent exits non-zero on ~3 runs
in 5 once the workers actually copy anything (one worker's cp fails with
ENOENT on the shared destination). That race is invisible on main only
because the workers currently install nothing at all; --jobs 1 or per-tool
destinations are clean. Reported in the PR discussion.
2026-09-02 20:48:48 -05:00
Michael SitarzewskiandClaude Fable 5.1 3febe026c1 fix(lib): get_field strips a quoted YAML scalar's outer quotes so quoted sources don't double-wrap (#826)
Contributors keep reaching for the same fix when a description contains ": "
and breaks YAML frontmatter: double-quote the source scalar (#473 for
zk-steward, re-proposed in #548, and again in #810). #778 fixed the same
problem one layer down by having the converters emit quoted scalars. The two
compose badly: get_field returned a quoted source's quotes as content, so
yaml_quote wrapped them again and every generated file for an already-quoted
agent shipped as  description: '"..."'  with a literal quote leaking into the
parsed value. zk-steward and ai-data-remediation-engineer are affected on
main today.

get_field now treats one matching outer pair of double or single quotes as
delimiters: it strips them and unescapes (\" -> ", \\ -> \, '' -> '). Quoting a
source description is now safe and harmless, so #473/#548/#810's instinct and
#778's generator-side fix reconcile. Unquoted sources are unchanged.

Verified in a sandbox end-to-end (source -> convert.sh -> parsed YAML):
zk-steward and ai-data-remediation-engineer no longer leak a quote and keep
their internal apostrophes; developer-tooling-engineer (unquoted, contains
": ") is byte-identical to before; synthetic '...''...' and "...\"...\\" cases
unescape correctly. lib.sh is only ever sourced by bash (its shebang); note
`repeat()` at the top of the TUI section is a zsh reserved word, so sourcing
lib.sh from a zsh shell errors — pre-existing and unrelated.

Refs #473 #548 #810 #778

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 20:32:18 -05:00
Michael SitarzewskiandClaude Fable 5.1 91d37aa3af fix(scripts): kimi skipped in --parallel, stale progress idx, --path multi-tool guard, rm -rf slug guard (#825)
Four install/convert bugs reported by @sunilkumarvalmiki (#817-#820), each
verified in a sandbox before and after the change.

- convert.sh --tool all --parallel silently skipped kimi: the parallel batch
  listed 11 tools and the sequential batch 2, so the 14th tool ran in neither
  (kimi output: 0 files vs 273 for every other tool). Add kimi to the parallel
  batch — it writes to its own integrations/kimi/<slug>/ dir so it is
  parallel-safe. (#817)
- The sequential batch's progress counter was hardcoded idx=8, stale from an
  older batch size, printing "aider (8/14)" instead of 12/14. Derive it from
  the parallel list length so it can never drift again; now prints 13/14 and
  14/14. Same root cause as #817. (#818)
- --path is a documented single-destination override; with several --tool
  values every tool resolved to the same directory and clobbered each other.
  Refuse --path with more than one tool. Deliberately NOT restricting the path
  itself: the override is the supported way to redirect installs (e.g. to a
  sandbox), and validating it against an expected dir would break that. (#819)
- clean_tool_output runs rm -rf on $OUT_DIR/$1. The tool name is validated
  upstream so this is not reachable today, but a plain-slug guard on $1 makes a
  future direct caller unable to steer it outside $OUT_DIR via "../" or "/".
  A prefix check would not do: "$OUT_DIR/../x" still starts with the prefix. (#820)

Fixes #817
Fixes #818
Fixes #819
Fixes #820

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 20:04:22 -05:00
3c9588880b Add Research division + Research Synthesist (consolidated #770) (#807)
* Add Research division with Research Synthesist agent

New division for literature review, source evaluation, and evidence
synthesis. Wired into divisions.json, convert.sh, lint-agents.sh,
install.sh, and the lint-agents CI workflow.

* chore(hermes): regenerate agent count 272 -> 273 for research division (#770)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Prashant Raj Bista <prashant.bista.18@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-26 09:57:10 -05:00
hari 9572c66428 fix(installer): deduplicate repeated tool selections (#777)
Signed-off-by: Mr-Neutr0n <harikp2002@gmail.com>
2026-08-26 08:50:52 -05:00
hari 3464daa3c6 fix(convert): quote YAML frontmatter values (#778)
Signed-off-by: Mr-Neutr0n <harikp2002@gmail.com>
2026-08-26 08:49:51 -05:00
hari 3570801f74 fix(install): reject unknown agent selections (#779)
Signed-off-by: Mr-Neutr0n <harikp2002@gmail.com>
2026-08-26 08:46:45 -05:00
Pip, Agent PipandMichael Sitarzewski 6e45066041 fix(hermes): expose Agency tool parameters (#717)
Co-authored-by: Michael Sitarzewski <michael@sitarzewski.com>
2026-07-15 09:55:50 -05:00
Michael SitarzewskiandClaude Opus 4.8 00fb28a4cf feat(tools): add ZCode (Z.ai GLM agent harness) (#700)
* feat(tools): add ZCode (Z.ai GLM agent harness)

ZCode reads per-agent markdown from `.zcode/agents/{slug}.md` (project) and
`~/.config/zcode/agents/{slug}.md` (global), with `name` + `description` YAML
frontmatter and an optional `tools` list — the same plain-agent-markdown shape
as Qwen.

- tools.json: add the `zcode` entry (dual-scope, per-agent, format `zcode-md`).
- convert.sh: add `convert_zcode` (byte-identical to the qwen converter,
  output to integrations/zcode/agents/) + register in the dispatch, valid_tools,
  tools_to_run, and parallel_tools.
- install.sh: add `zcode` to ALL_TOOLS, `install_zcode`, `detect_zcode`, and the
  resolve_dest / bin / is_detected / display / label dispatches.

Directories + file format verified against ZCode's published docs. Renderer
contract: `zcode-md` output is byte-identical to `qwen-md`, verified by
diffing `convert.sh --tool zcode` against `--tool qwen` (243/243 files match).

check-tools.sh passes (16 tools consistent across tools.json, install.sh, and
convert.sh); bash -n clean on both scripts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdX6PvnCfRgYD11yVpXVor

* zcode: gitignore generated output + add integration README

Bring the ZCode integration in line with the other tools: the 243 generated
agent files (integrations/zcode/agents/*.md) are output of `convert.sh`, not
source — add a .gitignore rule so they can't be committed, and add
integrations/zcode/README.md documenting generate/install (matching the vibe
and qwen integration READMEs).

Verified: generated agents are now git-ignored, the README is tracked, and
check-tools.sh still passes at 16 tools.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

* zcode: fix user-scope install path to ~/.zcode/agents (per ZCode docs)

The user-scope dest was ~/.config/zcode/agents/, but the official ZCode docs
state subagents are read from ~/.zcode/agents/<name>.md — so user-scope
installs landed where ZCode never looks. (detect.agentsDir and project scope
were already correct at .zcode/agents, so this was an internal inconsistency.)

Point tools.json dest.user, install_zcode's default, the header comment, and
the list display at ~/.zcode/agents; drop the stale ~/.config/zcode detection
clause; update the integration README. Keep format `zcode-md` distinct — ZCode's
native format supports color/model/permissions, so it will diverge from gemini-md
as the converter matures rather than being a permanent alias.

Verified: a default user-scope install now writes to ~/.zcode/agents/<slug>.md
(not ~/.config/zcode); check-tools.sh passes at 16 tools; install.sh syntax and
tools.json JSON both valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 11:29:16 -05:00
134b4d08e6 fix(lint): stop SIGPIPE from faking missing sections (#710)
`grep -q` exits at its first match without draining stdin, killing the
piping `echo` with SIGPIPE. Under `set -o pipefail` that 141 becomes the
pipeline's status, which is indistinguishable from "no match" — so a
section that is present gets reported missing. The race only surfaces on
bodies large enough that `echo` is still writing when `grep` bails, which
made the warning set differ between identical runs (full repo: 106/87/90
warnings across three runs; now a stable 59).

Feed both checks from a herestring so there is no writer to signal.

Co-authored-by: Jaak Vaher <jaak.vaher@cyber.ee>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 14:00:09 -05:00
Michael SitarzewskiandClaude Opus 4.8 3293712e41 fix(install): honor comma-separated --tool list (as the help documents) (#678)
The help text advertised `--tool <a,b>` (a comma list), but --tool took a
single value and validated it whole, so `--tool claude-code,cursor` failed
with "Unknown tool 'claude-code,cursor'". --division and --agent already
split on commas; --tool didn't (#671).

Split --tool on commas, trim each entry, and validate each against
ALL_TOOLS (mirrors --division), so a bad entry still errors clearly by
name. Single-tool use is unchanged.

Verified: --tool claude-code,cursor installs both; "claude-code, cursor"
(with spaces) works; --tool claude-code,nope errors on 'nope'; --tool
cursor still works.

Fixes #671


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:43:03 -05:00
Michael SitarzewskiandClaude Opus 4.8 1b31b7a670 fix(install): derive division list from divisions.json (adds healthcare) (#677)
install.sh hardcoded the division set in two lists (AGENT_DIRS,
ALL_DIVISIONS), and both had gone stale — missing healthcare (#655). So
`--tool claude-code`/`copilot` skipped healthcare's 2 agents,
`--division healthcare` errored "Unknown division", the interactive team
list omitted it, and the agent count was low by 2 (#668).

Derive both lists from divisions.json (the single source of truth), using
the same no-jq awk/grep/sed parse as check-divisions.sh. ALL_DIVISIONS is
now exactly the divisions.json entries; AGENT_DIRS is that set plus
strategy/ (preserving the intentional scan of its frontmatter-less docs,
which is_agent_file filters out). This is the same fix pattern as #659
(check-agent-originality.sh) and #666 (build-hermes-plugin.py): a derived
list can't drift, so check-divisions.sh needn't be extended to cover it.

Verified: ALL_DIVISIONS resolves to 17 (healthcare in, strategy out),
strategy still scanned, and `--division healthcare --dry-run` now finds
2 agents instead of "Unknown division".

Fixes #668


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:49:43 -05:00
Michael SitarzewskiandClaude Opus 4.8 ef1352f84b fix(install): never rm -rf a shared Hermes plugins directory (#676)
install_hermes() resolved `dest` from HERMES_PLUGIN_DIR and ran `rm -rf
"$dest"`. The var name invites setting it to the plugins *parent*
(~/.hermes/plugins) rather than the full plugin path, in which case the
rm -rf wiped the entire plugins directory and every other plugin in it.

Always target the agency-agents-router subdir (append it when the resolved
path doesn't already end in it), and add a defensive guard that refuses to
remove any path whose basename isn't `agency-agents-router`. Verified: with
HERMES_PLUGIN_DIR set to the plugins parent, a sibling plugin's data now
survives the install.

Fixes #669


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:49:40 -05:00
Michael SitarzewskiandClaude Opus 4.8 6f8d5e50ea fix(hermes): accept slug alias + derive divisions from divisions.json (#666)
Two correctness fixes to the Hermes plugin generator:

1. slug alias (#665): agency_agents_search returns results keyed by `slug`,
   but load/inspect/delegate only accepted a param named `agent`, so the
   natural chain search -> load(slug=...) failed with "agent not found".
   Add `slug` as an optional alias across the READ/PROMPT/DELEGATE schemas
   and resolve either key in the handlers (via _identifier), with a clear
   "agent or slug is required" error when neither is passed. Backward
   compatible; `required` relaxed to [] (task-only for delegate).

2. division drift: AGENT_DIRS was a hardcoded copy of the division list that
   the bash check-divisions.sh guard can't see (it's a Python list), so it
   silently dropped healthcare (#655) — the two healthcare agents were
   missing from the Hermes roster. Derive the division dirs from
   divisions.json instead (mirrors the #659 fix to check-agent-originality.sh),
   so the roster stays in sync with the catalog by construction.

Verified on the regenerated plugin: roster is 235 agents (healthcare now
indexed); search "clinical evidence healthcare" -> inspect(slug=...) resolves
to Clinical Evidence Agent — exercising both fixes together. agent= still works.

Fixes #665


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:25:08 -05:00
Michael SitarzewskiandClaude Opus 4.8 217a63b8b6 Derive originality check's division set from divisions.json (#659)
check-agent-originality.sh hardcoded its own copy of the division list
(AGENT_DIRS) in the Python heredoc — a 5th copy that check-divisions.sh's
bash-array parser never saw, so it drifted: it was missing `gis` and
`security` and still carried the retired `strategy`. The practical effect
was that every gis/ and security/ agent — including newly added ones —
skipped the duplicate-detection scan entirely.

Read divisions.json directly instead of hardcoding, so this check can
never drift from the catalog again. Now scans all 16 divisions; verified
green in full-audit mode.

Supersedes #649/#650, which patch the hardcoded constants rather than
removing them.


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:52:12 -05:00
Michael SitarzewskiandClaude Opus 4.8 384dbbd2a8 docs: add tool-integration checklist + stop hardcoding roster counts (#663)
Two related drift traps, both from hand-typed numbers/lists that no guard
watches:

1. CONTRIBUTING had no "how to add a tool" checklist, and its wording
   ("all output is gitignored") implied gitignoring was automatic — so
   tool contributors kept committing generated integrations/<tool>/ output.
2. The division set and agent/division counts were hardcoded in prose in
   several places and had already gone stale (CONTRIBUTING said "16" and
   omitted healthcare; EXECUTIVE-BRIEF said "9 divisions").

Changes:
- Add an "Adding a Tool Integration" checklist to CONTRIBUTING (discuss-first,
  reuse an existing `format`, the ~5-file touch list incl. the required
  .gitignore rule, run check-tools.sh). Harmonize the "committed build
  output" policy line to point at it.
- De-hardcode the division list in CONTRIBUTING — defer to divisions.json.
- Stop scattering roster counts: strategy/EXECUTIVE-BRIEF ("9 divisions") and
  check-agent-originality.sh ("184-agent library") drop the number entirely;
  README keeps a showcase stat but softens "232 across 16" to "230+ across
  every division" so it never becomes a lie as the roster grows.


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:45:36 -05:00
Michael SitarzewskiandClaude Opus 4.8 cb45d3ea8c Add strategy/runbooks.json — NEXUS runbook rosters by slug + CI guard (#664)
The app can't reliably resolve runbook rosters from display names (catalog
slugs are inconsistently division-prefixed, and names drift). This adds a
machine-readable manifest so the app reads rosters as data and maps each
slug to a catalog agent for one-click team deploy.

- strategy/runbooks.json: the 4 NEXUS scenarios (startup-mvp,
  enterprise-feature, marketing-campaign, incident-response), each with
  mode, duration, summary, doc, and a grouped roster. Every agents[] entry
  is a verified slug = the agent .md filename stem (the corpus id), resolved
  against the live roster — not a slugified display name. (Notably
  "Senior Project Manager" is project-manager-senior, NOT
  project-management-senior-project-manager, which naive mapping assumes.)
- scripts/check-runbooks.sh + .github/workflows/check-runbooks.yml: guard
  (mirrors check-divisions.sh) failing the build if any roster slug doesn't
  resolve to a real agent file, a doc path is missing, or JSON is malformed —
  so renaming/removing an agent can't silently break the app's deploy.

All 64 slug references verified; guard passes and fails correctly.


Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:41:01 -05:00
Laurent WandrebeckandMistral Vibe 90ae2b27d1 Add Mistral Vibe support for Agency agents (#658)
* Add Mistral Vibe support for Agency agents

- Add Mistral Vibe entry to tools.json with proper configuration
  (id, label, kebab, format, installKind, dest, detection, version)
- Implement convert_vibe() function in convert.sh for Mistral Vibe's format
  - Generates TOML agent configuration files (~/.vibe/agents/<slug>.toml)
  - Generates markdown prompt files (~/.vibe/prompts/<slug>.md)
  - Each agent gets agent_type and system_prompt_id (no hardcoded active_model)
- Add install_vibe() function in install.sh with full feature support
  - Copies both agent TOML and prompt MD files
  - Supports division/agent filtering and environment variable overrides
  - Uses VIBE_HOME environment variable for custom install paths
- Add Mistral Vibe detection and tool labeling
- Add Mistral Vibe to all necessary case statements and arrays
- Update README.md to document Mistral Vibe support
- All changes validated with scripts/check-tools.sh

Mistral Vibe uses a two-file approach per agent:
- ~/.vibe/agents/<slug>.toml for agent configuration
- ~/.vibe/prompts/<slug>.md for system prompts

Users can specify active_model in their agent TOML files or rely on their
Vibe configuration default model.

Usage: ./scripts/install.sh --tool vibe [--division X] [--agent Y]

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

* Address PR #658 review feedback: add .gitignore, README, and fix icon

- Add integrations/vibe/README.md documenting the Mistral Vibe integration
- Update .gitignore to ignore integrations/vibe/agents/ and prompts/
- Update convert.sh usage() to include vibe in the tool list
- Fix tools.json: change vibe icon from 'mistral' to null (no mistral.svg)
- Bonus: update vibe accent color from #FF69B4 to #FA520F (Mistral brand orange)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>

---------

Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-07-05 04:21:50 -05:00