@jawache/flow 0.1.2 → 0.2.1
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 +15 -9
- package/dist/flow.mjs +94 -73
- package/dist/index.mjs +22 -16
- package/dist/packs.mjs +103 -68
- package/dist/types/adapter/archive.d.ts +8 -1
- package/dist/types/adapter/claude.d.ts +46 -5
- package/dist/types/adapter/domain.d.ts +369 -9
- package/dist/types/checks/domain.d.ts +71 -14
- package/dist/types/engine/domain.d.ts +10 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/language/domain.d.ts +23 -0
- package/dist/types/packs/docs.d.ts +3 -4
- package/dist/types/packs/work.d.ts +18 -2
- package/package.json +1 -1
|
@@ -33,9 +33,20 @@ export declare const textBan: (options: {
|
|
|
33
33
|
* (which is right for a file, where a hit must be able to name its line) every one of those
|
|
34
34
|
* patterns matches nothing, silently: the rule loads, counts, and cannot catch the thing it
|
|
35
35
|
* names.
|
|
36
|
+
*
|
|
37
|
+
* WITHOUT ITS HEREDOC BODIES (`elideHeredocs`), by default. A heredoc is text fed to a program,
|
|
38
|
+
* and a python script or a note that names a banned command is not a use of it: matched whole,
|
|
39
|
+
* `python3 - <<'PY'` scripts were refused for naming a recipe in a string. Only the bodies go —
|
|
40
|
+
* the newlines the patterns above span are kept — and a body a shell reads stays, because it is
|
|
41
|
+
* commands.
|
|
42
|
+
*
|
|
43
|
+
* `matchHeredocs: true` matches the whole command instead. A rule about what a command is FED —
|
|
44
|
+
* the text of a commit message given on stdin, `git commit -F - <<'EOF'` — has to see the body,
|
|
45
|
+
* and would be blind to that message without it.
|
|
36
46
|
*/
|
|
37
47
|
export declare const banCommands: (options: {
|
|
38
48
|
ban: readonly string[];
|
|
49
|
+
matchHeredocs?: boolean;
|
|
39
50
|
}) => Check;
|
|
40
51
|
/**
|
|
41
52
|
* A `banCommands` pattern for: a command that writes permanent prose, with a BACKTICK inside a
|
|
@@ -86,6 +97,24 @@ export declare const SUBSTITUTION_MESSAGE: string;
|
|
|
86
97
|
export declare const protectedPath: (options: {
|
|
87
98
|
existingOnly?: boolean;
|
|
88
99
|
}) => Check;
|
|
100
|
+
/**
|
|
101
|
+
* Only the listed categories may change a file: a write or a delete by any other actor is refused.
|
|
102
|
+
*
|
|
103
|
+
* It reads `ctx.actor` — the categories the acting session wears, recognised from what the host
|
|
104
|
+
* wrote about it, never from a claim it made — and nothing about the file. A session wearing none
|
|
105
|
+
* of `writers` is refused whatever it wrote, and one wearing none at all is refused too: "no
|
|
106
|
+
* category" is not "every category".
|
|
107
|
+
*
|
|
108
|
+
* `.for(…)` on an entry says which actors a RULE applies to; this says which actors may WRITE. Both
|
|
109
|
+
* read the same categories, and they answer different questions — a rule for a checker says what a
|
|
110
|
+
* checker may not do, this says that everybody but a builder may not write.
|
|
111
|
+
*
|
|
112
|
+
* An empty `writers` is refused at load: it would refuse every change by everyone, which is a
|
|
113
|
+
* frozen worktree, not a rule about who writes it.
|
|
114
|
+
*/
|
|
115
|
+
export declare const oneWriter: (options: {
|
|
116
|
+
writers: readonly string[];
|
|
117
|
+
}) => Check;
|
|
89
118
|
/**
|
|
90
119
|
* A file must have a required companion on disk — every `src/domain/**` needs its `.test.ts`.
|
|
91
120
|
*
|
|
@@ -241,6 +270,44 @@ export declare const changeTogether: (options: {
|
|
|
241
270
|
* aimed at a directory called HEAD.
|
|
242
271
|
*/
|
|
243
272
|
export declare const GIT_VALUE_OPTS: ReadonlySet<string>;
|
|
273
|
+
/** A heredoc's opener: its delimiter, whether `<<-` strips leading tabs, and where the opener ends. */
|
|
274
|
+
export interface HeredocOpen {
|
|
275
|
+
readonly delim: string;
|
|
276
|
+
readonly tabs: boolean;
|
|
277
|
+
readonly end: number;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The first heredoc opener anywhere in a line, quoted context or not — the one definition of "this
|
|
281
|
+
* line carries a heredoc". A here-string (`<<<`) is stepped over whole, so its tail is not read as
|
|
282
|
+
* one.
|
|
283
|
+
*/
|
|
284
|
+
export declare function firstHeredoc(s: string): HeredocOpen | null;
|
|
285
|
+
/**
|
|
286
|
+
* Where a `$(` closes, honouring quotes, nesting and any heredoc inside it — a body is text, so an
|
|
287
|
+
* apostrophe or a `)` in it closes nothing. -1 when it never closes.
|
|
288
|
+
*/
|
|
289
|
+
export declare function closeParen(s: string, from: number): number;
|
|
290
|
+
/** Where a double-quoted run closes — a `"` inside a `$(…)` in it does not close it. -1 when it never does. */
|
|
291
|
+
export declare function dquoteEnd(s: string, from: number): number;
|
|
292
|
+
/**
|
|
293
|
+
* THE COMMAND LINE WITHOUT ITS HEREDOC BODIES — what a command ban matches unless the rule sets
|
|
294
|
+
* `matchHeredocs`.
|
|
295
|
+
*
|
|
296
|
+
* A heredoc is text fed to a program on its standard input: a python script, a file being written,
|
|
297
|
+
* a note. The words of a banned command inside it are not a use of that command, and matching
|
|
298
|
+
* them refused `python3 - <<'PY'` scripts for naming a recipe in a string. Every body is removed
|
|
299
|
+
* (its terminator line stays, so the opener still ends), whether the delimiter is quoted or not,
|
|
300
|
+
* and wherever the heredoc sits —
|
|
301
|
+
* inside a `$(…)` or a quoted argument too. EVERYTHING ELSE IS LEFT AS WRITTEN — newlines
|
|
302
|
+
* included, because the patterns that police a commit message span them.
|
|
303
|
+
*
|
|
304
|
+
* One exception keeps a body: a heredoc fed to a SHELL (`bash`, `sh`, `zsh`, `eval`, or a pipeline
|
|
305
|
+
* into one), whose body is commands, and a banned one in it will run.
|
|
306
|
+
*
|
|
307
|
+
* Best-effort, and it errs toward matching: from a word whose quote never closes, the line is
|
|
308
|
+
* returned as written, so a ban still sees everything it saw before.
|
|
309
|
+
*/
|
|
310
|
+
export declare function elideHeredocs(command: string): string;
|
|
244
311
|
/**
|
|
245
312
|
* A shell-ish tokeniser: quotes respected (so a commit message mentioning `git commit` is not a
|
|
246
313
|
* second command), operators emitted as their own tokens.
|
|
@@ -266,19 +333,6 @@ export interface GitInvocation {
|
|
|
266
333
|
* operator — so a `git` inside a quoted message or as an argument to something else is not one.
|
|
267
334
|
*/
|
|
268
335
|
export declare function gitInvocations(command: string): GitInvocation[] | null;
|
|
269
|
-
/**
|
|
270
|
-
* The body of the first heredoc in the raw command, or null.
|
|
271
|
-
*
|
|
272
|
-
* `git commit -F -` reads its message on stdin, and a QUOTED heredoc is the safe way to write one
|
|
273
|
-
* containing backticks — a form this codebase's own rules push commits toward. The argument scan
|
|
274
|
-
* cannot see it: there is no `-m` token, so a message written that way goes unjudged, and every
|
|
275
|
-
* rule that reads a commit message is inert on it.
|
|
276
|
-
*
|
|
277
|
-
* Deliberately last-resort and deliberately dumb — it runs only when no inline message was found,
|
|
278
|
-
* and reads the FIRST heredoc, because a commit command carrying two is not a shape anyone writes
|
|
279
|
-
* and guessing between them would be worse than the gap.
|
|
280
|
-
*/
|
|
281
|
-
export declare function heredocBody(command: string): string | null;
|
|
282
336
|
/**
|
|
283
337
|
* The message a `git commit` in this command line carries, or null when there is no judgeable
|
|
284
338
|
* commit in it at all. Null is the "not our business" answer and every caller passes on it.
|
|
@@ -296,7 +350,10 @@ export declare function heredocBody(command: string): string | null;
|
|
|
296
350
|
*
|
|
297
351
|
* git commit -m MSG · -mMSG · --message MSG · --message=MSG judged
|
|
298
352
|
* git commit -m SUBJECT -m BODY (repeated, any mix of forms) judged, joined as git joins them
|
|
299
|
-
* git commit -F - <<'EOF' … EOF (
|
|
353
|
+
* git commit -F - <<'EOF' … EOF (the commit reads stdin) judged, via the heredoc body
|
|
354
|
+
* python3 - <<'PY' … PY; git commit -F msg.txt NOT judged — the heredoc is
|
|
355
|
+
* another command's script, and
|
|
356
|
+
* the message is on disk
|
|
300
357
|
* git commit -F path/to/file NOT judged — the message is on
|
|
301
358
|
* disk, and reading it is a read
|
|
302
359
|
* of the world this layer does not
|
|
@@ -266,8 +266,16 @@ export declare const FLOW_DIR = ".flow";
|
|
|
266
266
|
export declare const FLOW_GITIGNORE = "# flow's own state \u2014 telemetry and session marks, never committed.\n*\n";
|
|
267
267
|
/** The log schema this build writes, stamped into every session file's first row. */
|
|
268
268
|
export declare const LOG_VERSION = 1;
|
|
269
|
-
/**
|
|
270
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Every kind a log row may be. Closed — a reader that meets another word has met corruption.
|
|
271
|
+
*
|
|
272
|
+
* `read` and `write` are what went ROUND the tool rows: a file a shell command read by name, and a
|
|
273
|
+
* file a call changed that the call did not name — a heredoc, a `sed -i`, a script. A `tool` row
|
|
274
|
+
* says a call happened; these say what it did to the tree, so the coverage and lead numbers count
|
|
275
|
+
* work done through the shell as well as through Edit. `mismatch` is the host's own list of the
|
|
276
|
+
* files a command changed disagreeing with flow's diff: a fact to read, never an input to a rule.
|
|
277
|
+
*/
|
|
278
|
+
export declare const ROW_KINDS: readonly ["meta", "tool", "breadcrumb", "guardrail", "compaction", "run", "read", "write", "mismatch"];
|
|
271
279
|
export type RowKind = (typeof ROW_KINDS)[number];
|
|
272
280
|
export declare function flowDir(root: string): string;
|
|
273
281
|
/** A session id becomes a filename, so it is held to a filename's charset. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { guardrail, breadcrumb, type Sentence, type EntrySpec, type GuardrailSpe
|
|
|
2
2
|
export { definePack, pack, override, defineConfig, type Pack, type PackDefinition, type EntryRef, type RefTarget, type FlowConfig, type Binding, type PackBinding, type OverrideBinding, type ConfigSentence, } from "./language/domain.ts";
|
|
3
3
|
export { defineCheck, withFaults, faultsOf, type Check, type Ctx, type World, type Verdict, type ExecResult, type TurnAction, type Case, type CaseWorld, type Cases, } from "./language/domain.ts";
|
|
4
4
|
export { defineCategory, type Category, type Classifier, type SessionFacts } from "./language/domain.ts";
|
|
5
|
-
export { astGrep, banCommands, canonicalFiles, changeTogether, commitReason, depcruise, execPasses, jsonInvariant, protectedPath, ranSinceEdit, siblingExists, symbolsInSibling, textBan, type CanonOptions, type ChangeGroup, type Dialect, type Layer, type DiffAdds, type ForbidEdge, type JsonAssert, type RequireEdge, } from "./checks/domain.ts";
|
|
5
|
+
export { astGrep, banCommands, canonicalFiles, changeTogether, commitReason, depcruise, execPasses, jsonInvariant, oneWriter, protectedPath, ranSinceEdit, siblingExists, symbolsInSibling, textBan, type CanonOptions, type ChangeGroup, type Dialect, type Layer, type DiffAdds, type ForbidEdge, type JsonAssert, type RequireEdge, } from "./checks/domain.ts";
|
|
6
6
|
export { substitutionInProse, SUBSTITUTION_MESSAGE } from "./checks/domain.ts";
|
|
7
7
|
export { commitMessage, gitDirPrefix, gitInvocations, givesReason, quoteArg, tokenizeCommand, type GitInvocation, } from "./checks/domain.ts";
|
|
8
8
|
export { spawnedAs, type SpawnRecipe } from "./engine/domain.ts";
|
|
@@ -141,6 +141,12 @@ export interface Ctx {
|
|
|
141
141
|
readonly staged?: readonly string[] | undefined;
|
|
142
142
|
/** turn-end: what the actor did this turn, in order. */
|
|
143
143
|
readonly turn?: readonly TurnAction[] | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* Every moment: the category names that match the session making this change — the categories
|
|
146
|
+
* some bound entry names, whose recognizer said yes to the host-written evidence about it. Empty
|
|
147
|
+
* when it matches none, which is not "every category".
|
|
148
|
+
*/
|
|
149
|
+
readonly actor: readonly string[];
|
|
144
150
|
exec(cmd: string): Promise<ExecResult>;
|
|
145
151
|
readonly fs: {
|
|
146
152
|
read(path: string): Promise<string>;
|
|
@@ -263,16 +269,20 @@ export type Case =
|
|
|
263
269
|
string | {
|
|
264
270
|
readonly command: string;
|
|
265
271
|
readonly world?: CaseWorld;
|
|
272
|
+
readonly actor?: readonly string[];
|
|
266
273
|
} | {
|
|
267
274
|
readonly path: string;
|
|
268
275
|
readonly content: string;
|
|
269
276
|
readonly world?: CaseWorld;
|
|
277
|
+
readonly actor?: readonly string[];
|
|
270
278
|
} | {
|
|
271
279
|
readonly staged: readonly string[];
|
|
272
280
|
readonly world?: CaseWorld;
|
|
281
|
+
readonly actor?: readonly string[];
|
|
273
282
|
} | {
|
|
274
283
|
readonly actions: readonly TurnAction[];
|
|
275
284
|
readonly world?: CaseWorld;
|
|
285
|
+
readonly actor?: readonly string[];
|
|
276
286
|
};
|
|
277
287
|
/** The cases an entry carries: what must pass it, and what must be blocked by it. */
|
|
278
288
|
export interface Cases {
|
|
@@ -538,6 +548,19 @@ export interface Settings {
|
|
|
538
548
|
* it two ways.
|
|
539
549
|
*/
|
|
540
550
|
readonly grammars?: readonly Grammar[];
|
|
551
|
+
/**
|
|
552
|
+
* Globs for the IGNORED paths the snapshot records as well. The snapshot is git's view of the
|
|
553
|
+
* tree — tracked files, and untracked ones git does not ignore — so a shell write to an ignored
|
|
554
|
+
* file is never seen unless a glob here brings it back. `.env` is the usual one.
|
|
555
|
+
*/
|
|
556
|
+
readonly snapshotInclude?: readonly string[];
|
|
557
|
+
/**
|
|
558
|
+
* What is put back when a rule refuses a write that has already landed: `"refused"` (the default)
|
|
559
|
+
* writes back only the files a rule refused, `"all"` every file that call changed. While another
|
|
560
|
+
* agent's call overlaps it, only the refused files are put back whatever this says — that call's
|
|
561
|
+
* writes can be in the same diff.
|
|
562
|
+
*/
|
|
563
|
+
readonly revert?: "refused" | "all";
|
|
541
564
|
}
|
|
542
565
|
/**
|
|
543
566
|
* One hand-built tree-sitter grammar, as `registerDynamicLanguage` wants it.
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* least possible cognitive load, and the agent wants almost nothing in context. So `docs/` has
|
|
6
6
|
* exactly two doors and everything inside follows its door's rules.
|
|
7
7
|
*
|
|
8
|
-
* @setup A `docs/` folder holding exactly `user/` and `agent/`, each with its own index —
|
|
9
|
-
* `docs/user
|
|
10
|
-
*
|
|
8
|
+
* @setup A `docs/` folder holding exactly `user/` and `agent/`, each with its own index — an index
|
|
9
|
+
* file under `docs/user/` for people and `docs/agent/README.md` as a list of pointers. The
|
|
10
|
+
* guardrail below refuses anything else at the top of the folder, so a repo with a different shape
|
|
11
11
|
* either moves its pages or does not bind this pack.
|
|
12
12
|
*/
|
|
13
13
|
export interface Doors {
|
|
@@ -45,5 +45,4 @@ export declare const docs: import("../index.ts").Pack<{
|
|
|
45
45
|
* drawer, and neither audience's rules can be applied to what is in it.
|
|
46
46
|
*/
|
|
47
47
|
docsShape: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "on" | "description", "write" | "commit">;
|
|
48
|
-
noMarkdownInUserDocs: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "on" | "description", "write" | "commit">;
|
|
49
48
|
}, Doors | undefined>;
|
|
@@ -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<{
|
|
59
|
+
builderWrites?: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "description", "write" | "delete">;
|
|
44
60
|
planIsTheParents: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "for" | "description", "command">;
|
|
45
61
|
checkersDoNotWrite: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "for" | "description", "write">;
|
|
46
62
|
ticksAreTheChilds: import("../index.ts").GuardrailSentence<"at" | "check" | "message" | "test" | "for" | "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>;
|