@uzysjung/agent-harness 26.162.1 → 26.164.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.
@@ -17,7 +17,7 @@ import {
17
17
  init_esm_shims,
18
18
  residentCost,
19
19
  resolveBundleRoot
20
- } from "./chunk-3G34MEGZ.js";
20
+ } from "./chunk-X7NSQHXL.js";
21
21
 
22
22
  // src/trust-tier-drift.ts
23
23
  init_esm_shims();
@@ -33,8 +33,10 @@ var REPO_OVERRIDE = {
33
33
  // npm
34
34
  openspec: "Fission-AI/OpenSpec",
35
35
  // npm (v26.75.0)
36
- "bmad-method": "bmad-code-org/BMAD-METHOD"
36
+ "bmad-method": "bmad-code-org/BMAD-METHOD",
37
37
  // npx-run (v26.75.0)
38
+ "railway-mcp-server": "railwayapp/railway-mcp-server"
39
+ // internal — 트랙 표 MCP 행(#709)
38
40
  };
39
41
  function normalizeRepo(source) {
40
42
  const stripped = source.replace(/^https?:\/\/github\.com\//i, "");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/trust-tier-drift.ts"],"sourcesContent":["/**\n * A1 — Trust Tier star-drift 검출 데이터 + 순수 로직.\n *\n * TRUST_TIER 의 star 기반 라벨(vetted ≥ 1000★ / experimental < 1000★)이 실제 GitHub\n * star 와 어긋났는지(drift) 판정한다. `official` 은 star 무관(Anthropic 공식·하네스 자체)\n * 이라 검사 제외.\n *\n * repo 출처 = 각 자산 method (in-code authoritative — 주석이 아니라 실제 설치 source):\n * skill → method.source (\"owner/repo\" 또는 github URL)\n * plugin → method.marketplace (\"owner/repo\")\n * npm → NPM_REPO_OVERRIDE[id] (pkg 는 npm 명이므로 GitHub repo 를 별도 명시)\n *\n * fetch/네트워크는 본 모듈에 없음 — 순수 로직만(테스트 가능). 실 fetch 는\n * `scripts/trust-tier-drift.mjs` 가 담당.\n */\nimport { EXTERNAL_ASSETS, type ExternalAsset, TRUST_TIER } from \"./external-assets.js\";\n\n// v26.79.0 — gen-compatibility 의 카테고리 exhaustiveness 가드용 SSOT (하드코딩 drift 차단).\nexport { CATEGORIES } from \"./categories.js\";\n// v26.116.0 (ADR-043) — context-cost-report.mjs 가 dist 에서 비용 계측기를 읽도록 re-export.\n// v26.140.0 — formatResidentCostBlock: 리포트가 표를 직접 조립하지 않도록 표시 계약도 함께 노출.\nexport {\n assetCostRows,\n estimateTokens,\n formatResidentCostBlock,\n residentCost,\n resolveBundleRoot,\n} from \"./context-cost.js\";\n// v26.76.0 — gen-compatibility.mjs 가 dist 에서 자산 카탈로그+tier 를 읽도록 re-export.\n// v26.93.0 — DEV_METHOD_SKILL_IDS 추가: gen-compatibility 의 CLI scope override 를\n// 하드코딩 id 목록 대신 SSOT 에서 derive (no-false-ship drift 구조 차단).\n// v26.95.0 — INTERNAL_BUNDLED_SKILL_IDS (dev-method + opt-in gemini-consult) 로 CLI scope derive.\nexport {\n // v26.102.0 (ADR-031) — gen-compatibility 의 CLI 열이 도달 범위를 derive 하도록 re-export.\n assetCliSupport,\n DEV_METHOD_SKILL_IDS,\n EXTERNAL_ASSETS,\n INTERNAL_BUNDLED_SKILL_IDS,\n TRUST_TIER,\n} from \"./external-assets.js\";\nexport { buildAssetSpec, buildManifest } from \"./manifest.js\";\n// v26.102.0 (ADR-031) — 도달 라벨(\"N-CLI\")의 N 을 derive 하기 위한 re-export (매직 넘버 금지).\nexport { CLI_BASES, DEFAULT_OPTIONS, TRACKS } from \"./types.js\";\n\n/** vetted 경계 (NORTH_STAR / PRD v26-71 D2). */\nexport const STAR_THRESHOLD = 1000;\n\nexport type StarTier = \"vetted\" | \"experimental\";\nexport type DriftVerdict = \"ok\" | \"promote\" | \"demote\";\n\n/**\n * method 가 GitHub repo 를 안 담는 자산(npm.pkg / npx-run.cmd 는 npm 명) → 트러스트 근거가\n * 된 GitHub repo 를 명시 매핑. override 가 method 도출보다 우선.\n */\nconst REPO_OVERRIDE: Record<string, string> = {\n \"vercel-cli\": \"vercel/vercel\", // npm\n \"netlify-cli\": \"netlify/cli\", // npm\n \"supabase-cli\": \"supabase/cli\", // npm\n \"agent-browser\": \"vercel-labs/agent-browser\", // npm\n openspec: \"Fission-AI/OpenSpec\", // npm (v26.75.0)\n \"bmad-method\": \"bmad-code-org/BMAD-METHOD\", // npx-run (v26.75.0)\n};\n\n/** \"https://github.com/owner/repo\" 또는 \"owner/repo[/...]\" → \"owner/repo\". 실패 시 null. */\nexport function normalizeRepo(source: string): string | null {\n const stripped = source.replace(/^https?:\\/\\/github\\.com\\//i, \"\");\n const m = stripped.match(/^([^/\\s]+\\/[^/\\s]+)/);\n return m?.[1] ?? null;\n}\n\n/** 자산의 GitHub owner/repo 도출. override 우선 → skill/plugin method. 도출 불가 시 null. */\nexport function repoForAsset(asset: ExternalAsset): string | null {\n const override = REPO_OVERRIDE[asset.id];\n if (override) return override;\n const m = asset.method;\n if (m.kind === \"skill\") return normalizeRepo(m.source);\n if (m.kind === \"plugin\") return normalizeRepo(m.marketplace);\n return null;\n}\n\nexport interface DriftTarget {\n id: string;\n tier: StarTier;\n repo: string;\n}\n\n/** star 기반(vetted/experimental) 자산만 + repo 도출 가능한 것만 검사 대상. */\nexport function driftTargets(\n assets: ReadonlyArray<ExternalAsset> = EXTERNAL_ASSETS,\n): DriftTarget[] {\n const out: DriftTarget[] = [];\n for (const a of assets) {\n const tier = TRUST_TIER[a.id];\n if (tier !== \"vetted\" && tier !== \"experimental\") continue;\n const repo = repoForAsset(a);\n if (!repo) continue; // 도출 불가 — 테스트가 0건을 강제하므로 정상 경로에선 발생 안 함\n out.push({ id: a.id, tier, repo });\n }\n return out;\n}\n\n/** 정적 tier 가 실제 star 와 어긋났는지 판정. */\nexport function classifyDrift(tier: StarTier, stars: number): DriftVerdict {\n if (tier === \"vetted\" && stars < STAR_THRESHOLD) return \"demote\";\n if (tier === \"experimental\" && stars >= STAR_THRESHOLD) return \"promote\";\n return \"ok\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AA6CO,IAAM,iBAAiB;AAS9B,IAAM,gBAAwC;AAAA,EAC5C,cAAc;AAAA;AAAA,EACd,eAAe;AAAA;AAAA,EACf,gBAAgB;AAAA;AAAA,EAChB,iBAAiB;AAAA;AAAA,EACjB,UAAU;AAAA;AAAA,EACV,eAAe;AAAA;AACjB;AAGO,SAAS,cAAc,QAA+B;AAC3D,QAAM,WAAW,OAAO,QAAQ,8BAA8B,EAAE;AAChE,QAAM,IAAI,SAAS,MAAM,qBAAqB;AAC9C,SAAO,IAAI,CAAC,KAAK;AACnB;AAGO,SAAS,aAAa,OAAqC;AAChE,QAAM,WAAW,cAAc,MAAM,EAAE;AACvC,MAAI,SAAU,QAAO;AACrB,QAAM,IAAI,MAAM;AAChB,MAAI,EAAE,SAAS,QAAS,QAAO,cAAc,EAAE,MAAM;AACrD,MAAI,EAAE,SAAS,SAAU,QAAO,cAAc,EAAE,WAAW;AAC3D,SAAO;AACT;AASO,SAAS,aACd,SAAuC,iBACxB;AACf,QAAM,MAAqB,CAAC;AAC5B,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,WAAW,EAAE,EAAE;AAC5B,QAAI,SAAS,YAAY,SAAS,eAAgB;AAClD,UAAM,OAAO,aAAa,CAAC;AAC3B,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,KAAK,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAGO,SAAS,cAAc,MAAgB,OAA6B;AACzE,MAAI,SAAS,YAAY,QAAQ,eAAgB,QAAO;AACxD,MAAI,SAAS,kBAAkB,SAAS,eAAgB,QAAO;AAC/D,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/trust-tier-drift.ts"],"sourcesContent":["/**\n * A1 — Trust Tier star-drift 검출 데이터 + 순수 로직.\n *\n * TRUST_TIER 의 star 기반 라벨(vetted ≥ 1000★ / experimental < 1000★)이 실제 GitHub\n * star 와 어긋났는지(drift) 판정한다. `official` 은 star 무관(Anthropic 공식·하네스 자체)\n * 이라 검사 제외.\n *\n * repo 출처 = 각 자산 method (in-code authoritative — 주석이 아니라 실제 설치 source):\n * skill → method.source (\"owner/repo\" 또는 github URL)\n * plugin → method.marketplace (\"owner/repo\")\n * npm → NPM_REPO_OVERRIDE[id] (pkg 는 npm 명이므로 GitHub repo 를 별도 명시)\n *\n * fetch/네트워크는 본 모듈에 없음 — 순수 로직만(테스트 가능). 실 fetch 는\n * `scripts/trust-tier-drift.mjs` 가 담당.\n */\nimport { EXTERNAL_ASSETS, type ExternalAsset, TRUST_TIER } from \"./external-assets.js\";\n\n// v26.79.0 — gen-compatibility 의 카테고리 exhaustiveness 가드용 SSOT (하드코딩 drift 차단).\nexport { CATEGORIES } from \"./categories.js\";\n// v26.116.0 (ADR-043) — context-cost-report.mjs 가 dist 에서 비용 계측기를 읽도록 re-export.\n// v26.140.0 — formatResidentCostBlock: 리포트가 표를 직접 조립하지 않도록 표시 계약도 함께 노출.\nexport {\n assetCostRows,\n estimateTokens,\n formatResidentCostBlock,\n residentCost,\n resolveBundleRoot,\n} from \"./context-cost.js\";\n// v26.76.0 — gen-compatibility.mjs 가 dist 에서 자산 카탈로그+tier 를 읽도록 re-export.\n// v26.93.0 — DEV_METHOD_SKILL_IDS 추가: gen-compatibility 의 CLI scope override 를\n// 하드코딩 id 목록 대신 SSOT 에서 derive (no-false-ship drift 구조 차단).\n// v26.95.0 — INTERNAL_BUNDLED_SKILL_IDS (dev-method + opt-in gemini-consult) 로 CLI scope derive.\nexport {\n // v26.102.0 (ADR-031) — gen-compatibility 의 CLI 열이 도달 범위를 derive 하도록 re-export.\n assetCliSupport,\n DEV_METHOD_SKILL_IDS,\n EXTERNAL_ASSETS,\n INTERNAL_BUNDLED_SKILL_IDS,\n TRUST_TIER,\n} from \"./external-assets.js\";\nexport { buildAssetSpec, buildManifest } from \"./manifest.js\";\n// v26.102.0 (ADR-031) — 도달 라벨(\"N-CLI\")의 N 을 derive 하기 위한 re-export (매직 넘버 금지).\nexport { CLI_BASES, DEFAULT_OPTIONS, TRACKS } from \"./types.js\";\n\n/** vetted 경계 (NORTH_STAR / PRD v26-71 D2). */\nexport const STAR_THRESHOLD = 1000;\n\nexport type StarTier = \"vetted\" | \"experimental\";\nexport type DriftVerdict = \"ok\" | \"promote\" | \"demote\";\n\n/**\n * method 가 GitHub repo 를 안 담는 자산(npm.pkg / npx-run.cmd 는 npm 명) → 트러스트 근거가\n * 된 GitHub repo 를 명시 매핑. override 가 method 도출보다 우선.\n */\nconst REPO_OVERRIDE: Record<string, string> = {\n \"vercel-cli\": \"vercel/vercel\", // npm\n \"netlify-cli\": \"netlify/cli\", // npm\n \"supabase-cli\": \"supabase/cli\", // npm\n \"agent-browser\": \"vercel-labs/agent-browser\", // npm\n openspec: \"Fission-AI/OpenSpec\", // npm (v26.75.0)\n \"bmad-method\": \"bmad-code-org/BMAD-METHOD\", // npx-run (v26.75.0)\n \"railway-mcp-server\": \"railwayapp/railway-mcp-server\", // internal — 트랙 표 MCP 행(#709)\n};\n\n/** \"https://github.com/owner/repo\" 또는 \"owner/repo[/...]\" → \"owner/repo\". 실패 시 null. */\nexport function normalizeRepo(source: string): string | null {\n const stripped = source.replace(/^https?:\\/\\/github\\.com\\//i, \"\");\n const m = stripped.match(/^([^/\\s]+\\/[^/\\s]+)/);\n return m?.[1] ?? null;\n}\n\n/** 자산의 GitHub owner/repo 도출. override 우선 → skill/plugin method. 도출 불가 시 null. */\nexport function repoForAsset(asset: ExternalAsset): string | null {\n const override = REPO_OVERRIDE[asset.id];\n if (override) return override;\n const m = asset.method;\n if (m.kind === \"skill\") return normalizeRepo(m.source);\n if (m.kind === \"plugin\") return normalizeRepo(m.marketplace);\n return null;\n}\n\nexport interface DriftTarget {\n id: string;\n tier: StarTier;\n repo: string;\n}\n\n/** star 기반(vetted/experimental) 자산만 + repo 도출 가능한 것만 검사 대상. */\nexport function driftTargets(\n assets: ReadonlyArray<ExternalAsset> = EXTERNAL_ASSETS,\n): DriftTarget[] {\n const out: DriftTarget[] = [];\n for (const a of assets) {\n const tier = TRUST_TIER[a.id];\n if (tier !== \"vetted\" && tier !== \"experimental\") continue;\n const repo = repoForAsset(a);\n if (!repo) continue; // 도출 불가 — 테스트가 0건을 강제하므로 정상 경로에선 발생 안 함\n out.push({ id: a.id, tier, repo });\n }\n return out;\n}\n\n/** 정적 tier 가 실제 star 와 어긋났는지 판정. */\nexport function classifyDrift(tier: StarTier, stars: number): DriftVerdict {\n if (tier === \"vetted\" && stars < STAR_THRESHOLD) return \"demote\";\n if (tier === \"experimental\" && stars >= STAR_THRESHOLD) return \"promote\";\n return \"ok\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AA6CO,IAAM,iBAAiB;AAS9B,IAAM,gBAAwC;AAAA,EAC5C,cAAc;AAAA;AAAA,EACd,eAAe;AAAA;AAAA,EACf,gBAAgB;AAAA;AAAA,EAChB,iBAAiB;AAAA;AAAA,EACjB,UAAU;AAAA;AAAA,EACV,eAAe;AAAA;AAAA,EACf,sBAAsB;AAAA;AACxB;AAGO,SAAS,cAAc,QAA+B;AAC3D,QAAM,WAAW,OAAO,QAAQ,8BAA8B,EAAE;AAChE,QAAM,IAAI,SAAS,MAAM,qBAAqB;AAC9C,SAAO,IAAI,CAAC,KAAK;AACnB;AAGO,SAAS,aAAa,OAAqC;AAChE,QAAM,WAAW,cAAc,MAAM,EAAE;AACvC,MAAI,SAAU,QAAO;AACrB,QAAM,IAAI,MAAM;AAChB,MAAI,EAAE,SAAS,QAAS,QAAO,cAAc,EAAE,MAAM;AACrD,MAAI,EAAE,SAAS,SAAU,QAAO,cAAc,EAAE,WAAW;AAC3D,SAAO;AACT;AASO,SAAS,aACd,SAAuC,iBACxB;AACf,QAAM,MAAqB,CAAC;AAC5B,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,WAAW,EAAE,EAAE;AAC5B,QAAI,SAAS,YAAY,SAAS,eAAgB;AAClD,UAAM,OAAO,aAAa,CAAC;AAC3B,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,KAAK,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAGO,SAAS,cAAc,MAAgB,OAA6B;AACzE,MAAI,SAAS,YAAY,QAAQ,eAAgB,QAAO;AACxD,MAAI,SAAS,kBAAkB,SAAS,eAAgB,QAAO;AAC/D,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@uzysjung/agent-harness",
3
- "version": "26.162.1",
3
+ "version": "26.164.0",
4
4
  "description": "Curate vetted AI-coding skills & plugins by your tech stack — install only what you need, across Claude Code, Codex, OpenCode & Antigravity",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "engines": {
10
- "node": ">=20.0.0"
10
+ "node": ">=20.12.0"
11
11
  },
12
12
  "bin": {
13
13
  "agent-harness": "./dist/index.js"
@@ -32,9 +32,9 @@ templates/codex/
32
32
 
33
33
  ## Hook Event 매핑 (Claude → Codex)
34
34
 
35
- | Claude | Codex event (snake_case) | Blockable | 제약 |
35
+ | Claude | Codex event | Blockable | 제약 |
36
36
  |--------|--------------------------|-----------|------|
37
- | `SessionStart` | `session_start` | ❌ | — |
37
+ | `SessionStart` | `SessionStart` (PascalCase + 중첩 `[[hooks.SessionStart.hooks]]`, #627) | ❌ | flat `session_start` 는 현행 codex-cli 가 무음 무시 · `timeout = 10`(초) 상한 — 빼면 Codex 기본 10분 |
38
38
  | `UserPromptSubmit` | `user_prompt_submit` | ✅ (exit 2) | matcher 무시 |
39
39
  | `PreToolUse` (Skill) | `pre_tool_use` matcher="Skill" | ✅ | **Bash 툴 한정 발화 (Issue #16732)** |
40
40
  | `PreToolUse` (Write) | — | — | **Codex `sandbox_mode`로 이관** (ADR-002 v2 D3) |
@@ -43,12 +43,16 @@ enabled = true
43
43
  # ============================================================
44
44
 
45
45
  # SessionStart: 세션 컨텍스트 + SPEC 재참조 안내 — 본체는 templates/hooks/session-start.sh 를 설치기가 포팅한다(src/codex/transform.ts HOOK_NAMES)
46
- [[hooks.session_start]]
46
+ # #627 — 현행 codex-cli 스키마는 PascalCase 이벤트 + 중첩 [[hooks.SessionStart.hooks]] + command 문자열이다.
47
+ # 옛 flat `[[hooks.session_start]]`(command 배열)은 경고 없이 무시돼 훅이 한 번도 발화하지 않았다.
48
+ # timeout 은 초 단위 상한이다. 빼면 Codex 기본값(10분)이 적용돼 훅이 느려질 때 세션 시작이 그만큼 막힌다.
49
+ [[hooks.SessionStart]]
47
50
  name = "session-start"
48
- command = ["{PROJECT_DIR}/.codex/hooks/session-start.sh"]
51
+
52
+ [[hooks.SessionStart.hooks]]
53
+ command = "{PROJECT_DIR}/.codex/hooks/session-start.sh"
49
54
  type = "command"
50
55
  timeout = 10
51
- async = false
52
56
 
53
57
  # ============================================================
54
58
  # Features (본 설치가 사용하는 stable feature 명시)
@@ -48,17 +48,22 @@ case "$BASENAME" in
48
48
  .env.example|.env.sample|.env.template)
49
49
  exit 0
50
50
  ;;
51
- .env|.env.*)
51
+ # #604 — `.env*` 글롭에는 `.envrc`(direnv) 도 들어간다. 패턴이 `.env.*`(점 추가)라
52
+ # 두 번째 점이 없는 .envrc 가 새어나갔다 — 실제 시크릿을 담는 파일이다.
53
+ # #634 — 문서가 말하는 "lock files, certificate/key files" 에 맞춰 잠금·인증서·비밀키
54
+ # 확장자를 보강한다(폐쇄 목록 유지 — 과잉 차단은 게이트를 무력화하는 우회를 부른다).
55
+ .env|.env.*|.envrc|.envrc.*)
52
56
  log_block "$FILE_PATH"
53
57
  echo "BLOCKED: Protected file: $BASENAME. Environment files must be edited manually." >&2
54
58
  exit 2
55
59
  ;;
56
- package-lock.json|yarn.lock|pnpm-lock.yaml|Cargo.lock|poetry.lock|uv.lock)
60
+ package-lock.json|npm-shrinkwrap.json|yarn.lock|pnpm-lock.yaml|Cargo.lock|poetry.lock|uv.lock|\
61
+ Gemfile.lock|composer.lock|bun.lock|bun.lockb|Pipfile.lock|packages.lock.json|flake.lock|deno.lock|go.sum)
57
62
  log_block "$FILE_PATH"
