跳过主要内容
W

proj-doc-builder

walvka-code/proj-doc-builder

用随包的 Python 3.12+ 工具扫描仓库,为每个源码目录生成一份 Markdown 大纲,并随索引发布可证明文档与代码一致的哈希 manifest;自带读取器在输出概览或单份文档前会先校验 manifest。项目根需已有 project_docs/ 目录与 CLAUDE.md 入口。

安装

dsh plugin --profile web add github:walvka-code/proj-doc-builder

README

proj_doc_builder

Turn a codebase into documentation an AI can actually read. One command to build the outline, one command to load it.

Language: English (default) · 中文

README.zh-CN.md is a synced translation — update both together.

Ships as a DeepSeek Harness bundle (@walvka-code/proj-doc-builder) and as a plain CLI.

# The skill directory: SKILL.md + tools/ live here
SKILL="$(pwd)/skills/proj-doc-builder"

# Sync: build/refresh the documentation index (preview first, then publish)
python "$SKILL/tools/sync_project_docs.py" . --dry-run --json
python "$SKILL/tools/sync_project_docs.py" . --apply --json

# Load: give a fresh conversation an entry point (overview → one document)
python "$SKILL/tools/load_docs.py" . --json
python "$SKILL/tools/load_docs.py" . --mode target --target src/core --json

What it does:

  1. Keeps one Markdown outline per source directory (file inventory, key symbols, dependencies, role in the system).
  2. Remembers "what the tree looked like last time" via content hashes + a manifest, so the next sync only touches directories that actually changed.
  3. Publishes as a recoverable transaction; retiring a document is an explicit, evidence-gated decision — never an automatic cleanup.

The stance, in one sentence: "docs are in sync" must be backed by a manifest the tooling wrote, not by the model that produced the prose.


Contents


Install

The repository is a DeepSeek Harness bundle that packages the skill, its Python tools and its tests. Pick the install that fits your agent:

Option 1 — DeepSeek Harness bundle (recommended)

Add this market index under Plugins → More → Custom plugin markets → Discover, then install proj-doc-builder:

https://walvka-code.github.io/proj-doc-builder/index.json

