@gavin-hu/my-pi-agent 0.17.0 → 0.19.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 +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
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
|
+
}
|
package/skills/refactor/SKILL.md
CHANGED
|
@@ -1,141 +1,116 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: refactor
|
|
3
|
-
description: "
|
|
3
|
+
description: "Audit a project or path for refactoring opportunities — duplication, oversized modules, coupling, drift, high-churn code with weak tests — and return a ranked shortlist with path:line evidence, or carry one chosen candidate through a behaviour-preserving refactor. Use when asked what to refactor, where the code is tangled, for a refactor audit, or to execute a refactor already agreed; discovery is read-only."
|
|
4
|
+
compatibility: "Bun for scripts/inventory.ts; git for the churn window."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Find refactoring opportunities
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Goal: a ranked shortlist a reader can act on — evidence at `path:line`, a verification
|
|
10
|
+
command per candidate, and the reasoning a reviewer can argue with. This is an audit:
|
|
11
|
+
nothing is modified before the user picks a candidate.
|
|
11
12
|
|
|
12
13
|
A refactor changes structure without changing behavior. If the request changes
|
|
13
|
-
behavior,
|
|
14
|
-
|
|
14
|
+
behavior, say so and treat it as a behavior change, not as this. The project's own
|
|
15
|
+
instructions and thresholds win over anything here.
|
|
16
|
+
|
|
17
|
+
## Start here
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun <skill>/scripts/inventory.ts <path ...> [--churn-days N] [--limit N]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Resolved against this skill's own directory. It prints the scope, the languages it
|
|
24
|
+
measured, the size distribution, fan-in, churn over a stated window, test proximity,
|
|
25
|
+
and the files above the 90th percentile in any of those — facts, never a verdict. The
|
|
26
|
+
numbers are the evidence; the ranking is yours.
|
|
27
|
+
|
|
28
|
+
When it reports `no history`, an `unresolved` count, or a `skipped` language, repeat
|
|
29
|
+
that in the report rather than reading the gap as zero. See
|
|
30
|
+
[`references/signals.md`](./references/signals.md) for what it measures, what it
|
|
31
|
+
cannot, and what each signal needs before it counts.
|
|
15
32
|
|
|
16
33
|
## 1. Resolve the scope
|
|
17
34
|
|
|
18
|
-
The text after `/skill:refactor` is the task
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
leave the working directory unless the user explicitly supplied an outside
|
|
28
|
-
path.
|
|
35
|
+
The text after `/skill:refactor` is the task: a path, a question, or both.
|
|
36
|
+
|
|
37
|
+
- Resolve each path from the working directory. A directory is the audit scope; a file
|
|
38
|
+
is the initial target. Audit several paths separately and report them together.
|
|
39
|
+
- No path means the current project. When the project is large, do not scan
|
|
40
|
+
everything: ask which scope to audit, offering two to four concrete directories with
|
|
41
|
+
their file counts.
|
|
42
|
+
- A path that does not exist is a question, not a guess. Ask for a real one, and do not
|
|
43
|
+
leave the working directory unless the user named a path outside it.
|
|
29
44
|
- State the resolved scope and the number of files examined in the report.
|
|
30
45
|
|
|
31
46
|
## 2. Learn the project's standards
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| Size and responsibility | A module or function past the project's limit, or a relative outlier; unrelated exports; deep nesting; long parameter lists | Measured size or responsibility list; the seams where it could split |
|
|
66
|
-
| Coupling | Circular or layer-crossing imports, high fan-in or fan-out, feature envy, repeated type switches | The import edge or repeated branch, with both sides named |
|
|
67
|
-
| Cohesion and drift | A grab-bag utility module; a name that no longer matches behavior; dead exports; commented-out code; two implementations of one concept | The mismatch or unused export, with its callers |
|
|
68
|
-
| Change pressure | High recent churn combined with complexity or weak tests | Churn count over a stated window plus the complexity or test gap |
|
|
69
|
-
| Test friction | Behavior with no direct test; tests that mock the unit heavily or assert internals; duplicated setup that hides a missing seam | The test path and the missing or brittle assertion |
|
|
70
|
-
| Boundary erosion | Leaking internal types, unchecked casts, mode-selecting boolean parameters, null-heavy returns | The boundary location and the caller that must know the internals |
|
|
71
|
-
|
|
72
|
-
## 5. Verify and filter
|
|
73
|
-
|
|
74
|
-
A signal is not a candidate until it is confirmed.
|
|
75
|
-
|
|
76
|
-
- Read the callers and tests, not just the definition. Duplication may be
|
|
77
|
-
intentional for a protocol version; a large module may be cohesive; an
|
|
78
|
-
"unused" export may be public API.
|
|
79
|
-
- Require one concrete artifact: a path with line numbers, a counted duplicate, a
|
|
80
|
-
measured size, a dependency edge, or a test gap. If you cannot point at it,
|
|
81
|
-
drop it.
|
|
82
|
-
- Keep pure refactors separate from behavior changes and bugs. Report a bug as a
|
|
83
|
-
bug; do not smuggle it into the refactor list.
|
|
84
|
-
- Prefer candidates with an existing test that catches a regression. If the
|
|
85
|
-
behavior is untested, mark the candidate "tests first".
|
|
86
|
-
- Reject weak candidates explicitly. A short "not worth it" list proves the
|
|
87
|
-
audit was selective rather than a tour of everything unfamiliar.
|
|
88
|
-
|
|
89
|
-
## 6. Rank
|
|
90
|
-
|
|
91
|
-
Use three tiers:
|
|
92
|
-
|
|
93
|
-
- **Do next:** safe, contained blast radius, high leverage, small or medium
|
|
94
|
-
effort, and a fast verification command.
|
|
95
|
-
- **Worth planning:** high leverage but more risk or effort, a public surface, or
|
|
48
|
+
Read the instruction files, contributor guide, architecture notes, and CI
|
|
49
|
+
configuration. Record the languages, layout, test framework, the check command, the
|
|
50
|
+
size or complexity thresholds, and the ignore rules.
|
|
51
|
+
|
|
52
|
+
A threshold counts only when it is quoted with the file that states it. With no stated
|
|
53
|
+
threshold, compare against the rest of the scope — and say that the comparison is what
|
|
54
|
+
the finding rests on. Do not import an external style guide, install a tool, or apply a
|
|
55
|
+
number the project never chose.
|
|
56
|
+
|
|
57
|
+
## 3. Read, detect, confirm
|
|
58
|
+
|
|
59
|
+
The outlier list from the script is the reading list and the budget; the report says
|
|
60
|
+
what was left outside it. Walk the signals in
|
|
61
|
+
[`references/signals.md`](./references/signals.md) — duplication, size, coupling,
|
|
62
|
+
cohesion and drift, change pressure, shotgun surgery, test friction, boundary erosion,
|
|
63
|
+
doc-versus-code drift — and remember the script measures per file, not per symbol, so
|
|
64
|
+
nesting, parameter counts, and function length come from reading.
|
|
65
|
+
|
|
66
|
+
A signal is not a candidate until it is confirmed. Read the callers and tests, not just
|
|
67
|
+
the definition: duplication may be deliberate, a large module may be cohesive, an
|
|
68
|
+
"unused" export may be public API. Every kept candidate carries one concrete artifact —
|
|
69
|
+
a path with line numbers, a counted duplicate, a measured size, an import edge, or a
|
|
70
|
+
named test gap. A bug is reported as a bug, never smuggled into the list. A candidate
|
|
71
|
+
without a test is marked "tests first".
|
|
72
|
+
|
|
73
|
+
## 5. Rank
|
|
74
|
+
|
|
75
|
+
Three tiers, and no numeric score — the reasons have to be readable.
|
|
76
|
+
|
|
77
|
+
- **Do next**: contained blast radius, high leverage, small or medium effort, fast
|
|
78
|
+
verification.
|
|
79
|
+
- **Worth planning**: high leverage with more risk or effort, a public surface, or
|
|
96
80
|
missing tests.
|
|
97
|
-
- **Leave it
|
|
98
|
-
|
|
99
|
-
Within a tier, order by leverage
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- The
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
todo list or a short written sequence.
|
|
134
|
-
- Make one structural change per step and verify each step. Never mix in
|
|
135
|
-
formatting, bug fixes, or unrelated cleanup.
|
|
136
|
-
- Finish with the full project check and report the diff, results, and remaining
|
|
137
|
-
risk. Do not commit unless the user asks.
|
|
138
|
-
|
|
139
|
-
The audit is done when the scope is stated, every candidate has `path:line`
|
|
140
|
-
evidence and a verification command, the shortlist is capped, rejected signals
|
|
141
|
-
are explained, and no files were modified.
|
|
81
|
+
- **Leave it**: low confidence, little leverage, or not worth the churn.
|
|
82
|
+
|
|
83
|
+
Within a tier, order by leverage, then safety, then cost.
|
|
84
|
+
|
|
85
|
+
## 6. Report
|
|
86
|
+
|
|
87
|
+
- **Scope and method**: path, file count, what was measured, and what was not examined.
|
|
88
|
+
- **Top three to five**: title, tier, evidence at `path:line`, the structural change,
|
|
89
|
+
expected benefit, risk and blast radius, effort (S/M/L), and the narrow plus full
|
|
90
|
+
verification command.
|
|
91
|
+
- **Rejected signals**: one line each, with the reason it did not qualify.
|
|
92
|
+
- **Next step**: ask which candidate to take, offering the shortlist by name.
|
|
93
|
+
|
|
94
|
+
## 7. From audit to execution
|
|
95
|
+
|
|
96
|
+
When the user picks one, read [`references/handoff.md`](./references/handoff.md)
|
|
97
|
+
before the first edit: a stated behaviour contract, a recorded baseline,
|
|
98
|
+
characterisation tests first when the behaviour is untested, one structural change per
|
|
99
|
+
step, and no mixed cleanup are what keep the promise.
|
|
100
|
+
|
|
101
|
+
## Quality gate
|
|
102
|
+
|
|
103
|
+
- The scope, the file count, and what was not examined are stated.
|
|
104
|
+
- Every candidate has `path:line` evidence, a verification command, a tier, and an
|
|
105
|
+
effort; none carries a numeric score.
|
|
106
|
+
- The rejected list is not empty, and it explains each rejection.
|
|
107
|
+
- No file was modified, and no bug or behaviour change is in the refactor list.
|
|
108
|
+
|
|
109
|
+
## Stop conditions
|
|
110
|
+
|
|
111
|
+
- Nothing worth doing: say so with what was checked instead of inventing work.
|
|
112
|
+
- The request is one specific refactor, not an audit: skip the shortlist and go to
|
|
113
|
+
handoff.
|
|
114
|
+
- The request is a rewrite or a behaviour change: treat it as the design change it is.
|
|
115
|
+
- The scope cannot be read — missing path, secrets, generated or binary files, an
|
|
116
|
+
inaccessible tree: report the limitation and stop.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# After a candidate is chosen
|
|
2
|
+
|
|
3
|
+
The audit is over; this is execution, and its only promise is that behavior does not
|
|
4
|
+
change. These are the rules that keep that promise — the rest is ordinary work.
|
|
5
|
+
|
|
6
|
+
## Before the first edit
|
|
7
|
+
|
|
8
|
+
- **State the behaviour contract.** One paragraph: what must stay true, which callers
|
|
9
|
+
rely on it, and which parts are explicitly free to change. If the contract is
|
|
10
|
+
ambiguous, ask now rather than discovering it in the diff.
|
|
11
|
+
- **Record a baseline.** Run the project's documented check and keep the result. A
|
|
12
|
+
pre-existing failure is not this refactor's to fix, and a refactor that turns one
|
|
13
|
+
test from red to green has changed something nobody asked about.
|
|
14
|
+
- **Add characterisation tests first when the behaviour is untested.** They capture
|
|
15
|
+
what the code does today, including the parts you consider wrong, because that is
|
|
16
|
+
the only net under a structural change.
|
|
17
|
+
|
|
18
|
+
## While it moves
|
|
19
|
+
|
|
20
|
+
- **One structural change per step**, each with its own verification. A rename, an
|
|
21
|
+
extraction, and a move are three steps, not one; a step that cannot be verified on
|
|
22
|
+
its own is too large.
|
|
23
|
+
- **Never mix in** formatting, a bug fix, a dependency bump, or unrelated cleanup. If
|
|
24
|
+
the new structure makes one of those obvious, finish, report it, and do it as its
|
|
25
|
+
own change.
|
|
26
|
+
- **Ceremony by size**: plan mode for a change that spans files, touches a public
|
|
27
|
+
surface, or has a step list you cannot write down up front; a todo list for the rest.
|
|
28
|
+
- **Stop if it turns into a behaviour change.** Say so, and treat it as the design
|
|
29
|
+
change it now is, rather than smuggling it through under the old name.
|
|
30
|
+
|
|
31
|
+
## Finishing
|
|
32
|
+
|
|
33
|
+
- Run the full check, not only the narrow command, and compare against the baseline.
|
|
34
|
+
- Report the diff's shape, what the check said, and what is still risky or unverified.
|
|
35
|
+
- Do not commit unless the user asks.
|