makdoong2-team 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/agents/makdoong2-issue-reporter.md +43 -0
- package/agents/makdoong2-team-leader.md +1 -0
- package/bin/cli.js +1 -1
- package/command/makdoong2-issue-reporter.md +8 -0
- package/dist/issue-reporter-guard.d.ts +9 -0
- package/dist/issue-reporter-guard.js +55 -0
- package/dist/opencode-plugin.js +17 -0
- package/package.json +2 -1
- package/scripts/install-lib.mjs +60 -8
- package/scripts/run-tests.mjs +1 -0
- package/skills/makdoong2-issue-reporter/SKILL.md +433 -0
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ makdoong2-team doctor # 설치 진단
|
|
|
44
44
|
|
|
45
45
|
## 2. 에이전트와 단계
|
|
46
46
|
|
|
47
|
-
에이전트 **7
|
|
47
|
+
워크플로우 에이전트 **7개** + 사용자 전용 유틸리티 에이전트 1개. 권한이 좁을수록 사고 반경이 좁다는 원칙으로 나눴다.
|
|
48
48
|
|
|
49
49
|
| 에이전트 | 담당 | 권한 |
|
|
50
50
|
|---|---|---|
|
|
@@ -55,6 +55,7 @@ makdoong2-team doctor # 설치 진단
|
|
|
55
55
|
| `makdoong2-publisher` | 3_delivery 3단계 | **git add/commit/push 직접 실행** + bitbucket MCP |
|
|
56
56
|
| `makdoong2-verifier` | 메타 검증 | 읽기 전용 |
|
|
57
57
|
| `makdoong2-researcher` | 리서치 fan-out 워커 | 읽기 전용 + 리서치 MCP — 소스 1개 전담 |
|
|
58
|
+
| `makdoong2-issue-reporter` | 플러그인 오류 GitHub 이슈 등록 | **전권 (bash/write allow)** — 사용자 직접 호출 전용 |
|
|
58
59
|
|
|
59
60
|
> **Publisher 는 직접 실행자다.** commit·pr·review 모두 publisher 가 worktree 에서 직접 git 명령과 MCP 를 호출한다. 부장님은 git 권한이 아예 없고 dispatch 와 verdict 수신만 한다. (구버전의 "spec 계산 → 부장님 실행" 하이브리드 모델은 폐기됐다.)
|
|
60
61
|
|
|
@@ -94,6 +95,19 @@ makdoong2-team doctor # 설치 진단
|
|
|
94
95
|
|
|
95
96
|
결과는 `.makdoong2-team/<이슈>/research-findings.json` 으로 병합된다. 상세: ARCHITECTURE.md §3.6
|
|
96
97
|
|
|
98
|
+
### 오류 이슈 등록 — `/makdoong2-issue-reporter`
|
|
99
|
+
|
|
100
|
+
플러그인이 오작동했을 때(행 걸림, 잘못된 프롬프트 injection, 단계 실패 반복 등) 다음 커맨드로 GitHub 이슈를 등록한다:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/makdoong2-issue-reporter [증상 한 줄 설명(선택)]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- 커맨드가 **전용 full-permission 에이전트**(`makdoong2-issue-reporter`)로 라우팅되어, 호출 시점 이전의 로그·프롬프트·세션 컨텍스트를 스스로 수집해 이상 지점을 포착하고 [y00njinuk/makdoong2-team issues](https://github.com/y00njinuk/makdoong2-team/issues) 에 등록한다.
|
|
107
|
+
- **사용자 직접 호출이 유일한 트리거**다. 부장님·막둥이가 실패를 관측했다고 자율적으로 이슈를 만들지 않는다 (훅이 차단).
|
|
108
|
+
- 저장소가 public 이므로 사내 정보는 마스킹 후 첨부되며, **전송 전 마스킹 요약에 대한 사용자 최종 승인**을 반드시 거친다.
|
|
109
|
+
- PAT 는 `~/.config/opencode/.github` 파일에서 읽는다. 상세: ARCHITECTURE.md §4.6
|
|
110
|
+
|
|
97
111
|
---
|
|
98
112
|
|
|
99
113
|
## 3. 설정 — `makdoong2-team.json` 한 파일
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: makdoong2-issue-reporter
|
|
3
|
+
description: makdoong2-team 오류·비정상 동작 GitHub 이슈 리포터. 사용자가 /makdoong2-issue-reporter 커맨드로 직접 호출할 때만 활성화된다. 워크플로우 stage 에 참여하지 않으며 dispatch_stage 로 spawn 되지 않는다. 다른 에이전트가 자율적으로 선택·호출하지 않는다.
|
|
4
|
+
temperature: 0.1
|
|
5
|
+
mode: all
|
|
6
|
+
tools:
|
|
7
|
+
Read: true
|
|
8
|
+
Write: true
|
|
9
|
+
Edit: true
|
|
10
|
+
Bash: true
|
|
11
|
+
Grep: true
|
|
12
|
+
Glob: true
|
|
13
|
+
webfetch: true
|
|
14
|
+
skill: true
|
|
15
|
+
permission:
|
|
16
|
+
bash:
|
|
17
|
+
"*": "allow"
|
|
18
|
+
write:
|
|
19
|
+
"**/*": "allow"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
당신은 makdoong2-team 플러그인의 **이슈 리포터(makdoong2-issue-reporter)**다. 사용자가 `/makdoong2-issue-reporter` 커맨드로 직접 호출했을 때만 활성화되며, 직전 세션의 오류·비정상 동작을 스스로 수집·분석해 GitHub 이슈(y00njinuk/makdoong2-team)로 등록한다.
|
|
23
|
+
|
|
24
|
+
## 하드룰
|
|
25
|
+
|
|
26
|
+
1. **첫 행동으로 `skill(name="makdoong2-issue-reporter")` 를 로드**하고, 스킬에 정의된 절차를 그대로 따른다. 실행 순서는 스킬이 고정한다: **수집 → 이상 지점 포착 → 마스킹 → 중복 확인 → 최소 질의 → 이슈 생성**.
|
|
27
|
+
2. **마스킹 최종 확인 없이 전송 금지.** 이슈·코멘트·Gist 전송 전 마스킹 결과 요약을 사용자에게 제시하고 승인을 받는다. 사용자 승인 없이 GitHub 에 어떤 내용도 게시하지 않는다.
|
|
28
|
+
3. **토큰(PAT)은 어디에도 원문 노출 금지.** 커맨드 문자열에 직접 박지 않고 환경변수로 전달하며, 출력에는 마스킹(`ghp_****`)만 허용한다.
|
|
29
|
+
4. **워크플로우 상태를 변경하지 않는다.** state.json 은 증거 수집을 위한 읽기(`state.sh get`)만 허용. `state.sh set` / dispatch 계열 툴 호출 금지. 이 에이전트는 워크플로우 오케스트레이션과 완전히 분리된 조사·보고 전용이다.
|
|
30
|
+
5. **다른 에이전트로 위임하지 않는다.** 수집·분석·마스킹·등록 전 과정을 이 세션에서 직접 수행한다.
|
|
31
|
+
|
|
32
|
+
## 실행 컨텍스트
|
|
33
|
+
|
|
34
|
+
- 이 에이전트는 전권(full-permission)으로 실행된다: bash 전체 allow, 파일 쓰기 allow. 임시 페이로드 파일(`issue-payload.json` 등) 생성과 curl 호출을 직접 수행할 수 있다. 전송 후 임시 파일은 스킬 규약대로 삭제한다.
|
|
35
|
+
- 커맨드가 인라인으로 실행되므로 현재 세션의 직전 대화 턴(team-leader 오케스트레이션 로그 포함)을 그대로 볼 수 있다. 로그 파일 수집과 함께 대화 컨텍스트도 이상 지점 포착에 활용한다.
|
|
36
|
+
|
|
37
|
+
## 세션 종료 규약
|
|
38
|
+
|
|
39
|
+
완료 시 스킬 8장(완료 보고) 형식을 따른다. 최소한 다음을 한국어로 출력한다:
|
|
40
|
+
|
|
41
|
+
1. 생성된 이슈 번호와 `html_url` (또는 실패 시 수동 등록용 본문 전체)
|
|
42
|
+
2. 포착한 최초 이상 발생 지점(시각·단계·에이전트)과 수집 구간
|
|
43
|
+
3. 마스킹 내역 요약과 `미확인`으로 남긴 항목 목록
|
|
@@ -43,6 +43,7 @@ permission:
|
|
|
43
43
|
4. **모델 실패 시 `get_fallback_model` 툴로 폴백 모델 ID를 받아** `dispatch_stage`의 `model_override`에 넘긴다. 폴백이 exhausted면 사용자에게 보고.
|
|
44
44
|
5. **승인 게이트는 `.policy.auto_approve.<substage>` 마커가 결정한다.** 기본값은 minor·major 공통으로 전 substage `true` 이므로 전 흐름을 무인 진행한다. `.policy.category=="major"` 라도 auto_approve 가 true 인 한 사람 승인 없이 진행된다 — `category` 는 위험도 라벨/향후 opt-in 훅용이다. HITL 이 명시적으로 opt-in 된 경우(예: `.policy.auto_approve."3_delivery.commit"==false`) 에만 해당 substage 직전에 변경 보고서(`change-report.md`) 작성 → 사용자 승인 → 마커 기록 흐름을 밟는다.
|
|
45
45
|
6. **`retry_disallowed=true` 재-dispatch 금지 (hardrule).** `dispatch_stage` 반환 JSON 에 `retry_disallowed: true` 가 포함되면 **동일 stage 를 재호출하지 않는다**. 이 플래그는 `outcome_kind=="timeout"` 이면서 `transient_failures==0` 인 경우에만 세워지며, 네트워크·API 오류 없이 sub-agent 가 model/prompt 이슈로 hang 한 경우다. 재시도해도 동일 실패를 반복해 무한 루프에 빠진다. 대응: (a) `retry_disallowed_reason` 을 사용자에게 그대로 보고, (b) `get_fallback_model` 로 다른 모델 ID 를 받아 `model_override` 로 1회 한정 재시도, (c) fallback exhausted 면 세션 종료 후 사용자 개입 대기. `transient_failures>0` (네트워크 오류 등) 이거나 `retry_disallowed` 필드 자체가 없으면 기존 재시도 정책 유지.
|
|
46
|
+
7. **플러그인 오류 이슈 등록은 사용자 안내만 한다.** makdoong2-team 자체의 결함(행 걸림, 잘못된 프롬프트 injection, 단계 실패 반복 등)을 관측하거나 사용자가 "이슈 등록해줘"라고 요청해도 `skill(name="makdoong2-issue-reporter")` 를 직접 로드하지 않는다 (훅이 차단). 대신 사용자에게 `/makdoong2-issue-reporter [증상 한 줄(선택)]` 커맨드 실행을 안내한다 — 커맨드가 전용 전권 에이전트로 라우팅되어 수집·마스킹·등록을 수행한다.
|
|
46
47
|
|
|
47
48
|
## Hang 이력 조회 규약 (신규 — LLM API 안정성 관측)
|
|
48
49
|
|
package/bin/cli.js
CHANGED
|
@@ -122,7 +122,7 @@ function doDoctor(flags) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Config dir checks (agents & skills)
|
|
125
|
-
const configDirChecks = ["agents", "skills/jira-research", "skills/confluence-research", "skills/bitbucket-research", "skills/github-oss-research", "skills/bamboo-ci"];
|
|
125
|
+
const configDirChecks = ["agents", "skills/jira-research", "skills/confluence-research", "skills/bitbucket-research", "skills/github-oss-research", "skills/bamboo-ci", "skills/makdoong2-issue-reporter", "command/makdoong2-issue-reporter.md"];
|
|
126
126
|
for (const d of configDirChecks) {
|
|
127
127
|
check(existsSync(join(DEST, d)), `${d}/ in config dir`, `${d}/ missing in config dir (run: npx makdoong2-team install)`);
|
|
128
128
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: makdoong2-team 오류를 조사해 GitHub 이슈로 등록 (전용 에이전트 전권 실행)
|
|
3
|
+
agent: makdoong2-issue-reporter
|
|
4
|
+
subtask: false
|
|
5
|
+
---
|
|
6
|
+
`skill` 툴로 `makdoong2-issue-reporter` 스킬을 로드하고, 스킬 절차대로 수행하라: 호출 시점 이전의 로그·프롬프트·세션 컨텍스트를 수집해 이상 지점을 포착하고, 보안 마스킹과 사용자 최종 확인을 거쳐 GitHub 이슈(y00njinuk/makdoong2-team)로 등록한다.
|
|
7
|
+
|
|
8
|
+
사용자 보충 설명 (없으면 무시): $ARGUMENTS
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const ISSUE_REPORTER_SKILL_NAME = "makdoong2-issue-reporter";
|
|
2
|
+
export declare const ISSUE_REPORTER_AGENT = "makdoong2-issue-reporter";
|
|
3
|
+
/** skill 툴 args 에서 스킬 이름을 추출한다. { name } 및 { arguments: { name } } 형태 수용. */
|
|
4
|
+
export declare function extractSkillNameFromArgs(args: unknown): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* skill 툴 호출이 issue-reporter 트리거 정책을 위반하면 사용자에게 보여줄
|
|
7
|
+
* 에러 메시지를 반환하고, 정상이면 null 을 반환한다.
|
|
8
|
+
*/
|
|
9
|
+
export declare function issueReporterSkillLoadViolation(agent: string | undefined, args: unknown): string | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// makdoong2-issue-reporter 스킬의 사용자-전용 트리거 강제.
|
|
2
|
+
//
|
|
3
|
+
// Lives in its own module rather than opencode-plugin.ts because opencode's
|
|
4
|
+
// plugin loader invokes EVERY named export of the plugin entry file as a
|
|
5
|
+
// plugin factory (see test/plugin-exports-shape.test.mjs). Helpers must be
|
|
6
|
+
// imported, never re-exported from the entry file.
|
|
7
|
+
//
|
|
8
|
+
// 정책: makdoong2-issue-reporter 스킬의 유일한 트리거는 사용자의 직접 호출
|
|
9
|
+
// (/makdoong2-issue-reporter 커맨드)이다. 커맨드는 전용 full-permission
|
|
10
|
+
// 에이전트 makdoong2-issue-reporter 로 라우팅되므로, 그 외 에이전트
|
|
11
|
+
// (team-leader / sealed 서브에이전트)가 skill() 로 자율 로드하는 것은
|
|
12
|
+
// 트리거 정책 위반이다. 1차 방어는 SKILL.md description 의 명시, 이 모듈이
|
|
13
|
+
// tool.execute.before 훅에서 호출되는 2차(런타임) 방어다.
|
|
14
|
+
//
|
|
15
|
+
// agent 미상(undefined)은 차단하지 않는다 — outer/primary 세션 passthrough 로,
|
|
16
|
+
// OUTER_WORLD_TOOLS 가드와 동일한 설계 철학이다. 우리가 식별한 에이전트에만
|
|
17
|
+
// 정책을 적용하고, 식별 밖의 세션은 opencode 자체 permission 에 맡긴다.
|
|
18
|
+
export const ISSUE_REPORTER_SKILL_NAME = "makdoong2-issue-reporter";
|
|
19
|
+
export const ISSUE_REPORTER_AGENT = "makdoong2-issue-reporter";
|
|
20
|
+
/** skill 툴 args 에서 스킬 이름을 추출한다. { name } 및 { arguments: { name } } 형태 수용. */
|
|
21
|
+
export function extractSkillNameFromArgs(args) {
|
|
22
|
+
if (!args || typeof args !== "object")
|
|
23
|
+
return undefined;
|
|
24
|
+
const direct = args.name;
|
|
25
|
+
if (typeof direct === "string" && direct.length > 0)
|
|
26
|
+
return direct;
|
|
27
|
+
const nested = args.arguments;
|
|
28
|
+
if (nested && typeof nested === "object") {
|
|
29
|
+
const inner = nested.name;
|
|
30
|
+
if (typeof inner === "string" && inner.length > 0)
|
|
31
|
+
return inner;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* skill 툴 호출이 issue-reporter 트리거 정책을 위반하면 사용자에게 보여줄
|
|
37
|
+
* 에러 메시지를 반환하고, 정상이면 null 을 반환한다.
|
|
38
|
+
*/
|
|
39
|
+
export function issueReporterSkillLoadViolation(agent, args) {
|
|
40
|
+
const skillName = extractSkillNameFromArgs(args);
|
|
41
|
+
if (skillName !== ISSUE_REPORTER_SKILL_NAME)
|
|
42
|
+
return null;
|
|
43
|
+
if (agent === undefined)
|
|
44
|
+
return null;
|
|
45
|
+
if (agent === ISSUE_REPORTER_AGENT)
|
|
46
|
+
return null;
|
|
47
|
+
return (`[makdoong2-team issue-reporter trigger violation]\n` +
|
|
48
|
+
`Agent "${agent}" cannot load skill "${ISSUE_REPORTER_SKILL_NAME}".\n\n` +
|
|
49
|
+
`이 스킬의 유일한 트리거는 사용자의 직접 호출이다. 에이전트가 실패·예외를 ` +
|
|
50
|
+
`관측했더라도 자율적으로 이슈를 등록하지 않는다.\n\n` +
|
|
51
|
+
`**올바른 절차:** 사용자에게 다음 커맨드 실행을 안내하라:\n` +
|
|
52
|
+
` /makdoong2-issue-reporter [증상 한 줄 설명(선택)]\n\n` +
|
|
53
|
+
`커맨드는 전용 full-permission 에이전트(${ISSUE_REPORTER_AGENT})로 라우팅되어 ` +
|
|
54
|
+
`수집·마스킹·등록을 수행한다.`);
|
|
55
|
+
}
|
package/dist/opencode-plugin.js
CHANGED
|
@@ -33,6 +33,7 @@ import { injectAllSecrets } from "./mcp-secret-injector.js";
|
|
|
33
33
|
import { pollSubSession as pollSubSessionCore, pollOutcomeToLegacy } from "./poll-sub-session.js";
|
|
34
34
|
import { logger } from "./logger.js";
|
|
35
35
|
import { redactAndTruncate } from "./redact-secrets.js";
|
|
36
|
+
import { issueReporterSkillLoadViolation, ISSUE_REPORTER_AGENT } from "./issue-reporter-guard.js";
|
|
36
37
|
// All runtime paths come from makdoong2-team.json (paths.* overrides) or are
|
|
37
38
|
// derived from the opencode config dir. No MAKDOONG2 environment variables.
|
|
38
39
|
const { hooks: HOOKS_DIR, gates: GATES_DIR, scripts: SCRIPTS_DIR, stages: STAGES_DIR, skills: SKILLS_DIR, } = resolvePaths();
|
|
@@ -828,6 +829,10 @@ export const Makdoong2TeamPlugin = async ({ $, client, directory, worktree }) =>
|
|
|
828
829
|
// already omits Task (L1), but sealed workflow is defence in depth — a worker
|
|
829
830
|
// missing here would be caught by nothing at runtime (L2).
|
|
830
831
|
"makdoong2-researcher",
|
|
832
|
+
// User-only issue reporter. 워크플로우에 참여하지 않지만 outer-world 위임은
|
|
833
|
+
// 동일하게 금지 — 수집·마스킹·등록 전 과정을 자기 세션에서 완결해야 하며,
|
|
834
|
+
// 위임하면 마스킹·승인 게이트가 위임처에서 우회될 수 있다.
|
|
835
|
+
ISSUE_REPORTER_AGENT,
|
|
831
836
|
]);
|
|
832
837
|
// 미래의 oh-my-openagent 위임 툴을 조기 발견하기 위한 이름 패턴.
|
|
833
838
|
// 알려진 툴이 아니면서 위임/스폰을 시사하는 이름이 감지되면 경고한다.
|
|
@@ -987,6 +992,18 @@ export const Makdoong2TeamPlugin = async ({ $, client, directory, worktree }) =>
|
|
|
987
992
|
logger.debug(`[makdoong2-team hook] sealed sub-agent "${agent}" called delegation-like tool "${input.tool}" not in blocklist. ` +
|
|
988
993
|
`Consider adding to OUTER_WORLD_TOOLS if this is a new oh-my-openagent delegation tool.`);
|
|
989
994
|
}
|
|
995
|
+
// ── issue-reporter 스킬 사용자-전용 트리거 강제 ──
|
|
996
|
+
// makdoong2-issue-reporter 스킬은 사용자의 /makdoong2-issue-reporter
|
|
997
|
+
// 커맨드(전용 full-permission 에이전트로 라우팅)로만 실행된다. 다른
|
|
998
|
+
// 에이전트의 skill() 자율 로드는 트리거 정책 위반 — 1차 방어는 SKILL.md
|
|
999
|
+
// description, 이 블록이 2차(런타임) 방어다.
|
|
1000
|
+
if (toolLower === "skill") {
|
|
1001
|
+
const violation = issueReporterSkillLoadViolation(agent, output.args);
|
|
1002
|
+
if (violation) {
|
|
1003
|
+
logger.error(`[makdoong2-team hook] BLOCKED: agent "${agent}" attempted autonomous load of user-only skill makdoong2-issue-reporter (sessionID="${sessionID}")`);
|
|
1004
|
+
throw new Error(violation);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
990
1007
|
// ── skill_mcp lazy-load 사전 힌트 ──
|
|
991
1008
|
// opencode 는 skill 이 로드되기 전에 skill_mcp 를 호출하면
|
|
992
1009
|
// "MCP server not found" 로 튕기지만 정작 어떤 skill 을 로드해야 하는지는
|
package/package.json
CHANGED
package/scripts/install-lib.mjs
CHANGED
|
@@ -93,6 +93,11 @@ const TOOL_SEARCH_PLUGIN_PREFIX = "opencode-tool-search";
|
|
|
93
93
|
// and we need the list to remove them without touching unrelated user scripts.
|
|
94
94
|
const UTIL_SCRIPTS = ["state.sh", "rollback-commits.sh", "wt-sync-ignored.sh", "log-event.sh", "config.sh", "model-policy.mjs"];
|
|
95
95
|
const RESEARCH_SKILLS = ["jira-research", "confluence-research", "bitbucket-research", "github-oss-research", "bamboo-ci"];
|
|
96
|
+
// User-invoked utility skills — workflow 밖의 사용자 전용 도구. issue reporter 는
|
|
97
|
+
// /makdoong2-issue-reporter 커맨드(command/ 배포분)로만 트리거되고, 같은 이름의
|
|
98
|
+
// config command 가 opencode 의 skill-derived command 를 덮어써 전용
|
|
99
|
+
// full-permission 에이전트로 라우팅된다.
|
|
100
|
+
const UTILITY_SKILLS = ["makdoong2-issue-reporter"];
|
|
96
101
|
|
|
97
102
|
// Stale config-dir artifacts left behind by pre-refactor installs. Removed
|
|
98
103
|
// (with backup) at the start of install() so a re-run cleanly migrates users
|
|
@@ -315,8 +320,8 @@ export function install(opts) {
|
|
|
315
320
|
// Create directory structure — only agents/ and research skills/ are deployed.
|
|
316
321
|
// All other runtime assets (gates, scripts, stages, references) live in the
|
|
317
322
|
// npm module and are referenced via resolvePaths() in src/config.ts.
|
|
318
|
-
const skillDirs = RESEARCH_SKILLS.map((s) => `skills/${s}`);
|
|
319
|
-
for (const d of ["agents", ...skillDirs]) {
|
|
323
|
+
const skillDirs = [...RESEARCH_SKILLS, ...UTILITY_SKILLS].map((s) => `skills/${s}`);
|
|
324
|
+
for (const d of ["agents", "command", ...skillDirs]) {
|
|
320
325
|
mkdirSync(join(configDir, d), { recursive: true });
|
|
321
326
|
}
|
|
322
327
|
|
|
@@ -353,6 +358,35 @@ export function install(opts) {
|
|
|
353
358
|
}
|
|
354
359
|
ok("research skills (jira/confluence/bitbucket/github-oss/bamboo)");
|
|
355
360
|
|
|
361
|
+
// 2b) Utility skills — user-invoked helpers outside the workflow.
|
|
362
|
+
for (const skill of UTILITY_SKILLS) {
|
|
363
|
+
const src = join(pkgRoot, "skills", skill);
|
|
364
|
+
if (!existsSync(src)) continue;
|
|
365
|
+
const dst = join(configDir, "skills", skill);
|
|
366
|
+
mkdirSync(dst, { recursive: true });
|
|
367
|
+
for (const name of readdirSync(src)) {
|
|
368
|
+
cpSync(join(src, name), join(dst, name));
|
|
369
|
+
if (name.endsWith(".sh")) chmodSync(join(dst, name), 0o755);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
ok("utility skills (makdoong2-issue-reporter)");
|
|
373
|
+
|
|
374
|
+
// 2c) User commands — /makdoong2-issue-reporter 진입점. opencode 는 스킬을
|
|
375
|
+
// 자동으로 같은 이름의 커맨드로 노출하지만 그 커맨드에는 agent 필드가 없어
|
|
376
|
+
// 현재 에이전트(권한 제한된 team-leader 등)로 실행된다. cfg.command 가
|
|
377
|
+
// skill-derived command 보다 우선하므로, 같은 이름의 command 파일을 배포해
|
|
378
|
+
// 전용 full-permission 에이전트로 라우팅한다.
|
|
379
|
+
const commandSrc = join(pkgRoot, "command");
|
|
380
|
+
if (existsSync(commandSrc)) {
|
|
381
|
+
const commandDst = join(configDir, "command");
|
|
382
|
+
mkdirSync(commandDst, { recursive: true });
|
|
383
|
+
for (const name of readdirSync(commandSrc)) {
|
|
384
|
+
if (!name.endsWith(".md")) continue;
|
|
385
|
+
cpSync(join(commandSrc, name), join(commandDst, name));
|
|
386
|
+
}
|
|
387
|
+
ok("user commands (/makdoong2-issue-reporter)");
|
|
388
|
+
}
|
|
389
|
+
|
|
356
390
|
// 3) Config file — seed from default ONLY if absent (never clobber user edits unless --force)
|
|
357
391
|
const cfgPath = join(configDir, "makdoong2-team.json");
|
|
358
392
|
if (!existsSync(cfgPath) || force) {
|
|
@@ -839,8 +873,10 @@ function removeOpencodeCache(pkgRoot, { ok, skip, warn }) {
|
|
|
839
873
|
*
|
|
840
874
|
* What is removed (idempotent — absent targets are silently skipped):
|
|
841
875
|
* 1. Agent definitions: agents/makdoong2-*.md
|
|
842
|
-
* 2.
|
|
843
|
-
*
|
|
876
|
+
* 2. Skill directories: research skills (skills/jira-research, confluence-research,
|
|
877
|
+
* bitbucket-research, ...), utility skills (skills/makdoong2-issue-reporter),
|
|
878
|
+
* skills/_lib/ (only when it contains solely our files), and deployed
|
|
879
|
+
* command files (command/makdoong2-*.md)
|
|
844
880
|
* 3. opencode.json: plugin ref, tools keys, alwaysLoad entries (when
|
|
845
881
|
* opencode-tool-search is configured), pkgRoot/** external_directory entry
|
|
846
882
|
* 4. opencode plugin cache symlinks seeded by seedOpencodeCache()
|
|
@@ -888,10 +924,10 @@ export function uninstall(opts) {
|
|
|
888
924
|
skip("agents/: directory not found");
|
|
889
925
|
}
|
|
890
926
|
|
|
891
|
-
// 2)
|
|
892
|
-
info("Removing
|
|
927
|
+
// 2) Skill directories (research + utility)
|
|
928
|
+
info("Removing skills...");
|
|
893
929
|
let skillsRemoved = 0;
|
|
894
|
-
for (const skill of RESEARCH_SKILLS) {
|
|
930
|
+
for (const skill of [...RESEARCH_SKILLS, ...UTILITY_SKILLS]) {
|
|
895
931
|
const skillDir = join(configDir, "skills", skill);
|
|
896
932
|
if (!existsSync(skillDir)) continue;
|
|
897
933
|
rmSync(skillDir, { recursive: true, force: true });
|
|
@@ -909,9 +945,25 @@ export function uninstall(opts) {
|
|
|
909
945
|
warn("skills/_lib/ contains unrecognised files — leaving in place");
|
|
910
946
|
}
|
|
911
947
|
}
|
|
912
|
-
if (skillsRemoved > 0) ok(`${skillsRemoved}
|
|
948
|
+
if (skillsRemoved > 0) ok(`${skillsRemoved} skill director(y/ies) removed`);
|
|
913
949
|
else skip("skills/: no makdoong2-team skill directories found");
|
|
914
950
|
|
|
951
|
+
// 2b) Deployed command files (only ours — makdoong2-*.md)
|
|
952
|
+
info("Removing command files...");
|
|
953
|
+
const cmdDir = join(configDir, "command");
|
|
954
|
+
if (existsSync(cmdDir)) {
|
|
955
|
+
let cmdRemoved = 0;
|
|
956
|
+
for (const name of readdirSync(cmdDir)) {
|
|
957
|
+
if (!name.startsWith("makdoong2-") || !name.endsWith(".md")) continue;
|
|
958
|
+
rmSync(join(cmdDir, name), { force: true });
|
|
959
|
+
cmdRemoved++;
|
|
960
|
+
}
|
|
961
|
+
if (cmdRemoved > 0) ok(`${cmdRemoved} command file(s) removed from command/`);
|
|
962
|
+
else skip("command/: no makdoong2-*.md files found");
|
|
963
|
+
} else {
|
|
964
|
+
skip("command/: directory not found");
|
|
965
|
+
}
|
|
966
|
+
|
|
915
967
|
// 3) opencode.json
|
|
916
968
|
info("Patching opencode.json...");
|
|
917
969
|
unpatchOpencodeJson(configDir, pkgRoot, { ok, skip, warn });
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -38,6 +38,7 @@ const STEPS = [
|
|
|
38
38
|
"node test/install-lib.test.mjs",
|
|
39
39
|
"node test/skill-mcp-registry.test.mjs",
|
|
40
40
|
"node --test test/state-write-guard.test.mjs",
|
|
41
|
+
"node --test test/issue-reporter-guard.test.mjs",
|
|
41
42
|
"node --test test/state-sh-schema.test.mjs",
|
|
42
43
|
"node --test test/state-sh-init-review-shape.test.mjs",
|
|
43
44
|
"node --test test/doctor-phantom-scan.test.mjs",
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: makdoong2-issue-reporter
|
|
3
|
+
description: makdoong2-team 플러그인의 오류·비정상 동작을 GitHub 이슈(https://github.com/y00njinuk/makdoong2-team/issues)로 등록한다. 호출 시점 이전의 로그·프롬프트·세션 컨텍스트를 스스로 모두 수집해 문제가 발생한 지점을 포착하는 것이 핵심 동작이며, 사용자 질의는 그 결과를 보완·확인하는 용도로만 쓴다. 유일한 트리거는 사용자의 직접 호출(/makdoong2-issue-reporter 커맨드)이다. 에이전트가 세션 중 실패·예외·행 걸림을 관측했다는 이유로 자율적으로 로드해서는 안 되며, 전용 에이전트(makdoong2-issue-reporter) 외의 skill 로드는 플러그인 훅이 런타임 차단한다. 수집한 증거는 사내 보안 규칙에 따라 마스킹한 뒤 첨부한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# makdoong2-team Issue Reporter
|
|
7
|
+
|
|
8
|
+
makdoong2-team 플러그인의 결함을 재현 가능한 형태로 GitHub 이슈에 축적하기 위한 스킬.
|
|
9
|
+
|
|
10
|
+
## 트리거 & 실행 컨텍스트 (hardrule)
|
|
11
|
+
|
|
12
|
+
- **유일한 트리거는 사용자의 직접 호출** — `/makdoong2-issue-reporter [보충 설명]` 커맨드. 커맨드 frontmatter 의 `agent` 필드가 전용 에이전트 `makdoong2-issue-reporter` 로 라우팅한다.
|
|
13
|
+
- **전용 에이전트는 전권(full-permission) 으로 실행된다** — bash 전체 allow, 파일 쓰기 allow. team-leader 의 파일 쓰기·git 제한이 적용되지 않으므로 아래 절차의 임시 파일 생성(`issue-payload.json` 등)과 curl 호출을 그대로 수행할 수 있다.
|
|
14
|
+
- **다른 에이전트의 자율 로드 금지** — team-leader·sealed 서브에이전트가 `skill(name="makdoong2-issue-reporter")` 를 호출하면 플러그인 `tool.execute.before` 훅이 차단한다. 실패를 관측한 에이전트는 스킬을 로드하는 대신 사용자에게 `/makdoong2-issue-reporter` 실행을 안내한다.
|
|
15
|
+
- 인라인 실행이므로 현재 세션의 직전 대화 턴을 그대로 볼 수 있다. 4장(항목 확정)과 2.3(마스킹 최종 확인)의 사용자 문답도 같은 세션에서 이어서 진행한다.
|
|
16
|
+
|
|
17
|
+
## 핵심 동작
|
|
18
|
+
|
|
19
|
+
**이 스킬이 호출되면, 호출 시점 이전의 로그·프롬프트·컨텍스트를 모두 수집해 문제가 발생한 지점을 스스로 포착하고 이슈로 등록한다.**
|
|
20
|
+
|
|
21
|
+
- 사용자에게 "무슨 문제였나요"를 먼저 묻지 않는다. 호출 자체가 "직전에 뭔가 잘못됐으니 조사해서 남겨라"라는 지시다.
|
|
22
|
+
- 사용자가 증상을 한 줄만 말하거나 아무 설명 없이 호출해도 동작해야 한다. 부족한 정보는 질의가 아니라 **수집과 분석으로 먼저 메운다**.
|
|
23
|
+
- 질의(4장)는 수집·분석으로 확정할 수 없는 항목(기대 동작, 재현성, 시도한 조치)과 **분석 결과 확인**에만 사용한다.
|
|
24
|
+
- 실행 순서는 고정한다: **수집(3장) → 이상 지점 포착(3.3) → 마스킹(2장) → 중복 확인(5장) → 최소 질의(4장) → 이슈 생성(7장)**.
|
|
25
|
+
|
|
26
|
+
## 0. 대상 리소스
|
|
27
|
+
|
|
28
|
+
| 구분 | 값 |
|
|
29
|
+
|---|---|
|
|
30
|
+
| 저장소 | `y00njinuk/makdoong2-team` (**Public**) |
|
|
31
|
+
| 이슈 목록 | https://github.com/y00njinuk/makdoong2-team/issues |
|
|
32
|
+
| 신규 이슈 | https://github.com/y00njinuk/makdoong2-team/issues/new |
|
|
33
|
+
| 이슈 API | `https://api.github.com/repos/y00njinuk/makdoong2-team/issues` |
|
|
34
|
+
| 라벨 | https://github.com/y00njinuk/makdoong2-team/labels |
|
|
35
|
+
| PAT 파일 | `${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.github` (root 로 구동하는 WSL 환경에서는 `/root/.config/opencode/.github`) |
|
|
36
|
+
| 기본 로그 | `/var/log/opencode/opencode.log` |
|
|
37
|
+
|
|
38
|
+
이슈를 생성하거나 코멘트를 남긴 뒤에는 반드시 위 이슈 목록 링크와 생성된 이슈의 `html_url`을 사용자에게 함께 제시한다.
|
|
39
|
+
|
|
40
|
+
> **저장소가 public이다.** 이슈 본문·로그·프롬프트 발췌는 전 세계에 공개된다. 2장의 보안 규칙을 예외 없이 적용한다.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 1. 인증 (Personal Access Token)
|
|
45
|
+
|
|
46
|
+
PAT는 opencode config 디렉토리의 `.github` 파일에 기록되어 있다. 파일을 읽어 토큰을 획득한다.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
PAT_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/.github"
|
|
50
|
+
test -r "$PAT_FILE" || { echo "PAT 파일 없음 또는 읽기 권한 없음: $PAT_FILE"; exit 1; }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
파일 포맷은 다음 중 하나일 수 있으므로 순서대로 판별한다.
|
|
54
|
+
|
|
55
|
+
| 포맷 | 예시 | 추출 방법 |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| 토큰 단독 | `ghp_xxx` / `github_pat_xxx` | 파일 내용 trim |
|
|
58
|
+
| `KEY=VALUE` | `GITHUB_TOKEN=ghp_xxx` | `=` 우측 값 |
|
|
59
|
+
| INI/JSON | `{"token": "ghp_xxx"}` | 해당 키 값 |
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
GH_TOKEN="$(grep -oE '(ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]+' "$PAT_FILE" | head -n1)"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**제약 사항**
|
|
66
|
+
- 토큰 값은 대화 출력, 이슈 본문, 로그 어디에도 그대로 노출하지 않는다. 마스킹(`ghp_****`)만 허용.
|
|
67
|
+
- `curl` 사용 시 `-H "Authorization: Bearer $GH_TOKEN"` 형태로 환경변수를 통해 전달하고, 커맨드 문자열에 토큰을 직접 문자열로 박지 않는다.
|
|
68
|
+
- 토큰 파일 내용을 그대로 출력하거나 다른 경로로 복사하지 않는다.
|
|
69
|
+
- 토큰이 없거나 401/403이 반환되면 이슈 생성을 중단하고, 본문 전체를 마크다운으로 출력해 사용자가 수동 등록할 수 있게 한다.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 2. 보안 규칙 (최우선 · 예외 없음)
|
|
74
|
+
|
|
75
|
+
**회사 보안에 위반되는 소스 코드·설정·데이터는 어떤 형태로도 이슈에 노출되어서는 안 된다.** 부득이하게 포함이 필요한 경우 반드시 마스킹 처리한 뒤 첨부한다. 이 규칙은 이슈 본문, 제목, 라벨, 코멘트, Gist, 대화 출력 전체에 동일하게 적용된다.
|
|
76
|
+
|
|
77
|
+
### 2.1 절대 노출 금지 (마스킹으로도 대체 불가 — 아예 제외)
|
|
78
|
+
|
|
79
|
+
- 사내 저장소의 소스 코드 원문, 사내 라이브러리·모듈의 내부 구현
|
|
80
|
+
- 고객사명·고객 데이터·위협 인텔리전스 실데이터, 사내 DB 스키마·쿼리 결과
|
|
81
|
+
- 사내 시스템 자격 증명 일체: PAT, Bearer/Access token, Azure/Graph client secret, API key, 인증서, 비밀번호
|
|
82
|
+
- 사내 문서(Jira/Confluence) 본문 인용
|
|
83
|
+
|
|
84
|
+
포함하지 않고는 이슈가 성립하지 않는다면, 해당 내용을 **일반화된 서술로 재작성**한다(예: 사내 API 응답 원문 → "사내 REST API가 404를 반환").
|
|
85
|
+
|
|
86
|
+
### 2.2 마스킹 후 사용 가능
|
|
87
|
+
|
|
88
|
+
| 대상 | 원본 예 | 마스킹 형태 |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| 사내 IP·호스트 | `172.20.11.96:8000` | `<internal-host>:<port>` |
|
|
91
|
+
| 사내 도메인/URL | 사내 Jira·Confluence·Bitbucket·Bamboo URL | `<internal-jira>/browse/<ISSUE-KEY>` |
|
|
92
|
+
| 이슈 키·티켓 번호 | 실제 Jira 키 | `<ISSUE-KEY>` |
|
|
93
|
+
| 계정·사번·이메일 | 사내 계정명 | `<user>` |
|
|
94
|
+
| 파일 경로 중 사내 프로젝트명 | `/home/<user>/work/<사내repo>/...` | `/home/<user>/work/<internal-repo>/...` |
|
|
95
|
+
| 모델·프로바이더 사내 엔드포인트 | 사내 모델 서버 주소 | `<internal-model-endpoint>` |
|
|
96
|
+
| 토큰 유사 문자열 | `ghp_...`, `eyJ...`(JWT) | `<redacted-token>` |
|
|
97
|
+
|
|
98
|
+
### 2.3 절차
|
|
99
|
+
|
|
100
|
+
1. 첨부 후보 텍스트를 모은 뒤, **첨부 직전에** 마스킹 스캔을 1회 수행한다.
|
|
101
|
+
2. 마스킹 대상 여부가 불확실한 라인은 첨부에서 **제외**한다(포함하고 판단을 미루지 않는다).
|
|
102
|
+
3. 마스킹으로 인해 재현 정보가 소실되는 경우, 소실된 항목을 이슈 본문에 `<마스킹됨: 사유>`로 명시한다.
|
|
103
|
+
4. 이슈 전송 전, 마스킹 결과 요약(무엇을 몇 건 가렸는지)을 사용자에게 제시하고 **최종 확인을 받는다**. 사용자 승인 없이 전송하지 않는다.
|
|
104
|
+
|
|
105
|
+
스캔 보조:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
grep -nEi '(ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]+|eyJ[A-Za-z0-9_-]{10,}|172\.(1[6-9]|2[0-9]|3[01])\.[0-9]+\.[0-9]+|10\.[0-9]+\.[0-9]+\.[0-9]+|(client_)?secret|password|api[_-]?key|Authorization:' <파일>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
이 grep은 보조 수단일 뿐이며, 사내 코드·고객 데이터처럼 패턴으로 잡히지 않는 항목은 내용 판단으로 걸러낸다.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 3. 증거 수집 및 이상 지점 포착
|
|
116
|
+
|
|
117
|
+
호출 시점을 `T`로 두고, **`T` 이전 구간을 전수 수집한 뒤 좁혀 들어간다.** 수집 범위 기본값은 다음과 같다.
|
|
118
|
+
|
|
119
|
+
| 범위 | 기본값 | 확장 조건 |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| 시간 | `T - 2시간` ~ `T` | 해당 구간에 오류 흔적이 없으면 `T - 24시간`까지 확대 |
|
|
122
|
+
| 세션 | 현재 세션 전체 | 사용자가 다른 세션을 지목하면 해당 세션 |
|
|
123
|
+
| 턴 | 현재 세션의 모든 턴(프롬프트·응답·tool call) | — |
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
T="$(date -Is)"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3.1 사용 가능한 증거 소스
|
|
130
|
+
|
|
131
|
+
`opencode.log`에 한정하지 않는다. 문제 재현·원인 파악에 도움이 되는 것은 모두 사용해도 좋다. **단, 3.3의 처리를 거친 뒤 2장의 보안 규칙을 통과한 것만 첨부한다.**
|
|
132
|
+
|
|
133
|
+
| 소스 | 예 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| OpenCode 로그 | `/var/log/opencode/opencode.log`, 로테이션 파일(`*.log.1`, `*.gz`) |
|
|
136
|
+
| 세션·에이전트 로그 | omo/플러그인이 남기는 세션 로그, 에이전트별 실행 로그 |
|
|
137
|
+
| 프롬프트 입출력 | 부장님·막둥이에게 전달된 프롬프트 원문, 서브에이전트 응답 원문, injection된 시스템 프롬프트 |
|
|
138
|
+
| 도구 호출 기록 | tool call 파라미터·결과, MCP 서버 요청/응답 |
|
|
139
|
+
| 터미널 출력 | 플러그인 실행 시 stdout/stderr, 스택트레이스 |
|
|
140
|
+
| 설정 파일 | `opencode.json` 등 설정 스냅샷 (자격 증명 제거 후) |
|
|
141
|
+
| 외부 연동 기록 | Teams(Graph API/Power Automate) 호출 응답 코드·에러 메시지 |
|
|
142
|
+
|
|
143
|
+
### 3.2 수집 예
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 기본: 최근 500라인
|
|
147
|
+
tail -n 500 /var/log/opencode/opencode.log > /tmp/makdoong2-evidence.log
|
|
148
|
+
|
|
149
|
+
# 키워드 기반 구간 추출
|
|
150
|
+
grep -nE 'ERROR|WARN|Exception|Traceback|makdoong2|부장님|막둥이|agent|prompt' \
|
|
151
|
+
/var/log/opencode/opencode.log | tail -n 200
|
|
152
|
+
|
|
153
|
+
# 로테이션 파일 포함 검색
|
|
154
|
+
zgrep -hE 'ERROR|makdoong2' /var/log/opencode/opencode.log* | tail -n 200
|
|
155
|
+
|
|
156
|
+
# 설정 스냅샷 (자격 증명 키 제거)
|
|
157
|
+
jq 'walk(if type == "object" then with_entries(select(.key | test("token|secret|key|password"; "i") | not)) else . end)' opencode.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3.3 이상 지점 포착 (수집 직후 수행)
|
|
161
|
+
|
|
162
|
+
수집한 자료를 시간순으로 정렬한 뒤, 아래 신호를 탐지해 **문제 발생 지점 1곳을 특정**한다.
|
|
163
|
+
|
|
164
|
+
| 신호 유형 | 탐지 대상 |
|
|
165
|
+
|---|---|
|
|
166
|
+
| 예외·오류 | `ERROR`, `FATAL`, `Exception`, `Traceback`, non-zero exit, 4xx/5xx 응답 |
|
|
167
|
+
| 중단·정체 | 특정 단계 이후 로그 공백, timeout, 응답 없이 종료된 tool call, `session.idle` 미도달 |
|
|
168
|
+
| 프롬프트 이상 | 조건과 무관한 프롬프트 injection, 시스템 프롬프트 누락·중복, 컨텍스트 초과·절단 |
|
|
169
|
+
| 오케스트레이션 이상 | 호출되지 않아야 할 에이전트 호출, 응답 미수신, 결과 취합 누락, 잘못된 단계 전이 |
|
|
170
|
+
| 연동 실패 | MCP·Graph API·Jira 호출 실패, 인증 오류 |
|
|
171
|
+
| 반복 | 동일 단계·동일 메시지의 재시도 루프 |
|
|
172
|
+
|
|
173
|
+
특정 결과는 다음 형태로 정리한다.
|
|
174
|
+
|
|
175
|
+
- **최초 이상 발생 지점**: 타임스탬프 + 단계명 + 에이전트 + 로그 라인 번호
|
|
176
|
+
- **선행 정상 지점**: 마지막으로 정상 동작한 단계 (경계 확정용)
|
|
177
|
+
- **후행 파급**: 이상 이후 연쇄 실패 여부
|
|
178
|
+
- **근거 인용 3건 이내**: 위 판단을 뒷받침하는 로그/프롬프트 발췌
|
|
179
|
+
|
|
180
|
+
판정 규칙:
|
|
181
|
+
|
|
182
|
+
- 후보가 여러 개면 **가장 이른 시각의 것**을 근본 지점으로 삼고, 나머지는 파급으로 분류해 본문에 함께 적는다.
|
|
183
|
+
- 신호가 전혀 없으면 이슈를 임의로 만들지 말고, 수집 범위와 탐지 결과(무엇을 봤고 무엇이 없었는지)를 제시한 뒤 사용자에게 증상 설명을 요청한다.
|
|
184
|
+
- 이상 지점을 특정했더라도 **원인 단정은 하지 않는다.** 이슈 본문에는 관측된 사실과 추정(추정임을 명시)을 구분해 기재한다.
|
|
185
|
+
|
|
186
|
+
### 3.4 첨부 전 처리
|
|
187
|
+
|
|
188
|
+
1. 재현과 무관한 구간 제거 — 발생 시각 ±5분, 관련 세션 ID로 한정한다.
|
|
189
|
+
2. 2장 마스킹 적용.
|
|
190
|
+
3. 프롬프트 원문은 **문제 재현에 필요한 최소 범위**만 발췌한다. 전체 대화 로그를 통째로 붙이지 않는다.
|
|
191
|
+
4. 각 증거 블록에 출처와 범위를 명시한다(파일 경로 + 라인 범위, 또는 "세션 `<id>` 3번째 턴의 서브에이전트 프롬프트").
|
|
192
|
+
|
|
193
|
+
### 3.5 첨부 방식
|
|
194
|
+
|
|
195
|
+
GitHub REST API의 issue 생성 엔드포인트는 **바이너리 파일 첨부를 지원하지 않는다**(드래그 앤 드롭 업로드는 웹 UI 전용). 따라서 다음 순서로 처리한다.
|
|
196
|
+
|
|
197
|
+
| 우선순위 | 방식 | 조건 |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| 1 | 이슈 본문 내 `<details>` + 코드블록 인라인 | 발췌가 명확하고 본문 한도 내일 때 |
|
|
200
|
+
| 2 | Gist 생성 후 이슈 본문에 링크 | 로그가 크거나 전체 컨텍스트가 필요할 때 |
|
|
201
|
+
| 3 | 로컬 경로·라인 범위만 명시 | 마스킹 부담이 크거나 위 두 방식이 불가할 때 |
|
|
202
|
+
|
|
203
|
+
이슈 본문은 GitHub 제한상 65536자를 넘길 수 없다. 초과 시 방식 2로 전환한다.
|
|
204
|
+
|
|
205
|
+
> **Gist 주의**: secret gist는 비공개가 아니라 **URL을 아는 누구나 접근 가능**하다. 사내 정보 보호 수단이 아니므로, Gist에 올리는 내용에도 2장 규칙을 동일하게 적용한다.
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
curl -sS -X POST https://api.github.com/gists \
|
|
209
|
+
-H "Authorization: Bearer $GH_TOKEN" \
|
|
210
|
+
-H "Accept: application/vnd.github+json" \
|
|
211
|
+
-d @gist-payload.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 4. 항목 확정 (수집 우선 · 질의 최소화)
|
|
217
|
+
|
|
218
|
+
3장 수집·분석으로 **채울 수 있는 항목은 전부 스스로 채운다.** 아래 표의 "확보 방법"이 자동인 항목을 사용자에게 되묻지 않는다. 질의는 사용자만 알 수 있는 항목과 분석 결과 확인으로 한정한다.
|
|
219
|
+
|
|
220
|
+
### 4.1 필수 항목
|
|
221
|
+
|
|
222
|
+
| 항목 | 설명 | 확보 방법 |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `증상 요약` | 제목용 한 줄 요약 | 자동 (3.3 포착 결과에서 도출, 사용자 확인) |
|
|
225
|
+
| `재현 절차` | 입력 프롬프트 포함, 번호 매긴 단계 | 자동 (세션 턴 재구성) |
|
|
226
|
+
| `기대 동작` | 정상이라면 어떻게 되어야 하는가 | 질의 (자명한 경우 자동 서술 후 확인) |
|
|
227
|
+
| `실제 동작` | 관측된 결과 | 자동 (로그·응답) |
|
|
228
|
+
| `발생 시각` | ISO8601, 로그 구간 특정용 | 자동 (3.3 최초 이상 발생 지점) |
|
|
229
|
+
| `재현성` | 항상 / 간헐적(빈도) / 1회성 | 질의 (동일 패턴이 로그에 반복되면 자동 추정 후 확인) |
|
|
230
|
+
|
|
231
|
+
### 4.2 환경 항목 (자동 수집 우선)
|
|
232
|
+
|
|
233
|
+
| 항목 | 수집 명령 / 출처 |
|
|
234
|
+
|---|---|
|
|
235
|
+
| OpenCode 버전 | `opencode --version` |
|
|
236
|
+
| omo(oh-my-openagent) 버전 | omo 설정 또는 `omo --version` |
|
|
237
|
+
| makdoong2-team 커밋/브랜치 | `git -C <plugin-path> rev-parse --short HEAD` |
|
|
238
|
+
| OS / 커널 | `cat /etc/os-release; uname -r` (WSL2 여부 명시) |
|
|
239
|
+
| 런타임 | `node -v`, `bun -v` |
|
|
240
|
+
| provider / model | opencode 설정의 활성 provider·model (엔드포인트는 마스킹) |
|
|
241
|
+
|
|
242
|
+
### 4.3 makdoong2 고유 항목
|
|
243
|
+
|
|
244
|
+
| 항목 | 설명 |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `관련 에이전트` | 부장님(orchestrator) 또는 막둥이 8인 중 해당 에이전트명. 미상이면 `unknown` |
|
|
247
|
+
| `단계` | 실패한 워크플로 단계. 단계 파일명 그대로 기재(예: `1_planning.jira`). 특정 불가 시 요청 분배 / 에이전트 실행 / 결과 취합 / 응답 반환 중 선택 |
|
|
248
|
+
| `세션 ID` | OpenCode 세션 식별자 (로그 상관관계 추적용) |
|
|
249
|
+
| `연동 경로` | Teams(Graph API/Power Automate) 경유 여부, MCP 서버 경유 여부 |
|
|
250
|
+
| `프롬프트 이상 여부` | 의도치 않은 프롬프트 injection·누락·중복 발생 여부 |
|
|
251
|
+
| `에러 메시지` | 원문 (스택트레이스 포함, 마스킹 후) |
|
|
252
|
+
|
|
253
|
+
### 4.4 분류 항목
|
|
254
|
+
|
|
255
|
+
| 항목 | 값 |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `심각도` | `blocker` / `major` / `minor` |
|
|
258
|
+
| `영향 범위` | 특정 에이전트 / 오케스트레이션 전체 / 외부 연동만 |
|
|
259
|
+
| `시도한 조치` | 이미 해본 우회·수정 내용 (없으면 `없음`) |
|
|
260
|
+
|
|
261
|
+
질의 규칙:
|
|
262
|
+
|
|
263
|
+
1. 첫 응답은 질문이 아니라 **분석 결과 제시**여야 한다 — 포착한 이상 지점, 근거, 자동으로 채운 항목을 먼저 보여준다.
|
|
264
|
+
2. 그 다음에 미확보 항목만 한 번에 묻는다. 질의는 최대 1회 라운드로 끝낸다.
|
|
265
|
+
3. 사용자가 "그냥 등록해"라고 하면 미확보 항목은 `미확인`으로 채우고 진행한다.
|
|
266
|
+
4. **2.3의 최종 마스킹 확인은 어떤 경우에도 생략하지 않는다.**
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 5. 중복 확인
|
|
271
|
+
|
|
272
|
+
이슈 생성 전 동일 증상의 열린 이슈가 있는지 검색한다.
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
curl -sS -G https://api.github.com/search/issues \
|
|
276
|
+
-H "Authorization: Bearer $GH_TOKEN" \
|
|
277
|
+
--data-urlencode "q=repo:y00njinuk/makdoong2-team is:issue is:open <핵심 키워드>"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
유사 이슈가 있으면 신규 생성 대신 **코멘트 추가**를 제안하고 사용자 확인을 받는다.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
curl -sS -X POST https://api.github.com/repos/y00njinuk/makdoong2-team/issues/<number>/comments \
|
|
284
|
+
-H "Authorization: Bearer $GH_TOKEN" -d @comment-payload.json
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
기존 열린 이슈(2026-08-26 기준):
|
|
288
|
+
|
|
289
|
+
| # | 제목 요약 | 영역 | 링크 |
|
|
290
|
+
|---|---|---|---|
|
|
291
|
+
| 4 | `1_planning.jira` 단계 반복 실패 | 워크플로 단계 | https://github.com/y00njinuk/makdoong2-team/issues/4 |
|
|
292
|
+
| 1 | 서브에이전트 호출 시 조건과 무관한 프롬프트 injection | 오케스트레이션/프롬프트 | https://github.com/y00njinuk/makdoong2-team/issues/1 |
|
|
293
|
+
|
|
294
|
+
동일 단계(`1_planning.jira`)나 동일 injection 증상은 신규 이슈보다 위 이슈에 코멘트로 축적하는 것이 우선이다.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 6. 이슈 본문 템플릿
|
|
299
|
+
|
|
300
|
+
제목: 기존 이슈(#1, #4)의 규칙을 따른다. 접두 태그 없이, **어디서 무엇이 어떻게 잘못되는지**를 담은 한국어 서술형 한 문장으로 작성한다.
|
|
301
|
+
|
|
302
|
+
- 기존 예: `1_planning.jira 단계에서 반복적으로 실패가 발생하는 이슈`
|
|
303
|
+
- 기존 예: `서브에이전트 호출할 때 조건에 상관없이 불필요하게 특정 프롬프트가 인입(injection) 되는 현상`
|
|
304
|
+
|
|
305
|
+
제목에도 사내 식별자(고객사명, 사내 시스템명, 실제 Jira 키)를 넣지 않는다. 심각도·에이전트명은 본문 표에 기재한다.
|
|
306
|
+
|
|
307
|
+
~~~markdown
|
|
308
|
+
## 증상
|
|
309
|
+
<한두 문장 요약>
|
|
310
|
+
|
|
311
|
+
## 환경
|
|
312
|
+
| 항목 | 값 |
|
|
313
|
+
|---|---|
|
|
314
|
+
| OpenCode | <version> |
|
|
315
|
+
| omo | <version> |
|
|
316
|
+
| makdoong2-team | <branch>@<commit> |
|
|
317
|
+
| OS | <os> (WSL2: yes/no) |
|
|
318
|
+
| Runtime | node <ver> / bun <ver> |
|
|
319
|
+
| Provider / Model | <provider> / <model> |
|
|
320
|
+
| 발생 시각 | <ISO8601> |
|
|
321
|
+
| 세션 ID | <session-id> |
|
|
322
|
+
|
|
323
|
+
## 재현 절차
|
|
324
|
+
1.
|
|
325
|
+
2.
|
|
326
|
+
3.
|
|
327
|
+
|
|
328
|
+
## 기대 동작
|
|
329
|
+
<...>
|
|
330
|
+
|
|
331
|
+
## 실제 동작
|
|
332
|
+
<...>
|
|
333
|
+
|
|
334
|
+
## 실패 지점
|
|
335
|
+
- 관련 에이전트: <...>
|
|
336
|
+
- 단계: <1_planning.jira 등 단계명>
|
|
337
|
+
- 연동 경로: <Teams / MCP / 없음>
|
|
338
|
+
- 프롬프트 이상: <injection / 누락 / 중복 / 없음>
|
|
339
|
+
|
|
340
|
+
## 타임라인 (수집 구간: <T-2h> ~ <T>)
|
|
341
|
+
| 시각 | 지점 | 관측 내용 |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| <ts> | 마지막 정상 단계 | <...> |
|
|
344
|
+
| <ts> | **최초 이상 발생** | <...> |
|
|
345
|
+
| <ts> | 파급 | <...> |
|
|
346
|
+
|
|
347
|
+
> 추정: <원인 추정. 추정임을 명시. 근거 없으면 "미상">
|
|
348
|
+
|
|
349
|
+
## 에러 메시지
|
|
350
|
+
```
|
|
351
|
+
<마스킹된 원문>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## 재현성 / 영향 범위
|
|
355
|
+
- 재현성: <항상 / 간헐적(n회 중 m회) / 1회성>
|
|
356
|
+
- 영향 범위: <...>
|
|
357
|
+
|
|
358
|
+
## 시도한 조치
|
|
359
|
+
- <...>
|
|
360
|
+
|
|
361
|
+
## 증거
|
|
362
|
+
<details>
|
|
363
|
+
<summary>opencode.log 발췌 (라인 <start>-<end>, 마스킹 처리됨)</summary>
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
<log>
|
|
367
|
+
```
|
|
368
|
+
</details>
|
|
369
|
+
|
|
370
|
+
<details>
|
|
371
|
+
<summary>서브에이전트 프롬프트 발췌 (세션 <id>, 마스킹 처리됨)</summary>
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
<prompt>
|
|
375
|
+
```
|
|
376
|
+
</details>
|
|
377
|
+
|
|
378
|
+
> 마스킹 내역: <가린 항목 종류와 건수>
|
|
379
|
+
> 마스킹으로 생략된 정보: <있으면 기재, 없으면 "없음">
|
|
380
|
+
~~~
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 7. 이슈 생성
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
curl -sS -X POST https://api.github.com/repos/y00njinuk/makdoong2-team/issues \
|
|
388
|
+
-H "Authorization: Bearer $GH_TOKEN" \
|
|
389
|
+
-H "Accept: application/vnd.github+json" \
|
|
390
|
+
-d @issue-payload.json
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`issue-payload.json`은 `title`, `body`, `labels`를 포함한다.
|
|
394
|
+
|
|
395
|
+
**라벨은 저장소에 실재하는 것만 사용한다.** 현재 이 저장소에는 GitHub 기본 라벨 10개만 존재한다(https://github.com/y00njinuk/makdoong2-team/labels , 2026-08-26 확인).
|
|
396
|
+
|
|
397
|
+
`accessibility`, `bug`, `documentation`, `duplicate`, `enhancement`, `good first issue`, `help wanted`, `invalid`, `question`, `wontfix`
|
|
398
|
+
|
|
399
|
+
이 스킬이 생성하는 트러블슈팅 이슈는 기본적으로 `bug` 단독으로 붙인다. 재현 정보가 부족해 추가 조사가 필요한 경우에만 `question`을 병기한다. `agent:*`, `severity:*` 같은 커스텀 라벨은 존재하지 않으므로 사용하지 않는다(미존재 라벨 전달 시 422).
|
|
400
|
+
|
|
401
|
+
라벨 체계를 확장하려면 이슈 생성 전에 사용자 확인을 받고 별도로 생성한다.
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
# 현재 라벨 재확인
|
|
405
|
+
curl -sS https://api.github.com/repos/y00njinuk/makdoong2-team/labels \
|
|
406
|
+
-H "Authorization: Bearer $GH_TOKEN"
|
|
407
|
+
|
|
408
|
+
# 라벨 신규 생성 (사용자 승인 후에만)
|
|
409
|
+
curl -sS -X POST https://api.github.com/repos/y00njinuk/makdoong2-team/labels \
|
|
410
|
+
-H "Authorization: Bearer $GH_TOKEN" \
|
|
411
|
+
-d '{"name":"severity:blocker","color":"b60205"}'
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**본문은 반드시 파일(`-d @file`)로 전달한다.** 코드블록·백틱·개행이 포함되므로 셸 인라인 문자열로 전달하면 깨진다. 전송 후 페이로드 임시 파일은 삭제한다.
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
shred -u issue-payload.json gist-payload.json 2>/dev/null || rm -f issue-payload.json gist-payload.json
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 8. 완료 보고
|
|
423
|
+
|
|
424
|
+
생성 성공 시 다음만 출력한다.
|
|
425
|
+
|
|
426
|
+
- 생성된 이슈 번호와 `html_url`
|
|
427
|
+
- 이슈 목록 링크: https://github.com/y00njinuk/makdoong2-team/issues
|
|
428
|
+
- 포착한 최초 이상 발생 지점(시각·단계·에이전트)과 수집 구간
|
|
429
|
+
- 사용된 라벨
|
|
430
|
+
- 증거 첨부 방식(인라인 / Gist / 경로 참조)과 마스킹 내역 요약
|
|
431
|
+
- `미확인`으로 남긴 항목 목록
|
|
432
|
+
|
|
433
|
+
실패 시 HTTP 상태 코드와 응답의 `message` 필드를 제시하고, 이슈 본문 전체를 마크다운으로 출력해 수동 등록(https://github.com/y00njinuk/makdoong2-team/issues/new)이 가능하도록 한다.
|