58
63
  echo "BLOCKED: Protected file: $BASENAME. Lock files should not be edited directly." >&2
59
64
  exit 2
60
65
  ;;
61
- *.pem|*.key|*.p12|*.pfx)
66
+ *.pem|*.key|*.p12|*.pfx|*.crt|*.cer|*.csr|*.jks|id_rsa|id_dsa|id_ecdsa|id_ed25519)
62
67
  log_block "$FILE_PATH"
63
68
  echo "BLOCKED: Protected file: $BASENAME. Certificate/key files must not be modified by agents." >&2
64
69
  exit 2
@@ -16,10 +16,18 @@ else
16
16
  fi
17
17
 
18
18
  # 1. SPEC 존재 여부 확인
19
+ SPEC_PATH=""
20
+ for f in "docs/SPEC.md" "SPEC.md"; do
21
+ if [ -f "$f" ]; then SPEC_PATH="$f"; break; fi
22
+ done
19
23
  SPEC_EXISTS="false"
20
- if [ -f "docs/SPEC.md" ] || [ -f "SPEC.md" ]; then
21
- SPEC_EXISTS="true"
22
- fi
24
+ [ -n "$SPEC_PATH" ] && SPEC_EXISTS="true"
25
+ # #635 — 안내는 감지한 실제 경로로. 루트 SPEC.md 레이아웃에서 docs/SPEC.md 를 안내하면
26
+ # 존재하지 않는 파일을 첫 명령으로 읽게 한다(형제 헬퍼 spec-drift-check 의 first_existing 과 같은 판정).
27
+ CHANGELOG_EXISTS="false"
28
+ for c in "docs/CHANGELOG.md" "CHANGELOG.md"; do
29
+ if [ -f "$c" ]; then CHANGELOG_EXISTS="true"; break; fi
30
+ done
23
31
 
