@jawache/flow 0.2.1 → 0.3.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.
@@ -0,0 +1,142 @@
1
+ ---
2
+ summary: "upgrade to 0.1.0 from a checkout linked with `npm link`"
3
+ ---
4
+
5
+ # Upgrade to 0.1.0
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.1.0 is the first release published to npm. Before it, a repo ran flow from a clone of the flow repository, linked with `npm link`. This page moves a repo from such a checkout to the published package, and covers what changed in the packs before the release: the `guard` pack is renamed `flow`, some checks left `@jawache/flow/packs`, and some pack entries were removed or renamed. The hook registrations, `.githooks/pre-commit` and the CLI commands are the same as in the checkout.
10
+
11
+ ## 1 · Install 0.1.0
12
+
13
+ Replace the link with the published package. Keep `flow` on PATH: the hook registrations and `.githooks/pre-commit` run it by name.
14
+
15
+ ```sh
16
+ npm unlink -g @jawache/flow
17
+ npm i -g @jawache/flow@0.1.0 # puts `flow` on PATH
18
+ npm i -D @jawache/flow@0.1.0 # in a repo with a package.json: pins the version in its lockfile
19
+ ```
20
+
21
+ Commit `package.json` and the lockfile together. If the repo binds the `node` pack, the commit message needs a line `new-dep: @jawache/flow — <why>`, or `node.newDependencyNeedsReason` refuses the commit.
22
+
23
+ `flow status` does not check that `flow` is on PATH. Without it, the session hooks fail with nothing blocked, and `.githooks/pre-commit` refuses every commit.
24
+
25
+ ## 2 · Required changes
26
+
27
+ ### 2.1 · Rename the `guard` pack to `flow`
28
+
29
+ **Applies if:** `grep -rnE '\bguard\b' flow.config.ts` (and your own pack files) prints an import or a binding of `guard`. After the install, `flow status` exits 2 with `does not provide an export named 'guard'`.
30
+
31
+ **Change:**
32
+
33
+ ```ts
34
+ // before
35
+ import { guard } from "@jawache/flow/packs";
36
+ pack(guard, { packs: ["./guards/house.ts"] })
37
+ override(guard.someEntry)
38
+ // after
39
+ import { flow } from "@jawache/flow/packs";
40
+ pack(flow, { packs: ["./guards/house.ts"] })
41
+ override(flow.someEntry)
42
+ ```
43
+
44
+ `packs` is required. `[]` is a valid value. Leaving it out refuses with `missing-parameter`.
45
+
46
+ **Why:** the pack that protects the guard itself is named after the product.
47
+
48
+ ### 2.2 · Replace checks removed from `@jawache/flow/packs`
49
+
50
+ **Applies if:** this prints a line:
51
+
52
+ ```sh
53
+ grep -rnE 'conventionalCommit|noGitDiscard|noHandEditedVersion|justfileDocs|lockfileInStep|newCommandNeedsCaller|\bSurface\b' flow.config.ts <your pack files>
54
+ ```
55
+
56
+ **Change:** for `conventionalCommit`, `noGitDiscard` and `noHandEditedVersion`, bind the `git` pack. For `justfileDocs`, bind the `justfile` pack. For `lockfileInStep`, bind the `node` pack. Each pack includes the rule. To keep a rule without its pack, copy the check into your own pack.
57
+
58
+ Replace `newCommandNeedsCaller` with `commitReason` from `@jawache/flow`. Its regex patterns are now listed in the binding, one capture group each:
59
+
60
+ ```ts
61
+ // before
62
+ newCommandNeedsCaller({ surfaces: [{ file: "flow.ts", known: ["test", "hook"] }], token: "new-verb:" })
63
+ // after
64
+ commitReason({
65
+ diffAdds: [{ file: "flow.ts", patterns: ['command\\s*===\\s*"([a-z][a-z-]*)"'], known: ["test", "hook"] }],
66
+ token: "new-verb:",
67
+ })
68
+ ```
69
+
70
+ The old check matched three patterns without listing them: `'case\\s+"([a-z][a-z-]*)"\\s*:'`, `'command\\s*===\\s*"([a-z][a-z-]*)"'` and `'sub\\s*===\\s*"([a-z][a-z-]*)"'`. List the ones your surface uses. A test case that records the diff changes its key from `"git diff HEAD -- flow.ts"` to `"diff HEAD -- 'flow.ts'"`.
71
+
72
+ **Why:** a check lives in the pack that uses it, or in the grammar when more than one does.
73
+
74
+ ### 2.3 · Remove or rename overrides of entries that are gone
75
+
76
+ **Applies if:** this prints a line:
77
+
78
+ ```sh
79
+ grep -rnE 'override\((fcis\.(noLogicInTypesOrIndex|strictLayout)|typescript\.(tsconfigFromBase|eslintFromBase)|secrets\.dxSeam)' flow.config.ts <your pack files>
80
+ ```
81
+
82
+ After the install, `flow status` reports `unknown-entry` and the config does not load.
83
+
84
+ **Change:**
85
+
86
+ - `fcis.noLogicInTypesOrIndex`, `fcis.strictLayout` and `secrets.dxSeam` are removed: delete their overrides.
87
+ - `typescript.tsconfigFromBase` is now `typescript.tsconfigStrict`: rename the override.
88
+ - `typescript.eslintFromBase` exists only when the `typescript` pack is given `eslintBase` (2.4).
89
+
90
+ ### 2.4 · Name your base files to the `typescript` pack
91
+
92
+ **Applies if:** the repo has a `tsconfig.base.json` or an `eslint.config.base.js` that its other configs extend.
93
+
94
+ **Change:**
95
+
96
+ ```ts
97
+ // before
98
+ pack(typescript, { typecheck, lint })
99
+ // after
100
+ pack(typescript, { typecheck, lint, tsconfigBase: "tsconfig.base.json", eslintBase: "./eslint.config.base.js" })
101
+ ```
102
+
103
+ **Why:** the pack no longer assumes base files exist. Without these two settings it does not check that every config extends them.
104
+
105
+ ### 2.5 · Remove `names` from the `secrets` pack
106
+
107
+ **Applies if:** `pack(secrets, { … names: … })` appears in the config.
108
+
109
+ **Change:** delete `names:`. It is ignored at run time and is a type error in the editor. `dx` and `encrypt` are now optional and default to dotenvx.
110
+
111
+ ## 3 · What now blocks or passes differently
112
+
113
+ - **`flow.editingTheGuardrails` is shown for Claude Code settings too:** `.claude/settings.json`, `.claude/settings.local.json`, `.claude/agents/**` and `.claude/skills/**`. Pass `hostSurface: []` to the `flow` pack to turn this off.
114
+ - **`git.noGitDiscard` refuses more:** a bare `git checkout <tracked path>` is refused, not only the form with `--`. With `cd X && git …` or `git -C X`, it checks repo `X`.
115
+ - **`git.noForcePush` matches only a real command**, so the words in prose or a heredoc no longer trip it.
116
+ - **`flow status` exits 2 for every config that does not load.** A config the grammar refused used to exit 1. Check any script that tells the two apart.
117
+ - **While the grammar refuses the config, the agent may still write the config and its pack files** to fix it, and the turn can end. Every write used to be refused.
118
+ - **A CommonJS repo loads its config.** A `package.json` with `"type": "commonjs"`, or with no `type`, used to fail with `Cannot use import statement outside a module`.
119
+ - **`node.dependencies` watches all five lockfiles.**
120
+
121
+ ## 4 · Optional
122
+
123
+ New pack parameters, all with defaults:
124
+
125
+ - `docs`: `root`, `allow`
126
+ - `tdd`: `tests`
127
+ - `fcis`: `sibling`, `tests`
128
+ - `git`: `versionFile`, `types`
129
+ - `justfile`: `tools`, and `recipes`, which turns on the new `justfile.useTheRecipe` entry
130
+ - `secrets`: `publicPrefixes`, `localFiles`, `envFiles`, `extraSecretShapes`
131
+ - `typescript`: `strictOptions`
132
+
133
+ New exports from `@jawache/flow`: `DiffAdds`, `gitDirPrefix`, `escapeRe`, `substitutionInProse` and `SUBSTITUTION_MESSAGE`.
134
+
135
+ ## 5 · Verify
136
+
137
+ ```sh
138
+ command -v flow && flow --version # prints a path, then 0.1.0
139
+ flow status # green
140
+ flow test # every case green
141
+ flow commit $(git ls-files --cached --others --exclude-standard) # exit 0
142
+ ```
@@ -0,0 +1,44 @@
1
+ ---
2
+ summary: "upgrade to 0.1.1 from 0.1.0"
3
+ ---
4
+
5
+ # Upgrade to 0.1.1
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.1.1 adds two things a config can use: its own ast-grep grammar, and a breadcrumb shown before a shell command. No changes are needed: no pack changed, nothing was removed from the exports, and the hook registrations and `.githooks/pre-commit` are the same as in 0.1.0.
10
+
11
+ ## 1 · Install 0.1.1
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.1.1 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.1.1 # when `flow` is installed globally
16
+ ```
17
+
18
+ ## 2 · Required changes
19
+
20
+ None.
21
+
22
+ ## 3 · What now blocks or passes differently
23
+
24
+ Nothing.
25
+
26
+ ## 4 · Optional
27
+
28
+ - **Declare an ast-grep grammar** for a language with no published grammar package, such as astro, vue or svelte. Pass it in the second argument to `defineConfig`:
29
+
30
+ ```ts
31
+ export default defineConfig([pack(house)], {
32
+ grammars: [{ name: "astro", libraryPath: "grammars/astro.dylib", extensions: ["astro"] }],
33
+ });
34
+ ```
35
+
36
+ `flow status` then prints a `grammars` line. If the library file is missing, that line is red, and every rule on that language refuses.
37
+ - **Show a note before a shell command runs:** `breadcrumb().at(command).on("npm install*", "npm i *").text("…")`. `.on(…)` matches the command line. Without `.on(…)` the note is shown on every shell command. It uses the existing `PreToolUse` registration, so `flow init` does not need to run again.
38
+
39
+ ## 5 · Verify
40
+
41
+ ```sh
42
+ flow status # green
43
+ flow test # every case green
44
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ summary: "upgrade to 0.1.2 from 0.1.1"
3
+ ---
4
+
5
+ # Upgrade to 0.1.2
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.1.2 refuses a `depcruise` layer written as a regex, and lets a layer leave holes in itself. Only a repo with its own `depcruise` rule is affected: no shipped pack uses `depcruise`. The exports, the hook registrations and `.githooks/pre-commit` are the same as in 0.1.1.
10
+
11
+ ## 1 · Install 0.1.2
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.1.2 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.1.2 # when `flow` is installed globally
16
+ ```
17
+
18
+ ## 2 · Required changes
19
+
20
+ ### 2.1 · Rewrite a `depcruise` layer written as a regex
21
+
22
+ **Applies if:** after the install, `flow status` exits 2 and `flow test` fails, both reporting `bad-check-options` with the layer and the entry. To find them before you install, search the `layers:` of your own `depcruise` rules for an entry that starts with `^`, contains `(?` or `|`, or ends with `$`:
23
+
24
+ ```sh
25
+ grep -rnE '"\^|\(\?|\||\$"' flow.config.ts <your pack files>
26
+ ```
27
+
28
+ While the config does not load, flow refuses every write except a write that fixes the config.
29
+
30
+ **Change:** write the layer as globs. A part the layer must leave out goes in `not`:
31
+
32
+ ```ts
33
+ // before
34
+ layers: { pages: ["^src/pages/(?!app/|admin/)"] }
35
+ // after
36
+ layers: { pages: { path: ["src/pages/**"], not: ["src/pages/app/**", "src/pages/admin/**"] } }
37
+ ```
38
+
39
+ **Why:** a layer is a list of globs. In 0.1.1, a regex was read as literal text, so the fence loaded and reported itself armed but matched no file.
40
+
41
+ Two more refusals arrive with this: a layer with a `not` list cannot be listed in another layer's `only`, and `not: ["node:*"]` is refused.
42
+
43
+ ## 3 · What now blocks or passes differently
44
+
45
+ A fence that used to match nothing starts to work once its layers are globs. A commit that crosses it, and passed in 0.1.1, is now refused.
46
+
47
+ ## 4 · Optional
48
+
49
+ - **Leave holes in a layer** with `{ path: [...], not: [...] }`, the new `Layer` type.
50
+ - **Report unusable options from your own check:** `withFaults` and `faultsOf`, exported from `@jawache/flow`, turn a check's own complaint about its options into a load refusal.
51
+
52
+ ## 5 · Verify
53
+
54
+ ```sh
55
+ flow status # green
56
+ flow test # every case green
57
+ flow commit $(git ls-files --cached --others --exclude-standard) # exit 0, or the fence refusals in section 3
58
+ ```
@@ -0,0 +1,113 @@
1
+ ---
2
+ summary: "upgrade to 0.2.0 from 0.1.2"
3
+ ---
4
+
5
+ # Upgrade to 0.2.0
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.2.0 makes flow judge every change a tool call makes to the tree, not only the changes the Edit and Write tools make. A change a shell command, an MCP tool or a subagent makes is checked after it lands, and a refused change is put back. The hook registrations change to make that work, so every repo re-runs `flow init`. A repo that overrides `docs.noMarkdownInUserDocs` must remove the override, and a repo with its own `banCommands` rule about heredoc text must set `matchHeredocs: true`.
10
+
11
+ ## 1 · Install 0.2.0
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.2.0 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.2.0 # when `flow` is installed globally
16
+ ```
17
+
18
+ ## 2 · Required changes
19
+
20
+ ### 2.1 · Re-run `flow init` to update the hook registrations
21
+
22
+ **Applies if:** after the install, `flow status` prints a red `hooks:` line that says one or both of these:
23
+
24
+ - `does not call flow for PostToolUseFailure — re-run flow init`
25
+ - `calls flow for PostToolUse on some tools only — an older flow registered it that way`
26
+
27
+ Every repo upgrading from 0.1.2 has both.
28
+
29
+ **Change:** run `flow init` in the repo. It keeps the existing `flow.config.ts` and `.githooks/pre-commit`. In the Claude Code settings file it widens the `PostToolUse` registration to every tool, and adds `PostToolUseFailure`. The settings file is the user-level one, `~/.claude/settings.json`, or `$CLAUDE_CONFIG_DIR/settings.json` when that variable is set. It is not the repo's `.claude/settings.json`. Afterwards flow's five registrations are:
30
+
31
+ ```json
32
+ "SessionStart": [{ "matcher": "startup|resume|clear|compact", "hooks": [{ "type": "command", "command": "flow hook session-start" }] }],
33
+ "PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "flow hook pre-tool-use" }] }],
34
+ "PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "flow hook post-tool-use" }] }],
35
+ "PostToolUseFailure": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "flow hook post-tool-use-failure" }] }],
36
+ "Stop": [{ "hooks": [{ "type": "command", "command": "flow hook stop" }] }]
37
+ ```
38
+
39
+ If the settings file does not parse (a comment or a trailing comma), `flow init` writes nothing to it and says so. Fix the JSON and run `flow init` again.
40
+
41
+ Restart any Claude Code session that was open during the upgrade. A tool call that started under the old version prints one `flow — could not see what this call changed` message when it finishes.
42
+
43
+ **Why:** flow now takes a snapshot of the tree before every tool call and compares it after, so it needs the after-call hook for every tool, including calls that fail.
44
+
45
+ ### 2.2 · Remove any override of `docs.noMarkdownInUserDocs`
46
+
47
+ **Applies if:** `grep -rn "noMarkdownInUserDocs" flow.config.ts` (and your own pack files) prints a line. After the install, `flow status` reports `unknown-entry` for it, and the config does not load. While it does not load, flow refuses every write except a write that fixes the config.
48
+
49
+ **Change:** delete the `override(docs.noMarkdownInUserDocs)` line. The `docs` pack no longer refuses markdown under `docs/user/`. To keep refusing it, add the entry to your own pack. This is the entry 0.1.2 shipped, with a shorter message:
50
+
51
+ ```ts
52
+ noMarkdownInUserDocs: guardrail()
53
+ .at(write, commit)
54
+ .on("docs/user/**/*.md", "docs/user/**/*.mdx")
55
+ .check(protectedPath({}))
56
+ .message("User docs are HTML. Markdown belongs in docs/agent/.")
57
+ .test({ block: [{ path: "docs/user/guide.md", content: "# hi" }] }),
58
+ ```
59
+
60
+ **Why:** user docs may now be markdown. A repo that bound `docs` without an override loses the refusal with no error.
61
+
62
+ ### 2.3 · Set `matchHeredocs: true` on a `banCommands` rule about heredoc text
63
+
64
+ **Applies if:** the repo has its own `banCommands` rule whose patterns must match text a command is fed through a heredoc, such as a commit message. `flow test` reports a failing `block` case where the banned text appears only inside a heredoc.
65
+
66
+ **Change:**
67
+
68
+ ```ts
69
+ // before
70
+ banCommands({ ban: ["…"] })
71
+ // after
72
+ banCommands({ ban: ["…"], matchHeredocs: true })
73
+ ```
74
+
75
+ **Why:** `banCommands` now leaves heredoc bodies out of the text it matches, so a script that only contains the words of a banned command is not refused. A heredoc fed to a shell (`bash`, `sh`, `zsh` or `eval`) is still matched. The shipped `git.noAiAttributionInCommits` and both `noShellSubstitutionInProse` entries already set it.
76
+
77
+ ### 2.4 · Clear a red `snapshot:` line
78
+
79
+ **Applies if:** `flow status` prints a red `snapshot:` line saying a rule `guards <path>, which git ignores`.
80
+
81
+ **Change:** list the path's glob in `snapshotInclude`, the second argument to `defineConfig`, or narrow the rule with `.on(…)` or `.ignore(…)`:
82
+
83
+ ```ts
84
+ export default defineConfig([pack(house)], { snapshotInclude: [".env"] });
85
+ ```
86
+
87
+ **Why:** the snapshot records the files git lists, tracked or untracked, so a shell write to a path git ignores is never detected unless `snapshotInclude` lists it. `secrets.envEncrypted` on a gitignored `.env` file is the usual case.
88
+
89
+ ## 3 · What now blocks or passes differently
90
+
91
+ - **Shell commands, MCP tools and subagents are checked.** A write or delete rule now runs on a change any tool call makes: a heredoc, `sed -i`, a script, a formatter, a code generator, `npm install`. A refused file is put back, under `flow — write reverted after it landed` or `flow — delete reverted after it landed`. If a rule refuses generated output or a tool's own files, narrow it with `.ignore(…)`. Keep build output, logs and caches in paths git ignores.
92
+ - **A background command's later writes are judged.** A write a background process makes after its tool call returns is judged by the next tool call and put back if refused.
93
+ - **`rm x && cat > x` is judged as a write** of `x`, not only as a delete.
94
+ - **`banCommands` leaves heredoc bodies out** unless the rule sets `matchHeredocs: true` (2.3). The shipped force-push ban, the justfile recipe map and the work pack's rules now let heredoc text through.
95
+ - **A heredoc is the commit message only when the commit reads standard input**, with `-F -` or `--file=-`. `commitReason` and the git pack's message rules no longer check `git commit <<EOF` without `-F -`. Commit with `git commit -F - <<'EOF'`.
96
+ - **A breadcrumb is shown for a file a shell command reads.** `cat`, `head`, `sed -n`, `grep`, `rg`, `find`, `ls`, `<` and a git pathspec count as reads, and the note is shown before the command runs. A refused command shows no notes.
97
+ - **A folder that is not a git checkout is refused on every tool call**, because flow has nothing to take a snapshot with. Run `git init`, or remove `flow.config.ts` from a folder that is not a repo.
98
+ - **`flow status` and `flow facts` read transcripts from `$CLAUDE_CONFIG_DIR/projects`** when that variable is set.
99
+ - **Shipped text changed:** the `flow` pack's orientation note, and the `docs` pack's messages, which now say user docs may be HTML or markdown.
100
+
101
+ ## 4 · Optional
102
+
103
+ - **Let only named agents write:** `oneWriter({ writers: [...] })` refuses a write or delete by a session that has none of the listed categories. `pack(work, { writers: ["builder", "parent"] })` turns it on for the `work` pack. Test cases accept `actor: [...]`.
104
+ - **Choose what a refusal puts back:** `defineConfig([...], { revert: "all" })` puts back every file the refused call changed. The default, `"refused"`, puts back only the refused files.
105
+ - **`flow status`** shows the live session's permission mode. **`flow facts`** counts shell reads and writes, and calls per mode.
106
+
107
+ ## 5 · Verify
108
+
109
+ ```sh
110
+ flow status # green, with: ✓ hooks: … SessionStart · PreToolUse · PostToolUse · PostToolUseFailure · Stop
111
+ flow test # every case green
112
+ flow commit $(git ls-files --cached --others --exclude-standard) # exit 0
113
+ ```
@@ -0,0 +1,41 @@
1
+ ---
2
+ summary: "upgrade to 0.2.1 from 0.2.0"
3
+ ---
4
+
5
+ # Upgrade to 0.2.1
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.2.1 rewords two breadcrumbs in the `justfile` pack. No changes are needed: the config, the exports, the hook registrations and `.githooks/pre-commit` are the same as in 0.2.0.
10
+
11
+ ## 1 · Install 0.2.1
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.2.1 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.2.1 # when `flow` is installed globally
16
+ ```
17
+
18
+ ## 2 · Required changes
19
+
20
+ None.
21
+
22
+ ## 3 · What now blocks or passes differently
23
+
24
+ Nothing blocks or passes differently. Two breadcrumb texts changed, and a breadcrumb is a note that never refuses anything:
25
+
26
+ - **`justfile.orientation`**, shown at `session`, now says a script earns a recipe only when it is run regularly, across tasks. It used to say that anything a person should also run becomes a recipe.
27
+ - **`justfile.toolsHome`**, shown at `touch` on the tools folder, now lists a seed and a release's data step as one-offs that get no recipe.
28
+
29
+ A repo that binds `pack(justfile, …)` without overriding these two entries shows the new text. An agent may now argue against adding a recipe for a one-off script.
30
+
31
+ ## 4 · Optional
32
+
33
+ - If the repo added recipes for one-off scripts (a seed, a backfill, a data fix), remove those recipes from the justfile and keep the scripts.
34
+ - If the repo overrode `justfile.orientation` or `justfile.toolsHome` only to work around the old wording, remove the override.
35
+
36
+ ## 5 · Verify
37
+
38
+ ```sh
39
+ flow status # green
40
+ flow test # every case green
41
+ ```
@@ -0,0 +1,104 @@
1
+ ---
2
+ summary: "upgrade to 0.2.2 from 0.2.1"
3
+ ---
4
+
5
+ # Upgrade to 0.2.2
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.2.2 changes how the commit gate gets its list of files. The pre-commit hook now runs `flow hook commit` and passes nothing, and flow reads from git the files the commit adds, changes and deletes. A rule bound at both `deletion` and `commit` now also runs on each file a commit deletes, and on the old path of a renamed file. `protectedPath({ existingOnly: true })` now treats a file as new when it was not there before the change. One change is required: the line in `.githooks/pre-commit` that runs `flow commit`.
10
+
11
+ ## 1 · Install 0.2.2
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.2.2 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.2.2 # when `flow` is installed globally
16
+ ```
17
+
18
+ `npm ls @jawache/flow` and `npm ls -g @jawache/flow` show which of the two the repo uses. The hook registrations and `.githooks/pre-commit` run the `flow` that is on PATH.
19
+
20
+ ## 2 · Required changes
21
+
22
+ ### 2.1 · Make the pre-commit hook run `flow hook commit`
23
+
24
+ **Applies if:** `grep -n "flow commit" .githooks/pre-commit` prints a line. After the install, `flow status` also prints:
25
+
26
+ ```text
27
+ ✗ commit-gate: .githooks/pre-commit calls the old `flow commit`, which leaves every deleted file unchecked — replace its `flow commit …` line with `exec flow hook commit`.
28
+ ```
29
+
30
+ **Change:** edit the file. Do not delete it: the `flow` pack refuses a delete of `.githooks/pre-commit`. The hook `flow init` wrote before 0.2.2 ends with these lines:
31
+
32
+ ```sh
33
+ staged=$(git diff --cached --name-only --diff-filter=ACMR)
34
+ [ -z "$staged" ] && exit 0
35
+ # shellcheck disable=SC2086
36
+ exec flow commit $staged
37
+ ```
38
+
39
+ Replace all four with one line:
40
+
41
+ ```sh
42
+ exec flow hook commit
43
+ ```
44
+
45
+ If the hook runs other commands as well, keep them, and put `exec flow hook commit` last.
46
+
47
+ **Why:** the old lines pass flow a list that leaves out deleted files, and they exit before flow runs when a commit only deletes files. `flow commit` still works in 0.2.2, so nothing breaks before you make this change, but deletions are not checked until you do.
48
+
49
+ ### 2.2 · Replace any other command that runs `flow commit` with a file list
50
+
51
+ **Applies if:** `git grep -n "flow commit" -- ':!.githooks/pre-commit'` prints a line, for example a justfile recipe or a CI step.
52
+
53
+ **Change:** `flow commit <files…>` now ignores its file list and checks the staged changes. A command that checked a list of files on purpose checks the staged changes instead, which is usually fewer files. To check every file in the repo, use `--all`:
54
+
55
+ ```sh
56
+ # before
57
+ flow commit $(git ls-files --cached --others --exclude-standard)
58
+ # after
59
+ flow hook commit --all
60
+ ```
61
+
62
+ **Why:** git is now the one source of the file list. `flow hook commit` refuses a file list, and `flow commit` ignores one.
63
+
64
+ ## 3 · What now blocks or passes differently
65
+
66
+ - **A rule bound at both `deletion` and `commit` now runs on each file a commit deletes.** It is called with the file as a delete (`ctx.moment` is `delete`), with the content the last commit has. A rename is a deletion of the old path plus an added file, so the rule runs on the old path. For example, `protectedPath` bound with `.at(write, deletion, commit)` now refuses a commit that deletes or renames a file it protects. In 0.2.1 that commit passed. If a commit is now refused for this, keep the file, or change the rule.
67
+ - **A rule bound at `commit` alone is not called with deleted files.** It checks only the files the commit will contain, as before.
68
+ - **`protectedPath({ existingOnly: true })` lets a shell command add a new file.** In 0.2.0 and 0.2.1, a new file that a shell command created was reverted with the append-only message. Now it is kept. At commit, a file is new when the last commit does not have it. In 0.2.1, every staged file counted as existing, so a rule bound at `commit` refused every new file.
69
+ - **A config that does not load refuses every commit, including one with nothing staged.** The old hook exited before flow ran when nothing was staged.
70
+
71
+ ## 4 · Optional
72
+
73
+ - **Refuse a deleted or renamed file at commit as well.** Bind an append-only rule at all three moments:
74
+
75
+ ```ts
76
+ appendOnly: guardrail()
77
+ .at(write, deletion, commit)
78
+ .on("migrations/**")
79
+ .check(protectedPath({ existingOnly: true }))
80
+ .message("A migration already written is history — add a new one instead.")
81
+ .test({
82
+ pass: [
83
+ { path: "migrations/002.sql", content: "new" },
84
+ { staged: ["migrations/002.sql"], added: ["migrations/002.sql"], world: { fs: { "migrations/002.sql": "new" } } },
85
+ ],
86
+ block: [
87
+ { path: "migrations/001.sql", content: "edited", world: { fs: { "migrations/001.sql": "old" } } },
88
+ { staged: ["migrations/001.sql"], added: [], world: { fs: { "migrations/001.sql": "edited" } } },
89
+ { staged: [], deleted: { "migrations/001.sql": "old" } },
90
+ ],
91
+ }),
92
+ ```
93
+
94
+ - **Read `ctx.file.existed` in your own checks.** It is `true` or `false` for a change a shell command made, and for a file at commit. See the [`ctx` reference](../reference/ctx.md).
95
+ - **Test cases accept three new keys:** `existed` on a `{ path, content }` case, and `added` and `deleted` on a `{ staged }` case. See [Cases](../reference/config.md#cases).
96
+ - **`flow hook commit --all`** runs the commit gate over every file git lists, tracked or not, as if all of them were staged.
97
+
98
+ ## 5 · Verify
99
+
100
+ ```sh
101
+ flow status # green, with: ✓ commit-gate: .githooks/pre-commit runs `flow hook commit` over the staged changes
102
+ flow test # every case green
103
+ flow hook commit --all # exit 0, or only the refusals section 3 describes
104
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ summary: "upgrade to 0.2.3 from 0.2.2"
3
+ ---
4
+
5
+ # Upgrade to 0.2.3
6
+
7
+ [All guides](./index.md)
8
+
9
+ 0.2.3 widens `git.noAiAttributionInCommits`, the `git` pack's rule against crediting Claude or Anthropic in a commit. It now also reads a commit made with git's own options before `commit`, such as `git -C <dir> commit`. No changes are needed.
10
+
11
+ ## 1 · Install 0.2.3
12
+
13
+ ```sh
14
+ npm i -D @jawache/flow@0.2.3 # when flow is a dev dependency of the repo
15
+ npm i -g @jawache/flow@0.2.3 # when `flow` is installed globally
16
+ ```
17
+
18
+ ## 2 · Required changes
19
+
20
+ None.
21
+
22
+ ## 3 · What now blocks or passes differently
23
+
24
+ `git.noAiAttributionInCommits` now refuses a commit command that has git options between `git` and `commit` and credits Claude or Anthropic in its message: `git -C <dir> commit …`, `git -c key=value commit …` and `git --git-dir=… commit …`. An agent working in a git worktree often commits with `git -C`, and those commits passed in 0.2.2. If one is now refused, remove the `Co-Authored-By` line, the "Generated with Claude" line or the Claude Code link from the message, and commit again.
25
+
26
+ ## 4 · Optional
27
+
28
+ - **Bind the `git` pack, if the repo does not.** `git.noAiAttributionInCommits` is in the `git` pack, so a repo that binds only other packs has no rule against the credit line. Add the binding, with the command that cuts a release in your repo:
29
+
30
+ ```ts
31
+ pack(git, { release: "just release" }),
32
+ ```
33
+
34
+ The `git` pack also checks that every commit header is a Conventional Commits header. If the repo's headers use a type outside the default list, such as `review:`, pass the `types` parameter. It replaces the default list, so list every type the repo uses.
35
+
36
+ ## 5 · Verify
37
+
38
+ ```sh
39
+ flow status # green
40
+ flow test # every case green
41
+ flow hook commit --all # exit 0
42
+ ```