markdown-style
Apply one-line Markdown paragraphs with pinned native dprint, safe staged Lefthook fixes, and matching CI checks.
目录
Markdown style
当项目需要统一 Markdown 正文排版、提交前修复或 CI 样式检查时使用本 Policy。自然段保持一行,结构由空行、列表、表格、代码块和显式硬换行表达;作者意图中的独立段落应使用空行分隔。
Workflow
- 检查目标项目的 Markdown 范围、现有 formatter、Git hook 管理器、CI 和用户改动。首批范围为 Git 跟踪的
.md,formatter 使用官方 dprint Markdown 插件,不新增正文最大行宽。 - 预览项目 Policy 安装和工具接入变更。将
assets/templates/markdown.py与lefthook.sh放到目标项目的tools/,采用dprint.json配置;将 Lefthook 示例中的 Markdown job 合并到现有配置,不覆盖既有 hook、其他 job 或本地设置。在.gitignore中加入/.tmp/markdown-tools/。 - 用
python3 tools/markdown.py setup准备固定的原生工具与 Wasm;清单和 SHA256 来自assets/toolchain.json。只有显式选择setup --install-hooks才安装 hook。遇到已有core.hooksPath或非本 Policy 管理的 pre-commit hook 时,保留它并完成可审查的手工集成。 - 在提交阶段使用 Lefthook 的
stage_fixed: true与原生部分暂存备份/隐藏/恢复机制。通过staged只修复已暂存 Markdown。工具或插件缺失时提示初始化,提交阶段不下载;无 Markdown 的提交跳过 formatter。 - CI 先执行普通
setup,再执行check。使用项目已有 job 和 required check 名称,不自动重写分支保护。用真实 Git 暂存场景验证未暂存修改、无关文件和既有 stash 保留;失败或恢复冲突时停止提交并处理原生恢复材料。 - 用
fix建立独立的机械格式化提交。核对 frontmatter、代码内容、文档解析和公开导出,修正生成模板,使重新生成后仍能通过check。记录冷/热运行耗时,性能证据来自实际执行而不是工具名称。
Formatting contract
textWrap: never 保持自然段单行;中文软换行还需要 wrapUnspacedScripts: true,否则仅设置 never 仍可能保留换行。英文换行合并为空格,连续中文合并时不插入空格,混合正文遵循官方插件的词边界规则。
表格使用紧凑的 table.cellPadding: space;代码块跳过内嵌 formatter 并保留缩进与空行,HTML 同样跳过。显式硬换行统一成可见的反斜杠,保留原有换行语义。工具采用 LF;frontmatter 不接入 YAML formatter。不通过 lineWidth 推导正文需要按列宽拆行。
check 只读检查跟踪的 Markdown,失败展示差异;fix 修复同一集合。首次创建的未暂存文档需要先纳入 Git 跟踪;故意不合规的测试输入使用字符串或非 Markdown fixture。配置与包装脚本维护在项目中,工具实现与清单由安装的 Policy 提供,不依赖提供方仓库的其他 Python 模块。
Git 路径按 NUL 分隔读取,并对 dprint CLI 的 glob 字符转义和锚定。离线 Lefthook 包装启用字面 Git pathspec,避免重新暂存方括号等文件名时选中未跟踪的匹配文件;接入其他 job 时应检查其是否依赖 Git glob。原生 dprint 将反斜杠视为路径分隔符,含反斜杠的文件名会在写入前明确拒绝,需要先重命名。
提交前的包装先校验 Lefthook 摘要,并检查既有恢复材料。若 git stash list 中存在以 lefthook auto backup 开头的原生备份消息,或 git rev-parse --git-path info 定位的目录存在 lefthook-unstaged.patch / lefthook-unstaged-all.patch,立即停止,保留索引、工作区、stash 和 patch。先另行保存并核对这些材料,确认恢复完毕后再显式清理对应项;包装不自动 apply、drop 或覆盖它们。该检查避免固定版本原生 runner 清理旧同名备份或覆盖旧 patch。
包装在恢复材料检查前取得 Git info 目录中的 markdown-style.lock 文件锁,并持有至原生 runner 完成。该目录与 stash 在 linked worktree 间共享;并发 hook 会在修改任何恢复材料前停止,提示等待另一提交完成后重试。操作系统在进程退出时释放锁,保留的锁文件不表示仍有提交运行,不需要手工删除;锁文件不能是符号链接。其他 hook 管理器手工接入时,必须通过本包装运行 Lefthook,才能维持同一互斥边界。
原生 Lefthook 保存全量恢复 patch 时使用分离的 git diff --output <路径> -- 参数;共享 patch 位于 linked worktree 外时,Git 的早期无索引检测会误将它识别为比较路径。初始化同时准备本包的 Git 兼容入口,hook 按源文件摘要核对后仅在子进程 PATH 中使用;它将 diff 的该参数改为等价的 --output=<路径>,其余参数直接交给原生 Git。入口不解析 Markdown,不实现 stash 或恢复,不影响用户 shell 的 Git,缺失或改变时要求重新初始化。
暂存选择覆盖新增、复制、修改、重命名及 T 类型变更。Lefthook job 使用包含这些状态的 files 命令与 {files} 元数据,以便原生 stage_fixed 能跟踪类型变更;formatter 的实际选集仍由 NUL 分隔的 Git 输出决定。符号链接转为普通 Markdown 会修复后重新暂存,转为符号链接则明确停止并保留索引和链接目标。
Adoption preview
在目标项目预览 .agents/skills/markdown-style/ 与 skills-lock.json 的写入,并取得明确批准后,使用 npx skills add IvanLi-CN/style-playbook-skills --skill markdown-style --yes。CLI 负责选择标准项目安装面。本 Policy 不自行安装其他 Policy,也不把已有模板复制到未经批准的项目。
当前提供方仓库的薄包装直接使用 skills/policies/markdown-style/ 源包;这属于实现复用,不构成目标项目 Policy 安装对齐证明。其他项目使用标准安装面,包装脚本支持该路径。
Package resources
assets/scripts/markdown.py:仅依赖 Python 标准库的初始化、文件选择及原生 formatter 调用。assets/scripts/git-compat.py:供原生 Lefthook 使用的 Git 参数兼容入口,保持 linked worktree 的恢复 patch 写入有效。assets/toolchain.json:macOS/Linux arm64/x86_64 的固定官方 Release URL、版本和 SHA256。assets/templates/:项目包装、离线 Lefthook 入口、Markdown 配置、Lefthook job 与 CI step 示例。- dprint CLI 和 Lefthook stage_fixed:原生工具行为参考。
手动安装
将指令保存为技能目录中的 SKILL.md,并保持下列资源的相对路径。阅读与下载不会执行安装。
下载 SKILL.md公开资源
SKILL.md
- description
- Apply one-line Markdown paragraphs with pinned native dprint, safe staged Lefthook fixes, and matching CI checks.
- kind
- policy-skill
- name
- markdown-style
- policy_dependencies
- []
- primary_topic
- markdown-style
- public_url_hosts
- [ "github.com", "dprint.dev", "lefthook.dev" ]
- schema_version
- 1
- slug
- markdown-style
- visibility
- public
Markdown style
当项目需要统一 Markdown 正文排版、提交前修复或 CI 样式检查时使用本 Policy。自然段保持一行,结构由空行、列表、表格、代码块和显式硬换行表达;作者意图中的独立段落应使用空行分隔。
Workflow
- 检查目标项目的 Markdown 范围、现有 formatter、Git hook 管理器、CI 和用户改动。首批范围为 Git 跟踪的
.md,formatter 使用官方 dprint Markdown 插件,不新增正文最大行宽。 - 预览项目 Policy 安装和工具接入变更。将
assets/templates/markdown.py与lefthook.sh放到目标项目的tools/,采用dprint.json配置;将 Lefthook 示例中的 Markdown job 合并到现有配置,不覆盖既有 hook、其他 job 或本地设置。在.gitignore中加入/.tmp/markdown-tools/。 - 用
python3 tools/markdown.py setup准备固定的原生工具与 Wasm;清单和 SHA256 来自assets/toolchain.json。只有显式选择setup --install-hooks才安装 hook。遇到已有core.hooksPath或非本 Policy 管理的 pre-commit hook 时,保留它并完成可审查的手工集成。 - 在提交阶段使用 Lefthook 的
stage_fixed: true与原生部分暂存备份/隐藏/恢复机制。通过staged只修复已暂存 Markdown。工具或插件缺失时提示初始化,提交阶段不下载;无 Markdown 的提交跳过 formatter。 - CI 先执行普通
setup,再执行check。使用项目已有 job 和 required check 名称,不自动重写分支保护。用真实 Git 暂存场景验证未暂存修改、无关文件和既有 stash 保留;失败或恢复冲突时停止提交并处理原生恢复材料。 - 用
fix建立独立的机械格式化提交。核对 frontmatter、代码内容、文档解析和公开导出,修正生成模板,使重新生成后仍能通过check。记录冷/热运行耗时,性能证据来自实际执行而不是工具名称。
Formatting contract
textWrap: never 保持自然段单行;中文软换行还需要 wrapUnspacedScripts: true,否则仅设置 never 仍可能保留换行。英文换行合并为空格,连续中文合并时不插入空格,混合正文遵循官方插件的词边界规则。
表格使用紧凑的 table.cellPadding: space;代码块跳过内嵌 formatter 并保留缩进与空行,HTML 同样跳过。显式硬换行统一成可见的反斜杠,保留原有换行语义。工具采用 LF;frontmatter 不接入 YAML formatter。不通过 lineWidth 推导正文需要按列宽拆行。
check 只读检查跟踪的 Markdown,失败展示差异;fix 修复同一集合。首次创建的未暂存文档需要先纳入 Git 跟踪;故意不合规的测试输入使用字符串或非 Markdown fixture。配置与包装脚本维护在项目中,工具实现与清单由安装的 Policy 提供,不依赖提供方仓库的其他 Python 模块。
Git 路径按 NUL 分隔读取,并对 dprint CLI 的 glob 字符转义和锚定。离线 Lefthook 包装启用字面 Git pathspec,避免重新暂存方括号等文件名时选中未跟踪的匹配文件;接入其他 job 时应检查其是否依赖 Git glob。原生 dprint 将反斜杠视为路径分隔符,含反斜杠的文件名会在写入前明确拒绝,需要先重命名。
提交前的包装先校验 Lefthook 摘要,并检查既有恢复材料。若 git stash list 中存在以 lefthook auto backup 开头的原生备份消息,或 git rev-parse --git-path info 定位的目录存在 lefthook-unstaged.patch / lefthook-unstaged-all.patch,立即停止,保留索引、工作区、stash 和 patch。先另行保存并核对这些材料,确认恢复完毕后再显式清理对应项;包装不自动 apply、drop 或覆盖它们。该检查避免固定版本原生 runner 清理旧同名备份或覆盖旧 patch。
包装在恢复材料检查前取得 Git info 目录中的 markdown-style.lock 文件锁,并持有至原生 runner 完成。该目录与 stash 在 linked worktree 间共享;并发 hook 会在修改任何恢复材料前停止,提示等待另一提交完成后重试。操作系统在进程退出时释放锁,保留的锁文件不表示仍有提交运行,不需要手工删除;锁文件不能是符号链接。其他 hook 管理器手工接入时,必须通过本包装运行 Lefthook,才能维持同一互斥边界。
原生 Lefthook 保存全量恢复 patch 时使用分离的 git diff --output <路径> -- 参数;共享 patch 位于 linked worktree 外时,Git 的早期无索引检测会误将它识别为比较路径。初始化同时准备本包的 Git 兼容入口,hook 按源文件摘要核对后仅在子进程 PATH 中使用;它将 diff 的该参数改为等价的 --output=<路径>,其余参数直接交给原生 Git。入口不解析 Markdown,不实现 stash 或恢复,不影响用户 shell 的 Git,缺失或改变时要求重新初始化。
暂存选择覆盖新增、复制、修改、重命名及 T 类型变更。Lefthook job 使用包含这些状态的 files 命令与 {files} 元数据,以便原生 stage_fixed 能跟踪类型变更;formatter 的实际选集仍由 NUL 分隔的 Git 输出决定。符号链接转为普通 Markdown 会修复后重新暂存,转为符号链接则明确停止并保留索引和链接目标。
Adoption preview
在目标项目预览 .agents/skills/markdown-style/ 与 skills-lock.json 的写入,并取得明确批准后,使用 npx skills add IvanLi-CN/style-playbook-skills --skill markdown-style --yes。CLI 负责选择标准项目安装面。本 Policy 不自行安装其他 Policy,也不把已有模板复制到未经批准的项目。
当前提供方仓库的薄包装直接使用 skills/policies/markdown-style/ 源包;这属于实现复用,不构成目标项目 Policy 安装对齐证明。其他项目使用标准安装面,包装脚本支持该路径。
Package resources
assets/scripts/markdown.py:仅依赖 Python 标准库的初始化、文件选择及原生 formatter 调用。assets/scripts/git-compat.py:供原生 Lefthook 使用的 Git 参数兼容入口,保持 linked worktree 的恢复 patch 写入有效。assets/toolchain.json:macOS/Linux arm64/x86_64 的固定官方 Release URL、版本和 SHA256。assets/templates/:项目包装、离线 Lefthook 入口、Markdown 配置、Lefthook job 与 CI step 示例。- dprint CLI 和 Lefthook stage_fixed:原生工具行为参考。
---
{
"description": "Apply one-line Markdown paragraphs with pinned native dprint, safe staged Lefthook fixes, and matching CI checks.",
"kind": "policy-skill",
"name": "markdown-style",
"policy_dependencies": [],
"primary_topic": "markdown-style",
"public_url_hosts": [
"github.com",
"dprint.dev",
"lefthook.dev"
],
"schema_version": 1,
"slug": "markdown-style",
"visibility": "public"
}
---
# Markdown style
当项目需要统一 Markdown 正文排版、提交前修复或 CI 样式检查时使用本 Policy。自然段保持一行,结构由空行、列表、表格、代码块和显式硬换行表达;作者意图中的独立段落应使用空行分隔。
## Workflow
1. 检查目标项目的 Markdown 范围、现有 formatter、Git hook 管理器、CI 和用户改动。首批范围为 Git 跟踪的 `.md`,formatter 使用官方 dprint Markdown 插件,不新增正文最大行宽。
2. 预览项目 Policy 安装和工具接入变更。将 `assets/templates/markdown.py` 与 `lefthook.sh` 放到目标项目的 `tools/`,采用 `dprint.json` 配置;将 Lefthook 示例中的 Markdown job 合并到现有配置,不覆盖既有 hook、其他 job 或本地设置。在 `.gitignore` 中加入 `/.tmp/markdown-tools/`。
3. 用 `python3 tools/markdown.py setup` 准备固定的原生工具与 Wasm;清单和 SHA256 来自 `assets/toolchain.json`。只有显式选择 `setup --install-hooks` 才安装 hook。遇到已有 `core.hooksPath` 或非本 Policy 管理的 pre-commit hook 时,保留它并完成可审查的手工集成。
4. 在提交阶段使用 Lefthook 的 `stage_fixed: true` 与原生部分暂存备份/隐藏/恢复机制。通过 `staged` 只修复已暂存 Markdown。工具或插件缺失时提示初始化,提交阶段不下载;无 Markdown 的提交跳过 formatter。
5. CI 先执行普通 `setup`,再执行 `check`。使用项目已有 job 和 required check 名称,不自动重写分支保护。用真实 Git 暂存场景验证未暂存修改、无关文件和既有 stash 保留;失败或恢复冲突时停止提交并处理原生恢复材料。
6. 用 `fix` 建立独立的机械格式化提交。核对 frontmatter、代码内容、文档解析和公开导出,修正生成模板,使重新生成后仍能通过 `check`。记录冷/热运行耗时,性能证据来自实际执行而不是工具名称。
## Formatting contract
`textWrap: never` 保持自然段单行;中文软换行还需要 `wrapUnspacedScripts: true`,否则仅设置 never 仍可能保留换行。英文换行合并为空格,连续中文合并时不插入空格,混合正文遵循官方插件的词边界规则。
表格使用紧凑的 `table.cellPadding: space`;代码块跳过内嵌 formatter 并保留缩进与空行,HTML 同样跳过。显式硬换行统一成可见的反斜杠,保留原有换行语义。工具采用 LF;frontmatter 不接入 YAML formatter。不通过 `lineWidth` 推导正文需要按列宽拆行。
`check` 只读检查跟踪的 Markdown,失败展示差异;`fix` 修复同一集合。首次创建的未暂存文档需要先纳入 Git 跟踪;故意不合规的测试输入使用字符串或非 Markdown fixture。配置与包装脚本维护在项目中,工具实现与清单由安装的 Policy 提供,不依赖提供方仓库的其他 Python 模块。
Git 路径按 NUL 分隔读取,并对 dprint CLI 的 glob 字符转义和锚定。离线 Lefthook 包装启用字面 Git pathspec,避免重新暂存方括号等文件名时选中未跟踪的匹配文件;接入其他 job 时应检查其是否依赖 Git glob。原生 dprint 将反斜杠视为路径分隔符,含反斜杠的文件名会在写入前明确拒绝,需要先重命名。
提交前的包装先校验 Lefthook 摘要,并检查既有恢复材料。若 `git stash list` 中存在以 `lefthook auto backup` 开头的原生备份消息,或 `git rev-parse --git-path info` 定位的目录存在 `lefthook-unstaged.patch` / `lefthook-unstaged-all.patch`,立即停止,保留索引、工作区、stash 和 patch。先另行保存并核对这些材料,确认恢复完毕后再显式清理对应项;包装不自动 apply、drop 或覆盖它们。该检查避免固定版本原生 runner 清理旧同名备份或覆盖旧 patch。
包装在恢复材料检查前取得 Git info 目录中的 `markdown-style.lock` 文件锁,并持有至原生 runner 完成。该目录与 stash 在 linked worktree 间共享;并发 hook 会在修改任何恢复材料前停止,提示等待另一提交完成后重试。操作系统在进程退出时释放锁,保留的锁文件不表示仍有提交运行,不需要手工删除;锁文件不能是符号链接。其他 hook 管理器手工接入时,必须通过本包装运行 Lefthook,才能维持同一互斥边界。
原生 Lefthook 保存全量恢复 patch 时使用分离的 `git diff --output <路径> --` 参数;共享 patch 位于 linked worktree 外时,Git 的早期无索引检测会误将它识别为比较路径。初始化同时准备本包的 Git 兼容入口,hook 按源文件摘要核对后仅在子进程 PATH 中使用;它将 `diff` 的该参数改为等价的 `--output=<路径>`,其余参数直接交给原生 Git。入口不解析 Markdown,不实现 stash 或恢复,不影响用户 shell 的 Git,缺失或改变时要求重新初始化。
暂存选择覆盖新增、复制、修改、重命名及 `T` 类型变更。Lefthook job 使用包含这些状态的 `files` 命令与 `{files}` 元数据,以便原生 `stage_fixed` 能跟踪类型变更;formatter 的实际选集仍由 NUL 分隔的 Git 输出决定。符号链接转为普通 Markdown 会修复后重新暂存,转为符号链接则明确停止并保留索引和链接目标。
## Adoption preview
在目标项目预览 `.agents/skills/markdown-style/` 与 `skills-lock.json` 的写入,并取得明确批准后,使用 `npx skills add IvanLi-CN/style-playbook-skills --skill markdown-style --yes`。CLI 负责选择标准项目安装面。本 Policy 不自行安装其他 Policy,也不把已有模板复制到未经批准的项目。
当前提供方仓库的薄包装直接使用 `skills/policies/markdown-style/` 源包;这属于实现复用,不构成目标项目 Policy 安装对齐证明。其他项目使用标准安装面,包装脚本支持该路径。
## Package resources
- `assets/scripts/markdown.py`:仅依赖 Python 标准库的初始化、文件选择及原生 formatter 调用。
- `assets/scripts/git-compat.py`:供原生 Lefthook 使用的 Git 参数兼容入口,保持 linked worktree 的恢复 patch 写入有效。
- `assets/toolchain.json`:macOS/Linux arm64/x86_64 的固定官方 Release URL、版本和 SHA256。
- `assets/templates/`:项目包装、离线 Lefthook 入口、Markdown 配置、Lefthook job 与 CI step 示例。
- [dprint CLI](https://dprint.dev/cli/) 和 [Lefthook stage_fixed](https://lefthook.dev/configuration/stage_fixed/):原生工具行为参考。
skills/policies/markdown-style/assets/scripts/git-compat.py
#!/usr/bin/env python3
"""Preserve native Git behavior while disambiguating Lefthook's diff output."""
import os
import sys
native = os.environ.get("MARKDOWN_STYLE_NATIVE_GIT")
if not native:
raise SystemExit("Markdown Git compatibility entry requires the managed Lefthook wrapper")
arguments = sys.argv[1:]
if arguments[:1] == ["diff"]:
boundary = arguments.index("--") if "--" in arguments else len(arguments)
for index in range(1, boundary - 1):
if arguments[index] == "--output":
# Git's early no-index detection mistakes a separate outside path
# plus trailing -- for two comparison paths. Joined syntax is
# equivalent and cannot be mistaken for a positional argument.
arguments[index:index + 2] = ["--output=" + arguments[index + 1]]
break
os.execv(native, [native, *arguments])
skills/policies/markdown-style/assets/scripts/markdown.py
#!/usr/bin/env python3
"""Run pinned native Markdown tools; requires only Python's standard library."""
from __future__ import annotations
import argparse
from contextlib import contextmanager
import fcntl
import hashlib
import io
import json
import os
from pathlib import Path
import platform
import shutil
import stat
import subprocess
import sys
import tempfile
import urllib.request
import zipfile
ASSETS = Path(__file__).resolve().parents[1]
MANIFEST = ASSETS / "toolchain.json"
CACHE = Path(".tmp/markdown-tools")
HOOK_MARKER = "# markdown-style-managed-hook"
GIT_COMPAT = ASSETS / "scripts/git-compat.py"
class ToolError(Exception):
"""An actionable toolchain, configuration, or Git error."""
def git(root: Path, *arguments: str) -> bytes:
result = subprocess.run(["git", *arguments], cwd=root, capture_output=True)
if result.returncode:
raise ToolError(result.stderr.decode(errors="replace").strip())
return result.stdout
def repository_root() -> Path:
return Path(os.fsdecode(git(Path.cwd(), "rev-parse", "--show-toplevel")).strip()).resolve()
def platform_key() -> str:
system = platform.system().lower()
machine = platform.machine().lower()
architecture = {"aarch64": "arm64", "arm64": "arm64", "amd64": "x86_64", "x86_64": "x86_64"}.get(machine)
if system not in {"darwin", "linux"} or architecture is None:
raise ToolError(f"Unsupported native tool platform: {system}/{machine}")
return f"{system}-{architecture}"
def manifest() -> dict:
return json.loads(MANIFEST.read_text(encoding="utf-8"))
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def matches(path: Path, expected: str) -> bool:
return path.is_file() and not path.is_symlink() and digest(path.read_bytes()) == expected
def resources(root: Path) -> list[tuple[Path, dict, bool]]:
data = manifest()
key = platform_key()
return [
(root / CACHE / "dprint", data["dprint"]["platforms"][key], True),
(root / CACHE / "lefthook", data["lefthook"]["platforms"][key], True),
(root / CACHE / data["markdown"]["filename"], data["markdown"], False),
]
def expected_hash(resource: dict) -> str:
return resource.get("binary_sha256", resource["sha256"])
def download(resource: dict) -> bytes:
with urllib.request.urlopen(resource["url"], timeout=30) as response:
archive = response.read()
if digest(archive) != resource["sha256"]:
raise ToolError("Official download SHA256 mismatch; no tool was installed")
if resource.get("format") == "zip":
# Read only the declared member, never extract archive paths to disk.
with zipfile.ZipFile(io.BytesIO(archive)) as bundle:
data = bundle.read(resource["member"])
else:
data = archive
if digest(data) != expected_hash(resource):
raise ToolError("Native binary SHA256 mismatch; no tool was installed")
return data
def setup(root: Path) -> None:
cache = root / CACHE
if (root / ".tmp").is_symlink() or cache.is_symlink():
raise ToolError("Tool cache must be a repository-local regular directory")
ignored = subprocess.run(["git", "check-ignore", "-q", "--", str(CACHE / "probe")], cwd=root)
if ignored.returncode != 0:
raise ToolError("Ignore /.tmp/markdown-tools/ in .gitignore before preparing native tools")
cache.mkdir(parents=True, exist_ok=True)
for target, resource, executable in resources(root):
if not matches(target, expected_hash(resource)):
data = download(resource)
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as handle:
temporary = Path(handle.name)
try:
handle.write(data)
handle.flush()
temporary.chmod(0o755 if executable else 0o644)
temporary.replace(target)
finally:
temporary.unlink(missing_ok=True)
elif executable and not os.access(target, os.X_OK):
target.chmod(0o755)
compatibility = git_compat_path(root)
compatibility.parent.mkdir(exist_ok=True)
code = GIT_COMPAT.read_bytes()
if not matches(compatibility, digest(code)):
with tempfile.NamedTemporaryFile(dir=compatibility.parent, delete=False) as handle:
temporary = Path(handle.name)
try:
handle.write(code)
handle.flush()
temporary.chmod(0o755)
temporary.replace(compatibility)
finally:
temporary.unlink(missing_ok=True)
elif not os.access(compatibility, os.X_OK):
compatibility.chmod(0o755)
validate_config(root)
# Compilation is initialization work; the local Wasm cannot trigger a download.
result = subprocess.run(
[str(cache / "dprint"), "fmt", "--config", str(root / "dprint.json"),
"--config-discovery=false", "--stdin", "bootstrap.md"],
input=b"", cwd=root,
)
if result.returncode:
raise ToolError("Cannot initialize Markdown formatter; check dprint.json")
print("Pinned Markdown tools are ready.")
def ready(root: Path) -> None:
if (root / ".tmp").is_symlink() or (root / CACHE).is_symlink():
raise ToolError("Tool cache must be a repository-local regular directory")
for target, resource, executable in resources(root):
if not matches(target, expected_hash(resource)) or (executable and not os.access(target, os.X_OK)):
raise ToolError("Pinned Markdown tools are missing or changed. Run: python3 tools/markdown.py setup")
def validate_config(root: Path) -> None:
config_path = root / "dprint.json"
if config_path.is_symlink():
raise ToolError("dprint.json must be a regular repository configuration")
config = json.loads(config_path.read_text(encoding="utf-8"))
expected = root / CACHE / manifest()["markdown"]["filename"]
plugins = config.get("plugins", [])
if len(plugins) != 1 or not isinstance(plugins[0], str) or (root / plugins[0]).resolve() != expected.resolve() or config.get("extends"):
raise ToolError("dprint.json must use only the pinned local Markdown Wasm, without config extensions")
def install_hooks(root: Path) -> None:
configured = subprocess.run(["git", "config", "--get", "core.hooksPath"], cwd=root, capture_output=True)
if configured.returncode == 0:
raise ToolError("Existing core.hooksPath is preserved; integrate the Markdown command with that hook manager")
hook = Path(os.fsdecode(git(root, "rev-parse", "--git-path", "hooks/pre-commit")).strip())
if not hook.is_absolute():
hook = root / hook
if hook.is_symlink():
raise ToolError("Existing pre-commit symlink is preserved; integrate the Markdown command manually")
if hook.exists():
if HOOK_MARKER in hook.read_text(encoding="utf-8"):
print("Repository Markdown hook is already installed.")
return
raise ToolError("Existing pre-commit hook is preserved; integrate the Markdown command manually")
if not (root / "lefthook.yml").is_file():
raise ToolError("Add the reviewed lefthook.yml before installing hooks")
ready(root)
result = subprocess.run([str(root / CACHE / "lefthook"), "install", "pre-commit"], cwd=root)
if result.returncode:
raise ToolError("Lefthook installation failed; existing hooks were not forcibly replaced")
with hook.open("a", encoding="utf-8") as handle:
handle.write(f"\n{HOOK_MARKER}\n")
def selected_files(root: Path, staged: bool) -> list[str]:
if staged:
output = git(root, "diff", "--cached", "--name-only", "--diff-filter=ACMRT", "-z")
else:
output = git(root, "ls-files", "--cached", "-z")
selected: list[str] = []
for encoded in dict.fromkeys(output.split(b"\0")):
name = os.fsdecode(encoded)
if not name.endswith(".md"):
continue
if "\\" in name:
raise ToolError(f"dprint treats backslashes as path separators; rename this file before formatting: {name}")
path = root / name
if path.is_symlink() or path.resolve() != path:
raise ToolError(f"Markdown symlink is preserved; use a regular tracked file: {name}")
if path.is_file():
selected.append(name)
return selected
def format_files(root: Path, command: str) -> int:
files = selected_files(root, staged=command == "staged")
if not files:
print("No selected Markdown files; skipping.")
return 0
ready(root)
validate_config(root)
operation = "check" if command == "check" else "fmt"
base = [str(root / CACHE / "dprint"), operation, "--config", str(root / "dprint.json"),
"--config-discovery=false", "--no-gitignore", "--"]
# Keep every batch below native argv limits, even in larger consumer repos.
batches: list[list[str]] = [[]]
size = 0
for name in files:
# dprint's escape_glob_text_for_cli uses character classes, because
# CLI backslashes become separators. Anchor even plain basenames.
pattern = "./" + "".join(f"[{char}]" if char in "[]{}*?" else char for char in name)
length = len(os.fsencode(pattern)) + 1
if size + length > 65536 and batches[-1]:
batches.append([])
size = 0
batches[-1].append(pattern)
size += length
status = 0
for batch in batches:
result = subprocess.run([*base, *batch], cwd=root)
if result.returncode:
status = result.returncode
if operation != "check":
break
if status:
print("Markdown validation failed. Run: python3 tools/markdown.py fix", file=sys.stderr)
return status
@contextmanager
def pre_commit_lock(root: Path):
"""Keep shared native recovery files private to one hook transaction."""
info = Path(os.fsdecode(git(root, "rev-parse", "--path-format=absolute", "--git-path", "info")).rstrip("\n"))
info.mkdir(parents=True, exist_ok=True)
descriptor = os.open(info / "markdown-style.lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW | os.O_NONBLOCK, 0o600)
try:
if not stat.S_ISREG(os.fstat(descriptor).st_mode):
raise ToolError("Markdown hook lock must be a regular Git metadata file")
try:
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise ToolError("Another Markdown pre-commit is running in this repository or a linked worktree; retry after it finishes") from None
yield info, descriptor
finally:
# Closing also releases the advisory lock after exceptions or exit.
os.close(descriptor)
def git_compat_path(root: Path) -> Path:
directory = root / CACHE / "git-compat"
if directory.is_symlink():
raise ToolError("Git compatibility cache must be a regular repository-local directory")
return directory / "git"
def run_lefthook(root: Path, target: Path, arguments: list[str], lock_descriptor: int | None = None) -> int:
compatibility = git_compat_path(root)
if not matches(compatibility, digest(GIT_COMPAT.read_bytes())) or not os.access(compatibility, os.X_OK):
raise ToolError("Markdown Git compatibility entry is missing or changed. Run: python3 tools/markdown.py setup")
native_git = shutil.which("git")
if native_git is None or Path(native_git).resolve() == compatibility.resolve():
raise ToolError("Cannot locate native Git outside the Markdown compatibility entry")
environment = dict(os.environ)
environment["MARKDOWN_STYLE_NATIVE_GIT"] = native_git
environment["PATH"] = str(compatibility.parent) + os.pathsep + environment.get("PATH", "")
# The native runner retains the same open-file lock if its Python parent
# exits early; closing our descriptor must not unlock a still-live runner.
inherited = () if lock_descriptor is None else (lock_descriptor,)
return subprocess.run([str(target), *arguments], cwd=root, env=environment, pass_fds=inherited).returncode
def lefthook_command(arguments: list[str]) -> int:
"""Invoke the pinned hook runner without consuming earlier recovery data."""
try:
root = repository_root()
if (root / ".tmp").is_symlink() or (root / CACHE).is_symlink():
raise ToolError("Tool cache must be a repository-local regular directory")
target, resource, _ = resources(root)[1]
if not matches(target, expected_hash(resource)) or not os.access(target, os.X_OK):
raise ToolError("Pinned Lefthook is missing or changed. Run: python3 tools/markdown.py setup")
if arguments and arguments[0] == "run" and "pre-commit" in arguments:
with pre_commit_lock(root) as (info, lock_descriptor):
# Native Lefthook drops backups by this message prefix and overwrites
# these fixed patch paths. Earlier failed-hook recovery is not ours.
backups = git(root, "stash", "list", "--format=%gs").splitlines()
patches = [info / name for name in ("lefthook-unstaged.patch", "lefthook-unstaged-all.patch")]
if any(message.startswith(b"lefthook auto backup") for message in backups) or any(path.exists() or path.is_symlink() for path in patches):
raise ToolError(
"Existing Lefthook recovery data is preserved; commit stopped. "
"Inspect git stash list and git rev-parse --git-path info; "
"save or recover the lefthook auto backup / lefthook-unstaged*.patch "
"before explicitly clearing those recovery entries."
)
return run_lefthook(root, target, arguments, lock_descriptor)
return run_lefthook(root, target, arguments)
except (ToolError, OSError, ValueError) as error:
print(f"Markdown tools: {error}", file=sys.stderr)
return 1
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=("setup", "check", "fix", "staged"))
parser.add_argument("--install-hooks", action="store_true")
# Lefthook needs {files} metadata to re-stage type changes. Selection still
# comes from Git's NUL output, never from the shell-expanded metadata.
parser.add_argument("hook_files", nargs="*", help=argparse.SUPPRESS)
args = parser.parse_args()
if args.install_hooks and args.command != "setup":
parser.error("--install-hooks is only valid with setup")
if args.hook_files and args.command != "staged":
parser.error("hook file metadata is only valid with staged")
try:
root = repository_root()
if args.command == "setup":
setup(root)
if args.install_hooks:
install_hooks(root)
return 0
return format_files(root, args.command)
except (ToolError, OSError, ValueError, zipfile.BadZipFile) as error:
print(f"Markdown tools: {error}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
skills/policies/markdown-style/assets/templates/ci-steps.yml
# Merge these steps into the target's existing validation job.
# Install Python 3 and Git using the target's normal runtime setup.
- name: Prepare pinned Markdown tools
run: python3 tools/markdown.py setup
- name: Check Markdown style
run: python3 tools/markdown.py check
# In this repository the focused Git/formatter tests run with Python tests.
skills/policies/markdown-style/assets/templates/dprint.json
{
"includes": ["**/*.md"],
"newLineKind": "lf",
"markdown": {
"textWrap": "never",
"wrapUnspacedScripts": true,
"wrapCodeSpans": false,
"table.cellPadding": "space",
"codeBlock.skipFormat": true,
"codeBlock.preserveIndentation": true,
"codeBlock.preserveBlankLines": true,
"html.skipFormat": true
},
"plugins": ["./.tmp/markdown-tools/markdown-0.26.0.wasm"]
}
skills/policies/markdown-style/assets/templates/lefthook.sh
#!/bin/sh
set -eu
export GIT_LITERAL_PATHSPECS=1
exec python3 tools/markdown.py --lefthook "$@"
skills/policies/markdown-style/assets/templates/lefthook.yml
min_version: 2.1.16
lefthook: sh tools/lefthook.sh
assert_lefthook_installed: true
no_auto_install: true
glob_matcher: doublestar
pre-commit:
parallel: false
commands:
markdown:
files: git diff --cached --name-only --diff-filter=ACMRT
glob: "**/*.md"
run: python3 tools/markdown.py staged -- {files}
stage_fixed: true
react-doctor:
run: |
if [ -f web/doctor.config.ts ]; then
if [ ! -f web/scripts/react-doctor.ts ]; then
echo "React Doctor entrypoint is missing for this Web package." >&2
exit 1
fi
node web/scripts/react-doctor.ts staged
elif git cat-file -e :web/doctor.config.ts 2>/dev/null; then
echo "React Doctor configuration marker is staged but missing from the worktree." >&2
exit 1
elif ! git log --all --format='%H' -- web/doctor.config.ts | grep -q . || git diff --cached --quiet --diff-filter=ACMRT -- web/src web/scripts/render-public.tsx; then
exit 0
else
echo "React Doctor configuration marker is missing for a staged Web React change." >&2
exit 1
fi
skills/policies/markdown-style/assets/templates/markdown.py
#!/usr/bin/env python3
"""Delegate to the source or project-installed Markdown style Policy."""
from pathlib import Path
import runpy
import sys
root = Path(__file__).resolve().parents[1]
for package in (root / "skills/policies/markdown-style", root / ".agents/skills/markdown-style"):
script = package / "assets/scripts/markdown.py"
if script.is_file():
entry = runpy.run_path(str(script))
if sys.argv[1:2] == ["--lefthook"]:
raise SystemExit(entry["lefthook_command"](sys.argv[2:]))
raise SystemExit(entry["main"]())
raise SystemExit("Markdown style Policy is missing; install or restore the reviewed package.")
skills/policies/markdown-style/assets/toolchain.json
{
"schema_version": 1,
"dprint": {
"version": "0.60.1",
"platforms": {
"darwin-arm64": {
"url": "https://github.com/dprint/dprint/releases/download/0.60.1/dprint-aarch64-apple-darwin.zip",
"sha256": "b495103be1c01ba097449d73af3d76bbcef3deb291b87e9b7db6e8d12d497ef6",
"binary_sha256": "f99f7b413d381d44da765334eb4a048fcaeaaa0ca73d66de44d36bde64831a2d",
"format": "zip",
"member": "dprint"
},
"darwin-x86_64": {
"url": "https://github.com/dprint/dprint/releases/download/0.60.1/dprint-x86_64-apple-darwin.zip",
"sha256": "551fd5b0ce32a83682f6aaa425317db54402451a0ab66b46c0e11e17900f7db8",
"binary_sha256": "fa9851104aa1688a464d772c3aabf648c95caf86486adcc994aeffe5bbd0808a",
"format": "zip",
"member": "dprint"
},
"linux-arm64": {
"url": "https://github.com/dprint/dprint/releases/download/0.60.1/dprint-aarch64-unknown-linux-gnu.zip",
"sha256": "03fd8908f9b418bdc4701347ff0527a59d1c08db2bc09a9f5d8101eb711e7fe2",
"binary_sha256": "b42ebbd7202e66124f42fb584381f929d7baba9e6bdacb17d3df028595f2daf7",
"format": "zip",
"member": "dprint"
},
"linux-x86_64": {
"url": "https://github.com/dprint/dprint/releases/download/0.60.1/dprint-x86_64-unknown-linux-gnu.zip",
"sha256": "5612c587a34dfbbda37162d46755f9e8c6a4efba7950920b2a308ea84ce0886a",
"binary_sha256": "94c732df08088d95ed487070a3b619db9763d87212a8d06620062226c474b0b4",
"format": "zip",
"member": "dprint"
}
}
},
"lefthook": {
"version": "2.1.16",
"platforms": {
"darwin-arm64": {
"url": "https://github.com/evilmartians/lefthook/releases/download/v2.1.16/lefthook_2.1.16_MacOS_arm64",
"sha256": "67a3355bb1556b9a6f1cfc0684b139074b582ceffff45f28a8af327fea17ea2b"
},
"darwin-x86_64": {
"url": "https://github.com/evilmartians/lefthook/releases/download/v2.1.16/lefthook_2.1.16_MacOS_x86_64",
"sha256": "b298f32781371a8043f6705351561b5ee62326896645a9f1df8cc6821b5e6947"
},
"linux-arm64": {
"url": "https://github.com/evilmartians/lefthook/releases/download/v2.1.16/lefthook_2.1.16_Linux_arm64",
"sha256": "c16fbb52aa1b6bc7eeae28e13bffe5bfd4c3885204b73e00661fd9b240dbaccf"
},
"linux-x86_64": {
"url": "https://github.com/evilmartians/lefthook/releases/download/v2.1.16/lefthook_2.1.16_Linux_x86_64",
"sha256": "160094c2c7388baed140fdda63a1f8d238e81f629508d0a1f67af2faa5216e6a"
}
}
},
"markdown": {
"version": "0.26.0",
"filename": "markdown-0.26.0.wasm",
"url": "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.26.0/plugin.wasm",
"sha256": "5cebd0459bf1aa13fb027331720158d03e2a897b1d9f7b3bf93cac661ca4ddbd"
}
}