24
32
  # 2. 이전 세션이 남긴 고아 프로세스 감지
25
33
  #
@@ -104,7 +112,10 @@ fi
104
112
  # `hookSpecificOutput.{additionalContext|initialUserMessage}` 다.
105
113
  # 조용히 버려지는 출력은 컨텍스트 비용이 0 인 대신 기능도 0 이다.
106
114
  if [ "$SPEC_EXISTS" = "true" ]; then
107
- MSG="Session started. Branch: $BRANCH. SPEC exists — read docs/SPEC.md first (Persistent Anchor). Check Change Log and current Phase before starting work.${ORPHAN_NOTE}"
115
+ # #635 — Change Log 안내는 실존할 때만(없으면 매 세션 헛된 조회를 시킨다 — 감지도 안 하던 것).
116
+ CHANGELOG_NOTE=""
117
+ [ "$CHANGELOG_EXISTS" = "true" ] && CHANGELOG_NOTE=" Check Change Log and current Phase before starting work."
118
+ MSG="Session started. Branch: $BRANCH. SPEC exists — read ${SPEC_PATH} first (Persistent Anchor).${CHANGELOG_NOTE}${ORPHAN_NOTE}"
108
119
  else
109
120
  MSG="Session started. Branch: $BRANCH. No SPEC found.${ORPHAN_NOTE}"
110
121
  fi
@@ -23,7 +23,8 @@ esac
23
23
  RULESET_NAME="default-branch protection"
24
24
 
25
25
  say() { printf '%s\n' "$*"; }
26
- die() { printf '%s\n' "$*" >&2; exit "${2:-1}"; }
26
+ # #648 — $* 는 exit code 인자까지 메시지에 흘린다("… Rules. 3"). 첫 인자만 메시지로.
27
+ die() { printf '%s\n' "$1" >&2; exit "${2:-1}"; }
27
28
 
28
29
  command -v gh >/dev/null 2>&1 || die "gh (GitHub CLI) not found — install it, or apply the same rules in the repo's web settings under Rules." 3
29
30
  gh auth status >/dev/null 2>&1 || die "gh is not authenticated — run: gh auth login" 3
@@ -43,7 +43,10 @@ count_unchecked() {
43
43
  awk '
44
44
  /^[ \t]*<!--[ ]*ship-gate:ignore-start[ ]*-->[ \t]*$/ { skip = 1; opened++; next }
45
45
  /^[ \t]*<!--[ ]*ship-gate:ignore-end[ ]*-->[ \t]*$/ { skip = 0; closed++; next }
46
- /^- \[ \]|^ - \[ \]/ { total++; if (!skip) counted++ }
46
+ # #649 — 들여쓰기 무관(1·3·4칸·탭 포함) + 표 셀 unchecked. 옛 패턴은 0칸·정확히 2칸만
47
+ # 세어 나머지를 조용히 통과시켰다(ship 게이트 허위 통과).
48
+ /^[ \t]*- \[ \]/ { total++; if (!skip) counted++ }
49
+ /\|[ \t]*\[ \][ \t]*\|/ { total++; if (!skip) counted++ }
47
50
  END {
48
51
  if (opened != closed) {
49
52
  printf "spec-drift-check: ship-gate:ignore 표식 불일치 (start %d / end %d) — 면제를 무시한다\n", opened, closed > "/dev/stderr"
@@ -68,12 +71,16 @@ first_existing() {
68
71
  echo ""
69
72
  }
70
73
 
74
+ # #649 — 읽은 파일 수를 센다. 0개면 "없다"가 아니라 "안 봤다"다(루트 밖 cwd 호출 등) —
75
+ # 같은 원리로 check-absence.sh 가 'not examined' 를 구분한다.
76
+ EXAMINED=0
71
77
  SPEC_FILE=$(first_existing "$DOCS_DIR/SPEC.md" "$PROJECT_DIR/SPEC.md")
72
78
  TODO_FILE=$(first_existing "$DOCS_DIR/todo.md" "$DOCS_DIR/TODO.md" "$PROJECT_DIR/todo.md" \
73
79
  "$PROJECT_DIR/TODO.md" "$PROJECT_DIR/tasks/todo.md")
74
80
 
75
81
  # 1. SPEC unchecked 검사
76
82
  if [ -n "$SPEC_FILE" ]; then
83
+ EXAMINED=$((EXAMINED + 1))
77
84
  UNCHECKED=$(count_unchecked "$SPEC_FILE")
78
85
  UNCHECKED=${UNCHECKED:-0}
79
86
  if [ "$UNCHECKED" -gt 0 ] 2>/dev/null; then
@@ -84,6 +91,7 @@ fi
84
91
 
85
92
  # 2. TODO unchecked 검사
86
93
  if [ -n "$TODO_FILE" ]; then
94
+ EXAMINED=$((EXAMINED + 1))
87
95
  UNCHECKED=$(count_unchecked "$TODO_FILE")
88
96
  UNCHECKED=${UNCHECKED:-0}
89
97
  if [ "$UNCHECKED" -gt 0 ] 2>/dev/null; then
@@ -94,6 +102,10 @@ fi
94
102
 
95
103
  # 3. SPEC Status 일관성 — gate-status.json과 대조 (6-gate 워크플로 사용 프로젝트만; 파일 없으면 skip)
96
104
  GATE_FILE="$PROJECT_DIR/.claude/gate-status.json"
105
+ # #649 — jq 부재 스킵을 조용히 하지 않는다("정상"과 "못 봤음"이 같은 출력이 되므로).
106
+ if [ -f "$GATE_FILE" ] && [ -n "$SPEC_FILE" ] && ! command -v jq &> /dev/null; then
107
+ echo "NOTE: gate-status.json 이 있지만 jq 가 없어 Status 일관성 검사를 건너뛰었다 (not examined)" >&2
108
+ fi
97
109
  if [ -f "$GATE_FILE" ] && [ -n "$SPEC_FILE" ] && command -v jq &> /dev/null; then
98
110
  BUILD_DONE=$(jq -r '.build.completed // false' "$GATE_FILE")
99
111
  VERIFY_DONE=$(jq -r '.verify.completed // false' "$GATE_FILE")
@@ -116,7 +128,12 @@ fi
116
128
 
117
129
  # Summary
118
130
  if [ "$DRIFT" -eq 0 ]; then
119
- echo "OK: SPEC/TODO 동기화 상태 정상"
131
+ if [ "$EXAMINED" -eq 0 ]; then
132
+ # #649 — 후보를 하나도 못 찾았다면 "정상"은 할 수 없는 말이다(안 봤음).
133
+ echo "WARNING: SPEC/TODO 후보를 하나도 찾지 못했다 (not examined) — 프로젝트 루트에서 실행했는지 확인" >&2
134
+ exit 0
135
+ fi
136
+ echo "OK: SPEC/TODO 동기화 상태 정상 (${EXAMINED}개 파일 검사)"
120
137
  exit 0
121
138
  fi
122
139
 
@@ -2,7 +2,7 @@
2
2
  "_comment": "프로젝트 .claude/settings.json 템플릿. $CLAUDE_PROJECT_DIR 사용으로 절대 경로 제거. committable. 글로벌 ~/.claude/settings.json은 절대 건드리지 않음.",
3
3
  "statusLine": {
4
4
  "type": "command",
5
- "command": "npx -y @owloops/claude-powerline@latest --style=tui"
5
+ "command": "npx -y @owloops/claude-powerline@1.32.1 --style=tui"
6
6
  },
7
7
  "hooks": {
8
8
  "SessionStart": [
@@ -5,11 +5,12 @@
5
5
  # <mcp_name> <track_pattern> <command> <args_json>
6
6
  #
7
7
  # - mcp_name: .mcp.json의 mcpServers[키]
8
- # - track_pattern: any_track 패턴 (csr-*|full 같은 bash glob)
8
+ # - track_pattern: any_track 패턴 (csr-*|full 같은 bash glob). 패턴은 **기본** 트랙이다 — 같은 이름의
9
+ # 카탈로그 `internal` 자산이 있으면 그 서버는 어느 트랙에서든 `--with <id>`/위저드로 고를 수 있다(#709)
9
10
  # - command: 실행 명령 (npx 등)
10
11
  # - args_json: jq 인자 배열 (JSON 형식)
11
12
  #
12
13
  # 신규 MCP 추가 시: 이 파일에 1줄 추가만 하면 setup-harness.sh 수정 불필요.
13
14
  # 주석 (#) 라인은 무시.
14
- railway-mcp-server csr-supabase|csr-fastify|csr-fastapi|ssr-htmx|ssr-nextjs|full npx ["-y", "@railway/mcp-server"]
15
+ railway-mcp-server csr-supabase|csr-fastify|csr-fastapi|ssr-htmx|full npx ["-y", "@railway/mcp-server"]
15
16
  supabase csr-supabase|full npx ["-y", "@supabase/mcp-server"]