@gavin-hu/my-pi-agent 0.16.0 → 0.17.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.
Files changed (135) hide show
  1. package/README.md +14 -8
  2. package/README.zh-CN.md +14 -9
  3. package/extensions/bytefly/README.md +174 -101
  4. package/extensions/bytefly/footer/collect/entries.ts +1 -6
  5. package/extensions/bytefly/footer/collect/snapshot.ts +23 -9
  6. package/extensions/bytefly/footer/price/price.ts +12 -8
  7. package/extensions/bytefly/footer/register.ts +13 -1
  8. package/extensions/bytefly/footer/types.ts +11 -0
  9. package/extensions/bytefly/footer/unpriced.ts +36 -0
  10. package/extensions/bytefly/header/component.ts +15 -29
  11. package/extensions/bytefly/header/identity.ts +71 -0
  12. package/extensions/bytefly/header/register.ts +9 -3
  13. package/extensions/bytefly/index.ts +13 -5
  14. package/extensions/bytefly/message/format.ts +20 -13
  15. package/extensions/bytefly/message/register.ts +8 -7
  16. package/extensions/bytefly/panel/commands.ts +46 -25
  17. package/extensions/bytefly/panel/format.ts +49 -14
  18. package/extensions/bytefly/panel/join.ts +35 -9
  19. package/extensions/bytefly/panel/settings.ts +10 -27
  20. package/extensions/bytefly/panel/skills.ts +51 -0
  21. package/extensions/bytefly/panel/tui/extensions-view.ts +7 -61
  22. package/extensions/bytefly/panel/tui/frame.ts +9 -1
  23. package/extensions/bytefly/panel/tui/list-view.ts +72 -0
  24. package/extensions/bytefly/panel/tui/nav.ts +71 -3
  25. package/extensions/bytefly/panel/tui/settings-section.ts +250 -0
  26. package/extensions/bytefly/panel/tui/skills-view.ts +50 -0
  27. package/extensions/bytefly/panel/tui.ts +223 -331
  28. package/extensions/bytefly/panel/types.ts +26 -8
  29. package/extensions/bytefly/picker/discovery.ts +32 -27
  30. package/extensions/bytefly/picker/resources.ts +16 -8
  31. package/extensions/bytefly/picker/runtime.ts +62 -34
  32. package/extensions/bytefly/picker/toggle.ts +16 -10
  33. package/extensions/bytefly/picker/types.ts +31 -13
  34. package/extensions/bytefly/update/format.ts +66 -8
  35. package/extensions/bytefly/update/patch.ts +66 -0
  36. package/extensions/bytefly/update/register.ts +4 -11
  37. package/extensions/bytefly/update/release.ts +65 -0
  38. package/extensions/bytefly/update/state.ts +14 -0
  39. package/extensions/bytefly/update/suppress.ts +47 -29
  40. package/extensions/bytefly/update/types.ts +15 -5
  41. package/extensions/check/tui.ts +2 -0
  42. package/extensions/context/README.md +21 -11
  43. package/extensions/context/format.ts +10 -8
  44. package/extensions/context/report.ts +21 -8
  45. package/extensions/context/tui.ts +26 -10
  46. package/extensions/context/types.ts +5 -3
  47. package/extensions/file-browse/README.md +29 -1
  48. package/extensions/file-browse/client.ts +16 -463
  49. package/extensions/file-browse/html.ts +7 -5
  50. package/extensions/file-browse/palette.ts +224 -0
  51. package/extensions/file-browse/router.ts +2 -2
  52. package/extensions/file-browse/styles.ts +8 -3
  53. package/extensions/file-browse/tree.ts +381 -0
  54. package/extensions/file-read/README.md +4 -1
  55. package/extensions/file-read/extract/odt.ts +7 -5
  56. package/extensions/goal/tools.ts +6 -11
  57. package/extensions/job/index.ts +20 -45
  58. package/extensions/job/tools.ts +51 -77
  59. package/extensions/memory/tools.ts +4 -6
  60. package/extensions/memory/tui.ts +5 -1
  61. package/extensions/plan/README.md +88 -33
  62. package/extensions/plan/commands.ts +19 -4
  63. package/extensions/plan/config.ts +27 -0
  64. package/extensions/plan/plan-index.ts +3 -1
  65. package/extensions/plan/plans.ts +31 -13
  66. package/extensions/plan/review.ts +34 -14
  67. package/extensions/plan/runtime.ts +3 -1
  68. package/extensions/plan/steps.ts +43 -14
  69. package/extensions/plan/tools/cancel.ts +79 -0
  70. package/extensions/plan/tools/enter.ts +10 -10
  71. package/extensions/plan/tools/exit.ts +54 -43
  72. package/extensions/plan/tools/steps.ts +7 -19
  73. package/extensions/plan/tools/write.ts +22 -29
  74. package/extensions/plan/tools.ts +5 -2
  75. package/extensions/plan/tui.ts +9 -7
  76. package/extensions/plan/types.ts +8 -0
  77. package/extensions/plan/web-chrome.ts +101 -0
  78. package/extensions/plan/web-client.ts +98 -311
  79. package/extensions/plan/web-markdown.ts +222 -0
  80. package/extensions/plan/web-page.ts +28 -2
  81. package/extensions/plan/web-router.ts +2 -2
  82. package/extensions/rewind/checklist.ts +4 -0
  83. package/extensions/rewind/review-tui.ts +3 -1
  84. package/extensions/rewind/rewind-tui.ts +4 -0
  85. package/extensions/subagent/README.md +25 -12
  86. package/extensions/subagent/format.ts +72 -25
  87. package/extensions/subagent/index.ts +21 -47
  88. package/extensions/subagent/orchestrate.ts +18 -41
  89. package/extensions/subagent/render.ts +29 -42
  90. package/extensions/subagent/stream.ts +24 -18
  91. package/extensions/subagent/types.ts +7 -0
  92. package/extensions/todo/tools.ts +5 -6
  93. package/extensions/todo/tui.ts +5 -1
  94. package/extensions/watch/index.ts +10 -35
  95. package/extensions/watch/tools.ts +31 -59
  96. package/extensions/wechat/README.md +40 -18
  97. package/extensions/wechat/bridge.ts +38 -40
  98. package/extensions/wechat/client.ts +1 -4
  99. package/extensions/wechat/commands.ts +1 -1
  100. package/extensions/wechat/interaction.ts +25 -4
  101. package/extensions/wechat/login-screen.ts +2 -3
  102. package/extensions/wechat/notices.ts +7 -5
  103. package/extensions/wechat/prompt.ts +19 -4
  104. package/extensions/wechat/qrcode.ts +9 -9
  105. package/extensions/wechat/slash.ts +14 -0
  106. package/extensions/wechat/status-screen.ts +2 -2
  107. package/extensions/wechat/tools.ts +3 -11
  108. package/extensions/worktree/README.md +48 -12
  109. package/extensions/worktree/commands.ts +109 -65
  110. package/extensions/worktree/enter.ts +1 -1
  111. package/extensions/worktree/exit.ts +11 -1
  112. package/extensions/worktree/lifecycle.ts +9 -1
  113. package/extensions/worktree/screen.ts +263 -0
  114. package/extensions/worktree/status.ts +84 -38
  115. package/lib/README.md +7 -6
  116. package/lib/config.ts +12 -0
  117. package/lib/format.ts +6 -2
  118. package/lib/interaction.ts +18 -1
  119. package/lib/report.ts +75 -2
  120. package/lib/tool-result.ts +54 -4
  121. package/lib/tui/README.md +16 -8
  122. package/lib/tui/chrome.ts +27 -6
  123. package/lib/tui/dock-screen.ts +6 -2
  124. package/lib/tui/gauge.ts +9 -5
  125. package/lib/usage.ts +26 -0
  126. package/lib/web/README.md +131 -0
  127. package/lib/web/base.ts +19 -2
  128. package/lib/web/components.ts +10 -6
  129. package/lib/web/layout.ts +2 -0
  130. package/lib/web/markup.ts +2 -1
  131. package/lib/web/runtime.ts +103 -25
  132. package/lib/web/tokens.ts +23 -7
  133. package/package.json +3 -1
  134. package/skills/changelog/SKILL.md +57 -45
  135. package/skills/next/SKILL.md +184 -0
