Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(git rev-parse --show-toplevel)"

bash "$ROOT_DIR/scripts/build-runtime-plugins.sh" --check

generated_paths='^(plugins/ndf-claude|plugins/ndf-codex|plugins/ndf-kiro|plugins/mcp/claude|plugins/mcp/codex|plugins/mcp/kiro)/'
unstaged_generated="$(git -C "$ROOT_DIR" diff --name-only | grep -E "$generated_paths" || true)"
if [ -n "$unstaged_generated" ]; then
echo "Generated runtime files have unstaged changes. Run build and git add the generated files:" >&2
echo "$unstaged_generated" >&2
exit 1
fi
6 changes: 6 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(git rev-parse --show-toplevel)"

bash "$ROOT_DIR/scripts/validate-runtime-plugins.sh"
67 changes: 67 additions & 0 deletions .github/workflows/runtime-plugin-validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Runtime plugin validation

on:
pull_request:
paths:
- ".agents/**"
- ".claude-plugin/**"
- ".github/workflows/runtime-plugin-validate.yml"
- ".githooks/**"
- "AGENTS.md"
- "CLAUDE.md"
- "KIRO.md"
- "README.md"
- "docs/**"
- "plugins/**"
- "scripts/**"
push:
branches:
- main
- "release/**"
paths:
- ".agents/**"
- ".claude-plugin/**"
- ".github/workflows/runtime-plugin-validate.yml"
- ".githooks/**"
- "AGENTS.md"
- "CLAUDE.md"
- "KIRO.md"
- "README.md"
- "docs/**"
- "plugins/**"
- "scripts/**"

jobs:
runtime-plugin-build-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: bash scripts/build-runtime-plugins.sh --check

runtime-plugin-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: npm install -g @anthropic-ai/claude-code
- run: bash scripts/validate-runtime-plugins.sh

markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- run: python3 scripts/check-markdown-links.py --root .
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ ai-plugins/
└── CLAUDE.md # AIエージェント向けガイドライン
```

#### Runtime plugin の検証

共通ソースや runtime 配布物を変更した場合は、生成物同期と manifest / link 検証を実行します。

```bash
bash scripts/build-runtime-plugins.sh
bash scripts/validate-runtime-plugins.sh
```

ローカル hook を使う場合は以下を実行します。

```bash
bash scripts/install-dev-hooks.sh
```

#### 新しいプラグインの作成手順

**1. プラグインディレクトリを作成:**
Expand Down
5 changes: 4 additions & 1 deletion docs/ndf-plugin-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ Claude Code と Codex は marketplace から同じ plugin 名で install しま
```bash
bash scripts/build-runtime-plugins.sh
bash scripts/build-runtime-plugins.sh --check
bash scripts/validate-runtime-plugins.sh
claude plugin validate plugins/ndf-claude
python3 -m json.tool plugins/ndf-codex/.codex-plugin/plugin.json >/dev/null
bash plugins/ndf-kiro/install.sh --help
```

`--check` は `plugins/ndf-*` と `plugins/mcp/claude|codex|kiro` の生成物が共通編集元と同期していることを検証します。
`--check` は `plugins/ndf-*` と `plugins/mcp/claude|codex|kiro` の生成物が共通編集元と同期していることを検証します。`validate-runtime-plugins.sh` は生成物同期、JSON / manifest、marketplace source、Kiro installer、Markdown ローカルリンクをまとめて確認します。

ローカル hook を使う場合は `bash scripts/install-dev-hooks.sh` で `.githooks/` を有効化します。

## 外部 AI 委譲

Expand Down
15 changes: 15 additions & 0 deletions docs/plugin-development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ plugins/{plugin-name}/
/plugin install {plugin-name}@ai-plugins
```

### Runtime plugin 検証

NDF / MCP の runtime 別配布物を変更した場合は、以下を実行します。

```bash
bash scripts/build-runtime-plugins.sh
bash scripts/validate-runtime-plugins.sh
```

ローカル hook は任意で導入できます。

```bash
bash scripts/install-dev-hooks.sh
```

### 検証チェックリスト

- [ ] marketplace.jsonが正しい形式
Expand Down
12 changes: 6 additions & 6 deletions issues/agent-runtime-plugin-split.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,17 +549,17 @@ base branch: `main`
| merge 済み | #47 | `feature/runtime-split-claude` | Task 2: `plugins/ndf-claude` を追加し、Claude marketplace を切替 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。worktree / local branch cleanup 済み |
| merge 済み | #48 | `feature/runtime-split-codex` | Task 3: `plugins/ndf-codex` を追加し、Codex marketplace を切替 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。worktree / local branch cleanup 済み |
| merge 済み | #49 | `feature/runtime-split-kiro` | Task 4: `plugins/ndf-kiro` を作成し、Kiro installer / docs / templates を移動 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。worktree / local branch cleanup 済み |
| 実装中 | #50 | `feature/runtime-split-docs-cleanup` | Task 5: README / AGENTS / docs / specs 更新、旧 `plugins/ndf` の削除 | 旧 monolithic plugin directory を削除し、docs / README / AGENTS を runtime 分離後の構成へ更新中 |
| merge 済み | #50 | `feature/runtime-split-docs-cleanup` | Task 5: README / AGENTS / docs / specs 更新、旧 `plugins/ndf` の削除 | cross-review approved。`release/runtime-plugin-split` へ merge 済み。merge 後検証済み |
| merge 済み | #51 / #54 | `feature/runtime-split-mcp-plugins` | Task 6: MCP plugin を runtime 別に分離 | #51 はローテーションで close。#54 が cross-review approved、`release/runtime-plugin-split` へ merge 済み |
| 未着手 | #52 | `feature/runtime-split-validation` | Task 7: validate script、dev hook、CI、リンク検証 | #50 merge 後に実施 |
| 実装中 | #52 | `feature/runtime-split-validation` | Task 7: validate script、dev hook、CI、リンク検証 | `validate-runtime-plugins.sh`、`.githooks/`、GitHub Actions、Markdown link check を追加。ローカル検証通過 |
| 未着手 | #53 | `feature/runtime-split-container-smoke` | Task 8: runtime smoke test | PR7 merge 後 |

