projectops 4.36.4 → 4.38.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 CHANGED
@@ -6,10 +6,10 @@
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/projectops?label=npm)](https://www.npmjs.com/package/projectops) [![Release](https://img.shields.io/github/v/release/Cassiiopeia/projectops?label=release)](https://github.com/Cassiiopeia/projectops/releases) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Docs](https://img.shields.io/badge/docs-site-4f46e5)](https://cassiiopeia.github.io/projectops/)
8
8
 
9
- **Status: actively maintained.** New versions ship often, sometimes several a day. To stay on a version you have tested, run `npx projectops@<version>` ([releases](https://github.com/Cassiiopeia/projectops/releases)).
9
+ **Status: actively maintained.** npm `latest` is exactly what is on the `main` branch; work in progress on `develop` is never published. Versions can still ship several times a day. To stay on a version you have tested, run `npx projectops@<version>` ([releases](https://github.com/Cassiiopeia/projectops/releases)).
10
10
 
11
11
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
12
- ## Latest version : v4.36.3 (2026-10-07)
12
+ ## Latest version : v4.37.0 (2026-10-10)
13
13
 
14
14
  [View full version history](CHANGELOG.md)
15
15
 
@@ -178,7 +178,7 @@ flowchart TD
178
178
  K --> L["/pro-changelog-deploy<br/>Release PR + automerge"]
179
179
  ```
180
180
 
181
- > Full list of Skills and usage: **[docs/SKILLS.md](docs/SKILLS.md)** (Korean)
181
+ > Full list of Skills and usage: **[docs/en/skills.md](docs/en/skills.md)**
182
182
 
183
183
  ### GitHub Actions pipeline
184
184
 
@@ -215,14 +215,14 @@ codex plugin marketplace add Cassiiopeia/projectops
215
215
 
216
216
  The `--mode skills` wizard registers the Codex marketplace and also prepares the native skills fallback. Use `/plugins` only to check or manage the install.
217
217
 
218
- Where the Codex plugin marketplace is unavailable, use the fallback install described in the [Skills guide](docs/SKILLS.md).
218
+ Where the Codex plugin marketplace is unavailable, use the fallback install described in the [Skills guide](docs/en/skills.md).
219
219
 
220
220
  ```bash
221
221
  # Cursor / the full Agent Skills install menu (recommended: npx)
222
222
  npx projectops --mode skills
223
223
  ```
224
224
 
225
- > Claude Code prefers `/pro-` autocomplete, Gemini prefers its extension, and Codex prefers its plugin marketplace. See the [Skills guide](docs/SKILLS.md) for details.
225
+ > Claude Code prefers `/pro-` autocomplete, Gemini prefers its extension, and Codex prefers its plugin marketplace. See the [Skills guide](docs/en/skills.md) for details.
226
226
 
227
227
  </details>
228
228
 
@@ -257,7 +257,7 @@ Run automation by commenting on an issue or PR.
257
257
  | `@projectops ios build` | Build iOS only | Flutter |
258
258
  | `@projectops create qa` | Create a QA issue automatically | All projects |
259
259
 
260
- > Details: [PR Preview](docs/PR-PREVIEW.md) | [Flutter builds](docs/FLUTTER-TEST-BUILD-TRIGGER.md) | [Issue automation](docs/ISSUE-AUTOMATION.md)
260
+ > Details: [PR Preview](docs/en/pr-preview.md) | [Flutter builds](docs/FLUTTER-TEST-BUILD-TRIGGER.md) | [Issue automation](docs/en/issue-automation.md)
261
261
 
262
262
  ---
263
263
 
@@ -300,25 +300,25 @@ We would rather tell you up front when this is not a fit.
300
300
  - **Releases assume a develop branch → default branch PR flow.** Branch names can be changed in `version.yml`, but it does not suit a repo that does not use two branches.
301
301
  - **A personal access token is optional.** You need one only to merge past branch protection or to sync a Projects board. See [Setup](#setup).
302
302
  - **It adds about 50 files to your repository** (workflows, helper scripts, templates). See [the measurement](#compared-with-other-tools).
303
- - **New versions ship often.** Pin a version you have tested with `npx projectops@<version>`.
303
+ - **New versions ship often.** `latest` follows every push to `main` with no delay. Pin a version you have tested with `npx projectops@<version>`.
304
304
  - **Server deploy workflows assume a Docker server reachable over SSH.**
305
305
 
306
306
  ---
307
307
 
308
308
  ## Documentation
309
309
 
310
- Browse and search everything on the **[docs site](https://cassiiopeia.github.io/projectops/en/)**. Most guides are currently Korean only.
310
+ Browse and search everything on the **[docs site](https://cassiiopeia.github.io/projectops/en/)**. The main guides are in English. A few wizard pages are still Korean only (translations are welcome, see [#799](https://github.com/Cassiiopeia/projectops/issues/799)).
311
311
 
312
312
  - [Getting started](https://cassiiopeia.github.io/projectops/en/getting-started)
313
313
  - [CLI reference](https://cassiiopeia.github.io/projectops/en/cli)
314
- - [Agent Skills guide (Korean)](https://cassiiopeia.github.io/projectops/SKILLS)
315
- - [Versioning (Korean)](https://cassiiopeia.github.io/projectops/VERSION-CONTROL)
316
- - [Changelog automation (Korean)](https://cassiiopeia.github.io/projectops/CHANGELOG-AUTOMATION)
317
- - [PR Preview (Korean)](https://cassiiopeia.github.io/projectops/PR-PREVIEW)
318
- - [Issue automation (Korean)](https://cassiiopeia.github.io/projectops/ISSUE-AUTOMATION)
319
- - [SSH + Docker deploy (Korean)](https://cassiiopeia.github.io/projectops/SSH-DOCKER-DEPLOYMENT-GUIDE)
320
- - [Flutter CI/CD (Korean)](https://cassiiopeia.github.io/projectops/FLUTTER-CICD-OVERVIEW)
321
- - [Troubleshooting (Korean)](https://cassiiopeia.github.io/projectops/TROUBLESHOOTING)
314
+ - [Agent Skills guide](https://cassiiopeia.github.io/projectops/en/skills)
315
+ - [Versioning](https://cassiiopeia.github.io/projectops/en/version-control)
316
+ - [Changelog automation](https://cassiiopeia.github.io/projectops/en/changelog-automation)
317
+ - [PR Preview](https://cassiiopeia.github.io/projectops/en/pr-preview)
318
+ - [Issue automation](https://cassiiopeia.github.io/projectops/en/issue-automation)
319
+ - [SSH + Docker deploy](https://cassiiopeia.github.io/projectops/en/ssh-docker-deployment-guide)
320
+ - [Flutter CI/CD](https://cassiiopeia.github.io/projectops/en/flutter-cicd-overview)
321
+ - [Troubleshooting](https://cassiiopeia.github.io/projectops/en/troubleshooting)
322
322
 
323
323
  ---
324
324
 
@@ -327,6 +327,7 @@ Browse and search everything on the **[docs site](https://cassiiopeia.github.io/
327
327
  - [Issues](https://github.com/Cassiiopeia/projectops/issues): bug reports and feature requests
328
328
  - Finished issues are marked with the `status: done` label and closed automatically when a release is merged (`close_on_release`).
329
329
  - [CONTRIBUTING.md](CONTRIBUTING.md): contribution guide
330
+ - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md): how we expect everyone to behave, and where to report problems
330
331
  - [SECURITY.md](SECURITY.md): please report security vulnerabilities privately, not in a public issue
331
332
 
332
333
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.36.4",
3
+ "version": "4.38.0",
4
4
  "description": "ProjectOps: installer CLI for a fully automated GitHub project management template (versioning, CI/CD, release notes, agent skills)",
5
5
  "keywords": [
6
6
  "devops",
package/src/cli/args.js CHANGED
@@ -5,17 +5,18 @@ import { VALID_TYPES } from "../context.js";
5
5
  import { SUPPORTED_LANGS, t } from "../i18n/index.js";
6
6
  import { REPO_LANGUAGES } from "../core/repo-language.js";
7
7
 
8
- export const DEPLOY_TARGETS = ["docker-ssh", "vercel", "none"];
9
- export const PUBLISH_TARGETS = ["nexus", "npm", "github-packages"];
10
- export const LABEL_STYLE_VALUES = ["en", "ko"];
8
+ import { DEPLOY_TARGETS, PUBLISH_TARGETS, INTENT_VALUES, LABEL_STYLES } from "../core/constants.js";
9
+
10
+ // 값 목록은 core/constants.js 가 정본이다. 기존 import 경로를 깨지 않도록 여기서 다시 내보낸다.
11
+ export { DEPLOY_TARGETS, PUBLISH_TARGETS, INTENT_VALUES };
12
+ export const LABEL_STYLE_VALUES = LABEL_STYLES;
11
13
  // 지원 언어의 단일 목록은 repo-language.js 다 (#787). 여기서 따로 들고 있으면 두 벌이 어긋난다.
12
14
  export const LANGUAGE_VALUES = REPO_LANGUAGES;
13
- export const INTENT_VALUES = ["app", "library", "both", "none", "manual"];
14
15
  const SEMVER_RE = /^\d+\.\d+\.\d+$/;
15
16
  // 유니코드 글자·숫자 허용 (#719) — 공백·셸 메타문자는 문자 클래스에 없어 그대로 거부된다.
16
17
  const BRANCH_RE = /^[\p{L}\p{N}][\p{L}\p{N}._/-]*$/u;
17
18
  // 허용 실행 모드 (#665) — 오타가 조용히 "복사 0건 성공"으로 끝나지 않게 한다.
18
- export const MODE_VALUES = ["full", "version", "workflows", "issues", "skills", "doctor", "interactive"];
19
+ export const MODE_VALUES = ["full", "version", "workflows", "issues", "skills", "doctor", "options", "interactive"];
19
20
 
20
21
  // argv(process.argv.slice(2)) → 파싱 결과. 오류 시 throw(호출부에서 exit 1).
21
22
  export function parseArgs(argv) {
@@ -33,6 +34,8 @@ export function parseArgs(argv) {
33
34
  includeSecretBackup: null,
34
35
  aiPrSummary: null, // #566 — AI 변경 요약 워크플로우 포함 여부
35
36
  projectsSync: null, // #716 — Projects 보드 동기화 워크플로우 포함 여부 (null=미지정)
37
+ json: false, // --json: 기계가 읽는 출력 (--mode options)
38
+ removeLegacy: false, // #809 — 구세대 배포 워크플로우(confirm 티어)도 .bak 으로 치운다
36
39
  pathsCsv: "", // "flutter=app,react=client" 원문 (정규화는 resolve 단계)
37
40
  force: false,
38
41
  lang: null, // --lang en|ko (null = decided by src/i18n resolveLang)
@@ -170,6 +173,8 @@ export function parseArgs(argv) {
170
173
  case "--no-ai-summary": result.aiPrSummary = false; break;
171
174
  case "--projects-sync": result.projectsSync = true; break;
172
175
  case "--no-projects-sync": result.projectsSync = false; break;
176
+ case "--remove-legacy": result.removeLegacy = true; break;
177
+ case "--json": result.json = true; break;
173
178
  case "--npm-publish":
174
179
  process.stderr.write(t("args.npmPublishDeprecated") + "\n");
175
180
  result.publishTargets = [...new Set([...(result.publishTargets ?? []), "npm"])];
package/src/cli/help.js CHANGED
@@ -7,8 +7,9 @@ Usage:
7
7
  npx projectops [options]
8
8
 
9
9
  Options:
10
- -m, --mode MODE Mode (full | version | workflows | issues | skills | doctor)
10
+ -m, --mode MODE Mode (full | version | workflows | issues | skills | doctor | options)
11
11
  doctor: diagnose the installation and repository settings (read-only)
12
+ options: print every flag and version.yml key; add --json for agents
12
13
  default: interactive
13
14
  -t, --type CSV Project types, comma separated (e.g. spring,react,python)
14
15
  supported: spring flutter react react-native
@@ -26,7 +27,9 @@ Options:
26
27
  --secret-backup / --no-secret-backup Include / exclude the secret backup workflow
27
28
  --ai-summary / --no-ai-summary Include / exclude the PR summary workflow
28
29
  --projects-sync / --no-projects-sync Include / exclude the GitHub Projects status sync workflow (default: off for new installs)
30
+ --remove-legacy Rename retired old-generation workflows to .bak (otherwise only listed)
29
31
  --nexus / --npm-publish (deprecated: use --publish nexus / --publish npm)
32
+ --json With --mode options: machine-readable output
30
33
  --force, -y, --yes Skip every confirmation and use non-interactive defaults
31
34
  --lang en|ko Display language (default: system language, pinned to en in CI and with --force)
32
35
  -v, --version Print the projectops version
@@ -45,8 +48,9 @@ const HELP_KO = `projectops — GitHub 프로젝트 자동화 템플릿 통합 C
45
48
  npx projectops [옵션]
46
49
 
47
50
  옵션:
48
- -m, --mode MODE 통합 모드 (full | version | workflows | issues | skills | doctor)
51
+ -m, --mode MODE 통합 모드 (full | version | workflows | issues | skills | doctor | options)
49
52
  doctor: 통합 상태·저장소 설정 진단 (읽기 전용)
53
+ options: 모든 플래그·version.yml 키 출력 (--json 이면 agent용 JSON)
50
54
  기본: interactive (대화형)
51
55
  -t, --type CSV 프로젝트 타입 csv (예: spring,react,python)
52
56
  지원: spring flutter react react-native
@@ -64,7 +68,9 @@ const HELP_KO = `projectops — GitHub 프로젝트 자동화 템플릿 통합 C
64
68
  --secret-backup / --no-secret-backup Secret 백업 워크플로우 포함/제외
65
69
  --ai-summary / --no-ai-summary PR 변경 요약 워크플로우 포함/제외
66
70
  --projects-sync / --no-projects-sync GitHub Projects 상태 동기화 워크플로우 포함/제외 (신규 설치 기본 제외)
71
+ --remove-legacy 은퇴한 구세대 워크플로우를 .bak 으로 치움 (없으면 목록만 안내)
67
72
  --nexus / --npm-publish (deprecated — --publish nexus / --publish npm 사용)
73
+ --json --mode options 와 함께: 기계가 읽는 JSON 출력
68
74
  --force, -y, --yes 모든 확인 생략, 비대화형 기본값 사용
69
75
  --lang en|ko 화면 언어 (기본: 시스템 언어, CI/--force 는 en 고정)
70
76
  -v, --version projectops 버전 출력
@@ -19,6 +19,7 @@ import { parseExisting } from "../core/version-yml.js";
19
19
  import { verifyInstall } from "../core/verify.js";
20
20
  import { readBaseline } from "../core/baseline.js";
21
21
  import { detectRepoName } from "../core/detect-fs.js";
22
+ import { detectMigrations } from "../core/migrations/index.js";
22
23
  import { t } from "../i18n/index.js";
23
24
 
24
25
  // 언어 전환(withLang 등) 시점에 평가되도록 함수로 둔다
@@ -149,6 +150,21 @@ export function localChecks(cwd = ".") {
149
150
  });
150
151
  }
151
152
 
153
+ // 은퇴한 구세대 워크플로우 (#809) — 업데이트는 감지만 하고 남겨 두므로 신형과 함께 돈다.
154
+ // 예전엔 로그에만 남아 doctor 가 "문제 없음"이라 했다.
155
+ const legacy = detectMigrations(cwd).confirm;
156
+ add({
157
+ name: t("doctor.legacy.name"), purpose: t("doctor.legacy.purpose"),
158
+ status: legacy.length ? "WARN" : "OK",
159
+ value: legacy.length ? t("doctor.count", { n: legacy.length }) : t("doctor.none"),
160
+ detail: legacy.length ? [
161
+ ...legacy.map((e) => ` ${e.file}${e.replacedBy ? ` → ${e.replacedBy}` : ""}`),
162
+ t("doctor.legacy.l1"),
163
+ ...legacy.map((e) => ` git rm .github/workflows/${e.file}`),
164
+ t("doctor.legacy.l2"),
165
+ ] : null,
166
+ });
167
+
152
168
  const baseline = readBaseline(cwd);
153
169
  add({
154
170
  name: t("doctor.bl.name"), purpose: t("doctor.bl.purpose"),
@@ -5,7 +5,8 @@ import { join } from "node:path";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { writeText } from "../core/fsutil.js";
7
7
  import { PATHS } from "../core/paths.js";
8
- import { buildVersionYml, mergeDeployValues } from "../core/version-yml.js";
8
+ import { installAgentGuide } from "../core/agent-guide.js";
9
+ import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
9
10
  import { markerForType } from "../core/detect.js";
10
11
  import { addVersionSectionToReadme } from "../core/copy/readme.js";
11
12
  import { copyWorkflows } from "../core/copy/workflows.js";
@@ -29,7 +30,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
29
30
  force = true, now, today, templateVersion = "unknown",
30
31
  deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, aiPrSummary = true,
31
32
  changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
32
- deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en" } = context;
33
+ deployBranch = "", intent = null, semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = "en", excludedWorkflows = null } = context;
33
34
 
34
35
  // project_paths 마커 계산 (.sh existing_marker_in_dir 등가 — 대표 마커명)
35
36
  const pathMarkers = new Map();
@@ -50,8 +51,9 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
50
51
  buildVersionYml({
51
52
  version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
52
53
  deployValues,
54
+ updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
53
55
  templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, aiPrSummary, optionsDate: today,
54
- changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language },
56
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit, intent, mode: "full", semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
55
57
  })), { version, versionCode });
56
58
 
57
59
  // 2. README 버전 섹션
@@ -86,6 +88,7 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
86
88
  { enabled: codeReviewCoderabbit });
87
89
  step("ensure-gitignore", () => ensureGitignore(targetRoot));
88
90
  step("copy-setup-guide", () => copySetupGuide(tempDir, targetRoot));
91
+ step("install-agent-guide", () => installAgentGuide(targetRoot, templateVersion)); // agent 가 옵션을 확인하는 법 안내
89
92
 
90
93
  // 상태 라벨 표기(#776) — 템플릿 원본은 영문이라, 한글 레포는 복사가 끝난 뒤 이름만 되돌린다.
91
94
  step("apply-label-style", () => applyLabelStyle(targetRoot, labelStyle), { labelStyle });
@@ -137,11 +137,13 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
137
137
  let deployBranchCreated = null; // #493 — 마법사가 직접 생성했는지 (가이드 기록용)
138
138
  let intent = existing?.options?.intent ?? null; // #485 프로젝트 성격
139
139
  // semver 자동 승격(#546) — 질문하지 않는다(#485 질문 부담 축소 방향 유지).
140
- // 저장값이 있으면 보존, 없으면 신규 통합만 ON. 기존 레포는 업데이트만으로 버전이 튀지 않는다.
141
- const semverAuto = existing?.options?.semverAuto ?? (existing ? false : true);
140
+ // 저장값(명시적 false 포함)은 보존하고, 키가 없으면 기존 레포도 ON. 켜진 사실은 완료 화면이 알린다.
141
+ const semverAuto = existing?.options?.semverAuto ?? true;
142
+ const semverAutoNewlyOn = !!existing && existing?.options?.semverAuto == null;
142
143
  const appRelease = existing?.options?.appRelease ?? null; // #553 저장값 보존 (묻지 않음)
143
144
  // 상태 라벨 표기(#776): 저장값 > (신규 en / 기존 ko). 기존 한글 레포에는 영문 전환을 한 번 제안한다(아래).
144
145
  const closeOnRelease = existing?.options?.closeOnRelease ?? (existing ? null : true); // #771 묻지 않는다
146
+ const excludedWorkflows = existing?.options?.excludedWorkflows ?? null; // #810 저장값 보존
145
147
  // Projects 보드 동기화(#716): 묻지 않는다. 저장값 > 이미 설치돼 있으면 유지 > 신규는 제외 (index.js 와 같은 규칙).
146
148
  const projectsSync = existing?.options?.projectsSync
147
149
  ?? existsSync(join(cwd, ".github/workflows/PROJECT-COMMON-PROJECTS-SYNC-MANAGER.yaml"));
@@ -328,7 +330,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
328
330
  const { now, today } = clock || utcNow();
329
331
  const ctx = createContext({
330
332
  mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
331
- codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language,
333
+ codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch, intent, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows,
332
334
  repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
333
335
  // #502 — version 모드가 기존 full 기록을 강등하지 않도록 (full이 우세)
334
336
  recordMode: existing?.templateMode === "full" ? "full" : "version",
@@ -423,7 +425,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
423
425
  migrationGuidePath = appendGuideEntry(cwd, {
424
426
  now, mode, types, repoName,
425
427
  templateFrom: existing?.templateVersion || "", templateTo: templateVersion,
426
- options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language },
428
+ options: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup, coderabbit: codeReviewCoderabbit, changelogProvider, intent, semverAuto , appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
427
429
  branches: { defaultBranch: branch, deployBranch, ready: deployBranchReady, created: deployBranchCreated },
428
430
  breaking: breakingReport, migrations: migrationsResult, orphans: orphanReport,
429
431
  events: trace.events, counters: { skipped: result?.workflows?.skipped ?? 0 },
@@ -437,6 +439,9 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
437
439
  counters: { workflows: result?.workflows?.copied ?? 0, workflowFiles: result?.workflows?.copiedFiles ?? [], utilModules: 0 },
438
440
  skippedConflicts: result?.workflows?.skippedConflicts ?? [], // #654 병합 안내
439
441
  replacedBak: result?.workflows?.replacedBak ?? [], // #673 기준점 없이 교체한 파일 안내
442
+ legacyLeftover: migrationsResult?.confirmPending ?? [], // #809 신·구 세대 동시 실행 안내
443
+ notInstalled: result?.workflows?.notInstalled ?? [], // #810 깔지 않은 워크플로우 안내
444
+ semverAutoNewlyOn,
440
445
  verification: result?.verification, // #549 설치 검증 결과
441
446
  aiPrSummary, codeReviewCoderabbit, // #569 — 고른 것만 안내
442
447
  logDir: files ? MIGRATION_DIR : null, // #561 기록 위치 안내
@@ -5,7 +5,8 @@ import { existsSync, readFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { writeText } from "../core/fsutil.js";
7
7
  import { PATHS } from "../core/paths.js";
8
- import { buildVersionYml, mergeDeployValues } from "../core/version-yml.js";
8
+ import { installAgentGuide } from "../core/agent-guide.js";
9
+ import { buildVersionYml, mergeDeployValues, resolveUpdatedBy } from "../core/version-yml.js";
9
10
  import { markerForType } from "../core/detect.js";
10
11
  import { addVersionSectionToReadme } from "../core/copy/readme.js";
11
12
  import { applyLabelStyle } from "../core/label-style.js";
@@ -16,7 +17,7 @@ export function runVersion(context, tempDir, targetRoot = ".") {
16
17
  const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
17
18
  now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
18
19
  changelogProvider = "commit", changelogBaseUrl = "", codeReviewCoderabbit = true,
19
- deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null } = context;
20
+ deployBranch = "", recordMode = "version", semverAuto = true , appRelease = null, labelStyle = "en", closeOnRelease = null, projectsSync = null, language = null, excludedWorkflows = null } = context;
20
21
 
21
22
  const pathMarkers = new Map();
22
23
  for (const [t] of paths) pathMarkers.set(t, markerForType(t));
@@ -29,10 +30,11 @@ export function runVersion(context, tempDir, targetRoot = ".") {
29
30
  buildVersionYml({
30
31
  version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
31
32
  deployValues,
33
+ updatedBy: resolveUpdatedBy(existsSync(vyFile) ? readFileSync(vyFile, "utf8") : "", targetRoot), // #811
32
34
  // mode(#502): version 모드가 기존 full 통합 기록을 "version"으로 강등하지 않도록
33
35
  // 호출부가 recordMode로 기존 값을 넘긴다 (full이 우세 — 업데이트 재실행 범위 축소 방지).
34
36
  templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
35
- changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language },
37
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit, mode: recordMode, semverAuto, appRelease, labelStyle, closeOnRelease, projectsSync, language, excludedWorkflows },
36
38
  }));
37
39
  addVersionSectionToReadme(version, targetRoot);
38
40
  copyScripts(tempDir, targetRoot);
@@ -40,4 +42,5 @@ export function runVersion(context, tempDir, targetRoot = ".") {
40
42
  applyLabelStyle(targetRoot, labelStyle); // #776
41
43
  ensureGitignore(targetRoot);
42
44
  copySetupGuide(tempDir, targetRoot);
45
+ installAgentGuide(targetRoot, templateVersion);
43
46
  }
@@ -9,6 +9,7 @@ import { applyLabelStyle } from "../core/label-style.js";
9
9
  import { copyScripts, copyConfigFolder, copySetupGuide } from "../core/copy/simple.js";
10
10
  import { copyUtilModules } from "../core/copy/util.js";
11
11
  import { convertLegacySingularType, mergeDeployValues } from "../core/version-yml.js";
12
+ import { installAgentGuide } from "../core/agent-guide.js";
12
13
  import { verifyInstall } from "../core/verify.js";
13
14
 
14
15
  export function runWorkflows(context, tempDir, targetRoot = ".", hooks = {}) {
@@ -35,6 +36,7 @@ export function runWorkflows(context, tempDir, targetRoot = ".", hooks = {}) {
35
36
  applyLabelStyle(targetRoot, labelStyle); // #776 — 워크플로우(QA 봇)와 라벨 정의를 같은 표기로 맞춘다
36
37
  for (const t of types) copyUtilModules(tempDir, t, { force }, targetRoot);
37
38
  copySetupGuide(tempDir, targetRoot);
39
+ installAgentGuide(targetRoot, context.templateVersion);
38
40
 
39
41
  // 설치 후 검증 (#549) — 워크플로우만 설치하는 모드라 오히려 더 필요하다.
40
42
  const verification = verifyInstall(targetRoot);
package/src/context.js CHANGED
@@ -27,10 +27,11 @@ export function createContext(overrides = {}) {
27
27
  codeReviewCoderabbit: null,
28
28
  deployBranch: "", // 릴리스 PR head 브랜치 (#456). 빈 값=metadata.deploy_branch 미출력
29
29
  intent: null, // 프로젝트 성격 (#485 — app/library/both/none/manual). null=미설정(역추론)
30
- semverAuto: null, // semver 자동 승격 (#546). null=미설정(신규 통합 true / 기존 레포 false)
30
+ semverAuto: null, // semver 자동 승격 (#546). null=미설정 → 신규·기존 모두 true (#814). 명시적 false 만 끈다
31
31
  closeOnRelease: null, // 릴리스 시 완료 이슈 닫기 (#771). null=미설정(신규 true / 기존 레포 키 없음)
32
32
  language: null, // 레포 문구 언어 (#769). null=미설정(신규 en / 기존 레포 ko)
33
33
  labelStyle: null, // 상태 라벨 표기 (#776). null=미설정(신규 en / 기존 레포 ko)
34
+ excludedWorkflows: null, // 설치하지 않을 워크플로우 파일명 (#810). null=없음
34
35
  appRelease: null, // 앱 심사 배포 레포인가 (#553). null=미설정(키 기록 안 함)
35
36
  templateVersion: "",
36
37
  tempDir: "",
@@ -0,0 +1,44 @@
1
+ // 사용자 레포에 설치하는 AI agent 안내문 (.github/.projectops/AGENT-GUIDE.md).
2
+ //
3
+ // 왜 파일인가: 이 레포의 AGENTS.md 는 템플릿 전용이라 사용자 프로젝트로 가지 않는다. 그래서 projectops 가
4
+ // 깔린 레포에서 일하는 agent 는 "이 레포가 무엇으로 관리되는지, 옵션을 어디서 확인하는지"를 알 길이 없었다.
5
+ // 레포 루트의 AGENTS.md·CLAUDE.md 는 사용자 것이라 건드리지 않고, 폴더 안에 두고 한 줄 포인터만 안내한다.
6
+ //
7
+ // 내용에 옵션 표를 박지 않는다 — 박으면 곧 낡는다. 표는 항상 `npx projectops --mode options --json` 이 정본이다.
8
+ import { join } from "node:path";
9
+ import { writeText } from "./fsutil.js";
10
+
11
+ export const AGENT_GUIDE_PATH = ".github/.projectops/AGENT-GUIDE.md";
12
+
13
+ export function agentGuideText(templateVersion = "") {
14
+ return `# projectops — guide for AI agents
15
+
16
+ This repository is managed with [projectops](https://github.com/Cassiiopeia/projectops)${templateVersion ? ` (installed template ${templateVersion})` : ""}.
17
+ GitHub Actions workflows in \`.github/workflows/PROJECT-*\` handle versioning, release notes, CI/CD and issue automation.
18
+ This file is generated by the installer and overwritten on update. Do not edit it.
19
+
20
+ ## Ask the tool, do not guess
21
+
22
+ \`\`\`bash
23
+ npx projectops --mode options --json # every CLI flag and every version.yml key, with allowed values and defaults
24
+ npx projectops --mode doctor # read-only diagnosis of this install and the repository settings
25
+ \`\`\`
26
+
27
+ Both are read-only and need no network.
28
+
29
+ ## Rules
30
+
31
+ - Settings live in \`version.yml\` under \`metadata.template.options\`. The options table says which keys are safe to edit by hand.
32
+ - \`version\` in \`version.yml\` is the single source of the project version. Workflows bump it on release; do not bump it by hand unless asked.
33
+ - Commit titles decide the release bump when \`semver_auto\` is on: \`feat:\` = minor, \`feat!:\` = major, anything else = patch.
34
+ - Running the installer again updates workflows. Files you changed are kept (a copy of the new version is saved in \`.github/.projectops/incoming/\`). Files you deleted are not restored.
35
+ - To stop a template workflow from ever being installed, list its file name in \`options.excluded_workflows\`.
36
+ - Run the installer from an agent or CI only with an explicit mode and \`--force\`, e.g. \`npx projectops --mode workflows --force\`. Without \`--force\` it asks questions and blocks.
37
+ - Do not edit \`.github/.projectops/logs\`, \`baseline.json\` or \`incoming/\`. They are diagnostics for the next update.
38
+ `;
39
+ }
40
+
41
+ export function installAgentGuide(targetRoot = ".", templateVersion = "") {
42
+ writeText(join(targetRoot, AGENT_GUIDE_PATH), agentGuideText(templateVersion));
43
+ return AGENT_GUIDE_PATH;
44
+ }
@@ -0,0 +1,28 @@
1
+ // 여러 파일이 함께 써야 하는 값 목록의 정본.
2
+ //
3
+ // 왜 한 곳인가: deploy·publish 목록이 CLI 인자 검사(args.js), 마법사 질문(options-ask.js),
4
+ // 복사 엔진(copy/workflows.js), version.yml 파서(version-yml.js), 옵션 표(options-schema.js)
5
+ // 다섯 곳에 따로 적혀 있었다. 하나만 고치면 "CLI 는 받는데 파서가 버린다" 같은 조용한 불일치가 생긴다.
6
+ // 값을 추가할 때는 여기 한 줄만 고치고, 그 값에 해당하는 워크플로우 폴더를 만든다
7
+ // (deploy: project-types/common/deploy/<값>/ · publish: project-types/<type>/publish/<값>/).
8
+ //
9
+ // 이 파일은 아무것도 import 하지 않는다 — 어디서 불러도 순환 import 가 생기지 않게.
10
+ // 레포 문구 언어 목록은 repo-language.js 의 REPO_LANGUAGES 가 정본이다 (번역 파일과 같이 검사된다).
11
+
12
+ /** 실행물을 어디에 배포하나 — 택1 (#439) */
13
+ export const DEPLOY_TARGETS = Object.freeze(["docker-ssh", "vercel", "none"]);
14
+
15
+ /** 라이브러리를 어느 레지스트리에 내나 — 0..n (#439) */
16
+ export const PUBLISH_TARGETS = Object.freeze(["nexus", "npm", "github-packages"]);
17
+
18
+ /** 프로젝트 성격 — deploy/publish 질문을 유도한다 (#485) */
19
+ export const INTENT_VALUES = Object.freeze(["app", "library", "both", "none", "manual"]);
20
+
21
+ /** 상태 라벨 표기 — en: `status: todo`, ko: 기존 한글 `작업전` (#776) */
22
+ export const LABEL_STYLES = Object.freeze(["en", "ko"]);
23
+
24
+ /**
25
+ * 릴리스 노트 생성기 (#455, #566). 기본값은 commit — 외부 의존이 없어 어디서나 결과가 나온다.
26
+ * 실제 구현은 .github/scripts/changelog_providers/ 에 있고, 그쪽 목록과의 정합은 테스트가 본다.
27
+ */
28
+ export const CHANGELOG_PROVIDERS = Object.freeze(["copilot", "coderabbit", "openai", "gemini", "claude", "groq", "mistral", "ollama", "commit"]);
@@ -1,6 +1,6 @@
1
1
  // 단순 복사 함수 (무조건 덮어쓰기류) — .sh copy_scripts/config/issue/discussion/setup_guide 등가.
2
2
  // 실측: template_integrator.sh 3818, 3845, 3872, 3895, 4114.
3
- import { join } from "node:path";
3
+ import { join, basename } from "node:path";
4
4
  import { chmodSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { PATHS } from "../paths.js";
6
6
  import { exists, copyFileSync, copyDirSync } from "../fsutil.js";
@@ -97,7 +97,8 @@ export function stripIssueTemplateAssignees(targetRoot = ".") {
97
97
  // .github/ISSUE_TEMPLATE/ 전체 + PULL_REQUEST_TEMPLATE.md 덮어쓰기.
98
98
  export function copyIssueTemplates(tempDir, targetRoot = ".") {
99
99
  const srcIssue = join(tempDir, ".github", "ISSUE_TEMPLATE");
100
- if (exists(srcIssue)) copyDirSync(srcIssue, join(targetRoot, ".github", "ISSUE_TEMPLATE"));
100
+ // `projectops-` 로 시작하는 파일은 이 저장소(projectops 자체)의 이슈 양식이라 사용자 레포로 복사하지 않는다 (#797).
101
+ if (exists(srcIssue)) copyDirSync(srcIssue, join(targetRoot, ".github", "ISSUE_TEMPLATE"), { filter: (src) => !basename(src).startsWith("projectops-") });
101
102
  const srcPr = join(tempDir, ".github", "PULL_REQUEST_TEMPLATE.md");
102
103
  if (exists(srcPr)) copyFileSync(srcPr, join(targetRoot, ".github", "PULL_REQUEST_TEMPLATE.md"));
103
104
  }