@ryusenkei/conventions 0.2.0 → 0.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.
package/README.md CHANGED
@@ -53,7 +53,7 @@ The PreToolUse hook blocks the call and tells Claude why, so Claude can fix it a
53
53
  | PR title = squash commit message | Checks `gh pr create` / `gh pr edit` titles with prose-lint's commit rules |
54
54
  | No AI signature | `attribution` is empty, and the hook blocks PR bodies with an AI signature |
55
55
  | Squash merge only | Blocks `gh pr merge --merge` / `--rebase` |
56
- | Merge only after CI passes | Blocks `gh pr merge` unless every check of the PR has passed (`gh pr checks`). Pending, failed, missing, or unreadable checks are blocked, also with `--auto` |
56
+ | Merge only after CI passes | Blocks `gh pr merge` unless every check of the PR has passed (`gh pr checks`). Pending, failed, missing, or unreadable checks are blocked, also with `--auto` (with the epic flow, a PR into `dev/*` may have no checks) |
57
57
  | Epic flow (opt-in) | See [Epic flow](#epic-flow) |
58
58
  | Do not edit `node_modules` | Blocks Edit and Write under `node_modules` |
59
59
  | Secrets | `permissions.deny` blocks reading `.env` and `.dev.vars` with Claude's file tools (not Bash) |
@@ -74,6 +74,7 @@ Opt in per repository in the root `package.json`:
74
74
  - Each epic gets an integration branch `dev/<issue>-<name>` (e.g. `dev/12-search`). Sub-issue PRs target it, and only a `dev/*` branch is merged into `main` (on deploy).
75
75
  - `gh pr create` to `main` (`--base main`, or no `--base`) is blocked unless the head (`--head`, or the current branch) is `dev/*`.
76
76
  - A PR from any other branch must target a `dev/*` base. `gh pr edit` is checked only when it changes `--base`.
77
+ - A PR into `dev/*` runs no CI; the pre-push `pnpm check` is the gate. `gh pr merge` allows such a PR with no checks, but any checks it has must still pass.
77
78
  - Without the opt-in, none of this applies and `dev/*` branch names are rejected.
78
79
 
79
80
  ## Synced files
@@ -81,6 +82,7 @@ Opt in per repository in the root `package.json`:
81
82
  `sync` writes these files and `check` compares them, ignoring line endings:
82
83
 
83
84
  - `.editorconfig`, `.gitattributes`, `.gitignore`, `.gitleaks.toml`, `mise.toml`, `.github/dependabot.yaml`, `.githooks/commit-msg`, `.githooks/pre-push`
85
+ - `.github/dependabot.yaml` updates npm and GitHub Actions. When the repository has a `go.mod` at the root, a `gomod` entry is appended (same schedule, rebase strategy and cooldown, minor and patch grouped). Without one it is left out, because Dependabot fails on a missing `go.mod`
84
86
  - `.claude/settings.json`: only the hooks, `attribution`, and `permissions.deny` are managed. Other settings and hooks are kept
85
87
  - `pnpm-workspace.yaml`: `minimumReleaseAge` and `minimumReleaseAgeExclude`, matching the Dependabot `cooldown`
86
88
 
package/dist/files.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // 利用側のリポにある共通ファイルの同期・検査
2
2
  // - マスタ: パッケージの `files/`(npm は `.gitignore` を同梱しない → 先頭の `.` を除いた名前で格納)
3
3
  // - 比較は改行コードを LF に正規化(Windows の作業ツリーは CRLF になりうる)
4
+ // - `append`: 条件のファイルがあるリポのみ末尾に追加(例: go.mod のないリポで gomod の更新はエラー)
4
5
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
6
  import { dirname, join } from 'node:path';
6
7
  import { hasStop, managedSettings } from "./settings.js";
@@ -10,7 +11,7 @@ const FILES = [
10
11
  { from: 'gitignore', to: '.gitignore' },
11
12
  { from: 'gitleaks.toml', to: '.gitleaks.toml' },
12
13
  { from: 'mise.toml', to: 'mise.toml' },
13
- { from: 'dependabot.yaml', to: '.github/dependabot.yaml' },
14
+ { from: 'dependabot.yaml', to: '.github/dependabot.yaml', append: { from: 'dependabot-gomod.yaml', if: 'go.mod' } },
14
15
  { from: 'commit-msg', to: '.githooks/commit-msg', mode: 0o755 },
15
16
  { from: 'pre-push', to: '.githooks/pre-push', mode: 0o755 },
16
17
  ];
@@ -27,11 +28,12 @@ const RELEASE_AGE = [
27
28
  ];
28
29
  const lf = (s) => s.replaceAll('\r\n', '\n');
29
30
  const read = (path) => (existsSync(path) ? lf(readFileSync(path, 'utf8')) : undefined);
31
+ const master = (name) => lf(readFileSync(new URL(`../files/${name}`, import.meta.url), 'utf8'));
30
32
  /** マスタとの差分 */
31
33
  export function drifts(root, options = {}) {
32
34
  const out = [];
33
35
  for (const f of FILES) {
34
- const expected = lf(readFileSync(new URL(`../files/${f.from}`, import.meta.url), 'utf8'));
36
+ const expected = master(f.from) + (f.append && existsSync(join(root, f.append.if)) ? master(f.append.from) : '');
35
37
  if (read(join(root, f.to)) !== expected)
36
38
  out.push({ path: f.to, expected, message: 'マスタと不一致' });
37
39
  }
package/dist/hook.js CHANGED
@@ -14,7 +14,7 @@ const root = process.env.CLAUDE_PROJECT_DIR ?? input.cwd ?? process.cwd();
14
14
  // Windows の `pnpm` は `.cmd` → シェル経由でのみ起動可能
15
15
  const shell = process.platform === 'win32';
16
16
  if (input.hook_event_name === 'PreToolUse') {
17
- const reason = denyReason(input, { root, titleIssues, readFile, currentBranch, prHead, prChecks });
17
+ const reason = denyReason(input, { root, titleIssues, readFile, currentBranch, prHead, prBase, prChecks });
18
18
  if (reason) {
19
19
  process.stderr.write(`${reason}\n`);
20
20
  process.exit(2);
@@ -53,6 +53,10 @@ function prHead(pr, cwd) {
53
53
  const r = spawnSync('gh', ['pr', 'view', ...(pr ? [pr] : []), '--json', 'headRefName', '--jq', '.headRefName'], { cwd: cwd ?? root, encoding: 'utf8' });
54
54
  return (r.status === 0 && r.stdout.trim()) || undefined;
55
55
  }
56
+ function prBase(pr, cwd) {
57
+ const r = spawnSync('gh', ['pr', 'view', ...(pr ? [pr] : []), '--json', 'baseRefName', '--jq', '.baseRefName'], { cwd: cwd ?? root, encoding: 'utf8' });
58
+ return (r.status === 0 && r.stdout.trim()) || undefined;
59
+ }
56
60
  // `gh pr checks` は実行中・失敗で終了コードが0以外(8・1)→ 終了コードではなく出力で判定
57
61
  function prChecks(pr, cwd) {
58
62
  const r = spawnSync('gh', ['pr', 'checks', ...(pr ? [pr] : []), '--json', 'bucket'], { cwd: cwd ?? root, encoding: 'utf8' });
package/dist/policy.d.ts CHANGED
@@ -18,6 +18,8 @@ export type Context = {
18
18
  currentBranch: (cwd: string | undefined) => string | undefined;
19
19
  /** PR の head のブランチ(`pr` の省略時は現在のブランチの PR) */
20
20
  prHead: (pr: string | undefined, cwd: string | undefined) => string | undefined;
21
+ /** PR の base のブランチ(`pr` の省略時は現在のブランチの PR) */
22
+ prBase: (pr: string | undefined, cwd: string | undefined) => string | undefined;
21
23
  /** PR のチェックの集計(`pr` の省略時は現在のブランチの PR) */
22
24
  prChecks: (pr: string | undefined, cwd: string | undefined) => Checks;
23
25
  };
package/dist/policy.js CHANGED
@@ -195,11 +195,20 @@ function prReasons(sub, args, cwd, ctx) {
195
195
  return reasons;
196
196
  }
197
197
  // `--auto` も対象(必須のチェックがないリポでは即時にマージされる)
198
+ // - epic のフローの dev/* への PR: チェックなしを許可(CI を実行せず pre-push の `pnpm check` が関門)
198
199
  function mergeReasons(args, cwd, ctx) {
199
200
  if (args.some((a) => a === '--merge' || a === '--rebase' || a === '-m' || a === '-r'))
200
201
  return ['マージは squash のみ(`--squash`)'];
201
- const checks = ctx.prChecks(positionals(args, MERGE_VALUE)[0], cwd);
202
- return checks === 'pass' ? [] : [CHECKS_REASON[checks]];
202
+ const pr = positionals(args, MERGE_VALUE)[0];
203
+ const checks = ctx.prChecks(pr, cwd);
204
+ if (checks === 'pass')
205
+ return [];
206
+ if (checks === 'none' && epicFlow(ctx)) {
207
+ const base = ctx.prBase(pr, cwd);
208
+ if (base !== undefined && DEV_BRANCH.test(base))
209
+ return [];
210
+ }
211
+ return [CHECKS_REASON[checks]];
203
212
  }
204
213
  // epic のフロー: main への PR は dev/* から、それ以外のブランチの PR は dev/* へ
205
214
  // - `gh pr create` の base の省略 = main(既定のブランチ)
@@ -0,0 +1,19 @@
1
+
2
+ # Go のモジュール(ルートに go.mod があるリポのみ `sync` が追加)
3
+ - package-ecosystem: gomod
4
+ directory: "/"
5
+ # 頻度・rebase・公開直後のバージョンの回避は npm と同じ
6
+ schedule:
7
+ interval: monthly
8
+ rebase-strategy: disabled
9
+ cooldown:
10
+ default-days: 2
11
+ commit-message:
12
+ prefix: build
13
+ include: scope
14
+ groups:
15
+ # minor・patch は1つの PR にまとめる(Go の major はモジュールのパスが変わるため Dependabot の対象外)
16
+ minor-and-patch:
17
+ update-types:
18
+ - minor
19
+ - patch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryusenkei/conventions",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "RYUSENKEI のモノリポの共通の規約(Claude Code の規約・hook、共通ファイルの同期)",
5
5
  "license": "MIT",
6
6
  "author": "RYUSENKEI",
package/rules.md CHANGED
@@ -39,6 +39,7 @@
39
39
  - マージ: CI の全チェックの成功を確認してから(フックで強制)
40
40
  - epic のフロー(有効にしたリポのみ)
41
41
  - epic ごとに `dev/<issue の番号>-<内容>` を作成し、sub-issue の PR は dev のブランチへ
42
+ - dev のブランチへのマージは CI なし、pre-push の `pnpm check` が関門(チェックがある場合は成功が必要)
42
43
  - `main` へのマージはデプロイのときだけ dev のブランチから
43
44
 
44
45
  ## 完了の条件