@@ -10,10 +10,16 @@
10
10
  /** localStorage key for the browser's explicit light/dark choice, shared by both surfaces. */
11
11
  export const WEB_THEME_STORAGE_KEY = "pi-web-theme";
12
12
 
13
+ /**
14
+ * Where both routers serve {@link THEME_JS}. `docHead` emits the same path, so
15
+ * the script tag and the route cannot drift apart.
16
+ */
17
+ export const THEME_JS_PATH = "/theme.js";
18
+
13
19
  /**
14
20
  * A tiny head script that applies the saved theme before the body is painted,
15
- * so an explicit choice does not flash the system theme on load. Served as
16
- * `/theme.js` and shared by both pages.
21
+ * so an explicit choice does not flash the system theme on load. Served at
22
+ * {@link THEME_JS_PATH} and shared by both pages.
17
23
  */
18
24
  export const THEME_JS = `(function(){try{var t=localStorage.getItem("${WEB_THEME_STORAGE_KEY}");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t);}catch(e){}})();`;
19
25
 
@@ -67,52 +73,124 @@ export const THEME_TOGGLE_JS = String.raw`(function () {
67
73
  }
68
74
  })();`;
69
75
 
76
+ /** Options for {@link dialogScript}. */
77
+ export interface DialogOptions {
78
+ /** Id of the dialog root: the element carrying `.bf-dialog` and `hidden`. */
79
+ dialogId: string;
80
+ /** Id of the dialog's own close button, when the markup has one. */
81
+ closeId?: string;
82
+ /** Id of the page's trigger button, when the dialog opens from one. */
83
+ triggerId?: string;
84
+ /** Property to expose on `window`, so the page can open and close it. */
85
+ globalName?: string;
86
+ }
87
+
88
+ /** Everything inside a dialog that can take focus, in DOM order. */
89
+ const FOCUSABLE =
90
+ 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
91
+
70
92
  /**
71
- * The `?` keyboard sheet's wiring, shared by both clients.
72
- *
73
- * It owns the sheet's pointer behavior — the page's trigger button, the sheet's
74
- * own close button, and a click on the backdrop — and nothing else: each page
75
- * keeps its own keyboard policy in its keydown handler, because the two differ
76
- * (the review peels `Esc` through a composer first), and calls in through
77
- * `window.bfKeys`.
93
+ * The wiring every modal in the kit shares: open and close, the backdrop, the
94
+ * `Tab` trap, `Esc`, and focus returning to whatever opened it.
78
95
  *
79
- * `triggerId` is the page's own shortcut button, since browse opens the sheet
80
- * from `#keys-open` and review from `#shortcuts`. The sheet's ids are the fixed
81
- * contract in `keysSheet` (`lib/web/markup.ts`).
96
+ * A dialog is modal before this script runs too — `aria-modal` and `hidden` are
97
+ * in the markup — so a page without JavaScript still shows it closed rather
98
+ * than half-open.
82
99
  *
83
- * Value-only: the open state is the dialog's own `hidden` attribute, so this
84
- * module holds nothing.
100
+ * Value-only: the open state is the dialog's own `hidden` attribute, and the
101
+ * element that opened it lives in this script's closure.
85
102
  */
