makdoong2-team 1.3.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/agents/makdoong2-analyzer.md +135 -0
  4. package/agents/makdoong2-engineer.md +165 -0
  5. package/agents/makdoong2-planner.md +267 -0
  6. package/agents/makdoong2-publisher.md +481 -0
  7. package/agents/makdoong2-team-leader.md +249 -0
  8. package/agents/makdoong2-verifier.md +353 -0
  9. package/assets/makdoong2-team.default.json +46 -0
  10. package/assets/makdoong2-team.schema.json +357 -0
  11. package/bin/cli.js +404 -0
  12. package/dist/agent-stage-config.d.ts +15 -0
  13. package/dist/agent-stage-config.js +119 -0
  14. package/dist/config.d.ts +79 -0
  15. package/dist/config.js +96 -0
  16. package/dist/logger.d.ts +14 -0
  17. package/dist/logger.js +131 -0
  18. package/dist/mcp-secret-injector.d.ts +56 -0
  19. package/dist/mcp-secret-injector.js +89 -0
  20. package/dist/model-chain-cli.d.ts +1 -0
  21. package/dist/model-chain-cli.js +21 -0
  22. package/dist/model-fallback-policy.d.ts +69 -0
  23. package/dist/model-fallback-policy.js +211 -0
  24. package/dist/opencode-plugin.d.ts +8 -0
  25. package/dist/opencode-plugin.js +2457 -0
  26. package/dist/poll-sub-session.d.ts +139 -0
  27. package/dist/poll-sub-session.js +494 -0
  28. package/dist/redact-secrets.d.ts +3 -0
  29. package/dist/redact-secrets.js +68 -0
  30. package/dist/session-index.d.ts +11 -0
  31. package/dist/session-index.js +71 -0
  32. package/dist/skill-mcp-registry.d.ts +59 -0
  33. package/dist/skill-mcp-registry.js +178 -0
  34. package/dist/stall-escalation.d.ts +1 -0
  35. package/dist/stall-escalation.js +22 -0
  36. package/dist/tmux-monitor.d.ts +193 -0
  37. package/dist/tmux-monitor.js +694 -0
  38. package/dist/verdict-hash.d.ts +1 -0
  39. package/dist/verdict-hash.js +62 -0
  40. package/gates/stage-analysis-verify.sh +84 -0
  41. package/gates/stage2-requirements-verify.sh +13 -0
  42. package/gates/stage3-scope-verify.sh +45 -0
  43. package/gates/stage4-dev-post-verify.sh +64 -0
  44. package/gates/stage4-dev-verify.sh +36 -0
  45. package/gates/stage5-coverage-verify.sh +36 -0
  46. package/gates/stage5-test-verify.sh +24 -0
  47. package/gates/stage6-commit-verify.sh +41 -0
  48. package/gates/stage6-post-commit-verify.sh +131 -0
  49. package/gates/stage7-post-pr-verify.sh +53 -0
  50. package/gates/stage7-pr-verify.sh +48 -0
  51. package/gates/stage8-post-review-verify.sh +84 -0
  52. package/gates/stage8-review-verify.sh +45 -0
  53. package/gates/verify.sh +44 -0
  54. package/opencode.json.example +40 -0
  55. package/package.json +84 -0
  56. package/postinstall.mjs +56 -0
  57. package/references/commit-convention.md +130 -0
  58. package/references/jira-issue-templates.md +203 -0
  59. package/references/pr-template.md +381 -0
  60. package/scripts/config.sh +46 -0
  61. package/scripts/coverage-record.sh +67 -0
  62. package/scripts/gate-policy-test.sh +152 -0
  63. package/scripts/install-lib.mjs +1029 -0
  64. package/scripts/lint-agent-prompts.sh +74 -0
  65. package/scripts/log-event.sh +44 -0
  66. package/scripts/model-policy.mjs +183 -0
  67. package/scripts/publish-if-changed.sh +207 -0
  68. package/scripts/release.sh +276 -0
  69. package/scripts/rollback-commits.sh +35 -0
  70. package/scripts/smoke-test.mjs +194 -0
  71. package/scripts/state.sh +192 -0
  72. package/scripts/test-postinstall.mjs +141 -0
  73. package/scripts/with-fallback.sh +56 -0
  74. package/scripts/wt-sync-ignored.sh +193 -0
  75. package/skills/_lib/load-secret.sh +149 -0
  76. package/skills/bamboo-ci/SKILL.md +81 -0
  77. package/skills/bamboo-ci/run-bamboo.sh +23 -0
  78. package/skills/bitbucket-research/SKILL.md +87 -0
  79. package/skills/bitbucket-research/run-repos.sh +23 -0
  80. package/skills/confluence-research/SKILL.md +75 -0
  81. package/skills/confluence-research/run-docs.sh +23 -0
  82. package/skills/github-oss-research/SKILL.md +59 -0
  83. package/skills/jira-research/SKILL.md +75 -0
  84. package/skills/jira-research/run-works.sh +23 -0
  85. package/src/hooks/guard-bash.sh +67 -0
  86. package/src/hooks/session-start.sh +96 -0
  87. package/src/hooks/sync-state.sh +47 -0
  88. package/stages/01-jira.md +43 -0
  89. package/stages/01-planning.md +229 -0
  90. package/stages/02-requirements.md +298 -0
  91. package/stages/03-scope.md +81 -0
  92. package/stages/04-analysis.md +281 -0
  93. package/stages/05-worktree-dev.md +124 -0
  94. package/stages/06-test.md +161 -0
  95. package/stages/07-commit.md +229 -0
  96. package/stages/08-pr.md +177 -0
  97. package/stages/09-review-comments.md +277 -0
@@ -0,0 +1,62 @@
1
+ import { createHash } from "node:crypto";
2
+ export function computeVerdictHash(raw, stage) {
3
+ if (typeof raw !== "string") {
4
+ return createHash("sha256").update(`__nonstring__::${stage}`).digest("hex").slice(0, 16);
5
+ }
6
+ const trimmed = raw.trim();
7
+ const parsed = parseVerdictJSON(trimmed);
8
+ if (parsed && Array.isArray(parsed.findings)) {
9
+ const items = parsed.findings
10
+ .map((f) => {
11
+ const severity = String(f?.severity ?? "").toLowerCase();
12
+ const key = String(f?.item ?? f?.key ?? f?.name ?? "").toLowerCase();
13
+ return `${severity}::${key}`;
14
+ })
15
+ .filter((s) => s !== "::")
16
+ .sort();
17
+ const signature = JSON.stringify({
18
+ stage,
19
+ verdict: String(parsed.verdict ?? parsed.status ?? "").toUpperCase(),
20
+ items,
21
+ });
22
+ return createHash("sha256").update(signature).digest("hex").slice(0, 16);
23
+ }
24
+ return createHash("sha256").update(trimmed.slice(0, 800)).digest("hex").slice(0, 16);
25
+ }
26
+ function parseVerdictJSON(raw) {
27
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
28
+ const candidates = [];
29
+ if (fenced)
30
+ candidates.push(fenced[1]);
31
+ const firstBrace = raw.indexOf("{");
32
+ if (firstBrace >= 0) {
33
+ const tail = raw.slice(firstBrace);
34
+ let depth = 0;
35
+ let end = -1;
36
+ for (let i = 0; i < tail.length; i++) {
37
+ const ch = tail[i];
38
+ if (ch === "{")
39
+ depth++;
40
+ else if (ch === "}") {
41
+ depth--;
42
+ if (depth === 0) {
43
+ end = i + 1;
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ if (end > 0)
49
+ candidates.push(tail.slice(0, end));
50
+ }
51
+ for (const c of candidates) {
52
+ try {
53
+ const obj = JSON.parse(c.trim());
54
+ if (obj && typeof obj === "object")
55
+ return obj;
56
+ }
57
+ catch {
58
+ /* candidate not valid JSON — try next */
59
+ }
60
+ }
61
+ return null;
62
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # stage-analysis-verify.sh — 2_implementation.analysis 진입 게이트 + SKIP 판정
3
+ #
4
+ # 사용: stage-analysis-verify.sh <issue>
5
+ #
6
+ # 진입 조건:
7
+ # 1. state.json 존재
8
+ # 2. 1_planning.scope substage done=true
9
+ #
10
+ # SKIP 조건:
11
+ # 프로젝트 루트에 build tool 마커 파일이 하나도 없음
12
+ # → state.json 에 skipped=true, skip_reason, done=true 마킹 후 exit 0
13
+ #
14
+ # 의존: jq, git (state.sh 내부에서 사용)
15
+ set -euo pipefail
16
+
17
+ ISSUE="${1:?usage: stage-analysis-verify.sh <issue>}"
18
+ HERE="$(cd "$(dirname "$0")" && pwd)"
19
+ SCRIPTS="$HERE/../scripts"
20
+
21
+ fail() { echo "MAKDOONG2-GATE BLOCKED [2_implementation.analysis]: $*" >&2; exit 2; }
22
+
23
+ q() {
24
+ "$SCRIPTS/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo ""
25
+ }
26
+
27
+ # 1. state.json 존재 확인
28
+ [ -n "$(q '.issue')" ] || fail "state.json 없음. 1_planning 단계 먼저 완료 필요."
29
+
30
+ # 2. 이미 done=true 인 경우 재-dispatch 차단 (SKIP 마킹으로 done 이 이미 세팅되었을 수 있음)
31
+ ANALYSIS_DONE="$(q '.stages."2_implementation".substages."analysis".done')"
32
+ [ "$ANALYSIS_DONE" != "true" ] || fail "이미 done=true 완료됨 — auto_advance_stage 로 다음 단계 진행"
33
+
34
+ # 3. scope substage 완료 확인
35
+ SCOPE_DONE="$(q '.stages."1_planning".substages."scope".done')"
36
+ [ "$SCOPE_DONE" = "true" ] || fail "1_planning.scope 미완료 (done=$SCOPE_DONE)"
37
+
38
+ # 3. SKIP 판정: worktree 루트에서 build tool 마커 파일 존재 여부
39
+ WT="$(q '.worktree' | tr -d '"')"
40
+ [ -d "$WT" ] || WT="$(pwd)"
41
+
42
+ MARKERS=(
43
+ "package.json"
44
+ "build.gradle"
45
+ "build.gradle.kts"
46
+ "pom.xml"
47
+ "Cargo.toml"
48
+ "go.mod"
49
+ "pyproject.toml"
50
+ "setup.py"
51
+ "requirements.txt"
52
+ "Gemfile"
53
+ "mix.exs"
54
+ "build.sbt"
55
+ "composer.json"
56
+ "Package.swift"
57
+ "Makefile"
58
+ "CMakeLists.txt"
59
+ )
60
+
61
+ FOUND=""
62
+ for m in "${MARKERS[@]}"; do
63
+ if [ -f "$WT/$m" ]; then FOUND="$m"; break; fi
64
+ done
65
+
66
+ # .csproj 는 glob 확인
67
+ if [ -z "$FOUND" ]; then
68
+ if compgen -G "$WT/*.csproj" > /dev/null 2>&1; then FOUND=".csproj"; fi
69
+ fi
70
+
71
+ if [ -z "$FOUND" ]; then
72
+ # SKIP: state.json 마킹 후 exit 0
73
+ "$SCRIPTS/state.sh" set "$ISSUE" \
74
+ '.stages."2_implementation".substages."analysis".skipped' 'true' > /dev/null
75
+ "$SCRIPTS/state.sh" set "$ISSUE" \
76
+ '.stages."2_implementation".substages."analysis".skip_reason' \
77
+ '"no_build_tool_marker"' > /dev/null
78
+ "$SCRIPTS/state.sh" set "$ISSUE" \
79
+ '.stages."2_implementation".substages."analysis".done' 'true' > /dev/null
80
+ echo "MAKDOONG2-GATE SKIP: 2_implementation.analysis (no build tool marker in $WT)"
81
+ exit 0
82
+ fi
83
+
84
+ echo "MAKDOONG2-GATE OK: 2_implementation.analysis (marker=$FOUND)"
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ # stage2-requirements-verify.sh <issue> — 2단계(요구사항) 진입 게이트
3
+ # 의존: jq
4
+ set -euo pipefail
5
+ HERE="$(cd "$(dirname "$0")" && pwd)"
6
+ ISSUE="${1:?usage: stage2-requirements-verify.sh <issue>}"
7
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
8
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [1_planning.requirements]: $1" >&2; exit 2; }
9
+ [ -f "$P" ] || fail "state.json 없음 — jira substage부터 시작하라"
10
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
11
+ [ "$(q '.stages."1_planning".substages."requirements".done')" != "true" ] || fail "이미 done=true 완료됨 — auto_advance_stage 로 다음 단계 진행"
12
+ [ "$(q '.stages."1_planning".substages."jira".done')" = "true" ] || fail "jira substage 미완료"
13
+ echo "MAKDOONG2-GATE OK: 1_planning.requirements"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env bash
2
+ # stage3-scope-verify.sh <issue> — 3단계(범위 확정) 진입 게이트
3
+ set -euo pipefail
4
+ HERE="$(cd "$(dirname "$0")" && pwd)"
5
+ ISSUE="${1:?usage: stage3-scope-verify.sh <issue>}"
6
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
7
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [1_planning.scope]: $1" >&2; exit 2; }
8
+ [ -f "$P" ] || fail "state.json 없음 — jira substage부터 시작하라"
9
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
10
+ [ "$(q '.stages."1_planning".substages."scope".done')" != "true" ] || fail "이미 done=true 완료됨 — auto_advance_stage 로 다음 단계 진행"
11
+ [ "$(q '.stages."1_planning".substages."requirements".done')" = "true" ] || fail "requirements substage 미완료"
12
+
13
+ if [ "$(q '.policy.auto_approve."1_planning.requirements"')" != "true" ]; then
14
+ [ "$(q '.stages."1_planning".substages."requirements".approved_by_user')" = "true" ] \
15
+ || fail "requirements substage 사용자 승인 없음 (또는 .policy.auto_approve.\"1_planning.requirements\" 미설정)"
16
+ if [ "$(q '.stages."1_planning".substages."requirements".verification_pending')" = "true" ]; then
17
+ fail "requirements substage 검증 대기 중 (verification_pending) — 사용자 승인 후 approved_by_user를 기록하라"
18
+ fi
19
+ fi
20
+
21
+ if [ "$(q '.stages."1_planning".substages."requirements".interview_required')" = "true" ]; then
22
+ [ "$(q '.stages."1_planning".substages."requirements".interview_completed')" = "true" ] \
23
+ || fail "requirements substage 인터뷰 미완료 — 모든 미결 항목 해소 후 interview_completed=true 기록 필요"
24
+ fi
25
+
26
+ # --- 요구사항 품질 게이트 (조건부 — 마커 존재 시에만 검사, 구형 state 호환) ---
27
+ # 1) Ambiguity Score 수렴: 기록된 값이 0.2 초과면 요구사항 미수렴 (stages/02-requirements.md §2-3-2b)
28
+ AMB="$(q '.stages."1_planning".substages."requirements".ambiguity_score')"
29
+ if [ "$AMB" != "__MISSING__" ] && [ "$AMB" != "null" ] && [ -n "$AMB" ]; then
30
+ awk -v a="$AMB" 'BEGIN { exit (a + 0 <= 0.2) ? 0 : 1 }' \
31
+ || fail "ambiguity_score=$AMB > 0.2 — 요구사항 미수렴. 인터뷰로 미결 항목 해소 후 재산정하라"
32
+ fi
33
+
34
+ # 2) 명세 동결(spec drift) 검증: spec_hash 기록 시 draft 파일 해시 재계산 일치 필요 (§2-4a)
35
+ SPEC_HASH="$(q '.stages."1_planning".substages."requirements".spec_hash')"
36
+ if [ "$SPEC_HASH" != "__MISSING__" ] && [ "$SPEC_HASH" != "null" ] && [ -n "$SPEC_HASH" ]; then
37
+ DRAFT="$(q '.stages."1_planning".substages."requirements".draft_path')"
38
+ ROOT="$("$HERE/../scripts/state.sh" root)"
39
+ { [ "$DRAFT" != "__MISSING__" ] && [ "$DRAFT" != "null" ] && [ -f "$ROOT/$DRAFT" ]; } \
40
+ || fail "spec_hash 기록됨 그러나 확정 명세 파일 없음 (draft_path=$DRAFT)"
41
+ ACTUAL="$(sha256sum "$ROOT/$DRAFT" | cut -d' ' -f1)"
42
+ [ "$ACTUAL" = "$SPEC_HASH" ] \
43
+ || fail "확정 명세 무단 변경 감지 (spec drift) — 동결 후 변경은 사용자 재승인 + spec_hash 재기록 절차만 허용 (stages/02-requirements.md §2-4a)"
44
+ fi
45
+ echo "MAKDOONG2-GATE OK: 1_planning.scope"
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # stage4-dev-post-verify.sh <issue> — 4단계(개발) 종료 게이트.
3
+ # ─────────────────────────────────────────────────────────
4
+ # 검증 항목:
5
+ # 1. worktree 가 존재하는가
6
+ # 2. dev-written-files.txt 에 기록된 모든 파일이 staging(index) 혹은 HEAD tree 에 존재하는가
7
+ # 3. .gitignore 를 존중한 untracked 파일이 0인가
8
+ #
9
+ # 불변식: 3_delivery.commit 는 untracked 를 자동 제외하므로,
10
+ # 본 게이트를 통과한 파일만이 커밋 대상에 진입한다.
11
+ set -euo pipefail
12
+ HERE="$(cd "$(dirname "$0")" && pwd)"
13
+ ISSUE="${1:?usage: stage4-dev-post-verify.sh <issue>}"
14
+
15
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [2_implementation.dev_post]: $1" >&2; exit 2; }
16
+
17
+ WT="$("$HERE/../scripts/state.sh" get "$ISSUE" '.worktree' 2>/dev/null | tr -d '"' || echo "")"
18
+ [ -n "$WT" ] && [ -d "$WT" ] || fail "worktree 경로 부재 또는 접근 불가: $WT"
19
+
20
+ TRACK="$WT/.makdoong2-team/$ISSUE/dev-written-files.txt"
21
+
22
+ MISSING=""
23
+ if [ -f "$TRACK" ]; then
24
+ while IFS= read -r F; do
25
+ [ -z "$F" ] && continue
26
+ if git -C "$WT" diff --cached --name-only | grep -qxF "$F"; then
27
+ continue
28
+ fi
29
+ if git -C "$WT" ls-tree -r --name-only HEAD 2>/dev/null | grep -qxF "$F"; then
30
+ continue
31
+ fi
32
+ if [ ! -e "$WT/$F" ]; then
33
+ continue
34
+ fi
35
+ MISSING="${MISSING}
36
+ - $F"
37
+ done < <(sort -u "$TRACK")
38
+ fi
39
+
40
+ UNTRACKED="$(git -C "$WT" ls-files --others --exclude-standard 2>/dev/null || true)"
41
+
42
+ if [ -n "$MISSING" ] || [ -n "$UNTRACKED" ]; then
43
+ MSG="Engineer 가 편집한 파일이 staging area 에 포함되지 않았다."
44
+ MSG="${MSG}
45
+ Publisher(3_delivery.commit) 가 untracked 파일을 자동 제외하므로 여기서 통과하지 못하면 신규 소스코드가 커밋되지 않는다."
46
+ if [ -n "$MISSING" ] && [ -f "$TRACK" ]; then
47
+ MSG="${MSG}
48
+
49
+ [dev-written-files.txt 에 기록됐지만 staged 아님]${MISSING}"
50
+ fi
51
+ if [ -n "$UNTRACKED" ]; then
52
+ MSG="${MSG}
53
+
54
+ [.gitignore 존중 untracked 파일 (안전망 감지)]
55
+ $(echo "$UNTRACKED" | sed 's/^/ - /')"
56
+ fi
57
+ MSG="${MSG}
58
+
59
+ → 조치: worktree($WT) 에서 각 파일에 'git add -- <파일>' 실행 후 dev.done 재기록.
60
+ → 자동화 필요: tool.execute.after 훅이 auto git add 를 수행해야 하는데 미동작. 훅 상태 점검 필요."
61
+ fail "$MSG"
62
+ fi
63
+
64
+ echo "MAKDOONG2-GATE OK: 2_implementation.dev_post"
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # stage4-dev-verify.sh <issue> — 4단계(개발) 진입 게이트
3
+ set -euo pipefail
4
+ HERE="$(cd "$(dirname "$0")" && pwd)"
5
+ ISSUE="${1:?usage: stage4-dev-verify.sh <issue>}"
6
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
7
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [2_implementation.dev]: $1" >&2; exit 2; }
8
+ [ -f "$P" ] || fail "state.json 없음 — planning phase부터 시작하라"
9
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
10
+ assert_worktree_sibling(){
11
+ local wt="$1" main pwt pmain
12
+ [ -d "$wt" ] || fail "worktree 경로가 존재하지 않음: $wt"
13
+ main="$(git -C "$wt" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
14
+ [ -n "$main" ] || fail "메인 repo 식별 실패"
15
+ [ "$wt" != "$main" ] || fail "worktree가 메인 repo와 동일 경로 — dev 단계는 별도 worktree 필요 (메인: $main)"
16
+ pwt="$(cd "$(dirname "$wt")" && pwd -P)"
17
+ pmain="$(cd "$(dirname "$main")" && pwd -P)"
18
+ [ "$pwt" = "$pmain" ] || fail "worktree가 메인 repo의 형제 디렉토리가 아님: $wt (메인: $main)"
19
+ }
20
+ [ "$(q '.stages."2_implementation".substages."dev".done')" != "true" ] || fail "이미 done=true 완료됨 — auto_advance_stage 로 다음 단계 진행"
21
+ [ "$(q '.stages."1_planning".substages."scope".done')" = "true" ] || fail "scope substage 미완료"
22
+ # analysis substage 는 게이트가 SKIP 시 done=true 로 마킹하므로 skipped 여부와 무관하게 done=true 검사만 하면 된다.
23
+ [ "$(q '.stages."2_implementation".substages."analysis".done')" = "true" ] || fail "analysis substage 미완료 (skipped 도 done=true 로 처리됨)"
24
+
25
+ WT="$(q '.worktree')"
26
+ [ -n "$WT" ] && [ "$WT" != "__MISSING__" ] || fail "worktree 경로 미설정 — state.json의 .worktree 필드 확인"
27
+ assert_worktree_sibling "$WT"
28
+
29
+ if [ "$(q '.policy.auto_approve."1_planning.scope"')" != "true" ]; then
30
+ [ "$(q '.stages."1_planning".substages."scope".approved_by_user')" = "true" ] \
31
+ || fail "scope substage 사용자 승인 없음 (또는 .policy.auto_approve.\"1_planning.scope\" 미설정)"
32
+ if [ "$(q '.stages."1_planning".substages."scope".verification_pending')" = "true" ]; then
33
+ fail "scope substage 검증 대기 중 (verification_pending) — 사용자 승인 후 approved_by_user를 기록하라"
34
+ fi
35
+ fi
36
+ echo "MAKDOONG2-GATE OK: 2_implementation.dev"
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # stage5-coverage-verify.sh <issue>
3
+ # 수정된 코드의 테스트 커버리지가 임계값 이상인지 검증한다.
4
+ # 임계값은 makdoong2-team.json 의 coverage.threshold 로 조정 (기본: 95).
5
+ set -euo pipefail
6
+ HERE="$(cd "$(dirname "$0")" && pwd)"
7
+ ISSUE="${1:?usage: stage5-coverage-verify.sh <issue>}"
8
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
9
+ THRESHOLD="$("$HERE/../scripts/config.sh" get coverage.threshold 95)"
10
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [2_implementation.coverage]: $1" >&2; exit 2; }
11
+ [ -f "$P" ] || fail "state.json 없음"
12
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
13
+
14
+ COV="$(q '.stages."2_implementation".substages."test".coverage')"
15
+ ATTEMPT="$(q '.stages."2_implementation".substages."test".coverage_attempt')"
16
+ [ "$ATTEMPT" = "__MISSING__" ] && ATTEMPT=1
17
+
18
+ case "$COV" in
19
+ pass)
20
+ PCT="$(q '.stages."2_implementation".substages."test".coverage_pct')"
21
+ echo "MAKDOONG2-GATE OK: 2_implementation.coverage (${PCT}% >= ${THRESHOLD}%, 시도 ${ATTEMPT}/2)"
22
+ ;;
23
+ exempt)
24
+ echo "MAKDOONG2-GATE OK: 2_implementation.coverage (exempt — 사용자 승인됨, 시도 ${ATTEMPT}/2)"
25
+ ;;
26
+ fail)
27
+ PCT="$(q '.stages."2_implementation".substages."test".coverage_pct')"
28
+ fail "커버리지 미달 (${PCT}% < ${THRESHOLD}%, 시도 ${ATTEMPT}/2, 최대 2라운드) — 테스트를 추가하거나 사용자 승인 후 exempt 처리"
29
+ ;;
30
+ __MISSING__)
31
+ fail "커버리지 미측정 — test substage에서 커버리지를 측정하고 state.json에 기록하라"
32
+ ;;
33
+ *)
34
+ fail "coverage 상태 불명확 ($COV) — pass / fail / exempt 중 하나여야 함"
35
+ ;;
36
+ esac
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # stage5-test-verify.sh <issue> — 5단계(테스트) 진입 게이트
3
+ set -euo pipefail
4
+ HERE="$(cd "$(dirname "$0")" && pwd)"
5
+ ISSUE="${1:?usage: stage5-test-verify.sh <issue>}"
6
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
7
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [2_implementation.test]: $1" >&2; exit 2; }
8
+ [ -f "$P" ] || fail "state.json 없음 — planning phase부터 시작하라"
9
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
10
+ assert_worktree_sibling(){
11
+ local wt="$1" main pwt pmain
12
+ [ -d "$wt" ] || fail "worktree 경로가 존재하지 않음: $wt"
13
+ main="$(git -C "$wt" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
14
+ [ -n "$main" ] || fail "메인 repo 식별 실패"
15
+ [ "$wt" != "$main" ] || fail "worktree가 메인 repo와 동일 경로 — 별도 worktree 필요"
16
+ pwt="$(cd "$(dirname "$wt")" && pwd -P)"
17
+ pmain="$(cd "$(dirname "$main")" && pwd -P)"
18
+ [ "$pwt" = "$pmain" ] || fail "worktree가 메인 repo의 형제 디렉토리가 아님: $wt (메인: $main)"
19
+ }
20
+ [ "$(q '.stages."2_implementation".substages."test".done')" != "true" ] || fail "이미 done=true 완료됨 — auto_advance_stage 로 다음 단계 진행"
21
+ [ "$(q '.stages."2_implementation".substages."dev".done')" = "true" ] || fail "dev substage 미완료"
22
+ WT="$(q '.worktree')"
23
+ assert_worktree_sibling "$WT"
24
+ echo "MAKDOONG2-GATE OK: 2_implementation.test"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # stage6-commit-verify.sh <issue> — 6단계(커밋) 진입 게이트
3
+ set -euo pipefail
4
+ HERE="$(cd "$(dirname "$0")" && pwd)"
5
+ ISSUE="${1:?usage: stage6-commit-verify.sh <issue>}"
6
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
7
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [3_delivery.commit]: $1" >&2; exit 2; }
8
+ [ -f "$P" ] || fail "state.json 없음 — planning phase부터 시작하라"
9
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
10
+ U="$(q '.stages."2_implementation".substages."test".unit')"
11
+ case "$U" in
12
+ pass|skip) : ;;
13
+ fail) fail "단위 테스트 실패 — dev substage로 복귀해 수정하라" ;;
14
+ *) fail "단위 테스트 결과 미기록(unit=$U) — test substage 미완료" ;;
15
+ esac
16
+ I="$(q '.stages."2_implementation".substages."test".integration')"
17
+ case "$I" in
18
+ pass|skip) : ;;
19
+ fail) fail "통합 테스트 실패 — dev substage로 복귀해 수정하라" ;;
20
+ *) fail "통합 테스트 결과 미기록(integration=$I) — test substage 미완료" ;;
21
+ esac
22
+ "$HERE/stage5-coverage-verify.sh" "$ISSUE"
23
+
24
+ if [ "$(jq -r '.policy.auto_approve."3_delivery.commit"' "$P" 2>/dev/null)" = "false" ]; then
25
+ # [Check 1] verification_pending=true: 보고서 생성 완료, 사용자 승인 대기 중 → BLOCK
26
+ # (report 존재 여부보다 먼저 확인 — publisher가 보고서를 생성하고 pending을 세팅한 상태)
27
+ if [ "$(q '.stages."3_delivery".substages."commit".verification_pending')" = "true" ]; then
28
+ fail "major 이슈 — 커밋 승인 대기 중(verification_pending) — 사용자 승인 후 approved_by_user=true 기록 + verification_pending=false 해소"
29
+ fi
30
+ # [Check 2] 보고서가 있는데 미승인 → BLOCK
31
+ # (report 없음 = 최초 dispatch 시점 → PASS하여 publisher가 보고서를 생성할 수 있도록 허용)
32
+ REPORT="$(dirname "$P")/change-report.md"
33
+ if [ -f "$REPORT" ]; then
34
+ [ "$(q '.stages."3_delivery".substages."commit".approved_by_user')" = "true" ] \
35
+ || fail "major 이슈 — 최종 커밋 사용자 미승인. 보고서 검토 후 .stages.\"3_delivery\".substages.\"commit\".approved_by_user=true를 기록하라"
36
+ fi
37
+ # [Check 3 — implicit] report 없음 + pending 없음 = 초기 상태 → PASS
38
+ # publisher 첫 dispatch가 진입하여 change-report.md를 생성한다.
39
+ fi
40
+
41
+ echo "MAKDOONG2-GATE OK: 3_delivery.commit"
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ # stage6-post-commit-verify.sh <issue> — 6단계 커밋 완료 후 검증 게이트
3
+ # ─────────────────────────────────────────────────────────
4
+ # 검증 항목:
5
+ # 1. worktree 가 메인 repo 의 형제 디렉토리인지
6
+ # 2. base_sha 기록 + HEAD 조상 여부
7
+ # 3. base_sha..HEAD 커밋 수 > 0
8
+ # 4. 각 커밋이 정확히 1개 파일만 포함 (1 파일 = 1 commit 원칙)
9
+ # 5. 각 커밋 subject 에 결합어 (and / & / + / 및 / 그리고) 없음
10
+ # 6. 각 커밋 subject 가 Git Commit Guidelines 형식 준수
11
+ # - 형식: <Type>: <이슈키> - <요약> (제목 50자 이내, 마침표 없음)
12
+ # - Type: Feat/Fix/Chore/Refactor/Docs/Style/Test/Perf/Ci/Build/Revert
13
+ # - 이슈키 = 인자로 받은 $ISSUE 와 정확히 일치
14
+ # 7. 각 커밋 본문에 이슈 참조 마커 ([RV] <이슈키>) 포함
15
+ # 8. atomic_review.all_atomic == true + one_file_per_commit == true
16
+ # 9. atomic_review.count_commits == 실제 커밋 수
17
+ set -euo pipefail
18
+ HERE="$(cd "$(dirname "$0")" && pwd)"
19
+ ISSUE="${1:?usage: stage6-post-commit-verify.sh <issue>}"
20
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
21
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [3_delivery.commit_post]: $1" >&2; exit 2; }
22
+ [ -f "$P" ] || fail "state.json 없음 — planning phase부터 시작하라"
23
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
24
+
25
+ assert_worktree_sibling(){
26
+ local wt="$1" main pwt pmain
27
+ [ -d "$wt" ] || fail "worktree 경로가 존재하지 않음: $wt"
28
+ main="$(git -C "$wt" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
29
+ [ -n "$main" ] || fail "메인 repo 식별 실패"
30
+ [ "$wt" != "$main" ] || fail "worktree가 메인 repo와 동일 경로 — 별도 worktree 필요"
31
+ pwt="$(cd "$(dirname "$wt")" && pwd -P)"
32
+ pmain="$(cd "$(dirname "$main")" && pwd -P)"
33
+ [ "$pwt" = "$pmain" ] || fail "worktree가 메인 repo의 형제 디렉토리가 아님: $wt (메인: $main)"
34
+ }
35
+
36
+ WT="$(q '.worktree')"
37
+ assert_worktree_sibling "$WT"
38
+
39
+ # ─── §2. base_sha ───
40
+ BASE="$(q '.stages."3_delivery".substages."commit".base_sha')"
41
+ { [ -n "$BASE" ] && [ "$BASE" != "null" ] && [ "$BASE" != "__MISSING__" ]; } || \
42
+ fail "commit substage base_sha 미기록 — commit substage 시작 시 git rev-parse HEAD를 base_sha로 기록해야 함"
43
+ git -C "$WT" merge-base --is-ancestor "$BASE" HEAD 2>/dev/null || \
44
+ fail "base_sha($BASE)가 현재 HEAD의 조상이 아님 — base_sha 또는 분기 상태 확인 필요"
45
+
46
+ # ─── §3. 커밋 수 > 0 ───
47
+ N=$(git -C "$WT" rev-list --count "$BASE..HEAD" 2>/dev/null || echo 0)
48
+ [ "$N" -gt 0 ] || fail "base_sha와 HEAD가 동일 — commit substage에서 만든 커밋이 없음"
49
+
50
+ # ─── §4. 1 파일 = 1 commit 원칙 (hardrule) ───
51
+ MULTI_FILE_VIOLATIONS=""
52
+ for SHA in $(git -C "$WT" rev-list "$BASE..HEAD"); do
53
+ NF=$(git -C "$WT" show --name-only --pretty="" "$SHA" | grep -c . || true)
54
+ if [ "$NF" -ne 1 ]; then
55
+ SUBJ=$(git -C "$WT" log -1 --format='%s' "$SHA")
56
+ MULTI_FILE_VIOLATIONS="${MULTI_FILE_VIOLATIONS}
57
+ - ${SHA:0:7} '${SUBJ}' 파일수=${NF} (기대: 1)"
58
+ fi
59
+ done
60
+ [ -z "$MULTI_FILE_VIOLATIONS" ] || \
61
+ fail "1 파일 = 1 commit 원칙 위반. 각 커밋은 정확히 1개 파일만 포함해야 함:${MULTI_FILE_VIOLATIONS}
62
+ → 조치: 'bash <SCRIPTS_DIR>/rollback-commits.sh $ISSUE' 후 파일별로 재커밋"
63
+
64
+ # ─── §5. 결합어 검사 ───
65
+ BAD=$(git -C "$WT" log --format='%s' "$BASE..HEAD" | grep -cE ' and | & | \+ | 및 | 그리고 ' || true)
66
+ [ "$BAD" -eq 0 ] || \
67
+ fail "커밋 subject에 결합어(and / & / + / 및 / 그리고) ${BAD}건 발견 — 1 commit = 1 change 위반 가능. git log로 확인 후 재구성하라"
68
+
69
+ # ─── §6. 커밋 메시지 형식 재검증 ───
70
+ # 형식: ^(Feat|Fix|Chore|Refactor|Docs|Style|Test|Perf|Ci|Build|Revert): <ISSUE> - <요약>$
71
+ # 제목 50자 이내, 마침표 없음
72
+ MSG_FMT_RE="^(Feat|Fix|Chore|Refactor|Docs|Style|Test|Perf|Ci|Build|Revert): ${ISSUE} - .+$"
73
+ MSG_VIOLATIONS=""
74
+ while IFS= read -r line; do
75
+ # line 형식: <SHA_short><TAB><subject>
76
+ SHA="${line%%$'\t'*}"
77
+ SUBJ="${line#*$'\t'}"
78
+ if ! [[ "$SUBJ" =~ $MSG_FMT_RE ]]; then
79
+ MSG_VIOLATIONS="${MSG_VIOLATIONS}
80
+ - ${SHA} '${SUBJ}' 형식 불일치 (기대: <Type>: ${ISSUE} - <요약>)"
81
+ continue
82
+ fi
83
+ # wc -m 은 로케일 기준 문자 수 (bash ${#SUBJ} 는 UTF-8 바이트 수로 한글 3배).
84
+ SUBJ_LEN=$(printf '%s' "$SUBJ" | wc -m)
85
+ if [ "$SUBJ_LEN" -gt 50 ]; then
86
+ MSG_VIOLATIONS="${MSG_VIOLATIONS}
87
+ - ${SHA} '${SUBJ}' 제목 ${SUBJ_LEN}자 초과 (최대 50자)"
88
+ fi
89
+ # 마침표 금지
90
+ if [[ "$SUBJ" == *. ]]; then
91
+ MSG_VIOLATIONS="${MSG_VIOLATIONS}
92
+ - ${SHA} '${SUBJ}' 제목 끝에 마침표 금지"
93
+ fi
94
+ done < <(git -C "$WT" log --format='%h%x09%s' "$BASE..HEAD")
95
+
96
+ [ -z "$MSG_VIOLATIONS" ] || \
97
+ fail "커밋 메시지 형식 위반:${MSG_VIOLATIONS}
98
+ → 조치: git commit --amend 로 수정하거나 rollback-commits.sh 후 재작성"
99
+
100
+ # ─── §7. 이슈 참조 마커 확인 ───
101
+ # 커밋 body 어딘가에 [RV] <이슈키> 마커가 포함되어야 한다.
102
+ # body 가 빈 커밋은 허용하되 (선택), body 가 있다면 마커 필수.
103
+ KW_RE='\[RV\][[:space:]]+[A-Z]+-[0-9]+'
104
+ KW_VIOLATIONS=""
105
+ for SHA in $(git -C "$WT" rev-list "$BASE..HEAD"); do
106
+ BODY=$(git -C "$WT" log -1 --format='%b' "$SHA")
107
+ if [ -n "$(printf '%s' "$BODY" | tr -d '[:space:]')" ]; then
108
+ if ! printf '%s' "$BODY" | grep -qE "$KW_RE"; then
109
+ SUBJ=$(git -C "$WT" log -1 --format='%s' "$SHA")
110
+ KW_VIOLATIONS="${KW_VIOLATIONS}
111
+ - ${SHA:0:7} '${SUBJ}' 본문에 이슈 참조 마커 없음 ([RV] ${ISSUE} 필요)"
112
+ fi
113
+ fi
114
+ done
115
+ [ -z "$KW_VIOLATIONS" ] || \
116
+ fail "커밋 본문에 이슈 참조 마커 누락:${KW_VIOLATIONS}"
117
+
118
+ # ─── §8. atomic_review 마커 ───
119
+ A="$(q '.stages."3_delivery".substages."commit".atomic_review.all_atomic')"
120
+ [ "$A" = "true" ] || \
121
+ fail "atomic_review.all_atomic 미기록 또는 false — 각 커밋이 단일 변경인지 검토 후 true로 기록해야 함"
122
+ OFPC="$(q '.stages."3_delivery".substages."commit".atomic_review.one_file_per_commit')"
123
+ [ "$OFPC" = "true" ] || \
124
+ fail "atomic_review.one_file_per_commit 미기록 또는 false — 1 파일/commit 자체 검토 후 true로 기록해야 함"
125
+
126
+ # ─── §9. count_commits 일치 ───
127
+ C="$(q '.stages."3_delivery".substages."commit".atomic_review.count_commits')"
128
+ [ "$C" = "$N" ] || \
129
+ fail "atomic_review.count_commits=$C 와 실제 커밋 수 $N 불일치 — 다시 기록하라"
130
+
131
+ echo "MAKDOONG2-GATE OK: 3_delivery.commit_post (${N}개 커밋 모두 1파일/commit + 메시지 형식 통과)"
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bash
2
+ # stage7-post-pr-verify.sh <issue> — 7단계(PR 생성) post-execution 게이트
3
+ #
4
+ # 원칙: entry gate 는 전제조건, post-verify 는 완료 조건. publisher 가 §2-5 마지막에
5
+ # .done=true 를 기록하기 직전 본 스크립트를 호출한다. 실패 시 exit 2 로 publisher
6
+ # 재작업 loop 를 유도한다.
7
+ #
8
+ # 검사 항목 (완료조건):
9
+ # 1) origin/<current-branch> 원격 브랜치 존재 (git push 성공 여부)
10
+ # 2) .stages.3_delivery.substages.pr.draft_url 이 HTTPS URL 문자열
11
+ # 3) .stages.3_delivery.substages.pr.body_validation 3항목 모두 true
12
+ # 4) reviewer 마커 (reviewer_added=true XOR reviewer_self_skipped=true; 상호 배타)
13
+ # 5) .stages.3_delivery.substages.pr.done == true (publisher 자기선언)
14
+ set -euo pipefail
15
+ HERE="$(cd "$(dirname "$0")" && pwd)"
16
+ ISSUE="${1:?usage: stage7-post-pr-verify.sh <issue>}"
17
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
18
+ fail(){ echo "MAKDOONG2-POSTGATE BLOCKED [3_delivery.pr_post]: $1" >&2; exit 2; }
19
+ [ -f "$P" ] || fail "state.json 없음"
20
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
21
+
22
+ WT="$(q '.worktree')"
23
+ [ -d "$WT" ] || fail "worktree 경로 없음: $WT"
24
+
25
+ BR="$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
26
+ [ -n "$BR" ] || fail "현재 브랜치 확인 불가"
27
+ git -C "$WT" rev-parse --verify "origin/$BR" >/dev/null 2>&1 \
28
+ || fail "remote 에 push 되지 않음 (origin/$BR 없음) — publisher 가 §2-4 에서 git push -u origin HEAD 를 실행했는지 확인"
29
+
30
+ URL="$(q '.stages."3_delivery".substages."pr".draft_url')"
31
+ case "$URL" in
32
+ http://*|https://*) : ;;
33
+ *) fail "draft_url 이 유효한 HTTPS URL 문자열 아님: $URL" ;;
34
+ esac
35
+
36
+ for k in no_orphan_scenarios template_match section_content_match; do
37
+ v="$(q ".stages.\"3_delivery\".substages.\"pr\".body_validation.$k")"
38
+ [ "$v" = "true" ] || fail "body_validation.$k=$v — 3항목 모두 true 여야 함"
39
+ done
40
+
41
+ RA="$(q '.stages."3_delivery".substages."pr".reviewer_added')"
42
+ RS="$(q '.stages."3_delivery".substages."pr".reviewer_self_skipped')"
43
+ if [ "$RA" = "true" ] && [ "$RS" = "true" ]; then
44
+ fail "reviewer 마커 상호 배타 위반 — reviewer_added 와 reviewer_self_skipped 가 동시에 true"
45
+ fi
46
+ if [ "$RA" != "true" ] && [ "$RS" != "true" ]; then
47
+ fail "reviewer 마커 미기록 — reviewer_added=true 또는 reviewer_self_skipped=true 중 하나 필수"
48
+ fi
49
+
50
+ D="$(q '.stages."3_delivery".substages."pr".done')"
51
+ [ "$D" = "true" ] || fail "pr.done 이 true 아님(=$D) — publisher 가 §2-5 완료 마킹을 누락"
52
+
53
+ echo "MAKDOONG2-POSTGATE OK: 3_delivery.pr_post"
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # stage7-pr-verify.sh <issue> — 7단계(PR 생성) 진입 게이트
3
+ #
4
+ # 원칙: entry gate 는 substage 진입 "전제조건" 만 검사한다.
5
+ # 완료 조건(원격 push 여부, PR URL 존재, reviewer 등)은 publisher 가 substage 수행 후
6
+ # stage7-post-pr-verify.sh + verifier 에서 검증된다.
7
+ #
8
+ # 검사 항목 (전제조건):
9
+ # 1) state.json 존재
10
+ # 2) 3_delivery.commit substage 완료
11
+ # 3) worktree 가 메인 repo 의 형제 디렉토리로 존재
12
+ # 4) worktree 에 uncommitted 변경 없음 (tracked 파일 기준; untracked 는 무시)
13
+ # 5) 현재 브랜치 확인 가능
14
+ #
15
+ # 제거된 조건 (완료조건 → post-verify 로 이동):
16
+ # * origin/<BR> 원격 브랜치 존재 여부 — publisher 가 §2-4 에서 git push 를 수행하므로
17
+ # first-entry 시점에는 존재할 수 없음. chicken-and-egg 방지를 위해 post-verify 로 이관.
18
+ set -euo pipefail
19
+ HERE="$(cd "$(dirname "$0")" && pwd)"
20
+ ISSUE="${1:?usage: stage7-pr-verify.sh <issue>}"
21
+ P="$("$HERE/../scripts/state.sh" root)/.makdoong2-team/$ISSUE/state.json"
22
+ fail(){ echo "MAKDOONG2-GATE BLOCKED [3_delivery.pr]: $1" >&2; exit 2; }
23
+ [ -f "$P" ] || fail "state.json 없음 — planning phase부터 시작하라"
24
+ q(){ "$HERE/../scripts/state.sh" get "$ISSUE" "$1" 2>/dev/null || echo "__MISSING__"; }
25
+ assert_worktree_sibling(){
26
+ local wt="$1" main pwt pmain
27
+ [ -d "$wt" ] || fail "worktree 경로가 존재하지 않음: $wt"
28
+ main="$(git -C "$wt" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2; exit}')"
29
+ [ -n "$main" ] || fail "메인 repo 식별 실패"
30
+ [ "$wt" != "$main" ] || fail "worktree가 메인 repo와 동일 경로 — 별도 worktree 필요"
31
+ pwt="$(cd "$(dirname "$wt")" && pwd -P)"
32
+ pmain="$(cd "$(dirname "$main")" && pwd -P)"
33
+ [ "$pwt" = "$pmain" ] || fail "worktree가 메인 repo의 형제 디렉토리가 아님: $wt (메인: $main)"
34
+ }
35
+ [ "$(q '.stages."3_delivery".substages."commit".done')" = "true" ] || fail "commit substage 미완료"
36
+ WT="$(q '.worktree')"
37
+ assert_worktree_sibling "$WT"
38
+
39
+ MODIFIED="$(git -C "$WT" status --porcelain | grep -v '^?? ' || true)"
40
+
41
+ if [ -n "$MODIFIED" ]; then
42
+ fail "worktree에 uncommitted 변경(M/A/D/R)이 있음 — 커밋하거나 제거 필요"
43
+ fi
44
+
45
+ BR="$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
46
+ [ -n "$BR" ] || fail "현재 브랜치 확인 불가"
47
+
48
+ echo "MAKDOONG2-GATE OK: 3_delivery.pr"