makdoong2-team 2.3.1 → 3.0.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.
Files changed (44) hide show
  1. package/README.md +4 -12
  2. package/agents/makdoong2-engineer.md +1 -0
  3. package/agents/makdoong2-planner.md +17 -61
  4. package/agents/makdoong2-team-leader.md +33 -4
  5. package/agents/makdoong2-verifier.md +6 -6
  6. package/assets/makdoong2-team.schema.json +0 -18
  7. package/bin/cli.js +27 -1
  8. package/bin/cli.ts +27 -1
  9. package/dist/agent-stage-config.d.ts +1 -1
  10. package/dist/agent-stage-config.js +0 -11
  11. package/dist/apply-patch-paths.d.ts +46 -0
  12. package/dist/apply-patch-paths.js +114 -0
  13. package/dist/config.d.ts +21 -8
  14. package/dist/config.js +34 -0
  15. package/dist/model-fallback-policy.js +0 -4
  16. package/dist/opencode-plugin.js +218 -332
  17. package/dist/poll-sub-session.d.ts +2 -0
  18. package/dist/poll-sub-session.js +37 -9
  19. package/dist/stage-completion.d.ts +55 -0
  20. package/dist/stage-completion.js +91 -0
  21. package/gates/stage-analysis-verify.sh +63 -5
  22. package/gates/stage4-dev-verify.sh +6 -6
  23. package/gates/verify.sh +0 -2
  24. package/opencode.json.example +0 -1
  25. package/package.json +1 -1
  26. package/scripts/gate-policy-test.sh +15 -14
  27. package/scripts/install-lib.mjs +1 -1
  28. package/scripts/install-lib.mts +1 -1
  29. package/scripts/model-policy.mjs +0 -4
  30. package/scripts/model-policy.mts +0 -4
  31. package/scripts/release.sh +13 -1
  32. package/scripts/run-tests.mts +4 -1
  33. package/scripts/state.sh +24 -2
  34. package/skills/_lib/load-secret.sh +15 -5
  35. package/src/hooks/session-start.sh +0 -1
  36. package/stages/01-planning.md +35 -51
  37. package/stages/02-requirements.md +53 -33
  38. package/stages/04-analysis.md +2 -2
  39. package/stages/05-worktree-dev.md +15 -0
  40. package/agents/makdoong2-researcher.md +0 -68
  41. package/dist/research-fanout.d.ts +0 -133
  42. package/dist/research-fanout.js +0 -291
  43. package/gates/stage3-scope-verify.sh +0 -69
  44. package/stages/03-scope.md +0 -81
@@ -0,0 +1,114 @@
1
+ /**
2
+ * apply_patch 대상 경로 추출 — 순수 계약.
3
+ *
4
+ * ── 왜 필요한가 ──
5
+ * opencode 1.18 의 `ToolRegistry.tools` 는 모델 id 에 따라 **파일 쓰기 툴 자체를
6
+ * 바꿔 끼운다**. 1.18.23 바이너리에서 확인한 필터:
7
+ *
8
+ * const isGpt = modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4")
9
+ * if (tool.id === apply_patch) return isGpt // gpt-5 계열에만 노출
10
+ * if (tool.id === edit || tool.id === write) return !isGpt // 그 외 모델에만 노출
11
+ *
12
+ * 즉 `github-copilot/gpt-5.6-luna` 같은 세션에는 `write` · `edit` 가 **툴 스키마에
13
+ * 아예 없고** `apply_patch` 하나만 있다. "산출물은 write 툴로 만들어라" 는 stage
14
+ * 지침은 그 세션에서 물리적으로 수행 불가능한 지시가 된다 (GitHub #8 재발).
15
+ *
16
+ * ── 왜 경로 파싱인가 ──
17
+ * `apply_patch` 의 인자는 `{ patchText }` 한 덩어리라 `filePath` 인자가 없다.
18
+ * 그래서 산출물 제한 훅은 대상 파일을 몰라 **보수적으로 전부 차단**했고, 그
19
+ * 결과 planner 에게는 초안을 만들 합법적 수단이 하나도 남지 않아 워크플로가
20
+ * 구조적으로 정지했다. 패치 본문에서 대상 경로를 뽑아내면 write 와 동일한
21
+ * 정밀도로 허용/차단을 판정할 수 있다 — 우회가 아니라 같은 규칙의 확장이다.
22
+ *
23
+ * 파싱은 opencode 의 `Patch.parsePatch` 를 그대로 모사한다 (Begin/End 마커,
24
+ * `*** Add|Delete|Update File:`, `*** Move to:`). 훅과 실제 적용기가 서로 다른
25
+ * 파일을 본다고 믿게 되면 방어가 무너지므로 관대하게 파싱하지 않는다.
26
+ */
27
+ /** opencode 가 apply_patch 로 노출하는 툴 이름들 (소문자 비교 기준). */
28
+ export const APPLY_PATCH_TOOLS = new Set(["apply_patch", "applypatch", "patch"]);
29
+ export const isApplyPatchTool = (toolName) => APPLY_PATCH_TOOLS.has(toolName.toLowerCase());
30
+ /**
31
+ * opencode 의 정규화 단계: 패치 본문이 heredoc 으로 감싸여 오는 경우를 벗긴다.
32
+ * (`Patch` 모듈의 동일 정규식)
33
+ */
34
+ const stripHeredoc = (text) => text.match(/^(?:cat\s+)?<<['"]?(\w+)['"]?\s*\n([\s\S]*?)\n\1\s*$/)?.[2] ?? text;
35
+ const BEGIN = "*** Begin Patch";
36
+ const END = "*** End Patch";
37
+ /** 패치 본문에서 대상 경로를 모두 뽑는다. 형식 위반이면 실패로 보고한다. */
38
+ export const parsePatchTargets = (patchText) => {
39
+ if (typeof patchText !== "string" || patchText.trim() === "") {
40
+ return { ok: false, reason: "patch_text_empty" };
41
+ }
42
+ const lines = stripHeredoc(patchText.trim()).split("\n");
43
+ const begin = lines.findIndex((l) => l.trim() === BEGIN);
44
+ const end = lines.findIndex((l) => l.trim() === END);
45
+ if (begin === -1 || end === -1 || begin >= end) {
46
+ return { ok: false, reason: "missing_begin_end_markers" };
47
+ }
48
+ const paths = [];
49
+ for (let i = begin + 1; i < end; i++) {
50
+ const line = lines[i];
51
+ if (line.startsWith("*** Add File:")) {
52
+ const p = line.slice("*** Add File:".length).trim();
53
+ if (!p)
54
+ return { ok: false, reason: "empty_path_in_add" };
55
+ paths.push(p);
56
+ }
57
+ else if (line.startsWith("*** Delete File:")) {
58
+ const p = line.slice("*** Delete File:".length).trim();
59
+ if (!p)
60
+ return { ok: false, reason: "empty_path_in_delete" };
61
+ paths.push(p);
62
+ }
63
+ else if (line.startsWith("*** Update File:")) {
64
+ const p = line.slice("*** Update File:".length).trim();
65
+ if (!p)
66
+ return { ok: false, reason: "empty_path_in_update" };
67
+ paths.push(p);
68
+ // Update 바로 다음 줄에만 올 수 있는 이동 대상. 원본과 목적지 둘 다
69
+ // 쓰기 대상이므로 둘 다 검사 대상에 넣는다.
70
+ const next = lines[i + 1];
71
+ if (next && next.startsWith("*** Move to:")) {
72
+ const mv = next.slice("*** Move to:".length).trim();
73
+ if (!mv)
74
+ return { ok: false, reason: "empty_path_in_move" };
75
+ paths.push(mv);
76
+ i++;
77
+ }
78
+ }
79
+ }
80
+ if (paths.length === 0)
81
+ return { ok: false, reason: "no_target_path" };
82
+ return { ok: true, paths };
83
+ };
84
+ /**
85
+ * apply_patch 툴 인자에서 대상 경로를 뽑는다.
86
+ *
87
+ * 두 가지 인자 모양을 모두 받는다:
88
+ * - opencode 내장 툴: `{ patchText }` (별칭 `patch`)
89
+ * - OpenAI provider tool 경유: `{ callId, operation: { type, path, move_path } }`
90
+ */
91
+ export const extractApplyPatchPaths = (args) => {
92
+ if (!args || typeof args !== "object")
93
+ return { ok: false, reason: "args_not_object" };
94
+ const a = args;
95
+ const op = a.operation;
96
+ if (op && typeof op === "object") {
97
+ const o = op;
98
+ const paths = [];
99
+ for (const key of ["path", "move_path", "movePath"]) {
100
+ const v = o[key];
101
+ if (typeof v === "string" && v.trim() !== "")
102
+ paths.push(v.trim());
103
+ }
104
+ if (paths.length > 0)
105
+ return { ok: true, paths };
106
+ return { ok: false, reason: "operation_without_path" };
107
+ }
108
+ for (const key of ["patchText", "patch_text", "patch", "input"]) {
109
+ const v = a[key];
110
+ if (typeof v === "string" && v.trim() !== "")
111
+ return parsePatchTargets(v);
112
+ }
113
+ return { ok: false, reason: "no_patch_text" };
114
+ };
package/dist/config.d.ts CHANGED
@@ -33,13 +33,6 @@ export interface TimeoutConfig {
33
33
  stall_escalate_threshold?: number;
34
34
  }
35
35
  export declare const DEFAULT_STALL_ESCALATE_THRESHOLD = 5;
36
- export interface ResearchConfig {
37
- /** Max research sub-sessions spawned simultaneously. Clamped to [1, 6]. */
38
- max_parallel?: number;
39
- /** Per-source wall-clock budget. Shorter than a substage on purpose — a source
40
- * that cannot answer in this window is reported as failed, not waited on. */
41
- timeout_minutes?: number;
42
- }
43
36
  export type LogLevel = "silent" | "error" | "warn" | "info" | "debug" | "trace";
44
37
  export type LogMode = "stdin" | "file";
45
38
  export interface LoggingConfig {
@@ -56,7 +49,6 @@ export interface Makdoong2Config {
56
49
  threshold?: number;
57
50
  };
58
51
  timeout?: TimeoutConfig;
59
- research?: ResearchConfig;
60
52
  tmux?: TmuxConfigJson;
61
53
  worktree?: {
62
54
  extra_exclude?: string;
@@ -107,4 +99,25 @@ export declare function loadOpencodeExternalDirAllows(): string[];
107
99
  * because scripts/install-lib.mjs deploys skill directories there, not into the
108
100
  * package root. All other paths default to package-root subdirs.
109
101
  */
102
+ /**
103
+ * 플러그인이 **자기 자신을 실행하기 위해** 반드시 접근해야 하는 디렉토리들.
104
+ *
105
+ * 모든 substage 가 `bash <SCRIPTS_DIR>/state.sh` 를 호출하고, 게이트·stage spec·
106
+ * 리서치 스킬도 전부 opencode 설정 디렉토리(또는 패키지 루트) 아래에 있다.
107
+ * 이들은 **워크스페이스 밖**이므로 opencode 가 `external_directory` 승인을 묻는다
108
+ * (1.18 `ShellTool.ask` 는 bash 명령이 참조하는 디렉토리에도 발화한다).
109
+ *
110
+ * 종전에는 그 승인 근거가 설치 시 opencode.json 에 심어둔 시드 하나뿐이었다.
111
+ * 시드가 남지 않은 부분 설치에서는 서브에이전트가 **자기 상태 파일조차 읽지 못한 채**
112
+ * PERMISSION_STALL 로 죽었다 (GitHub #8). 파일 배포만 성공한 설치는 겉보기에 정상이라
113
+ * 원인 규명도 어려웠다.
114
+ *
115
+ * 그래서 플러그인이 자기 경로를 **설정과 무관하게** 스스로 허용한다. opencode.json
116
+ * 시드는 1차 방어로 그대로 두고(사용자가 직접 승인할 때의 UX), 이것이 2차 방어다 —
117
+ * 다른 하드룰들과 같은 이중 방어 구조다.
118
+ *
119
+ * 허용 범위를 넓히는 변경이 아니다: 여기 담기는 경로는 플러그인이 이미 자기 손으로
120
+ * 실행하고 있는 것들뿐이고, 워크스페이스·`/tmp`·홈 디렉토리는 포함되지 않는다.
121
+ */
122
+ export declare function pluginOwnAllowPatterns(paths?: Required<PathsConfig>): string[];
110
123
  export declare function resolvePaths(): Required<PathsConfig>;
package/dist/config.js CHANGED
@@ -196,6 +196,40 @@ export function loadOpencodeExternalDirAllows() {
196
196
  * because scripts/install-lib.mjs deploys skill directories there, not into the
197
197
  * package root. All other paths default to package-root subdirs.
198
198
  */
199
+ /**
200
+ * 플러그인이 **자기 자신을 실행하기 위해** 반드시 접근해야 하는 디렉토리들.
201
+ *
202
+ * 모든 substage 가 `bash <SCRIPTS_DIR>/state.sh` 를 호출하고, 게이트·stage spec·
203
+ * 리서치 스킬도 전부 opencode 설정 디렉토리(또는 패키지 루트) 아래에 있다.
204
+ * 이들은 **워크스페이스 밖**이므로 opencode 가 `external_directory` 승인을 묻는다
205
+ * (1.18 `ShellTool.ask` 는 bash 명령이 참조하는 디렉토리에도 발화한다).
206
+ *
207
+ * 종전에는 그 승인 근거가 설치 시 opencode.json 에 심어둔 시드 하나뿐이었다.
208
+ * 시드가 남지 않은 부분 설치에서는 서브에이전트가 **자기 상태 파일조차 읽지 못한 채**
209
+ * PERMISSION_STALL 로 죽었다 (GitHub #8). 파일 배포만 성공한 설치는 겉보기에 정상이라
210
+ * 원인 규명도 어려웠다.
211
+ *
212
+ * 그래서 플러그인이 자기 경로를 **설정과 무관하게** 스스로 허용한다. opencode.json
213
+ * 시드는 1차 방어로 그대로 두고(사용자가 직접 승인할 때의 UX), 이것이 2차 방어다 —
214
+ * 다른 하드룰들과 같은 이중 방어 구조다.
215
+ *
216
+ * 허용 범위를 넓히는 변경이 아니다: 여기 담기는 경로는 플러그인이 이미 자기 손으로
217
+ * 실행하고 있는 것들뿐이고, 워크스페이스·`/tmp`·홈 디렉토리는 포함되지 않는다.
218
+ */
219
+ export function pluginOwnAllowPatterns(paths = resolvePaths()) {
220
+ const seen = new Set();
221
+ const out = [];
222
+ for (const dir of [paths.hooks, paths.gates, paths.scripts, paths.stages, paths.skills]) {
223
+ if (typeof dir !== "string" || dir.trim() === "")
224
+ continue;
225
+ const norm = dir.replace(/\\/g, "/").replace(/\/+$/, "");
226
+ if (norm === "" || norm === "/" || seen.has(norm))
227
+ continue;
228
+ seen.add(norm);
229
+ out.push(`${norm}/**`);
230
+ }
231
+ return out;
232
+ }
199
233
  export function resolvePaths() {
200
234
  const p = loadConfig().paths ?? {};
201
235
  const root = packageRoot();
@@ -74,10 +74,6 @@ export const POLICIES = {
74
74
  fallbacks: [{ id: "github-copilot/claude-haiku-4.5", tier: "low" }],
75
75
  },
76
76
  // Research fan-out worker — one session per source, spawned in parallel.
77
- "makdoong2-researcher": {
78
- primary: { id: "github-copilot/gpt-5.6-luna", variant: "xhigh", tier: "medium" },
79
- fallbacks: [{ id: "github-copilot/claude-haiku-4.5", tier: "low" }],
80
- },
81
77
  "makdoong2-planner": {
82
78
  primary: { id: "github-copilot/gpt-5.6-luna", variant: "xhigh", tier: "medium" },
83
79
  fallbacks: [{ id: "github-copilot/claude-haiku-4.5", tier: "low" }],