@descent-vtt/spec-brief 0.2.7 → 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/CHANGELOG.md +67 -1
- package/README.md +162 -29
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +61 -43
- package/dist/cli.js.map +1 -1
- package/dist/collisions.js +13 -8
- package/dist/collisions.js.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/corpus.d.ts +5 -0
- package/dist/corpus.js +7 -0
- package/dist/corpus.js.map +1 -1
- package/dist/glob.d.ts +6 -5
- package/dist/glob.js +22 -103
- package/dist/glob.js.map +1 -1
- package/dist/lint.js +1 -0
- package/dist/lint.js.map +1 -1
- package/dist/report.d.ts +18 -4
- package/dist/report.js +98 -28
- package/dist/report.js.map +1 -1
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +21 -8
- package/dist/rules.js.map +1 -1
- package/dist/schedule.js +12 -10
- package/dist/schedule.js.map +1 -1
- package/dist/schema.d.ts +5 -1
- package/dist/schema.js +6 -2
- package/dist/schema.js.map +1 -1
- package/dist/scope.d.ts +26 -4
- package/dist/scope.js +64 -8
- package/dist/scope.js.map +1 -1
- package/dist/trigger.js +59 -3
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +11 -1
- package/dist/vendor/spec-core/markdown/frontmatter.js +20 -3
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
- package/dist/vendor/spec-core/pattern/glob.d.ts +85 -0
- package/dist/vendor/spec-core/pattern/glob.js +207 -17
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -1
- package/dist/vendor/spec-core/pattern/index.d.ts +2 -2
- package/dist/vendor/spec-core/pattern/index.js +1 -1
- package/dist/vendor/spec-core/pattern/index.js.map +1 -1
- package/dist/vendor/spec-core/text/index.d.ts +1 -0
- package/dist/vendor/spec-core/text/index.js +1 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -1
- package/dist/vendor/spec-core/text/width.d.ts +30 -0
- package/dist/vendor/spec-core/text/width.js +113 -0
- package/dist/vendor/spec-core/text/width.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
Notable changes, newest first.
|
|
3
|
+
Notable changes, newest first. Before 1.0, a minor release (0.2 to 0.3)
|
|
4
|
+
carries anything that can turn a passing run red or change what a script
|
|
5
|
+
reads - a finding not reported before, a changed exit code, input refused
|
|
6
|
+
that was accepted, a flag, key or JSON field removed or renamed after a
|
|
7
|
+
release of warning - and new features; a patch release carries only fixes
|
|
8
|
+
that report less, crashes, speed and documentation, so `^0.3.0` takes no
|
|
9
|
+
release that can turn a run red. This is the family's policy, recorded in
|
|
10
|
+
[spec-core ADR-0009](https://github.com/DescentVTT/spec-core/blob/main/docs/adr/0009-versions-before-1-0.md);
|
|
11
|
+
releases up to 0.2.7 came before it, and several of their patches changed
|
|
12
|
+
what a run reports.
|
|
13
|
+
|
|
14
|
+
## 0.3.0
|
|
15
|
+
|
|
16
|
+
Deferral triggers and placeholders written in Chinese are read, the GitLab
|
|
17
|
+
fingerprint no longer changes with a message's wording, `matrix` names a file
|
|
18
|
+
the tree holds, and Chinese columns line up. spec-core is at 5666c96.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- spec-core at 5666c96: `.//docs`, `./{/docs,src}` and `{.//docs,src}` read
|
|
23
|
+
as relative patterns, where they were refused as rooted (ADR-0005).
|
|
24
|
+
Upgrading: a scope refused before is read now; run `spec-brief matrix`.
|
|
25
|
+
- A front-matter key may be written in any script: `狀態: 已接受` is an
|
|
26
|
+
`unknown-field` warning where it was a `front-matter` error, and
|
|
27
|
+
`status.field` may name `狀態` or `状态`. Upgrading: write `status:`, or
|
|
28
|
+
set `status.field`.
|
|
29
|
+
- `archive`, `unarchive` and `new` write a status word such as `封存`
|
|
30
|
+
unquoted. Upgrading: a script matching the written line sees
|
|
31
|
+
`status: 封存`.
|
|
32
|
+
- A deferral trigger that names only a time in Chinese (`下個月`,
|
|
33
|
+
`2026年10月`, `第三季`, full-width `Q3`) is refused by `deferral-trigger`
|
|
34
|
+
(ADR-0003). Upgrading: name the event that brings the work back.
|
|
35
|
+
- The default `placeholders` gain `待定`, `未定`, `待補`, `待確認` and their
|
|
36
|
+
Simplified forms, and a full-width colon ends a placeholder as a colon
|
|
37
|
+
does. Upgrading: fill the section, or list the old `placeholders`.
|
|
38
|
+
- The GitLab fingerprint is built from the rule, the file, the brief and
|
|
39
|
+
the finding's subject, never its message. Upgrading: the first merge
|
|
40
|
+
request after upgrading shows each issue resolved and found again once.
|
|
41
|
+
- `matrix` and `schedule` name a file the tree holds where both patterns
|
|
42
|
+
match one outside the protections, or say the file named is an example
|
|
43
|
+
(ADR-0005).
|
|
44
|
+
- A pair the search could not decide is a `collision` (an error) when the
|
|
45
|
+
tree holds a file both patterns match and neither brief protects.
|
|
46
|
+
Upgrading: run the two briefs in different waves, or narrow a scope.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- `inTree` on each pair of patterns in `matrix` and `schedule` JSON
|
|
51
|
+
(`schemaVersion` stays 2); `meet` takes the tree's files.
|
|
52
|
+
- A rule's result, a plugin's included, may carry a `subject`, which the
|
|
53
|
+
GitLab fingerprint is built from.
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- `list`, `schedule` and `matrix` line up columns holding Chinese.
|
|
58
|
+
- `unknown-field` no longer suggests a key that shares nothing with the one
|
|
59
|
+
written (`狀態` was asked about `id`).
|
|
60
|
+
- `list` shows the status word the brief writes, such as `封存`, not its
|
|
61
|
+
English equivalent.
|
|
62
|
+
- `list` and `schedule` show a title without the id it repeats.
|
|
63
|
+
- `--help` lists the formats each command writes.
|
|
64
|
+
|
|
65
|
+
### Documentation
|
|
66
|
+
|
|
67
|
+
- The README is reorganised into lists and tables, with a "Scope patterns"
|
|
68
|
+
list of one rule per item; SECURITY.md says how to report a vulnerability
|
|
69
|
+
privately.
|
|
4
70
|
|
|
5
71
|
## 0.2.7
|
|
6
72
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
A *brief* is the input to one round of work - for a coding agent or a person: what done looks like, what the round must not touch, and the checks that prove it finished. spec-brief manages briefs as files in your repository. It has no runtime dependencies, reads and writes nothing but Markdown in two directories, and never writes to git.
|
|
6
6
|
|
|
7
|
+
spec-brief is one of the [spec-\* tools](https://github.com/DescentVTT/spec-core#the-family). The family's words - brief, round, wave, scope, the two kinds of plugin - are defined in [concepts](https://github.com/DescentVTT/spec-core/blob/main/docs/concepts.md), and the [tutorial](https://github.com/DescentVTT/spec-core/blob/main/docs/tutorial.md) takes one round from the brief to the archive in ten steps.
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
npm install --save-dev @descent-vtt/spec-brief
|
|
9
11
|
npx spec-brief init
|
|
@@ -48,11 +50,34 @@ Every privilege change issues a new session token and invalidates the old one.
|
|
|
48
50
|
- [ ] a rotated token is rejected by every endpoint
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
Work put off for later is a brief too, with `status: deferred` and a `trigger` naming the event that brings it back - "when the second tenant signs", "when p95 exceeds 200 ms", never a date
|
|
53
|
+
Work put off for later is a brief too, with `status: deferred` and a `trigger` naming the event that brings it back - "when the second tenant signs", "when p95 exceeds 200 ms", `當第二個租戶簽約時`, never a date: not `Q3`, not `下個月` (next month), not `2026年10月`. A deferred brief stays in the briefs directory, is never ready, and runs in no wave until someone sets it `active`.
|
|
54
|
+
|
|
55
|
+
- The id comes from the file name (`012_rotate-session-tokens.md`) or from an `id` field.
|
|
56
|
+
- `affectedFiles` is the scope the round may write.
|
|
57
|
+
- `protectedFiles` is what it is not empowered to change, and archival refuses a round that changed it.
|
|
58
|
+
- Protection wins: what a round may write is `affectedFiles` less `protectedFiles`, so "all of `src/` but the schema" is `affectedFiles: [src/**]` with `protectedFiles: [src/db/schema.ts]`.
|
|
59
|
+
|
|
60
|
+
### Scope patterns
|
|
61
|
+
|
|
62
|
+
Scopes are globs in spec-core's `path` dialect, the one every spec-\* tool reads: `*`, `?`, `[a-z]` and `{a,b}` within a name, `**` for any number of directories, compared case-sensitively on every host. The rules:
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
- `**` is a whole segment: `docs/**.md` is refused, since tools read it three ways - write `docs/**/*.md` for any depth, or `docs/*.md` for one level.
|
|
65
|
+
- Parentheses are literal, `C++(notes).md`, unless a group holds a `|`: an extended glob such as `+(a|b)` is refused, and written `{a,b}`.
|
|
66
|
+
- A trailing `/**` or `/` is a directory's contents - at least one name below it, never the directory itself - on a brace alternative as on the whole pattern: `{src/,lib}` is `src/` or `lib`.
|
|
67
|
+
- Each alternative names a path under the root, as a whole pattern does: `./` is refused, and so is `{./,src}`.
|
|
68
|
+
- A pattern is relative to the repository root, so a leading `/` is refused, on an alternative as on the whole pattern: `/docs` and `{/docs,src}` alike. A leading `./` takes the slashes after it along, as POSIX reads them: `.//docs` is `docs`, and `./{/docs,src}` is `docs` or `src`.
|
|
69
|
+
- After a name, a `/` inside braces names nothing: `docs/{/a,b}` is `docs/a` or `docs/b`.
|
|
70
|
+
- A path with no glob syntax is read from the tree: a file the tree holds is that file, a directory it holds is everything beneath it, and a path it does not hold yet is a file unless it ends in `/`.
|
|
71
|
+
- Write `src/newmod/` for a directory the round creates; `lint` notes a bare `src/newmod` it cannot place.
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
### How a brief is read
|
|
74
|
+
|
|
75
|
+
A brief is read as CommonMark reads it wherever that decides what is code or a comment - fenced and indented code, `<pre>`, `<script>`, `<style>` and `<textarea>` blocks, code spans, HTML comments - and nothing inside those is structure.
|
|
76
|
+
|
|
77
|
+
- **A section**, and the title, is an ATX heading, `## Name`, outside a block quote: prose over a `---` line is prose and a rule, and a heading quoted from another document is not one of the brief's sections.
|
|
78
|
+
- **Section names** compare without case, typographic quotes, emphasis, a leading number or a trailing colon, and a heading may add a qualifier after a separator: `## 2. Commander’s Intent:` fills `Commander's Intent`, and `## Invariants (must hold)` fills `Invariants`.
|
|
79
|
+
- **A task** is a list item outside a block quote with `[ ]`, `[x]` or `[X]`.
|
|
80
|
+
- **The links archival rewrites** are the ones that write a destination: `[text](dest)`, `` - one inside a link's text too - and a definition, `[label]: dest`, through which a reference is rewritten once. A definition is one where CommonMark reads it, opening a paragraph or under another: the shape of one under a paragraph's text, or in alt text, is text and stays as written.
|
|
56
81
|
|
|
57
82
|
## Commands
|
|
58
83
|
|
|
@@ -80,7 +105,16 @@ briefs/035_the-background-side.md
|
|
|
80
105
|
|
|
81
106
|
### `spec-brief list`
|
|
82
107
|
|
|
83
|
-
Live briefs with their status,
|
|
108
|
+
Live briefs with their status, in the word the brief writes (`封存` where the configuration names it for archived), wave, task count, readiness and title.
|
|
109
|
+
|
|
110
|
+
- **The title** is shown without the id it repeats: `new` writes `# 012 — Rotate tokens`, and the table has an id column, so the title shown is `Rotate tokens`. Only the brief's own id comes off, and only before a dash, a colon or a spaced hyphen, so `Fix - the login bug` is shown whole. `schedule` shows titles the same way, and `--format json` gives the title as written.
|
|
111
|
+
- **A brief is *ready*** when every brief it depends on is archived, and it is neither a draft nor deferred.
|
|
112
|
+
|
|
113
|
+
| Option | Shows |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `--ready` | Only the ready briefs, which is the question an orchestrator asks. |
|
|
116
|
+
| `--archived` | The archive as well. |
|
|
117
|
+
| `--format json` | The whole table, for an orchestrator, and the sections the configuration asks for, each with its `hint`. |
|
|
84
118
|
|
|
85
119
|
### `spec-brief matrix`
|
|
86
120
|
|
|
@@ -96,11 +130,21 @@ wave 1 · 3 briefs
|
|
|
96
130
|
? 003 declares no affectedFiles and cannot be checked
|
|
97
131
|
```
|
|
98
132
|
|
|
99
|
-
Scopes are intersected as globs, not compared as strings: the two above share no prefix and still meet
|
|
133
|
+
Scopes are intersected as globs, not compared as strings: the two above share no prefix and still meet. Exit 1 on a collision.
|
|
134
|
+
|
|
135
|
+
- **The file named** is one both patterns match and neither brief protects - always a file, never a directory. It is a file the tree holds where there is one, the first in git's order; where the tree holds none, it is the shortest path the search built, said to be an example: `both cover src/newmod/a.ts, an example not in the tree`. `--format json` says which, as `inTree` on each pair of patterns: `true`, `false`, or `null` when the tree could not be read.
|
|
136
|
+
- **One collision per pair of briefs**: two briefs whose scopes meet in several places are one collision, listing every pair of patterns that meets.
|
|
137
|
+
- **Unscoped**: a brief with no scope is reported as unscoped rather than counted as safe.
|
|
138
|
+
- **Undecided**: the search for a shared file has a budget; a pair it cannot decide within it is marked `?` and reported as `collision-undecided`, a warning, never as a collision and never as clean - unless the tree holds a file both patterns match and neither brief protects, which proves the collision.
|
|
100
139
|
|
|
101
140
|
### `spec-brief schedule`
|
|
102
141
|
|
|
103
|
-
Computes the waves the live briefs can run in, drafts included, and sets each beside the wave it declares
|
|
142
|
+
Computes the waves the live briefs can run in, drafts included, and sets each beside the wave it declares:
|
|
143
|
+
|
|
144
|
+
- A brief runs after every dependency that is still live - an archived one is done - and shares a wave with no brief whose writable scope meets its own.
|
|
145
|
+
- Briefs are taken in dependency order, ties broken by id, and each goes into the lowest wave that is after its dependencies and holds nothing it collides with.
|
|
146
|
+
- A brief with no `affectedFiles` cannot be proved apart from anything, so it runs in a wave of its own and is reported as `unscoped`.
|
|
147
|
+
- The first wave is the lowest any brief declares, or 1.
|
|
104
148
|
|
|
105
149
|
```text
|
|
106
150
|
wave 1 · 2 briefs
|
|
@@ -118,32 +162,96 @@ deferred: 006
|
|
|
118
162
|
2 briefs would move; "spec-brief schedule --write" writes the waves
|
|
119
163
|
```
|
|
120
164
|
|
|
121
|
-
Each move says why:
|
|
165
|
+
Each move says why:
|
|
166
|
+
|
|
167
|
+
- first, whether the declared wave holds - whether `lint` and `matrix` would pass it: no brief there that it collides with, its dependencies in earlier waves, and the briefs that depend on it in later ones;
|
|
168
|
+
- the dependency that sets the earliest wave;
|
|
169
|
+
- for every wave passed over, the brief there and the file both would write.
|
|
170
|
+
|
|
171
|
+
A deferred brief, and one that waits on it, is placed nowhere; a dependency cycle is an error, and nothing in or after it is placed.
|
|
172
|
+
|
|
173
|
+
`--write` sets `wave` in the front matter of each brief whose wave changes, in one transaction, and writes nothing over a cycle:
|
|
174
|
+
|
|
175
|
+
- every other line stays as it was;
|
|
176
|
+
- a comment after the old wave, `wave: 2 # after the review`, stays after the new one;
|
|
177
|
+
- a brief whose lines end in both LF and CRLF is written with the ending of its first line throughout, as `archive` writes one.
|
|
178
|
+
|
|
179
|
+
`--format json` gives an orchestrator the waves, each brief's declared and proposed wave with the reasons and whether the declared one holds, and what waits on what; `sarif`, `github` and `gitlab` carry a `wave-schedule` finding per move.
|
|
122
180
|
|
|
123
|
-
The result is the same every time for the same briefs. It is a valid schedule, not always the shortest: no fast method promises the fewest waves, and a person can always move a brief later by hand, which `lint` and `matrix` then check. So a move is an error only when the declared wave does not hold, or there is none: a declared wave that holds is the person's to keep, and its move is a note.
|
|
181
|
+
The result is the same every time for the same briefs. It is a valid schedule, not always the shortest: no fast method promises the fewest waves, and a person can always move a brief later by hand, which `lint` and `matrix` then check. So a move is an error only when the declared wave does not hold, or there is none: a declared wave that holds is the person's to keep, and its move is a note.
|
|
182
|
+
|
|
183
|
+
| Exit | When |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `0` | Every declared wave holds. |
|
|
186
|
+
| `1` | A brief declares no wave or one that does not hold, or the dependencies form a cycle. |
|
|
187
|
+
| `2` | The run cannot be trusted. |
|
|
124
188
|
|
|
125
189
|
### `spec-brief archive <brief>`
|
|
126
190
|
|
|
127
191
|
Closes a round. Refused, with every reason, when:
|
|
128
192
|
|
|
129
193
|
- the brief has lint errors, is a draft, or depends on a brief that is still live;
|
|
130
|
-
- a task item is neither ticked nor dispositioned
|
|
194
|
+
- a task item is neither ticked nor dispositioned:
|
|
195
|
+
- an open box counts as closed when a note under it starts with one of the `dispositions` (`**Delegated`, `**Accepted debt`, `**Rejected` by default);
|
|
196
|
+
- a note is a line under the box, above any box nested in it, that starts a bullet, a paragraph after a blank line, or a line after one that ends a sentence;
|
|
197
|
+
- a line that continues a sentence is the box's text wrapping, not a note, so `- [ ] Explain why the` over ` **Rejected** designs failed` is open, and `- [ ] Add a cache.` over ` **Rejected** by 012` is closed;
|
|
131
198
|
- the working tree holds uncommitted work outside the brief directories (`--allow-dirty` to proceed);
|
|
132
|
-
- the round's commit changed a file in `protectedFiles` - one refusal per file, naming it, unless a plugin that verifies rulings [waives it](#waiving-a-refusal).
|
|
199
|
+
- the round's commit changed a file in `protectedFiles` - one refusal per file, naming it, unless a spec-brief plugin that verifies rulings [waives it](#waiving-a-refusal).
|
|
200
|
+
|
|
201
|
+
Two warnings, which `--strict` turns into refusals:
|
|
202
|
+
|
|
203
|
+
- Changes outside `affectedFiles` are a warning, and an error under `--strict`.
|
|
204
|
+
- A scope is never passed by checks that measured nothing. When the files the round changed are unknown - no `--commit`, `--base` or `archiving.base`, `--no-git`, or a commit already in the base branch, as on `main` after the merge - a brief that declares one gets `scope-unmeasured`, a warning, and a refusal under `--strict`.
|
|
205
|
+
|
|
206
|
+
Otherwise, in one transaction, it:
|
|
133
207
|
|
|
134
|
-
|
|
208
|
+
- writes the archived brief with its status set, a frozen banner under the front matter, every relative link rewritten for the archive directory, and an `integrity` hash;
|
|
209
|
+
- rewrites the links other live briefs hold to it;
|
|
210
|
+
- removes the original.
|
|
211
|
+
|
|
212
|
+
With `archiving.rewriteLinks` off, no link is rewritten, and the links other live briefs are left holding are reported as `stale-link`. Archiving an archived brief does nothing and exits 0.
|
|
213
|
+
|
|
214
|
+
| Option | What it does |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `--commit <rev>` | Records the commit and the files it changed. |
|
|
217
|
+
| `--base <rev>` | Measures from the merge base instead, for a branch of several commits. |
|
|
218
|
+
| `--pr <n>` | Links the pull request. |
|
|
219
|
+
| `--summary <text>` | What the round did, in the banner. |
|
|
220
|
+
| `--dry-run` | Prints the plan and the banner, and writes nothing. |
|
|
135
221
|
|
|
136
222
|
spec-brief reads git and never writes it. Review the change and commit it with the round.
|
|
137
223
|
|
|
138
224
|
### `spec-brief unarchive <brief>`
|
|
139
225
|
|
|
140
|
-
Reopens an archived brief: the banner and the hash come off, the status goes back to the live word, and the links are rewritten again. A brief archived and reopened is the brief it was, blank lines and final newline included, with four exceptions:
|
|
226
|
+
Reopens an archived brief: the banner and the hash come off, the status goes back to the live word, and the links are rewritten again. A brief archived and reopened is the brief it was, blank lines and final newline included, with four exceptions:
|
|
227
|
+
|
|
228
|
+
1. The status line is written in its plain spelling.
|
|
229
|
+
2. A relative link that had to be rewritten comes back in its shortest form (`./b.md` returns as `b.md`).
|
|
230
|
+
3. An empty front matter block, `---` over `---`, comes back as none, since archival gives a brief with no front matter a block for its hash and reopening takes away a block it leaves empty.
|
|
231
|
+
4. A brief whose lines end in both LF and CRLF comes back with the ending of its first line throughout.
|
|
141
232
|
|
|
142
233
|
### Every command
|
|
143
234
|
|
|
144
|
-
|
|
235
|
+
| Flag | What it does |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `--root <dir>` | Runs from another directory. |
|
|
238
|
+
| `--config <file>` | Names the configuration. |
|
|
239
|
+
| `--no-config` | Uses the defaults. |
|
|
240
|
+
| `--format <format>` | `pretty` by default, or `json`; `lint`, `matrix` and `schedule`, which report findings, also write `sarif`, `github` and `gitlab`, and `--help` lists each command's formats. |
|
|
241
|
+
| `--strict` | Makes warnings fail the run. |
|
|
242
|
+
| `--no-git` | Leaves git out even inside a repository: the tree is read from disk, and `archive` records no commit. |
|
|
243
|
+
| `--color`, `--no-color` | Override `NO_COLOR`, `FORCE_COLOR` and the terminal check. |
|
|
244
|
+
| `--help`, `--version` | Do what they say. |
|
|
245
|
+
|
|
246
|
+
**Exit codes:**
|
|
145
247
|
|
|
146
|
-
|
|
248
|
+
| Code | Means |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| `0` | Clean. |
|
|
251
|
+
| `1` | Findings, a collision, or a refused action. |
|
|
252
|
+
| `2` | The run could not be trusted - a bad flag, a configuration that does not load, a brief that does not exist. |
|
|
253
|
+
|
|
254
|
+
A run over a briefs directory that does not exist exits 2, because a check over nothing looks exactly like a clean one.
|
|
147
255
|
|
|
148
256
|
## Configuration
|
|
149
257
|
|
|
@@ -157,11 +265,11 @@ Reopens an archived brief: the banner and the hash come off, the status goes bac
|
|
|
157
265
|
| `exclude` | `[]` | File-name globs that are never briefs, such as an index. |
|
|
158
266
|
| `template` | `null` | A template file for `new`, with `{id}`, `{title}`, `{date}`, `{type}`, `{wave}`, `{status}`. |
|
|
159
267
|
| `id` | `{ "source": "filename", "separator": "_", "digits": 3 }` | Where an id comes from, and how a new one is written. |
|
|
160
|
-
| `status` | `{ "field": "status", "draft": "draft", "active": "active", "deferred": "deferred", "archived": "archived" }` | The words a repository uses. `field: null` reads status from location alone; `draft` and `deferred` may be `null` where a repository has no such word. |
|
|
161
|
-
| `sections` | Intent, Negative Scope, Not Empowered (optional), Invariants (checklist) | Sections every live brief carries: a name, or `{ name, aliases, mustContain, checklist, optional, hint }`. `hint`
|
|
268
|
+
| `status` | `{ "field": "status", "draft": "draft", "active": "active", "deferred": "deferred", "archived": "archived" }` | The words a repository uses. `field` may be a key of any script, `狀態` as well as `status`, followed by an ASCII colon in the brief; `field: null` reads status from location alone; `draft` and `deferred` may be `null` where a repository has no such word. |
|
|
269
|
+
| `sections` | Intent, Negative Scope, Not Empowered (optional), Invariants (checklist) | Sections every live brief carries: a name, or `{ name, aliases, mustContain, checklist, optional, hint }`. `hint` is below. |
|
|
162
270
|
| `sectionOrder` | `false` | Sections must appear in the listed order. |
|
|
163
271
|
| `types` | `feature`, `defect`, `refactor`, `chore` | Brief types and the sections each adds. |
|
|
164
|
-
| `placeholders` | `TBD`, `TODO`, `FIXME`,
|
|
272
|
+
| `placeholders` | `TBD`, `TODO`, `FIXME`, ..., `待定`, `未定`, `待補`, `待確認` and their Simplified forms | Words that mark a section as unwritten: alone, or followed by a space, a colon (`:` too), a period or a dash. |
|
|
165
273
|
| `fields` | `[]` | Front-matter keys the repository uses beyond the built-in ones. |
|
|
166
274
|
| `archiving.tasks` | `"all"` | `"all"`, or the sections whose boxes must be closed. |
|
|
167
275
|
| `archiving.dispositions` | `**Delegated`, `**Accepted debt`, `**Rejected` | What a note under an open box starts with to close it. |
|
|
@@ -172,6 +280,12 @@ Reopens an archived brief: the banner and the hash come off, the status goes bac
|
|
|
172
280
|
| `rules` | `{}` | Severity per rule: `off`, `note`, `warning` or `error`. |
|
|
173
281
|
| `plugins` | `[]` | Modules that contribute rules: a path or package, or `{ module, options }`. |
|
|
174
282
|
|
|
283
|
+
A section's `hint` says what the section must answer, so a tool helping to write a brief can ask for each section by what it is for:
|
|
284
|
+
|
|
285
|
+
- a new brief carries it as a comment under the heading;
|
|
286
|
+
- a finding that the section is missing or unwritten gives it as the next step;
|
|
287
|
+
- the JSON of `list` and `lint` reports it.
|
|
288
|
+
|
|
175
289
|
A repository whose briefs have eight ordered sections, `proposed` and `archived` for words, and a banner of its own:
|
|
176
290
|
|
|
177
291
|
```json
|
|
@@ -198,9 +312,7 @@ A repository whose briefs have eight ordered sections, `proposed` and `archived`
|
|
|
198
312
|
}
|
|
199
313
|
```
|
|
200
314
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
A brief is read as CommonMark reads it wherever that decides what is code or a comment - fenced and indented code, `<pre>`, `<script>`, `<style>` and `<textarea>` blocks, code spans, HTML comments - and nothing inside those is structure. A section, and the title, is an ATX heading, `## Name`, outside a block quote: prose over a `---` line is prose and a rule, and a heading quoted from another document is not one of the brief's sections. A task is a list item outside a block quote with `[ ]`, `[x]` or `[X]`. The links archival rewrites are the ones that write a destination: `[text](dest)`, `` - one inside a link's text too - and a definition, `[label]: dest`, through which a reference is rewritten once. A definition is one where CommonMark reads it, opening a paragraph or under another: the shape of one under a paragraph's text, or in alt text, is text and stays as written.
|
|
315
|
+
How a heading fills a section, and what counts as a heading, a task or a link, is under [How a brief is read](#how-a-brief-is-read).
|
|
204
316
|
|
|
205
317
|
## Rules
|
|
206
318
|
|
|
@@ -218,21 +330,21 @@ A brief is read as CommonMark reads it wherever that decides what is code or a c
|
|
|
218
330
|
| `missing-section` | error | A required section that is absent. |
|
|
219
331
|
| `duplicate-section` | warning | A section that appears twice. |
|
|
220
332
|
| `empty-section` | error | A section with no content; a comment is not content. A warning in a draft. |
|
|
221
|
-
| `placeholder` | error | A section holding only `TBD`, `TODO
|
|
333
|
+
| `placeholder` | error | A section holding only `TBD`, `TODO`, `待定` or an empty box. A warning in a draft. |
|
|
222
334
|
| `missing-checklist` | error | A checklist section with no task item. A warning in a draft. |
|
|
223
335
|
| `must-contain` | error | A section without the text the configuration requires of it. |
|
|
224
336
|
| `section-order` | error | Sections out of the configured order, when one is configured. |
|
|
225
337
|
| `dependency` | error | A dependency on itself, or on a brief that does not exist. |
|
|
226
338
|
| `dependency-cycle` | error | Live briefs that depend on each other in a cycle, reported once, as a path. |
|
|
227
339
|
| `wave-order` | error | A live dependency that does not run in an earlier wave. |
|
|
228
|
-
| `deferral-trigger` | error | A deferred brief with no `trigger`, or one that is only a date or a time - `2026-10`, `Q3`, `next month`, `October` - or a placeholder, rather than an event such as "when the second tenant signs". |
|
|
340
|
+
| `deferral-trigger` | error | A deferred brief with no `trigger`, or one that is only a date or a time - `2026-10`, `Q3`, `next month`, `October`, `下個月`, `2026年10月`, `第三季` - or a placeholder, rather than an event such as "when the second tenant signs". Full-width `Q3` reads as `Q3`. |
|
|
229
341
|
| `glob` | error | A scope pattern spec-brief cannot read. |
|
|
230
342
|
| `scope-contradiction` | error | Patterns in `affectedFiles` that `protectedFiles` cover entirely, so nothing of them is writable - one finding per brief, naming each. A pattern the search cannot decide is a warning. |
|
|
231
343
|
| `glob-matches-nothing` | note | A scope pattern that matches no file git sees, tracked or untracked - expected when the round creates it. |
|
|
232
344
|
| `literal-read-as-file` | note | A path with no glob syntax that the tree does not hold and whose name has no extension, such as `src/newmod`: read as a file, and `src/newmod/` if a directory was meant. Inside braces the advice is the pattern written with the `/` added, `lib/{util,new/}`, or the alternative in an entry of its own, `lib/new/`. |
|
|
233
345
|
| `archive-freeze` | error | An archived brief that changed after it was archived. |
|
|
234
346
|
| `collision` | error | Two briefs in one wave whose scopes can name the same file (`matrix`). |
|
|
235
|
-
| `collision-undecided` | warning | Two briefs in one wave whose collision the search could not decide within its budget (`matrix`), or a wave `schedule` passed over for it. |
|
|
347
|
+
| `collision-undecided` | warning | Two briefs in one wave whose collision the search could not decide within its budget and no file of the tree proves (`matrix`), or a wave `schedule` passed over for it. |
|
|
236
348
|
| `unscoped` | note | A brief sharing a wave that declares no scope (`matrix`), or one `schedule` runs alone. |
|
|
237
349
|
| `shared-directory` | off | Two briefs in one wave writing into the same directory (`matrix`). |
|
|
238
350
|
| `wave-schedule` | error | A brief with no wave, or a declared wave that does not hold - a collision in it, a dependency out of order - with the wave `schedule` computes and why (`schedule`). A declared wave that holds and is not the computed one is a note. |
|
|
@@ -250,7 +362,12 @@ A brief is read as CommonMark reads it wherever that decides what is code or a c
|
|
|
250
362
|
- run: npx spec-brief schedule --format github
|
|
251
363
|
```
|
|
252
364
|
|
|
253
|
-
|
|
365
|
+
| Format | What it writes |
|
|
366
|
+
| --- | --- |
|
|
367
|
+
| `github` | Workflow commands, which annotate the pull request with no upload and no permission. |
|
|
368
|
+
| `sarif` | SARIF 2.1.0, for code-scanning upload. |
|
|
369
|
+
| `gitlab` | A GitLab Code Quality report - an array of `{ description, check_name, fingerprint, severity, location: { path, lines: { begin } } }` - for a merge request to show. |
|
|
370
|
+
| `json` | A versioned document for anything else: `schemaVersion` changes when a field changes meaning, and is 2 since a collision became a pair of briefs rather than a pair of patterns. |
|
|
254
371
|
|
|
255
372
|
```yaml
|
|
256
373
|
spec-brief:
|
|
@@ -260,7 +377,11 @@ spec-brief:
|
|
|
260
377
|
codequality: gl-code-quality.json
|
|
261
378
|
```
|
|
262
379
|
|
|
263
|
-
|
|
380
|
+
In the GitLab report:
|
|
381
|
+
|
|
382
|
+
- An error is `major`, a warning `minor` and a note `info`; GitLab's `critical` and `blocker` are for security holes and crashes, which no finding here is.
|
|
383
|
+
- The `description` is the message and the next action.
|
|
384
|
+
- The fingerprint is a SHA-256 of what the finding is: its rule, its file, its brief, and what it is about there - a section, a pattern, a dependency, the other brief of a pair - never the message, the hint or the line. So a finding that moves, or is worded another way, or a collision that names another file both briefs write, is the same issue; two findings alike in all four, such as two problems in one front matter, are told apart by their order.
|
|
264
385
|
|
|
265
386
|
## As a library
|
|
266
387
|
|
|
@@ -282,7 +403,7 @@ Everything below the engine is a pure function of text: `parseBrief`, `lint`, `c
|
|
|
282
403
|
|
|
283
404
|
## Plugins
|
|
284
405
|
|
|
285
|
-
A plugin is a module
|
|
406
|
+
A plugin here is a spec-brief plugin, a module this tool loads, not a Claude Code plugin. It exports `{ name, rules }`, or a function of its configured options that returns one. Its rules run beside the built-in ones as `<name>/<rule>`, and configuration sets their severity like any other.
|
|
286
407
|
|
|
287
408
|
```js
|
|
288
409
|
// tools/departures.mjs
|
|
@@ -301,13 +422,21 @@ export default (options) => ({
|
|
|
301
422
|
{ "plugins": [{ "module": "./tools/departures.mjs", "options": { "marker": "Signed-off-by" } }] }
|
|
302
423
|
```
|
|
303
424
|
|
|
425
|
+
A result is `{ line, message }`, with optional fields:
|
|
426
|
+
|
|
427
|
+
| Field | What it is |
|
|
428
|
+
| --- | --- |
|
|
429
|
+
| `hint` | The next action. |
|
|
430
|
+
| `severity` | Lower than the rule's, for this one finding. |
|
|
431
|
+
| `subject` | What the finding is about, where the rule can report several in one brief; GitLab's fingerprint is built from it. |
|
|
432
|
+
|
|
304
433
|
This is where integrations belong. A tool that defines a format - signed departures, recorded reproducers, a code graph's blast radius - is the one that can check it, and spec-brief carries no copy of formats it does not own. Loading a plugin runs its code, exactly as loading a linter configuration does.
|
|
305
434
|
|
|
306
435
|
A path is relative to the root. A package is found from the root as an `import` there would find it, its `exports` read under the import conditions, so a plugin published as ESM only loads, and so does a subpath such as `@descent-vtt/spec-harness/spec-brief-plugin`.
|
|
307
436
|
|
|
308
437
|
### Waiving a refusal
|
|
309
438
|
|
|
310
|
-
A plugin may also export `waive`, which lets the archive accept a change its own check allows - spec-harness's plugin verifies a signed ruling for a protected path, which spec-brief does not know how to read.
|
|
439
|
+
A plugin may also export `waive`, which lets the archive accept a change its own check allows - spec-harness's spec-brief plugin verifies a signed ruling for a protected path, which spec-brief does not know how to read.
|
|
311
440
|
|
|
312
441
|
```js
|
|
313
442
|
export default () => ({
|
|
@@ -321,7 +450,11 @@ export default () => ({
|
|
|
321
450
|
});
|
|
322
451
|
```
|
|
323
452
|
|
|
324
|
-
`findings` are the archive's refusals, as a frozen copy: a hook reads them and cannot change them, and a write to them throws.
|
|
453
|
+
- `findings` are the archive's refusals, as a frozen copy: a hook reads them and cannot change them, and a write to them throws.
|
|
454
|
+
- Two refusals can be waived, and no others: `protected-file`, one per file, which names its `path`; and `out-of-scope`, which refuses only under `--strict` and lists its `paths`.
|
|
455
|
+
- A waiver matches one of the plan's own refusals by rule and path and turns it into a `waived` note naming the plugin, the rule, the path and the reason. A waiver for any other rule is ignored with a `waiver-ignored` warning.
|
|
456
|
+
- The hook is asked only when the plan has a refusal it could lift.
|
|
457
|
+
- A hook that returns nothing waives nothing. One that throws - a write to `findings` included - or answers in another shape stops the run with exit 2, as a plugin that fails to load does.
|
|
325
458
|
|
|
326
459
|
## What it does not do
|
|
327
460
|
|
|
@@ -332,7 +465,7 @@ export default () => ({
|
|
|
332
465
|
|
|
333
466
|
## Design
|
|
334
467
|
|
|
335
|
-
The decisions and what they cost are in [`docs/adr/`](https://github.com/DescentVTT/spec-brief/blob/main/docs/adr/README.md).
|
|
468
|
+
The decisions and what they cost are in [`docs/adr/`](https://github.com/DescentVTT/spec-brief/blob/main/docs/adr/README.md). Working on spec-brief itself: [CONTRIBUTING.md](https://github.com/DescentVTT/spec-brief/blob/main/CONTRIBUTING.md); reporting a vulnerability: [SECURITY.md](https://github.com/DescentVTT/spec-brief/blob/main/SECURITY.md).
|
|
336
469
|
|
|
337
470
|
## License
|
|
338
471
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -23,8 +23,8 @@ export interface CliIO {
|
|
|
23
23
|
readonly cwd?: string;
|
|
24
24
|
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
25
25
|
}
|
|
26
|
-
export declare const HELP = "spec-brief - lint, schedule and archive task briefs\n\nUsage:\n spec-brief <command> [options]\n\nCommands:\n init write .spec-brief.json and the brief directories\n new <title> scaffold a brief with the next free id\n lint [brief...] check briefs against the configuration\n list briefs with their status, wave and readiness\n matrix scope collisions between briefs in the same wave\n schedule the waves the live briefs can run in, computed\n archive <brief> close a round: check it, stamp it, freeze it, move it\n unarchive <brief> reopen an archived brief\n\nOptions for every command:\n --root <dir> run from this directory instead of the current one\n --config <file> use this configuration file\n --no-config use the defaults, ignoring any configuration file\n --format <format> pretty (default), json, sarif, github or gitlab\n --strict treat warnings as errors\n --no-git leave git out; read the files on disk instead\n --color, --no-color force colour on or off\n -h, --help show this help\n -v, --version show the version\n\ninit:\n --briefs <dir> the directory of live briefs; default briefs\n --archive <dir> the directory of archived briefs; default <briefs>/archive\n\nnew:\n --id <id> use this id instead of the next free one\n --type <type> a brief type from the configuration\n --wave <n> the wave it runs in\n --depends-on <ids> comma-separated ids it runs after\n --date <YYYY-MM-DD> the date in its front matter; default today\n\nlist:\n --ready only briefs that can run now: dependencies archived, not drafts or deferred\n --archived include archived briefs\n\nmatrix:\n --all-waves compare every live brief, whatever its wave\n\nschedule:\n --write write each proposed wave into its brief's front matter\n\narchive:\n --pr <number> the pull request the round merged in\n --commit <rev> the commit the round landed as\n --base <rev> the branch it started from; the diff runs from the merge base\n --summary <text> what the round did, for the banner\n --date <YYYY-MM-DD> the archival date; default today\n --allow-dirty archive although the tree has uncommitted work\n --dry-run print the plan and change nothing\n\nunarchive:\n --dry-run print the plan and change nothing\n\nExit codes: 0 clean, 1 findings or refused, 2 the run could not be trusted.\n";
|
|
27
26
|
type Values = Record<string, string | boolean | undefined>;
|
|
27
|
+
export declare const HELP: string;
|
|
28
28
|
interface Parsed {
|
|
29
29
|
readonly command: string | undefined;
|
|
30
30
|
readonly values: Values;
|
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,63 @@ export class UsageError extends Error {
|
|
|
24
24
|
this.name = 'UsageError';
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
const GLOBAL = {
|
|
28
|
+
root: { type: 'string' },
|
|
29
|
+
config: { type: 'string' },
|
|
30
|
+
'no-config': { type: 'boolean' },
|
|
31
|
+
format: { type: 'string' },
|
|
32
|
+
strict: { type: 'boolean' },
|
|
33
|
+
'no-git': { type: 'boolean' },
|
|
34
|
+
color: { type: 'boolean' },
|
|
35
|
+
'no-color': { type: 'boolean' },
|
|
36
|
+
help: { type: 'boolean', short: 'h' },
|
|
37
|
+
version: { type: 'boolean', short: 'v' },
|
|
38
|
+
};
|
|
39
|
+
const COMMANDS = {
|
|
40
|
+
init: { options: { briefs: { type: 'string' }, archive: { type: 'string' } }, formats: ['pretty', 'json'] },
|
|
41
|
+
new: {
|
|
42
|
+
options: {
|
|
43
|
+
id: { type: 'string' },
|
|
44
|
+
type: { type: 'string' },
|
|
45
|
+
wave: { type: 'string' },
|
|
46
|
+
'depends-on': { type: 'string' },
|
|
47
|
+
date: { type: 'string' },
|
|
48
|
+
},
|
|
49
|
+
formats: ['pretty', 'json'],
|
|
50
|
+
},
|
|
51
|
+
lint: { options: {}, formats: FORMATS },
|
|
52
|
+
list: { options: { ready: { type: 'boolean' }, archived: { type: 'boolean' } }, formats: ['pretty', 'json'] },
|
|
53
|
+
matrix: { options: { 'all-waves': { type: 'boolean' } }, formats: FORMATS },
|
|
54
|
+
schedule: { options: { write: { type: 'boolean' } }, formats: FORMATS },
|
|
55
|
+
archive: {
|
|
56
|
+
options: {
|
|
57
|
+
pr: { type: 'string' },
|
|
58
|
+
commit: { type: 'string' },
|
|
59
|
+
base: { type: 'string' },
|
|
60
|
+
summary: { type: 'string' },
|
|
61
|
+
date: { type: 'string' },
|
|
62
|
+
'allow-dirty': { type: 'boolean' },
|
|
63
|
+
'dry-run': { type: 'boolean' },
|
|
64
|
+
},
|
|
65
|
+
formats: ['pretty', 'json'],
|
|
66
|
+
},
|
|
67
|
+
unarchive: { options: { 'dry-run': { type: 'boolean' } }, formats: ['pretty', 'json'] },
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The formats each command writes, read from the table `main` checks a
|
|
71
|
+
* `--format` against, so the help names no format a command refuses: the
|
|
72
|
+
* finding formats are `lint`'s, `matrix`'s and `schedule`'s alone.
|
|
73
|
+
*/
|
|
74
|
+
function formatsHelp() {
|
|
75
|
+
const groups = new Map();
|
|
76
|
+
for (const [command, spec] of Object.entries(COMMANDS)) {
|
|
77
|
+
const formats = spec.formats.join(', ');
|
|
78
|
+
groups.set(formats, [...(groups.get(formats) ?? []), command]);
|
|
79
|
+
}
|
|
80
|
+
const rows = [...groups].map(([formats, commands]) => [commands.join(', '), formats]);
|
|
81
|
+
const width = Math.max(...rows.map(([commands]) => commands.length));
|
|
82
|
+
return rows.map(([commands, formats]) => ` ${commands.padEnd(width)} ${formats}`).join('\n');
|
|
83
|
+
}
|
|
27
84
|
export const HELP = `spec-brief - lint, schedule and archive task briefs
|
|
28
85
|
|
|
29
86
|
Usage:
|
|
@@ -43,13 +100,16 @@ Options for every command:
|
|
|
43
100
|
--root <dir> run from this directory instead of the current one
|
|
44
101
|
--config <file> use this configuration file
|
|
45
102
|
--no-config use the defaults, ignoring any configuration file
|
|
46
|
-
--format <format> pretty
|
|
103
|
+
--format <format> the output format, pretty by default; each command's are below
|
|
47
104
|
--strict treat warnings as errors
|
|
48
105
|
--no-git leave git out; read the files on disk instead
|
|
49
106
|
--color, --no-color force colour on or off
|
|
50
107
|
-h, --help show this help
|
|
51
108
|
-v, --version show the version
|
|
52
109
|
|
|
110
|
+
Formats:
|
|
111
|
+
${formatsHelp()}
|
|
112
|
+
|
|
53
113
|
init:
|
|
54
114
|
--briefs <dir> the directory of live briefs; default briefs
|
|
55
115
|
--archive <dir> the directory of archived briefs; default <briefs>/archive
|
|
@@ -85,48 +145,6 @@ unarchive:
|
|
|
85
145
|
|
|
86
146
|
Exit codes: 0 clean, 1 findings or refused, 2 the run could not be trusted.
|
|
87
147
|
`;
|
|
88
|
-
const GLOBAL = {
|
|
89
|
-
root: { type: 'string' },
|
|
90
|
-
config: { type: 'string' },
|
|
91
|
-
'no-config': { type: 'boolean' },
|
|
92
|
-
format: { type: 'string' },
|
|
93
|
-
strict: { type: 'boolean' },
|
|
94
|
-
'no-git': { type: 'boolean' },
|
|
95
|
-
color: { type: 'boolean' },
|
|
96
|
-
'no-color': { type: 'boolean' },
|
|
97
|
-
help: { type: 'boolean', short: 'h' },
|
|
98
|
-
version: { type: 'boolean', short: 'v' },
|
|
99
|
-
};
|
|
100
|
-
const COMMANDS = {
|
|
101
|
-
init: { options: { briefs: { type: 'string' }, archive: { type: 'string' } }, formats: ['pretty', 'json'] },
|
|
102
|
-
new: {
|
|
103
|
-
options: {
|
|
104
|
-
id: { type: 'string' },
|
|
105
|
-
type: { type: 'string' },
|
|
106
|
-
wave: { type: 'string' },
|
|
107
|
-
'depends-on': { type: 'string' },
|
|
108
|
-
date: { type: 'string' },
|
|
109
|
-
},
|
|
110
|
-
formats: ['pretty', 'json'],
|
|
111
|
-
},
|
|
112
|
-
lint: { options: {}, formats: FORMATS },
|
|
113
|
-
list: { options: { ready: { type: 'boolean' }, archived: { type: 'boolean' } }, formats: ['pretty', 'json'] },
|
|
114
|
-
matrix: { options: { 'all-waves': { type: 'boolean' } }, formats: FORMATS },
|
|
115
|
-
schedule: { options: { write: { type: 'boolean' } }, formats: FORMATS },
|
|
116
|
-
archive: {
|
|
117
|
-
options: {
|
|
118
|
-
pr: { type: 'string' },
|
|
119
|
-
commit: { type: 'string' },
|
|
120
|
-
base: { type: 'string' },
|
|
121
|
-
summary: { type: 'string' },
|
|
122
|
-
date: { type: 'string' },
|
|
123
|
-
'allow-dirty': { type: 'boolean' },
|
|
124
|
-
'dry-run': { type: 'boolean' },
|
|
125
|
-
},
|
|
126
|
-
formats: ['pretty', 'json'],
|
|
127
|
-
},
|
|
128
|
-
unarchive: { options: { 'dry-run': { type: 'boolean' } }, formats: ['pretty', 'json'] },
|
|
129
|
-
};
|
|
130
148
|
/** Global options that take a value, so the value is not mistaken for the command. */
|
|
131
149
|
const VALUED = new Set(['--root', '--config', '--format']);
|
|
132
150
|
function commandOf(argv) {
|