feat: migrate fix-title skill
This commit is contained in:
parent
ef50a19d89
commit
fa61d46933
|
|
@ -29,6 +29,10 @@ tmp/
|
||||||
temp/
|
temp/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Manual Skill test artifacts
|
||||||
|
skills/*/manual-test/*.md
|
||||||
|
!skills/*/manual-test/README.md
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
This repository separates Skill development source from the local public installation surface.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
Development source:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\Documents\Codex\skills-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
Public local Skill installation surface:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\.agents\skills
|
||||||
|
```
|
||||||
|
|
||||||
|
Agent-specific private Skill folders, such as `C:\Users\wangq\.claude\skills`, should link to the public installation surface instead of linking directly to the development source.
|
||||||
|
|
||||||
|
## Conda Environment
|
||||||
|
|
||||||
|
Use the shared repository environment by default:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda env create -f environment.yml
|
||||||
|
conda activate skills-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
If the environment already exists:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda activate skills-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
Most lightweight Skills should use this shared environment. Create a dedicated environment only when a Skill has conflicting or heavy dependencies.
|
||||||
|
|
||||||
|
## Install One Skill
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
Git Bash / Linux / macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install All Default Skills
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -All
|
||||||
|
```
|
||||||
|
|
||||||
|
Git Bash / Linux / macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh --all
|
||||||
|
```
|
||||||
|
|
||||||
|
Full installation uses `registry/skills-index.md` and installs only Skills with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Status: tested, installable, or installed
|
||||||
|
Install Policy: default
|
||||||
|
```
|
||||||
|
|
||||||
|
Conditional, optional, disabled, candidate, deprecated, and archived Skills are skipped during full installation.
|
||||||
|
|
||||||
|
## Updating An Existing Installed Skill
|
||||||
|
|
||||||
|
By default, install scripts refuse to overwrite an existing Skill.
|
||||||
|
|
||||||
|
Use `-Force` or `--force` to back up the current installed directory and copy the repository version:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title --force
|
||||||
|
```
|
||||||
|
|
||||||
|
Backups are created next to the installed Skill:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\.agents\skills\fix-title.backup-YYYYMMDDHHMMSS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Claude Link
|
||||||
|
|
||||||
|
To create or update the Claude private Skill entry as a link to the public installation surface:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title -InstallClaudeLink
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title --install-claude-link
|
||||||
|
```
|
||||||
|
|
||||||
|
Codex is not currently treated as a separate private install target for these shared automation Skills.
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
name: skills-vault
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- python=3.11
|
||||||
|
- pip
|
||||||
|
|
@ -11,9 +11,9 @@ Existing CCPE content is not migrated here.
|
||||||
|
|
||||||
## Skills
|
## Skills
|
||||||
|
|
||||||
| Skill | Purpose | Source | Status | Installed Path | CCPE Registration |
|
| Skill | Purpose | Source | Status | Install Policy | Installed Path | CCPE Registration |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
| _none yet_ | | | | | |
|
| `fix-title` | Shift Markdown ATX heading depth for pasted LLM replies before insertion under parent sections. | `C:\Users\wangq\Documents\Codex\knowledge-vault\skills\fix-title` | `installed` | `default` | `C:\Users\wangq\.agents\skills\fix-title` | none |
|
||||||
|
|
||||||
## Status Values
|
## Status Values
|
||||||
|
|
||||||
|
|
@ -25,3 +25,12 @@ installed
|
||||||
deprecated
|
deprecated
|
||||||
archived
|
archived
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Install Policy Values
|
||||||
|
|
||||||
|
```text
|
||||||
|
default
|
||||||
|
optional
|
||||||
|
conditional
|
||||||
|
disabled
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ Examples of future scripts:
|
||||||
|
|
||||||
- validate Skill directory structure
|
- validate Skill directory structure
|
||||||
- list registered Skills
|
- list registered Skills
|
||||||
- install selected Skills to `C:\Users\wangq\.agents\skills`
|
- install selected or default Skills to `C:\Users\wangq\.agents\skills`
|
||||||
- sync selected Skills
|
- create Agentic private Skill links that point to `.agents\skills`
|
||||||
- check registry consistency
|
- check registry consistency
|
||||||
|
|
||||||
Skill-specific scripts should live under:
|
Skill-specific scripts should live under:
|
||||||
|
|
@ -15,3 +15,15 @@ Skill-specific scripts should live under:
|
||||||
```text
|
```text
|
||||||
skills/<skill-name>/scripts/
|
skills/<skill-name>/scripts/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Current install scripts:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -All
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title
|
||||||
|
bash scripts/install-skill.sh --all
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
param(
|
||||||
|
[string]$Skill,
|
||||||
|
[switch]$All,
|
||||||
|
[switch]$Force,
|
||||||
|
[switch]$InstallClaudeLink,
|
||||||
|
[string]$InstallRoot = "$HOME\.agents\skills",
|
||||||
|
[string]$ClaudeSkillsRoot = "$HOME\.claude\skills"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Show-Usage {
|
||||||
|
Write-Host "Usage:"
|
||||||
|
Write-Host " powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title"
|
||||||
|
Write-Host " powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -All"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Options:"
|
||||||
|
Write-Host " -Force Back up and replace existing installed Skill"
|
||||||
|
Write-Host " -InstallClaudeLink Create/update .claude\skills link to .agents\skills"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($Skill -and $All) -or (-not $Skill -and -not $All)) {
|
||||||
|
Show-Usage
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$RepoRoot = Resolve-Path (Join-Path $ScriptDir "..")
|
||||||
|
$SkillsRoot = Join-Path $RepoRoot "skills"
|
||||||
|
$RegistryPath = Join-Path $RepoRoot "registry\skills-index.md"
|
||||||
|
|
||||||
|
function Get-RegistryMeta {
|
||||||
|
param([string]$Name)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $RegistryPath)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$escaped = [regex]::Escape("`$Name")
|
||||||
|
$line = Get-Content -LiteralPath $RegistryPath | Where-Object { $_ -match "^\|\s*``$([regex]::Escape($Name))``\s*\|" } | Select-Object -First 1
|
||||||
|
if (-not $line) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = $line.Trim() -split "\|"
|
||||||
|
$cells = @()
|
||||||
|
foreach ($part in $parts) {
|
||||||
|
$cell = $part.Trim()
|
||||||
|
if ($cell.Length -gt 0) {
|
||||||
|
$cells += $cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cells.Count -lt 7) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return [PSCustomObject]@{
|
||||||
|
Skill = $cells[0].Trim("``")
|
||||||
|
Purpose = $cells[1]
|
||||||
|
Source = $cells[2]
|
||||||
|
Status = $cells[3].Trim("``")
|
||||||
|
InstallPolicy = $cells[4].Trim("``")
|
||||||
|
InstalledPath = $cells[5]
|
||||||
|
CcpeRegistration = $cells[6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AllDefaultSkills {
|
||||||
|
$allowedStatuses = @("tested", "installable", "installed")
|
||||||
|
$skills = @()
|
||||||
|
|
||||||
|
Get-ChildItem -LiteralPath $SkillsRoot -Directory | ForEach-Object {
|
||||||
|
$skillName = $_.Name
|
||||||
|
$skillFile = Join-Path $_.FullName "SKILL.md"
|
||||||
|
if (-not (Test-Path -LiteralPath $skillFile)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = Get-RegistryMeta -Name $skillName
|
||||||
|
if (-not $meta) {
|
||||||
|
Write-Host "Skipping ${skillName}: no registry row"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($allowedStatuses -notcontains $meta.Status) {
|
||||||
|
Write-Host "Skipping ${skillName}: status is $($meta.Status)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($meta.InstallPolicy -ne "default") {
|
||||||
|
Write-Host "Skipping ${skillName}: install policy is $($meta.InstallPolicy)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$skills += $skillName
|
||||||
|
}
|
||||||
|
|
||||||
|
return $skills
|
||||||
|
}
|
||||||
|
|
||||||
|
function Copy-SkillDirectory {
|
||||||
|
param(
|
||||||
|
[string]$Name,
|
||||||
|
[string]$Source,
|
||||||
|
[string]$Target
|
||||||
|
)
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Target) | Out-Null
|
||||||
|
New-Item -ItemType Directory -Force -Path $Target | Out-Null
|
||||||
|
|
||||||
|
Get-ChildItem -Force -LiteralPath $Source | Where-Object {
|
||||||
|
$_.Name -notin @("manual-test", "__pycache__", ".pytest_cache")
|
||||||
|
} | ForEach-Object {
|
||||||
|
Copy-Item -LiteralPath $_.FullName -Destination $Target -Recurse -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Backup-ExistingPath {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
$stamp = Get-Date -Format "yyyyMMddHHmmss"
|
||||||
|
$backup = "$Path.backup-$stamp"
|
||||||
|
Move-Item -LiteralPath $Path -Destination $backup
|
||||||
|
return $backup
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-OneSkill {
|
||||||
|
param([string]$Name)
|
||||||
|
|
||||||
|
if ($Name -notmatch "^[a-z0-9][a-z0-9-]*$") {
|
||||||
|
throw "Invalid Skill name: $Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = Join-Path $SkillsRoot $Name
|
||||||
|
$skillFile = Join-Path $source "SKILL.md"
|
||||||
|
if (-not (Test-Path -LiteralPath $skillFile)) {
|
||||||
|
throw "Skill source not found: $skillFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = Get-RegistryMeta -Name $Name
|
||||||
|
if ($meta -and $meta.InstallPolicy -eq "disabled" -and -not $Force) {
|
||||||
|
throw "Skill $Name has install policy disabled. Use -Force only if you intentionally want to install it."
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = Join-Path $InstallRoot $Name
|
||||||
|
if (Test-Path -LiteralPath $target) {
|
||||||
|
if (-not $Force) {
|
||||||
|
throw "Installed Skill already exists: $target. Re-run with -Force to back it up and replace it."
|
||||||
|
}
|
||||||
|
$backup = Backup-ExistingPath -Path $target
|
||||||
|
Write-Host "Backed up existing Skill to $backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
Copy-SkillDirectory -Name $Name -Source $source -Target $target
|
||||||
|
Write-Host "Installed $Name to $target"
|
||||||
|
|
||||||
|
if ($InstallClaudeLink) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $ClaudeSkillsRoot | Out-Null
|
||||||
|
$claudeTarget = Join-Path $ClaudeSkillsRoot $Name
|
||||||
|
if (Test-Path -LiteralPath $claudeTarget) {
|
||||||
|
if (-not $Force) {
|
||||||
|
throw "Claude Skill link/path already exists: $claudeTarget. Re-run with -Force to back it up and replace it."
|
||||||
|
}
|
||||||
|
$backup = Backup-ExistingPath -Path $claudeTarget
|
||||||
|
Write-Host "Backed up existing Claude path to $backup"
|
||||||
|
}
|
||||||
|
New-Item -ItemType Junction -Path $claudeTarget -Target $target | Out-Null
|
||||||
|
Write-Host "Linked Claude Skill $claudeTarget -> $target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($All) {
|
||||||
|
$skillNames = Get-AllDefaultSkills
|
||||||
|
if ($skillNames.Count -eq 0) {
|
||||||
|
Write-Host "No default installable Skills found."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
foreach ($name in $skillNames) {
|
||||||
|
Install-OneSkill -Name $name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Install-OneSkill -Name $Skill
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
bash scripts/install-skill.sh fix-title
|
||||||
|
bash scripts/install-skill.sh --all
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--force Back up and replace existing installed Skill
|
||||||
|
--install-claude-link Create/update .claude/skills link to .agents/skills
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
skill=""
|
||||||
|
install_all=0
|
||||||
|
force=0
|
||||||
|
install_claude_link=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--all)
|
||||||
|
install_all=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--force)
|
||||||
|
force=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--install-claude-link)
|
||||||
|
install_claude_link=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$skill" ]]; then
|
||||||
|
echo "Only one Skill name may be provided." >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
skill="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$install_all" -eq 1 && -n "$skill" ]] || [[ "$install_all" -eq 0 && -z "$skill" ]]; then
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/.." && pwd)"
|
||||||
|
skills_root="$repo_root/skills"
|
||||||
|
registry_path="$repo_root/registry/skills-index.md"
|
||||||
|
|
||||||
|
if [[ -n "${USERPROFILE:-}" ]]; then
|
||||||
|
home_dir="$USERPROFILE"
|
||||||
|
else
|
||||||
|
home_dir="$HOME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_root="${SKILLS_VAULT_INSTALL_ROOT:-$home_dir/.agents/skills}"
|
||||||
|
claude_skills_root="${SKILLS_VAULT_CLAUDE_SKILLS_ROOT:-$home_dir/.claude/skills}"
|
||||||
|
|
||||||
|
registry_meta() {
|
||||||
|
local name="$1"
|
||||||
|
[[ -f "$registry_path" ]] || return 1
|
||||||
|
grep -E "^\|[[:space:]]*\`$name\`[[:space:]]*\|" "$registry_path" | head -n 1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
registry_cell() {
|
||||||
|
local line="$1"
|
||||||
|
local index="$2"
|
||||||
|
printf '%s\n' "$line" | awk -F'|' -v i="$index" '{gsub(/^ +| +$/, "", $(i + 1)); gsub(/`/, "", $(i + 1)); print $(i + 1)}'
|
||||||
|
}
|
||||||
|
|
||||||
|
all_default_skills() {
|
||||||
|
local allowed_statuses=" tested installable installed "
|
||||||
|
for dir in "$skills_root"/*; do
|
||||||
|
[[ -d "$dir" ]] || continue
|
||||||
|
[[ -f "$dir/SKILL.md" ]] || continue
|
||||||
|
local name
|
||||||
|
name="$(basename "$dir")"
|
||||||
|
local line
|
||||||
|
line="$(registry_meta "$name")"
|
||||||
|
if [[ -z "$line" ]]; then
|
||||||
|
echo "Skipping $name: no registry row" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
local status policy
|
||||||
|
status="$(registry_cell "$line" 4)"
|
||||||
|
policy="$(registry_cell "$line" 5)"
|
||||||
|
if [[ "$allowed_statuses" != *" $status "* ]]; then
|
||||||
|
echo "Skipping $name: status is $status" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$policy" != "default" ]]; then
|
||||||
|
echo "Skipping $name: install policy is $policy" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_existing_path() {
|
||||||
|
local path="$1"
|
||||||
|
local stamp
|
||||||
|
stamp="$(date +%Y%m%d%H%M%S)"
|
||||||
|
local backup="$path.backup-$stamp"
|
||||||
|
mv "$path" "$backup"
|
||||||
|
printf '%s\n' "$backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_skill_directory() {
|
||||||
|
local source="$1"
|
||||||
|
local target="$2"
|
||||||
|
mkdir -p "$target"
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for item in "$source"/*; do
|
||||||
|
local base
|
||||||
|
base="$(basename "$item")"
|
||||||
|
case "$base" in
|
||||||
|
manual-test|__pycache__|.pytest_cache)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
cp -a "$item" "$target/"
|
||||||
|
done
|
||||||
|
shopt -u dotglob nullglob
|
||||||
|
}
|
||||||
|
|
||||||
|
create_claude_link() {
|
||||||
|
local name="$1"
|
||||||
|
local target="$2"
|
||||||
|
local link="$claude_skills_root/$name"
|
||||||
|
mkdir -p "$claude_skills_root"
|
||||||
|
if [[ -e "$link" || -L "$link" ]]; then
|
||||||
|
if [[ "$force" -ne 1 ]]; then
|
||||||
|
echo "Claude Skill link/path already exists: $link. Re-run with --force to back it up and replace it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local backup
|
||||||
|
backup="$(backup_existing_path "$link")"
|
||||||
|
echo "Backed up existing Claude path to $backup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${OS:-}" == "Windows_NT" ]]; then
|
||||||
|
cmd.exe /c mklink /J "$(cygpath -w "$link")" "$(cygpath -w "$target")" >/dev/null
|
||||||
|
else
|
||||||
|
ln -s "$target" "$link"
|
||||||
|
fi
|
||||||
|
echo "Linked Claude Skill $link -> $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_one_skill() {
|
||||||
|
local name="$1"
|
||||||
|
if [[ ! "$name" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
|
||||||
|
echo "Invalid Skill name: $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local source="$skills_root/$name"
|
||||||
|
if [[ ! -f "$source/SKILL.md" ]]; then
|
||||||
|
echo "Skill source not found: $source/SKILL.md" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local line
|
||||||
|
line="$(registry_meta "$name")"
|
||||||
|
if [[ -n "$line" ]]; then
|
||||||
|
local policy
|
||||||
|
policy="$(registry_cell "$line" 5)"
|
||||||
|
if [[ "$policy" == "disabled" && "$force" -ne 1 ]]; then
|
||||||
|
echo "Skill $name has install policy disabled. Use --force only if you intentionally want to install it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local target="$install_root/$name"
|
||||||
|
mkdir -p "$install_root"
|
||||||
|
if [[ -e "$target" || -L "$target" ]]; then
|
||||||
|
if [[ "$force" -ne 1 ]]; then
|
||||||
|
echo "Installed Skill already exists: $target. Re-run with --force to back it up and replace it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local backup
|
||||||
|
backup="$(backup_existing_path "$target")"
|
||||||
|
echo "Backed up existing Skill to $backup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
copy_skill_directory "$source" "$target"
|
||||||
|
echo "Installed $name to $target"
|
||||||
|
|
||||||
|
if [[ "$install_claude_link" -eq 1 ]]; then
|
||||||
|
create_claude_link "$name" "$target"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$install_all" -eq 1 ]]; then
|
||||||
|
mapfile -t skills < <(all_default_skills)
|
||||||
|
if [[ "${#skills[@]}" -eq 0 ]]; then
|
||||||
|
echo "No default installable Skills found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
for name in "${skills[@]}"; do
|
||||||
|
install_one_skill "$name"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
install_one_skill "$skill"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Install fix-title
|
||||||
|
|
||||||
|
`fix-title` uses the repository-level `skills-vault` Conda environment.
|
||||||
|
|
||||||
|
## Prepare Environment
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda env create -f environment.yml
|
||||||
|
conda activate skills-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
If the environment already exists:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda activate skills-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m unittest discover -s skills\fix-title\tests -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install To Public Skill Surface
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace an existing installed copy after backing it up:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
Git Bash / Linux / macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title
|
||||||
|
bash scripts/install-skill.sh fix-title --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optional Claude Link
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File .\scripts\install-skill.ps1 -Skill fix-title -InstallClaudeLink
|
||||||
|
```
|
||||||
|
|
||||||
|
Git Bash / Linux / macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/install-skill.sh fix-title --install-claude-link
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# fix-title Migration
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\Documents\Codex\knowledge-vault\skills\fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\Documents\Codex\skills-vault\skills\fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
This migration copies the automation Skill source into `skills-vault`.
|
||||||
|
|
||||||
|
The original `knowledge-vault` directory is intentionally left in place. Deleting the old source is a manual user action.
|
||||||
|
|
||||||
|
## Install Model
|
||||||
|
|
||||||
|
`skills-vault` is the development source repository.
|
||||||
|
|
||||||
|
The local public installation surface remains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
C:\Users\wangq\.agents\skills\fix-title
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude, OpenClaw, and other Agentic private Skill folders may link to the public installation surface instead of linking directly to this development source.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
```text
|
||||||
|
migration status: migrated-source-and-installed
|
||||||
|
installed version updated: yes
|
||||||
|
installed backup: C:\Users\wangq\.agents\skills\fix-title.backup-20260613235817
|
||||||
|
manual test: passed
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
name: fix-title
|
||||||
|
description: Use when a pasted ChatGPT or LLM Markdown reply has heading levels that need to be shifted down before insertion under a parent section such as "## GPT"; also use for Markdown title repair, heading nesting fixes, ATX header adjustment, and adding one or more # levels to headings in a file.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fix Title
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Fix Markdown heading depth for copied LLM replies before inserting them into a discussion record. Use the bundled script for deterministic file edits instead of asking the model to rewrite the document.
|
||||||
|
|
||||||
|
The default operation adds two heading levels, turning `# Title` into `### Title`, which fits content placed under a parent heading like `## GPT`.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Confirm the input is a Markdown file containing the raw reply to adjust.
|
||||||
|
2. Choose the number of levels to add. Default to `2` unless the user specifies another value.
|
||||||
|
3. Run `scripts/fix_markdown_titles.py` on the file.
|
||||||
|
4. If the target is important or the requested level shift is unusual, use `--dry-run` or `--output` first and inspect the result.
|
||||||
|
5. Report the path modified and the level shift used.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python scripts/fix_markdown_titles.py path/to/reply.md
|
||||||
|
python scripts/fix_markdown_titles.py path/to/reply.md --levels 1
|
||||||
|
python scripts/fix_markdown_titles.py path/to/reply.md --levels 3 --dry-run
|
||||||
|
python scripts/fix_markdown_titles.py path/to/reply.md --levels 2 --output path/to/fixed.md
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows PowerShell, quote paths that contain spaces:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python .\scripts\fix_markdown_titles.py "C:\path with spaces\reply.md" --levels 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
| Input | `--levels 2` output |
|
||||||
|
| --- | --- |
|
||||||
|
| `# A` | `### A` |
|
||||||
|
| `## B` | `#### B` |
|
||||||
|
| `###### C` | `###### C` |
|
||||||
|
|
||||||
|
The script only changes ATX headings: lines beginning with up to three spaces followed by `#` through `######` and then whitespace. It does not change headings inside fenced code blocks, quoted lines such as `> # text`, or indented code.
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
- Do not use a broad replace like `#` to `###`; it will corrupt code blocks, quoted content, tags, and inline text.
|
||||||
|
- Do not increase headings past level six; Markdown ATX headings stop at `######`.
|
||||||
|
- Do not change the recorder's outer headings such as `# 1` or `## GPT` unless the user explicitly points the skill at the recorder file and requests that behavior.
|
||||||
|
- Do not use prompt rewriting for this task when the script can perform the edit directly.
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
display_name: Fix Markdown Titles
|
||||||
|
short_description: Shift Markdown headings down before inserting pasted chat replies.
|
||||||
|
default_prompt: Fix the Markdown heading levels in this file so it can be pasted under a parent section.
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Manual Test Input
|
||||||
|
|
||||||
|
Place one raw Markdown file here for manual validation.
|
||||||
|
|
||||||
|
Recommended filename:
|
||||||
|
|
||||||
|
```text
|
||||||
|
raw-reply.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The file should be an original pasted LLM reply before heading repair.
|
||||||
|
|
||||||
|
After the file is added, run the Skill against a copy or with `--output` first, then compare the result before updating the installed version.
|
||||||
|
|
@ -0,0 +1,371 @@
|
||||||
|
结论:**V4.1 已经基本补齐了前面讨论里最关键的纲领级缺口**,尤其是这五个补得很准:
|
||||||
|
|
||||||
|
1. **生产工坊**补上了 `video-workbench / PPT / 多模态素材生产` 这类场景的位置。
|
||||||
|
2. **QPI Drift Guard**补上了“P 域任务滑入 I 域治理项目”的防线。
|
||||||
|
3. **Whole-source Gestalt First**吸收了你素材提炼测试里的关键教训。
|
||||||
|
4. **Execution Authenticity**吸收了第一次多 Agent 评审测试里的“伪执行”教训。
|
||||||
|
5. **Cost Ledger + Stop Right**吸收了 3000 万 tokens 的 ROI 教训。
|
||||||
|
|
||||||
|
这版已经不是“有没有骨架”的问题,而是**有几处术语、边界和强度需要校准**。我会把它分成三类:必须改的小 bug、建议补入纲领的遗漏、建议下放到 Agentic 守则的内容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 一、必须改的几个小 bug / 强冲突
|
||||||
|
|
||||||
|
## 1. “两种封装策略”要改成“三种封装策略”
|
||||||
|
|
||||||
|
你第 2 节现在写:
|
||||||
|
|
||||||
|
> 根据业务场景的熵值与秩序,我们采用两种不同的封装策略。
|
||||||
|
|
||||||
|
但表格已经是三类:
|
||||||
|
|
||||||
|
```text
|
||||||
|
逻辑轮机
|
||||||
|
生产工坊
|
||||||
|
战略透镜
|
||||||
|
```
|
||||||
|
|
||||||
|
应改为:
|
||||||
|
|
||||||
|
```md
|
||||||
|
根据业务场景的熵值、秩序与产物形态,我们采用三种不同的封装策略。
|
||||||
|
```
|
||||||
|
|
||||||
|
同时,下面这句也要改:
|
||||||
|
|
||||||
|
> 针对 B 类(战略透镜)场景……
|
||||||
|
|
||||||
|
因为加了“生产工坊”之后,如果继续按 A/B/C 排列,战略透镜已经不是 B 类,而是 C 类。最稳妥的写法是不要写字母:
|
||||||
|
|
||||||
|
```md
|
||||||
|
针对“战略透镜”场景,当面临试错成本极高、失败即引发法理追责的“单向门”商业决策时……
|
||||||
|
```
|
||||||
|
|
||||||
|
这是小 bug,但很重要。分类体系一旦留下歧义,后面守则会被带歪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. “Agent 输出必须包含 CoT 摘要”建议改掉
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Agent 输出结果时,必须包含 CoT(思维链)摘要,即“我为什么这么判/这么想”
|
||||||
|
```
|
||||||
|
|
||||||
|
这里建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Agent 输出结果时,必须包含可审计的理由摘要(Decision Rationale),说明其关键依据、适用规则、不确定性、排除选项与可推翻条件。
|
||||||
|
```
|
||||||
|
|
||||||
|
原因不是咬文嚼字。**你真正需要的不是 CoT,而是可校准、可审计、可反驳的理由结构。**
|
||||||
|
|
||||||
|
CoT 这个词容易把系统引向“暴露内部推理过程”。但真实产品里更稳的形态是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
结论
|
||||||
|
依据
|
||||||
|
规则
|
||||||
|
不确定性
|
||||||
|
反例
|
||||||
|
人工确认点
|
||||||
|
可推翻条件
|
||||||
|
```
|
||||||
|
|
||||||
|
这和你的“钢尺与皮尺”“不仅做对,还要好改”更一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. QPI 的 I 域响应里,“强制启动双离合榨取”太重
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
【I】系统响应:生态学干预。强制启动后道的“思想考古”与“双离合榨取”……
|
||||||
|
```
|
||||||
|
|
||||||
|
但后面 3.6 又明确说“双离合榨取”是 Beta / 理论沙盘,工程实现成本极高。这两个地方有冲突。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
【I】系统响应:生态学干预。优先启动思想考古、战略透镜与人机回环;仅在高危、非遍历、明确授权的深水区,才考虑进入“双离合榨取”或摩擦蒸馏等 Beta 机制。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样层级就清楚了:
|
||||||
|
|
||||||
|
```text
|
||||||
|
I 域默认:思想考古 + 战略透镜 + 人机回环
|
||||||
|
I 域高危:预设委员会
|
||||||
|
I 域极高危/实验:双离合榨取
|
||||||
|
```
|
||||||
|
|
||||||
|
否则会把一个 Beta 沙盘机制误升成 I 域默认动作,容易再度重工业化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. “全源完形前置”需要加适用条件
|
||||||
|
|
||||||
|
这个原则非常重要,但现在写得有点绝对:
|
||||||
|
|
||||||
|
```md
|
||||||
|
严禁对连贯的长篇语料直接采用基于分块的并发提取机制……
|
||||||
|
必须首先调用高上下文承载力的主节点……
|
||||||
|
```
|
||||||
|
|
||||||
|
核心判断对,但建议补上适用边界:
|
||||||
|
|
||||||
|
```md
|
||||||
|
当源材料是连贯长篇语料,且仍处于高上下文参与者可承载范围内时,应优先进行 Whole-source Gestalt。若源材料是混合型,应先做宏主题拆分;若源材料是碎片型,应采用平铺发现,不强行制造层级。
|
||||||
|
```
|
||||||
|
|
||||||
|
这个边界其实你原来的 runbook 已经有:Step 0 会先判断 source 是 coherent、mixed 还是 fragmented,并据此选择 structure-first、macro-topic 或 flat-discovery 模式;它还要求在没有真实参与者输出时停止。
|
||||||
|
|
||||||
|
所以我建议纲领别写成“所有长材料都必须全源完形”,而是写成:
|
||||||
|
|
||||||
|
> **连贯长材料禁止 chunk-first;混合材料先宏拆;碎片材料不强行升层。**
|
||||||
|
|
||||||
|
这更精准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. “确定性的专家能力”建议改成“可校准的专家能力”
|
||||||
|
|
||||||
|
第 0 节你写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
通过架构工程封装为确定性的专家能力
|
||||||
|
```
|
||||||
|
|
||||||
|
这句话对逻辑轮机很对,但对战略透镜不完全对。I 域本来就不是确定性求解,而是动态权衡、反馈控制、人类裁决。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
通过架构工程封装为高保真、可校准、可追溯的专家能力。
|
||||||
|
```
|
||||||
|
|
||||||
|
或者:
|
||||||
|
|
||||||
|
```md
|
||||||
|
在 Q/P 域追求确定性执行,在 I 域追求高保真辅助、责任锚定与动态校准。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样不会和你自己的 QPI / 战略透镜冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. “执行真实性”要和“绿野仙踪”显式解耦
|
||||||
|
|
||||||
|
你现在同时有:
|
||||||
|
|
||||||
|
* 绿野仙踪:人类专家幕后扮演 Agent;
|
||||||
|
* 执行真实性:禁止主控节点代写模拟。
|
||||||
|
|
||||||
|
这两条都对,但需要一条桥接规则,否则读者可能觉得矛盾。
|
||||||
|
|
||||||
|
建议在第 11 条下面加一句:
|
||||||
|
|
||||||
|
```md
|
||||||
|
绿野仙踪阶段允许人类或主控节点模拟 Agent 以验证价值流,但所有此类产物必须标记为 simulation-only,不得作为正式 Agentic 输出进入生产决策链。正式运行阶段必须具备真实 Invocation Record。
|
||||||
|
```
|
||||||
|
|
||||||
|
这很关键。
|
||||||
|
|
||||||
|
因为你的第一次流程测试失败,不是因为“模拟”本身不允许,而是因为**模拟产物被当成正式多 Agent 产物**。你原来的 distillation runbook 已经有类似底线:不能把 packet-only 或 prompt-only 当成已执行,主会话不得做 primary source analysis / routing / topic-doc drafting / material extraction,carrier 不可用时要停在 blocked 状态。
|
||||||
|
|
||||||
|
所以这里要明确:
|
||||||
|
|
||||||
|
```text
|
||||||
|
模拟可以用于价值验证;
|
||||||
|
模拟不能冒充正式执行。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. “绝对停止权”的“代码级”可以稍微下放
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
系统必须具备代码级的绝对停止权……
|
||||||
|
```
|
||||||
|
|
||||||
|
作为工程守则可以;作为方法论纲领稍微细了点。
|
||||||
|
|
||||||
|
建议纲领里写成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
系统必须具备可执行、可审计、可由人类覆盖的停止机制。
|
||||||
|
```
|
||||||
|
|
||||||
|
然后把“代码级熔断、状态锁定、预算上限、自动停止条件”放到 Agentic Engineering Handbook。
|
||||||
|
|
||||||
|
更稳的版本:
|
||||||
|
|
||||||
|
```md
|
||||||
|
一旦达到最小充分产物或触发成本 / 范围 / 风险阈值,系统必须停止后续自我扩展,除非人类显式授权升级模式。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样保留原则,又不把实现细节塞进宪法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 二、我认为还缺的纲领级内容
|
||||||
|
|
||||||
|
整体看,V4.1 已经很完整。但如果问“是否还有遗漏”,我认为还有 **4 个纲领级补丁** 值得加入。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 增加“方法论与 Agentic 守则的边界”
|
||||||
|
|
||||||
|
你前面已经决定拆分:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HiFi Agent Studio = 纲领
|
||||||
|
Agentic Handbook = 守则
|
||||||
|
CCPE System = 工坊
|
||||||
|
Project Runbook = 项目规程
|
||||||
|
```
|
||||||
|
|
||||||
|
但 V4.1 本文里还没有显式声明这个边界。
|
||||||
|
|
||||||
|
建议加在开头 Profile 后,或者最后加一节:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## 文档边界
|
||||||
|
|
||||||
|
HiFi Agent Studio 是 AI 开发与专家型 Agentic 系统建设的方法论纲领,只规定世界观、产品观、架构底线与最高航行纪律。
|
||||||
|
|
||||||
|
具体的 Project Intake、Mode Selector、Invocation Record、Cost Ledger 模板、Authority Matrix、Evaluation Stack、Runtime Maturity Model、平台适配规则,应下放至 HiFi Agentic Engineering Handbook 与各项目 Runbook。
|
||||||
|
|
||||||
|
当具体工程守则与本纲领冲突时,以本纲领的核心原则为准;当本纲领没有规定时,以守则和项目 Runbook 执行。
|
||||||
|
```
|
||||||
|
|
||||||
|
这个要加。否则 V4.1 会继续向“宪法 + 施工手册混合体”膨胀。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 增加“责任不可外包 / Liability Boundary”
|
||||||
|
|
||||||
|
你在 Captain、非遍历性、控制棒里都提到了责任,但我建议把它升成航行纪律之一。
|
||||||
|
|
||||||
|
可以作为第 14 条:
|
||||||
|
|
||||||
|
```md
|
||||||
|
14. **责任不可外包 (Liability Boundary)**
|
||||||
|
* *定义*:在 I 域与高风险 P 域,AI 可以扩展人类专家的认知半径,但不能替代人类承担最终责任。
|
||||||
|
* *红线*:任何涉及法理追责、客户重大利益、不可逆决策或高风险判断的系统,必须明确 AI 建议、人类裁决、组织责任与客户责任的边界。禁止用“模型判断”掩盖人的责任坐标。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条应该进纲领,不应只放守则。
|
||||||
|
|
||||||
|
因为这是 HiFi Agent Studio 区别于“全自动幻觉派”的价值底线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 增加“最小权限与副作用隔离”
|
||||||
|
|
||||||
|
你在产品定义里提到了 Reactor 里的 Authority,但航行纪律里还没有把它作为底线写出来。
|
||||||
|
|
||||||
|
Agentic 系统和普通 prompt 最大区别之一是:它能调用工具、改文件、发请求、写数据库、触发外部动作。这里必须有一条纲领级红线。
|
||||||
|
|
||||||
|
建议加:
|
||||||
|
|
||||||
|
```md
|
||||||
|
15. **最小权限与副作用隔离 (Least Privilege & Side-effect Isolation)**
|
||||||
|
* *定义*:Agent 的工具权限必须小于其语言能力。能说不代表能做,能建议不代表能执行。
|
||||||
|
* *红线*:任何具备文件修改、外部 API、数据库写入、消息发送、支付、发布、删除或客户可见输出能力的 Agent,必须采用最小权限、沙箱隔离、人工确认与可回滚机制。禁止让通用推理能力直接获得不可逆执行权。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条非常适合你的“物理反应堆”隐喻。
|
||||||
|
反应堆不是光靠控制棒,还要有阀门、屏蔽层和紧急停堆。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 增加“过程数据的授权与主权”
|
||||||
|
|
||||||
|
你有“过程即数据”,也有“双离合榨取 / 隐性遥测”。但这里最好补一条伦理和数据边界。
|
||||||
|
|
||||||
|
尤其你未来服务客户,专家修改轨迹、光标悬停、编辑距离、反驳意见,这些都可能是高价值、高敏感数据。
|
||||||
|
|
||||||
|
建议在第 3 条“过程即数据”后补一句,或者单独作为第 16 条:
|
||||||
|
|
||||||
|
```md
|
||||||
|
16. **过程数据主权与授权 (Process Data Sovereignty)**
|
||||||
|
* *定义*:专家的修改痕迹、反馈、犹豫、反驳、编辑轨迹与隐性遥测,是高价值认知资产,也是高敏感数据。
|
||||||
|
* *红线*:任何过程数据采集必须具备明确授权、用途边界、最小采集、可撤回机制与隔离存储策略。禁止以“系统学习”为名,默认吞并专家经验或客户场景数据。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条会让你的“双离合榨取”更稳,不会显得像认知压榨机。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 三、已有内容中建议弱化或移动的部分
|
||||||
|
|
||||||
|
## 1. “双离合榨取”可以保留,但建议再降半档
|
||||||
|
|
||||||
|
这部分很有你自己的特色,也确实是战略透镜高危区的未来方向。但它现在语言太硬,比如:
|
||||||
|
|
||||||
|
```md
|
||||||
|
在数学底层上绝对不奖励多数派的平庸共识,精准重奖反共识真理
|
||||||
|
```
|
||||||
|
|
||||||
|
建议改柔一点:
|
||||||
|
|
||||||
|
```md
|
||||||
|
尝试提高少数高质量反共识信号的可见性,避免系统被多数派平庸共识吞没。
|
||||||
|
```
|
||||||
|
|
||||||
|
“反共识”不天然等于“真理”。BTS 是一种机制,不是神谕。这里稍微降调,会让方法论更稳。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. “全源完形前置”应该从思想考古里稍微移出来
|
||||||
|
|
||||||
|
现在它在 3.2 思想考古下面。我理解你的意图:它来自知识加工和长讨论蒸馏。
|
||||||
|
|
||||||
|
但严格说,它更像:
|
||||||
|
|
||||||
|
> 上下文工程 / 材料工程原则
|
||||||
|
|
||||||
|
而不是思想考古本身。
|
||||||
|
|
||||||
|
可以保留在 3.2,但标题可以改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
3.2 思想考古与上下文完形
|
||||||
|
```
|
||||||
|
|
||||||
|
或者在 3.2 前加一个小节:
|
||||||
|
|
||||||
|
```md
|
||||||
|
3.2 上下文工程:Whole-source Gestalt First
|
||||||
|
3.3 思想考古
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你想保持纲领简洁,当前放法也可以,但建议在表述中明确它是“长材料处理原则”,不是所有思想考古任务的默认动作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. “实测去魅”的例子不要只写专家相似度
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
构建自动化评测集(如:100 道真题),用数据(如:85% 专家相似度)说话。
|
||||||
|
```
|
||||||
|
|
||||||
|
这对逻辑轮机很好,但对战略透镜不够。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
构建分层评测:对逻辑轮机使用标准答案、专家相似度与自动化测试;对生产工坊使用可用性、转化效率与返工率;对战略透镜使用决策效用、可校准性、反例处理与治理可追踪性。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样不会把所有 HiFi 系统都压成“准确率系统”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,375 @@
|
||||||
|
# 1
|
||||||
|
|
||||||
|
I said .....
|
||||||
|
|
||||||
|
## GPT
|
||||||
|
|
||||||
|
结论:**V4.1 已经基本补齐了前面讨论里最关键的纲领级缺口**,尤其是这五个补得很准:
|
||||||
|
|
||||||
|
1. **生产工坊**补上了 `video-workbench / PPT / 多模态素材生产` 这类场景的位置。
|
||||||
|
2. **QPI Drift Guard**补上了“P 域任务滑入 I 域治理项目”的防线。
|
||||||
|
3. **Whole-source Gestalt First**吸收了你素材提炼测试里的关键教训。
|
||||||
|
4. **Execution Authenticity**吸收了第一次多 Agent 评审测试里的“伪执行”教训。
|
||||||
|
5. **Cost Ledger + Stop Right**吸收了 3000 万 tokens 的 ROI 教训。
|
||||||
|
|
||||||
|
这版已经不是“有没有骨架”的问题,而是**有几处术语、边界和强度需要校准**。我会把它分成三类:必须改的小 bug、建议补入纲领的遗漏、建议下放到 Agentic 守则的内容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 一、必须改的几个小 bug / 强冲突
|
||||||
|
|
||||||
|
#### 1. “两种封装策略”要改成“三种封装策略”
|
||||||
|
|
||||||
|
你第 2 节现在写:
|
||||||
|
|
||||||
|
> 根据业务场景的熵值与秩序,我们采用两种不同的封装策略。
|
||||||
|
|
||||||
|
但表格已经是三类:
|
||||||
|
|
||||||
|
```text
|
||||||
|
逻辑轮机
|
||||||
|
生产工坊
|
||||||
|
战略透镜
|
||||||
|
```
|
||||||
|
|
||||||
|
应改为:
|
||||||
|
|
||||||
|
```md
|
||||||
|
根据业务场景的熵值、秩序与产物形态,我们采用三种不同的封装策略。
|
||||||
|
```
|
||||||
|
|
||||||
|
同时,下面这句也要改:
|
||||||
|
|
||||||
|
> 针对 B 类(战略透镜)场景……
|
||||||
|
|
||||||
|
因为加了“生产工坊”之后,如果继续按 A/B/C 排列,战略透镜已经不是 B 类,而是 C 类。最稳妥的写法是不要写字母:
|
||||||
|
|
||||||
|
```md
|
||||||
|
针对“战略透镜”场景,当面临试错成本极高、失败即引发法理追责的“单向门”商业决策时……
|
||||||
|
```
|
||||||
|
|
||||||
|
这是小 bug,但很重要。分类体系一旦留下歧义,后面守则会被带歪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. “Agent 输出必须包含 CoT 摘要”建议改掉
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Agent 输出结果时,必须包含 CoT(思维链)摘要,即“我为什么这么判/这么想”
|
||||||
|
```
|
||||||
|
|
||||||
|
这里建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
Agent 输出结果时,必须包含可审计的理由摘要(Decision Rationale),说明其关键依据、适用规则、不确定性、排除选项与可推翻条件。
|
||||||
|
```
|
||||||
|
|
||||||
|
原因不是咬文嚼字。**你真正需要的不是 CoT,而是可校准、可审计、可反驳的理由结构。**
|
||||||
|
|
||||||
|
CoT 这个词容易把系统引向“暴露内部推理过程”。但真实产品里更稳的形态是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
结论
|
||||||
|
依据
|
||||||
|
规则
|
||||||
|
不确定性
|
||||||
|
反例
|
||||||
|
人工确认点
|
||||||
|
可推翻条件
|
||||||
|
```
|
||||||
|
|
||||||
|
这和你的“钢尺与皮尺”“不仅做对,还要好改”更一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. QPI 的 I 域响应里,“强制启动双离合榨取”太重
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
【I】系统响应:生态学干预。强制启动后道的“思想考古”与“双离合榨取”……
|
||||||
|
```
|
||||||
|
|
||||||
|
但后面 3.6 又明确说“双离合榨取”是 Beta / 理论沙盘,工程实现成本极高。这两个地方有冲突。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
【I】系统响应:生态学干预。优先启动思想考古、战略透镜与人机回环;仅在高危、非遍历、明确授权的深水区,才考虑进入“双离合榨取”或摩擦蒸馏等 Beta 机制。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样层级就清楚了:
|
||||||
|
|
||||||
|
```text
|
||||||
|
I 域默认:思想考古 + 战略透镜 + 人机回环
|
||||||
|
I 域高危:预设委员会
|
||||||
|
I 域极高危/实验:双离合榨取
|
||||||
|
```
|
||||||
|
|
||||||
|
否则会把一个 Beta 沙盘机制误升成 I 域默认动作,容易再度重工业化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4. “全源完形前置”需要加适用条件
|
||||||
|
|
||||||
|
这个原则非常重要,但现在写得有点绝对:
|
||||||
|
|
||||||
|
```md
|
||||||
|
严禁对连贯的长篇语料直接采用基于分块的并发提取机制……
|
||||||
|
必须首先调用高上下文承载力的主节点……
|
||||||
|
```
|
||||||
|
|
||||||
|
核心判断对,但建议补上适用边界:
|
||||||
|
|
||||||
|
```md
|
||||||
|
当源材料是连贯长篇语料,且仍处于高上下文参与者可承载范围内时,应优先进行 Whole-source Gestalt。若源材料是混合型,应先做宏主题拆分;若源材料是碎片型,应采用平铺发现,不强行制造层级。
|
||||||
|
```
|
||||||
|
|
||||||
|
这个边界其实你原来的 runbook 已经有:Step 0 会先判断 source 是 coherent、mixed 还是 fragmented,并据此选择 structure-first、macro-topic 或 flat-discovery 模式;它还要求在没有真实参与者输出时停止。
|
||||||
|
|
||||||
|
所以我建议纲领别写成“所有长材料都必须全源完形”,而是写成:
|
||||||
|
|
||||||
|
> **连贯长材料禁止 chunk-first;混合材料先宏拆;碎片材料不强行升层。**
|
||||||
|
|
||||||
|
这更精准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 5. “确定性的专家能力”建议改成“可校准的专家能力”
|
||||||
|
|
||||||
|
第 0 节你写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
通过架构工程封装为确定性的专家能力
|
||||||
|
```
|
||||||
|
|
||||||
|
这句话对逻辑轮机很对,但对战略透镜不完全对。I 域本来就不是确定性求解,而是动态权衡、反馈控制、人类裁决。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
通过架构工程封装为高保真、可校准、可追溯的专家能力。
|
||||||
|
```
|
||||||
|
|
||||||
|
或者:
|
||||||
|
|
||||||
|
```md
|
||||||
|
在 Q/P 域追求确定性执行,在 I 域追求高保真辅助、责任锚定与动态校准。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样不会和你自己的 QPI / 战略透镜冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 6. “执行真实性”要和“绿野仙踪”显式解耦
|
||||||
|
|
||||||
|
你现在同时有:
|
||||||
|
|
||||||
|
* 绿野仙踪:人类专家幕后扮演 Agent;
|
||||||
|
* 执行真实性:禁止主控节点代写模拟。
|
||||||
|
|
||||||
|
这两条都对,但需要一条桥接规则,否则读者可能觉得矛盾。
|
||||||
|
|
||||||
|
建议在第 11 条下面加一句:
|
||||||
|
|
||||||
|
```md
|
||||||
|
绿野仙踪阶段允许人类或主控节点模拟 Agent 以验证价值流,但所有此类产物必须标记为 simulation-only,不得作为正式 Agentic 输出进入生产决策链。正式运行阶段必须具备真实 Invocation Record。
|
||||||
|
```
|
||||||
|
|
||||||
|
这很关键。
|
||||||
|
|
||||||
|
因为你的第一次流程测试失败,不是因为“模拟”本身不允许,而是因为**模拟产物被当成正式多 Agent 产物**。你原来的 distillation runbook 已经有类似底线:不能把 packet-only 或 prompt-only 当成已执行,主会话不得做 primary source analysis / routing / topic-doc drafting / material extraction,carrier 不可用时要停在 blocked 状态。
|
||||||
|
|
||||||
|
所以这里要明确:
|
||||||
|
|
||||||
|
```text
|
||||||
|
模拟可以用于价值验证;
|
||||||
|
模拟不能冒充正式执行。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 7. “绝对停止权”的“代码级”可以稍微下放
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
系统必须具备代码级的绝对停止权……
|
||||||
|
```
|
||||||
|
|
||||||
|
作为工程守则可以;作为方法论纲领稍微细了点。
|
||||||
|
|
||||||
|
建议纲领里写成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
系统必须具备可执行、可审计、可由人类覆盖的停止机制。
|
||||||
|
```
|
||||||
|
|
||||||
|
然后把“代码级熔断、状态锁定、预算上限、自动停止条件”放到 Agentic Engineering Handbook。
|
||||||
|
|
||||||
|
更稳的版本:
|
||||||
|
|
||||||
|
```md
|
||||||
|
一旦达到最小充分产物或触发成本 / 范围 / 风险阈值,系统必须停止后续自我扩展,除非人类显式授权升级模式。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样保留原则,又不把实现细节塞进宪法。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 二、我认为还缺的纲领级内容
|
||||||
|
|
||||||
|
整体看,V4.1 已经很完整。但如果问“是否还有遗漏”,我认为还有 **4 个纲领级补丁** 值得加入。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 1. 增加“方法论与 Agentic 守则的边界”
|
||||||
|
|
||||||
|
你前面已经决定拆分:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HiFi Agent Studio = 纲领
|
||||||
|
Agentic Handbook = 守则
|
||||||
|
CCPE System = 工坊
|
||||||
|
Project Runbook = 项目规程
|
||||||
|
```
|
||||||
|
|
||||||
|
但 V4.1 本文里还没有显式声明这个边界。
|
||||||
|
|
||||||
|
建议加在开头 Profile 后,或者最后加一节:
|
||||||
|
|
||||||
|
```md
|
||||||
|
## 文档边界
|
||||||
|
|
||||||
|
HiFi Agent Studio 是 AI 开发与专家型 Agentic 系统建设的方法论纲领,只规定世界观、产品观、架构底线与最高航行纪律。
|
||||||
|
|
||||||
|
具体的 Project Intake、Mode Selector、Invocation Record、Cost Ledger 模板、Authority Matrix、Evaluation Stack、Runtime Maturity Model、平台适配规则,应下放至 HiFi Agentic Engineering Handbook 与各项目 Runbook。
|
||||||
|
|
||||||
|
当具体工程守则与本纲领冲突时,以本纲领的核心原则为准;当本纲领没有规定时,以守则和项目 Runbook 执行。
|
||||||
|
```
|
||||||
|
|
||||||
|
这个要加。否则 V4.1 会继续向“宪法 + 施工手册混合体”膨胀。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. 增加“责任不可外包 / Liability Boundary”
|
||||||
|
|
||||||
|
你在 Captain、非遍历性、控制棒里都提到了责任,但我建议把它升成航行纪律之一。
|
||||||
|
|
||||||
|
可以作为第 14 条:
|
||||||
|
|
||||||
|
```md
|
||||||
|
14. **责任不可外包 (Liability Boundary)**
|
||||||
|
* *定义*:在 I 域与高风险 P 域,AI 可以扩展人类专家的认知半径,但不能替代人类承担最终责任。
|
||||||
|
* *红线*:任何涉及法理追责、客户重大利益、不可逆决策或高风险判断的系统,必须明确 AI 建议、人类裁决、组织责任与客户责任的边界。禁止用“模型判断”掩盖人的责任坐标。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条应该进纲领,不应只放守则。
|
||||||
|
|
||||||
|
因为这是 HiFi Agent Studio 区别于“全自动幻觉派”的价值底线。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. 增加“最小权限与副作用隔离”
|
||||||
|
|
||||||
|
你在产品定义里提到了 Reactor 里的 Authority,但航行纪律里还没有把它作为底线写出来。
|
||||||
|
|
||||||
|
Agentic 系统和普通 prompt 最大区别之一是:它能调用工具、改文件、发请求、写数据库、触发外部动作。这里必须有一条纲领级红线。
|
||||||
|
|
||||||
|
建议加:
|
||||||
|
|
||||||
|
```md
|
||||||
|
15. **最小权限与副作用隔离 (Least Privilege & Side-effect Isolation)**
|
||||||
|
* *定义*:Agent 的工具权限必须小于其语言能力。能说不代表能做,能建议不代表能执行。
|
||||||
|
* *红线*:任何具备文件修改、外部 API、数据库写入、消息发送、支付、发布、删除或客户可见输出能力的 Agent,必须采用最小权限、沙箱隔离、人工确认与可回滚机制。禁止让通用推理能力直接获得不可逆执行权。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条非常适合你的“物理反应堆”隐喻。
|
||||||
|
反应堆不是光靠控制棒,还要有阀门、屏蔽层和紧急停堆。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4. 增加“过程数据的授权与主权”
|
||||||
|
|
||||||
|
你有“过程即数据”,也有“双离合榨取 / 隐性遥测”。但这里最好补一条伦理和数据边界。
|
||||||
|
|
||||||
|
尤其你未来服务客户,专家修改轨迹、光标悬停、编辑距离、反驳意见,这些都可能是高价值、高敏感数据。
|
||||||
|
|
||||||
|
建议在第 3 条“过程即数据”后补一句,或者单独作为第 16 条:
|
||||||
|
|
||||||
|
```md
|
||||||
|
16. **过程数据主权与授权 (Process Data Sovereignty)**
|
||||||
|
* *定义*:专家的修改痕迹、反馈、犹豫、反驳、编辑轨迹与隐性遥测,是高价值认知资产,也是高敏感数据。
|
||||||
|
* *红线*:任何过程数据采集必须具备明确授权、用途边界、最小采集、可撤回机制与隔离存储策略。禁止以“系统学习”为名,默认吞并专家经验或客户场景数据。
|
||||||
|
```
|
||||||
|
|
||||||
|
这条会让你的“双离合榨取”更稳,不会显得像认知压榨机。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 三、已有内容中建议弱化或移动的部分
|
||||||
|
|
||||||
|
#### 1. “双离合榨取”可以保留,但建议再降半档
|
||||||
|
|
||||||
|
这部分很有你自己的特色,也确实是战略透镜高危区的未来方向。但它现在语言太硬,比如:
|
||||||
|
|
||||||
|
```md
|
||||||
|
在数学底层上绝对不奖励多数派的平庸共识,精准重奖反共识真理
|
||||||
|
```
|
||||||
|
|
||||||
|
建议改柔一点:
|
||||||
|
|
||||||
|
```md
|
||||||
|
尝试提高少数高质量反共识信号的可见性,避免系统被多数派平庸共识吞没。
|
||||||
|
```
|
||||||
|
|
||||||
|
“反共识”不天然等于“真理”。BTS 是一种机制,不是神谕。这里稍微降调,会让方法论更稳。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. “全源完形前置”应该从思想考古里稍微移出来
|
||||||
|
|
||||||
|
现在它在 3.2 思想考古下面。我理解你的意图:它来自知识加工和长讨论蒸馏。
|
||||||
|
|
||||||
|
但严格说,它更像:
|
||||||
|
|
||||||
|
> 上下文工程 / 材料工程原则
|
||||||
|
|
||||||
|
而不是思想考古本身。
|
||||||
|
|
||||||
|
可以保留在 3.2,但标题可以改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
3.2 思想考古与上下文完形
|
||||||
|
```
|
||||||
|
|
||||||
|
或者在 3.2 前加一个小节:
|
||||||
|
|
||||||
|
```md
|
||||||
|
3.2 上下文工程:Whole-source Gestalt First
|
||||||
|
3.3 思想考古
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你想保持纲领简洁,当前放法也可以,但建议在表述中明确它是“长材料处理原则”,不是所有思想考古任务的默认动作。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. “实测去魅”的例子不要只写专家相似度
|
||||||
|
|
||||||
|
你现在写:
|
||||||
|
|
||||||
|
```md
|
||||||
|
构建自动化评测集(如:100 道真题),用数据(如:85% 专家相似度)说话。
|
||||||
|
```
|
||||||
|
|
||||||
|
这对逻辑轮机很好,但对战略透镜不够。
|
||||||
|
|
||||||
|
建议改成:
|
||||||
|
|
||||||
|
```md
|
||||||
|
构建分层评测:对逻辑轮机使用标准答案、专家相似度与自动化测试;对生产工坊使用可用性、转化效率与返工率;对战略透镜使用决策效用、可校准性、反例处理与治理可追踪性。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样不会把所有 HiFi 系统都压成“准确率系统”。
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Shift Markdown ATX headings down by a fixed number of levels."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
HEADING_RE = re.compile(r"^( {0,3})(#{1,6})([ \t]+.*)$")
|
||||||
|
FENCE_RE = re.compile(r"^( {0,3})(`{3,}|~{3,})")
|
||||||
|
|
||||||
|
|
||||||
|
def shift_headings(text: str, levels: int) -> str:
|
||||||
|
if levels <= 0:
|
||||||
|
raise ValueError("levels must be a positive integer")
|
||||||
|
|
||||||
|
lines = text.splitlines(keepends=True)
|
||||||
|
in_fence = False
|
||||||
|
fence_marker = ""
|
||||||
|
output: list[str] = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
body = line[:-1] if line.endswith("\n") else line
|
||||||
|
newline = "\n" if line.endswith("\n") else ""
|
||||||
|
if body.endswith("\r"):
|
||||||
|
body = body[:-1]
|
||||||
|
newline = "\r" + newline
|
||||||
|
|
||||||
|
fence_match = FENCE_RE.match(body)
|
||||||
|
if fence_match:
|
||||||
|
marker = fence_match.group(2)
|
||||||
|
marker_char = marker[0]
|
||||||
|
if not in_fence:
|
||||||
|
in_fence = True
|
||||||
|
fence_marker = marker_char * len(marker)
|
||||||
|
elif marker_char == fence_marker[0] and len(marker) >= len(fence_marker):
|
||||||
|
in_fence = False
|
||||||
|
fence_marker = ""
|
||||||
|
output.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if in_fence:
|
||||||
|
output.append(line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
heading_match = HEADING_RE.match(body)
|
||||||
|
if heading_match:
|
||||||
|
indent, hashes, rest = heading_match.groups()
|
||||||
|
new_level = min(6, len(hashes) + levels)
|
||||||
|
output.append(f"{indent}{'#' * new_level}{rest}{newline}")
|
||||||
|
else:
|
||||||
|
output.append(line)
|
||||||
|
|
||||||
|
return "".join(output)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Shift Markdown ATX headings down by adding # characters."
|
||||||
|
)
|
||||||
|
parser.add_argument("path", type=pathlib.Path, help="Markdown file to rewrite")
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--levels",
|
||||||
|
type=int,
|
||||||
|
default=2,
|
||||||
|
help="Number of heading levels to add. Default: 2",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-o",
|
||||||
|
"--output",
|
||||||
|
type=pathlib.Path,
|
||||||
|
help="Write to this file instead of modifying the source file",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Print converted content to stdout without writing files",
|
||||||
|
)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str]) -> int:
|
||||||
|
args = parse_args(argv)
|
||||||
|
|
||||||
|
if args.levels <= 0:
|
||||||
|
print("error: --levels must be a positive integer", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
source = args.path.read_text(encoding="utf-8")
|
||||||
|
except OSError as exc:
|
||||||
|
print(f"error: cannot read {args.path}: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
fixed = shift_headings(source, args.levels)
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
sys.stdout.write(fixed)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
destination = args.output or args.path
|
||||||
|
try:
|
||||||
|
destination.write_text(fixed, encoding="utf-8")
|
||||||
|
except OSError as exc:
|
||||||
|
print(f"error: cannot write {destination}: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "fix_markdown_titles.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run_cli(*args):
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), *args],
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FixMarkdownTitlesTest(unittest.TestCase):
|
||||||
|
def test_shifts_atx_headings_and_leaves_code_blocks(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
target = pathlib.Path(tmp) / "reply.md"
|
||||||
|
target.write_text(
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
"Intro",
|
||||||
|
"",
|
||||||
|
"# One",
|
||||||
|
"## Two ##",
|
||||||
|
"```md",
|
||||||
|
"# Not a heading",
|
||||||
|
"```",
|
||||||
|
"> # quoted content",
|
||||||
|
" # indented code",
|
||||||
|
"###### Six",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = run_cli(str(target), "--levels", "2")
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(
|
||||||
|
target.read_text(encoding="utf-8"),
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
"Intro",
|
||||||
|
"",
|
||||||
|
"### One",
|
||||||
|
"#### Two ##",
|
||||||
|
"```md",
|
||||||
|
"# Not a heading",
|
||||||
|
"```",
|
||||||
|
"> # quoted content",
|
||||||
|
" # indented code",
|
||||||
|
"###### Six",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_dry_run_prints_without_writing(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
target = pathlib.Path(tmp) / "reply.md"
|
||||||
|
target.write_text("# One\n", encoding="utf-8")
|
||||||
|
|
||||||
|
result = run_cli(str(target), "--levels", "1", "--dry-run")
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(result.stdout, "## One\n")
|
||||||
|
self.assertEqual(target.read_text(encoding="utf-8"), "# One\n")
|
||||||
|
|
||||||
|
def test_output_writes_to_separate_file(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
source = pathlib.Path(tmp) / "reply.md"
|
||||||
|
output = pathlib.Path(tmp) / "fixed.md"
|
||||||
|
source.write_text("# One\n", encoding="utf-8")
|
||||||
|
|
||||||
|
result = run_cli(str(source), "--levels", "3", "--output", str(output))
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(source.read_text(encoding="utf-8"), "# One\n")
|
||||||
|
self.assertEqual(output.read_text(encoding="utf-8"), "#### One\n")
|
||||||
|
|
||||||
|
def test_rejects_non_positive_levels(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
target = pathlib.Path(tmp) / "reply.md"
|
||||||
|
target.write_text("# One\n", encoding="utf-8")
|
||||||
|
|
||||||
|
result = run_cli(str(target), "--levels", "0")
|
||||||
|
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn("positive integer", result.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue