diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e23f83b..1347857 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,3 +59,4 @@ jobs: python scripts/test_check_activation.py python scripts/test_macos_quarantine_bootstrap.py python scripts/check_packaging.py + python scripts/package_release_assets.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d86a0a0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +name: Publish TreeWork Editions + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Build release assets from the tagged commit + run: python3 scripts/package_release_assets.py --tag "${GITHUB_REF_NAME}" + + - name: Publish both TreeWork editions + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "${GITHUB_REF_NAME}" \ + dist/releases/TreeWork-Coding-Agents-"${GITHUB_REF_NAME}".zip \ + dist/releases/TreeWork-Manual-"${GITHUB_REF_NAME}".zip \ + --title "TreeWork ${GITHUB_REF_NAME}" \ + --generate-notes \ + --verify-tag diff --git a/AGENTS.md b/AGENTS.md index 6fc3bc1..58eee86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # TreeWork Repository Guidance -This repository contains both an installable Codex plugin and the source used -to maintain it. +This repository contains two independently installable TreeWork editions and +the source used to maintain them. ## Read Before Editing @@ -16,8 +16,11 @@ to maintain it. ## Repository Boundaries -- `plugins/treework/` is the installable plugin. Keep it free of - project history, UI source, prototypes, and maintainer-only documents. +- `plugins/treework/` is the installable Coding Agents edition. Keep + it free of project history, UI source, prototypes, and maintainer-only + documents. +- `skills/treework-manual/` is the independently installable Manual edition. + Keep its complete working contract in one self-contained `SKILL.md`. - Agent references explain how to use TreeWork. Do not put Rust modules, API internals, migration plans, or frontend architecture there. - `project-map-ui/` is source; `plugins/treework/assets/graph-panel/` diff --git a/Makefile b/Makefile index 8b73fdc..5d0de66 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,13 @@ validate: build-ui python3 $(PLUGIN_CREATOR)/scripts/validate_plugin.py $(PLUGIN) python3 $(SKILL_CREATOR)/scripts/quick_validate.py \ $(PLUGIN)/skills/treework + python3 $(SKILL_CREATOR)/scripts/quick_validate.py \ + skills/treework-manual package: python3 scripts/package_plugin.py python3 scripts/check_package_commit_source.py + python3 scripts/package_release_assets.py browser-test: mkdir -p $(ARTIFACTS) diff --git a/README.md b/README.md index ced09aa..bf35e75 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,66 @@ # TreeWork -TreeWork is a **tree-guided development plugin for Codex**. It helps coding -agents organize complex projects as branches, prepare the important design -before coding, and move through long-running work without losing direction. +TreeWork is a **tree-guided working model for Agents**. It externalizes evolving +work as a persistent Tree, gives every line of effort a branch, and teaches an +Agent to move from the root into one branch and back instead of chasing requests +as a flat queue. Plans, progress, and findings keep each location recoverable +across interruptions, context resets, and handoffs. + +This repository provides two independently installable editions. They share the +same root-to-branch mental model, but each defines its own state and operating +contract. + +## TreeWork Editions + +- **TreeWork for Coding Agents** is a runtime-backed development system. It adds + Alignment, Specs, declarative Tree transactions, isolated Git worktrees, + protected completion, Recall, and Project Map through the Codex plugin. +- **[TreeWork Manual](skills/treework-manual/SKILL.md)** is a standalone, + single-file Agent Skill for writing, research, notes, planning, creative work, + operations, and other evolving work. The Agent maintains the Tree and its + project state directly in Markdown. + +Choose an edition from the user's needs and the actual work rather than treating +one as a fallback for the other. Do not combine both state models inside one +project without an explicit migration. + +## Installation + +### TreeWork Manual + +Download `TreeWork-Manual-vX.Y.Z.zip` from the matching +[GitHub Release](https://github.com/Johnny-xuan/TreeWork/releases), then extract +its `treework-manual/` directory into the Skills directory used by your Agent +host. For Codex, install it under `$CODEX_HOME/skills/` (normally +`~/.codex/skills/`) and start a new task. The package is self-contained and +contains one `SKILL.md`; the ZIP root also carries the MIT license. + +The same independently installable source is available at +[`skills/treework-manual`](skills/treework-manual). -Code inspection shows what exists and retrieval memory recalls fragments, but -neither reliably tells an Agent what the project has accepted, where work -stands, or why it stopped. TreeWork therefore keeps the accepted project -structure, branch state, Specs, progress, findings, and verification outside -the model context so work can resume without reconstructing the project from -scratch. +### TreeWork For Coding Agents -## First Install +TreeWork for Coding Agents currently targets Codex on macOS and Linux. Native +Windows support has not been release-tested. -TreeWork currently targets Codex on macOS and Linux. Native Windows support has -not been release-tested. +The matching GitHub Release also provides +`TreeWork-Coding-Agents-vX.Y.Z.zip`. After extracting it, the +`treework-coding-agents/` directory is a self-contained local Codex marketplace: + +```bash +codex plugin marketplace add /path/to/treework-coding-agents +codex plugin add treework@treework +``` + +The Git marketplace installation below is recommended when normal GitHub access +is available because Codex can update it directly from the repository. Runtime prerequisites: Git, Bash, Python 3, Rust, and Cargo. The Project Map frontend is bundled; Node.js is needed only for frontend development. +#### Codex guided install + Give the following prompt to a Codex Agent with terminal access: ```text @@ -67,7 +108,7 @@ inside the current project until I explicitly approve it. new one. Leave project initialization to the new Codex task after I choose. ``` -### Manual Install +#### Codex manual install ```bash codex plugin marketplace add https://github.com/Johnny-xuan/TreeWork @@ -204,7 +245,7 @@ Read the formal model and evaluation design in the ## Package Contents -The installable plugin lives at +The installable Codex plugin lives at [`plugins/treework`](plugins/treework) and includes: - the staged project-state Skill and Agent-facing references; @@ -213,12 +254,16 @@ The installable plugin lives at - a local read-only MCP server for Recall and Project Map launch; - bundled Project Map assets. -TreeWork stores project state under `.TreeWork/`. +TreeWork for Coding Agents stores project state under `.TreeWork/`. + +The independently installable [`skills/treework-manual`](skills/treework-manual) +edition contains one self-contained Agent Skill. ## Repository Layout ```text -plugins/treework/ Installable Codex plugin +plugins/treework/ TreeWork for Coding Agents Codex plugin +skills/treework-manual/ Standalone manual TreeWork Skill project-map-ui/ React/D3/SVG Project Map source docs/product/ Product behavior and UX contracts docs/architecture/ Runtime and transaction contracts @@ -231,9 +276,10 @@ while using TreeWork. Maintainer implementation contracts stay under `docs/`. ## Community and Help Wanted -TreeWork currently ships and is release-tested as a Codex plugin. Support for -Claude Code, Cursor, Gemini CLI, OpenCode, and other agent hosts is welcome -through focused host adapters. +TreeWork for Coding Agents currently ships and is release-tested for Codex. +Support for Claude Code, Cursor, Gemini CLI, OpenCode, and other agent hosts is +welcome through focused host adapters. TreeWork Manual can be loaded by hosts +that support standalone Agent Skills. High-impact contribution areas include: @@ -268,10 +314,11 @@ make validate ## Status -`v0.1.7` is the current version. Alignment, declarative Tree construction, -hierarchy-aligned branch documents, protected branch traversal, Recall, Project -Map, and Replay form a usable end-to-end loop. Project Map interaction design -will continue to evolve. +`v0.1.8` is the current Coding Agents edition version. Alignment, declarative Tree +construction, hierarchy-aligned branch documents, protected branch traversal, +Recall, Project Map, and Replay form a usable end-to-end loop. TreeWork Manual +is released independently from the same repository and tag. Project Map +interaction design will continue to evolve. ## Privacy diff --git a/README.zh-CN.md b/README.zh-CN.md index 0317bd0..af6607f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -10,23 +10,59 @@ # TreeWork -TreeWork 是一个面向 Codex 的**树引导开发插件**。它帮助 Coding Agent 把复杂 -项目组织成一棵 branch 树,在编码前完成重要设计,并在长期开发中沿着树移动而 -不失去方向。 +TreeWork 是一种面向 Agent 的**树引导工作模型**。它把持续演化的工作外化成一棵 +持久的 Tree,让每条工作线归属一个 branch,并让 Agent 从根进入 branch、完成 +局部工作后返回根,而不是把不断到来的请求当成一条扁平队列。Plan、Progress 和 +Findings 让每个位置在中断、上下文重置或 Agent 交接后仍然可以恢复。 -代码检查能够看到已经实现了什么,检索型记忆能够找回历史片段,但两者都不能 -稳定回答项目已经接受了什么、工作进行到哪里,以及上一个 Agent 为什么停下。 -TreeWork 将已接受的项目结构、branch 状态、Spec、进度、结论和验证记录保存在 -模型上下文之外,让长期任务不必每次从头重建项目就能继续。 +本仓库提供两个可以独立安装的版本。它们共享从根到 branch 的工作心智模型, +但各自拥有独立的状态模型和运行契约。 -## 首次安装 +## TreeWork 版本 -TreeWork 当前面向 macOS 和 Linux 上的 Codex。原生 Windows 支持尚未经过发布 -测试。 +- **TreeWork for Coding Agents** 是带运行时的开发系统。它通过 Codex 插件提供 + Alignment、Spec、声明式 Tree transaction、隔离 Git worktree、 + completion 保护、Recall 和 Project Map。 +- **[TreeWork Manual](skills/treework-manual/SKILL.md)** 是一份独立、单文件的 + Agent Skill,适用于写作、研究、笔记、规划、创作、运营及其他持续演化的工作。 + Agent 直接在 Markdown 中维护 Tree 与项目状态。 + +根据用户需要和实际工作选择版本,不要把其中一个视为另一个的 fallback。同一个 +项目不能在没有显式迁移的情况下混用两套状态模型。 + +## 安装 + +### TreeWork Manual + +从对应的 [GitHub Release](https://github.com/Johnny-xuan/TreeWork/releases) +下载 `TreeWork-Manual-vX.Y.Z.zip`,将其中的 `treework-manual/` 目录解压到 Agent +host 的 Skills 目录。对于 Codex,安装到 `$CODEX_HOME/skills/`(通常为 +`~/.codex/skills/`),然后新建一个任务。这个包是独立完整的,只包含一份 +`SKILL.md`;ZIP 根目录同时包含 MIT License。 + +同一份可独立安装的源码位于 [`skills/treework-manual`](skills/treework-manual)。 + +### TreeWork for Coding Agents + +TreeWork for Coding Agents 当前面向 macOS 和 Linux 上的 Codex。原生 Windows +支持尚未经过发布测试。 + +对应的 GitHub Release 同时提供 `TreeWork-Coding-Agents-vX.Y.Z.zip`。解压后, +`treework-coding-agents/` 目录就是一个自包含的本地 Codex marketplace: + +```bash +codex plugin marketplace add /path/to/treework-coding-agents +codex plugin add treework@treework +``` + +如果能够正常访问 GitHub,更推荐使用下面的 Git marketplace 安装方式,因为 Codex +可以直接从仓库更新。 运行依赖包括 Git、Bash、Python 3、Rust 和 Cargo。Project Map 前端已经打包; 只有开发前端时才需要 Node.js。 +#### Codex 引导安装 + 把下面这段 prompt 直接交给一个能够使用终端的 Codex Agent: ```text @@ -59,7 +95,7 @@ TreeWork。 后,再进行项目初始化。 ``` -### 手动安装 +#### Codex 手动安装 ```bash codex plugin marketplace add https://github.com/Johnny-xuan/TreeWork @@ -188,8 +224,7 @@ Agent 在其中移动的有效方式,同时把局部实现决策留给 Agent ## 插件内容 -可安装插件位于 [`plugins/treework`](plugins/treework), -其中包括: +可安装的 Codex 插件位于 [`plugins/treework`](plugins/treework),其中包括: - 分阶段项目状态 Skill 及面向 Agent 的参考文档; - Rust 编写的 `tw` 事务运行时; @@ -197,12 +232,16 @@ Agent 在其中移动的有效方式,同时把局部实现决策留给 Agent - 用于 Recall 和启动 Project Map 的本地只读 MCP 服务; - 已打包的 Project Map 资源。 -TreeWork 将项目状态保存在 `.TreeWork/` 下。 +TreeWork for Coding Agents 将项目状态保存在 `.TreeWork/` 下。 + +可独立安装的 [`skills/treework-manual`](skills/treework-manual) 版本只包含一份 +自包含的 Agent Skill。 ## 仓库结构 ```text -plugins/treework/ 可安装的 Codex 插件 +plugins/treework/ TreeWork for Coding Agents Codex 插件 +skills/treework-manual/ 独立的手动 TreeWork Skill project-map-ui/ React/D3/SVG Project Map 源码 docs/product/ 产品行为和交互契约 docs/architecture/ 运行时和 transaction 契约 @@ -215,9 +254,9 @@ paper/ 研究论文源码与图片 ## 社区参与 -TreeWork 目前以 Codex 插件形式提供,并以 Codex 作为发布测试目标。欢迎贡献者 -通过聚焦的 host adapter,为 Claude Code、Cursor、Gemini CLI、OpenCode 等 -Agent host 增加支持。 +TreeWork for Coding Agents 目前以 Codex 作为发布测试目标。欢迎贡献者通过聚焦的 +host adapter,为 Claude Code、Cursor、Gemini CLI、OpenCode 等 Agent host +增加支持。支持独立 Agent Skills 的 host 可以直接加载 TreeWork Manual。 当前特别需要贡献者参与的方向包括: @@ -246,9 +285,10 @@ make validate ## 当前状态 -`v0.1.7` 是当前版本。Alignment、声明式 Tree 构建、与 Tree 层级一致的 branch -文档、受保护的 branch 移动、Recall、Project Map 和 Replay 已经形成可用的 -端到端闭环。Project Map 的交互设计仍会持续演化。 +`v0.1.8` 是当前 Coding Agents 版本。Alignment、声明式 Tree 构建、与 Tree 层级一致的 +branch 文档、受保护的 branch 移动、Recall、Project Map 和 Replay 已经形成 +可用的端到端闭环。TreeWork Manual 从同一仓库和 tag 独立发布。Project Map 的 +交互设计仍会持续演化。 ## 隐私 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index da89240..72e4ded 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,10 +1,15 @@ # Release Notes -## Unreleased - -- Withdraws the experimental Pi host adapter, its root package manifest, - Pi-specific host commands, and adapter validation. The Codex plugin and the - persisted TreeWork project-state format are unchanged. +## v0.1.8 - Two Independent Editions + +- Adds `treework-manual`, an independently installable single-file TreeWork + edition for writing, research, notes, planning, creative work, operations, + and other evolving projects. Agents maintain its Tree and project state + directly in Markdown. +- Publishes TreeWork for Coding Agents and TreeWork Manual as two independently + installable assets from the same release tag. +- Withdraws the experimental Pi host adapter. The Codex plugin and persisted + TreeWork project-state format are unchanged. ## v0.1.7 - Hierarchical Branch Artifacts diff --git a/docs/releasing.md b/docs/releasing.md index 1cbe058..65617e1 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -18,12 +18,19 @@ Packaging reads tracked bytes from `HEAD`, not the mutable worktree: ```bash -python3 scripts/package_plugin.py -python3 scripts/check_package_commit_source.py +make package ``` -The clean package appears at `dist/treework/`. It contains only the -tracked installable plugin subtree. +Packaging produces three local forms: + +- `dist/treework/` is the unpacked Coding Agents plugin candidate; +- `dist/releases/TreeWork-Coding-Agents-vX.Y.Z.zip` contains a self-contained + local Codex marketplace rooted at `treework-coding-agents/`; +- `dist/releases/TreeWork-Manual-vX.Y.Z.zip` is the independently installable + single-file Manual release asset rooted at `treework-manual/`. + +Both ZIP assets come from the same committed `HEAD` and share the release tag, +but users install them independently. ## Install the Candidate @@ -51,6 +58,7 @@ git push origin main git push origin vX.Y.Z ``` -Create a GitHub release from the matching section of `RELEASE-NOTES.md`. Do not -move an existing release tag. Do not publish from a dirty worktree or from +The tag workflow validates that `vX.Y.Z` matches the committed plugin version, +creates one GitHub Release, and uploads both edition ZIPs together. Do not move +an existing release tag. Do not publish from a dirty worktree or from uncommitted `dist/` contents. diff --git a/plugins/treework/.codex-plugin/plugin.json b/plugins/treework/.codex-plugin/plugin.json index 2fcce74..c1e8c8b 100644 --- a/plugins/treework/.codex-plugin/plugin.json +++ b/plugins/treework/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "treework", - "version": "0.1.7", + "version": "0.1.8", "description": "State-native project memory for long-running coding agents.", "author": { "name": "Zhongxuan Song", diff --git a/plugins/treework/Cargo.lock b/plugins/treework/Cargo.lock index f17f4ec..b94c8cf 100644 --- a/plugins/treework/Cargo.lock +++ b/plugins/treework/Cargo.lock @@ -975,7 +975,7 @@ dependencies = [ [[package]] name = "treework-cli" -version = "0.1.7" +version = "0.1.8" dependencies = [ "axum", "clap", diff --git a/plugins/treework/crates/treework-cli/Cargo.toml b/plugins/treework/crates/treework-cli/Cargo.toml index 8b1904a..3852b6c 100644 --- a/plugins/treework/crates/treework-cli/Cargo.toml +++ b/plugins/treework/crates/treework-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "treework-cli" -version = "0.1.7" +version = "0.1.8" edition = "2021" authors = ["Zhongxuan Song "] description = "TreeWork state, transaction, and Project Map runtime" diff --git a/project-map-ui/package-lock.json b/project-map-ui/package-lock.json index cfdacd7..a4b15c9 100644 --- a/project-map-ui/package-lock.json +++ b/project-map-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "@treework/project-map-ui", - "version": "0.1.7", + "version": "0.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@treework/project-map-ui", - "version": "0.1.7", + "version": "0.1.8", "license": "MIT", "dependencies": { "@fontsource/fraunces": "5.3.0", diff --git a/project-map-ui/package.json b/project-map-ui/package.json index cf29cb8..b3bf091 100644 --- a/project-map-ui/package.json +++ b/project-map-ui/package.json @@ -1,6 +1,6 @@ { "name": "@treework/project-map-ui", - "version": "0.1.7", + "version": "0.1.8", "private": true, "type": "module", "license": "MIT", diff --git a/scripts/package_release_assets.py b/scripts/package_release_assets.py new file mode 100644 index 0000000..922fe88 --- /dev/null +++ b/scripts/package_release_assets.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +"""Build both independently installable TreeWork release assets from HEAD.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import shutil +import stat +import subprocess +import tarfile +import tempfile +import zipfile +from pathlib import Path + +from _paths import REPOSITORY_ROOT + + +RELEASE_ROOT = REPOSITORY_ROOT / "dist" / "releases" +EDITIONS = { + "Coding-Agents": { + "sources": [ + (Path("plugins/treework"), Path("plugins/treework")), + ], + "files": [ + ( + Path(".agents/plugins/marketplace.json"), + Path(".agents/plugins/marketplace.json"), + ), + ], + "directory": "treework-coding-agents", + "required": { + ".agents/plugins/marketplace.json", + "plugins/treework/.codex-plugin/plugin.json", + "plugins/treework/.mcp.json", + "plugins/treework/skills/treework/SKILL.md", + }, + }, + "Manual": { + "sources": [(Path("skills/treework-manual"), Path("."))], + "files": [], + "directory": "treework-manual", + "required": {"SKILL.md"}, + }, +} + + +def git_bytes(*args: str) -> bytes: + result = subprocess.run( + ["git", *args], + cwd=REPOSITORY_ROOT, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if result.returncode != 0: + raise SystemExit( + f"git {' '.join(args)} failed with {result.returncode}: " + f"{result.stderr.decode(errors='replace').strip()}" + ) + return result.stdout + + +def committed_file(commit: str, path: Path) -> bytes: + return git_bytes("show", f"{commit}:{path.as_posix()}") + + +def toml_package_version(raw: bytes, name: str) -> str | None: + package = raw.decode().split("[package]", 1) + if len(package) != 2: + return None + match = re.search(r'^version\s*=\s*"([^"]+)"', package[1], re.MULTILINE) + return match.group(1) if match else None + + +def lock_package_version(raw: bytes, package_name: str) -> str | None: + pattern = re.compile( + rf'\[\[package\]\]\s+name\s*=\s*"{re.escape(package_name)}"\s+' + r'version\s*=\s*"([^"]+)"', + re.MULTILINE, + ) + matches = pattern.findall(raw.decode()) + return matches[0] if len(matches) == 1 else None + + +def source_version(commit: str) -> str: + raw = committed_file(commit, Path("plugins/treework/.codex-plugin/plugin.json")) + try: + manifest = json.loads(raw) + version = manifest["version"] + except (json.JSONDecodeError, KeyError, TypeError) as error: + raise SystemExit("committed plugin manifest has no valid version") from error + if not isinstance(version, str) or not version: + raise SystemExit("committed plugin version must be a non-empty string") + + versions = { + "plugin manifest": version, + "treework-cli": toml_package_version( + committed_file( + commit, Path("plugins/treework/crates/treework-cli/Cargo.toml") + ), + "treework-cli", + ), + "Cargo.lock treework-cli": lock_package_version( + committed_file(commit, Path("plugins/treework/Cargo.lock")), + "treework-cli", + ), + "Project Map": json.loads( + committed_file(commit, Path("project-map-ui/package.json")) + ).get("version"), + } + mismatched = { + name: value for name, value in versions.items() if value != version + } + if mismatched: + raise SystemExit( + f"committed release versions do not match {version}: {mismatched}" + ) + return version + + +def extract_commit_path(commit: str, source: Path, destination: Path) -> None: + with tempfile.TemporaryDirectory(prefix="treework-release-archive-") as temporary: + archive_path = Path(temporary) / "source.tar" + result = subprocess.run( + [ + "git", + "archive", + "--format=tar", + f"--output={archive_path}", + commit, + "--", + source.as_posix(), + ], + cwd=REPOSITORY_ROOT, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + if result.returncode != 0: + raise SystemExit( + f"cannot archive {source} from {commit}: " + f"{result.stderr.decode(errors='replace').strip()}" + ) + + with tarfile.open(archive_path, mode="r") as archive: + for member in archive.getmembers(): + archived = Path(member.name) + if ( + archived.is_absolute() + or not archived.parts + or any(part in {"", ".", ".."} for part in archived.parts) + or member.issym() + or member.islnk() + ): + raise SystemExit(f"unsafe release path: {member.name}") + if member.isdir() and ( + archived == source or source.is_relative_to(archived) + ): + continue + try: + relative = archived.relative_to(source) + except ValueError as error: + raise SystemExit( + f"release archive escaped {source}: {member.name}" + ) from error + if not relative.parts: + continue + target = destination / relative + if member.isdir(): + target.mkdir(parents=True, exist_ok=True) + target.chmod(member.mode & 0o777) + continue + if not member.isfile(): + raise SystemExit(f"unsupported release entry: {member.name}") + stream = archive.extractfile(member) + if stream is None: + raise SystemExit(f"cannot read release entry: {member.name}") + target.parent.mkdir(parents=True, exist_ok=True) + with target.open("wb") as output: + shutil.copyfileobj(stream, output) + target.chmod(member.mode & 0o777) + + +def validate_edition(name: str, root: Path, required: set[str]) -> None: + actual = { + path.relative_to(root).as_posix() + for path in root.rglob("*") + if path.is_file() + } + missing = required - actual + if missing: + raise SystemExit(f"{name} release is missing {sorted(missing)}") + if any(path.is_symlink() for path in root.rglob("*")): + raise SystemExit(f"{name} release contains a symlink") + if name == "Manual" and actual != {"SKILL.md"}: + raise SystemExit( + "Manual release must remain a self-contained single-file Skill; " + f"found {sorted(actual)}" + ) + + +def build_candidate( + commit: str, + candidate: Path, + sources: list[tuple[Path, Path]], + files: list[tuple[Path, Path]], +) -> None: + candidate.mkdir() + for source, relative_destination in sources: + destination = candidate / relative_destination + destination.mkdir(parents=True, exist_ok=True) + extract_commit_path(commit, source, destination) + for source, relative_destination in files: + destination = candidate / relative_destination + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(committed_file(commit, source)) + + +def zip_info(name: str, mode: int) -> zipfile.ZipInfo: + info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + info.create_system = 3 + info.external_attr = (stat.S_IFREG | mode) << 16 + info.compress_type = zipfile.ZIP_DEFLATED + return info + + +def write_reproducible_zip( + source: Path, archive_path: Path, extras: dict[str, bytes] | None = None +) -> None: + with zipfile.ZipFile( + archive_path, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9 + ) as archive: + for name, content in sorted((extras or {}).items()): + archive.writestr(zip_info(name, 0o644), content, compresslevel=9) + for path in sorted(source.rglob("*"), key=lambda item: item.as_posix()): + if not path.is_file(): + continue + relative = path.relative_to(source.parent).as_posix() + mode = stat.S_IMODE(path.stat().st_mode) + archive.writestr( + zip_info(relative, mode), path.read_bytes(), compresslevel=9 + ) + + +def verify_zip( + archive_path: Path, + directory: str, + source: Path, + extras: dict[str, bytes] | None = None, +) -> None: + expected = { + f"{directory}/{path.relative_to(source).as_posix()}": path.read_bytes() + for path in source.rglob("*") + if path.is_file() + } + expected.update(extras or {}) + with zipfile.ZipFile(archive_path) as archive: + names = {name for name in archive.namelist() if not name.endswith("/")} + if names != set(expected): + raise SystemExit( + f"{archive_path.name} file set differs: " + f"missing={sorted(set(expected) - names)} " + f"extra={sorted(names - set(expected))}" + ) + for name, content in expected.items(): + if archive.read(name) != content: + raise SystemExit(f"{archive_path.name} differs at {name}") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--tag", + help="Require the release tag to equal v.", + ) + args = parser.parse_args() + + repository = Path(git_bytes("rev-parse", "--show-toplevel").decode().strip()) + if repository.resolve() != REPOSITORY_ROOT.resolve(): + raise SystemExit("run package_release_assets.py from the TreeWork repository") + commit = git_bytes("rev-parse", "HEAD").decode().strip() + version = source_version(commit) + license_bytes = committed_file(commit, Path("LICENSE")) + expected_tag = f"v{version}" + if args.tag and args.tag != expected_tag: + raise SystemExit( + f"release tag {args.tag!r} does not match committed version {expected_tag!r}" + ) + + if RELEASE_ROOT.exists(): + shutil.rmtree(RELEASE_ROOT) + RELEASE_ROOT.mkdir(parents=True) + + with tempfile.TemporaryDirectory( + prefix=".treework-release-", dir=RELEASE_ROOT.parent + ) as temporary: + temporary_root = Path(temporary) + built: list[Path] = [] + for name, config in EDITIONS.items(): + directory = str(config["directory"]) + candidate = temporary_root / directory + build_candidate( + commit, + candidate, + config["sources"], + config["files"], + ) + validate_edition(name, candidate, config["required"]) + archive_path = RELEASE_ROOT / f"TreeWork-{name}-v{version}.zip" + extras = {"LICENSE": license_bytes} + write_reproducible_zip(candidate, archive_path, extras) + verify_zip(archive_path, directory, candidate, extras) + built.append(archive_path) + + for archive_path in built: + digest = hashlib.sha256(archive_path.read_bytes()).hexdigest() + print(f"{archive_path.name} sha256:{digest}") + print(f"Packaged both TreeWork editions from commit {commit}") + + +if __name__ == "__main__": + main() diff --git a/skills/treework-manual/SKILL.md b/skills/treework-manual/SKILL.md new file mode 100644 index 0000000..91a962c --- /dev/null +++ b/skills/treework-manual/SKILL.md @@ -0,0 +1,292 @@ +--- +name: treework-manual +description: Use TreeWork Manual when the user asks for it, or when the Agent judges that organizing ongoing, multi-part work as a persistent Markdown Tree would improve direction, continuity, recovery, or handoff. Adapt the Tree's depth and document detail to the user's needs, domain, and situation rather than a fixed threshold. +--- + +# TreeWork Manual + +TreeWork gives an Agent a durable sense of place in ongoing work. It externalizes +the work as a Tree: the root holds the whole project, each branch owns one +coherent line of effort, and documents at the root and branches preserve intent, +reality, and durable knowledge. Direction no longer depends on the current +conversation remaining intact. + +The Agent's unit of movement is not the next message; it is the current branch. +A new request is first located in the Tree. The Agent then works from the root +into the branch that owns the work, leaves that branch recoverable, returns to +the root, and chooses the next branch. Requests may arrive in any order, but the +work still moves through the Tree. + +```text +Start at the root +-> locate the right branch +-> work within that branch +-> leave a recoverable state +-> return to the root +-> choose the next branch +``` + +The Tree provides direction; the documents provide continuity: + +```text +PROJECT shows the global map. +Plan points toward intended work. +Progress records present reality. +Findings preserve what the work has taught the Agent. +``` + +TreeWork is a working mental model rather than a prescribed sequence. A branch +may be a chapter, theme, research question, note collection, deliverable, phase, +module, client, or another coherent scope that fits the project. The Tree may +begin shallow and grow as understanding improves. The Agent maintains this +topology and its state directly in Markdown so that people and other Agents can +inspect, correct, and continue the same work. + +## Core Mental Model + +1. **Locate before acting.** Route each meaningful request to the root, the + current branch, another existing branch, or a genuinely new branch before + doing the work. +2. **Keep global and local state separate.** Keep the root concise and + navigational. Keep details inside the branch that owns them. +3. **Move instead of jumping.** When attention must move elsewhere, first leave + the current branch in a state that can be resumed without conversation + history. +4. **Let reality correct the documents.** Plans express intent; Progress records + what is actually true. When artifacts, user direction, and older documents + disagree, investigate and synchronize them instead of preserving a stale + story. +5. **Grow the Tree only as needed.** Sketch enough structure to orient the work, + then refine it as understanding improves. Do not invent a large branch + hierarchy merely to appear organized. + +User requests may arrive in a jumpy order. Treat them as routing signals, not +permission to teleport between unrelated work. If the request belongs to +another branch, checkpoint the current branch, return to the root view, and +then enter the target branch. + +## State Documents + +Use this default layout: + +```text +.TreeWork/ + PROJECT.md + task_plan.md + progress.md + findings.md + branches/ + / + task_plan.md + progress.md + findings.md + / + task_plan.md + progress.md + findings.md + / + task_plan.md + progress.md + findings.md +``` + +These four document roles are the minimum shared language of TreeWork: + +- **`PROJECT.md` answers "What is this work and how is it organized?"** Keep the + purpose, durable constraints, Tree map, and branch meanings here. Do not turn + it into a status dashboard or detailed work log. +- **`task_plan.md` answers "What do we intend to do?"** Keep desired outcomes, + ordered steps, dependencies, boundaries, and checklists here. A Plan is not + evidence that the work happened. +- **`progress.md` answers "What is actually true now?"** Keep current reality, + completed and open work, blockers, and the exact restart point here. Record + meaningful state changes, not a diary of every action or command. +- **`findings.md` answers "What did we learn that should survive?"** Keep + conclusions, decisions and reasons, useful evidence, source notes, changed + assumptions, and risks here. Do not use Findings as another to-do list. + +The root has all four documents. A branch normally has Plan, Progress, and +Findings; its identity and place in the whole live in root `PROJECT.md`. A +parent branch may also contain child branches when a local area needs its own +map. + +Use existing artifacts in their natural locations. A writing branch should +point to its draft, a research branch to evidence, and a note branch to its +source material. Do not copy whole artifacts into `.TreeWork/` merely to make +the state directory look complete. + +## Minimal Document Shape + +Use these headings as a starting point, not as a rigid schema. Add or remove +sections when the domain genuinely needs a different shape. + +Root `PROJECT.md`: + +```md +# Project + +## Purpose + +## Durable Direction + +## Tree +- `` - what this branch owns. + - `` - what this child owns. + +## Global Constraints +``` + +Root or branch `task_plan.md`: + +```md +# Plan + +## Outcome + +## Steps +- [ ] A meaningful work item. + +## Dependencies And Boundaries +``` + +Root or branch `progress.md`. Keep the `Current branch` line at the root and +omit it inside a branch: + +```md +# Progress + +Current branch: `` + +## Current Reality + +## Completed + +## Open Or Blocked + +## Resume From +``` + +Root or branch `findings.md`: + +```md +# Findings + +## Decisions And Reasons + +## Learned + +## Risks And Unknowns + +## References +``` + +Prefer numbered lists for order and reasoning, checklists for work that can be +completed, and bullets for facts. Mark an item complete only when the relevant +outcome exists, not merely because the Agent touched it. + +## Starting Or Adopting Work + +1. Inspect the user's request, existing artifacts, and existing organization + before creating TreeWork files. +2. Decide from the user's request and the actual situation whether an explicit + Tree will make the work easier to locate, continue, inspect, or hand off. + An explicit user request is sufficient; otherwise use judgment rather than a + fixed size, duration, or branch-count threshold. +3. When TreeWork is appropriate, create `.TreeWork/` and write root `PROJECT.md`, + Plan, Progress, and Findings from what is currently + known. Do not fabricate certainty to fill headings. +4. Create only the first useful branches. It is valid to work at the root when + the work has one coherent scope. +5. Choose branch boundaries that future Agents can recognize without guessing. + Prefer an existing branch whenever it can own the work coherently. + +Create a branch when the scope has its own outcome, materials, continuation +state, or likely future follow-up. Split a branch when it has become too broad +to understand or resume locally. Do not create a branch for every small request, +temporary thought, or checklist item. + +## Working Along The Tree + +### Enter a branch + +1. Read root `PROJECT.md` and root `progress.md` to recover global direction. +2. Read the target branch's Plan, Progress, and Findings. +3. Inspect only the linked artifacts and ancestor context needed for this + branch. +4. Restate internally what this branch owns, what is already true, and what the + next meaningful action is. + +Do not load every branch into context. The Tree exists so the Agent can recover +the relevant path without rereading the whole project. + +### Work inside a branch + +- Keep effort inside the branch's declared scope. +- Update the Plan when intended work changes. +- Update Progress when reality materially changes or the restart point moves. +- Update Findings when a conclusion, decision, source, risk, or corrected + assumption should survive the current context. +- Update root documents immediately when a local discovery changes global + direction, another branch, or the Tree itself. + +The Agent may decide local execution details while working. It must not silently +change the user's goal, durable constraints, branch ownership, or cross-branch +direction. Surface those changes and synchronize the relevant documents. + +### Leave or switch branches + +Before moving away: + +1. Reconcile the branch Plan with what remains intended. +2. Record current reality and an exact restart point in branch Progress. +3. Preserve durable conclusions in branch Findings. +4. Update root `PROJECT.md` if the Tree or a branch meaning changed. +5. Update root Progress with the new current location; update root Plan or + Findings only with information that matters globally. +6. Return mentally to the root, then route to the next branch. + +Do not perform empty document maintenance after every minor action. Synchronize +when meaning, reality, direction, ownership, or the continuation point changes, +and always before leaving a branch or ending a long session. + +### Finish or retire a branch + +Treat a branch as finished when its intended outcome is actually present and +its remaining work is either resolved or deliberately moved elsewhere. Record +the result and where it lives, then update the root Tree. If work is abandoned +or superseded, preserve the reason and any reusable Findings instead of merely +deleting the branch. + +TreeWork Manual does not require a fixed lifecycle vocabulary. Use plain states +appropriate to the domain when a status helps the root map, such as `active`, +`paused`, `done`, or `dropped`; do not create lifecycle ceremony that does not +improve recovery. + +## Recovery And Handoff + +To continue after interruption, compaction, or Agent handoff: + +1. Read root `PROJECT.md`. +2. Read root `progress.md` and identify the current branch. +3. Follow the Tree path to that branch. +4. Read its Plan, Progress, and Findings. +5. Inspect the linked working artifacts to confirm the recorded reality. +6. Continue from `Resume From`, correcting the documents first if they have + drifted. + +This is state recovery, not historical retrieval. The goal is not to remember +every past action. The goal is to recover the accepted structure, current +reality, durable knowledge, and next viable movement. + +## Boundaries + +- Do not duplicate information across root and branch documents. Promote only + information whose effect crosses branch boundaries. +- Do not maintain documents as ceremony. Their purpose is to preserve the + work's current state, the Agent's project understanding, and the context + needed for later work to recover direction and continue correctly. +- Do not invent extra process, files, or bookkeeping merely to make the Tree + look complete. Add structure only when it improves orientation or recovery. +- Treat an existing `.TreeWork/` as user data. If its structure differs from + the one described here, inspect and preserve it, then ask before adopting, + reorganizing, or replacing it.