再開時の前提:

- 現在の作業ブランチは `feature/runtime-split-docs-cleanup`。
- `release/runtime-plugin-split` には PR #46 / #47 / #48 / #49 / #54 が merge 済み。
- 次は PR #50docs cleanup を push し、cross-review 後に `release/runtime-plugin-split` へ merge する。
- PR #50 merge 後に PR #52 `feature/runtime-split-validation`、PR #53 `feature/runtime-split-container-smoke` の順で進める
- 現在の作業ブランチは `feature/runtime-split-validation`。
- `release/runtime-plugin-split` には PR #46 / #47 / #48 / #49 / #50 / #54 が merge 済み。
- 次は PR #52validation 実装を push し、cross-review 後に `release/runtime-plugin-split` へ merge する。
- PR #52 merge 後に PR #53 `feature/runtime-split-container-smoke` を進める

## 影響範囲

Expand Down
110 changes: 110 additions & 0 deletions scripts/check-markdown-links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Check repository-relative Markdown links.

External URLs, mailto links, pure anchors, and absolute filesystem paths are
ignored. This keeps the check stable in CI while still catching broken links
between repository documents.
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path
from urllib.parse import unquote, urlparse


LINK_RE = re.compile(r"(?<!!)\[[^\]]+\]\(([^)]+)\)")
INLINE_HTML_RE = re.compile(r"<a\s+[^>]*href=[\"']([^\"']+)[\"']", re.IGNORECASE)
TITLE_RE = re.compile(r"\s+(?:\"[^\"]*\"|'[^']*'|\([^)]*\))\s*$")


def iter_markdown_files(root: Path) -> list[Path]:
roots = [root / "README.md", root / "AGENTS.md", root / "CLAUDE.md", root / "KIRO.md", root / "docs", root / "plugins"]
files: list[Path] = []
for item in roots:
if item.is_file():
files.append(item)
elif item.is_dir():
files.extend(item.rglob("*.md"))
return sorted(set(files))


def strip_title(target: str) -> str:
target = target.strip()
if target.startswith("<") and target.endswith(">"):
target = target[1:-1].strip()
return TITLE_RE.sub("", target)


def should_skip(target: str) -> bool:
if not target or target.startswith("#"):
return True
parsed = urlparse(target)
if parsed.scheme or target.startswith("//"):
return True
if target.startswith("/"):
return True
if "{" in target or "}" in target:
return True
return False


def target_path(root: Path, source: Path, raw_target: str) -> Path | None:
target = strip_title(raw_target)
if should_skip(target):
return None
path_part = target.split("#", 1)[0]
if not path_part:
return None
path_part = unquote(path_part)
return (source.parent / path_part).resolve()


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default=".", help="repository root")
args = parser.parse_args()

root = Path(args.root).resolve()
failures: list[str] = []

for md in iter_markdown_files(root):
raw_lines = md.read_text(encoding="utf-8").splitlines()
filtered: list[str] = []
in_fence = False
for line in raw_lines:
if line.lstrip().startswith("```"):
in_fence = not in_fence
continue
if in_fence or line.lstrip().startswith(">"):
continue
filtered.append(line)
text = "\n".join(filtered)
targets = [m.group(1) for m in LINK_RE.finditer(text)]
targets.extend(m.group(1) for m in INLINE_HTML_RE.finditer(text))
for raw in targets:
resolved = target_path(root, md, raw)
if resolved is None:
continue
try:
resolved.relative_to(root)
except ValueError:
failures.append(f"{md.relative_to(root)}: link escapes repository: {raw}")
continue
if not resolved.exists():
failures.append(f"{md.relative_to(root)}: missing link target: {raw}")

if failures:
print("Markdown link check failed:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1

print("Markdown local links are valid")
return 0


if __name__ == "__main__":
raise SystemExit(main())
12 changes: 12 additions & 0 deletions scripts/install-dev-hooks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

if [ ! -d "$ROOT_DIR/.git" ] && ! git -C "$ROOT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
echo "ERROR: not a git repository: $ROOT_DIR" >&2
exit 1
fi

git -C "$ROOT_DIR" config core.hooksPath .githooks
echo "Installed development hooks: core.hooksPath=.githooks"
Loading
Loading