@jawache/flow 0.1.2 → 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.
- package/README.md +52 -271
- package/dist/flow.mjs +12293 -106
- package/dist/index.mjs +7945 -18
- package/dist/packs.mjs +9477 -115
- package/dist/types/adapter/archive.d.ts +8 -1
- package/dist/types/adapter/claude.d.ts +67 -18
- package/dist/types/adapter/domain.d.ts +991 -154
- package/dist/types/adapter/product.d.ts +5 -0
- package/dist/types/checks/domain.d.ts +134 -72
- package/dist/types/engine/domain.d.ts +75 -17
- package/dist/types/engine/state.d.ts +7 -4
- package/dist/types/index.d.ts +4 -3
- package/dist/types/language/domain.d.ts +227 -21
- package/dist/types/packs/docs.d.ts +17 -14
- package/dist/types/packs/flow.d.ts +15 -5
- package/dist/types/packs/git.d.ts +20 -25
- package/dist/types/packs/justfile.d.ts +3 -3
- package/dist/types/packs/secrets.d.ts +32 -4
- package/dist/types/packs/work.d.ts +22 -6
- package/docs/user/migrations/0.1.0.md +142 -0
- package/docs/user/migrations/0.1.1.md +44 -0
- package/docs/user/migrations/0.1.2.md +58 -0
- package/docs/user/migrations/0.2.0.md +113 -0
- package/docs/user/migrations/0.2.1.md +41 -0
- package/docs/user/migrations/0.2.2.md +104 -0
- package/docs/user/migrations/0.2.3.md +42 -0
- package/docs/user/migrations/0.3.0.md +289 -0
- package/docs/user/migrations/index.md +45 -0
- package/package.json +5 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** The top-level chat:
|
|
1
|
+
/** The top-level chat: not spawned, no agent type. The supervisor's rung. */
|
|
2
2
|
export declare const parent: import("../index.ts").Category;
|
|
3
3
|
/**
|
|
4
4
|
* A supervised builder — one phase of a plan, its own to write.
|
|
@@ -17,6 +17,20 @@ export declare const builder: import("../index.ts").Category;
|
|
|
17
17
|
* a frontmatter list somebody can widen.
|
|
18
18
|
*/
|
|
19
19
|
export declare const checker: import("../index.ts").Category;
|
|
20
|
+
/** The rungs that may be named as writers — a checker is the rung that never writes. */
|
|
21
|
+
export type Writer = "builder" | "parent";
|
|
22
|
+
/** What a repo may say about how it runs the lifecycle. Every field is optional. */
|
|
23
|
+
export interface Rungs {
|
|
24
|
+
/**
|
|
25
|
+
* The rungs allowed to change files in a worktree, turning on ONE WRITER: a write or a delete by
|
|
26
|
+
* any other actor is refused — an Edit before it lands, anything else put back after.
|
|
27
|
+
*
|
|
28
|
+
* OPTIONAL, and with no list there is no entry at all. One writer is for a worktree several
|
|
29
|
+
* agents share with one builder in it; in a repo where a helper agent of any other kind is
|
|
30
|
+
* expected to write, it would refuse that helper's every change, so it is off until a repo asks.
|
|
31
|
+
*/
|
|
32
|
+
readonly writers?: readonly Writer[];
|
|
33
|
+
}
|
|
20
34
|
/**
|
|
21
35
|
* The WORK LIFECYCLE's rungs: who is acting, and what each rung may do.
|
|
22
36
|
*
|
|
@@ -38,12 +52,14 @@ export declare const checker: import("../index.ts").Category;
|
|
|
38
52
|
* so in a repo that does not run the lifecycle they are five rules that can never fire.
|
|
39
53
|
* @adopt Bind it only in a repo whose tasks are actually run through the lifecycle. The three rung
|
|
40
54
|
* rules are scoped to categories this pack defines, so they judge nobody until a session is
|
|
41
|
-
* classified as a builder or a checker — a human at a keyboard is neither.
|
|
55
|
+
* classified as a builder or a checker — a human at a keyboard is neither. Name `writers` to let
|
|
56
|
+
* only those rungs change files in the worktree.
|
|
42
57
|
*/
|
|
43
58
|
export declare const work: import("../index.ts").Pack<{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
builderWrites?: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "description", "write" | "delete">;
|
|
60
|
+
planIsTheParents: import("../index.ts").GuardrailSentence<"at" | "for" | "check" | "message" | "test" | "description", "command">;
|
|
61
|
+
checkersDoNotWrite: import("../index.ts").GuardrailSentence<"at" | "for" | "check" | "message" | "test" | "description", "write">;
|
|
62
|
+
ticksAreTheChilds: import("../index.ts").GuardrailSentence<"at" | "for" | "check" | "message" | "test" | "description", "command">;
|
|
47
63
|
noAgentInboxItems: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "description", "command">;
|
|
48
64
|
noShellSubstitutionInProse: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "description", "command">;
|
|
49
|
-
}, undefined>;
|
|
65
|
+
}, Rungs | undefined>;
|
|
@@ -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
|
+
```
|