projectops 4.2.7 → 4.2.8

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 CHANGED
@@ -7,7 +7,7 @@
7
7
  > 이슈 등록부터 커밋, 보고서, 배포까지. 개발자는 코드만 작성하세요.
8
8
 
9
9
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
10
- ## 최신 버전 : v4.2.6 (2026-07-10)
10
+ ## 최신 버전 : v4.2.7 (2026-07-10)
11
11
 
12
12
  [전체 버전 기록 보기](CHANGELOG.md)
13
13
 
@@ -93,22 +93,7 @@ npx projectops
93
93
 
94
94
  > Node.js 20.12+ 만 있으면 별도 설치 없이 대화형 마법사가 실행됩니다. 비대화형: `npx projectops --mode full --type spring,react --force`
95
95
 
96
- <details>
97
- <summary>대안 — 스크립트 직접 실행 (Node 없이, <b>deprecated</b>)</summary>
98
-
99
- > ⚠️ **`template_integrator.sh` / `.ps1`은 deprecated입니다 (#458).** 실행하면 `npx projectops`로 안내하며, 다음 minor에서 제거될 수 있습니다. Node 20.12+를 쓸 수 있으면 위의 `npx projectops`를 사용하세요.
100
-
101
- ```bash
102
- # macOS / Linux
103
- bash <(curl -fsSL "https://raw.githubusercontent.com/Cassiiopeia/projectops/main/template_integrator.sh")
104
- ```
105
-
106
- ```powershell
107
- # Windows PowerShell
108
- $wc=New-Object Net.WebClient;$wc.Encoding=[Text.Encoding]::UTF8;iex $wc.DownloadString("https://raw.githubusercontent.com/Cassiiopeia/projectops/main/template_integrator.ps1")
109
- ```
110
-
111
- </details>
96
+ > ⚠️ 구 `template_integrator.sh` / `.ps1`은 **지원 종료(EOF)** 되었습니다 (#458). 실행하면 `npx projectops` 안내만 출력하며, 다음 minor에서 파일이 제거됩니다.
112
97
 
113
98
  ### Agent Skills만 설치
114
99
 
@@ -137,14 +122,7 @@ Codex plugin marketplace를 사용할 수 없는 환경에서는 [Skills 가이
137
122
  npx projectops --mode skills
138
123
  ```
139
124
 
140
- <details>
141
- <summary>대안 — 스크립트 직접 실행 (Node 없이)</summary>
142
-
143
- ```bash
144
- bash <(curl -fsSL "https://raw.githubusercontent.com/Cassiiopeia/projectops/main/template_integrator.sh") --mode skills
145
- ```
146
125
 
147
- </details>
148
126
 
149
127
  > Claude Code는 `/projectops:` 자동완성, Gemini는 extension, Codex는 plugin marketplace를 우선 사용합니다. 자세한 설치 방식은 [Skills 가이드](docs/SKILLS.md)를 확인하세요.
150
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.2.7",
3
+ "version": "4.2.8",
4
4
  "description": "ProjectOps — 완전 자동화 GitHub 프로젝트 관리 템플릿 통합 CLI",
5
5
  "keywords": [
6
6
  "devops",
@@ -7,11 +7,16 @@ import { exists, copyFileSync, copyDirSync } from "../fsutil.js";
7
7
 
8
8
  // 버전관리/릴리스노트 스크립트만 무조건 덮어쓰기 + chmod +x.
9
9
  // version_manager는 .sh(위임 shim) + .py(실 로직) 한 쌍 — 둘 다 복사해야 동작 (#448).
10
+ // changelog provider 사다리(.py 5종, #455)는 RELEASE-CHANGELOG 워크플로우 fallback-summary가
11
+ // 호출하므로 함께 복사해야 사용자 프로젝트에서 릴리스 노트 생성이 동작한다.
10
12
  export function copyScripts(tempDir, targetRoot = ".") {
11
13
  const scripts = [
12
14
  "version_manager.sh", "version_manager.py",
13
15
  "changelog_manager.py",
14
16
  "truncate_release_notes.sh", "truncate_release_notes.py",
17
+ "changelog_providers/_common.py", "changelog_providers/ladder.py",
18
+ "changelog_providers/commit.py", "changelog_providers/github_ai.py",
19
+ "changelog_providers/openai_compatible.py",
15
20
  ];
16
21
  let copied = 0;
17
22
  for (const s of scripts) {
@@ -3,7 +3,7 @@
3
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import { join, basename } from "node:path";
5
5
  import { execFileSync } from "node:child_process";
6
- import { detectTypesFromMarkers, detectVersionFromFiles } from "./detect.js";
6
+ import { detectTypesFromMarkers, detectVersionFromFiles, suggestTypesByExtScan } from "./detect.js";
7
7
  import { parseExisting } from "./version-yml.js";
8
8
 
9
9
  const hasFile = (root) => (rel) => existsSync(join(root, rel));
@@ -17,14 +17,44 @@ function gitOut(root, args) {
17
17
  } catch { return ""; }
18
18
  }
19
19
 
20
+ // 확장자 스캔용 파일 목록 (.sh suggest_types_by_scan find 등가 — depth≤3, vendor 폴더 프루닝).
21
+ const SCAN_PRUNE = new Set([
22
+ "node_modules", ".git", "build", "dist", ".dart_tool",
23
+ "android", "ios", ".gradle", "venv", ".venv", "__pycache__",
24
+ ]);
25
+ export function listScanFiles(root, maxDepth = 3) {
26
+ const files = [];
27
+ const walk = (rel, depth) => {
28
+ if (depth > maxDepth) return;
29
+ let entries;
30
+ try { entries = readdirSync(join(root, rel), { withFileTypes: true }); } catch { return; }
31
+ for (const e of entries) {
32
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
33
+ if (e.isDirectory()) {
34
+ if (!SCAN_PRUNE.has(e.name)) walk(childRel, depth + 1);
35
+ } else {
36
+ files.push(childRel);
37
+ }
38
+ }
39
+ };
40
+ walk("", 1);
41
+ return files;
42
+ }
43
+
20
44
  // 타입 감지 — version.yml의 project_types 최우선(source of truth), 없으면 마커 스캔.
45
+ // 마커도 전혀 없으면(basic 폴백) 확장자 빈도 스캔으로 2차 추천 (.sh suggest_types_by_scan 등가, #458).
21
46
  export function detectTypes(root) {
22
47
  const vy = join(root, "version.yml");
23
48
  if (existsSync(vy)) {
24
49
  const { types } = parseExisting(readFileSync(vy, "utf8"));
25
50
  if (types.length) return types; // basic 포함, 명시돼 있으면 그대로
26
51
  }
27
- return detectTypesFromMarkers({ has: hasFile(root), read: readFile(root) });
52
+ const byMarkers = detectTypesFromMarkers({ has: hasFile(root), read: readFile(root) });
53
+ if (byMarkers.length === 1 && byMarkers[0] === "basic") {
54
+ const scanned = suggestTypesByExtScan(listScanFiles(root));
55
+ if (scanned.length) return scanned; // 추천값 — 마법사 확인/수정 루프에서 사용자가 확정
56
+ }
57
+ return byMarkers;
28
58
  }
29
59
 
30
60
  // 버전 감지 — .sh detect_version 순서. jq 유무는 command 존재로 판정.
@@ -60,3 +60,19 @@ export function markerForType(type) {
60
60
  export function extraMarkers(type) {
61
61
  return { python: ["setup.py", "requirements.txt"], spring: ["build.gradle.kts", "pom.xml"] }[type] || [];
62
62
  }
63
+
64
+ // 확장자 빈도 스캔 추천 (.sh suggest_types_by_scan 2단계 등가, #458 npx 이식).
65
+ // 마커가 전혀 없을 때(=basic 폴백)만 쓰는 2차 추천. files = 프루닝된 상대경로 목록.
66
+ // 임계: dart≥1→flutter, java+kt+gradle≥3→spring, tsx+jsx≥3→react, py≥3→python,
67
+ // 아무것도 없고 ts+js≥3→node. 반환은 메뉴 정의 순서로 정렬.
68
+ export function suggestTypesByExtScan(files) {
69
+ const count = (re) => files.filter((f) => re.test(f)).length;
70
+ const found = new Set();
71
+ if (count(/\.dart$/) >= 1) found.add("flutter");
72
+ if (count(/\.java$/) + count(/\.kt$/) + count(/\.gradle$/) >= 3) found.add("spring");
73
+ if (count(/\.tsx$/) + count(/\.jsx$/) >= 3) found.add("react");
74
+ if (count(/\.py$/) >= 3) found.add("python");
75
+ if (found.size === 0 && count(/\.ts$/) + count(/\.js$/) >= 3) found.add("node");
76
+ const order = ["spring", "flutter", "react", "react-native", "react-native-expo", "node", "python"];
77
+ return order.filter((t) => found.has(t));
78
+ }
@@ -1,6 +1,7 @@
1
- // 템플릿 다운로드 후 제거하는 항목 .sh download_template 내부 배열과 동기화
2
- // ⚠️ CLAUDE.md "3곳 동시 수정" 규칙의 4번째 동기화 지점:
3
- // template_initializer.sh / template_integrator.sh / .ps1 / 이 파일
1
+ // 템플릿 다운로드 후 제거하는 항목 (복사 제외 목록).
2
+ // ⚠️ CLAUDE.md "템플릿 전용 파일 추가" 규칙의 동기화 지점:
3
+ // template_initializer.sh(삭제) / 이 파일(복사 제외)
4
+ // (구 template_integrator.sh/.ps1의 배열은 #458 EOF로 소멸 — 여기가 유일한 복사 제외 지점)
4
5
  export const DOCS_TO_REMOVE = [
5
6
  "CONTRIBUTING.md",
6
7
  "CLAUDE.md",