From 496890011dcfc38947e91b0a26efc50d286a3dc7 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:38:40 +0800 Subject: [PATCH] refactor(skills): remove discovery guidance from built-ins --- docs/agent-skills/skill-necessity.md | 74 ----------- .../multica-autopilots/SKILL.md | 5 +- .../multica-creating-agents/SKILL.md | 19 ++- .../multica-projects-and-resources/SKILL.md | 12 +- .../multica-runtimes-and-repos/SKILL.md | 3 +- .../multica-skill-discovery/SKILL.md | 120 ----------------- .../references/skill-discovery-source-map.md | 124 ------------------ .../multica-skill-importing/SKILL.md | 25 ++-- .../multica-working-on-issues/SKILL.md | 17 ++- .../internal/service/builtin_skills_test.go | 59 +-------- 10 files changed, 48 insertions(+), 410 deletions(-) delete mode 100644 server/internal/service/builtin_skills/multica-skill-discovery/SKILL.md delete mode 100644 server/internal/service/builtin_skills/multica-skill-discovery/references/skill-discovery-source-map.md diff --git a/docs/agent-skills/skill-necessity.md b/docs/agent-skills/skill-necessity.md index dda2ffb94e..a209f6e830 100644 --- a/docs/agent-skills/skill-necessity.md +++ b/docs/agent-skills/skill-necessity.md @@ -337,80 +337,6 @@ The run with the skill passes if the agent: The brief should not permanently carry every import source and duplicate-handling workflow. The workflow matters when importing skills, so it belongs in an on-demand skill. -## `multica-skill-discovery` - -`multica-skill-discovery` exists because users may describe a capability without knowing which skill URL to import. The agent needs a discovery workflow that finds candidates but still returns to Multica's workspace import path. - -### Purpose - -The skill teaches the agent how to turn a user's need into a search query, evaluate candidate skills, pick an importable URL, and then use Multica's import API/CLI. It does not make external discovery tools the source of truth for installation. - -### Platform contract - -Discovery and installation are separate: - -- discovery should use `multica skill search --output json` / `GET /api/skills/search?q=...` to find structured candidate URLs; -- installation must use `multica skill import --url --output json` / `POST /api/skills/import`. - -### Without this skill - -A prompt like this exercises the failure: - -```text -Find a skill that helps agents improve frontend UI quality, install the best one, and explain why you chose it. -``` - -Without the skill, the agent may: - -- search poorly or use the user's whole sentence as a bad query; -- import the first search result without checking whether the `SKILL.md` matches the need; -- optimize only for install count and ignore source reputation; -- finish with `npx skills add` instead of importing into Multica; -- fail to explain why the selected skill is better than alternatives. - -### Failure mode - -The agent may install a plausible but wrong skill, or install it outside Multica. The user sees a confident recommendation, but the workspace may not contain a usable skill and future agents cannot rely on the result. - -### With this skill - -With the skill, the agent must: - -1. Convert the request into a focused search query. -2. Run `multica skill search --output json`. -3. Compare candidates using `SKILL.md` content, `install_count`, `github_stars` / `repo` when present, source reputation, generality, and importability. -4. Reject weak matches instead of importing something just to act. -5. Import the selected URL with `multica skill import --url --output json`. -6. Report the selected URL, selection rationale, import result, and whether agent binding is still needed. - -### Test scenario - -Use this prompt for an A/B evaluation: - -```text -I need a skill for frontend design review, but I do not know the URL. Find the best one and import it into Multica. -``` - -The run without the skill fails if the agent: - -- imports the first search result without reading/verifying the skill content; -- uses `npx skills add` as the final step; -- cannot justify candidate ranking; -- reports installation without Multica import output. - -The run with the skill passes if the agent: - -- searches for candidates; -- verifies candidates before import; -- chooses an importable URL; -- uses `multica skill import --url --output json`; -- reports the import result and rationale. - -### Why this belongs in a skill - -Discovery is a conditional workflow. It should not live in the always-on brief because it only matters when the user needs a skill but does not know which one. It also needs product-specific guidance: discovery is not installation; Multica import remains the final source of truth. - - ## `multica-skill-authoring` `multica-skill-authoring` exists because creating or updating a skill is not just writing Markdown. A Multica skill is durable workspace behavior: it has a trigger contract, reusable procedure, verification path, and optional supporting files that future agents will load on demand. diff --git a/server/internal/service/builtin_skills/multica-autopilots/SKILL.md b/server/internal/service/builtin_skills/multica-autopilots/SKILL.md index 3d95887cdc..20cd45112f 100644 --- a/server/internal/service/builtin_skills/multica-autopilots/SKILL.md +++ b/server/internal/service/builtin_skills/multica-autopilots/SKILL.md @@ -27,8 +27,9 @@ The chain is: trigger fires (`schedule`, `webhook`, or `manual`) -> `autopilot_r Execution modes: -- `create_issue` creates a Multica issue. Use it when the automation needs a visible issue for humans or future agents. -- `run_only` creates an agent task directly. No issue is created, so report location must come from instructions. +- `create_issue` creates a Multica issue, making the run visible as issue state. +- `run_only` creates an agent task directly. No issue is created; any durable + report location has to come from other task context or instructions. `issue-title-template` only supports `{{date}}`. Do not invent `{{trigger_id}}`, `{{branch}}`, or other variables. diff --git a/server/internal/service/builtin_skills/multica-creating-agents/SKILL.md b/server/internal/service/builtin_skills/multica-creating-agents/SKILL.md index 1253b1229d..5724c69516 100644 --- a/server/internal/service/builtin_skills/multica-creating-agents/SKILL.md +++ b/server/internal/service/builtin_skills/multica-creating-agents/SKILL.md @@ -95,13 +95,11 @@ daemon-side task error at execution time. ### model vs custom_args -Prefer `--model` over model flags in `--custom-args`. `model` is a first-class -persisted column the daemon reads directly. The CLI help notes that some -providers (codex app-server, openclaw) reject `--model` inside `custom_args` — -but that is documented CLI guidance, not a server-enforced invariant; nothing -in the create handler inspects `custom_args` for a model flag. Use `--model` -because it is the supported, persisted path, not because the server blocks the -alternative. +`model` is a first-class persisted column the daemon reads directly. +`custom_args` are raw provider CLI args. The CLI help notes that some providers +(codex app-server, openclaw) reject `--model` inside `custom_args` — but that is +documented CLI guidance, not a server-enforced invariant; nothing in the create +handler inspects `custom_args` for a model flag. ## Env & secrets @@ -129,8 +127,9 @@ Read-side facts (these are the wrong assumptions to avoid): role — a running agent cannot read another agent's secrets. - Writing values after creation does NOT go through `agent update`. The generic update handler rejects any `custom_env` field with a 400 ("use PUT - /api/agents/{id}/env"). Use `PUT /api/agents/{id}/env` - (`multica agent env set`), which is owner/admin-only and writes an audit row. + /api/agents/{id}/env"). Plaintext env writes are handled by + `PUT /api/agents/{id}/env` (`multica agent env set`), which is owner/admin-only + and writes an audit row. ## Skill binding @@ -138,7 +137,7 @@ Creating an agent does NOT bind any workspace skill — binding is a separate call after the agent exists. Two distinct verbs: - `add` is additive — it merges the given ids with existing bindings - (`POST /api/agents/{id}/skills/add`). This is the normal path. + (`POST /api/agents/{id}/skills/add`). - `set` is replace-all — it overwrites the entire binding list with exactly the given ids (`PUT /api/agents/{id}/skills`); `--skill-ids ''` clears all. diff --git a/server/internal/service/builtin_skills/multica-projects-and-resources/SKILL.md b/server/internal/service/builtin_skills/multica-projects-and-resources/SKILL.md index 9b3e788852..3069a95a8d 100644 --- a/server/internal/service/builtin_skills/multica-projects-and-resources/SKILL.md +++ b/server/internal/service/builtin_skills/multica-projects-and-resources/SKILL.md @@ -17,7 +17,8 @@ multica project get --output json multica project resource list --output json ``` -If the user asks to bind a repo or local directory to a project, use project resource commands, not an issue comment. +Project resources are mutated through project resource commands/endpoints. Issue +comments do not create durable project resources. ## Core model @@ -49,15 +50,18 @@ Use `--ref ''` only for resource types or payloads not covered by shortcut Add/update a project resource when the user asks for durable project context: "把这个 GitHub repo 绑到项目上", "以后都用这个 repo", "agent 总是拿不到这个项目的仓库", or "这个项目要在我的本地目录里跑". -Do not add a resource just because you need a one-off checkout for the current task. Use `multica repo checkout` for task-local checkout. +Project resources are durable and affect future tasks. `multica repo checkout` +is task-local checkout state. ## Debugging wrong context 1. `multica project get --output json`. 2. `multica project resource list --output json`. 3. Check `github_repo.resource_ref.url`, `default_branch_hint`, and `local_directory.resource_ref.daemon_id`. -4. If resources are wrong, update the resource, then verify by listing again. -5. If resources are right, inspect runtime/repo checkout path next. +4. Updating resources is a durable mutation. After an update, listing the + resource is the verification path. +5. If resources match the expected task context, inspect runtime/repo checkout + path next. ## Side effects diff --git a/server/internal/service/builtin_skills/multica-runtimes-and-repos/SKILL.md b/server/internal/service/builtin_skills/multica-runtimes-and-repos/SKILL.md index a2fe01dbf6..70ef44b7d9 100644 --- a/server/internal/service/builtin_skills/multica-runtimes-and-repos/SKILL.md +++ b/server/internal/service/builtin_skills/multica-runtimes-and-repos/SKILL.md @@ -58,7 +58,8 @@ Check in this order: 4. Is the runtime online? `multica runtime list --output json`. 5. Did the daemon heartbeat recently? Runtime `last_seen_at` is the visible clue. 6. Did the task get claimed or is it stuck pending/running/waiting for local directory? -7. If repo checkout failed, verify the repo was in the task/project context before blaming git. +7. If repo checkout failed, classify it after checking whether repo context was + present in the task/project context. ## Repos diff --git a/server/internal/service/builtin_skills/multica-skill-discovery/SKILL.md b/server/internal/service/builtin_skills/multica-skill-discovery/SKILL.md deleted file mode 100644 index 3ba4407137..0000000000 --- a/server/internal/service/builtin_skills/multica-skill-discovery/SKILL.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -name: multica-skill-discovery -description: Use when a user describes a capability but does not name a specific skill URL to import. Documents the facts of Multica's skill search surface — what the search returns (metadata-only candidates from clawhub.ai), which candidate fields are populated versus always-null, how upstream outage is reported, and the handoff to the import path. Discovery is candidate search, not installation; full content is only visible after import. -user-invocable: false -allowed-tools: Bash(multica *) ---- - -# Skill discovery in Multica - -Discovery turns a capability description into a list of importable skill -candidates. It is candidate search over remote metadata. It does not install -anything, and it does not fetch or preview remote skill content. - -## Quick start - -```bash -multica skill search --output json -``` - -This returns a JSON array of candidate objects. Pick one candidate's `url` and -hand it to the import skill: - -```bash -multica skill import --url --output json -``` - -## Core model - -Three facts define the discovery surface: - -1. **Discovery is metadata-only candidate search.** Each result is a small - metadata record (name, url, source, install_count, description), not the - skill body. The search path never downloads a `SKILL.md`. -2. **discovery is not installation.** `multica skill search` only lists - candidates. The skill is created in the workspace only by - `multica skill import` (see the importing skill). -3. **There is no remote content preview during search.** You cannot inspect what - a candidate skill actually does from the search result. - **full content verification happens after import** by reading the imported - workspace skill. - -## The search CLI and API - -`multica skill search --output json` (CLI subcommand `skill search`) -calls `GET /api/skills/search?q=...`, URL-escaping the query. The server handler -delegates to a clawhub.ai search and normalizes the upstream response into a -flat candidate array, so agents never parse external human-readable output. - -Empty query is rejected: - -- CLI: `runSkillSearch` returns `query is required` before any request. -- Server: the handler writes HTTP 400 `query is required` when `q` is blank. - -## Candidate field contract - -Every candidate is a `SkillSearchCandidateResponse`. The JSON shape is fixed, -but only a subset of fields ever carries a value: - -| Field | Status today | Notes | -| --- | --- | --- | -| `name` | populated | display name; falls back to the slug when blank | -| `url` | populated | always a `clawhub.ai` URL, built from owner handle + slug | -| `source` | populated | hardcoded literal `"clawhub.ai"` | -| `description` | populated | upstream summary; may be empty string | -| `install_count` | sometimes populated | only fetched for the first results (stats limit); otherwise null | -| `repo` | **always null** | the search handler never assigns it | -| `github_stars` | **always null** | the search handler never assigns it; upstream `stars` is deliberately ignored | - -`repo` and `github_stars` are dead fields. The search handler builds each -candidate with only Name/URL/Source/Description plus a conditional InstallCount -and never sets Repo or GitHubStars, so both serialize to `null` on every result. -A handler test pins this: it asserts `github_stars` stays null even when the -upstream payload carries a `stars` value. **Do not rank or justify a selection -on `repo` or `github_stars`** — they convey nothing. Rank on `install_count`, -`source`/`url`, and `description` instead. - -Because `source` is the hardcoded literal `clawhub.ai` and every `url` is built -as a clawhub.ai URL, a search result is never a `skills.sh` or `github.com` -candidate. (Those URLs are still importable directly via the import skill — they -are simply not search *results*.) - -## Upstream-unavailable handling - -The search depends on a live external upstream. When that upstream cannot be -reached or returns a non-200, the server responds with: - -- HTTP 502 -- JSON body `{"code":"upstream_unavailable","error":"..."}` - -A handler test pins the `upstream_unavailable` code on a 502. When search fails -this way, report the outage. There is no local fallback list to fall back to. - -## Handoff to import - -A candidate's `url` is the input to the import path: - -```bash -multica skill import --url --output json -``` - -Import is the only operation that creates a workspace skill. After import, read -the installed skill's full body and files to confirm fit: - -```bash -multica skill get --output json -``` - -This is where content becomes inspectable — -**full content verification happens after import**, not during search. - -Import is in-platform. It is **not `npx skills add`** or any other local -installer; those install outside Multica and do not create a managed workspace -skill. Use the importing skill for duplicate handling, returned fields, and -agent binding. - -## References - -`references/skill-discovery-source-map.md` maps every contract above to -`file:line` in the server source, quotes the candidate struct, and shows the -proof that `repo`/`github_stars` are never assigned. diff --git a/server/internal/service/builtin_skills/multica-skill-discovery/references/skill-discovery-source-map.md b/server/internal/service/builtin_skills/multica-skill-discovery/references/skill-discovery-source-map.md deleted file mode 100644 index 9e659313c1..0000000000 --- a/server/internal/service/builtin_skills/multica-skill-discovery/references/skill-discovery-source-map.md +++ /dev/null @@ -1,124 +0,0 @@ -# Skill discovery — source map - -Every contract in `SKILL.md` traced to server source. Paths are relative to the -repo root. Line numbers re-derived on branch `agent/howard/219a700a`. - -## CLI: `multica skill search ` - -- `server/cmd/multica/cmd_skill.go:66-71` — `skillSearchCmd` (`Use: "search "`, - `Args: exactArgs(1)`, `RunE: runSkillSearch`). -- `server/cmd/multica/cmd_skill.go:472-510` — `runSkillSearch`: - - `:478-481` trims the arg and returns `query is required` when empty. - - `:487` builds the path `"/api/skills/search?q=" + url.QueryEscape(query)`. - - `:488` issues `GetJSON` into `[]map[string]any`. - - `:492-495` prints raw JSON when `--output json`. - - `:497-508` table mode columns: NAME, URL, SOURCE, INSTALLS, DESCRIPTION - (no repo / github_stars column — they are never useful). - -## Route: `GET /api/skills/search` - -- `server/cmd/server/router.go:620` — `r.Get("/search", h.SearchSkills)` inside - the `r.Route("/api/skills", ...)` block (`:617`). - -## Handler: `SearchSkills` - -- `server/internal/handler/skill.go:280-297`: - - `:281-285` reads `q`, trims it, writes HTTP 400 `query is required` when blank. - - `:287-288` 30s HTTP client; delegates to `searchClawHubSkills(httpClient, query)`. - - `:289-295` on error writes HTTP 502 with body - `{"code":"upstream_unavailable","error": err.Error()}`. - - `:296` on success writes HTTP 200 with the candidate array. - -## Candidate struct: `SkillSearchCandidateResponse` - -- `server/internal/handler/skill.go:89-97`: - -```go -type SkillSearchCandidateResponse struct { - Name string `json:"name"` - URL string `json:"url"` - Source string `json:"source"` - Repo *string `json:"repo"` - InstallCount *int64 `json:"install_count"` - GitHubStars *int64 `json:"github_stars"` - Description string `json:"description"` -} -``` - -`Repo` and `GitHubStars` are pointers, so when unset they serialize to JSON -`null`. - -## Dead-field proof: `repo` and `github_stars` are never assigned - -- `server/internal/handler/skill.go:781-819` — `searchClawHubSkills`. The - per-result candidate is built at `:802-807` setting **only** Name, URL, Source, - Description: - -```go -candidate := SkillSearchCandidateResponse{ - Name: result.DisplayName, - URL: buildClawHubSkillURL(result.OwnerHandle, result.Slug), - Source: "clawhub.ai", - Description: result.Summary, -} -``` - - - `:808-810` fills `Name` from the slug only when blank. - - `:811-814` conditionally sets `InstallCount` (only for `i < clawHubSearchStatsLimit`). - - No line in the function ever assigns `candidate.Repo` or - `candidate.GitHubStars`. Both stay nil → JSON `null` on every result. -- `server/internal/handler/skill.go:805` — `Source` is the hardcoded literal - `"clawhub.ai"`. -- `server/internal/handler/skill.go:804`, `:821-826` — - `buildClawHubSkillURL` always returns a `https://clawhub.ai/...` URL, so `url` - is always a clawhub.ai URL (never skills.sh / github.com). -- `server/internal/handler/skill.go:618` — `clawHubSearchStatsLimit = 10` (the - install-count fetch cutoff). -- `server/internal/handler/skill.go:828-849` — `fetchClawHubInstallCount` - returns only an install count from clawhub stats; nothing assigns stars to the - candidate. - -### Test pinning the dead fields - -- `server/internal/handler/skill_search_test.go:92-97` — - `TestSearchSkillsReturnsNormalizedClawHubCandidates` asserts `repo` is null and - `github_stars` is null even though the upstream `/skills/react` detail payload - returns `"stars": 3` (`:40-44`). It also asserts `install_count == 62` (`:98`) - and `source == "clawhub.ai"` (`:89-91`). This is the live proof that upstream - stars are deliberately not surfaced as `github_stars`. - -## Upstream-unavailable contract - -- `server/internal/handler/skill.go:289-295` — HTTP 502 + `upstream_unavailable` - code (handler, quoted above). -- `server/internal/handler/skill.go:781-790` — `searchClawHubSkills` returns an - error on transport failure or any non-200 upstream status, which is what - triggers the 502. -- `server/internal/handler/skill_search_test.go:118-141` — - `TestSearchSkillsUpstreamUnavailableReturnsStructuredError` asserts a 502 with - `code == "upstream_unavailable"` when upstream returns 502. - -## ClawHub upstream types (for reference) - -- `server/internal/handler/skill.go:616` — `clawHubAPIBase = "https://clawhub.ai/api/v1"`. -- `server/internal/handler/skill.go:620-647` — `clawhubSearchResponse`, - `clawhubSearchResult` (`slug`, `displayName`, `summary`, `ownerHandle`), - `clawhubSkillStats` (`installsAllTime`, `installsCurrent`), `clawhubSkill`. - Upstream `stars` is not modeled into any field that reaches the candidate. - -## Post-import content verification - -- `server/cmd/multica/cmd_skill.go:33-38` — `skillGetCmd` (`Use: "get "`). -- `server/cmd/multica/cmd_skill.go:119` — `skill get` defaults `--output` to `json`. -- `server/cmd/multica/cmd_skill.go:251-279` — `runSkillGet` calls - `GET /api/skills/` and prints the skill (body + files) as JSON. This is the - first point at which full skill content is inspectable — after import, not - during search. - -## Import handoff - -- `server/cmd/multica/cmd_skill.go:60-64` — `skillImportCmd`. -- `server/cmd/multica/cmd_skill.go:412-445` — `runSkillImport` POSTs - `/api/skills/import` with the selected `--url`. This is the only path that - creates a workspace skill. See the importing skill for duplicate handling and - agent binding. diff --git a/server/internal/service/builtin_skills/multica-skill-importing/SKILL.md b/server/internal/service/builtin_skills/multica-skill-importing/SKILL.md index 3f3bee4610..afb025fc12 100644 --- a/server/internal/service/builtin_skills/multica-skill-importing/SKILL.md +++ b/server/internal/service/builtin_skills/multica-skill-importing/SKILL.md @@ -1,6 +1,6 @@ --- name: multica-skill-importing -description: Use when a user provides a skill URL, slug, or clear intent to import/install a specific skill into the current Multica workspace. Teaches the workspace import API/CLI path (POST /api/skills/import), the supported URL source families, the SkillWithFilesResponse shape returned on success, duplicate 409 handling with the existing_skill body, additive agent binding vs replace-all, and the reserved SKILL.md supporting-file rule. Do not use it to decide which skill the user needs (load multica-skill-discovery for that), and never treat an external local installer like npx skills add as the final Multica install. +description: Use when a user provides a skill URL, slug, or clear intent to import/install a specific skill into the current Multica workspace. Teaches the workspace import API/CLI path (POST /api/skills/import), the supported URL source families, the SkillWithFilesResponse shape returned on success, duplicate 409 handling with the existing_skill body, additive agent binding vs replace-all, and the reserved SKILL.md supporting-file rule. Do not use it to decide which skill the user needs, and never treat an external local installer like npx skills add as the final Multica install. user-invocable: false allowed-tools: Bash(multica *) --- @@ -11,7 +11,9 @@ Use this skill when the user already provided a skill URL, slug, or a clear inte to import a specific skill into the current Multica workspace. Do not use this skill to decide which skill the user needs. If the user only -describes a capability and no URL is known, load `multica-skill-discovery` first. +describes a capability and no URL is known, external search may produce candidate +URLs, but this import skill starts only once a URL or concrete import target is +known. Every claim below is traced to source in `references/skill-importing-source-map.md`. When in doubt, read that file. @@ -57,7 +59,8 @@ multica skill import --url github.com/owner/repo/blob/main/path/to/SKILL.md --ou ## Direct URL flow -1. If the user gave a URL, do not search first. Import it directly: +1. When the request contains a concrete URL, the import endpoint can be called +directly; search is not required by the API: ```bash multica skill import --url --output json @@ -78,8 +81,8 @@ supporting `files` array. Report the relevant fields: Because the response is structured, read these returned fields instead of guessing whether the import succeeded. -3. If the user wants an agent to use the skill, bind the returned skill id -additively. `add` preserves existing assignments and appends the new id: +3. Agent-skill binding is a separate mutable operation. `add` preserves existing +assignments and appends the new id: ```bash multica agent skills add --skill-ids --output json @@ -92,15 +95,12 @@ target skill id is present before claiming the skill is available to that agent. ## Additive add vs replace-all set `multica agent skills add` is additive: the server inserts the assignments without -clearing existing ones (`AddAgentSkills`). This is the default for "let this agent -also use the new skill." +clearing existing ones (`AddAgentSkills`). `multica agent skills set` is replace-all: the server clears every current assignment, then re-adds exactly the ids you pass (`SetAgentSkills`). -Use `set` only when the user explicitly wants to replace the full skill list. -For a normal "add this one," never use `set`, and never pass only the new id to -`set` — that -would silently drop every other skill the agent already has. +`set` is the replacement path. Passing only one id to `set` leaves the agent with +only that one skill and drops every previous assignment. ## Reserved SKILL.md supporting file @@ -177,7 +177,8 @@ Correct import: multica skill import --url https://skills.sh/owner/repo/skill --output json ``` -Correct follow-up when the skill must be available to an agent: +Agent binding after import, when the caller intentionally wants to mutate that +agent's skill assignments: ```bash multica agent skills add --skill-ids --output json diff --git a/server/internal/service/builtin_skills/multica-working-on-issues/SKILL.md b/server/internal/service/builtin_skills/multica-working-on-issues/SKILL.md index bea557d4a7..e850348b14 100644 --- a/server/internal/service/builtin_skills/multica-working-on-issues/SKILL.md +++ b/server/internal/service/builtin_skills/multica-working-on-issues/SKILL.md @@ -44,8 +44,9 @@ Fix login MUL-2759 # links only — keyword not ``` Consequence: a bare title prefix or a branch reference links the PR but does not -close the issue on merge. Use a closing keyword only when merge should move the -issue to `done`. +close the issue on merge. A closing keyword immediately adjacent to the issue key +records close intent; on merge, that close intent can move the linked issue to +`done`. ## Reading a linked PR's real state @@ -74,13 +75,14 @@ Returns `{"pull_requests": [...]}`. Each element exposes: So "is it merged?" is `state == "merged"` (or `merged_at != null`); "is it still a draft?" is `state == "draft"`; CI status is `checks_conclusion`. -If the command returns no linked PRs after you opened one, the title/body/branch -is missing the issue key — fix the PR rather than asserting the issue is linked. +If the command returns no linked PRs after a PR was opened, the link scanner did +not observe a routable issue key in the PR title/body/branch. ## Metadata: high-signal keys only -Read metadata on entry when the runtime supplies issue context. Write a key only -when a future run on the same issue is likely to re-read it. +Metadata is durable issue state. Reading metadata is safe. Writing a metadata key +is a state mutation and should be tied to an explicit task requirement to record +that state for later readers or runs. High-signal keys (reuse these names so queries stay consistent): @@ -112,7 +114,8 @@ on it. These are the contracts, not advice: - **`backlog`** parks an agent-assigned issue: the assignee is set but no task fires. Moving `backlog → todo` (or any non-done/non-cancelled status) enqueues the assigned agent then. -- **`in_review`** is the normal state once a PR is open and awaiting review. +- **`in_review`** is an accepted issue status. Some workflows use it while a PR + is open and awaiting review; moving to it is an explicit mutation. - **`done`** on a child issue posts a system comment on its parent. If a PR carries close intent (`Closes MUL-XXXX`), it advances the issue to `done` itself on merge — you do not also need to flip it manually. diff --git a/server/internal/service/builtin_skills_test.go b/server/internal/service/builtin_skills_test.go index 87c72f0b8c..30becf1577 100644 --- a/server/internal/service/builtin_skills_test.go +++ b/server/internal/service/builtin_skills_test.go @@ -235,7 +235,7 @@ func TestSkillImportingSkillCoversWorkspaceImportContracts(t *testing.T) { "multica agent skills add --skill-ids --output json", "multica agent skills list --output json", "replace-all", - "Use `set` only when the user explicitly wants to replace", + "`set` is the replacement path", "references/skill-importing-source-map.md", } for _, want := range mustContain { @@ -259,59 +259,6 @@ func TestSkillImportingSkillCoversWorkspaceImportContracts(t *testing.T) { } } -func TestSkillDiscoverySkillCoversMetadataOnlyPreImportContracts(t *testing.T) { - skill, ok := findSkill(t, "multica-skill-discovery") - if !ok { - return - } - fm, body, _ := splitFrontmatter(skill.Content) - - if got := strings.TrimSpace(fm["user-invocable"]); got != "false" { - t.Errorf("user-invocable = %q, want false (skill discovery guidance triggers from context)", got) - } - if got := strings.TrimSpace(fm["allowed-tools"]); !strings.Contains(got, "Bash(multica *)") { - t.Errorf("allowed-tools = %q, want access to the Multica CLI", got) - } - - // repo/github_stars are intentionally NOT pinned: searchClawHubSkills never - // populates them (skill.go), so they are always null and the skill must not - // teach them as live selection signals. - mustContain := []string{ - "multica skill search --output json", - "GET /api/skills/search?q=...", - "clawhub.ai", - "upstream_unavailable", - "metadata-only", - "full content verification happens after import", - "install_count", - "multica skill import --url --output json", - "not `npx skills add`", - "discovery is not installation", - "references/skill-discovery-source-map.md", - } - for _, want := range mustContain { - if !strings.Contains(body, want) { - t.Errorf("skill-discovery skill missing %q", want) - } - } - - mustNotContain := []string{ - "content match in `SKILL.md`, not only the title", - "If a candidate looks good from the search result but the `SKILL.md` does not", - "source reputation, and SKILL.md content", - "verify before import", - } - for _, forbidden := range mustNotContain { - if strings.Contains(body, forbidden) { - t.Errorf("skill-discovery skill should not imply remote content preview %q", forbidden) - } - } - - if !skillHasFile(skill, "references/skill-discovery-source-map.md") { - t.Errorf("skill-discovery skill missing supporting file references/skill-discovery-source-map.md") - } -} - func TestCreatingAgentsSkillCoversAgentCreationContracts(t *testing.T) { skill, ok := findSkill(t, "multica-creating-agents") if !ok { @@ -331,7 +278,7 @@ func TestCreatingAgentsSkillCoversAgentCreationContracts(t *testing.T) { "`description` is a catalog summary", "`instructions` is the runtime behavior contract", "multica agent create --name --runtime-id ", - "Prefer `--model`", + "`model` is a first-class persisted column", "custom_env", "--custom-env-stdin", "--custom-env-file", @@ -495,7 +442,7 @@ func TestProjectsAndResourcesSkillCoversDurableContext(t *testing.T) { "multica project resource list --output json", "multica project resource add --type github_repo --url --output json", "multica project resource add --type local_directory", - "Do not add a resource just because you need a one-off checkout", + "Project resources are durable and affect future tasks", "github_repo.resource_ref.url", "references/projects-and-resources-source-map.md", }