From 1a4ebffe1b8a28d109d17ffb83cbf2acb2107ffe Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Thu, 23 Jul 2026 18:06:34 +0800 Subject: [PATCH 1/3] feat(harness): add PR-title CI lint, contexts/docs boundary, and commit-body wrap rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the remaining #134 work. - .github/workflows/pr-title.yml lints the PR title (which becomes the squash-merge commit subject) as an English Conventional Commit, reusing scripts/hooks/commit_msg_check.py so the convention keeps one source. This is the server-side floor for external contributors, whom the local commit-msg git hook cannot bind because it only fires after `pre-commit install`. - contexts/README.md now states the contexts/ vs docs/ boundary explicitly: contexts/ is internal navigation and design memory for agents and maintainers; docs/ is the user-facing catalog and guides. This was the last open acceptance criterion in #134. - commit.md (both skill copies) adds the commit-body convention: follow the same no-hard-wrap prose rule as GitHub bodies, so a squash-merge keeps a clean description. This commit message follows it — one physical line per list item. - contexts/dev/harness-engineering.md records the PR-title lint in the enforcement table and notes that making it a required status check is a one-time repo-settings toggle. verify.sh passes (426 passed, 85.65% coverage). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../skills/quantmind-dev/references/commit.md | 5 ++++ .../skills/quantmind-dev/references/commit.md | 5 ++++ .github/workflows/pr-title.yml | 26 +++++++++++++++++++ contexts/README.md | 1 + contexts/dev/harness-engineering.md | 3 ++- 5 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-title.yml diff --git a/.agents/skills/quantmind-dev/references/commit.md b/.agents/skills/quantmind-dev/references/commit.md index c6e12de..fc00d87 100644 --- a/.agents/skills/quantmind-dev/references/commit.md +++ b/.agents/skills/quantmind-dev/references/commit.md @@ -16,6 +16,11 @@ English, [Conventional Commits](https://www.conventionalcommits.org/): `chore(verify): ...`, `docs(news): ...`). Omit the scope only for repo-wide changes (`feat: flows + magic apex layer`). - **summary**: imperative, lower-case start, no trailing period. +- **body** (optional): explain what changed and why. Follow the same + no-hard-wrap prose rule as GitHub bodies (see + [GitHub writing style](../../../../contexts/dev/github-writing.md)) — keep + each paragraph or list item on one physical line, with no fixed-width + wrapping — so a squash-merge keeps a clean description on GitHub. Examples from history: diff --git a/.claude/skills/quantmind-dev/references/commit.md b/.claude/skills/quantmind-dev/references/commit.md index c6e12de..fc00d87 100644 --- a/.claude/skills/quantmind-dev/references/commit.md +++ b/.claude/skills/quantmind-dev/references/commit.md @@ -16,6 +16,11 @@ English, [Conventional Commits](https://www.conventionalcommits.org/): `chore(verify): ...`, `docs(news): ...`). Omit the scope only for repo-wide changes (`feat: flows + magic apex layer`). - **summary**: imperative, lower-case start, no trailing period. +- **body** (optional): explain what changed and why. Follow the same + no-hard-wrap prose rule as GitHub bodies (see + [GitHub writing style](../../../../contexts/dev/github-writing.md)) — keep + each paragraph or list item on one physical line, with no fixed-width + wrapping — so a squash-merge keeps a clean description on GitHub. Examples from history: diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..22a8a60 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,26 @@ +name: PR title + +# The commit-msg git hook only fires for contributors who ran +# `pre-commit install`, so it cannot bind external contributors. This job is +# the server-side floor: it lints the PR title (which becomes the squash-merge +# commit subject) with the same rule as the commit-msg hook, reusing +# scripts/hooks/commit_msg_check.py so the convention has a single source. + +on: + pull_request: + types: [opened, edited, reopened] + +permissions: + contents: read + +jobs: + lint-pr-title: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Lint PR title as an English Conventional Commit + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + printf '%s\n' "$PR_TITLE" > "$RUNNER_TEMP/pr_title.txt" + python3 scripts/hooks/commit_msg_check.py "$RUNNER_TEMP/pr_title.txt" diff --git a/contexts/README.md b/contexts/README.md index 39db7cb..3a841d9 100644 --- a/contexts/README.md +++ b/contexts/README.md @@ -22,6 +22,7 @@ This directory is the public repository context system for coding agents and mai ## Where Information Lives +- **`contexts/` vs `docs/`**: `contexts/` is the internal navigation and design memory for coding agents and maintainers — design decisions in `contexts/design/`, contributor rules in `contexts/dev/`, usage routing in `contexts/usage/`. `docs/` is the user-facing surface for people consuming QuantMind as a library — the public component catalog `docs/README.md` and usage guides such as `docs/library.md`. A fact that helps an agent build the repo lives in `contexts/`; a fact that helps someone use the library lives in `docs/`. Neither keeps a second copy of the other. - Keep each kind of information in one place so agents do not have to compare competing versions. - `contexts/design/` records accepted design decisions and planned behavior. - `contexts/dev/` and `contexts/usage/` route readers to existing rules and examples instead of copying them. diff --git a/contexts/dev/harness-engineering.md b/contexts/dev/harness-engineering.md index a1feab6..d5c0af6 100644 --- a/contexts/dev/harness-engineering.md +++ b/contexts/dev/harness-engineering.md @@ -77,10 +77,11 @@ Each rule is enforced at the layer that fits it, and hooks are kept to mechanica | Contexts page structure (Quick Summary / Contents / anchors / index links) | `tests/test_contexts.py` inside `verify.sh` | CI + local | | English Conventional Commit subject | `scripts/hooks/commit_msg_check.py` | `commit-msg` git hook (tool-agnostic: humans, Claude, Codex) | | No `--no-verify` bypass of the hooks above | `scripts/hooks/pre_tool_use_no_bypass.py` | Agent PreToolUse hook | +| English Conventional Commit PR title | `.github/workflows/pr-title.yml` (reuses `commit_msg_check.py`) | CI check | The anti-bypass hook is the one guarantee that cannot live in a git hook: `--no-verify` is by definition the flag that turns git hooks off, and CI only notices after the fact. A PreToolUse `deny` stops an agent from skipping the checks at the source. It is a mechanical check only: the command is tokenized with `shlex` and split into simple commands, so it denies only when `--no-verify` is a real argument token of a real `git` invocation — a command that merely mentions the flag inside a quoted string (an `echo`, a `grep`, a commit message) keeps it inside one token and passes. The residual gap is shell indirection (`$VAR` / `eval`), which a mechanical guard does not resolve. Commit-*message* format, by contrast, is a git hook rather than an agent hook, so it also catches a human committing from a terminal. -Planned next: enforce the commit convention for external contributors at the CI floor (a PR-title lint), since local git hooks only fire for contributors who ran `pre-commit install`. +The PR-title lint (`.github/workflows/pr-title.yml`) is the server-side floor for the commit convention. Local git hooks only fire for contributors who ran `pre-commit install`, so the PR title — which becomes the squash-merge commit subject — is linted in CI with the same `commit_msg_check.py` rule (single source). Making that workflow a *required* status check is a one-time repository-settings toggle, separate from committing the workflow here. ## References From c2c4847cc114088d9587d1a9d494f3e1f851fe50 Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Thu, 23 Jul 2026 18:22:40 +0800 Subject: [PATCH 2/3] docs(commit): use a repo-root-relative reference instead of a deep ../ link The commit-body rule referenced github-writing.md via `../../../../contexts/dev/github-writing.md`, matching the repo's tested clickable-link convention but reading badly. commit.md is not test-bound to that link, so use a readable repo-root-relative backtick reference instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/skills/quantmind-dev/references/commit.md | 8 ++++---- .claude/skills/quantmind-dev/references/commit.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.agents/skills/quantmind-dev/references/commit.md b/.agents/skills/quantmind-dev/references/commit.md index fc00d87..7b61608 100644 --- a/.agents/skills/quantmind-dev/references/commit.md +++ b/.agents/skills/quantmind-dev/references/commit.md @@ -17,10 +17,10 @@ English, [Conventional Commits](https://www.conventionalcommits.org/): repo-wide changes (`feat: flows + magic apex layer`). - **summary**: imperative, lower-case start, no trailing period. - **body** (optional): explain what changed and why. Follow the same - no-hard-wrap prose rule as GitHub bodies (see - [GitHub writing style](../../../../contexts/dev/github-writing.md)) — keep - each paragraph or list item on one physical line, with no fixed-width - wrapping — so a squash-merge keeps a clean description on GitHub. + no-hard-wrap prose rule as GitHub bodies (see the GitHub writing style in + `contexts/dev/github-writing.md`, a repo-root-relative path) — keep each + paragraph or list item on one physical line, with no fixed-width wrapping — + so a squash-merge keeps a clean description on GitHub. Examples from history: diff --git a/.claude/skills/quantmind-dev/references/commit.md b/.claude/skills/quantmind-dev/references/commit.md index fc00d87..7b61608 100644 --- a/.claude/skills/quantmind-dev/references/commit.md +++ b/.claude/skills/quantmind-dev/references/commit.md @@ -17,10 +17,10 @@ English, [Conventional Commits](https://www.conventionalcommits.org/): repo-wide changes (`feat: flows + magic apex layer`). - **summary**: imperative, lower-case start, no trailing period. - **body** (optional): explain what changed and why. Follow the same - no-hard-wrap prose rule as GitHub bodies (see - [GitHub writing style](../../../../contexts/dev/github-writing.md)) — keep - each paragraph or list item on one physical line, with no fixed-width - wrapping — so a squash-merge keeps a clean description on GitHub. + no-hard-wrap prose rule as GitHub bodies (see the GitHub writing style in + `contexts/dev/github-writing.md`, a repo-root-relative path) — keep each + paragraph or list item on one physical line, with no fixed-width wrapping — + so a squash-merge keeps a clean description on GitHub. Examples from history: From 38a22011e9970454516f6faf2f66b612e3917ad6 Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Thu, 23 Jul 2026 18:31:53 +0800 Subject: [PATCH 3/3] refactor(contexts): adopt repo-root-relative cross-references (align with data-mono) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skill files referenced contexts pages with deep, fragile clickable links like `../../../../contexts/dev/github-writing.md`. The sister repo llmquant-data-mono instead uses repo-root-relative backtick references (`contexts/...`) from nested files, because a clean clickable repo-root link is only possible from a root-level file — GitHub resolves a bare relative link against the current file's directory, not the repo root, so a nested file needs the ugly `../` chain to stay clickable. - SKILL.md and pull-request.md (both skill copies) now reference contexts pages as repo-root backtick paths (`contexts/README.md`, `contexts/dev/labels.md`, `contexts/dev/github-writing.md`) instead of `../../../` links. Root-level files (AGENTS.md) and sibling contexts pages keep their clean clickable links. - tests/test_contexts.py: the three route tests now check that each source references the canonical guide in the form appropriate to its location — a backtick repo-root reference for nested skill files, a clickable link for root or sibling pages — and that the guide exists at the repo root, instead of asserting a specific deep relative link path. verify.sh passes (426 passed, 85.65% coverage). Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/skills/quantmind-dev/SKILL.md | 6 +- .../quantmind-dev/references/pull-request.md | 2 +- .claude/skills/quantmind-dev/SKILL.md | 6 +- .../quantmind-dev/references/pull-request.md | 2 +- tests/test_contexts.py | 94 +++++++++---------- 5 files changed, 54 insertions(+), 56 deletions(-) diff --git a/.agents/skills/quantmind-dev/SKILL.md b/.agents/skills/quantmind-dev/SKILL.md index c391521..9e01dfc 100644 --- a/.agents/skills/quantmind-dev/SKILL.md +++ b/.agents/skills/quantmind-dev/SKILL.md @@ -9,16 +9,16 @@ Development workflow for contributing to the QuantMind codebase. ## Start Here -1. Use the repository [`contexts/README.md`](../../../contexts/README.md) to +1. Use the repository `contexts/README.md` to select the development context for this contribution. 2. Read the repository root `AGENTS.md` or `CLAUDE.md` for the stable architecture constraints and the module map. 3. Read `docs/README.md` when the task adds, changes, or uses a public operation or public-network source. 4. When creating, updating, or triaging an issue or pull request, use the - canonical [repository label guidance](../../../contexts/dev/labels.md). + canonical repository label guidance in `contexts/dev/labels.md`. 5. Before writing or editing any GitHub body, follow the canonical - [GitHub writing style](../../../contexts/dev/github-writing.md), including + GitHub writing style in `contexts/dev/github-writing.md`, including its no-hard-wrap rule. 6. Pick exactly one workflow reference below; do not load the others. diff --git a/.agents/skills/quantmind-dev/references/pull-request.md b/.agents/skills/quantmind-dev/references/pull-request.md index d57de56..480bf1c 100644 --- a/.agents/skills/quantmind-dev/references/pull-request.md +++ b/.agents/skills/quantmind-dev/references/pull-request.md @@ -35,7 +35,7 @@ batch input`, `docs(readme): update quick start`. Written in English (external audiences read it: contributors, search indexers, future maintainers). Follow `.github/PULL_REQUEST_TEMPLATE.md` and the canonical -[GitHub writing style](../../../../contexts/dev/github-writing.md). Do not +GitHub writing style in `contexts/dev/github-writing.md`. Do not hard-wrap body prose at 80 columns or another fixed width. Make sure the body covers: diff --git a/.claude/skills/quantmind-dev/SKILL.md b/.claude/skills/quantmind-dev/SKILL.md index c391521..9e01dfc 100644 --- a/.claude/skills/quantmind-dev/SKILL.md +++ b/.claude/skills/quantmind-dev/SKILL.md @@ -9,16 +9,16 @@ Development workflow for contributing to the QuantMind codebase. ## Start Here -1. Use the repository [`contexts/README.md`](../../../contexts/README.md) to +1. Use the repository `contexts/README.md` to select the development context for this contribution. 2. Read the repository root `AGENTS.md` or `CLAUDE.md` for the stable architecture constraints and the module map. 3. Read `docs/README.md` when the task adds, changes, or uses a public operation or public-network source. 4. When creating, updating, or triaging an issue or pull request, use the - canonical [repository label guidance](../../../contexts/dev/labels.md). + canonical repository label guidance in `contexts/dev/labels.md`. 5. Before writing or editing any GitHub body, follow the canonical - [GitHub writing style](../../../contexts/dev/github-writing.md), including + GitHub writing style in `contexts/dev/github-writing.md`, including its no-hard-wrap rule. 6. Pick exactly one workflow reference below; do not load the others. diff --git a/.claude/skills/quantmind-dev/references/pull-request.md b/.claude/skills/quantmind-dev/references/pull-request.md index d57de56..480bf1c 100644 --- a/.claude/skills/quantmind-dev/references/pull-request.md +++ b/.claude/skills/quantmind-dev/references/pull-request.md @@ -35,7 +35,7 @@ batch input`, `docs(readme): update quick start`. Written in English (external audiences read it: contributors, search indexers, future maintainers). Follow `.github/PULL_REQUEST_TEMPLATE.md` and the canonical -[GitHub writing style](../../../../contexts/dev/github-writing.md). Do not +GitHub writing style in `contexts/dev/github-writing.md`. Do not hard-wrap body prose at 80 columns or another fixed width. Make sure the body covers: diff --git a/tests/test_contexts.py b/tests/test_contexts.py index 4da59b4..783f0ca 100644 --- a/tests/test_contexts.py +++ b/tests/test_contexts.py @@ -101,50 +101,48 @@ def test_context_index_targets_exist(self) -> None: def test_required_files_link_to_context_entry_point(self) -> None: repo_root = Path(__file__).resolve().parents[1] - required_links = { - "AGENTS.md": "contexts/README.md", - "CLAUDE.md": "contexts/README.md", - ".agents/skills/quantmind-dev/SKILL.md": "../../../contexts/README.md", - ".claude/skills/quantmind-dev/SKILL.md": "../../../contexts/README.md", - } - - for source_path, target in required_links.items(): - source = repo_root / source_path + entry_point = "contexts/README.md" + # Root-level guides link to the entry point; nested skill files + # reference the same repo-root path in backticks. Both contain the + # path string, so one substring check is form-agnostic. + sources = ( + "AGENTS.md", + "CLAUDE.md", + ".agents/skills/quantmind-dev/SKILL.md", + ".claude/skills/quantmind-dev/SKILL.md", + ) + self.assertTrue( + (repo_root / entry_point).is_file(), + f"missing context entry point: {entry_point}", + ) + for source_path in sources: with self.subTest(source=source_path): self.assertIn( - f"]({target})", + entry_point, _guide_text(repo_root, source_path), - f"{source_path} must link to contexts/README.md", - ) - self.assertTrue( - (source.parent / target).resolve().is_file(), - f"broken context entry link from {source_path}: {target}", + f"{source_path} must reference {entry_point}", ) def test_label_guide_has_required_routes(self) -> None: repo_root = Path(__file__).resolve().parents[1] - required_links = { - "contexts/dev/README.md": "labels.md", - ".agents/skills/quantmind-dev/SKILL.md": ( - "../../../contexts/dev/labels.md" - ), - ".claude/skills/quantmind-dev/SKILL.md": ( - "../../../contexts/dev/labels.md" - ), + self.assertTrue( + (repo_root / "contexts/dev/labels.md").is_file(), + "missing canonical label guide", + ) + # The dev index links a sibling page; nested skill files use a + # repo-root backtick reference. + references = { + "contexts/dev/README.md": "](labels.md)", + ".agents/skills/quantmind-dev/SKILL.md": "`contexts/dev/labels.md`", + ".claude/skills/quantmind-dev/SKILL.md": "`contexts/dev/labels.md`", } - - for source_path, target in required_links.items(): - source = repo_root / source_path + for source_path, expected in references.items(): with self.subTest(source=source_path): self.assertIn( - f"]({target})", + expected, _guide_text(repo_root, source_path), f"{source_path} must route to the canonical label guide", ) - self.assertTrue( - (source.parent / target).resolve().is_file(), - f"broken label guide link from {source_path}: {target}", - ) def test_label_guide_has_complete_taxonomy(self) -> None: repo_root = Path(__file__).resolve().parents[1] @@ -186,37 +184,37 @@ def test_label_guide_has_complete_taxonomy(self) -> None: def test_github_writing_guide_has_required_routes(self) -> None: repo_root = Path(__file__).resolve().parents[1] - required_links = { - "contexts/dev/README.md": "github-writing.md", - "contexts/dev/labels.md": "github-writing.md", - "AGENTS.md": "contexts/dev/github-writing.md", - "CLAUDE.md": "contexts/dev/github-writing.md", + self.assertTrue( + (repo_root / "contexts/dev/github-writing.md").is_file(), + "missing GitHub writing guide", + ) + # Sibling contexts pages link the short path; root guides link the + # repo-root path; nested skill files use a repo-root backtick reference. + references = { + "contexts/dev/README.md": "](github-writing.md)", + "contexts/dev/labels.md": "](github-writing.md)", + "AGENTS.md": "](contexts/dev/github-writing.md)", + "CLAUDE.md": "](contexts/dev/github-writing.md)", ".agents/skills/quantmind-dev/SKILL.md": ( - "../../../contexts/dev/github-writing.md" + "`contexts/dev/github-writing.md`" ), ".claude/skills/quantmind-dev/SKILL.md": ( - "../../../contexts/dev/github-writing.md" + "`contexts/dev/github-writing.md`" ), ".agents/skills/quantmind-dev/references/pull-request.md": ( - "../../../../contexts/dev/github-writing.md" + "`contexts/dev/github-writing.md`" ), ".claude/skills/quantmind-dev/references/pull-request.md": ( - "../../../../contexts/dev/github-writing.md" + "`contexts/dev/github-writing.md`" ), } - - for source_path, target in required_links.items(): - source = repo_root / source_path + for source_path, expected in references.items(): with self.subTest(source=source_path): self.assertIn( - f"]({target})", + expected, _guide_text(repo_root, source_path), f"{source_path} must route to the GitHub writing guide", ) - self.assertTrue( - (source.parent / target).resolve().is_file(), - f"broken GitHub writing guide link: {target}", - ) marker = "