pi-anchorloop 2.4.1 → 2.7.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.md CHANGED
@@ -25,12 +25,12 @@ shared grilling · domain-modeling · human-feedback · agent-terse
25
25
 
26
26
  | File | What it does |
27
27
  | --- | --- |
28
- | `pi/book.ts` | The loop book. The first `todo` create or `kind` call opens a loop; task records need the `todo` tool of [`@juicesharp/rpiv-todo`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo#readme) (its `tool_result` details `{tasks, nextId}` and `rpiv-todo-state` entries). The agent declares only what the extension cannot observe with `anchorloop_stage`: `kind` (`quick`, `inline`, `reviewed`, `durable`, with an intent and the anchor command; a later call changes the kind only with a `reason`), `verify` (a check not run in bash), `fresh-read`, `friction` (`tried`, `found`, `led_by`), `reflect`, `skip`, `back`, `close`, and the query `read`. Everything else is read from evidence: production edits (a write or edit in any git repo, outside its `scratch/` and `docs/specs/`; a linked child's edits and a successful `git merge`, `cherry-pick`, or `pull` count too), the exit code of the bash call that ran the anchor command (an anchor that passed, failed, and passed again records its own friction entry: the rerun command and the failed run's key line, at most 120 characters and never a stack trace, still waiting for a `reflect`), reviewer `Blocking: P0=<n> P1=<m>` counts, summaries of `docs/specs/*/` artifacts as they are edited, and a returned subagent that Jev judges to have been a fresh-context read of the spec. A phase machine over that evidence gives each kind a fixed sequence (`quick` act → check; `inline` anchor → act → verify; `reviewed` adds review; `durable` adds decide → grill → spec → read → approve → design; every kind ends with reflect → close), and `read` shows `<kind> · phase <current> ✓ → <next> (missing: …) · skipped: …`. The pi footer shows `anchorloop <kind> · <current> ✓ → <next>`, with the friction still to reflect, after every tool call and on `session_start`, and clears when the loop closes. An anchor run or declared verify of a `quick` or `inline` loop also measures the diff's reach: source files (not tests, lockfiles, specs, or prose), the top-level directories they sit in, and a README, changelog, or `*.schema.json` changed beside them. Files already changed when a repo joined the loop count only once their content changes, and a file at a repository root counts as a file, not a directory. At 3 source files, 2 directories, or one such file, the result carries one line asking to re-classify, and verify waits for a `kind` call with `reason` that answers each threshold crossed; a later threshold asks again. A phase that starts before an earlier one is complete warns once in one line and names the `skip` escape; phases regress when evidence changes or on `back`. The book also records each todo with its `anchor`, `ref` (`<path>#<heading>`, resolved to line ranges), and `followup` metadata. One YAML book per open loop lives under `~/.pi/agent/anchorloop/books/` (`ANCHORLOOP_BOOKS_DIR` overrides it; empty project and `agents/` directories are removed), subagent books under `agents/` (a child that closes keeps its book for the main session); `session_start` sweeps books whose session is gone or that have not changed in 7 days. `close` lists every phase as done, skipped, or auto-skipped and never refuses over phases or friction, only while a pending or in-progress todo lacks `metadata.followup` (a ticket id, `memory`, or `drop — <reason>`). |
29
- | `pi/anchor-gate.ts` | Inside an open loop, shows one warning line when `write` or `edit` starts the `act` phase while an earlier phase of the loop kind is incomplete: no anchor (`anchor` on the `kind` call, a `Reality anchor:` line, or a skip), and in a `durable` loop a missing decision, spec, fresh read, approval, or design, read from the `docs/specs/<folder>/` files (found from the book, the intent, or a todo `ref`). It warns again only when a new phase goes missing; the call still goes through. Outside a loop and in `quick` loops it stays silent. `.md`, `.txt`, and `.html` files, `scratch/`, `docs/specs/`, and temp files outside the project pass silently. Edits made through `bash` are not checked. |
30
- | `pi/decision-tool.ts` | The `anchorloop_decision` tool appends single-line, spreadsheet-safe rows to a decision trail TSV, or a `start` row when a run picks up another run's trail. |
31
- | `pi/second-thought.ts` | Inside an open loop, asks Jev (TypeSafe's typed-decision model, through pi's classifier registry) to check the phase machine when a run settles and the loop book gained an entry since the last judgment, and at a `close` declaration. The program computes the next phase from the book; Jev sees the kind, intent, anchor, diff summary (≤1200 chars), `phase` (current, next, missing), verify state, open todos, the task list, and friction (a child's under its name), and judges only whether to `advance`, go `back`, or `ask-user`, plus `done`, the review tier still needed, and quality; phase completion comes from the program's evidence alone. The one-line advisory leads with `next → <phase>`. The run continues once, only when Jev agrees to advance and the next phase is a verify, review, or fix; an advisory identical to the last one is not repeated, and a `full` rating after a clean review that follows the last change shows a note instead of a recheck. A run that called `MonitorCreate`, `LoopCreate`, `goal_wait`, or a `subagent` tool is waiting and is not judged. Without a TypeSafe key it does nothing, and outside a loop it never runs. In a subagent session (`PI_SUBAGENT_ID` from pi-herdr-agents, or `PI_SUBAGENT_CHILD` from pi-subagents) it registers nothing, since the agent that spawned it judges the result. |
32
- | `pi/validate-tool-use.ts` | After each `write` or `edit`, validates the file in-process (`pi/support/validators.ts`, a port of `hooks/validators/`) and appends suggestions for `SKILL.md`, instruction files (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `rules/`), and `docs/specs/` artifacts. Needs no Python; the Python validators in `hooks/` remain for `validate_all.py` and the parity test. |
33
- | `pi/ttsr.ts` | A port of oh-my-pi's Time Traveling Stream Rules. A Markdown rule in `~/.pi/agent/rules/` or `<project>/.pi/rules/` whose frontmatter has a `condition` regex is watched during the run. A match in streamed assistant text aborts the response, drops it, and retries with the rule injected; a match in tool arguments (the command for `bash`, the new text for `write` and `edit`) blocks the call with the rule as its reason. Frontmatter fields follow oh-my-pi: `condition`, `scope` (`text`, `thinking`, `tool`, `tool:bash`, `tool:edit(*.ts)`), `globs`, and `interruptMode` (`always`, `prose-only`, `tool-only`, `never`), plus `repeatMode` (`once` or `after-gap`) and `repeatGap`. A rule fires once per session by default. `astCondition`, `question`, and `agents` are not supported; a rule that uses them is skipped and reported. `/ttsr` lists the loaded rules. |
28
+ | `pi/book.ts` | The loop book. The first `todo` create opens an `inline` loop (intent from the first todo's subject, anchor from a `Reality anchor:` line or the todo's `metadata.anchor`), and a `kind` call opens or changes one; task records need the `todo` tool of [`@juicesharp/rpiv-todo`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo#readme) (its `tool_result` details `{tasks, nextId}` and `rpiv-todo-state` entries). The agent declares only what the extension cannot observe with `anchorloop_stage`: `kind` (to choose `quick`, `reviewed`, or `durable`, with an intent and the anchor command; changing a kind the agent chose takes a `reason`), `verify` (a check not run in bash), `fresh-read`, `friction` (`tried`, `found`, `led_by`), `reflect`, `skip`, `back`, `close`, and the query `read`. Everything else is read from evidence: production edits (a write or edit in any git repo, outside its `scratch/` and `docs/specs/`; a linked child's edits and a successful `git merge`, `cherry-pick`, or `pull` count too), the exit code of the bash call that ran the anchor command (an anchor that passed, failed, and passed again records its own friction entry, already reflected as `resolved`: the rerun command and the failed run's key line, at most 120 characters and never a stack trace; the agent is told nothing), reviewer `Blocking: P0=<n> P1=<m>` counts, summaries of `docs/specs/*/` artifacts as they are edited, and a returned subagent that Jev judges to have been a fresh-context read of the spec. A phase machine over that evidence gives each kind a fixed sequence (`quick` act → check; `inline` anchor → act → verify; `reviewed` adds review; `durable` adds decide → grill → spec → read → approve → design; every kind ends with reflect → close), and `read` shows `<kind> · phase <current> ✓ → <next> (missing: …) · skipped: …`. A widget below the editor shows `⚓ <kind> · <current> ✓ → <next>`, with `· N friction` while friction waits for a `reflect`, after every tool call, on `session_start`, and when a `Reality anchor:` line is stated, and clears when the loop closes. An anchor run or declared verify of a `quick` or `inline` loop also measures the diff's reach: source files (not tests, lockfiles, specs, or prose), the top-level directories they sit in, and a README, changelog, or `*.schema.json` changed beside them. Files already changed when a repo joined the loop count only once their content changes, and a file at a repository root counts as a file, not a directory. At 3 source files, 2 directories, or one such file, the result carries one line asking to re-classify, and verify waits for a `kind` call with `reason` that answers each threshold crossed; a later threshold asks again. A phase that starts before an earlier one is complete warns once in one line; phases regress when evidence changes or on `back`. The book also records each todo with its `anchor`, `ref` (`<path>#<heading>`, resolved to line ranges), and `followup` metadata. One YAML book per open loop lives under `~/.pi/agent/anchorloop/books/` (`ANCHORLOOP_BOOKS_DIR` overrides it; empty project and `agents/` directories are removed), subagent books under `agents/` (a child that closes keeps its book for the main session). A child is linked only through [pi-herdr-agents](https://github.com/wayne930242/pi-herdr-agents) (`PI_SUBAGENT_ID` in the child, `details.id` on the parent's `subagent` result; pi itself has no subagents): a linked child's edits are the parent's act, and its anchor runs, recorded as `check`, carry the working-state tree of each repo it works in (tracked and untracked files, ignored files and `scratch/` left out). When a parent's `git merge`, `cherry-pick` or `pull` succeeds, or a subagent result returns, and the parent's working-state trees are exactly the trees of a child's latest run of the same anchor (a leading `cd` into a repo root aside), and that run passed, the parent records it as its own verify (`adopted`): a fast-forward of a worker's branch, or a child that worked in the parent's checkout, needs no second run, while a merge onto a base that moved, a parent edit or repo the child never saw, or a file the child ran with but did not commit does; `session_start` sweeps books whose session is gone or that have not changed in 7 days. `close` answers `closed (landed). Clear the todo list.`, adding only the phases not shown complete and the unreflected friction, and never refuses over phases or friction, only while a pending or in-progress todo lacks `metadata.followup` (a ticket id, `memory`, or `drop — <reason>`). |
29
+ | `pi/anchor-gate.ts` | Inside an open loop, appends one warning line to the edit's tool result (where the agent reads it) when `write` or `edit` starts the `act` phase while an earlier phase of the loop kind is incomplete: no anchor (`anchor` on the `kind` call, a `Reality anchor:` line, or a skip), and in a `durable` loop a missing decision, spec, fresh read, approval, or design, read from the `docs/specs/<folder>/` files (found from the book, the intent, or a todo `ref`). It warns again only when a new phase goes missing; the call still goes through. Outside a loop and in `quick` loops it stays silent. `.md`, `.txt`, and `.html` files, `scratch/`, `docs/specs/`, and temp files outside the project pass silently. Edits made through `bash` are not checked. |
30
+ | `pi/decision-tool.ts` | The `anchorloop_decision` tool appends single-line, spreadsheet-safe rows to a decision trail TSV for forks, pivots, and blockers (completed units come from `git log`), or a `start` row when a run picks up another run's trail. |
31
+ | `pi/second-thought.ts` | Inside an open loop, asks Jev (TypeSafe's typed-decision model, through pi's classifier registry) to check the phase machine when a run settles and the loop book gained an entry since the last judgment, and at a `close` declaration. The program computes the next phase from the book; Jev sees the kind, intent, anchor, diff summary (≤1200 chars), `phase` (current, next, missing), verify state, the open todos by id and subject, the task list, and friction (a child's under its name), and judges only whether to `advance`, go `back`, or `ask-user`, plus `done`, the review tier still needed, and quality; phase completion comes from the program's evidence alone. The advisory is `next: <phase> (<missing>)` plus Jev's concerns, with no scores. It is sent only when Jev has a concern, todos are open without a followup, or the run continues, and stays silent when Jev agrees with nothing to add (the verdict is still recorded in the session); at close it carries concerns only. Away from close it also names the loop's open todos that have no valid `metadata.followup` (the first three, subjects cut to 60 characters, then `+N more`) and asks the agent to continue each, complete it, or set that followup. The run continues once, when Jev agrees to advance and the next phase is a verify, review, or fix, or when such todos are open and Jev's move is not `ask-user` (then the advisory tells the agent to ask the user instead); an advisory with the same next phase, concerns, and open todo ids as the last one is not repeated, and a `full` rating after a clean review that follows the last change is not a concern. A run that called `MonitorCreate`, `LoopCreate`, `goal_wait`, or a `subagent` tool is waiting and is not judged. Without a TypeSafe key it does nothing, and outside a loop it never runs. In a subagent session (`PI_SUBAGENT_ID` from pi-herdr-agents, or `PI_SUBAGENT_CHILD` from pi-subagents) it registers nothing, since the agent that spawned it judges the result. |
32
+ | `pi/validate-tool-use.ts` | After each `write` or `edit`, validates the file in-process (`pi/support/validators.ts`, a port of `hooks/validators/`) and appends suggestions for `SKILL.md` (at most three red-line advisories per edit), instruction files (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `rules/`), and `docs/specs/` artifacts. Needs no Python; the Python validators in `hooks/` remain for `validate_all.py` and the parity test. |
33
+ | `pi/ttsr.ts` | A port of oh-my-pi's Time Traveling Stream Rules. A Markdown rule in `~/.pi/agent/rules/` or `<project>/.pi/rules/` whose frontmatter has a `condition` regex is watched during the run. A match in streamed assistant text aborts the response, drops it, and retries with the rule injected; a match in tool arguments (the command for `bash`, the new text for `write` and `edit`) blocks the call with the rule as its reason. Frontmatter fields follow oh-my-pi: `condition`, `scope` (`text`, `thinking`, `tool`, `tool:bash`, `tool:edit(*.ts)`), `globs`, and `interruptMode` (`always`, `prose-only`, `tool-only`, `never`), plus `repeatMode` (`once` or `after-gap`) and `repeatGap`. Each injected rule arrives as `<rule name="…">body</rule>`. A rule fires once per session by default. `astCondition`, `question`, and `agents` are not supported; a rule that uses them is skipped and reported. `/ttsr` lists the loaded rules. |
34
34
 
35
35
  ## Install
36
36
 
package/README.zh-TW.md CHANGED
@@ -25,12 +25,12 @@ anchorloop 核心循環 · 原則 · playbook:autonomous-run、paus
25
25
 
26
26
  | 檔案 | 作用 |
27
27
  | --- | --- |
28
- | `pi/book.ts` | Loop book。第一次 `todo` create 或 `kind` 呼叫會開啟一個 loop;任務紀錄需要 [`@juicesharp/rpiv-todo`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo#readme) 的 `todo` 工具(它的 `tool_result` details `{tasks, nextId}` 和 `rpiv-todo-state` 條目)。agent 只用 `anchorloop_stage` 宣告 extension 看不到的事:`kind`(`quick`、`inline`、`reviewed`、`durable`,附 intent 和 anchor 指令;之後要改種類,必須附 `reason`)、`verify`(不是指令的檢查)、`fresh-read`、`friction`(`tried`、`found`、`led_by`)、`reflect`、`skip`、`back`、`close`,以及查詢用的 `read`。其餘都從證據讀出:production 的修改(任何 git repo 內、`scratch/` 與 `docs/specs/` 以外的 write 或 edit;已連結 child 的修改,以及成功的 `git merge`、`cherry-pick`、`pull` 也算)、執行過 anchor 指令的那次 bash 的 exit code(anchor 通過後失敗、又再通過時,extension 會自己記一筆 friction:重跑的指令,加上失敗那次最關鍵的一行輸出,最多 120 字元,不放 stack trace;這筆仍要 `reflect` 分類)、reviewer 的 `Blocking: P0=<n> P1=<m>` 數字、`docs/specs/*/` 產出物每次編輯後的摘要,以及 Jev 判定為「全新 context 讀過 spec」的 subagent 回報。階段機依這些證據給每種 loop 固定的階段順序(`quick` 為 act → check;`inline` 為 anchor → act → verify;`reviewed` 加 review;`durable` 再加 decide → grill → spec → read → approve → design;每一種都以 reflect → close 結尾),`read` 顯示 `<種類> · phase <目前> ✓ → <下一個>(缺:…)· 已跳過:…`。pi 的 footer 會顯示 `anchorloop <種類> · <目前> ✓ → <下一個>`,以及還沒 reflect 的 friction 數;每次工具呼叫後和 `session_start` 時更新,loop 結案後清掉。`quick` 或 `inline` loop 跑 anchor 或宣告 verify 時,也會量 diff 的影響範圍:原始碼檔案(不含測試、lockfile、spec 與文字檔)、它們所在的頂層目錄,以及跟原始碼一起改的 README、changelog 或 `*.schema.json`。repo 加入 loop 時就已經改過的檔案,要等內容再變動才算;repo 根目錄的檔案只算檔案數,不算一個目錄。達到 3 個原始碼檔案、2 個目錄或 1 個這類檔案時,結果會附一行請 agent 重新分類;verify 要等到以 `reason` 呼叫 `kind`、回應每一個跨過的門檻才算完成,之後再跨過新的門檻會再問一次。前一個階段還沒完成就開始下一個階段,會用一行警告一次並指出 `skip` 的出口;證據改變或用 `back` 時階段會退回。book 也記錄每個 todo 的 `anchor`、`ref`(`<path>#<heading>`,解析成行號範圍)和 `followup` metadata。每個開啟中的 loop 有一份 YAML book,放在 `~/.pi/agent/anchorloop/books/`(可用 `ANCHORLOOP_BOOKS_DIR` 覆寫),subagent 的 book 放在 `agents/`(subagent 自己 close 時 book 留給主 session);`session_start` 會清掉 session 已不存在或 7 天沒有更新的 book。`close` 不會因為階段或 friction 而拒絕,只在還有 pending 或 in-progress 的 todo 沒有 `metadata.followup`(票號、`memory` 或 `drop — <原因>`)時拒絕。 |
29
- | `pi/anchor-gate.ts` | 只在開啟的 loop 內:`write` 或 `edit` 開始 `act` 階段、而這種 loop 前面的階段還沒完成時,顯示一行警告:沒有 anchor(kind 呼叫的 `anchor`、`Reality anchor:` 這一行,或已跳過),`durable` loop 則還缺決策、spec、全新 context 的閱讀、核准或設計(從 `docs/specs/<資料夾>/` 的檔案判斷,資料夾由 book、intent 或 todo 的 `ref` 找出)。只有新缺了一個階段時才會再警告;呼叫仍會放行。沒有 loop 或是 `quick` loop 時保持安靜。`.md`、`.txt`、`.html` 檔、`scratch/`、`docs/specs/` 和專案外的暫存檔不會觸發。透過 `bash` 做的修改不檢查。 |
30
- | `pi/decision-tool.ts` | `anchorloop_decision` 工具把一筆決策寫進 TSV 決策軌跡,每筆只佔一行,用試算表開啟也安全;接手別人的軌跡時寫一筆 `start`。 |
31
- | `pi/second-thought.ts` | 只在開啟的 loop 內:一次執行結束且 book 自上次判斷後多了條目時,以及宣告 `close` 時,透過 pi 的 classifier registry 請 Jev(TypeSafe 的型別化決策模型)複核階段機。下一個階段由程式從 book 算出;Jev 看到種類、intent、anchor、diff 摘要(≤1200 字元)、`phase`(目前、下一個、缺什麼)、verify 狀態、未完成 todo、任務清單和 friction(子 agent 的以它的名字列出),只判斷要 `advance`、退回(`back`)還是 `ask-user`,另外還問 `done`、還需要哪一級 review 與品質;階段是否完成只由程式依證據判定。一行提醒以 `next → <階段>` 開頭。只有 Jev 同意前進、而下一個階段是 verify、review 或 fix 時,才讓 agent 多跑一輪;與上一則相同的提醒不重複,最後一次修改之後已有無 blocking 發現的 review 時,`full` 評等只顯示備註、不要求複查。呼叫過 `MonitorCreate`、`LoopCreate`、`goal_wait` 或 `subagent` 工具的執行在等待中,不被判斷。沒有 TypeSafe key 時什麼都不做,沒有 loop 時不會執行。subagent session(pi-herdr-agents 的 `PI_SUBAGENT_ID`,或 pi-subagents 的 `PI_SUBAGENT_CHILD`)不註冊任何東西,因為由派它的 agent 判斷結果。 |
32
- | `pi/validate-tool-use.ts` | 每次 `write` 或 `edit` 後在行程內驗證該檔(`pi/support/validators.ts`,移植自 `hooks/validators/`),替 `SKILL.md`、指令檔(`AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`rules/`)和 `docs/specs/` 產出物附上建議。不需要 Python;`hooks/` 的 Python 驗證器仍供 `validate_all.py` 和對照測試使用。 |
33
- | `pi/ttsr.ts` | 移植自 oh-my-pi 的 Time Traveling Stream Rules。`~/.pi/agent/rules/` 或 `<專案>/.pi/rules/` 裡 frontmatter 帶有 `condition` regex 的 Markdown 規則,會在執行過程中被監看。assistant 串流文字命中時,中斷回應、丟棄那段輸出,並注入規則後重試;工具參數命中時(`bash` 看指令,`write`/`edit` 看新寫入的文字),直接擋下呼叫,以規則內容作為理由。frontmatter 欄位沿用 oh-my-pi:`condition`、`scope`(`text`、`thinking`、`tool`、`tool:bash`、`tool:edit(*.ts)`)、`globs`、`interruptMode`(`always`、`prose-only`、`tool-only`、`never`),另加 `repeatMode`(`once` 或 `after-gap`)與 `repeatGap`。預設每條規則每個 session 只觸發一次。不支援 `astCondition`、`question`、`agents`,用到的規則會被跳過並回報。`/ttsr` 列出已載入的規則。 |
28
+ | `pi/book.ts` | Loop book。第一次 `todo` create 會以 `inline` 開啟 loop(intent 取第一個 todo 的標題,anchor 取 `Reality anchor:` 這一行或該 todo 的 `metadata.anchor`),`kind` 呼叫也能開啟或更改 loop;任務紀錄需要 [`@juicesharp/rpiv-todo`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo#readme) 的 `todo` 工具(它的 `tool_result` details `{tasks, nextId}` 和 `rpiv-todo-state` 條目)。agent 只用 `anchorloop_stage` 宣告 extension 看不到的事:`kind`(用來選 `quick`、`reviewed` 或 `durable`,附 intent 和 anchor 指令;改掉 agent 自己選的種類時,必須附 `reason`)、`verify`(不是指令的檢查)、`fresh-read`、`friction`(`tried`、`found`、`led_by`)、`reflect`、`skip`、`back`、`close`,以及查詢用的 `read`。其餘都從證據讀出:production 的修改(任何 git repo 內、`scratch/` 與 `docs/specs/` 以外的 write 或 edit;已連結 child 的修改,以及成功的 `git merge`、`cherry-pick`、`pull` 也算)、執行過 anchor 指令的那次 bash 的 exit code(anchor 通過後失敗、又再通過時,extension 會自己記一筆 friction,並直接 reflect 為 `resolved`:重跑的指令,加上失敗那次最關鍵的一行輸出,最多 120 字元,不放 stack trace;不會通知 agent)、reviewer 的 `Blocking: P0=<n> P1=<m>` 數字、`docs/specs/*/` 產出物每次編輯後的摘要,以及 Jev 判定為「全新 context 讀過 spec」的 subagent 回報。階段機依這些證據給每種 loop 固定的階段順序(`quick` 為 act → check;`inline` 為 anchor → act → verify;`reviewed` 加 review;`durable` 再加 decide → grill → spec → read → approve → design;每一種都以 reflect → close 結尾),`read` 顯示 `<種類> · phase <目前> ✓ → <下一個>(缺:…)· 已跳過:…`。編輯器下方的 widget 會顯示 `⚓ <種類> · <目前> ✓ → <下一個>`,還有 friction 等著 `reflect` 時附 `· N friction`;每次工具呼叫後、`session_start` 時,以及 agent 寫出 `Reality anchor:` 這一行時更新,loop 結案後清掉。`quick` 或 `inline` loop 跑 anchor 或宣告 verify 時,也會量 diff 的影響範圍:原始碼檔案(不含測試、lockfile、spec 與文字檔)、它們所在的頂層目錄,以及跟原始碼一起改的 README、changelog 或 `*.schema.json`。repo 加入 loop 時就已經改過的檔案,要等內容再變動才算;repo 根目錄的檔案只算檔案數,不算一個目錄。達到 3 個原始碼檔案、2 個目錄或 1 個這類檔案時,結果會附一行請 agent 重新分類;verify 要等到以 `reason` 呼叫 `kind`、回應每一個跨過的門檻才算完成,之後再跨過新的門檻會再問一次。前一個階段還沒完成就開始下一個階段,會用一行警告一次;證據改變或用 `back` 時階段會退回。book 也記錄每個 todo 的 `anchor`、`ref`(`<path>#<heading>`,解析成行號範圍)和 `followup` metadata。每個開啟中的 loop 有一份 YAML book,放在 `~/.pi/agent/anchorloop/books/`(可用 `ANCHORLOOP_BOOKS_DIR` 覆寫),subagent 的 book 放在 `agents/`(subagent 自己 close 時 book 留給主 session)。子代理只透過 [pi-herdr-agents](https://github.com/wayne930242/pi-herdr-agents) 關聯(子行程的 `PI_SUBAGENT_ID`、父 session `subagent` 結果的 `details.id`;pi 本身沒有 subagent):關聯的子代理,其改動算父 loop 的 act;其 anchor 執行記為 `check`,並附上它所在各 repo 的工作狀態 tree(含已追蹤與未追蹤檔案,不含 ignored 檔案與 `scratch/`)。父 session 的 `git merge`、`cherry-pick`、`pull` 成功時,或收到子代理結果時,若父 session 的工作狀態 tree 正好等於子代理最近一次同一 anchor 執行(忽略開頭切到 repo 根目錄的 `cd`)的 tree,且那次通過,就把它記為父 loop 自己的 verify(`adopted`):fast-forward 合入 worker 分支、或子代理直接在父 checkout 裡工作,都不必再跑一次;合到已前進的 base、父 session 有子代理沒看過的改動或 repo、或子代理跑測試時有沒 commit 的檔案,則要自己跑;`session_start` 會清掉 session 已不存在或 7 天沒有更新的 book。`close` 回覆 `closed (landed). Clear the todo list.`,只多列沒有完成的階段和還沒 reflect 的 friction;它不會因為階段或 friction 而拒絕,只在還有 pending 或 in-progress 的 todo 沒有 `metadata.followup`(票號、`memory` 或 `drop — <原因>`)時拒絕。 |
29
+ | `pi/anchor-gate.ts` | 只在開啟的 loop 內:`write` 或 `edit` 開始 `act` 階段、而這種 loop 前面的階段還沒完成時,把一行警告附在該次編輯的 tool result 後面(agent 在那裡讀到它):沒有 anchor(kind 呼叫的 `anchor`、`Reality anchor:` 這一行,或已跳過),`durable` loop 則還缺決策、spec、全新 context 的閱讀、核准或設計(從 `docs/specs/<資料夾>/` 的檔案判斷,資料夾由 book、intent 或 todo 的 `ref` 找出)。只有新缺了一個階段時才會再警告;呼叫仍會放行。沒有 loop 或是 `quick` loop 時保持安靜。`.md`、`.txt`、`.html` 檔、`scratch/`、`docs/specs/` 和專案外的暫存檔不會觸發。透過 `bash` 做的修改不檢查。 |
30
+ | `pi/decision-tool.ts` | `anchorloop_decision` 工具把一筆決策寫進 TSV 決策軌跡(記岔路、轉向與卡點;完成的工作單元看 `git log`),每筆只佔一行,用試算表開啟也安全;接手別人的軌跡時寫一筆 `start`。 |
31
+ | `pi/second-thought.ts` | 只在開啟的 loop 內:一次執行結束且 book 自上次判斷後多了條目時,以及宣告 `close` 時,透過 pi 的 classifier registry 請 Jev(TypeSafe 的型別化決策模型)複核階段機。下一個階段由程式從 book 算出;Jev 看到種類、intent、anchor、diff 摘要(≤1200 字元)、`phase`(目前、下一個、缺什麼)、verify 狀態、未完成 todo(id 與標題)、任務清單和 friction(子 agent 的以它的名字列出),只判斷要 `advance`、退回(`back`)還是 `ask-user`,另外還問 `done`、還需要哪一級 review 與品質;階段是否完成只由程式依證據判定。提醒是 `next: <階段>(<缺什麼>)` 加上 Jev 的疑慮,不附分數;只在 Jev 有疑慮、有 todo 未完成而沒有 followup、或這次會讓 agent 多跑一輪時才送出,Jev 同意且沒有補充時保持安靜(判斷結果仍記在 session 裡);結案時只附疑慮。離開 close 時,提醒也會點名 loop 中還沒有有效 `metadata.followup` 的未完成 todo(前三個,標題截成 60 字元,其餘以 `+N more` 帶過),請 agent 逐一繼續、完成,或設定該 followup。Jev 同意前進、而下一個階段是 verify、review 或 fix,或有這類 todo 未完成且 Jev 的判斷不是 `ask-user`(此時提醒改請 agent 去問使用者)時,才讓 agent 多跑一輪;下一個階段、疑慮和未完成 todo 的 id 都與上一則相同的提醒不重複;最後一次修改之後已有無 blocking 發現的 review 時,`full` 評等不算疑慮。呼叫過 `MonitorCreate`、`LoopCreate`、`goal_wait` 或 `subagent` 工具的執行在等待中,不被判斷。沒有 TypeSafe key 時什麼都不做,沒有 loop 時不會執行。subagent session(pi-herdr-agents 的 `PI_SUBAGENT_ID`,或 pi-subagents 的 `PI_SUBAGENT_CHILD`)不註冊任何東西,因為由派它的 agent 判斷結果。 |
32
+ | `pi/validate-tool-use.ts` | 每次 `write` 或 `edit` 後在行程內驗證該檔(`pi/support/validators.ts`,移植自 `hooks/validators/`),替 `SKILL.md`(每次編輯最多三則紅線提醒)、指令檔(`AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`rules/`)和 `docs/specs/` 產出物附上建議。不需要 Python;`hooks/` 的 Python 驗證器仍供 `validate_all.py` 和對照測試使用。 |
33
+ | `pi/ttsr.ts` | 移植自 oh-my-pi 的 Time Traveling Stream Rules。`~/.pi/agent/rules/` 或 `<專案>/.pi/rules/` 裡 frontmatter 帶有 `condition` regex 的 Markdown 規則,會在執行過程中被監看。assistant 串流文字命中時,中斷回應、丟棄那段輸出,並注入規則後重試;工具參數命中時(`bash` 看指令,`write`/`edit` 看新寫入的文字),直接擋下呼叫,以規則內容作為理由。frontmatter 欄位沿用 oh-my-pi:`condition`、`scope`(`text`、`thinking`、`tool`、`tool:bash`、`tool:edit(*.ts)`)、`globs`、`interruptMode`(`always`、`prose-only`、`tool-only`、`never`),另加 `repeatMode`(`once` 或 `after-gap`)與 `repeatGap`。注入的規則以 `<rule name="…">內容</rule>` 送出。預設每條規則每個 session 只觸發一次。不支援 `astCondition`、`question`、`agents`,用到的規則會被跳過並回報。`/ttsr` 列出已載入的規則。 |
34
34
 
35
35
  ## 安裝
36
36
 
@@ -72,7 +72,7 @@ def main() -> None:
72
72
  rel = target_path
73
73
 
74
74
  lines = "\n".join(f" - {w}" for w in warnings)
75
- msg = f"⚠ [anchorloop] Validation suggestions for {rel}:\n{lines}"
75
+ msg = f"validator {rel}:\n{lines}"
76
76
  print(json.dumps({"additionalContext": msg}))
77
77
  else:
78
78
  print(json.dumps({}))
@@ -29,6 +29,7 @@ RED_LINE_MARKERS = (
29
29
  r"\b(never|must not|do not|don't|forbidden|barred|prohibited|avoid)\b"
30
30
  r"|不要|禁止|不得|不准|嚴禁"
31
31
  )
32
+ MAX_ADVISORIES = 3
32
33
  RED_LINE_MIN_BUDGET = 3
33
34
  RED_LINE_DENSITY = 0.10
34
35
  # Emphatic capitals are matched case-sensitively and need a reason on the same or next line.
@@ -8,7 +8,7 @@ from .utils import find_defensive_phrases
8
8
 
9
9
 
10
10
  def check_decision_md(path: Path) -> list[str]:
11
- """Check decision.md for required structure and pre-flight skill/rule readiness."""
11
+ """Check decision.md for its decision table."""
12
12
  warnings: list[str] = []
13
13
  text = path.read_text(encoding="utf-8")
14
14
 
@@ -16,18 +16,11 @@ def check_decision_md(path: Path) -> list[str]:
16
16
  if not re.search(r"\|\s*Question\s*\|\s*Answer\s*\|\s*Basis\s*\|\s*Status\s*\|", text, re.IGNORECASE):
17
17
  warnings.append("decision.md should include a Question | Answer | Basis | Status table (Status: grounded, confirmed, or open).")
18
18
 
19
- # Check for pre-flight readiness (core rules)
20
- lower = text.lower()
21
- if "rule" not in lower and "readiness" not in lower and "prerequisite" not in lower and "core" not in lower:
22
- warnings.append(
23
- "confirm core rules readiness before implementation; note readiness in decision.md."
24
- )
25
-
26
19
  return warnings
27
20
 
28
21
 
29
22
  def check_spec_md(path: Path) -> list[str]:
30
- """Check spec.md for approval status and reality anchor definition."""
23
+ """Check spec.md for its approval status."""
31
24
  warnings: list[str] = []
32
25
  text = path.read_text(encoding="utf-8")
33
26
 
@@ -35,10 +28,6 @@ def check_spec_md(path: Path) -> list[str]:
35
28
  if not re.search(r"Status:\s*(proposed|approved)", text, re.IGNORECASE):
36
29
  warnings.append("spec.md must declare 'Status: proposed' or 'Status: approved' at the top.")
37
30
 
38
- # Reality anchor declaration
39
- if "reality anchor" not in text.lower() and "anchor" not in text.lower():
40
- warnings.append("spec.md should name the chosen reality anchor and its checkpoint.")
41
-
42
31
  return warnings
43
32
 
44
33
 
@@ -62,16 +51,14 @@ def check_verification_md(path: Path) -> list[str]:
62
51
  ]
63
52
  if invalid_results:
64
53
  warnings.append(
65
- f"invalid verification Result values found: {', '.join(set(invalid_results))}. "
66
- "Result must be one of: pass, fail, unknown."
54
+ f"invalid verification Result values found: {', '.join(set(invalid_results))} (use pass, fail, unknown)"
67
55
  )
68
56
 
69
57
  # 2. Review of newly added rules or skills (tuning vs defensive vocabulary)
70
58
  found_defensive = find_defensive_phrases(text, DEFENSIVE_PATTERNS)
71
59
  if found_defensive:
72
60
  warnings.append(
73
- f"detected defensive wording in verification/review: {', '.join(found_defensive)}. "
74
- "Reviews of new rules and skills must focus on adjustment and tuning, not adding defensive red lines."
61
+ f"detected defensive wording in verification/review: {', '.join(found_defensive)}"
75
62
  )
76
63
 
77
64
  return warnings
@@ -18,16 +18,14 @@ def check_rules_file(path: Path) -> list[str]:
18
18
  found_patterns = find_defensive_phrases(text, DEFENSIVE_PATTERNS)
19
19
  if found_patterns:
20
20
  warnings.append(
21
- f"detected defensive wording: {', '.join(found_patterns)}. "
22
- "State desired behavior directly; modern models do not need excessive red lines."
21
+ f"detected defensive wording: {', '.join(found_patterns)}"
23
22
  )
24
23
 
25
24
  # 2. Rule proliferation / bloat check
26
25
  lines = [line.strip() for line in text.splitlines() if line.strip() and not line.strip().startswith("#")]
27
26
  if len(lines) > 200:
28
27
  warnings.append(
29
- f"rule file has {len(lines)} non-empty lines. Consider consolidating into higher-level principles "
30
- "or separating into specialized modules rather than accumulating micro-rules."
28
+ f"rule file has {len(lines)} non-empty lines (over 200)"
31
29
  )
32
30
 
33
31
  # 3. Check for Simplified Chinese common distinctive characters
@@ -36,7 +34,7 @@ def check_rules_file(path: Path) -> list[str]:
36
34
  found_sc = [c for c in simplified_chinese_chars if c in text]
37
35
  if found_sc:
38
36
  warnings.append(
39
- f"possible Simplified Chinese detected ({''.join(found_sc[:5])}). Always use Traditional Chinese."
37
+ f"possible Simplified Chinese detected ({''.join(found_sc[:5])})"
40
38
  )
41
39
 
42
40
  return warnings
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from .constants import (
8
8
  DEFENSIVE_PATTERNS,
9
+ MAX_ADVISORIES,
9
10
  EMPHATIC_WORDS,
10
11
  REASON_WORDS,
11
12
  RED_LINE_DENSITY,
@@ -63,8 +64,7 @@ def check_skill_md(path: Path) -> list[str]:
63
64
  has_references = (skill_dir / "references").is_dir()
64
65
  if not has_references:
65
66
  warnings.append(
66
- f"skill has {len(lines)} lines (exceeds recommended {SKILL_MAX_RECOMMENDED_LINES}). "
67
- "Move detailed guidelines or templates to references/ to keep SKILL.md streamlined."
67
+ f"skill has {len(lines)} lines (over {SKILL_MAX_RECOMMENDED_LINES}) and no references/"
68
68
  )
69
69
 
70
70
  # 3. Broken markdown links, followed through every Markdown file SKILL.md reaches
@@ -98,8 +98,7 @@ def check_skill_md(path: Path) -> list[str]:
98
98
  found_patterns = find_defensive_phrases(text, DEFENSIVE_PATTERNS)
99
99
  if found_patterns:
100
100
  warnings.append(
101
- f"detected defensive anti-pattern(s): {', '.join(found_patterns)}. "
102
- "State expected behavior directly rather than constructing defensive red lines."
101
+ f"detected defensive anti-pattern(s): {', '.join(found_patterns)}"
103
102
  )
104
103
 
105
104
  return warnings
@@ -120,10 +119,7 @@ def check_skill_advisories(path: Path) -> list[str]:
120
119
  markers = len(re.findall(RED_LINE_MARKERS, body, re.IGNORECASE))
121
120
  budget = max(RED_LINE_MIN_BUDGET, int(sum(1 for line in lines if line.strip()) * RED_LINE_DENSITY))
122
121
  if markers > budget:
123
- advisories.append(
124
- f"{markers} red-line markers exceed the budget of {budget}. Keep a red line only when an "
125
- "observed failure backs it, and delete the rest."
126
- )
122
+ advisories.append(f"{markers} red-line markers exceed the budget of {budget}")
127
123
 
128
124
  # 2. Reasoned: an emphatic word carries its reason on the same or next line.
129
125
  # 3. Once: an emphatic word is spent a single time.
@@ -133,12 +129,9 @@ def check_skill_advisories(path: Path) -> list[str]:
133
129
  seen[word] += 1
134
130
  window = " ".join(lines[number - 1 : number + 1])
135
131
  if not re.search(REASON_WORDS, window, re.IGNORECASE):
136
- advisories.append(
137
- f"line {number}: emphatic '{word}' has no reason beside it. "
138
- "State the consequence, or drop the emphasis."
139
- )
132
+ advisories.append(f"line {number}: emphatic '{word}' has no reason beside it")
140
133
  for word, count in seen.items():
141
134
  if count > 1:
142
- advisories.append(f"emphatic '{word}' appears {count} times. Spend emphasis once, on the rule that causes real damage.")
135
+ advisories.append(f"emphatic '{word}' appears {count} times")
143
136
 
144
- return advisories
137
+ return advisories[:MAX_ADVISORIES]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-anchorloop",
3
- "version": "2.4.1",
3
+ "version": "2.7.0",
4
4
  "description": "Agent workflow skills for pi: one entry skill per task type, each with playbooks, over a shared Align → Anchor → Act → Verify loop, plus loop extensions (loop book, anchor gate, decision trail tool, Jev second thought, skill validator, stream rules).",
5
5
  "author": "Wei Hung",
6
6
  "license": "MIT",
package/pi/anchor-gate.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  * pi extension: inside an open loop, warns once per jump when write or edit starts the `act` phase while an
3
3
  * earlier phase of the loop kind is incomplete: no anchor, or in a durable loop a decision, spec, read,
4
4
  * approval, or design still missing. The phase machine in support/phases.ts decides what is missing; the
5
- * warning names it and the `skip` escape. Outside a loop, or in a `quick` one, it stays silent.
5
+ * warning is one line appended to the edit's tool result, which is the only place the agent reads it.
6
+ * Outside a loop, or in a `quick` one, it stays silent.
6
7
  */
7
8
 
8
9
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -11,6 +12,8 @@ import { JumpMemory, computePhases, exempt, incompleteBefore, jumpLine } from ".
11
12
 
12
13
  export default function (pi: ExtensionAPI) {
13
14
  const jumps = new JumpMemory();
15
+ // The warning each write or edit call earned, held until its result arrives.
16
+ const pending = new Map<string, string>();
14
17
 
15
18
  pi.on(
16
19
  "tool_call",
@@ -22,7 +25,17 @@ export default function (pi: ExtensionAPI) {
22
25
  if (!book || exempt(filePath, ctx.cwd) || currentKind(book.entries) === "quick") return;
23
26
  const phases = computePhases(book, { cwd: ctx.cwd, todos: replayTodos(ctx.sessionManager.getBranch()) });
24
27
  const missing = incompleteBefore(phases, "act");
25
- if (jumps.fresh(String(book.header.id), "act", missing.map((status) => status.name))) ctx.ui.notify(jumpLine("act", missing), "warning");
28
+ if (jumps.fresh(String(book.header.id), "act", missing.map((status) => status.name))) pending.set(event.toolCallId, jumpLine("act", missing));
29
+ }),
30
+ );
31
+
32
+ pi.on(
33
+ "tool_result",
34
+ guarded((event) => {
35
+ const line = pending.get(event.toolCallId);
36
+ if (line === undefined) return;
37
+ pending.delete(event.toolCallId);
38
+ return { content: [...event.content, { type: "text" as const, text: line }] };
26
39
  }),
27
40
  );
28
41
  }