Skip to content

feat: Agent Skills for the Sawala CLI and MCP servers, plus sawala skills install - #97

Merged
sutisnamulyana merged 6 commits into
mainfrom
feat/agent-skills
Aug 4, 2026
Merged

feat: Agent Skills for the Sawala CLI and MCP servers, plus sawala skills install#97
sutisnamulyana merged 6 commits into
mainfrom
feat/agent-skills

Conversation

@sutisnamulyana

Copy link
Copy Markdown
Member

What

Ships nine Agent Skills that teach an AI coding agent how to drive the Sawala CLI and MCP servers, plus a sawala skills install command that puts them on a customer's machine and a CI guard that stops them drifting behind the code.

The skills carry what --help does not: that a collection or schema update is a PUT replacement so adding a field means pull-append-push, that a Datana boolean filter silently returns zero rows, that Tugasna dates are epoch-ms numbers, that sebar broadcast create sends with no undo, and that akuna isolate is effectively one-way.

Layout — one copy, three vendors

SKILL.md is a cross-vendor standard, but the discovery directory is not:

Tool Reads
Codex .agents/skills/ only — not .codex, not .claude
Copilot .agents/skills/, .github/skills/, or .claude/skills/
Claude Code .claude/skills/

So .agents/skills/ is canonical (the one directory Codex and Copilot both read) with .claude/skills a relative symlink. No generated per-vendor copies, nothing to drift.

The skills

sawala-cli (orientation, shared conventions, Formulir + Berkasna) · sawala-kontena · sawala-datana · sawala-tugasna · sawala-sebar · sawala-akuna · sawala-ajena · kodena-deploy · sawala-cli-dev (internal, not published).

Formulir and Berkasna get no skill of their own — read-only list/get with no traps, so a skill would only restate --help.

Five documented facts were wrong at HEAD

Every gotcha was verified against source before being written down. Five did not survive:

  • --q searches all text fields, not data.title. The service deliberately widened it; the CLI/MCP help strings are stale.
  • record list sends limit=100 when the flag is omitted, while its help says 25.
  • The blank Content-Type defect is already fixed for static deploys — the default shim carries an extension→MIME safety net. Only worker-bundle (OpenNext) deploys are exposed, because they replace that shim.
  • run_worker_first is set by the service, not the caller.
  • The Sebar surface is inbound-email config + broadcasts, not senders/messages. Those commands do not exist.

Two CLI defects found, not fixed here

Documentation-only PR, so both are documented rather than patched:

  1. SAWALA_PROJECT desynchronises slug from id. loadContext reads activeProject from the env var but activeProjectId only from config. Kontena/Datana put the ULID in the path while apiFetch sends the slug as x-project-id — so the request names two different projects. Cross-project correctness bug.
  2. requireActiveProject advertises --project, a flag @sawala/cli never wires (every call site is loadContext(SAWALA_BRAND) with no options). Only kodena deploy passes them.

Also stale: Datana's --q and --limit help strings. All four are small follow-ups.

The guard

scripts/check-skills-coverage.mjs, wired as npm run check:skills and into ci.yml. Fails when a CLI command group or MCP tool name appears in no skill, when a skill breaks the spec (name pattern, name/dir match, description ≤1024, body ≤500 lines, dangling links), or when a skill contains a credential-shaped string.

It tests mention, not correctness — no script can judge whether a description is accurate, and a stricter rule would be gamed by pasting names in a footer. Mention is enough to force the author to open the skill.

Adding the 53 tool names surfaced them as undocumented; each is now in a CLI↔MCP mapping table in its owning skill.

Verification

  • npm run typecheck — clean
  • npm run test — 538 passed
  • npm run check:skills — 53/53 MCP tools, 29/29 command groups across 9 skills
  • Guard demonstrated failing on an injected undocumented tool, then passing
  • End-to-end from a scratch dir: list, install --dry-run (writes nothing), install, re-run refuses without --force, --target all writes three copies
  • Negative paths: unknown skill name, unknown --target, --dir /etc/cron.d — all exit 1 and write nothing
  • 16 tests in packages/sawala/test/skills.test.ts, including four negative cases

Not done

Confirming activation in Codex and Copilot needs a human at those agents. The mechanical half is covered by the spec validation in CI; what remains is the disambiguation pass — that "add a field to my collection" reaches sawala-datana and not sawala-kontena, and three similar pairs.

ExecPlan: docs/plan/cli/PLAN-sawala-agent-skills.md in sawala-cloud.

Establish .agents/skills/ as the canonical Agent Skills directory (read by
Codex and Copilot) with .claude/skills as a relative symlink for Claude Code.

Adds the sawala-cli orientation skill: establish scope before acting, choose
between CLI and MCP, the surface map, shared write conventions (PUT
replacement), safety rails, and failure triage keyed to the CLI's real error
strings.
Datana: collections, records, the filter grammar, the pipeline plane, and a
references/field-types.md covering every SchemaField type and the options keys
that are silently dropped.

Kontena: schemas, entries, locales, and the single-vs-collection distinction.
Documents the entry envelope ({locale,data,slug,status}) against Datana's bare
field-values body, since confusing them is a guaranteed 422.

Corrects two facts that no longer hold at HEAD: --q searches all text fields
(not data.title), and record list defaults to limit=100 (not 25).
Tugasna: boards/statuses/items/comments/tags/timeline plus a backlog
reference. Documents two departures from the content stores — updates are
PATCH not PUT, and dates are epoch-ms numbers not ISO strings.

Sebar: the CLI is inbound email config (org-scoped) plus broadcast campaigns
(project-scoped) — not sender/message management. broadcast create SENDS with
no undo, so it is documented as dry-run-then-approve.

Akuna: connections and data residency. Isolation is effectively one-way, so
the skill requires status -> dry-run -> explicit user approval.

Also corrects sawala-cli's shared write conventions: PUT-replacement is a
Kontena/Datana rule, not universal.
kodena-deploy: the three deploy shapes, the mandatory kodena.json, and the
blank Content-Type trap scoped correctly — Kodena's default shim already
carries an extension-map safety net, so only worker-bundle (OpenNext) deploys
are exposed. Also records that run_worker_first is set by the service, so
callers should not try to set it.

sawala-ajena: pull -> edit -> validate -> push --check as the only safe
workflow, the token-derived scoping that puts no projectId in the path, the
per-step enabled switch for bisecting, and the secret-free round-trip.
scripts/check-skills-coverage.mjs fails CI when a CLI command group or MCP
tool name appears in no skill, when a skill breaks the Agent Skills spec
(name pattern, name/dir match, description length, 500-line body, dangling
links), or when a skill contains a credential-shaped string.

Wired as npm run check:skills and added to ci.yml. Adding the tool names
surfaced 53 undocumented tools; each is now in a CLI-to-MCP mapping table in
the owning skill rather than a bare name list.
Relocates the eight customer-facing skills to packages/sawala/skills/ so they
ride the npm tarball, symlinked back into .agents/skills/ so contributors and
customers read the same bytes.

Adds sawala skills list|install|uninstall. --target maps to each vendor's
discovery directory (agents/codex -> .agents/skills, claude -> .claude/skills,
copilot -> .github/skills or ~/.copilot/skills, all -> three); default is
agents, which Codex and Copilot both read. When only .agents is written and a
.claude/ exists, it suggests --target all or a symlink rather than acting.

Requested names are matched against the bundled set and never joined into a
path; destinations outside cwd/home need --force; existing folders are never
overwritten without --force. 16 tests including four negative cases.
@sutisnamulyana
sutisnamulyana merged commit 066b7cf into main Aug 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant