@shiwenbin1617/pstack 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,373 @@
1
+ <div align="center">
2
+
3
+ # pstack
4
+
5
+ **Senior engineering habits, installed into your AI coding agent**
6
+
7
+ A set of Markdown rules that make an agent understand the problem before it writes, and produce runtime evidence after. Not "done!" with nothing behind it.
8
+
9
+ [![npm](https://img.shields.io/npm/v/@shiwenbin1617/pstack?color=cb3837&logo=npm)](https://www.npmjs.com/package/@shiwenbin1617/pstack)
10
+ [![license](https://img.shields.io/npm/l/@shiwenbin1617/pstack?color=blue)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/@shiwenbin1617/pstack)](https://nodejs.org)
12
+
13
+ [Install](#install) · [Use it](#use-it) · [How it works](#how-it-works) · [Skills](#skills) · [FAQ](#faq)
14
+
15
+ Works with **Claude Code** and **Codex** · 44 skills · 23 playbooks · 21 engineering principles
16
+
17
+ [中文](./README.md) · English
18
+
19
+ ```bash
20
+ npx @shiwenbin1617/pstack add
21
+ ```
22
+
23
+ </div>
24
+
25
+ ---
26
+
27
+ ## Why pstack
28
+
29
+ The default failure mode of AI-written code is that it looks reasonable, does not run, or runs but nobody checked.
30
+
31
+ | What you get | What it does |
32
+ |---|---|
33
+ | **Understand before you touch** | `/how` and `/why` fan out parallel subagents across the subsystem and its design history. You do not move on until you can state the full call path |
34
+ | **Interfaces before implementation** | `/architect` fixes types, signatures, and module boundaries before any code crosses a function boundary. Skipping it requires a written reason and cannot hide inside the implementation |
35
+ | **Adversarial across models** | `/arena` runs N designs in parallel and grafts the best parts together. `/interrogate` sends different models at your diff in turn |
36
+ | **Evidence, not assertion** | The prove-it-works principle. Green CI is not evidence. An agent saying it passed is not evidence. Only a result observed on the real surface by someone who did not write the code counts |
37
+ | **23 playbooks** | Bug fixes, features, refactors, performance, shipping, and long unattended runs each have fixed steps, exit conditions, and acceptance criteria |
38
+ | **21 engineering principles** | From "pick the core data structure first" to "migrate the callers, then delete the old API". Skills cite them where they apply |
39
+ | **No AI smell** | `/no-comments` strips narrating comments. `/unslop` removes AI tells. `/technical-writing` handles PRs and commit messages |
40
+ | **One install, two hosts** | One body of methodology generates two native trees. Claude Code and Codex keep separate files, agents, and config |
41
+
42
+ The goal is not more output. It is less output where every line holds up.
43
+
44
+ ---
45
+
46
+ ## Requirements
47
+
48
+ - Node.js >= 18
49
+ - Claude Code or Codex. Install whichever you use. If both are present, pstack detects them.
50
+ - Bun is optional. Only the full PR watcher in `babysit` and the ledger CLI in `orchestrate` need it. The helpers never install dependencies behind your back.
51
+
52
+ ---
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ npx @shiwenbin1617/pstack add
58
+ ```
59
+
60
+ First pick the agent: Claude Code, Codex, or both. The cursor starts on whichever host is detected on this machine.
61
+
62
+ Then pick your skills. Arrow keys move, `space` toggles, `a` selects all, `enter` confirms. Ten core entry points come preselected.
63
+
64
+ **The set is coupled and only works installed whole.** `poteto-mode` reads all 33 principle skills, so whatever you pick, the installer pulls in what it references. The preselected core lands on 39 of the 44. `pstack add --all` saves you the arithmetic.
65
+
66
+ Last, it asks once whether to write the pstack block into CLAUDE.md / AGENTS.md. See "Writing into CLAUDE.md / AGENTS.md" below.
67
+
68
+ To type less, install it globally. The command is then `pstack`.
69
+
70
+ ```bash
71
+ npm i -g @shiwenbin1617/pstack
72
+ ```
73
+
74
+ > The unscoped `pstack` on npm is an unrelated package from 2015. It is not this project.
75
+
76
+ ### Other ways to install
77
+
78
+ ```bash
79
+ pstack add --core # core entry points plus their dependencies, 39 of them
80
+ pstack add --all # all 44
81
+ pstack add how why # named skills only
82
+ pstack add --core --host codex --memory # Codex only, and write AGENTS.md
83
+ ```
84
+
85
+ ### Managing what you installed
86
+
87
+ ```bash
88
+ pstack list # what is installed, and where
89
+ pstack find review # search the catalog
90
+ pstack update # reinstall what you already have
91
+ pstack remove # interactive uninstall
92
+ pstack doctor # installation status for both hosts
93
+ ```
94
+
95
+ Any of these also works as `npx @shiwenbin1617/pstack <command>` without a global install.
96
+
97
+ ### Where files land
98
+
99
+ Each host always gets **independent copies**. Claude Code and Codex use different invocation syntax, frontmatter, agent formats, and config paths. Editing an installed file on one side never touches the other.
100
+
101
+ ```
102
+ ~/.agents/skills/how/ Codex skill, invoked as $how
103
+ ~/.codex/agents/*.toml Codex custom agents
104
+ ~/.claude/skills/how/ Claude Code skill, invoked as /how
105
+ ~/.claude/agents/*.md Claude Code custom agents
106
+ ```
107
+
108
+ | Flag | Effect |
109
+ |---|---|
110
+ | `--host claude` / `codex` / `both` | Target one agent. Defaults to whichever hosts are detected on this machine |
111
+ | `--scope user` / `project` | Install into `~/`, or into this repo at `./.claude/` and `./.agents/`. Defaults to user |
112
+ | `--copy` | Ask for independent copies explicitly. Currently the default and the only mode |
113
+ | `--memory` / `--no-memory` | Whether to write the pstack block into CLAUDE.md / AGENTS.md. Without the flag, an interactive install asks and a non-interactive one writes nothing |
114
+ | `--dry-run` | Print what would happen. Write nothing |
115
+ | `-y` / `--yes` | Skip the confirmation |
116
+
117
+ ### Writing into CLAUDE.md / AGENTS.md
118
+
119
+ Installed skills are not much use if the agent never learns they exist. `--memory` writes a block into the always-loaded instruction file naming what is installed, how to invoke it, and where the model config lives.
120
+
121
+ Which file depends on host and scope. Claude Code gets `CLAUDE.md`, Codex gets `AGENTS.md`. `--scope project` writes into the repo root, `--scope user` writes `~/.claude/CLAUDE.md` and `~/.codex/AGENTS.md`.
122
+
123
+ The block sits between two markers, and pstack never touches anything outside them.
124
+
125
+ ```markdown
126
+ <!-- pstack:start -->
127
+ ## pstack
128
+
129
+ Rigorous agent workflows, installed as skills in `~/.claude/skills`.
130
+ Invoke one by name: `/architect`, `/how`, `/interrogate`, ...
131
+ <!-- pstack:end -->
132
+ ```
133
+
134
+ Installing again replaces that block in place rather than appending a second one. `pstack update` refreshes a block that is already there and never introduces one. Removing the last skill removes the block, leaving everything you wrote intact.
135
+
136
+ ### Giving it to your team
137
+
138
+ One line is all a colleague needs.
139
+
140
+ ```bash
141
+ npx @shiwenbin1617/pstack add --core
142
+ ```
143
+
144
+ To pin a version or serve from an internal mirror, publish this repo to a private registry and run `npx <your-package> add`. There is no Claude Code plugin entry point, because plugin loading would bypass the host adapter. Both hosts install their own artifacts through the CLI and nothing else.
145
+
146
+ ---
147
+
148
+ ## Use it
149
+
150
+ ### 1. Bind your model tiers
151
+
152
+ This step is optional, but run it once.
153
+
154
+ ```text
155
+ # Claude Code
156
+ /setup-pstack
157
+
158
+ # Codex
159
+ $setup-pstack
160
+ ```
161
+
162
+ It reads the models your session can actually reach and binds three tiers.
163
+
164
+ | Tier | Where it goes |
165
+ |---|---|
166
+ | Fast code model | Mechanical changes with a clear spec |
167
+ | Precise-execution model | Work that must follow steps to the letter |
168
+ | Judgment model | Copy, design calls, adversarial review |
169
+
170
+ Skill text only ever says "use your judgment model". This file decides who that is. Claude Code writes `~/.claude/pstack-models.md` and Codex writes `~/.codex/pstack-models.md`. Neither reads the other. Skip the step and every skill falls back to its own inline tier defaults.
171
+
172
+ ### 2. One entry point covers most days
173
+
174
+ ```text
175
+ # Claude Code
176
+ /poteto-mode There is a strange bug in this PR. Reproduce it, fix it, then verify.
177
+
178
+ # Codex
179
+ $poteto-mode Add CSV and JSON export to the settings page, with runtime evidence.
180
+ ```
181
+
182
+ ### 3. It routes itself
183
+
184
+ It reads your request, matches a playbook, copies that playbook's steps verbatim into a todo list, and calls the other skills step by step.
185
+
186
+ On Claude Code, `/poteto-mode` keeps its native sticky mode. Codex has no such mode and pstack does not fake one. Invoke `$poteto-mode` for each new task there, and use the goal, wait, or recurring-monitoring features of your Codex session for long runs.
187
+
188
+ ### 4. Call a skill by name when you want one
189
+
190
+ ```
191
+ /how How do we cancel a run? Is there an N+1 query when cancelling in bulk?
192
+ /why Why was this retry written as exponential backoff with jitter?
193
+ /interrogate Review this PR.
194
+ ```
195
+
196
+ On Codex, replace `/` with `$`, as in `$poteto-mode`.
197
+
198
+ ---
199
+
200
+ ## How it works
201
+
202
+ ### The five-stage loop
203
+
204
+ ```
205
+ 1 Understand 2 Design 3 Build 4 Verify 5 Deliver
206
+ ──────────── ──────────── ──────────── ──────────── ────────────
207
+ /how /architect write code /interrogate /unslop
208
+ /why /arena /tdd verification /technical-writing
209
+ /recall /blast-radius /swarm real evidence /no-comments
210
+ open the PR
211
+
212
+ └── every stage has an exit condition and blocks until it is met ──┘
213
+ ```
214
+
215
+ | Stage | Exit condition |
216
+ |---|---|
217
+ | 1 Understand | You can state the full path from input to output without hedging |
218
+ | 2 Design | Interfaces and data shapes are fixed. Implementation is filling in blanks |
219
+ | 3 Build | The code explains itself without comments propping it up |
220
+ | 4 Verify | You hold runtime evidence, not an assertion |
221
+ | 5 Deliver | Nothing a human reads smells like AI wrote it |
222
+
223
+ ### The 23 playbooks
224
+
225
+ `/poteto-mode` matches one of these.
226
+
227
+ | Group | Playbooks |
228
+ |---|---|
229
+ | **Find the problem** | `investigation` read-only research · `bug-fix` reproduce, locate, fix, prove · `perf-issue` optimize against a baseline · `hillclimb` push one metric over time · `runtime-forensics` leaks, idle spin, glitches · `trace-forensics` analyze a captured profile |
230
+ | **Write something** | `feature` new behavior built from a data shape · `refactoring` structural change that preserves behavior · `prototype` a throwaway sketch that settles a decision · `visual-parity` pixel-exact match between two implementations |
231
+ | **Deliver** | `opening-a-pr` · `babysit` drive a PR to merge-ready · `shipping` land a verified run after independent checks · `autopilot-full` one owner per PR through merge · `autopilot-stack` build a Graphite stack for a human to land |
232
+ | **Long runs** | `autonomous-run` drive to a predicate without stopping · `orchestrate` a standing multi-day, multi-PR, multi-agent program · `multi-phase-plan` work spanning phases · `session-pickup` take over another agent's in-flight work · `pause-safely` stop cleanly with a checkpoint |
233
+ | **Meta** | `authoring-a-skill` write a SKILL.md · `eval` blind-test the effect of a prompt change · `worktree-cleanup` prune worktrees and reclaim disk |
234
+
235
+ ### What the `feature` playbook actually does
236
+
237
+ 1. `/how` over the subsystem you are about to change.
238
+ 2. `/architect` for parallel design exploration. **Skipping requires a written reason.** A design decision may not be folded silently into the implementation.
239
+ 3. Write the throughput checkpoint. What must run first, what can run in parallel, and how shared state gets split.
240
+ 4. Only now write code. A delegate gets file paths, the **data shape decided up front**, and acceptance criteria. You review the diff yourself.
241
+ 5. Verify on the real surface. "Inconclusive" is not a pass, and neither is the wrong surface.
242
+ 6. Rebase into small, ordered commits.
243
+ 7. If the design is contested, run `/interrogate` before shipping.
244
+ 8. Run `opening-a-pr`.
245
+
246
+ The phrase "data shape decided up front" in step 4 carries the weight. A state machine instead of scattered booleans. A table or registry instead of branching. A typed model instead of the same shape assumption repeated across files. You pick it **before the first line of logic**. This is where new features bury their landmines.
247
+
248
+ ---
249
+
250
+ ## Skills
251
+
252
+ | Group | Skills |
253
+ |---|---|
254
+ | **Entry point** | `poteto-mode` |
255
+ | **Understand** | `how` `why` `recall` `blast-radius` `teach` |
256
+ | **Design and build** | `architect` `arena` `swarm` `tdd` `typescript-best-practices` `figure-it-out` |
257
+ | **Verify** | `interrogate` `create-verification-skill` `maintain-verification-skill` |
258
+ | **Writing** | `unslop` `no-comments` `technical-writing` `bro` |
259
+ | **Meta** | `setup-pstack` `automate-me` `reflect` `show-me-your-work` |
260
+ | **21 principles** | `principle-*`, cited by the skills above where they apply |
261
+
262
+ <details>
263
+ <summary>The 21 principles</summary>
264
+
265
+ `boundary-discipline` `build-the-lever` `encode-lessons-in-structure` `exhaust-the-design-space` `experience-first` `fix-root-causes` `foundational-thinking` `guard-the-context-window` `laziness-protocol` `make-operations-idempotent` `migrate-callers-then-delete-legacy-apis` `minimize-reader-load` `model-the-domain` `never-block-on-the-human` `outcome-oriented-execution` `prove-it-works` `redesign-from-first-principles` `separate-before-serializing-shared-state` `sequence-verifiable-units` `subtract-before-you-add` `type-system-discipline`
266
+
267
+ </details>
268
+
269
+ Run `pstack find` for the full list with descriptions.
270
+
271
+ ---
272
+
273
+ ## Resources
274
+
275
+ | What you want | Where to look |
276
+ |---|---|
277
+ | Install it, or read the version history | [npm: `@shiwenbin1617/pstack`](https://www.npmjs.com/package/@shiwenbin1617/pstack) · [GitHub Releases](https://github.com/shiwenbin1617/pstack/releases) |
278
+ | File an issue or a PR | [github.com/shiwenbin1617/pstack](https://github.com/shiwenbin1617/pstack) |
279
+ | See what the port changed | [`adapters/claude-code.md`](./adapters/claude-code.md) · [`adapters/codex.md`](./adapters/codex.md) |
280
+ | Follow the original author through a full task | [`docs/guide/`](./docs/guide/README.md) |
281
+ | Validate a skill you edited | `node scripts/build.mjs --check` |
282
+ | Generate the distribution trees | `node scripts/build.mjs`, which writes `dist/` |
283
+ | Triage Slack issues automatically | [`automations/benny/`](./automations/benny/README.md), which needs your own Slack MCP and a scheduled agent |
284
+
285
+ ---
286
+
287
+ ## FAQ
288
+
289
+ <details>
290
+ <summary><b>How is this different from CLAUDE.md, AGENTS.md, or .cursorrules?</b></summary>
291
+
292
+ Those are **always-resident** project rules. Every session loads all of them into context, so they have to stay short and general. "Use TypeScript." "Tests go in tests/."
293
+
294
+ pstack skills load **on demand**. Only the description line of each of the 44 skills stays resident. The agent reads the body when it judges the skill relevant. That budget is what lets each skill go deep. The `feature` playbook has 8 steps with named exit conditions. The `refactoring` one demands a behavior fixture before any structural change. You cannot fit that density into an always-resident file.
295
+
296
+ They do not conflict. CLAUDE.md holds facts about your project. pstack holds general engineering method.
297
+
298
+ </details>
299
+
300
+ <details>
301
+ <summary><b>Is it Claude Code only?</b></summary>
302
+
303
+ No. Claude Code and Codex both work, and the installer detects which you have.
304
+
305
+ `skills/` holds the shared methodology. The builder generates two independent host trees. The Claude Code tree uses `/skill`, Markdown agents, and Claude frontmatter. The Codex tree uses `$skill`, `agents/openai.yaml`, TOML agents, and Codex paths. `scripts/build.mjs --check` blocks cross-host leakage.
306
+
307
+ Adding a third host touches none of the 44 skills. Write one adapter and add an entry to `HOSTS` in `scripts/lib.mjs`.
308
+
309
+ </details>
310
+
311
+ <details>
312
+ <summary><b>Will installing all 44 blow up my context?</b></summary>
313
+
314
+ No. Only the `description` line of each skill stays resident. Bodies load on demand.
315
+
316
+ Codex has a hard limit worth knowing. Its skill index gets 2% of context or 8000 characters, whichever is smaller, and long descriptions get truncated past that. pstack keeps its descriptions tight, but if you also run other skill packs and hit the truncation, install a subset with `pstack add` instead of `--all`.
317
+
318
+ </details>
319
+
320
+ <details>
321
+ <summary><b>Does it conflict with Trellis?</b></summary>
322
+
323
+ They divide differently, and one part does collide.
324
+
325
+ pstack is **stateless**. It owns the method and the standard for doing one thing. It remembers no project state across sessions. Trellis owns the specs, tasks, and work logs that accumulate in `.trellis/`, which solves the problem of an agent starting from zero every time.
326
+
327
+ The collision is workflow orchestration. Trellis runs plan, implement, verify, finish. pstack runs playbooks. Their verification bars are far apart. A Trellis check runs lint, type-check, and tests. pstack says none of those count as verification.
328
+
329
+ To combine them, let Trellis own state and pstack own method. Put the pstack core rules in `.trellis/spec/` so its auto-injection carries the standard into every task.
330
+
331
+ </details>
332
+
333
+ <details>
334
+ <summary><b>Does it make simple tasks slow?</b></summary>
335
+
336
+ It can, which is why `/poteto-mode` is aimed at work that needs rigor rather than at everything. When it cannot match a playbook it backs out instead of forcing a fit.
337
+
338
+ One principle exists for this specific risk. `laziness-protocol` ships the smallest change that reaches the goal and reverts speculative cleanup that might be useful later.
339
+
340
+ If it still feels heavy, skip `/poteto-mode` and call `/how` or `/interrogate` directly.
341
+
342
+ </details>
343
+
344
+ <details>
345
+ <summary><b>How do I make it ours?</b></summary>
346
+
347
+ Shared methodology lives in `skills/`. Host differences live in `adapters/claude-code/` and `adapters/codex/`. After editing, run `node scripts/build.mjs --check`, then `pstack update --host claude` and `pstack update --host codex` separately. Installed directories never sync to each other.
348
+
349
+ That check validates frontmatter, catches directory name collisions, resolves relative links, and finds hardcoded model names or host-specific tool names.
350
+
351
+ To make an agent work the way you personally do, run `/automate-me`. It reads your past sessions and drafts a `-mode` skill from how you actually work.
352
+
353
+ </details>
354
+
355
+ <details>
356
+ <summary><b>What changed in the port?</b></summary>
357
+
358
+ - Cursor's `readonly` mode strips MCP access. The `Explore` subagent in Claude Code does not, so it keeps MCP and loses file writes instead. The "please do not edit files" convention inside `why` and `reflect` is now enforced by the harness.
359
+ - Skills that depended on `cursor-team-kit`, namely `deslop`, `control-ui`, and `control-cli`, were replaced by the bundled `/unslop` and by project-local verification skills that `/create-verification-skill` generates.
360
+ - `grokbot/make-bot-ui` was deleted. It was tied to Cursor's automation webhook, which neither target host has.
361
+ - Every hardcoded model slug upstream became one of three semantic tiers, bound by `/setup-pstack`.
362
+
363
+ The full mapping is in [`adapters/`](./adapters/).
364
+
365
+ </details>
366
+
367
+ ---
368
+
369
+ ## Upstream and license
370
+
371
+ Forked from [cursor/plugins/pstack](https://github.com/cursor/plugins/tree/main/pstack) by Lauren Tan ([@poteto](https://x.com/poteto), React core team, previously Meta, Netflix, and Cursor). The original [usage guide](./docs/guide/README.md) is preserved. It still uses Cursor as its backdrop, and the methodology carries over unchanged.
372
+
373
+ MIT License. Improvements and PRs welcome.
package/README.md CHANGED
@@ -6,10 +6,20 @@
6
6
 
7
7
  一套 Markdown 规则,逼 AI 在动手前把问题想清楚、动手后拿出运行时证据,而不是写完就说"改好了"。
8
8
 
9
+ [![npm](https://img.shields.io/npm/v/@shiwenbin1617/pstack?color=cb3837&logo=npm)](https://www.npmjs.com/package/@shiwenbin1617/pstack)
10
+ [![license](https://img.shields.io/npm/l/@shiwenbin1617/pstack?color=blue)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/@shiwenbin1617/pstack)](https://nodejs.org)
12
+
9
13
  [安装](#安装) · [使用](#使用) · [工作原理](#工作原理) · [技能清单](#技能清单) · [常见问题](#常见问题)
10
14
 
11
15
  同时支持 **Claude Code** 和 **Codex** · 44 个技能 · 23 个 playbook · 21 条工程原则
12
16
 
17
+ 中文 · [English](./README.en.md)
18
+
19
+ ```bash
20
+ npx @shiwenbin1617/pstack add
21
+ ```
22
+
13
23
  </div>
14
24
 
15
25
  ---
@@ -47,7 +57,13 @@ AI 写代码的默认失败模式是:看上去合理、跑不起来、或者
47
57
  npx @shiwenbin1617/pstack add
48
58
  ```
49
59
 
50
- 交互式勾选要装哪些技能:`↑↓` 移动,`空格` 勾选,`a` 全选,`回车` 确认。默认预选 10 个核心入口,安装时自动补齐它们引用的运行依赖。
60
+ 先单选装给哪个 agent:Claude Code、Codex,或者两个都装。本机探测到哪个,光标就落在哪个上。
61
+
62
+ 然后勾选要装哪些技能:`↑↓` 移动,`空格` 勾选,`a` 全选,`回车` 确认。默认预选 10 个核心入口。
63
+
64
+ **这套东西是耦合的,装全了才有效果。** `poteto-mode` 会去读全部 33 个 principle 技能,所以不管你勾几个,安装时都会按引用关系补齐,勾满核心入口最后落在 44 个里的 39 个。省事就直接 `pstack add --all`。
65
+
66
+ 最后问一次要不要把 pstack 那段写进 CLAUDE.md / AGENTS.md,见下面的「写进 CLAUDE.md / AGENTS.md」。
51
67
 
52
68
  嫌包名长就装成全局,之后命令就是 `pstack`:
53
69
 
@@ -60,9 +76,10 @@ npm i -g @shiwenbin1617/pstack
60
76
  ### 其他安装方式
61
77
 
62
78
  ```bash
63
- pstack add --core # 核心入口及其自动展开的运行依赖
79
+ pstack add --core # 核心入口及其自动展开的运行依赖,共 39 个
64
80
  pstack add --all # 全装 44 个
65
81
  pstack add how why # 按名字装指定技能
82
+ pstack add --core --host codex --memory # 只装 Codex,并写入 AGENTS.md
66
83
  ```
67
84
 
68
85
  ### 管理命令
@@ -93,9 +110,29 @@ pstack doctor # 检查两端的安装状态
93
110
  | `--host claude` / `codex` / `both` | 只装给指定 agent。默认自动探测本机装了哪些 |
94
111
  | `--scope user` / `project` | 装到全局 `~/`,还是当前仓库的 `./.claude/`、`./.agents/`。默认 user |
95
112
  | `--copy` | 显式使用独立副本;当前也是默认且唯一模式 |
113
+ | `--memory` / `--no-memory` | 写不写 CLAUDE.md / AGENTS.md 里那段 pstack 说明。不给这个选项时,交互安装会问一次,非交互安装默认不写 |
96
114
  | `--dry-run` | 只打印会做什么,不写任何文件 |
97
115
  | `-y` / `--yes` | 跳过确认 |
98
116
 
117
+ ### 写进 CLAUDE.md / AGENTS.md
118
+
119
+ 装完技能,agent 未必知道它们存在。`--memory` 会往常驻指令文件里写一段说明:装了什么、怎么调用、模型配置在哪。
120
+
121
+ 写哪个文件由 host 和 scope 决定。Claude Code 写 `CLAUDE.md`,Codex 写 `AGENTS.md`;`--scope project` 写当前仓库根目录,`--scope user` 写 `~/.claude/CLAUDE.md` 和 `~/.codex/AGENTS.md`。
122
+
123
+ 那段内容夹在两个标记之间,标记之外的东西 pstack 不碰:
124
+
125
+ ```markdown
126
+ <!-- pstack:start -->
127
+ ## pstack
128
+
129
+ Rigorous agent workflows, installed as skills in `~/.claude/skills`.
130
+ Invoke one by name: `/architect`, `/how`, `/interrogate`, ...
131
+ <!-- pstack:end -->
132
+ ```
133
+
134
+ 重复安装会原地替换这一段,不会越写越长。`pstack update` 只刷新已经存在的那一段,不会给没有的文件新加。卸载完最后一个技能时,这一段会被一起删掉,你自己写的内容原样保留。
135
+
99
136
  ### 分发给同事
100
137
 
101
138
  同事只需要一行:
@@ -235,6 +272,8 @@ Codex 上把 `/` 换成 `$`,例如 `$poteto-mode`。
235
272
 
236
273
  | 想做什么 | 看哪里 |
237
274
  |---|---|
275
+ | 装包 / 看版本历史 | [npm: `@shiwenbin1617/pstack`](https://www.npmjs.com/package/@shiwenbin1617/pstack) · [GitHub Releases](https://github.com/shiwenbin1617/pstack/releases) |
276
+ | 提 issue 或 PR | [github.com/shiwenbin1617/pstack](https://github.com/shiwenbin1617/pstack) |
238
277
  | 了解移植时改了什么 | [`adapters/claude-code.md`](./adapters/claude-code.md) · [`adapters/codex.md`](./adapters/codex.md) |
239
278
  | 跟着原作者走一遍完整任务 | [`docs/guide/`](./docs/guide/README.md) |
240
279
  | 改技能后做校验 | `node scripts/build.mjs --check` |
package/bin/pstack.mjs CHANGED
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import {
6
6
  REPO_ROOT, HOSTS, findSkills, installSkill, installAgents,
7
7
  listInstalled, removeSkill, CORE_SKILLS, expandSkillDependencies,
8
+ writeMemory, removeMemory, hasMemory,
8
9
  } from "../scripts/lib.mjs";
9
10
 
10
11
  const c = {
@@ -41,6 +42,7 @@ function help() {
41
42
  console.log(` --host <claude|codex|both> ${paint(c.grey, "Target agent (default: both, detected)")}`);
42
43
  console.log(` --scope <user|project> ${paint(c.grey, "Install globally or into this repo (default: user)")}`);
43
44
  console.log(` --copy ${paint(c.grey, "Install independent host-specific copies (default)")}`);
45
+ console.log(` --memory / --no-memory ${paint(c.grey, "Write the pstack block into CLAUDE.md / AGENTS.md")}`);
44
46
  console.log(` --core ${paint(c.grey, "Core entry set plus required dependencies, no prompts")}`);
45
47
  console.log(` --all ${paint(c.grey, "Every skill, no prompts")}`);
46
48
  console.log(` -y, --yes ${paint(c.grey, "Skip confirmation")}`);
@@ -49,13 +51,15 @@ function help() {
49
51
  }
50
52
 
51
53
  function parseArgs(argv) {
52
- const opts = { _: [], host: null, scope: "user", core: false, all: false, yes: false, dryRun: false };
54
+ const opts = { _: [], host: null, scope: "user", core: false, all: false, yes: false, dryRun: false, memory: null };
53
55
  for (let i = 0; i < argv.length; i++) {
54
56
  const a = argv[i];
55
57
  if (a === "--host") opts.host = argv[++i];
56
58
  else if (a === "--scope") opts.scope = argv[++i];
57
59
  else if (a === "--link") { console.error(paint(c.red, "--link was removed: Claude Code and Codex installs must stay independent.")); process.exit(1); }
58
60
  else if (a === "--copy") {}
61
+ else if (a === "--memory") opts.memory = true;
62
+ else if (a === "--no-memory") opts.memory = false;
59
63
  else if (a === "--core") opts.core = true;
60
64
  else if (a === "--all") opts.all = true;
61
65
  else if (a === "-y" || a === "--yes") opts.yes = true;
@@ -146,6 +150,49 @@ function multiSelect(items, { preselected = new Set(), title }) {
146
150
  });
147
151
  }
148
152
 
153
+ /** Arrow keys to move, enter to pick one. Returns null on cancel. */
154
+ function singleSelect(items, { title, initial = 0 }) {
155
+ return new Promise((resolve) => {
156
+ let cursor = initial;
157
+ emitKeypressEvents(process.stdin);
158
+ process.stdin.setRawMode(true);
159
+ process.stdin.resume();
160
+
161
+ let lastLines = 0;
162
+ const render = () => {
163
+ if (lastLines) process.stdout.write(`\x1b[${lastLines}A\x1b[0J`);
164
+ const lines = [`${paint(c.bold, title)} ${paint(c.grey, "↑↓ move · enter confirm · esc cancel")}`];
165
+ items.forEach((it, i) => {
166
+ const pointer = i === cursor ? paint(c.cyan, "❯") : " ";
167
+ const label = i === cursor ? paint(c.bold, it.label) : it.label;
168
+ const hint = it.hint ? " " + paint(c.grey, it.hint) : "";
169
+ lines.push(`${pointer} ${label}${hint}`);
170
+ });
171
+ process.stdout.write(lines.join("\n") + "\n");
172
+ lastLines = lines.length;
173
+ };
174
+
175
+ const done = (result) => {
176
+ process.stdin.setRawMode(false);
177
+ process.stdin.pause();
178
+ process.stdin.removeListener("keypress", onKey);
179
+ process.stdout.write("\n");
180
+ resolve(result);
181
+ };
182
+
183
+ const onKey = (_str, key) => {
184
+ if (key.name === "up" || key.name === "k") cursor = (cursor - 1 + items.length) % items.length;
185
+ else if (key.name === "down" || key.name === "j") cursor = (cursor + 1) % items.length;
186
+ else if (key.name === "return") return done(items[cursor].value);
187
+ else if (key.name === "escape" || (key.ctrl && key.name === "c")) return done(null);
188
+ render();
189
+ };
190
+
191
+ process.stdin.on("keypress", onKey);
192
+ render();
193
+ });
194
+ }
195
+
149
196
  function confirm(question) {
150
197
  return new Promise((resolve) => {
151
198
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -163,13 +210,26 @@ const NAME_COL = Math.max(...findSkills().map((s) => s.name.length)) + 2;
163
210
 
164
211
  async function cmdAdd(opts) {
165
212
  const catalog = findSkills();
166
- const hosts = resolveHosts(opts);
213
+ let hosts = resolveHosts(opts);
167
214
  let names;
168
215
 
169
216
  if (opts.all) names = catalog.map((s) => s.dirName);
170
217
  else if (opts.core) names = CORE_SKILLS;
171
218
  else if (opts._.length) names = opts._;
172
219
  else if (isTTY()) {
220
+ console.log(paint(c.cyan, BANNER));
221
+ if (!opts.host) {
222
+ const detected = detectHosts();
223
+ const choices = [
224
+ { value: ["claude"], label: HOSTS.claude.label.padEnd(14), hint: detected.includes("claude") ? "detected" : "not detected" },
225
+ { value: ["codex"], label: HOSTS.codex.label.padEnd(14), hint: detected.includes("codex") ? "detected" : "not detected" },
226
+ { value: ["claude", "codex"], label: "Both".padEnd(14), hint: "independent copies for each" },
227
+ ];
228
+ const initial = detected.length > 1 ? 2 : Math.max(0, choices.findIndex((ch) => ch.value[0] === (detected[0] || "claude")));
229
+ const picked = await singleSelect(choices, { title: "Install for which agent?", initial });
230
+ if (picked === null) { console.log(paint(c.grey, "cancelled.")); return; }
231
+ hosts = picked;
232
+ }
173
233
  const width = Math.max(20, (process.stdout.columns || 80) - NAME_COL - 10);
174
234
  const items = catalog.map((s) => ({
175
235
  value: s.dirName,
@@ -177,7 +237,6 @@ async function cmdAdd(opts) {
177
237
  hint: truncate(s.description.replace(/\s+/g, " "), width),
178
238
  }));
179
239
  const preselected = new Set(CORE_SKILLS.filter((n) => catalog.some((s) => s.dirName === n)));
180
- console.log(paint(c.cyan, BANNER));
181
240
  console.log(` installing to: ${paint(c.bold, hosts.map((h) => HOSTS[h].label).join(" + "))} ${paint(c.grey, "(" + opts.scope + " scope)")}\n`);
182
241
  const picked = await multiSelect(items, { preselected, title: "Select skills to install" });
183
242
  if (picked === null) { console.log(paint(c.grey, "cancelled.")); return; }
@@ -199,10 +258,17 @@ async function cmdAdd(opts) {
199
258
  const selected = expandSkillDependencies(requested, catalog);
200
259
  const addedDependencies = selected.filter((skill) => !requested.includes(skill));
201
260
  if (!opts.yes && !opts.dryRun && isTTY()) {
202
- const ok = await confirm(`install ${paint(c.bold, String(selected.length))} skills to ${hosts.map((h) => HOSTS[h].label).join(" + ")}?`);
261
+ const split = addedDependencies.length ? paint(c.grey, ` (${requested.length} picked, ${addedDependencies.length} referenced)`) : "";
262
+ const ok = await confirm(`install ${paint(c.bold, String(selected.length))} skills to ${hosts.map((h) => HOSTS[h].label).join(" + ")}${split}?`);
203
263
  if (!ok) { console.log(paint(c.grey, "cancelled.")); return; }
204
264
  }
205
265
 
266
+ let memory = opts.memory;
267
+ if (memory === null) {
268
+ const files = hosts.map((h) => HOSTS[h].memoryFile(opts.scope)).join(" and ");
269
+ memory = !opts.dryRun && isTTY() && !opts.yes ? await confirm(`add the pstack block to ${paint(c.bold, files)}?`) : false;
270
+ }
271
+
206
272
  if (addedDependencies.length) console.log(`\n${paint(c.grey, `added ${addedDependencies.length} required skill dependencies`)}`);
207
273
  for (const host of hosts) {
208
274
  const dir = HOSTS[host].skillsDir(opts.scope);
@@ -213,6 +279,10 @@ async function cmdAdd(opts) {
213
279
  }
214
280
  const agentsDir = installAgents({ host, scope: opts.scope, dryRun: opts.dryRun });
215
281
  if (agentsDir) console.log(` ${paint(c.green, "✓")} agents ${paint(c.grey, "→ " + agentsDir)}`);
282
+ if (memory) {
283
+ const file = writeMemory({ host, scope: opts.scope, skills: selected, dryRun: opts.dryRun });
284
+ console.log(` ${paint(c.green, "✓")} memory ${paint(c.grey, "→ " + (file || HOSTS[host].memoryFile(opts.scope) + " (already current)"))}`);
285
+ }
216
286
  }
217
287
 
218
288
  if (opts.dryRun) { console.log(`\n${paint(c.yellow, "dry run — nothing written.")}`); return; }
@@ -243,6 +313,11 @@ async function cmdRemove(opts) {
243
313
  const removed = removeSkill(n, { host, scope: opts.scope, dryRun: opts.dryRun });
244
314
  console.log(removed ? ` ${paint(c.green, "✓")} removed ${n}` : ` ${paint(c.grey, "· not installed: " + n)}`);
245
315
  }
316
+ // The block only describes installed skills, so it goes with the last of them.
317
+ if (!listInstalled({ host, scope: opts.scope }).length) {
318
+ const file = removeMemory({ host, scope: opts.scope, dryRun: opts.dryRun });
319
+ if (file) console.log(` ${paint(c.green, "✓")} removed the pstack block ${paint(c.grey, "→ " + file)}`);
320
+ }
246
321
  }
247
322
  console.log("");
248
323
  }
@@ -280,6 +355,11 @@ async function cmdUpdate(opts) {
280
355
  console.log(` ${paint(c.green, "✓")} ${s.name}`);
281
356
  }
282
357
  installAgents({ host, scope: opts.scope, dryRun: opts.dryRun });
358
+ // Refresh the block only where one already exists; update never introduces it.
359
+ if (opts.memory !== false && (opts.memory || hasMemory({ host, scope: opts.scope }))) {
360
+ const file = writeMemory({ host, scope: opts.scope, skills: selected, dryRun: opts.dryRun });
361
+ if (file) console.log(` ${paint(c.green, "✓")} memory ${paint(c.grey, "→ " + file)}`);
362
+ }
283
363
  }
284
364
  console.log("");
285
365
  }
@@ -306,7 +386,8 @@ function cmdDoctor(opts) {
306
386
  for (const scope of ["user", "project"]) {
307
387
  const dir = HOSTS[host].skillsDir(scope);
308
388
  const n = listInstalled({ host, scope }).length;
309
- console.log(` ${scope.padEnd(8)} ${n ? paint(c.green, n + " installed") : paint(c.grey, "0 installed")} ${paint(c.grey, dir)}`);
389
+ const memory = hasMemory({ host, scope }) ? paint(c.grey, " · block in " + HOSTS[host].memoryFile(scope)) : "";
390
+ console.log(` ${scope.padEnd(8)} ${n ? paint(c.green, n + " installed") : paint(c.grey, "0 installed")} ${paint(c.grey, dir)}${memory}`);
310
391
  }
311
392
  }
312
393
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiwenbin1617/pstack",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "description": "if you want to go fast, go deep first. rigorous agent workflows for Claude Code and Codex. ported from poteto's pstack.",
5
5
  "bin": {
6
6
  "pstack": "bin/pstack.mjs"
package/scripts/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { readdirSync, readFileSync, statSync, lstatSync, mkdirSync, rmSync, cpSync, existsSync, realpathSync } from "node:fs";
1
+ import { readdirSync, readFileSync, statSync, lstatSync, mkdirSync, rmSync, cpSync, existsSync, realpathSync, writeFileSync } from "node:fs";
2
2
  import { join, dirname, relative } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { fileURLToPath } from "node:url";
@@ -16,6 +16,7 @@ export const HOSTS = {
16
16
  skillsDir: (scope) => (scope === "project" ? join(process.cwd(), ".claude", "skills") : join(homedir(), ".claude", "skills")),
17
17
  agentsDir: (scope) => (scope === "project" ? join(process.cwd(), ".claude", "agents") : join(homedir(), ".claude", "agents")),
18
18
  memoryFile: (scope) => (scope === "project" ? join(process.cwd(), "CLAUDE.md") : join(homedir(), ".claude", "CLAUDE.md")),
19
+ modelConfig: "~/.claude/pstack-models.md",
19
20
  invoke: (name) => `/${name}`,
20
21
  },
21
22
  codex: {
@@ -23,6 +24,7 @@ export const HOSTS = {
23
24
  skillsDir: (scope) => (scope === "project" ? join(process.cwd(), ".agents", "skills") : join(homedir(), ".agents", "skills")),
24
25
  agentsDir: (scope) => (scope === "project" ? join(process.cwd(), ".codex", "agents") : join(homedir(), ".codex", "agents")),
25
26
  memoryFile: (scope) => (scope === "project" ? join(process.cwd(), "AGENTS.md") : join(homedir(), ".codex", "AGENTS.md")),
27
+ modelConfig: "~/.codex/pstack-models.md",
26
28
  invoke: (name) => `$${name}`,
27
29
  },
28
30
  };
@@ -110,6 +112,74 @@ export function installAgents({ host, scope, dryRun = false }) {
110
112
  return dir;
111
113
  }
112
114
 
115
+ const MEMORY_START = "<!-- pstack:start -->";
116
+ const MEMORY_END = "<!-- pstack:end -->";
117
+
118
+ /** The managed block pstack owns inside CLAUDE.md / AGENTS.md. Everything else in the file is the user's. */
119
+ export function memoryBlock({ host, scope, skills = [] }) {
120
+ const h = HOSTS[host];
121
+ const entries = skills.filter((s) => CORE_SKILLS.includes(s.dirName)).map((s) => h.invoke(s.name));
122
+ const shown = entries.length ? entries : skills.map((s) => h.invoke(s.name));
123
+ return [
124
+ MEMORY_START,
125
+ "## pstack",
126
+ "",
127
+ `Rigorous agent workflows, installed as skills in \`${h.skillsDir(scope)}\`.`,
128
+ `Invoke one by name: ${shown.slice(0, 6).map((i) => `\`${i}\``).join(", ")}.`,
129
+ "",
130
+ `Reach for \`${h.invoke("poteto-mode")}\` on a non-trivial task that wants the full workflow. Mode activation is`,
131
+ "explicit and scoped to one task, so invoke it again for the next one.",
132
+ "",
133
+ `When a pstack skill asks for a per-role model, read \`${h.modelConfig}\`.`,
134
+ MEMORY_END,
135
+ ].join("\n");
136
+ }
137
+
138
+ /**
139
+ * Write the managed block into the host's memory file, replacing an earlier one so
140
+ * reinstalls stay idempotent. Returns the path, or null when nothing changed.
141
+ */
142
+ export function writeMemory({ host, scope, skills = [], dryRun = false }) {
143
+ const file = HOSTS[host].memoryFile(scope);
144
+ const block = memoryBlock({ host, scope, skills });
145
+ const existing = existsSync(file) ? readFileSync(file, "utf8") : "";
146
+ const start = existing.indexOf(MEMORY_START);
147
+ const end = existing.indexOf(MEMORY_END);
148
+
149
+ let next;
150
+ if (start !== -1 && end > start) {
151
+ next = existing.slice(0, start) + block + existing.slice(end + MEMORY_END.length);
152
+ } else {
153
+ next = existing.trimEnd();
154
+ next = next ? `${next}\n\n${block}\n` : `${block}\n`;
155
+ }
156
+ if (next === existing) return null;
157
+ if (dryRun) return file;
158
+ mkdirSync(dirname(file), { recursive: true });
159
+ writeFileSync(file, next);
160
+ return file;
161
+ }
162
+
163
+ /** Whether the host's memory file already carries a pstack block. */
164
+ export function hasMemory({ host, scope }) {
165
+ const file = HOSTS[host].memoryFile(scope);
166
+ return existsSync(file) && readFileSync(file, "utf8").includes(MEMORY_START);
167
+ }
168
+
169
+ /** Drop the managed block, leaving the rest of the file untouched. */
170
+ export function removeMemory({ host, scope, dryRun = false }) {
171
+ const file = HOSTS[host].memoryFile(scope);
172
+ if (!existsSync(file)) return null;
173
+ const existing = readFileSync(file, "utf8");
174
+ const start = existing.indexOf(MEMORY_START);
175
+ const end = existing.indexOf(MEMORY_END);
176
+ if (start === -1 || end < start) return null;
177
+ const next = (existing.slice(0, start).trimEnd() + "\n" + existing.slice(end + MEMORY_END.length).trimStart()).trim();
178
+ if (dryRun) return file;
179
+ writeFileSync(file, next ? next + "\n" : "");
180
+ return file;
181
+ }
182
+
113
183
  export function expandSkillDependencies(selected, catalog = findSkills()) {
114
184
  const selectedNames = new Set(selected.map((skill) => skill.name));
115
185
  const queued = [...selected];
package/scripts/test.mjs CHANGED
@@ -18,9 +18,13 @@ import {
18
18
  REPO_ROOT,
19
19
  expandSkillDependencies,
20
20
  findSkills,
21
+ hasMemory,
21
22
  installAgents,
22
23
  installSkill,
24
+ removeMemory,
25
+ writeMemory,
23
26
  } from "./lib.mjs";
27
+ import { writeFileSync } from "node:fs";
24
28
 
25
29
  const scriptsDir = dirname(fileURLToPath(import.meta.url));
26
30
  const check = spawnSync(process.execPath, [join(scriptsDir, "build.mjs")], {
@@ -98,6 +102,32 @@ try {
98
102
  assert.match(codexSetup, /~\/\.codex\/pstack-models\.md/);
99
103
  assert.doesNotMatch(claudeSetup, /~\/\.codex/);
100
104
  assert.doesNotMatch(codexSetup, /~\/\.claude/);
105
+
106
+ // The memory block appends to whatever the user already wrote, and rewrites in place.
107
+ const claudeMemory = join(testRoot, "CLAUDE.md");
108
+ const codexMemory = join(testRoot, "AGENTS.md");
109
+ writeFileSync(claudeMemory, "# House rules\n\nRun the linter.\n");
110
+
111
+ for (const host of ["claude", "codex"]) {
112
+ assert.equal(hasMemory({ host, scope: "project" }), false);
113
+ writeMemory({ host, scope: "project", skills: selected });
114
+ assert.equal(hasMemory({ host, scope: "project" }), true);
115
+ assert.equal(writeMemory({ host, scope: "project", skills: selected }), null, "rewriting the same block is a no-op");
116
+ }
117
+
118
+ const claudeBody = readFileSync(claudeMemory, "utf8");
119
+ assert.match(claudeBody, /Run the linter\./);
120
+ assert.match(claudeBody, /\/poteto-mode/);
121
+ assert.match(claudeBody, /~\/\.claude\/pstack-models\.md/);
122
+ assert.equal(claudeBody.match(/pstack:start/g).length, 1);
123
+
124
+ const codexBody = readFileSync(codexMemory, "utf8");
125
+ assert.match(codexBody, /\$poteto-mode/);
126
+ assert.doesNotMatch(codexBody, /\.claude|Claude Code/);
127
+
128
+ removeMemory({ host: "claude", scope: "project" });
129
+ assert.equal(hasMemory({ host: "claude", scope: "project" }), false);
130
+ assert.equal(readFileSync(claudeMemory, "utf8"), "# House rules\n\nRun the linter.\n");
101
131
  } finally {
102
132
  process.chdir(originalCwd);
103
133
  rmSync(testRoot, { recursive: true, force: true });