@gavin-hu/my-pi-agent 0.20.0 → 0.22.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 +46 -30
- package/README.zh-CN.md +40 -25
- package/assets/screens/board.png +0 -0
- package/assets/screens/chrome.png +0 -0
- package/assets/screens/file-browse.png +0 -0
- package/assets/screens/plan-review.png +0 -0
- package/assets/screens/plan.png +0 -0
- package/docs/extensions/README.md +22 -23
- package/docs/extensions/board.md +397 -168
- package/docs/extensions/bytefly.md +234 -197
- package/docs/extensions/check.md +15 -17
- package/docs/extensions/context.md +35 -15
- package/docs/extensions/file-browse.md +57 -63
- package/docs/extensions/file-open.md +9 -11
- package/docs/extensions/file-read.md +12 -14
- package/docs/extensions/goal.md +15 -17
- package/docs/extensions/job.md +54 -42
- package/docs/extensions/memory.md +17 -19
- package/docs/extensions/plan.md +197 -140
- package/docs/extensions/question.md +16 -18
- package/docs/extensions/rewind.md +17 -19
- package/docs/extensions/serve.md +145 -0
- package/docs/extensions/session-bus.md +38 -16
- package/docs/extensions/subagent.md +23 -25
- package/docs/extensions/team.md +83 -44
- package/docs/extensions/todo.md +18 -18
- package/docs/extensions/watch.md +31 -31
- package/docs/extensions/web-access.md +2 -4
- package/docs/extensions/wechat.md +34 -35
- package/docs/extensions/worktree.md +56 -50
- package/extensions/board/commands.ts +79 -25
- package/extensions/board/format.ts +43 -24
- package/extensions/board/index.ts +17 -4
- package/extensions/board/input.ts +4 -9
- package/extensions/board/join.ts +5 -20
- package/extensions/board/lazy-page.ts +40 -0
- package/extensions/board/page.ts +65 -93
- package/extensions/board/result.ts +30 -39
- package/extensions/board/rows.ts +62 -25
- package/extensions/board/schema.ts +49 -11
- package/extensions/board/snapshot.ts +8 -6
- package/extensions/board/tone.ts +22 -0
- package/extensions/board/tools.ts +154 -55
- package/extensions/board/transport/router.ts +3 -5
- package/extensions/board/transport/server.ts +59 -28
- package/extensions/board/types.ts +36 -42
- package/extensions/board/view/card.ts +229 -0
- package/extensions/board/view/client.ts +35 -16
- package/extensions/board/view/dialog.ts +169 -6
- package/extensions/board/view/empty.ts +47 -0
- package/extensions/board/view/keys.ts +13 -2
- package/extensions/board/view/live.ts +90 -28
- package/extensions/board/view/names.ts +13 -4
- package/extensions/board/view/page.ts +38 -13
- package/extensions/board/view/render.ts +219 -277
- package/extensions/board/view/styles.ts +130 -32
- package/extensions/board/view/team.ts +297 -76
- package/extensions/bytefly/block/fallback.ts +1 -1
- package/extensions/bytefly/doctor/runtime.ts +5 -2
- package/extensions/bytefly/footer/collect/label.ts +60 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
- package/extensions/bytefly/footer/config.ts +7 -8
- package/extensions/bytefly/footer/register.ts +33 -4
- package/extensions/bytefly/footer/render/footer.ts +18 -4
- package/extensions/bytefly/footer/render/key.ts +46 -0
- package/extensions/bytefly/footer/render/layout.ts +13 -3
- package/extensions/bytefly/footer/render/lines.ts +10 -7
- package/extensions/bytefly/footer/render/segments.ts +41 -38
- package/extensions/bytefly/footer/render/status.ts +0 -11
- package/extensions/bytefly/footer/types.ts +7 -0
- package/extensions/bytefly/header/component.ts +4 -20
- package/extensions/bytefly/header/config.ts +1 -1
- package/extensions/bytefly/index.ts +32 -6
- package/extensions/bytefly/mode/editor.ts +115 -0
- package/extensions/bytefly/panel/commands.ts +210 -120
- package/extensions/bytefly/panel/format.ts +4 -4
- package/extensions/bytefly/panel/settings.ts +73 -6
- package/extensions/bytefly/panel/tui/keys.ts +2 -6
- package/extensions/bytefly/update/cache.ts +1 -1
- package/extensions/bytefly/update/register.ts +4 -4
- package/extensions/bytefly/working/config.ts +5 -7
- package/extensions/bytefly/working/format.ts +10 -13
- package/extensions/bytefly/working/register.ts +22 -27
- package/extensions/check/commands.ts +3 -2
- package/extensions/check/render.ts +10 -1
- package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
- package/extensions/context/commands.ts +2 -2
- package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
- package/extensions/file-browse/config/config.ts +4 -19
- package/extensions/file-browse/index.ts +89 -79
- package/extensions/file-browse/live/live.ts +1 -1
- package/extensions/file-browse/transport/router.ts +12 -4
- package/extensions/file-browse/transport/server.ts +61 -28
- package/extensions/file-browse/view/chrome.ts +4 -4
- package/extensions/file-browse/view/client.ts +3 -1
- package/extensions/file-browse/view/html.ts +73 -40
- package/extensions/file-browse/view/live-client.ts +2 -2
- package/extensions/file-browse/view/markdown.ts +13 -14
- package/extensions/file-browse/view/palette.ts +2 -2
- package/extensions/file-browse/view/raw-html.ts +9 -9
- package/extensions/file-browse/view/tree.ts +1 -1
- package/extensions/file-browse/view/urls.ts +20 -6
- package/extensions/file-read/render.ts +4 -4
- package/extensions/goal/plan-route.ts +2 -2
- package/extensions/goal/tui.ts +12 -2
- package/extensions/job/child.ts +527 -0
- package/extensions/job/clock.ts +142 -0
- package/extensions/job/commands.ts +4 -2
- package/extensions/job/config.ts +8 -1
- package/extensions/job/format.ts +22 -1
- package/extensions/job/log-writer.ts +13 -2
- package/extensions/job/registry.ts +63 -7
- package/extensions/job/render.ts +34 -0
- package/extensions/job/runtime.ts +296 -655
- package/extensions/job/session.ts +132 -19
- package/extensions/job/store.ts +7 -7
- package/extensions/job/tools.ts +21 -9
- package/extensions/job/types.ts +47 -0
- package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
- package/extensions/job/waiters.ts +44 -1
- package/extensions/memory/commands.ts +4 -2
- package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
- package/extensions/plan/commands.ts +12 -9
- package/extensions/plan/feedback.ts +62 -16
- package/extensions/plan/format.ts +8 -2
- package/extensions/plan/git-bash.ts +115 -16
- package/extensions/plan/index.ts +58 -51
- package/extensions/plan/plan-index.ts +4 -2
- package/extensions/plan/plan-text.ts +114 -0
- package/extensions/plan/plans.ts +5 -54
- package/extensions/plan/policy.ts +8 -10
- package/extensions/plan/review-lazy.ts +85 -0
- package/extensions/plan/review.ts +2 -2
- package/extensions/plan/runtime.ts +61 -41
- package/extensions/plan/sections.ts +1 -1
- package/extensions/plan/steps.ts +3 -3
- package/extensions/plan/todos.ts +1 -1
- package/extensions/plan/tools/cancel.ts +27 -62
- package/extensions/plan/tools/enter.ts +55 -81
- package/extensions/plan/tools/exit.ts +221 -238
- package/extensions/plan/tools/refusal.ts +18 -0
- package/extensions/plan/tools/steps.ts +75 -92
- package/extensions/plan/tools/write.ts +77 -120
- package/extensions/plan/tools.ts +188 -30
- package/extensions/plan/tui.ts +5 -5
- package/extensions/plan/types.ts +48 -5
- package/extensions/plan/web-blocks.ts +76 -5
- package/extensions/plan/web-chrome.ts +4 -0
- package/extensions/plan/web-client.ts +102 -2
- package/extensions/plan/web-collab.ts +4 -4
- package/extensions/plan/web-comments.ts +39 -8
- package/extensions/plan/web-composer.ts +14 -3
- package/extensions/plan/web-keys.ts +149 -25
- package/extensions/plan/web-lazy.ts +53 -0
- package/extensions/plan/web-markdown.ts +10 -68
- package/extensions/plan/web-outline.ts +22 -2
- package/extensions/plan/web-page.ts +56 -86
- package/extensions/plan/web-payload.ts +188 -0
- package/extensions/plan/web-plan.ts +23 -13
- package/extensions/plan/web-router.ts +18 -9
- package/extensions/plan/web-settle.ts +14 -7
- package/extensions/plan/web-share-session.ts +24 -55
- package/extensions/plan/web-share.ts +19 -37
- package/extensions/plan/web-write.ts +112 -0
- package/extensions/plan/web.ts +67 -131
- package/extensions/question/tools.ts +6 -2
- package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
- package/extensions/rewind/commands/commands.ts +6 -3
- package/extensions/rewind/state/runtime.ts +1 -1
- package/extensions/rewind/{view/status.ts → status.ts} +4 -4
- package/extensions/rewind/view/review-tui.ts +4 -4
- package/extensions/serve/config.ts +39 -0
- package/extensions/serve/index.ts +85 -0
- package/extensions/serve/registry.ts +260 -0
- package/extensions/session-bus/commands.ts +11 -6
- package/extensions/session-bus/join-request.ts +17 -9
- package/extensions/session-bus/mail-watch.ts +26 -17
- package/extensions/session-bus/protocol.ts +15 -3
- package/extensions/session-bus/resolve.ts +5 -7
- package/extensions/session-bus/result.ts +1 -27
- package/extensions/session-bus/runtime.ts +40 -12
- package/extensions/session-bus/store.ts +101 -19
- package/extensions/session-bus/tools.ts +5 -4
- package/extensions/session-bus/types.ts +19 -0
- package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
- package/extensions/subagent/agents.ts +1 -1
- package/extensions/subagent/commands.ts +4 -2
- package/extensions/subagent/format.ts +15 -15
- package/extensions/subagent/index.ts +1 -1
- package/extensions/subagent/render.ts +3 -3
- package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
- package/extensions/team/commands.ts +24 -5
- package/extensions/team/dispatch.ts +18 -4
- package/extensions/team/format.ts +17 -2
- package/extensions/team/index.ts +9 -8
- package/extensions/team/schema.ts +6 -5
- package/extensions/team/tools.ts +107 -45
- package/extensions/team/types.ts +3 -2
- package/extensions/todo/commands.ts +4 -2
- package/extensions/todo/format.ts +11 -0
- package/extensions/todo/render.ts +57 -0
- package/extensions/todo/runtime.ts +1 -1
- package/extensions/todo/tools.ts +1 -1
- package/extensions/todo/{tui.ts → view/todo-screen.ts} +18 -90
- package/extensions/todo/widget.ts +47 -0
- package/extensions/watch/commands.ts +4 -2
- package/extensions/watch/format.ts +36 -7
- package/extensions/watch/render.ts +57 -0
- package/extensions/watch/runtime.ts +27 -22
- package/extensions/watch/tools.ts +4 -4
- package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
- package/extensions/web-access/fetch/tool.ts +12 -4
- package/extensions/wechat/commands/commands.ts +18 -14
- package/extensions/wechat/index.ts +72 -37
- package/extensions/wechat/state/credentials.ts +1 -1
- package/extensions/wechat/state/state.ts +1 -1
- package/extensions/wechat/text/status.ts +112 -0
- package/extensions/wechat/types.ts +2 -2
- package/extensions/wechat/view/status-screen.ts +4 -103
- package/extensions/worktree/commands.ts +11 -9
- package/extensions/worktree/enter.ts +2 -2
- package/extensions/worktree/exit.ts +2 -2
- package/extensions/worktree/index.ts +5 -5
- package/extensions/worktree/screen.ts +3 -3
- package/extensions/worktree/status.ts +2 -2
- package/extensions/worktree/tools.ts +113 -116
- package/lib/README.md +28 -13
- package/lib/board/actions.ts +111 -0
- package/lib/board/artifact.ts +149 -0
- package/lib/board/card.ts +163 -52
- package/lib/board/config.ts +58 -0
- package/lib/board/contract.ts +113 -0
- package/lib/board/join.ts +38 -0
- package/lib/board/lanes.ts +182 -0
- package/lib/board/lead.ts +130 -0
- package/lib/board/limits.ts +78 -0
- package/lib/board/links.ts +31 -10
- package/lib/board/page-pointer.ts +30 -5
- package/lib/board/read.ts +83 -0
- package/lib/board/reason.ts +102 -0
- package/lib/board/round-text.ts +68 -6
- package/lib/board/round.ts +31 -15
- package/lib/board/rules.ts +151 -307
- package/lib/board/seat.ts +35 -0
- package/lib/board/session.ts +199 -51
- package/lib/board/skipped.ts +22 -0
- package/lib/board/steps.ts +487 -0
- package/lib/board/store.ts +119 -36
- package/lib/board/tone.ts +92 -0
- package/lib/board/view.ts +81 -63
- package/lib/bus-request.ts +83 -0
- package/{extensions/watch → lib}/child.ts +40 -21
- package/lib/env.ts +65 -7
- package/lib/format.ts +13 -0
- package/lib/http/index.ts +17 -11
- package/lib/http/mount.ts +142 -0
- package/lib/http/port.ts +30 -0
- package/lib/http/serve.ts +170 -46
- package/lib/http/server.ts +3 -3
- package/lib/lazy.ts +45 -0
- package/lib/plan-entry.ts +46 -21
- package/lib/plans/paths.ts +82 -0
- package/lib/policy.ts +9 -6
- package/lib/report.ts +3 -2
- package/lib/requirements.ts +10 -10
- package/lib/session-label.ts +129 -7
- package/lib/session-roster.ts +43 -1
- package/lib/session-tool.ts +39 -22
- package/lib/stray-fields.ts +7 -2
- package/lib/tool-names.ts +5 -10
- package/lib/tui/README.md +29 -11
- package/lib/tui/chrome.ts +16 -15
- package/lib/tui/cursor.ts +18 -3
- package/lib/tui/glyphs.ts +11 -11
- package/lib/tui/render-cache.ts +29 -0
- package/lib/tui/tokens.ts +33 -2
- package/lib/tui/tool.ts +33 -4
- package/lib/web/README.md +21 -0
- package/lib/web/client-runtime.ts +27 -1
- package/lib/web/components.ts +5 -1
- package/lib/web/index.ts +1 -0
- package/lib/web/markdown.ts +83 -0
- package/lib/web/markup.ts +20 -2
- package/lib/web/runtime.ts +9 -10
- package/package.json +8 -2
- package/skills/init/SKILL.md +5 -5
- package/skills/next/SKILL.md +3 -5
- package/extensions/board/config.ts +0 -36
- package/extensions/board/seat.ts +0 -28
- package/lib/tui/index.ts +0 -34
- package/skills/project-docs/SKILL.md +0 -133
- package/skills/project-docs/references/audit.md +0 -131
- package/skills/project-docs/references/changelog.md +0 -105
- package/skills/project-docs/references/discovery.md +0 -127
- package/skills/project-docs/references/readme.md +0 -80
- package/skills/project-docs/references/release.md +0 -84
- package/skills/project-docs/references/roadmap.md +0 -93
- package/skills/project-docs/scripts/state.ts +0 -316
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Release
|
|
2
|
-
|
|
3
|
-
Only when the user asked for a specific version. A release is a promise: a tag, a
|
|
4
|
-
published artifact, and a section of the changelog that says what is in it. The
|
|
5
|
-
project's release document owns the whole procedure; this file adds the changelog
|
|
6
|
-
steps and the order they fit into, not a competing one.
|
|
7
|
-
|
|
8
|
-
## Before touching the file
|
|
9
|
-
|
|
10
|
-
1. Read the project's release document — `docs/release.md`, `RELEASING.md`,
|
|
11
|
-
`CONTRIBUTING.md`'s release section, `.github/workflows/publish.yml`. It decides
|
|
12
|
-
the files to bump, the tag format, and the push and publish order.
|
|
13
|
-
2. Confirm the version and the reason with the user. Say the reason in SemVer's
|
|
14
|
-
terms: a fix with no surface change is a patch, an additive surface is a minor, a
|
|
15
|
-
breaking change is a major — and ask when a project in `0.x` treats minors as the
|
|
16
|
-
breaking channel, because many do. Never infer a version from the size of the
|
|
17
|
-
diff and never raise the version in the release without the user's word for it.
|
|
18
|
-
|
|
19
|
-
## Move the entries
|
|
20
|
-
|
|
21
|
-
A project that keeps no changelog file — only GitHub or GitLab releases, or release
|
|
22
|
-
notes a tool generates — has nothing to move: say so and stop, and do not create the
|
|
23
|
-
file as part of a release. The rest of this section assumes one exists.
|
|
24
|
-
|
|
25
|
-
A file a tool generates is moved by that tool, not here: `changeset version`,
|
|
26
|
-
`git-cliff --tag`, `release-please`, `semantic-release`, or whatever the project's
|
|
27
|
-
release document names. In that case this mode's job is to check the result — the
|
|
28
|
-
version, the dated heading, the category order, the link refs — and to report the step
|
|
29
|
-
that is still owed, never to edit the generated file.
|
|
30
|
-
|
|
31
|
-
- Read `[Unreleased]` and check it is what the release contains: everything since
|
|
32
|
-
the last tag, nothing that already shipped.
|
|
33
|
-
- Move every entry under `## [X.Y.Z] - YYYY-MM-DD`, categories in the file's own
|
|
34
|
-
order and spelling, creating only the categories that hold an entry. Keep each
|
|
35
|
-
bullet's text; a release is a move, not a rewrite.
|
|
36
|
-
- Leave `[Unreleased]` present and empty, unless the file's preamble says it is
|
|
37
|
-
removed between releases. An empty heading is how the next change knows where to
|
|
38
|
-
land.
|
|
39
|
-
- Update the link references: `[Unreleased]` now compares from the new tag, and the
|
|
40
|
-
new version gets a reference in the file's existing style (`compare/vOLD...vNEW`
|
|
41
|
-
or `releases/tag/vNEW`).
|
|
42
|
-
- Bump the version where the project keeps it — usually one manifest, sometimes a
|
|
43
|
-
lockfile, a version file, and an install pin. The build manifest and the newest
|
|
44
|
-
dated heading must agree, or a consistency guard fails.
|
|
45
|
-
- The release commit holds the changelog move, the version bump, and the link refs.
|
|
46
|
-
No behavior, no new entries, no unrelated docs: a release commit that also changes
|
|
47
|
-
code cannot be diffed as a release.
|
|
48
|
-
|
|
49
|
-
## Finish
|
|
50
|
-
|
|
51
|
-
- Run the project's release guard (often `release.test.ts` or a `changelog` check),
|
|
52
|
-
then the project's full check. The guard is the one test that names a version, so
|
|
53
|
-
run it directly while iterating.
|
|
54
|
-
- Then, in the project's order and only with explicit approval for that push: commit,
|
|
55
|
-
tag (`vX.Y.Z`, annotated where the project does that), push the tag together with
|
|
56
|
-
the branch — a branch push that lands before its tag races the checkout that
|
|
57
|
-
asserts they agree — and publish through the project's pipeline, not a local
|
|
58
|
-
publish.
|
|
59
|
-
- The release body is that section verbatim, heading included. Do not add a summary,
|
|
60
|
-
a thank-you, or a rewrite of the bullets a reader already has.
|
|
61
|
-
|
|
62
|
-
## Verify a release you did not cut
|
|
63
|
-
|
|
64
|
-
A release is a chain of steps, and the last one can sit for hours waiting on a
|
|
65
|
-
pipeline's approval, so checking a tagged version is its own job. It touches no file:
|
|
66
|
-
open the tag, read each of these, and report the line.
|
|
67
|
-
|
|
68
|
-
- The tag's own manifest — `git show vX.Y.Z:package.json`, or the equivalent — because
|
|
69
|
-
the publish job proves that before it publishes, not the branch's.
|
|
70
|
-
- The newest dated heading, and the project's release guard where it has one.
|
|
71
|
-
- The link refs: `[Unreleased]` compares from the new tag, and `X.Y.Z` points at it.
|
|
72
|
-
- Whatever the release document bumps in the same commit — an install pin, a version
|
|
73
|
-
stamped in a guide, a captured image.
|
|
74
|
-
- The release body against the section the project's own tooling prints, never against
|
|
75
|
-
memory.
|
|
76
|
-
- The publish run itself: a workflow that checks out `vX.Y.Z` publishes the tagged tree
|
|
77
|
-
whatever ref the dispatch named, so what remains is its environment approval and the
|
|
78
|
-
registry version after it.
|
|
79
|
-
|
|
80
|
-
## After
|
|
81
|
-
|
|
82
|
-
If a shipped change turns out to have no bullet, do not edit the released section.
|
|
83
|
-
Record the gap to the user and add a bullet to `[Unreleased]` for the next release,
|
|
84
|
-
naming the fix; a released section is a record of what was published, not a draft.
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
The roadmap is the one document that describes the tree rather than the last release,
|
|
4
|
-
so it goes stale two ways: it keeps what already shipped, and it promises what the
|
|
5
|
-
tree already does differently. This mode re-derives it and deletes what is done. It
|
|
6
|
-
never decides what matters.
|
|
7
|
-
|
|
8
|
-
Read [`discovery.md`](./discovery.md) first for the file, the item form, the buckets
|
|
9
|
-
and their caps, the ledger, and the stated rule for a shipped item. A file with no ids
|
|
10
|
-
and no buckets is not a defect to fix: it gets the deletion pass below, and no invented
|
|
11
|
-
id scheme.
|
|
12
|
-
|
|
13
|
-
## Re-derive before editing
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
git log --oneline <last-tag>..HEAD
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Use `HEAD`, or the branch the project documents as its default — a project's branch is
|
|
20
|
-
not always `main`. Read the changelog's newest dated heading beside it: the window is
|
|
21
|
-
everything that landed, and the release is what a reader has. A project with no tags has
|
|
22
|
-
no window at all: re-derive from the newest dated heading and the tree instead, and say
|
|
23
|
-
that is what you did. An item is not "shipped" because a branch merged — the tree
|
|
24
|
-
decides whether the promise is met, the maintainer decides whether it shipped.
|
|
25
|
-
|
|
26
|
-
Then, for every item in the file, open the code it names. Three outcomes:
|
|
27
|
-
|
|
28
|
-
- **The tree does it.** Delete the item, and record the reason where it was — one line
|
|
29
|
-
is enough, and it is what stops the same item being re-added next review. A file with
|
|
30
|
-
its own history or "previous `Now`" section gets the note there instead.
|
|
31
|
-
- **The tree does part of it.** Rewrite the item down to the part still missing. A
|
|
32
|
-
condensed item is a finding; a deleted one that was not done is a lost promise.
|
|
33
|
-
- **The tree contradicts it** — the approach changed, or the problem was solved another
|
|
34
|
-
way. Delete it with that reason. Parking it as "still available" describes a tree
|
|
35
|
-
that no longer exists.
|
|
36
|
-
|
|
37
|
-
Never tick an item and leave it. A checklist with finished rows is a changelog written
|
|
38
|
-
in the wrong file, and it is what the project's own rule usually forbids.
|
|
39
|
-
|
|
40
|
-
## Renumber and fix every citation
|
|
41
|
-
|
|
42
|
-
After a deletion, ids break in four places, and all four move in the same edit:
|
|
43
|
-
|
|
44
|
-
- The item headings, so a prefix's numbers stay contiguous.
|
|
45
|
-
- Every reference elsewhere in the file — a ledger row, a "if only two things ship"
|
|
46
|
-
list, a `P1–P3` range whose endpoints both still resolve.
|
|
47
|
-
- Every mention in the guides, the front door, and the instruction file, which is what
|
|
48
|
-
a stale id breaks first because nothing validates it.
|
|
49
|
-
- The relative links and `path:line` citations the items carry.
|
|
50
|
-
|
|
51
|
-
Change an id only when a deletion forces it. Renumbering for tidiness loses the
|
|
52
|
-
citations the file's readers already have.
|
|
53
|
-
|
|
54
|
-
## Keep the buckets honest
|
|
55
|
-
|
|
56
|
-
- A bucket over the cap the file states is a priority list that stopped being one:
|
|
57
|
-
demote an item rather than widening the cap.
|
|
58
|
-
- A horizon whose exit condition has been met gets rewritten, not left as decoration.
|
|
59
|
-
- A ledger row whose items have all closed is closed; an open item with no ledger row
|
|
60
|
-
gets one, or it is not part of a promise.
|
|
61
|
-
|
|
62
|
-
## Numbers belong to the tree
|
|
63
|
-
|
|
64
|
-
Any count the file states about the code — modules, extensions, tools, screens — is
|
|
65
|
-
read from the tree, not adjusted by hand. A project that derives it in a guard has the
|
|
66
|
-
count already: run the guard, and change the tree or the guard, never the number. A
|
|
67
|
-
project that does not is a finding for the audit, and the fix is a guard.
|
|
68
|
-
|
|
69
|
-
## Non-goals are decisions
|
|
70
|
-
|
|
71
|
-
The non-goal list carries a reason per entry. When the changelog, a discussion, or the
|
|
72
|
-
audit contradicts one, that is a decision for the maintainer: list it, with the
|
|
73
|
-
evidence, and ask. Reversing a documented non-goal is not a roadmap edit, and an item
|
|
74
|
-
that needs one reversed says so in the item.
|
|
75
|
-
|
|
76
|
-
## Report
|
|
77
|
-
|
|
78
|
-
Every edit named by the item it touched: deleted, rewritten, renumbered, or cited.
|
|
79
|
-
Then the items that need a decision, one line each. A roadmap whose window changed and
|
|
80
|
-
whose items are all still true is a valid result — say so instead of finding work to
|
|
81
|
-
look thorough.
|
|
82
|
-
|
|
83
|
-
## In this repository
|
|
84
|
-
|
|
85
|
-
- `ROADMAP.md` states its own rules under *How items are ordered*: an item is deleted
|
|
86
|
-
when it ships, never ticked, and `Now` and `Next` hold at most ten.
|
|
87
|
-
- `test/roadmap.test.ts` pins the mechanical half — id uniqueness and contiguity, the
|
|
88
|
-
links, the citations, and the caps — and cannot tell that an item has shipped, which
|
|
89
|
-
is why the file states that rule in prose.
|
|
90
|
-
- `AGENTS.md` requires the deletion or renumbering in the same commit as the change.
|
|
91
|
-
|
|
92
|
-
The paths are named, not linked: a skill is installed into projects that do not
|
|
93
|
-
contain them, so a link would point nowhere from the place it is read.
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Changelog state, for the `project-docs` skill.
|
|
3
|
-
*
|
|
4
|
-
* The skill's judgement is the model's; the counting is not. This prints what a
|
|
5
|
-
* changelog can be asked mechanically — the file, the last tag, the commits since it,
|
|
6
|
-
* the `[Unreleased]` categories, the empty and duplicate headings, the scope prefixes
|
|
7
|
-
* in use, and the link references — so an audit starts from facts instead of a
|
|
8
|
-
* hand-run `git log` whose flags and column are remembered differently each time.
|
|
9
|
-
*
|
|
10
|
-
* It ships inside the skill and resolves against the skill's own directory, so it
|
|
11
|
-
* imports nothing from the package's `lib/`: an installed copy has `skills/` and
|
|
12
|
-
* nothing else.
|
|
13
|
-
*
|
|
14
|
-
* Usage: `bun <skill>/scripts/state.ts [dir]`
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
-
import { join, relative, resolve } from "node:path";
|
|
19
|
-
|
|
20
|
-
/** One `###` category under `[Unreleased]`, as the file spells it. */
|
|
21
|
-
export interface Category {
|
|
22
|
-
/** The heading text, exactly as written: `Fixed`, not `Fix`. */
|
|
23
|
-
name: string;
|
|
24
|
-
/** 1-based line of the heading. */
|
|
25
|
-
line: number;
|
|
26
|
-
/** Top-level bullets between this heading and the next one. */
|
|
27
|
-
bullets: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** What the file's text alone says; `gitFacts` adds the part that needs a repository. */
|
|
31
|
-
export interface State {
|
|
32
|
-
/** Whether the first paragraphs name Keep a Changelog and Semantic Versioning. */
|
|
33
|
-
keepAChangelog: boolean;
|
|
34
|
-
/** The `[Unreleased]` section, when the file keeps one. */
|
|
35
|
-
unreleased?: { line: number; categories: Category[]; bullets: number };
|
|
36
|
-
/** Every dated `## [X.Y.Z]` heading, newest first, which is the order released sections appear in. */
|
|
37
|
-
dated: { version: string; line: number }[];
|
|
38
|
-
/** A heading text used twice inside one section, with every line it occurs on. */
|
|
39
|
-
duplicates: { name: string; lines: number[] }[];
|
|
40
|
-
/** Scope prefixes and how often each appears, most used first. */
|
|
41
|
-
scopes: { name: string; count: number }[];
|
|
42
|
-
/** The link references the file defines, lowercased, and the dated sections that lack one. */
|
|
43
|
-
refs: { unreleased: boolean; dated: number; missing: string[] };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** What the repository adds: the tag the window starts at, and what is in the window. */
|
|
47
|
-
export interface GitFacts {
|
|
48
|
-
/** The newest tag, when the repository has one. */
|
|
49
|
-
tag?: string;
|
|
50
|
-
/** Subject lines of the non-merge commits since the tag, newest first. */
|
|
51
|
-
commits: string[];
|
|
52
|
-
/** Why a fact is absent, in words the report can print: no repository, no tags. */
|
|
53
|
-
note?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Keep a Changelog's `## [Unreleased]`, and the un-bracketed spelling some files use. */
|
|
57
|
-
const UNRELEASED = /^\[?unreleased\]?$/i;
|
|
58
|
-
/** A dated section heading: `[0.17.0] - 2025-06-01`, `0.17.0`, or a project's own suffix. */
|
|
59
|
-
const DATED = /^\[?(\d+\.\d+\.\d+[^\s\]]*)\]?/;
|
|
60
|
-
const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
|
|
61
|
-
const FENCE = /^\s*(?:```|~~~)/;
|
|
62
|
-
const BULLET = /^[-*+]\s+/;
|
|
63
|
-
/** A bullet's scope: backticked, bold, or a bare `area:` prefix. Not a path, and not prose. */
|
|
64
|
-
const SCOPE = /^[-*+]\s+(?:`([^`]+)`|\*\*([^*]+)\*\*|([A-Za-z][A-Za-z0-9./-]{0,24}))\s*:/;
|
|
65
|
-
const LINK_REF = /^\[([^\]]+)\]:\s*\S+/;
|
|
66
|
-
|
|
67
|
-
/** Report caps, so a long history stays one screen. */
|
|
68
|
-
export const MAX_COMMITS = 40;
|
|
69
|
-
export const MAX_SCOPES = 6;
|
|
70
|
-
|
|
71
|
-
interface Heading {
|
|
72
|
-
level: number;
|
|
73
|
-
text: string;
|
|
74
|
-
line: number;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** The changelog a project root holds, in the order projects name one. */
|
|
78
|
-
export const CHANGELOG_NAMES = [
|
|
79
|
-
"CHANGELOG.md",
|
|
80
|
-
"CHANGELOG",
|
|
81
|
-
"CHANGES.md",
|
|
82
|
-
"HISTORY.md",
|
|
83
|
-
"NEWS.md",
|
|
84
|
-
join("docs", "CHANGELOG.md"),
|
|
85
|
-
join("docs", "changelog.md"),
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
/** The first changelog file in `dir`, as an absolute path, or undefined when it has none. */
|
|
89
|
-
export function findChangelog(dir: string): string | undefined {
|
|
90
|
-
const root = resolve(dir);
|
|
91
|
-
for (const name of CHANGELOG_NAMES) {
|
|
92
|
-
const candidate = join(root, name);
|
|
93
|
-
if (existsSync(candidate)) return candidate;
|
|
94
|
-
}
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Every heading outside a fence, so a `## example` in a code block is not a section. */
|
|
99
|
-
function headingsOf(markdown: string): Heading[] {
|
|
100
|
-
const headings: Heading[] = [];
|
|
101
|
-
let fenced = false;
|
|
102
|
-
markdown.split("\n").forEach((text, index) => {
|
|
103
|
-
if (FENCE.test(text)) {
|
|
104
|
-
fenced = !fenced;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (fenced) return;
|
|
108
|
-
const match = HEADING.exec(text);
|
|
109
|
-
if (match !== null) headings.push({ level: match[1].length, text: match[2].trim(), line: index + 1 });
|
|
110
|
-
});
|
|
111
|
-
return headings;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** The scope a bullet leads with, if it leads with one. */
|
|
115
|
-
function scopeOf(line: string): string | undefined {
|
|
116
|
-
const match = SCOPE.exec(line);
|
|
117
|
-
if (match === null) return undefined;
|
|
118
|
-
return (match[1] ?? match[2] ?? match[3]).trim();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Bullet count and scopes over the given 1-based inclusive line window, outside fences. */
|
|
122
|
-
function scanBullets(lines: readonly string[], start: number, end: number): { bullets: number; scopes: string[] } {
|
|
123
|
-
let bullets = 0;
|
|
124
|
-
const scopes: string[] = [];
|
|
125
|
-
let fenced = false;
|
|
126
|
-
for (let line = start; line <= end && line <= lines.length; line++) {
|
|
127
|
-
const text = lines[line - 1];
|
|
128
|
-
if (FENCE.test(text)) {
|
|
129
|
-
fenced = !fenced;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (fenced || !BULLET.test(text)) continue;
|
|
133
|
-
bullets++;
|
|
134
|
-
const scope = scopeOf(text);
|
|
135
|
-
if (scope !== undefined) scopes.push(scope);
|
|
136
|
-
}
|
|
137
|
-
return { bullets, scopes };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Read the file's text into the facts the report prints. Pure, so a fixture can drive it. */
|
|
141
|
-
export function readState(markdown: string): State {
|
|
142
|
-
const lines = markdown.split("\n");
|
|
143
|
-
const headings = headingsOf(markdown);
|
|
144
|
-
const preamble = lines.slice(0, 20).join("\n");
|
|
145
|
-
|
|
146
|
-
const unreleasedAt = headings.findIndex((heading) => heading.level === 2 && UNRELEASED.test(heading.text));
|
|
147
|
-
const dated = headings
|
|
148
|
-
.filter((heading) => heading.level === 2)
|
|
149
|
-
.flatMap((heading) => {
|
|
150
|
-
const version = DATED.exec(heading.text)?.[1];
|
|
151
|
-
return version === undefined ? [] : [{ version, line: heading.line }];
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const scopes = new Map<string, number>();
|
|
155
|
-
for (const scope of scanBullets(lines, 1, lines.length).scopes) {
|
|
156
|
-
scopes.set(scope, (scopes.get(scope) ?? 0) + 1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const duplicates = duplicateHeadings(headings);
|
|
160
|
-
|
|
161
|
-
const refs = new Map<string, number>();
|
|
162
|
-
for (const line of lines) {
|
|
163
|
-
const name = LINK_REF.exec(line)?.[1];
|
|
164
|
-
if (name !== undefined) refs.set(name.toLowerCase(), (refs.get(name.toLowerCase()) ?? 0) + 1);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
let unreleased: State["unreleased"];
|
|
168
|
-
if (unreleasedAt !== -1) {
|
|
169
|
-
const heading = headings[unreleasedAt];
|
|
170
|
-
const nextSection = headings.slice(unreleasedAt + 1).find((entry) => entry.level <= 2);
|
|
171
|
-
const windowEnd = nextSection === undefined ? lines.length : nextSection.line - 1;
|
|
172
|
-
const inner = headings.filter((entry) => entry.line > heading.line && entry.line <= windowEnd);
|
|
173
|
-
const categories: Category[] = [];
|
|
174
|
-
inner
|
|
175
|
-
.filter((entry) => entry.level === 3)
|
|
176
|
-
.forEach((entry, index) => {
|
|
177
|
-
const next = inner.filter((item) => item.level === 3)[index + 1];
|
|
178
|
-
const categoryEnd = next === undefined ? windowEnd : next.line - 1;
|
|
179
|
-
categories.push({
|
|
180
|
-
name: entry.text,
|
|
181
|
-
line: entry.line,
|
|
182
|
-
bullets: scanBullets(lines, entry.line + 1, categoryEnd).bullets,
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
unreleased = {
|
|
186
|
-
line: heading.line,
|
|
187
|
-
categories,
|
|
188
|
-
bullets: categories.reduce((total, category) => total + category.bullets, 0),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
keepAChangelog: /keep a changelog/i.test(preamble) && /semantic versioning/i.test(preamble),
|
|
194
|
-
unreleased,
|
|
195
|
-
dated,
|
|
196
|
-
duplicates,
|
|
197
|
-
scopes: [...scopes.entries()]
|
|
198
|
-
.map(([name, count]) => ({ name, count }))
|
|
199
|
-
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)),
|
|
200
|
-
refs: {
|
|
201
|
-
unreleased: refs.has("unreleased"),
|
|
202
|
-
dated: dated.length,
|
|
203
|
-
missing: dated.filter((entry) => !refs.has(entry.version.toLowerCase())).map((entry) => entry.version),
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/** A trim with an ellipsis, so one long line cannot wrap the report. */
|
|
209
|
-
function clip(text: string, width: number): string {
|
|
210
|
-
return text.length > width ? `${text.slice(0, width - 1)}…` : text;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* A heading repeated inside one section, which is what a reader sees as one category
|
|
215
|
-
* split in two. The same `### Fixed` under each release is the format working, not a
|
|
216
|
-
* duplicate, so headings are grouped by the `##` section that contains them.
|
|
217
|
-
*/
|
|
218
|
-
function duplicateHeadings(headings: readonly Heading[]): { name: string; lines: number[] }[] {
|
|
219
|
-
const groups = new Map<string, { name: string; lines: number[] }>();
|
|
220
|
-
let section = "top";
|
|
221
|
-
for (const heading of headings) {
|
|
222
|
-
if (heading.level <= 2) section = heading.level === 2 ? heading.text.toLowerCase() : "top";
|
|
223
|
-
const key = `${section}\u0000${heading.text.toLowerCase()}`;
|
|
224
|
-
const group = groups.get(key) ?? { name: heading.text, lines: [] };
|
|
225
|
-
group.lines.push(heading.line);
|
|
226
|
-
groups.set(key, group);
|
|
227
|
-
}
|
|
228
|
-
return [...groups.values()].filter((group) => group.lines.length > 1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* The report. Every line is a fact with its source, in the order an audit uses them:
|
|
233
|
-
* the file, the window, the unreleased section, then what is wrong with the file, then
|
|
234
|
-
* what the window contains.
|
|
235
|
-
*/
|
|
236
|
-
export function formatState(file: string, state: State, git: GitFacts): string {
|
|
237
|
-
const out: string[] = [];
|
|
238
|
-
const preamble = state.keepAChangelog ? "Keep a Changelog preamble" : "no Keep a Changelog preamble";
|
|
239
|
-
out.push(`changelog ${file} · ${preamble}`);
|
|
240
|
-
|
|
241
|
-
const count = `${git.commits.length} commit${git.commits.length === 1 ? "" : "s"} since, merges skipped`;
|
|
242
|
-
out.push(`last tag ${git.tag === undefined ? (git.note ?? "no tags") : `${git.tag} · ${count}`}`);
|
|
243
|
-
|
|
244
|
-
out.push(
|
|
245
|
-
state.unreleased === undefined
|
|
246
|
-
? "unreleased missing: the file has no `[Unreleased]` section"
|
|
247
|
-
: `unreleased line ${state.unreleased.line} · ${state.unreleased.bullets} bullets`,
|
|
248
|
-
);
|
|
249
|
-
if (state.unreleased !== undefined) {
|
|
250
|
-
const named = state.unreleased.categories.map((category) => `${category.name} ${category.bullets}`);
|
|
251
|
-
out.push(`categories ${named.length === 0 ? "none" : named.join(" · ")} (file order)`);
|
|
252
|
-
const empty = state.unreleased.categories.filter((category) => category.bullets === 0).map((item) => item.name);
|
|
253
|
-
out.push(`empty ${empty.length === 0 ? "none" : empty.join(" · ")}`);
|
|
254
|
-
}
|
|
255
|
-
out.push(
|
|
256
|
-
state.duplicates.length === 0
|
|
257
|
-
? "duplicate none"
|
|
258
|
-
: `duplicate ${state.duplicates
|
|
259
|
-
.slice(0, 4)
|
|
260
|
-
.map((entry) => `${entry.name} at lines ${entry.lines.join(", ")}`)
|
|
261
|
-
.join(" · ")}`,
|
|
262
|
-
);
|
|
263
|
-
const scopes = state.scopes.slice(0, MAX_SCOPES).map((entry) => `${entry.name} ${entry.count}`);
|
|
264
|
-
const rest = state.scopes.length - MAX_SCOPES;
|
|
265
|
-
out.push(
|
|
266
|
-
`scopes ${scopes.length === 0 ? "none" : `${scopes.join(" · ")}${rest > 0 ? ` · … ${rest} more` : ""}`}`,
|
|
267
|
-
);
|
|
268
|
-
out.push(
|
|
269
|
-
`refs [Unreleased] ${state.refs.unreleased ? "yes" : "no"} · dated refs ${state.refs.dated - state.refs.missing.length}/${state.refs.dated}${
|
|
270
|
-
state.refs.missing.length === 0 ? "" : `, missing ${state.refs.missing.slice(0, 5).join(", ")}`
|
|
271
|
-
}`,
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
if (git.commits.length > 0) {
|
|
275
|
-
out.push(`commits since ${git.tag ?? "the beginning"}`);
|
|
276
|
-
for (const commit of git.commits.slice(0, MAX_COMMITS)) out.push(` ${clip(commit, 110)}`);
|
|
277
|
-
const more = git.commits.length - MAX_COMMITS;
|
|
278
|
-
if (more > 0) out.push(` … ${more} more`);
|
|
279
|
-
}
|
|
280
|
-
return `${out.join("\n")}\n`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** Run one git command, returning its trimmed stdout or a reason it produced none. */
|
|
284
|
-
function git(args: readonly string[], cwd: string): { text?: string; error?: string } {
|
|
285
|
-
const result = Bun.spawnSync({ cmd: ["git", ...args], cwd, stdout: "pipe", stderr: "pipe" });
|
|
286
|
-
if (result.exitCode !== 0) return { error: result.stderr.toString().trim() || `git ${args[0]} failed` };
|
|
287
|
-
return { text: result.stdout.toString().trim() };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* The tag to audit from, and the non-merge commits after it. A repository with no
|
|
292
|
-
* tags has no window, and a directory outside git has neither; both are reported as a
|
|
293
|
-
* note rather than an error, because the file half of the report is still useful.
|
|
294
|
-
*/
|
|
295
|
-
export function gitFacts(cwd: string): GitFacts {
|
|
296
|
-
const inside = git(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
297
|
-
if (inside.text !== "true") return { commits: [], note: "not a git repository" };
|
|
298
|
-
|
|
299
|
-
const described = git(["describe", "--tags", "--abbrev=0"], cwd);
|
|
300
|
-
const tag = described.text === "" ? undefined : described.text;
|
|
301
|
-
const range = tag === undefined ? ["HEAD"] : [`${tag}..HEAD`];
|
|
302
|
-
const log = git(["log", "--no-merges", "--format=%h %s", ...range], cwd);
|
|
303
|
-
const commits = (log.text ?? "").split("\n").filter((line) => line.trim() !== "");
|
|
304
|
-
return { tag, commits, note: tag === undefined ? "no tags" : undefined };
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (import.meta.main) {
|
|
308
|
-
const dir = resolve(process.argv[2] ?? ".");
|
|
309
|
-
const changelog = findChangelog(dir);
|
|
310
|
-
if (changelog === undefined) {
|
|
311
|
-
console.error(`no changelog in ${dir}: looked for ${CHANGELOG_NAMES.join(", ")}`);
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
const state = readState(readFileSync(changelog, "utf-8"));
|
|
315
|
-
process.stdout.write(formatState(relative(dir, changelog), state, gitFacts(dir)));
|
|
316
|
-
}
|