package execenv import ( "strings" "testing" ) // Sub-issue Creation section — after MUL-2538 the platform posts the // child-done parent notification itself, so the brief no longer carries // any parent-notification rule (per Bohan's call on PR #3055: delete the // guidance entirely, do not replace it with a "do not post one" sentence // — the agent should not be thinking about parent comments at all). All // that remains is the `--status todo` vs `--status backlog` rule for // creating sub-issues, which is unrelated to the notification path. func TestSubIssueCreationSectionPresentForIssueRuns(t *testing.T) { t.Parallel() cases := []struct { name string ctx TaskContextForEnv }{ { name: "assignment-triggered", ctx: TaskContextForEnv{IssueID: "11111111-2222-3333-4444-555555555555"}, }, { name: "comment-triggered", ctx: TaskContextForEnv{ IssueID: "22222222-3333-4444-5555-666666666666", TriggerCommentID: "33333333-4444-5555-6666-777777777777", }, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() out := buildMetaSkillContent("claude", tc.ctx) if !strings.Contains(out, "## Sub-issue Creation") { t.Fatalf("expected Sub-issue Creation section in %s brief", tc.name) } for _, want := range []string{ "**Choosing `--status` when creating sub-issues.**", "`--status todo` = **start now**", "`--status backlog` = **wait**", "`multica issue status todo`", "all `--status todo`", "`--status backlog` from the start", } { if !strings.Contains(out, want) { t.Errorf("[%s] section missing %q", tc.name, want) } } }) } } // The brief must no longer carry any parent-notification guidance. PR // #2918 added a "Tell the parent when you finish a child" rule that // turned into noise (self-mention loops, planner ack ping-pong, // hardcoded `MUL-` prefix). PR #3055 first downgraded it to a "do NOT // post one" guardrail, but Bohan's product call was to remove the // guidance entirely rather than substitute a new prohibition. These // canaries lock that in: any wording that re-introduces the // parent-comment concept — positive, negative, or descriptive — must // not come back through future edits. func TestBriefHasNoParentNotificationGuidance(t *testing.T) { t.Parallel() cases := []TaskContextForEnv{ {IssueID: "11111111-2222-3333-4444-555555555555"}, { IssueID: "22222222-3333-4444-5555-666666666666", TriggerCommentID: "33333333-4444-5555-6666-777777777777", }, } for _, ctx := range cases { ctx := ctx out := buildMetaSkillContent("claude", ctx) // The pre-MUL-2538 phrasing instructed the agent to compose a // parent comment by hand — including a hardcoded `MUL-` prefix // and an assignee mention. The intermediate revision (PR #3055 // before Bohan's call) instead told the agent NOT to post one. // Both framings must stay out. for _, banned := range []string{ // Old "do it yourself" framing (PR #2918). "## Parent / Sub-issue Protocol", "**Tell the parent when you finish a child.**", "multica issue comment add ", "with NO `--parent`", "link the child as `[MUL-", "`@mention` the parent's assignee", "`mention://agent/`", "`mention://member/`", "`mention://squad/`", // Intermediate "do NOT do it yourself" framing (PR #3055 // before Bohan's call) — also out per product direction. "**Do NOT post your own parent-notification comment.**", "Do NOT post your own parent-notification comment", "parent-notification comment", "system comment on the parent fires from the status transition", "re-trigger the parent's assignee for nothing", "platform posts a top-level system comment on the parent", // Earlier revisions split rules by trigger type or used // table/subsection layouts. None of those structures should // come back either. "| Parent assignee | Parent status |", "The same agent as yourself", "| Member or squad |", "### A. Notify the parent", "### B. Choose", "When this issue has `parent_issue_id`:", "**Closing out child work** (only if this issue has `parent_issue_id`)", "**Notify the parent** (only if this issue has `parent_issue_id`", "**Creating sub-issues** (applies to any issue-bound run)", "For parent/child work, use these best-effort rules", // The protocol must no longer emit a placeholder // `` status flip — the workflow above owns // that command with the real issue id substituted. "`multica issue status in_review`", // Non-existent CLI form Elon's earlier review flagged. "issue list --parent", } { if strings.Contains(out, banned) { t.Errorf("expected %q to be removed from the brief", banned) } } } } // Comment-triggered briefs must NOT carry any unconditional status-flip // command targeting the current issue. Previous revisions had a // dedicated protocol step that wrote `multica issue status in_review`; // the comment-triggered workflow rule "Do NOT change the issue status // unless the comment explicitly asks for it" must remain the source of // truth (Elon's blocking review on PR #2918). func TestCommentTriggeredProtocolDoesNotForceInReview(t *testing.T) { t.Parallel() ctx := TaskContextForEnv{ IssueID: "55555555-6666-7777-8888-999999999999", TriggerCommentID: "66666666-7777-8888-9999-aaaaaaaaaaaa", } out := buildMetaSkillContent("claude", ctx) if strings.Contains(out, "`multica issue status in_review`") { t.Errorf("comment-triggered brief must not contain a placeholder ` in_review` flip — that conflicts with the comment-triggered \"do not change status unless asked\" rule") } const guardrail = "Do NOT change the issue status unless the comment explicitly asks for it" if !strings.Contains(out, guardrail) { t.Errorf("expected the comment-triggered workflow guardrail %q to be present", guardrail) } } // Assignment-triggered briefs are the inverse boundary: when the agent // owns the issue lifecycle, the brief AS A WHOLE must still tell it to // flip to in_review on completion. The flip lives in the // assignment-triggered workflow above (with the real id substituted). func TestAssignmentTriggeredProtocolStillFlipsInReview(t *testing.T) { t.Parallel() const issueID = "77777777-8888-9999-aaaa-bbbbbbbbbbbb" ctx := TaskContextForEnv{IssueID: issueID} out := buildMetaSkillContent("claude", ctx) want := "`multica issue status " + issueID + " in_review`" if !strings.Contains(out, want) { t.Errorf("assignment-triggered brief must still flip to in_review on completion (expected %q in the workflow above)", want) } } // The sub-issue creation rule must reach top-level parents that have no // `parent_issue_id` of their own — that is where the `todo` vs `backlog` // decision matters most. The section must not gate on this issue being // a child, and must not even mention `parent_issue_id`. func TestSubIssueCreationSectionIsUnconditional(t *testing.T) { t.Parallel() ctx := TaskContextForEnv{ IssueID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", } out := buildMetaSkillContent("claude", ctx) const header = "## Sub-issue Creation" start := strings.Index(out, header) if start == -1 { t.Fatalf("sub-issue creation section missing") } rest := out[start:] end := strings.Index(rest[len(header):], "\n## ") var section string if end == -1 { section = rest } else { section = rest[:len(header)+end] } if strings.Contains(section, "parent_issue_id") { t.Errorf("Sub-issue Creation section must not reference `parent_issue_id` — it applies to any issue-bound run, including top-level parents:\n%s", section) } } // Workspace Context block: workspace.context (the per-workspace system prompt // owners set in Settings → General) must reach the brief as `## Workspace // Context` for every task kind so agents see a consistent shared system prompt // regardless of how they were triggered. Empty content must skip the heading // entirely — bare headings would just add noise. func TestWorkspaceContextRenderedAcrossTaskKinds(t *testing.T) { t.Parallel() const wsContext = "All comments must be in English. Prefer concise PR descriptions." cases := []struct { name string ctx TaskContextForEnv }{ { name: "assignment-triggered", ctx: TaskContextForEnv{ IssueID: "11111111-2222-3333-4444-555555555555", WorkspaceContext: wsContext, }, }, { name: "comment-triggered", ctx: TaskContextForEnv{ IssueID: "22222222-3333-4444-5555-666666666666", TriggerCommentID: "33333333-4444-5555-6666-777777777777", WorkspaceContext: wsContext, }, }, { name: "chat", ctx: TaskContextForEnv{ ChatSessionID: "chat-1", WorkspaceContext: wsContext, }, }, { name: "quick-create", ctx: TaskContextForEnv{ QuickCreatePrompt: "create me an issue", WorkspaceContext: wsContext, }, }, { name: "autopilot run-only", ctx: TaskContextForEnv{ AutopilotRunID: "run-1", WorkspaceContext: wsContext, }, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() out := buildMetaSkillContent("claude", tc.ctx) if !strings.Contains(out, "## Workspace Context") { t.Fatalf("[%s] expected `## Workspace Context` heading", tc.name) } if !strings.Contains(out, wsContext) { t.Errorf("[%s] brief missing workspace context body %q", tc.name, wsContext) } // The block must precede Available Commands so it acts as // background framing, not a footer hidden below CLI usage. ctxIdx := strings.Index(out, "## Workspace Context") cmdsIdx := strings.Index(out, "## Available Commands") if ctxIdx == -1 || cmdsIdx == -1 || ctxIdx > cmdsIdx { t.Errorf("[%s] `## Workspace Context` must appear above `## Available Commands` (ctx=%d, cmds=%d)", tc.name, ctxIdx, cmdsIdx) } }) } } func TestWorkspaceContextHeadingSkippedWhenEmpty(t *testing.T) { t.Parallel() cases := []struct { name string ctx TaskContextForEnv }{ { name: "empty string", ctx: TaskContextForEnv{ IssueID: "11111111-2222-3333-4444-555555555555", WorkspaceContext: "", }, }, { name: "whitespace only", ctx: TaskContextForEnv{ IssueID: "11111111-2222-3333-4444-555555555555", WorkspaceContext: " \n\t \r\n", }, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() out := buildMetaSkillContent("claude", tc.ctx) if strings.Contains(out, "## Workspace Context") { t.Errorf("[%s] empty workspace context must NOT emit the heading", tc.name) } }) } } func TestSubIssueCreationSectionSkippedForNonIssueModes(t *testing.T) { t.Parallel() cases := []struct { name string ctx TaskContextForEnv }{ { name: "chat", ctx: TaskContextForEnv{ChatSessionID: "chat-1"}, }, { name: "quick-create", ctx: TaskContextForEnv{QuickCreatePrompt: "create me an issue"}, }, { name: "autopilot run-only", ctx: TaskContextForEnv{AutopilotRunID: "run-1"}, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() out := buildMetaSkillContent("claude", tc.ctx) if strings.Contains(out, "## Sub-issue Creation") { t.Errorf("%s mode must NOT emit the Sub-issue Creation section", tc.name) } }) } }