(The same file is also served from https://raw.githubusercontent.com/walvka-code/proj-doc-builder/main/index.json, but that host is unreachable from some networks; GitHub Pages is the primary address.)

Without the market, pin one commit (the bundle is a package directory in Git):

git+https://github.com/walvka-code/proj-doc-builder.git#<40-hex-commit>

or install a local checkout with the plugin_manager tool:

{ "action": "install_bundle", "target": "C:/path/to/proj-doc-builder" }

The bundle mounts one bundled skill provider (rank 600): the skill is available in every session of the profile, and a project-local .dsh/skills/proj-doc-builder/ copy still wins when a repository wants to pin its own version.

Option 2 — as a project skill

Copy skills/proj-doc-builder/ to <project>/.dsh/skills/proj-doc-builder/. SKILL.md resolves its tools through the skill directory the host reports, so both layouts work unchanged.

Option 3 — as a plain CLI (no agent framework needed)

git clone <this-repo> proj-doc-builder
SKILL="$(pwd)/proj-doc-builder/skills/proj-doc-builder"
python "$SKILL/tools/sync_project_docs.py" /path/to/your/project --dry-run --json

Python 3.12+, zero third-party runtime dependencies (standard library only). Tests need pytest (see requirements-dev.txt).

Prerequisites: your project root needs a project_docs/ directory and a CLAUDE.md entry point.


Repository layout

proj-doc-builder/
├── package.json                        # name / dsh.bundle.patch / engines
├── cordis.patch.yml                    # mounts the bundled skill provider
├── index.js                            # reads SKILL.md, registers the skill
├── index.json                          # plugin market index (format 1)
├── icon.svg / locale/                  # bundle display metadata
├── skills/proj-doc-builder/            # SKILL.md + tools/ + references/ + tests/
├── scripts/check-bundle.mjs            # offline bundle checks (CI)
└── .github/workflows/ci.yml

The two problems it solves

#ProblemWhat this repo gives you
1You want to map an unfamiliar codebase quickly, and keep a baseline for "what changed since"Outline: project_docs/**/*.md, one document per source directory. Baseline: _scan_state.json (per-file SHA-256 + directory fingerprints) plus the manifest's generation_id (content-only hash). Together they make the next sync incremental instead of a full rewrite.
2You want a new AI conversation to load the project shape instead of re-reading source from scratchThe loader ships in this repo: tools/load_docs.py first verifies the manifest against the documents (per-file hashes + complete-set check), then serves either an overview (tree + metadata + section names) or a single document / single section. Writer and reader import the same contract, so the rules cannot drift apart.

Why not just "have a model write a pile of .md files"? Because the producer and the verifier must be separate actors — otherwise "the model says it's in sync" can never be falsified.

Failure modeSymptomWhat this tool does
DriftSource gained a file; the document's inventory is permanently one row shortContent-hash snapshot + worklist; only changed directories are rewritten
Unfalsifiable claims"It's synced" with nothing to checkAn independent manifest records size/SHA-256/title/status per document; mismatch is refused
Mass rewrite destroys historyEverything is rewritten just to pass validationOnly documents for changed directories block; unchanged history is audited, not rewritten

Guarantees

GuaranteeMechanism
No writes by defaultWithout --apply everything is report-only; staging lives in the system temp dir, never in the project tree
Single commit pointThe atomic replace of project_docs/_manifest.json is the only documentation-generation commit point
Never clobbers concurrent writesPublication uses no-replace rename (Windows os.rename, Linux renameat2(RENAME_NOREPLACE)); a mismatched preimage aborts
Failures are classifiedOnly NOT_INSTALLED / INSTALLED / UNKNOWN / PARTIALLY_INSTALLED — never a fabricated "committed"
Working copy stays cleanLocks, transaction journals and CAS artifacts live in the temp dir; your VCS never sees them
Deletion is a burden of proofA document is only quarantined when its source directory is proven gone, it has zero inbound links, carries the exact marker and is registered — and even then it is report-only by default
IdempotentA zero-change run reports planned_writes == [] and does not refresh last_synced

The six-phase workflow

PhaseActionKey output
0Preflight: project root, project_docs/, CLAUDE.md, no pending recovery—
1--dry-run yields change classes and a worklistscan.worklist.scaffold (profile + precomputed relative links)
2The producer writes prose per worklist entry and --strict validates each oneproject_docs/*.md
3Whole-tree dry-run (links, blockers, manifest coverage)validation.blocking_failures == []
4Retirement report: documents whose source is gone and unreferencedphase4.candidates (report-only by default)
5--apply publishesmanifest / CLAUDE tree / scan state
6Terminal verification + read-only VCS scope checkplanned_writes == []

The coordinator never generates natural language: it validates and publishes; the prose is written by the producer (a human or a model).

Exit codes: 0 all gates passed / 1 a gate failed (including "a transaction needs recovery") / 2 input or runtime error / 3 publish failed (not the same as "not published" — read publish.disposition).


Data model

FilePurpose
project_docs/_manifest.jsonSchema v2; per document path/depth/title/status/bytes/sha256/synced_at, plus generation_id (content-only, timestamps excluded) and depth_counts
project_docs/_scan_state.jsonSource-tree snapshot (per-file SHA-256 + directory fingerprints): this is the change-tracking baseline that produces the next worklist
Six marker groups in CLAUDE.mddoc-tree-instruction / project-overview / common-commands / architecture / test-structure / dependencies. The tool replaces only the first group; the rest are only checked for unique, paired, non-overlapping markers

Using the loader

# Overview: tree + per-document title/status/bytes/section names
# (defaults: 80 documents, 6 section names each)
python tools/load_docs.py . --json
python tools/load_docs.py . --limit 0 --sections 0 --json   # every document, metadata only

# Target: a source directory, a document path, or a project-relative path
python tools/load_docs.py . --mode target --target src/core --json
python tools/load_docs.py . --mode target --target project_docs/src/core.md --json

# One section only
python tools/load_docs.py . --mode target --target src/core --section Dependencies --json

Verify, then serve. If the manifest disagrees with the documents (edited files, or an unregistered document), the loader exits 1 with manifest-stale — it will not serve a stale index.

Exit codes: 0 verified and served / 1 stale index or unregistered target / 2 usage or path-safety error (including ../ traversal).


Platform support

PlatformStatusNotes
Windows✅ VerifiedReparse-point/junction detection, handle-bound deletion (reclaims tombstones), msvcrt locks
Linux✅ Verifiedfcntl locks, dir_fd + O_NOFOLLOW path walking, renameat2(RENAME_NOREPLACE), directory fsync
macOS⚠️ UntestedA renamex_np branch exists but CI does not cover it; APFS case-insensitivity and fsync semantics are unverified

Three things to know on Linux:

  1. renameat2(RENAME_NOREPLACE) is a hard requirement. When unsupported, the tool raises NoReplaceRenameUnsupported and refuses to fall back to a replacing rename — a silent fallback would quietly break crash consistency.
  2. Directory fsync degrades instead of aborting. Filesystems that reject it are recorded and surfaced (warnings.warn plus a durability-degraded warning in the report); real I/O errors still propagate.
  3. On POSIX, tombstones are archived, not deleted. Retired transaction directories are renamed to .phase4-archive-* and kept forever (there is no handle-bound unlink), so they accumulate and need manual cleanup. Windows reclaims them.

Tests

python -m pytest skills/proj-doc-builder/tests -q   # the Python tools (360 cases)
npm test                                            # the bundled provider + frontmatter reader
npm run check                                       # manifest, patch, packaged files, market index

360 tests (Windows: 354 passed / 6 skipped; on Linux only the Windows-handle-semantics tests flip to skipped). Everything runs in tmp_path — your real project_docs/ is never touched. They pin crash consistency, not features:

  • a failure before the manifest commit must roll back completely; after it, only installed/partial/unknown may be reported and rollback is forbidden
  • KeyboardInterrupt injected before and after every rename
  • forged journal slots, ABA races, documents modified concurrently during staging
  • unavailable renameat2 → typed failure; rejected directory fsync → visible degradation (CI runs a dedicated container/overlayfs job for both)

Limitations and trade-offs

  • Journal and CAS artifact directories must live on the same filesystem as the project root (cross-volume setups make --apply refuse).
  • Path whitelist: relative POSIX, lowercase .md only; absolute paths, drive letters, backslashes, .., ADS, reserved device names and case-only conflicts are rejected.
  • Symlinks, junctions and reparse points are never followed; when path identity cannot be proven, the tool fails closed.
  • --full is only the scanner's force_full switch — it is not "reconcile every document's file inventory"; that remains a producer step.
  • Document retirement is a burden-of-proof flow, not a cleanup utility: report-only by default, --apply requires explicit authorization.

License

MIT

相关插件