86
- export function keysDialogScript(triggerId: string): string {
103
+ export function dialogScript(options: DialogOptions): string {
104
+ const trigger = options.triggerId ? `document.getElementById("${options.triggerId}")` : "null";
105
+ const closer = options.closeId ? `document.getElementById("${options.closeId}")` : "null";
106
+ const expose = options.globalName
107
+ ? `window.${options.globalName} = {
108
+ open: function () { toggle(true); },
109
+ toggle: toggle,
110
+ close: function () { toggle(false); },
111
+ isOpen: isOpen
112
+ };`
113
+ : "";
87
114
  return `(function () {
88
115
  "use strict";
89
- var dialog = document.getElementById("keys-dialog");
116
+ var dialog = document.getElementById("${options.dialogId}");
90
117
  if (!dialog) return;
118
+ var opener = null;
91
119
 
92
120
  function isOpen() {
93
121
  return !dialog.hidden;
94
122
  }
123
+ // A button inside a closed dialog has no boxes, so the rects are the test that
124
+ // survives a hidden attribute, a collapsed panel, and a stylesheet swap alike.
125
+ function focusables() {
126
+ return Array.prototype.filter.call(dialog.querySelectorAll('${FOCUSABLE}'), function (node) {
127
+ return node.getClientRects && node.getClientRects().length > 0;
128
+ });
129
+ }
95
130
  function toggle(force) {
96
131
  var open = force === undefined ? dialog.hidden : force;
132
+ if (open === isOpen()) return;
97
133
  dialog.hidden = !open;
98
134
  if (open) {
99
- var close = document.getElementById("keys-close");
100
- if (close && close.focus) close.focus();
135
+ opener = document.activeElement;
136
+ var first = focusables()[0];
137
+ if (first) first.focus();
138
+ } else if (opener && opener.focus) {
139
+ opener.focus();
140
+ opener = null;
101
141
  }
102
142
  }
103
143
 
104
- var trigger = document.getElementById("${triggerId}");
144
+ var trigger = ${trigger};
105
145
  if (trigger) trigger.addEventListener("click", function () { toggle(); });
106
- var closer = document.getElementById("keys-close");
146
+ var closer = ${closer};
107
147
  if (closer) closer.addEventListener("click", function () { toggle(false); });
108
148
  dialog.addEventListener("click", function (event) {
109
149
  if (event.target === dialog) toggle(false);
110
150
  });
111
151
 
112
- window.bfKeys = {
113
- toggle: toggle,
114
- close: function () { toggle(false); },
115
- isOpen: isOpen
116
- };
152
+ // Tab cycles inside the dialog: leaving it would put focus behind the scrim,
153
+ // where the page looks inert but is not.
154
+ dialog.addEventListener("keydown", function (event) {
155
+ if (event.key !== "Tab") return;
156
+ var nodes = focusables();
157
+ if (nodes.length === 0) { event.preventDefault(); return; }
158
+ var first = nodes[0];
159
+ var last = nodes[nodes.length - 1];
160
+ if (event.shiftKey && document.activeElement === first) {
161
+ event.preventDefault();
162
+ last.focus();
163
+ } else if (!event.shiftKey && document.activeElement === last) {
164
+ event.preventDefault();
165
+ first.focus();
166
+ }
167
+ });
168
+
169
+ // Capture, so Esc closes the dialog before a page handler peels another layer.
170
+ document.addEventListener("keydown", function (event) {
171
+ if (event.key !== "Escape" || !isOpen()) return;
172
+ toggle(false);
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ }, true);
176
+
177
+ ${expose}
117
178
  })();`;
118
179
  }
180
+
181
+ /**
182
+ * The `?` keyboard sheet's wiring, shared by both clients.
183
+ *
184
+ * `triggerId` is the page's own shortcut button, since browse opens the sheet
185
+ * from `#keys-open` and review from `#shortcuts`. The sheet's ids are the fixed
186
+ * contract in `keysSheet` (`lib/web/markup.ts`), and the page's keydown handler
187
+ * calls in through `window.bfKeys`.
188
+ */
189
+ export function keysDialogScript(triggerId: string): string {
190
+ return dialogScript({
191
+ dialogId: "keys-dialog",
192
+ closeId: "keys-close",
193
+ triggerId,
194
+ globalName: "bfKeys",
195
+ });
196
+ }
package/lib/web/tokens.ts CHANGED
@@ -21,12 +21,11 @@ interface ThemeJson {
21
21
  const DARK = darkTheme as unknown as ThemeJson;
22
22
  const LIGHT = lightTheme as unknown as ThemeJson;
23
23
 
24
- /** Web custom property -> Bytefly theme var. `--accent-soft` is derived. */
24
+ /** Web custom property -> Bytefly theme var. `--accent-soft`, `--on-accent`, `--scrim`,
25
+ * and `--shadow` are derived, so no appearance-only colour enters the kit. */
25
26
  const VAR_TOKENS: ReadonlyArray<readonly [string, string]> = [
26
27
  ["--canvas", "canvas"],
27
28
  ["--surface", "canvasSubtle"],
28
- ["--surface-raised", "selectionBg"],
29
- ["--selected", "selectionBg"],
30
29
  ["--border", "borderMuted"],
31
30
  ["--border-strong", "border"],
32
31
  ["--text", "text"],
@@ -90,19 +89,34 @@ function rgba(hex: string, alpha: number): string {
90
89
  return `rgba(${r},${g},${b},${alpha})`;
91
90
  }
92
91
 
92
+ /**
93
+ * Overlays are not palette entries: a scrim dims what is behind it and a shadow
94
+ * lifts a panel off it, so each appearance needs its own alpha rather than the
95
+ * theme's colours.
96
+ */
97
+ const OVERLAYS: Record<"dark" | "light", { scrim: string; shadow: string }> = {
98
+ dark: { scrim: "rgba(0,0,0,.5)", shadow: "0 8px 24px rgba(0,0,0,.4)" },
99
+ light: { scrim: "rgba(15,23,42,.3)", shadow: "0 8px 24px rgba(15,23,42,.14)" },
100
+ };
101
+
93
102
  /** The full custom-property set for one appearance. */
94
- function paletteOf(theme: ThemeJson, accentAlpha: number): Record<string, string> {
103
+ function paletteOf(theme: ThemeJson, appearance: "dark" | "light", accentAlpha: number): Record<string, string> {
95
104
  const palette: Record<string, string> = {};
96
105
  for (const [token, name] of VAR_TOKENS) palette[token] = theme.vars[name];
97
106
  palette["--accent-soft"] = rgba(palette["--accent"] ?? "#000000", accentAlpha);
107
+ // A label on the accent fill is the canvas colour: a light blue button takes dark
108
+ // text, and the same derivation gives white on the light theme's darker blue.
109
+ palette["--on-accent"] = palette["--canvas"];
110
+ palette["--scrim"] = OVERLAYS[appearance].scrim;
111
+ palette["--shadow"] = OVERLAYS[appearance].shadow;
98
112
  for (const [token, name] of SYNTAX_TOKENS) palette[token] = resolve(theme, theme.colors[name] ?? "");
99
113
  return palette;
100
114
  }
101
115
 
102
116
  /** Every token value per appearance, for tests and for building the CSS. */
103
117
  export const WEB_THEME_TOKENS: { dark: Record<string, string>; light: Record<string, string> } = {
104
- dark: paletteOf(DARK, 0.15),
105
- light: paletteOf(LIGHT, 0.1),
118
+ dark: paletteOf(DARK, "dark", 0.15),
119
+ light: paletteOf(LIGHT, "light", 0.1),
106
120
  };
107
121
 
108
122
  /** `a{color:var(--tok-keyword)}…` for each syntax token. */
@@ -125,4 +139,6 @@ export const WEB_THEME_CSS = `:root{${declarations(WEB_THEME_TOKENS.dark)};${SCA
125
139
  :root[data-theme="light"]{${declarations(WEB_THEME_TOKENS.light)};color-scheme:light}
126
140
  :root[data-theme="dark"]{color-scheme:dark}
127
141
  @media (prefers-color-scheme:light){:root:not([data-theme]){${declarations(WEB_THEME_TOKENS.light)};color-scheme:light}}
128
- ${SYNTAX_RULES}`;
142
+ ${SYNTAX_RULES}
143
+ /* Paper is white: print the light palette whatever the screen is showing. */
144
+ @media print{:root{${declarations(WEB_THEME_TOKENS.light)};color-scheme:light}}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavin-hu/my-pi-agent",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Personal Pi customizations, packaged as a Pi package: worktree isolation and rewind snapshots, question, todo, goal, plan, subagent, job, watch, web-access, file-browse, file-open, file-read, check, memory, context, wechat, and bytefly.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -127,6 +127,7 @@
127
127
  "compat:versions": "bun run scripts/peer-versions.ts",
128
128
  "bench:render": "bun run scripts/render-bench.ts",
129
129
  "screenshot:file-browse": "bun run scripts/file-browse-shot.ts",
130
+ "screenshot:plan-flow": "bun run scripts/plan-flow-shot.ts",
130
131
  "screenshot:plan": "bun run scripts/plan-review-shot.ts",
131
132
  "screenshot:tools": "bun run scripts/tool-renderings.ts gallery assets/tool-renderings.png",
132
133
  "screenshot:tool-pngs": "bun run scripts/tool-renderings.ts tools assets/tools",
@@ -158,6 +159,7 @@
158
159
  "@types/bun": "latest",
159
160
  "@types/qrcode": "^1.5.5",
160
161
  "get-east-asian-width": "^1.6.0",
162
+ "playwright-core": "^1.64.1",
161
163
  "typebox": "^1.3.27",
162
164
  "typescript": "^5.6.0"
163
165
  }
@@ -1,48 +1,37 @@
1
1
  ---
2
2
  name: changelog
3
- description: "Maintain a Keep a Changelog file: draft the candidate bullet before implementation, record one user-visible bullet in the same commit, audit commits since the last tag for missing or weak entries, and curate a release section from the project's own rules. Use when planning, writing, or auditing a changelog or preparing a release, not for ordinary code edits."
3
+ description: "Maintain a Keep a Changelog file: record one user-visible bullet in the same commit, audit commits since the last tag for missing or weak entries, and curate a release section from the project's own rules. Use when writing, auditing, or releasing a changelog, not for ordinary code edits."
4
4
  ---
5
5
 
6
6
  # Maintain the changelog
7
7
 
8
8
  This is a procedure, not a second policy. Read the project's instructions first;
9
- where they disagree with this skill, the project wins. There are four modes:
10
- **draft** (before the code exists), **record** (the default once it does),
11
- **audit**, and **release**. `/skill:changelog draft`, `... audit`, and
12
- `... release` name the others; with no mode, infer draft versus record from
13
- whether the behavior already exists.
9
+ where they disagree with this skill, the project wins. There are three modes:
10
+ **record** (the default), **audit**, and **release**. `/skill:changelog` records;
11
+ `... audit` and `... release` name the others.
14
12
 
15
13
  ## 1. Read the project profile
16
14
 
17
- Before editing, find:
15
+ In this package the profile is written down; read it rather than rediscover it:
18
16
 
19
- - the changelog file and its preamble;
20
- - the category spelling and order;
21
- - the scope prefix, and whether one bullet per extension or module is required;
22
- - whether the entry belongs in the same commit as the change;
23
- - the release document, version bump, link refs, and tag/push/publish order;
24
- - the check command and any release-consistency test.
17
+ - [`../../CHANGELOG.md`](../../CHANGELOG.md) — the preamble, the scope prefixes,
18
+ and the bullet shape in use;
19
+ - [`../../AGENTS.md`](../../AGENTS.md) — the entry lands in the same commit as the
20
+ change, and a behavior change also updates the two entry READMEs and `docs/`;
21
+ - [`../../docs/development.md#release`](../../docs/development.md#release) — the
22
+ normative release procedure: version bump, link refs, tag and push order;
23
+ - [`../../test/release.test.ts`](../../test/release.test.ts) — the guard on
24
+ subsection names, order, emptiness, and the version pin.
25
+
26
+ Everywhere else, find the same six things: the changelog and its preamble, the
27
+ category spelling and order, the scope prefix and one-bullet-per-unit rule,
28
+ whether the entry ships with the change, the release document with its bump and
29
+ push order, and the check command with any release-consistency test.
25
30
 
26
31
  State the profile in one short paragraph. Do not invent a rule the project never
27
32
  stated; ask instead.
28
33
 
29
- ## 2. Draft before implementing
30
-
31
- For a new feature, draft the bullet in the plan or a scratchpad, not in
32
- `CHANGELOG.md`.
33
-
34
- - Write one line in the project's shape: ``- `scope`: <what the user can now do>``.
35
- - If you cannot state the outcome, or need several unrelated bullets, the feature
36
- is not scoped yet; split it or ask before coding.
37
- - Name the intended category and any migration, breaking change, or required
38
- action now, while the scope is still open.
39
- - Use the draft as the acceptance criterion: implementation is done when the
40
- behavior it describes is true and tested.
41
- - Keep it out of the changelog until the code lands; a draft is not a shipped
42
- claim. Update it when the outcome changes, then move it into `[Unreleased]` in
43
- the same commit as the code (**Record**).
44
-
45
- ## 3. Classify the change
34
+ ## 2. Classify the change
46
35
 
47
36
  It qualifies when a user can observe it: behavior, a CLI/tool/command surface, a
48
37
  config default, packaging, security, or a documented promise that is now true or
@@ -57,7 +46,7 @@ be one bullet if users see one outcome; two observable changes are two bullets.
57
46
  Ask when the outcome is unclear. Never invent an entry to make an audit look
58
47
  complete.
59
48
 
60
- ## 4. Record
49
+ ## 3. Record
61
50
 
62
51
  - Write the entry in the same commit as the change. If the change is already
63
52
  pushed without one, add a follow-up commit rather than rewriting history.
@@ -68,24 +57,40 @@ complete.
68
57
  - Exclude file names, internal helpers, test counts, commit hashes, and vague
69
58
  words such as "various" or "improvements".
70
59
  - Match the file's voice and tense. Do not add marketing.
60
+ - A behavior change also updates its row in the catalog tables of
61
+ [`../../README.md`](../../README.md) and
62
+ [`../../README.zh-CN.md`](../../README.zh-CN.md) plus the `docs/` guide it
63
+ belongs to; the changelog is this skill's subject, not its only file. A skill's
64
+ change updates its `SKILL.md` and its README row together.
71
65
  - Re-read immediately before editing, append within the category, and preserve
72
66
  other bullets and concurrent edits. Never create a second category heading.
73
67
 
68
+ Shape: one blank-line-separated bullet per outcome, wrapped near 80 columns, the
69
+ scope in backticks followed by a colon. The scope is the extension or module name,
70
+ spelled as this package spells it — `bytefly`, `plan`, `wechat`, `todo` — or
71
+ `lib`, `docs`, `test`, `ci`, `skills`. A bullet with no scope, or with a name the
72
+ package does not use, is a defect. A bullet may run several sentences — say what
73
+ the user sees, what it replaced, and any required action. Brevity is not the
74
+ goal; vagueness is the defect.
75
+
74
76
  Good:
75
77
 
76
78
  ```text
77
- - `billing`: invoices now show the tax line separately.
78
- - `config`: `oldKey` is no longer read; run `bun run config:migrate`.
79
+ - `config`: the report now says what its percentages are shares of. A
80
+ `Composition · 11k estimate` header sits above the rows, and a disabled
81
+ compaction no longer prints a headroom figure for a threshold that will never
82
+ fire.
83
+ - `cli`: `oldKey` is no longer read; run `bun run config:migrate`.
79
84
  ```
80
85
 
81
86
  Bad:
82
87
 
83
88
  ```text
84
- - `billing`: refactored invoice rendering and added tests.
89
+ - `config`: refactored invoice rendering and added tests.
85
90
  - `misc`: improvements.
86
91
  ```
87
92
 
88
- ## 5. Audit (read-only)
93
+ ## 4. Audit (read-only)
89
94
 
90
95
  1. Find the last tag: `git describe --tags --abbrev=0`, or the project's
91
96
  equivalent.
@@ -107,29 +112,36 @@ since the tag contain user-visible changes.
107
112
  Do not edit during the audit unless the user asks. If they do, return to
108
113
  **Record** and make only the entries the evidence supports.
109
114
 
110
- ## 6. Release (only when asked)
115
+ ## 5. Release (only when asked)
111
116
 
112
- Follow the project's release document; it is normative. This is a checklist, not
113
- a replacement:
117
+ The project's release document is normative — in this package
118
+ [`../../docs/development.md#release`](../../docs/development.md#release), guarded
119
+ by [`../../test/release.test.ts`](../../test/release.test.ts). This is a
120
+ checklist, not a replacement:
114
121
 
115
122
  - Confirm the version and the SemVer reason with the user.
116
123
  - Move every `[Unreleased]` entry under `## [X.Y.Z] - YYYY-MM-DD`; leave an empty
117
124
  `[Unreleased]` heading.
118
- - Bump every manifest and update the compare and release link refs.
119
- - Run the project's release checks.
120
- - Tag, push, and publish only with explicit approval, in the project's order.
125
+ - Bump every manifest and update the compare and release link refs, in the same
126
+ commit as the movable part; the version in the manifest and the newest dated
127
+ heading must agree or the release test fails.
128
+ - Run the project's release checks, then `bun test test/release.test.ts` for the
129
+ version and subsection rules.
130
+ - Tag, push, and publish only with explicit approval, in the project's order. The
131
+ order matters: push the tag with `main`, never after it.
121
132
  - Keep the release commit mechanical: no behavior, code, or unrelated docs.
122
- - Use that section as the release body; do not add extra prose.
133
+ - Use that section as the release body (`bun run release:notes X.Y.Z` prints it);
134
+ do not add extra prose.
123
135
 
124
- ## 7. Quality gate
136
+ ## 6. Quality gate
125
137
 
126
138
  - One bullet per user-visible change; every bullet starts with a scope and a user
127
- outcome.
139
+ outcome, and may take several sentences to state it.
128
140
  - No internal-only or duplicate entries, and no empty category headings.
129
141
  - No entry for a change that shipped before its heading.
130
142
  - Links and versions agree; the project check passes.
131
143
 
132
- ## 8. Report and stop
144
+ ## 7. Report and stop
133
145
 
134
146
  Say what was recorded, what the audit found, or what the release moved. List what
135
147
  was deliberately left out and why. When nothing qualifies, say so. Stop before
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: next
3
+ description: "Decide what to do next from a project's real state, or from the current conversation: loose work in the tree, commits since the last tag, unreleased changelog entries, roadmap or plan items still open, and the project's check command. Returns a short ranked list with the evidence for each item and the command that starts it; read-only, it never edits, commits, or begins the work. Use when asked what to do next, where to pick up, what is left, or what to work on now."
4
+ ---
5
+
6
+ # Decide what to do next
7
+
8
+ This is a triage, not a work session. The deliverable is a short ranked list with
9
+ evidence; do not edit, commit, tag, or start the top item. The skill is
10
+ model-invoked when a request matches the description; `/skill:next` is the
11
+ explicit fallback. Read the project's own instructions first; where they disagree
12
+ with this skill, the project wins.
13
+
14
+ Two sibling skills own a narrower question: an audit of *where to refactor* is
15
+ `refactor`, and *what the last tag owes the changelog* is `changelog`. When the
16
+ request is already scoped to one of those, name the skill that answers it and
17
+ hand over, instead of returning a generic list.
18
+
19
+ A next-steps block that the host draws itself is not this skill's output: that is
20
+ the reply's suggested follow-up, a turn-level hint. This skill inspects state, so
21
+ its list survives the conversation that produced it.
22
+
23
+ ## 1. Read the request
24
+
25
+ The host appends the text after the command to these instructions as the request.
26
+ It is `<prompt>` in `/skill:next <prompt>`, it comes before any reading, and it
27
+ carries up to three parts:
28
+
29
+ ```text
30
+ /skill:next [session] [what you want] [@path ...]
31
+ ```
32
+
33
+ - **`session`, first word** — session mode (section 3). The rest of the prompt
34
+ keeps its own meaning: `/skill:next session what is left?` is session mode with
35
+ a filter, not a filter of `session`.
36
+ - **What you want** — a question, a goal, or a topic: "what is left before
37
+ 0.17?", "plan file outline". It chooses what the shortlist is about and keeps
38
+ only the sources that can answer it; say which ones you kept. A named release,
39
+ file, or feature is the filter.
40
+ - **One or more paths** — `@src`, `src`, `packages/api`, or one inside the
41
+ sentence. Resolve each from the working directory, and take the evidence from
42
+ there: that path's diffs, plan steps, roadmap items, and changelog scope. A path
43
+ that does not exist is reported and asked about, never guessed.
44
+ - **Both** — the usual case. `/skill:next plan file outline @extensions/plan/`
45
+ asks what to do next about the plan file outline, with `@extensions/plan/` as
46
+ the evidence, so a roadmap item, a plan step, or a changelog entry outside that
47
+ path is not a candidate.
48
+ - **An imperative** — "fix the failing test" names one piece of work, and the
49
+ prompt is not approval for it. Answer with that item alone — evidence, starting
50
+ command, effort — and ask before starting, instead of producing a list.
51
+ - **No prompt** — triage of the current project.
52
+ - **Anything unreadable** — triage anyway, and say in one clause how you read the
53
+ prompt, so a misreading is visible in the report rather than silently narrowing
54
+ the list.
55
+
56
+ ## 2. Read the project profile
57
+
58
+ With the request read, find:
59
+
60
+ - the instruction files, and whether they require isolation (a worktree, a
61
+ branch) or a specific check;
62
+ - the check command — the one that proves the tree is green; the triage quotes it
63
+ and never runs it (section 4);
64
+ - where work is tracked: a roadmap, an issue tracker, `TODO` docs, a plans
65
+ directory, a changelog;
66
+ - the release path: what a tag, a version bump, or an unreleased entry owes;
67
+ - the VCS, its branch model, and the last tag if there is one.
68
+
69
+ State the profile in one or two lines. Do not invent a source the project does
70
+ not have; a project with no roadmap is triaged from git and the check alone.
71
+
72
+ ## 3. Choose the mode
73
+
74
+ - **Triage** — what the tree says. Use git, the changelog, the roadmap or issues,
75
+ and the plans directory. This is the default.
76
+ - **Session** — what this conversation left open. Read the conversation's own
77
+ record: the todo list, the goal, the approved plan and its ticked steps, what
78
+ subagents and jobs returned, and the diff this session produced. Then list the
79
+ loose ends it left: uncommitted files, an unapproved plan, a claim that was
80
+ never run, a documented behavior that changed without a doc or changelog edit.
81
+ - When both apply, run triage and read the session for context; the tree wins,
82
+ because it is where the work actually is.
83
+
84
+ Mode and scope are independent: session mode still honours a path, and triage
85
+ with a path reads the whole tree's evidence before narrowing. State the mode, the
86
+ scope, and the prompt as read, at the top of the report.
87
+
88
+ ## 4. Gather evidence
89
+
90
+ Read only; nothing in this stage writes. Follow the project's own tool
91
+ discipline, and say what you could not read.
92
+
93
+ | Source | What it proves |
94
+ |---|---|
95
+ | `git status --short`, `git diff --stat` | Work in flight, and whose it is |
96
+ | `git log --oneline <last-tag>..HEAD` | What has landed unreleased |
97
+ | `git worktree list`, `git branch --no-merged` | An isolated tree left behind, a branch never merged |
98
+ | The changelog's `[Unreleased]` | Whether a landed change is recorded, and whether the window is empty |
99
+ | The roadmap, issue tracker, or `TODO` docs | The project's stated priorities, and which are already done |
100
+ | The plans directory (`.pi/plans` or the project's equivalent) | An approved plan whose steps remain |
101
+ | The todo list, the goal, job and subagent results | What the session promised and did not finish |
102
+ | The check command | Whether the tree is green — cite the last known result, and make running it the item's starting command rather than running it here |
103
+
104
+ A roadmap, a changelog, and a plan all lag the tree. Verify a candidate against
105
+ the code before recommending it, and drop one that is already done.
106
+
107
+ ## 5. Verify and filter
108
+
109
+ A candidate is not a suggestion until it has one concrete artifact: a path with
110
+ line numbers, a command's output, a diff hunk, or a failing test name. If you
111
+ cannot point at it, drop it.
112
+
113
+ - Reject anything already done, already documented as a deliberate non-goal, or
114
+ blocked on a decision the user has not made — say which, and ask if it matters.
115
+ - Keep a bug separate from a preference, and a refactor separate from a fix.
116
+ - Prefer the candidate with a fast verification command.
117
+ - Never invent work to fill the list. An empty list is a valid answer.
118
+
119
+ ## 6. Rank by cost of loss
120
+
121
+ Order by what it would cost to lose, cheapest loss first:
122
+
123
+ - **Loose work** — a dirty tree, an unapproved plan, a check that has not been
124
+ run, an unmerged branch, a worktree left behind, a stopped job.
125
+ - **A broken promise** — a README, roadmap, or doc claim the code no longer
126
+ meets, a released behavior with no test.
127
+ - **An unfinished promise** — a stated roadmap item, the remaining steps of an
128
+ approved plan, a documented limitation with no item, an unreleased change with
129
+ no changelog entry.
130
+ - **New reach** — a capability nobody has asked for comes last, and only when the
131
+ three above are empty.
132
+
133
+ Label each item with the ceremony it needs. The label is not a second ordering:
134
+ cost of loss decides the order, and the label says how much work the item takes.
135
+
136
+ - **Do next** — cheap, safe, and unblocks the rest.
137
+ - **Worth planning** — high value, but it spans files, changes a public surface,
138
+ or needs a decision first.
139
+ - **Later** — real, but not now.
140
+
141
+ Cap the list at three to five items, and let a tier be empty. A roadmap restated
142
+ as ten suggestions is not a triage. Do not invent a numeric score; the reason
143
+ must be readable.
144
+
145
+ ## 7. Report
146
+
147
+ - **State** — one paragraph: the mode, the scope and how the prompt was read,
148
+ the branch or worktree, what is dirty, what is unreleased, and whether the
149
+ check passes. Name anything you could not read.
150
+ - **The shortlist** — each item with its label, the action, the evidence at
151
+ `path:line` or the command output that proves it, the command that starts it,
152
+ and effort (S/M/L). Link the file the item lives in.
153
+ - **Rejected or stale** — one line each, with the reason the roadmap, changelog,
154
+ or plan entry no longer applies.
155
+ - **Next step** — ask which item to start. Do not start it, and do not open with
156
+ an edit.
157
+
158
+ If the host expects a closing next-steps block, keep it to the top two or three
159
+ choices as one line each; the evidence stays in the list above, never repeated
160
+ there.
161
+
162
+ ## 8. Handoff
163
+
164
+ When the user picks an item, the audit is over:
165
+
166
+ - Restate the chosen item as a task with its acceptance test.
167
+ - Enter plan mode when the host provides it and the item spans files, changes a
168
+ public surface, or has an unknown step list. Track a small one in a todo list.
169
+ - Run the project's check first for a baseline, and finish with the full check.
170
+ - Do not commit unless the user asks.
171
+
172
+ ## 9. Stop conditions
173
+
174
+ - Nothing worth doing: say so, with what was checked, instead of inventing work.
175
+ - The tree is mid-rebase, mid-merge, mid-bisect, or has a conflict: reconciling it
176
+ is the only next item; nothing else is safe to suggest.
177
+ - The project has no work tracking: say the list is derived from git and the check
178
+ only, and suggest recording the plan or roadmap the project is missing.
179
+ - A candidate needs a destructive command (a branch delete, a worktree prune, a
180
+ history rewrite) to start: name it as a decision for the user, not as a step.
181
+
182
+ The triage is done when the mode is stated, every item has evidence and a
183
+ starting command, the list is capped, rejected candidates are explained, and no
184
+ file was modified.