mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-19 04:38:50 +02:00
Compare commits
1 Commits
agent/lamb
...
agent/lamb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6ee718117 |
@@ -33,16 +33,20 @@ func writeContextFiles(workDir, provider string, ctx TaskContextForEnv) error {
|
||||
return fmt.Errorf("write issue_context.md: %w", err)
|
||||
}
|
||||
|
||||
if len(ctx.AgentSkills) > 0 {
|
||||
// Codex skills are written to codex-home in Prepare; skip writing them
|
||||
// into the workdir's skills dir here. The built-in multica-cli skill is
|
||||
// always installed (regardless of whether the agent has user-defined
|
||||
// skills) so the agent can lazy-load the full CLI manual on demand —
|
||||
// see MUL-1821 for why the manual is no longer inlined into the runtime
|
||||
// config.
|
||||
if provider != "codex" {
|
||||
skillsDir, err := resolveSkillsDir(workDir, provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve skills dir: %w", err)
|
||||
}
|
||||
// Codex skills are written to codex-home in Prepare; skip here.
|
||||
if provider != "codex" {
|
||||
if err := writeSkillFiles(skillsDir, ctx.AgentSkills); err != nil {
|
||||
return fmt.Errorf("write skill files: %w", err)
|
||||
}
|
||||
skills := append([]SkillContextForEnv{builtinMulticaCLISkill()}, ctx.AgentSkills...)
|
||||
if err := writeSkillFiles(skillsDir, skills); err != nil {
|
||||
return fmt.Errorf("write skill files: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,10 +197,12 @@ func Reuse(workDir, provider, codexVersion string, task TaskContextForEnv, logge
|
||||
}
|
||||
|
||||
func writeCodexWorkspaceSkills(codexHome string, skills []SkillContextForEnv) error {
|
||||
if len(skills) == 0 {
|
||||
return nil
|
||||
}
|
||||
return writeSkillFiles(filepath.Join(codexHome, "skills"), skills)
|
||||
// The built-in multica-cli skill is always installed alongside the
|
||||
// agent's user-defined skills so the lazy-loaded full CLI manual is
|
||||
// available even when the agent has no other skills attached
|
||||
// (MUL-1821).
|
||||
all := append([]SkillContextForEnv{builtinMulticaCLISkill()}, skills...)
|
||||
return writeSkillFiles(filepath.Join(codexHome, "skills"), all)
|
||||
}
|
||||
|
||||
// GCMeta is persisted to .gc_meta.json inside the env root so the GC loop
|
||||
|
||||
@@ -669,8 +669,13 @@ func TestInjectRuntimeConfigNoSkills(t *testing.T) {
|
||||
if !strings.Contains(s, "multica issue get") {
|
||||
t.Error("should reference multica CLI even without skills")
|
||||
}
|
||||
if strings.Contains(s, "## Skills") {
|
||||
t.Error("should not have Skills section when there are no skills")
|
||||
// The built-in multica-cli skill is always installed (MUL-1821) so the
|
||||
// Skills section is always rendered even without user-defined skills.
|
||||
if !strings.Contains(s, "## Skills") {
|
||||
t.Error("expected Skills section listing the built-in multica-cli skill")
|
||||
}
|
||||
if !strings.Contains(s, multicaCLISkillName) {
|
||||
t.Errorf("Skills section missing built-in skill %q", multicaCLISkillName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,14 +899,16 @@ func TestPrepareWithRepoContextOpencode(t *testing.T) {
|
||||
t.Fatalf("InjectRuntimeConfig failed: %v", err)
|
||||
}
|
||||
|
||||
// Workdir should only contain expected entries.
|
||||
// Workdir should only contain expected entries. `.opencode` is created
|
||||
// because the built-in multica-cli skill is installed natively under
|
||||
// `.opencode/skills/` (MUL-1821).
|
||||
entries, err := os.ReadDir(env.WorkDir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read workdir: %v", err)
|
||||
}
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if name != ".agent_context" && name != "AGENTS.md" {
|
||||
if name != ".agent_context" && name != "AGENTS.md" && name != ".opencode" {
|
||||
t.Errorf("unexpected entry in workdir: %s", name)
|
||||
}
|
||||
}
|
||||
@@ -924,9 +931,9 @@ func TestPrepareWithRepoContextOpencode(t *testing.T) {
|
||||
|
||||
// TestInjectRuntimeConfigRequiresExplicitCommentPost ensures the injected
|
||||
// workflow makes "post a comment with results" an explicit, unmissable step in
|
||||
// both the assignment- and comment-triggered branches, including a hard
|
||||
// warning that terminal/log text is not user-visible. Agents were silently
|
||||
// finishing tasks without ever posting their result to the issue; see
|
||||
// both the assignment- and comment-triggered branches, plus hard-warns in the
|
||||
// Output section that terminal/log text is not user-visible. Agents were
|
||||
// silently finishing tasks without ever posting their result to the issue; see
|
||||
// MUL-1124. Covering this in a test prevents the guidance from decaying back
|
||||
// into a nested clause again.
|
||||
func TestInjectRuntimeConfigRequiresExplicitCommentPost(t *testing.T) {
|
||||
@@ -956,22 +963,28 @@ func TestInjectRuntimeConfigRequiresExplicitCommentPost(t *testing.T) {
|
||||
s := string(data)
|
||||
|
||||
// The workflow must contain an explicit `multica issue comment add`
|
||||
// invocation for this issue, the word "mandatory" so the agent can
|
||||
// see the post-result step is non-optional, and a hard warning
|
||||
// that terminal/log text is not user-visible. After MUL-1823 the
|
||||
// terminal-not-delivered warning lives inside the workflow step
|
||||
// itself (single source of truth), not duplicated in an Output
|
||||
// section.
|
||||
// invocation for this issue — not just a prose mention of posting.
|
||||
mustContain := []string{
|
||||
"multica issue comment add issue-1",
|
||||
"mandatory",
|
||||
"NOT delivered",
|
||||
}
|
||||
for _, want := range mustContain {
|
||||
if !strings.Contains(s, want) {
|
||||
t.Errorf("%s: CLAUDE.md missing %q\n---\n%s", tc.name, want, s)
|
||||
}
|
||||
}
|
||||
|
||||
// The Output section must carry a hard warning that terminal/log
|
||||
// output is not user-visible. This is the second line of defense
|
||||
// in case the agent skips past the workflow steps.
|
||||
for _, want := range []string{
|
||||
"Final results MUST be delivered via `multica issue comment add`",
|
||||
"does NOT see your terminal output",
|
||||
} {
|
||||
if !strings.Contains(s, want) {
|
||||
t.Errorf("%s: Output warning missing %q", tc.name, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -996,9 +1009,10 @@ func TestInjectRuntimeConfigDirectsMultiLineWritesToStdin(t *testing.T) {
|
||||
s := string(data)
|
||||
|
||||
for _, want := range []string{
|
||||
"Multi-line content",
|
||||
"MUST pipe via stdin with a HEREDOC",
|
||||
"multi-line content",
|
||||
"MUST pipe via stdin",
|
||||
"--content-stdin",
|
||||
"<<'COMMENT'",
|
||||
"`--description`",
|
||||
"--description-stdin",
|
||||
} {
|
||||
@@ -2038,13 +2052,11 @@ func TestInjectRuntimeConfigMentionLoopHardening(t *testing.T) {
|
||||
t.Run("mentions-section-lists-loop-protocol", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := readClaudeMD(t, assignmentCtx)
|
||||
// After MUL-1823 the Mentions section is condensed: the When-NOT /
|
||||
// When-IS section headers are gone, but every anti-loop *signal* must
|
||||
// still survive in prose.
|
||||
for _, want := range []string{
|
||||
"side-effecting actions",
|
||||
"enqueues a new run for that agent",
|
||||
"Default to NO `@mention`",
|
||||
"When NOT to use a mention link",
|
||||
"When a mention IS appropriate",
|
||||
"end with no mention at all",
|
||||
"Silence ends conversations",
|
||||
} {
|
||||
@@ -2064,18 +2076,16 @@ func TestInjectRuntimeConfigMentionLoopHardening(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("workflow-points-to-per-turn-anti-loop-guidance", func(t *testing.T) {
|
||||
t.Run("workflow-carries-silence-as-exit-and-no-signoff-mention", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := readClaudeMD(t, commentTriggerCtx)
|
||||
// After MUL-1823 the canonical anti-loop guidance lives in the
|
||||
// per-turn user message (daemon.buildCommentPrompt agent block).
|
||||
// CLAUDE.md only carries a one-line pointer to it so the rule has a
|
||||
// single source of truth. Lock in the pointer so it cannot silently
|
||||
// decay; the per-turn message itself is covered by daemon prompt
|
||||
// tests.
|
||||
// The anti-loop signal for CLAUDE.md lives in the numbered workflow
|
||||
// steps (4 + 5), not in a dedicated preamble. Lock in the key phrases
|
||||
// so the signal can't decay back into pure prose again.
|
||||
for _, want := range []string{
|
||||
"per-turn user message",
|
||||
"when to stay silent",
|
||||
"Decide whether a reply is warranted",
|
||||
"Silence is a valid and preferred way",
|
||||
"Never @mention the agent you are replying to as a thank-you or sign-off",
|
||||
} {
|
||||
if !strings.Contains(s, want) {
|
||||
t.Errorf("comment-triggered CLAUDE.md missing %q", want)
|
||||
|
||||
87
server/internal/daemon/execenv/multica_cli_skill.go
Normal file
87
server/internal/daemon/execenv/multica_cli_skill.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package execenv
|
||||
|
||||
// multicaCLISkillName is the public-facing name of the built-in CLI reference
|
||||
// skill. Both the on-disk directory (sanitized) and the rendered Skills section
|
||||
// in CLAUDE.md / AGENTS.md / GEMINI.md derive from this constant.
|
||||
const multicaCLISkillName = "Multica CLI Reference"
|
||||
|
||||
// builtinMulticaCLISkill returns the SKILL.md bundle for the multica CLI
|
||||
// reference. The bundle is installed into every task's skills directory so the
|
||||
// agent can lazy-load the full flag-level manual on demand instead of paying
|
||||
// the ~1.5k-token cost of inlining it into the runtime config (see MUL-1821).
|
||||
//
|
||||
// The frontmatter `name` matches the sanitized directory; the `description`
|
||||
// is what providers like Claude Code use to decide when to autoload the skill.
|
||||
func builtinMulticaCLISkill() SkillContextForEnv {
|
||||
return SkillContextForEnv{
|
||||
Name: multicaCLISkillName,
|
||||
Content: multicaCLISkillContent,
|
||||
}
|
||||
}
|
||||
|
||||
const multicaCLISkillContent = `---
|
||||
name: multica-cli
|
||||
description: Full reference for the Multica CLI. Load this when you need a flag, subcommand, or behavior detail that is not in the CLAUDE.md / AGENTS.md quick reference — for example autopilot management, label/subscriber writes, run-message inspection, project-resource queries, or any flag combination beyond the common path.
|
||||
---
|
||||
|
||||
# Multica CLI Reference
|
||||
|
||||
This skill is the authoritative reference for the ` + "`multica`" + ` CLI. The runtime config (CLAUDE.md / AGENTS.md / GEMINI.md) only lists the high-frequency subset; everything else is here. Use this skill instead of guessing flags, and prefer ` + "`multica <command> --help`" + ` when you need to verify a flag at runtime.
|
||||
|
||||
**Always use ` + "`--output json`" + ` for all read commands** to get structured data with full IDs.
|
||||
|
||||
## Read
|
||||
|
||||
- ` + "`multica issue get <id> --output json`" + ` — Get full issue details (title, description, status, priority, assignee).
|
||||
- ` + "`multica issue list [--status X] [--priority X] [--assignee X | --assignee-id <uuid>] [--limit N] [--offset N] --output json`" + ` — List issues in workspace. Default limit: 50; JSON output includes ` + "`total`" + ` and ` + "`has_more`" + ` — use ` + "`--offset`" + ` to paginate while ` + "`has_more`" + ` is true. Prefer ` + "`--assignee-id <uuid>`" + ` when scripting from ` + "`multica workspace members --output json`" + ` / ` + "`multica agent list --output json`" + `.
|
||||
- ` + "`multica issue comment list <issue-id> [--limit N] [--offset N] [--since <RFC3339>] --output json`" + ` — List comments on an issue. Supports pagination; includes ` + "`id`" + ` and ` + "`parent_id`" + ` for threading.
|
||||
- ` + "`multica issue label list <issue-id> --output json`" + ` — List labels currently attached to an issue.
|
||||
- ` + "`multica issue subscriber list <issue-id> --output json`" + ` — List members/agents subscribed to an issue.
|
||||
- ` + "`multica label list --output json`" + ` — List all labels defined in the workspace (returns id + name + color).
|
||||
- ` + "`multica workspace get --output json`" + ` — Get workspace details and context.
|
||||
- ` + "`multica workspace members [workspace-id] --output json`" + ` — List workspace members (user IDs, names, roles).
|
||||
- ` + "`multica agent list --output json`" + ` — List agents in workspace.
|
||||
- ` + "`multica repo checkout <url> [--ref <branch-or-sha>]`" + ` — Check out a repository into the working directory. Creates a git worktree with a dedicated branch; use ` + "`--ref`" + ` for review/QA on a specific branch, tag, or commit.
|
||||
- ` + "`multica issue runs <issue-id> --output json`" + ` — List all execution runs for an issue (status, timestamps, errors).
|
||||
- ` + "`multica issue run-messages <task-id> [--since <seq>] --output json`" + ` — List messages for a specific execution run. Supports incremental fetch with ` + "`--since`" + `.
|
||||
- ` + "`multica attachment download <id> [-o <dir>]`" + ` — Download an attachment file locally by ID. Prints the local path; use ` + "`-o`" + ` to save elsewhere.
|
||||
- ` + "`multica autopilot list [--status X] --output json`" + ` — List autopilots (scheduled / triggered agent automations) in the workspace.
|
||||
- ` + "`multica autopilot get <id> --output json`" + ` — Get autopilot details including triggers.
|
||||
- ` + "`multica autopilot runs <id> [--limit N] --output json`" + ` — List execution history for an autopilot.
|
||||
- ` + "`multica project get <id> --output json`" + ` — Get project details. Includes ` + "`resource_count`" + `; the resources themselves live at the sub-collection below.
|
||||
- ` + "`multica project resource list <project-id> --output json`" + ` — List resources (e.g. ` + "`github_repo`" + `) attached to a project. Use this when ` + "`resource_count > 0`" + ` and you need the actual refs.
|
||||
|
||||
## Write
|
||||
|
||||
- ` + "`multica issue create --title \"...\" [--description \"...\" | --description-stdin] [--priority X] [--status X] [--assignee X | --assignee-id <uuid>] [--parent <issue-id>] [--project <project-id>] [--due-date <RFC3339>] [--attachment <path>]`" + ` — Create a new issue. ` + "`--attachment`" + ` may be repeated to upload multiple files. Labels and subscribers are not accepted here; attach them after create with the commands below.
|
||||
- ` + "`multica issue update <id> [--title X] [--description X | --description-stdin] [--priority X] [--status X] [--assignee X | --assignee-id <uuid>] [--parent <issue-id>] [--project <project-id>] [--due-date <RFC3339>]`" + ` — Update one or more issue fields in a single call. Use ` + "`--parent \"\"`" + ` to clear the parent.
|
||||
- ` + "`multica issue status <id> <status>`" + ` — Shortcut for ` + "`issue update --status`" + ` when you only need to flip status (` + "`todo`" + `, ` + "`in_progress`" + `, ` + "`in_review`" + `, ` + "`done`" + `, ` + "`blocked`" + `, ` + "`backlog`" + `, ` + "`cancelled`" + `).
|
||||
- ` + "`multica issue assign <id> --to <name>|--to-id <uuid>`" + ` — Assign an issue to a member or agent. ` + "`--to <name>`" + ` does fuzzy name matching; pass ` + "`--to-id <uuid>`" + ` (mutually exclusive with ` + "`--to`" + `) to assign by canonical UUID, e.g. when names overlap. Use ` + "`--unassign`" + ` to clear the assignee.
|
||||
- ` + "`multica issue label add <issue-id> <label-id>`" + ` — Attach a label to an issue (look up the label id via ` + "`multica label list`" + `).
|
||||
- ` + "`multica issue label remove <issue-id> <label-id>`" + ` — Detach a label from an issue.
|
||||
- ` + "`multica issue subscriber add <issue-id> [--user <name>|--user-id <uuid>]`" + ` — Subscribe a member or agent to issue updates (defaults to the caller when neither flag is set; the two flags are mutually exclusive).
|
||||
- ` + "`multica issue subscriber remove <issue-id> [--user <name>|--user-id <uuid>]`" + ` — Unsubscribe a member or agent.
|
||||
- ` + "`multica issue comment add <issue-id> --content-stdin [--parent <comment-id>] [--attachment <path>]`" + ` — Post a comment. Agent-authored comments should always pipe content via stdin, even for short single-line replies. Use ` + "`--parent`" + ` to reply to a specific comment; ` + "`--attachment`" + ` may be repeated.
|
||||
- ` + "`multica issue comment delete <comment-id>`" + ` — Delete a comment.
|
||||
- ` + "`multica label create --name \"...\" --color \"#hex\"`" + ` — Define a new workspace label. Use this only when the label you need does not exist yet; reuse existing labels via ` + "`multica label list`" + ` first.
|
||||
- ` + "`multica autopilot create --title \"...\" --agent <name> --mode create_issue [--description \"...\"]`" + ` — Create an autopilot.
|
||||
- ` + "`multica autopilot update <id> [--title X] [--description X] [--status active|paused]`" + ` — Update an autopilot.
|
||||
- ` + "`multica autopilot trigger <id>`" + ` — Manually trigger an autopilot to run once.
|
||||
- ` + "`multica autopilot delete <id>`" + ` — Delete an autopilot.
|
||||
|
||||
## Multi-line content rule (MUL-1467)
|
||||
|
||||
For ` + "`multica issue comment add`" + ` and the ` + "`--description`" + ` flag on ` + "`multica issue create`" + ` / ` + "`multica issue update`" + `, you MUST pipe via stdin (` + "`--content-stdin`" + ` / ` + "`--description-stdin`" + `) for any content that contains line breaks, paragraphs, code blocks, backticks, or quotes. Inline ` + "`--content \"...\\n\\n...\"`" + ` does not work because bash does not expand backslash escapes inside double quotes — agents using that form ended up with literal four-character ` + "`\\n`" + ` sequences in stored comments.
|
||||
|
||||
Use a HEREDOC instead:
|
||||
|
||||
` + "```" + `
|
||||
cat <<'COMMENT' | multica issue comment add <issue-id> --content-stdin
|
||||
First paragraph.
|
||||
|
||||
Second paragraph with ` + "`code`" + ` and "quotes".
|
||||
COMMENT
|
||||
` + "```" + `
|
||||
|
||||
The same shape works for ` + "`--description-stdin`" + ` on ` + "`issue create`" + ` / ` + "`issue update`" + `.
|
||||
`
|
||||
@@ -102,44 +102,32 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
}
|
||||
|
||||
b.WriteString("## Available Commands\n\n")
|
||||
b.WriteString("**Always use `--output json` for all read commands** to get structured data with full IDs.\n\n")
|
||||
b.WriteString("**Always use `--output json` for read commands.** This is a quick reference for the high-frequency commands. Full flag-level documentation for every `multica` subcommand (autopilots, labels, subscribers, project resources, run-messages, etc.) lives in the `multica-cli` skill — load that skill, or run `multica <command> --help`, when you need a flag or command not listed below.\n\n")
|
||||
b.WriteString("### Read\n")
|
||||
b.WriteString("- `multica issue get <id> --output json` — Get full issue details (title, description, status, priority, assignee)\n")
|
||||
b.WriteString("- `multica issue list [--status X] [--priority X] [--assignee X | --assignee-id <uuid>] [--limit N] [--offset N] --output json` — List issues in workspace (default limit: 50; JSON output includes `total`, `has_more` — use offset to paginate when `has_more` is true). Prefer `--assignee-id <uuid>` when scripting from `multica workspace members --output json` / `multica agent list --output json`.\n")
|
||||
b.WriteString("- `multica issue comment list <issue-id> [--limit N] [--offset N] [--since <RFC3339>] --output json` — List comments on an issue (supports pagination; includes id, parent_id for threading)\n")
|
||||
b.WriteString("- `multica issue label list <issue-id> --output json` — List labels currently attached to an issue\n")
|
||||
b.WriteString("- `multica issue subscriber list <issue-id> --output json` — List members/agents subscribed to an issue\n")
|
||||
b.WriteString("- `multica label list --output json` — List all labels defined in the workspace (returns id + name + color)\n")
|
||||
b.WriteString("- `multica workspace get --output json` — Get workspace details and context\n")
|
||||
b.WriteString("- `multica workspace members [workspace-id] --output json` — List workspace members (user IDs, names, roles)\n")
|
||||
b.WriteString("- `multica agent list --output json` — List agents in workspace\n")
|
||||
b.WriteString("- `multica repo checkout <url> [--ref <branch-or-sha>]` — Check out a repository into the working directory (creates a git worktree with a dedicated branch; use `--ref` for review/QA on a specific branch, tag, or commit)\n")
|
||||
b.WriteString("- `multica issue runs <issue-id> --output json` — List all execution runs for an issue (status, timestamps, errors)\n")
|
||||
b.WriteString("- `multica issue run-messages <task-id> [--since <seq>] --output json` — List messages for a specific execution run (supports incremental fetch)\n")
|
||||
b.WriteString("- `multica attachment download <id> [-o <dir>]` — Download an attachment file locally by ID\n")
|
||||
b.WriteString("- `multica autopilot list [--status X] --output json` — List autopilots (scheduled/triggered agent automations) in the workspace\n")
|
||||
b.WriteString("- `multica autopilot get <id> --output json` — Get autopilot details including triggers\n")
|
||||
b.WriteString("- `multica autopilot runs <id> [--limit N] --output json` — List execution history for an autopilot\n")
|
||||
b.WriteString("- `multica project get <id> --output json` — Get project details. Includes `resource_count`; the resources themselves live at the sub-collection below.\n")
|
||||
b.WriteString("- `multica project resource list <project-id> --output json` — List resources (e.g. github_repo) attached to a project. Use this when `resource_count > 0` and you need the actual refs.\n\n")
|
||||
b.WriteString("- `multica issue get <id> --output json` — Issue details (title, description, status, priority, assignee)\n")
|
||||
b.WriteString("- `multica issue list [--status X] [--assignee X | --assignee-id <uuid>] [--limit N] --output json` — List issues (default limit 50; paginate with `--offset` when `has_more`)\n")
|
||||
b.WriteString("- `multica issue comment list <issue-id> [--limit N] [--since <RFC3339>] --output json` — Issue comments (paginate large threads)\n")
|
||||
b.WriteString("- `multica workspace get --output json` / `multica workspace members --output json` / `multica agent list --output json` — Workspace context\n")
|
||||
b.WriteString("- `multica label list --output json` — All workspace labels (id + name + color)\n")
|
||||
b.WriteString("- `multica repo checkout <url> [--ref <branch-or-sha>]` — Check out a repo as a git worktree with a dedicated branch\n")
|
||||
b.WriteString("- `multica attachment download <id> [-o <dir>]` — Download an attachment locally\n\n")
|
||||
|
||||
b.WriteString("### Write\n")
|
||||
b.WriteString("- `multica issue create --title \"...\" [--description \"...\"] [--priority X] [--status X] [--assignee X | --assignee-id <uuid>] [--parent <issue-id>] [--project <project-id>] [--due-date <RFC3339>] [--attachment <path>]` — Create a new issue. `--attachment` may be repeated to upload multiple files; labels and subscribers are not accepted here, attach them after create with the commands below.\n")
|
||||
b.WriteString("- `multica issue update <id> [--title X] [--description X] [--priority X] [--status X] [--assignee X | --assignee-id <uuid>] [--parent <issue-id>] [--project <project-id>] [--due-date <RFC3339>]` — Update one or more issue fields in a single call. Use `--parent \"\"` to clear the parent.\n")
|
||||
b.WriteString("- `multica issue status <id> <status>` — Shortcut for `issue update --status` when you only need to flip status (todo, in_progress, in_review, done, blocked, backlog, cancelled)\n")
|
||||
b.WriteString("- `multica issue assign <id> --to <name>|--to-id <uuid>` — Assign an issue to a member or agent. `--to <name>` does fuzzy name matching; pass `--to-id <uuid>` (mutually exclusive with `--to`) to assign by canonical UUID, e.g. when names overlap. Use `--unassign` to clear the assignee.\n")
|
||||
b.WriteString("- `multica issue label add <issue-id> <label-id>` — Attach a label to an issue (look up the label id via `multica label list`)\n")
|
||||
b.WriteString("- `multica issue label remove <issue-id> <label-id>` — Detach a label from an issue\n")
|
||||
b.WriteString("- `multica issue subscriber add <issue-id> [--user <name>|--user-id <uuid>]` — Subscribe a member or agent to issue updates (defaults to the caller when neither flag is set; the two flags are mutually exclusive)\n")
|
||||
b.WriteString("- `multica issue subscriber remove <issue-id> [--user <name>|--user-id <uuid>]` — Unsubscribe a member or agent\n")
|
||||
b.WriteString("- `multica issue comment add <issue-id> --content-stdin [--parent <comment-id>] [--attachment <path>]` — Post a comment. Multi-line content (paragraphs, code blocks, backticks, quotes) MUST pipe via stdin with a HEREDOC (`cat <<'EOF' | ... --content-stdin`); inline `--content` and literal `\\n` escapes corrupt formatting. Use `--parent` to reply to a specific comment; `--attachment` may be repeated.\n")
|
||||
b.WriteString(" - The same rule applies to `--description` on `multica issue create` and `multica issue update` — use `--description-stdin` for multi-line text.\n")
|
||||
b.WriteString("- `multica issue comment delete <comment-id>` — Delete a comment\n")
|
||||
b.WriteString("- `multica label create --name \"...\" --color \"#hex\"` — Define a new workspace label (use this only when the label you need does not exist yet; reuse existing labels via `multica label list` first)\n")
|
||||
b.WriteString("- `multica autopilot create --title \"...\" --agent <name> --mode create_issue [--description \"...\"]` — Create an autopilot\n")
|
||||
b.WriteString("- `multica autopilot update <id> [--title X] [--description X] [--status active|paused]` — Update an autopilot\n")
|
||||
b.WriteString("- `multica autopilot trigger <id>` — Manually trigger an autopilot to run once\n")
|
||||
b.WriteString("- `multica autopilot delete <id>` — Delete an autopilot\n\n")
|
||||
b.WriteString("- `multica issue status <id> <status>` — Flip status (todo, in_progress, in_review, done, blocked, backlog, cancelled)\n")
|
||||
b.WriteString("- `multica issue assign <id> --to <name>|--to-id <uuid>` — Assign to member/agent (`--unassign` clears)\n")
|
||||
b.WriteString("- `multica issue create --title \"...\" [--description-stdin] [--priority X] [--status X] [--assignee X | --assignee-id <uuid>] [--parent <id>] [--project <id>] [--attachment <path>]` — Create an issue (`--attachment` repeatable)\n")
|
||||
b.WriteString("- `multica issue update <id> [--title X] [--description-stdin] [--priority X] [--status X] [--assignee X] [--parent <id>] [--project <id>]` — Update issue fields (use `--parent \"\"` to clear)\n")
|
||||
b.WriteString("- `multica issue label add|remove <issue-id> <label-id>` — Attach/detach a label (look up id via `multica label list`)\n")
|
||||
b.WriteString("- `multica issue comment add <issue-id> --content-stdin [--parent <comment-id>] [--attachment <path>]` — Post a comment (always pipe via stdin)\n\n")
|
||||
|
||||
b.WriteString("**multi-line content (CRITICAL — see MUL-1467):** `multica issue comment add` and `--description` on `multica issue create` / `multica issue update` MUST pipe via stdin (`--content-stdin` / `--description-stdin`) with a HEREDOC for any content that has line breaks, paragraphs, code blocks, backticks, or quotes. Inline `--content \"...\"` and literal `\\n` escapes will store the 4-char sequence `\\n` instead of a real newline. This rule applies to agent-authored comments even when the reply is a single line.\n\n")
|
||||
b.WriteString("```\n")
|
||||
b.WriteString("cat <<'COMMENT' | multica issue comment add <issue-id> --content-stdin\n")
|
||||
b.WriteString("First paragraph.\n")
|
||||
b.WriteString("\n")
|
||||
b.WriteString("Second paragraph with `code` and \"quotes\".\n")
|
||||
b.WriteString("COMMENT\n")
|
||||
b.WriteString("```\n\n")
|
||||
|
||||
if provider == "codex" {
|
||||
b.WriteString("## Codex-Specific Comment Formatting\n\n")
|
||||
@@ -236,11 +224,14 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
// Comment-triggered: focus on reading and replying
|
||||
b.WriteString("**This task was triggered by a NEW comment.** Your primary job is to respond to THIS specific comment, even if you have handled similar requests before in this session.\n\n")
|
||||
fmt.Fprintf(&b, "1. Run `multica issue get %s --output json` to understand the issue context\n", ctx.IssueID)
|
||||
fmt.Fprintf(&b, "2. Run `multica issue comment list %s --output json` to read the conversation (use `--limit 30` or `--since <timestamp>` if truncated)\n", ctx.IssueID)
|
||||
fmt.Fprintf(&b, "3. Find the triggering comment (ID: `%s`) — see the per-turn user message for full anti-loop guidance (when to stay silent, when to skip the @mention)\n", ctx.TriggerCommentID)
|
||||
b.WriteString("4. If a reply is warranted, posting it as a comment is mandatory — text in your terminal or run logs is NOT delivered. ")
|
||||
fmt.Fprintf(&b, "2. Run `multica issue comment list %s --output json` to read the conversation\n", ctx.IssueID)
|
||||
b.WriteString(" - If the output is very large or truncated, use pagination: `--limit 30` to get the latest 30 comments, or `--since <timestamp>` to fetch only recent ones\n")
|
||||
fmt.Fprintf(&b, "3. Find the triggering comment (ID: `%s`) and understand what is being asked — do NOT confuse it with previous comments\n", ctx.TriggerCommentID)
|
||||
b.WriteString("4. **Decide whether a reply is warranted.** If you produced actual work this turn (investigated, fixed, answered a real question), post the result via step 6 — that is a normal reply, not a noise comment. If the triggering comment was a pure acknowledgment / thanks / sign-off from another agent AND you produced no work this turn, do NOT post a reply — and do NOT post a comment saying 'No reply needed' or similar. Simply exit with no output. Silence is a valid and preferred way to end agent-to-agent conversations.\n")
|
||||
b.WriteString("5. If a reply IS warranted: do any requested work first, then **decide whether to include any `@mention` link.** The default is NO mention. Only mention when you are escalating to a human owner who is not yet involved, delegating a concrete new sub-task to another agent for the first time, or the user explicitly asked you to loop someone in. Never @mention the agent you are replying to as a thank-you or sign-off.\n")
|
||||
b.WriteString("6. **If you reply, post it as a comment — this step is mandatory when you reply.** Text in your terminal or run logs is NOT delivered to the user. ")
|
||||
b.WriteString(BuildCommentReplyInstructions(ctx.IssueID, ctx.TriggerCommentID))
|
||||
b.WriteString("5. Do NOT change the issue status unless the comment explicitly asks for it\n\n")
|
||||
b.WriteString("7. Do NOT change the issue status unless the comment explicitly asks for it\n\n")
|
||||
} else {
|
||||
// Assignment-triggered: defer to agent Skills for workflow specifics.
|
||||
b.WriteString("You are responsible for managing the issue status throughout your work.\n\n")
|
||||
@@ -254,35 +245,45 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
fmt.Fprintf(&b, "7. If blocked, run `multica issue status %s blocked` and post a comment explaining why\n\n", ctx.IssueID)
|
||||
}
|
||||
|
||||
if len(ctx.AgentSkills) > 0 {
|
||||
b.WriteString("## Skills\n\n")
|
||||
switch provider {
|
||||
case "claude":
|
||||
// Claude discovers skills natively from .claude/skills/ — just list names.
|
||||
b.WriteString("You have the following skills installed (discovered automatically):\n\n")
|
||||
case "codex", "copilot", "opencode", "openclaw", "pi", "cursor", "kimi", "kiro":
|
||||
// Codex, Copilot, OpenCode, OpenClaw, Pi, Cursor, Kimi, and Kiro discover skills natively from their respective paths — just list names.
|
||||
b.WriteString("You have the following skills installed (discovered automatically):\n\n")
|
||||
case "gemini", "hermes":
|
||||
// Gemini reads GEMINI.md directly; Hermes has no native skills discovery path
|
||||
// wired up in resolveSkillsDir, so both fall back to .agent_context/skills/.
|
||||
b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n")
|
||||
default:
|
||||
b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n")
|
||||
}
|
||||
for _, skill := range ctx.AgentSkills {
|
||||
fmt.Fprintf(&b, "- **%s**\n", skill.Name)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
// The built-in multica-cli skill is always installed alongside any
|
||||
// user-defined agent skills (see MUL-1821), so the Skills section is
|
||||
// always rendered.
|
||||
b.WriteString("## Skills\n\n")
|
||||
switch provider {
|
||||
case "claude":
|
||||
// Claude discovers skills natively from .claude/skills/ — just list names.
|
||||
b.WriteString("You have the following skills installed (discovered automatically):\n\n")
|
||||
case "codex", "copilot", "opencode", "openclaw", "pi", "cursor", "kimi", "kiro":
|
||||
// Codex, Copilot, OpenCode, OpenClaw, Pi, Cursor, Kimi, and Kiro discover skills natively from their respective paths — just list names.
|
||||
b.WriteString("You have the following skills installed (discovered automatically):\n\n")
|
||||
case "gemini", "hermes":
|
||||
// Gemini reads GEMINI.md directly; Hermes has no native skills discovery path
|
||||
// wired up in resolveSkillsDir, so both fall back to .agent_context/skills/.
|
||||
b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n")
|
||||
default:
|
||||
b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n")
|
||||
}
|
||||
fmt.Fprintf(&b, "- **%s** — full reference for the `multica` CLI; load when you need a flag or subcommand not in the quick reference above.\n", multicaCLISkillName)
|
||||
for _, skill := range ctx.AgentSkills {
|
||||
fmt.Fprintf(&b, "- **%s**\n", skill.Name)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
b.WriteString("## Mentions\n\n")
|
||||
b.WriteString("Mention links are **side-effecting actions**, not formatting:\n\n")
|
||||
b.WriteString("- `[MUL-123](mention://issue/<issue-id>)` — issue link (safe, no side effect)\n")
|
||||
b.WriteString("- `[@Name](mention://member/<user-id>)` — **notifies a human**\n")
|
||||
b.WriteString("Mention links are **side-effecting actions**, not just formatting:\n\n")
|
||||
b.WriteString("- `[MUL-123](mention://issue/<issue-id>)` — clickable link to an issue (safe, no side effect)\n")
|
||||
b.WriteString("- `[@Name](mention://member/<user-id>)` — **sends a notification to a human**\n")
|
||||
b.WriteString("- `[@Name](mention://agent/<agent-id>)` — **enqueues a new run for that agent**\n\n")
|
||||
b.WriteString("Default to NO `@mention`. Only mention to escalate to a new human owner, delegate a fresh sub-task to another agent, or when the user asked you to loop someone in. Never @mention as a thanks/acknowledgment/sign-off — that restarts loops; **end with no mention at all**. **Silence ends conversations; `@` restarts them.**\n\n")
|
||||
b.WriteString("Look up IDs via `multica workspace members --output json` and `multica issue list --output json`.\n\n")
|
||||
b.WriteString("### When NOT to use a mention link\n\n")
|
||||
b.WriteString("- Referring to someone in prose (e.g. \"GPT-Boy is right\") — write the plain name, no link.\n")
|
||||
b.WriteString("- **Replying to another agent that just spoke to you.** By default, do NOT put a `mention://agent/...` link anywhere in your reply. The platform already shows your comment to everyone on the issue; re-mentioning the other agent will make them run again, and if they reply with a mention back, you will be triggered again. That is a loop and it costs the user money.\n")
|
||||
b.WriteString("- Thanking, acknowledging, wrapping up, or signing off. These are exactly the moments where an accidental `@mention` causes the other agent to reply \"you're welcome\" and restart the loop. If the work is done, **end with no mention at all**.\n\n")
|
||||
b.WriteString("### When a mention IS appropriate\n\n")
|
||||
b.WriteString("- Escalating to a human owner who is not yet involved.\n")
|
||||
b.WriteString("- Delegating a concrete sub-task to another agent for the first time, with a clear request.\n")
|
||||
b.WriteString("- The user explicitly asked you to loop someone in.\n\n")
|
||||
b.WriteString("If you are unsure whether a mention is warranted, **don't mention**. Silence ends conversations; `@` restarts them.\n\n")
|
||||
b.WriteString("Use `multica issue list --output json` to look up issue IDs, and `multica workspace members --output json` for member IDs.\n\n")
|
||||
|
||||
b.WriteString("## Attachments\n\n")
|
||||
b.WriteString("Issues and comments may include file attachments (images, documents, etc.).\n")
|
||||
@@ -292,7 +293,11 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
b.WriteString("After downloading, you can read the file directly (e.g. view an image, read a document).\n\n")
|
||||
|
||||
b.WriteString("## Important: Always Use the `multica` CLI\n\n")
|
||||
b.WriteString("All Multica platform access (issues, comments, attachments, files) must go through the `multica` CLI — `curl`/`wget` cannot authenticate. If a needed operation is missing, post a comment to the workspace owner instead of working around it.\n\n")
|
||||
b.WriteString("All interactions with Multica platform resources — including issues, comments, attachments, images, files, and any other platform data — **must** go through the `multica` CLI. ")
|
||||
b.WriteString("Do NOT use `curl`, `wget`, or any other HTTP client to access Multica URLs or APIs directly. ")
|
||||
b.WriteString("Multica resource URLs require authenticated access that only the `multica` CLI can provide.\n\n")
|
||||
b.WriteString("If you need to perform an operation that is not covered by any existing `multica` command, ")
|
||||
b.WriteString("do NOT attempt to work around it. Instead, post a comment mentioning the workspace owner to request the missing functionality.\n\n")
|
||||
|
||||
b.WriteString("## Output\n\n")
|
||||
switch {
|
||||
@@ -304,10 +309,11 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
b.WriteString("- Print exactly one final line: `Created MUL-<n>: <title>` after a successful `multica issue create`.\n")
|
||||
b.WriteString("- On CLI failure, exit with the CLI error as the only output. The platform translates that into a `quick_create_failed` inbox item carrying the original prompt for the user.\n")
|
||||
default:
|
||||
b.WriteString("⚠️ **Final results MUST be delivered via `multica issue comment add`.** The user does NOT see your terminal output, assistant chat text, or run logs — only comments on the issue. A task that finishes without a result comment is invisible to the user, even if the work itself was correct.\n\n")
|
||||
b.WriteString("Keep comments concise and natural — state the outcome, not the process.\n")
|
||||
b.WriteString("Good: \"Fixed the login redirect. PR: https://...\"\n")
|
||||
b.WriteString("Bad: \"1. Read the issue 2. Found the bug in auth.go 3. Created branch 4. ...\"\n")
|
||||
b.WriteString("Reference issues with `[MUL-123](mention://issue/<issue-id>)` (issue mentions have no side effect; only member/agent mentions do).\n")
|
||||
b.WriteString("When referencing an issue in a comment, use the issue mention format `[MUL-123](mention://issue/<issue-id>)` so it renders as a clickable link. (Issue mentions have no side effect; only member/agent mentions do — see the Mentions section above.)\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
|
||||
Reference in New Issue
Block a user