@tsfpp/agents 1.3.4 → 1.4.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 +23 -0
- package/README.md +216 -31
- package/copilot/agents/tsfpp-annotate.agent.md +99 -119
- package/copilot/agents/tsfpp-audit.agent.md +182 -25
- package/copilot/agents/tsfpp-backfill-tests.agent.md +40 -1
- package/copilot/agents/tsfpp-guarded-coding.agent.md +22 -0
- package/copilot/agents/tsfpp-tdd.agent.md +59 -6
- package/copilot/prompts/trunk-changelog.prompt.md +160 -0
- package/copilot/skills/annotation-standard/SKILL.md +196 -0
- package/init.mjs +211 -197
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,29 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [1.4.0] - 2026-05-18
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added `copilot/prompts/trunk-changelog.prompt.md` to support trunk-based changelog authoring.
|
|
18
|
+
- Added `copilot/skills/annotation-standard/SKILL.md` to standardize annotation workflow and conventions.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Updated `README.md` with rationale updates and new LLM assets/flowchart documentation.
|
|
23
|
+
- Updated `init.mjs` to include deployment wiring for the new trunk changelog prompt and related README/documentation flow.
|
|
24
|
+
- Updated `copilot/agents/tsfpp-annotate.agent.md` to align with the annotation standard.
|
|
25
|
+
- Updated `copilot/agents/tsfpp-audit.agent.md` to include annotation-standard-aware checks.
|
|
26
|
+
- Updated `copilot/agents/tsfpp-guarded-coding.agent.md` to route annotation work through the standard.
|
|
27
|
+
|
|
28
|
+
## [1.3.5] - 2026-05-18
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Fixed an `init.mjs` regression by restoring the main-clause wrapper to prevent dangling awaits and improve idempotent handling of existing files.
|
|
33
|
+
- Updated `tdd` and `backfill-tests` agents to include a standards-enforcing self-review step.
|
|
34
|
+
- Expanded `audit` agent checklist coverage with an extensive backfill of checklist items.
|
|
35
|
+
|
|
13
36
|
## [1.3.4] - 2026-05-18
|
|
14
37
|
|
|
15
38
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,12 +1,103 @@
|
|
|
1
1
|
# @tsfpp/agents
|
|
2
2
|
|
|
3
|
-
GitHub Copilot agents, scoped instruction files, and Claude Code configuration for [TSF++](https://github.com/tsfpp/standard) projects.
|
|
3
|
+
GitHub Copilot agents, scoped instruction files, skills, and Claude Code configuration for [TSF++](https://github.com/tsfpp/standard) projects.
|
|
4
4
|
|
|
5
|
-
Installs a
|
|
5
|
+
Installs a pre-built AI tooling layer into your project's `.github/` directory. Once installed, agents are available in VS Code Copilot chat, instruction files are injected automatically whenever a matching file is open, and skills are loaded on demand based on semantic relevance.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why this works
|
|
10
|
+
|
|
11
|
+
TSF++ enforces a small, strict set of constraints: sum types with exhaustive dispatch, total functions via `Option` and `Result`, immutable records, no hidden effects, pipelines over imperative loops. These constraints exist because algebraic code is easier for a human to reason about locally — you can understand a function from its type alone, without tracing mutations or hidden control flow.
|
|
12
|
+
|
|
13
|
+
The same properties that help humans happen to help language models significantly more. A codebase that never throws, never mutates, and always makes failure explicit in the type signature gives a model far less surface to hallucinate over. Exhaustive `switch` with `absurd` means the model cannot forget a variant. `Result<T, E>` means error paths are always visible in the return type. `pipe` makes data flow linear and readable in one pass.
|
|
14
|
+
|
|
15
|
+
In practice: the agents generate fewer violations, the audit agent catches the ones that slip through, and the refactor agent can fix them mechanically because the fix is always the same shape. The standard makes the code predictable for humans — and predictable code is cheap for a model to generate correctly.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quickstart — greenfield project
|
|
20
|
+
|
|
21
|
+
The fastest way to create a fully configured TSF++ project is the bootstrap script. It handles everything in one command:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bash <(curl -fsSL https://raw.githubusercontent.com/tsfpp/agents/main/bootstrap/tsfpp-bootstrap.sh) my-project
|
|
25
|
+
cd my-project
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or, if you already have the script locally:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
bash tsfpp-bootstrap.sh my-project
|
|
32
|
+
cd my-project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Omit the project name to bootstrap in the current directory.
|
|
36
|
+
|
|
37
|
+
### What the bootstrap script does
|
|
38
|
+
|
|
39
|
+
| Step | What happens |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| 1 | `git init -b main` (skipped if a repo already exists) |
|
|
42
|
+
| 2 | `pnpm init` |
|
|
43
|
+
| 3 | Installs the full TSF++ ecosystem: `typescript`, `@tsfpp/standard`, `@tsfpp/tsconfig`, `@tsfpp/eslint-config`, `@tsfpp/prelude`, `@tsfpp/boundary`, `@tsfpp/agents` |
|
|
44
|
+
| 4 | Writes `tsconfig.json` extending `@tsfpp/tsconfig/app` |
|
|
45
|
+
| 5 | Writes `eslint.config.js` with the base TSF++ ESLint config |
|
|
46
|
+
| 6 | Patches `package.json` with `type: module` and `typecheck`, `lint`, `check` scripts |
|
|
47
|
+
| 7 | Creates `src/index.ts` with a getting-started comment |
|
|
48
|
+
| 8 | Runs `node node_modules/@tsfpp/agents/init.mjs` — installs all agents, instructions, skills, and prompts |
|
|
49
|
+
| 9 | Writes `.gitignore` (skipped if one already exists) |
|
|
50
|
+
| 10 | Runs `pnpm exec husky install` to activate pre-commit hooks |
|
|
51
|
+
|
|
52
|
+
### After bootstrapping
|
|
53
|
+
|
|
54
|
+
The bootstrap script does not install `@tsfpp/workflow` (commit linting, release automation) or configure a git remote — those steps are intentionally separate and optional.
|
|
55
|
+
|
|
56
|
+
**Add workflow tooling** (recommended for real projects):
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pnpm add -D @tsfpp/workflow @commitlint/cli @commitlint/config-conventional husky
|
|
60
|
+
node node_modules/@tsfpp/workflow/init.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This installs:
|
|
64
|
+
- `commitlint` with the Conventional Commits config
|
|
65
|
+
- Husky hooks: `commit-msg` (lint) and `pre-commit` (typecheck + lint)
|
|
66
|
+
- Release Please GitHub Actions workflow for automated semver releases and changelog generation
|
|
67
|
+
|
|
68
|
+
**Push to GitHub** — use the `/trunk-init-repo` prompt in Copilot chat:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
/trunk-init-repo
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This handles the initial conventional commit, adds the remote, pushes to `main`, and ensures Husky hooks are active after the git init.
|
|
75
|
+
|
|
76
|
+
### Complete sequence
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
# 1. Bootstrap
|
|
80
|
+
bash tsfpp-bootstrap.sh my-project
|
|
81
|
+
cd my-project
|
|
82
|
+
|
|
83
|
+
# 2. Open in VS Code
|
|
84
|
+
code .
|
|
85
|
+
|
|
86
|
+
# 3. Add workflow tooling (optional but recommended)
|
|
87
|
+
pnpm add -D @tsfpp/workflow @commitlint/cli @commitlint/config-conventional husky
|
|
88
|
+
node node_modules/@tsfpp/workflow/init.mjs
|
|
89
|
+
|
|
90
|
+
# 4. Push to GitHub — in Copilot chat:
|
|
91
|
+
# /trunk-init-repo
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
From there, the full agent workflow is available immediately.
|
|
95
|
+
|
|
96
|
+
---
|
|
6
97
|
|
|
7
98
|
## Prerequisites
|
|
8
99
|
|
|
9
|
-
All `@tsfpp/*` packages must be installed in the consumer project. The agents reference their documentation from `node_modules/@tsfpp/*/` at runtime — this
|
|
100
|
+
All `@tsfpp/*` packages must be installed in the consumer project. The agents reference their documentation from `node_modules/@tsfpp/*/` at runtime — this gives them stable, version-locked access to the standard and API surface without bundling duplicate content.
|
|
10
101
|
|
|
11
102
|
```sh
|
|
12
103
|
pnpm add -D \
|
|
@@ -17,37 +108,64 @@ pnpm add -D \
|
|
|
17
108
|
@tsfpp/tsconfig
|
|
18
109
|
```
|
|
19
110
|
|
|
111
|
+
---
|
|
112
|
+
|
|
20
113
|
## Installation
|
|
21
114
|
|
|
22
115
|
```sh
|
|
23
116
|
pnpm add -D @tsfpp/agents
|
|
24
|
-
|
|
117
|
+
node node_modules/@tsfpp/agents/init.mjs
|
|
25
118
|
```
|
|
26
119
|
|
|
27
120
|
The `init` script copies all files into your project and prompts before overwriting anything that already exists. Commit the generated files — they are workspace configuration, not build artefacts.
|
|
28
121
|
|
|
29
|
-
|
|
122
|
+
### Non-interactive mode
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
node node_modules/@tsfpp/agents/init.mjs --yes
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Copies all package-managed files without prompting. Skips `eslint.config.js` and `tsconfig.json` — those are workspace-owned and never touched automatically. Use this in `postinstall` scripts:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"scripts": {
|
|
133
|
+
"postinstall": "node node_modules/@tsfpp/agents/init.mjs --yes"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This ensures agents and instructions stay up to date with the installed package version on every `pnpm install`.
|
|
139
|
+
|
|
140
|
+
### Re-run without reinstalling
|
|
30
141
|
|
|
31
142
|
```sh
|
|
32
143
|
node node_modules/@tsfpp/agents/init.mjs
|
|
33
144
|
```
|
|
34
145
|
|
|
146
|
+
---
|
|
147
|
+
|
|
35
148
|
## What gets installed
|
|
36
149
|
|
|
37
150
|
```
|
|
38
151
|
.github/
|
|
39
|
-
copilot-instructions.md
|
|
152
|
+
copilot-instructions.md ← always-on workspace context
|
|
40
153
|
instructions/
|
|
41
|
-
tsfpp-base.instructions.md
|
|
42
|
-
tsfpp-prelude.instructions.md
|
|
43
|
-
tsfpp-
|
|
44
|
-
tsfpp-
|
|
154
|
+
tsfpp-base.instructions.md ← applyTo: **/*.ts
|
|
155
|
+
tsfpp-prelude.instructions.md ← applyTo: **/*.ts
|
|
156
|
+
tsfpp-api.instructions.md ← applyTo: routes, handlers, api dirs
|
|
157
|
+
tsfpp-react.instructions.md ← applyTo: **/*.tsx
|
|
158
|
+
tsfpp-testing.instructions.md ← applyTo: **/*.{test,spec}.{ts,tsx}
|
|
159
|
+
trunk.instructions.md ← applyTo: git workflow contexts
|
|
45
160
|
agents/
|
|
161
|
+
tsfpp-tdd.agent.md
|
|
162
|
+
tsfpp-backfill-tests.agent.md
|
|
46
163
|
tsfpp-guarded-coding.agent.md
|
|
47
164
|
tsfpp-audit.agent.md
|
|
48
165
|
tsfpp-refactor-engineer.agent.md
|
|
49
166
|
tsfpp-annotate.agent.md
|
|
50
167
|
prompts/
|
|
168
|
+
trunk-init-repo.prompt.md
|
|
51
169
|
tsfpp-new-module.prompt.md
|
|
52
170
|
tsfpp-boundary-review.prompt.md
|
|
53
171
|
skills/
|
|
@@ -55,53 +173,116 @@ node node_modules/@tsfpp/agents/init.mjs
|
|
|
55
173
|
prelude-api/SKILL.md
|
|
56
174
|
boundary-api/SKILL.md
|
|
57
175
|
react-coding-standard/SKILL.md
|
|
176
|
+
test-standard/SKILL.md
|
|
177
|
+
annotation-standard/SKILL.md
|
|
58
178
|
.claude/
|
|
59
|
-
CLAUDE.md
|
|
179
|
+
CLAUDE.md ← Claude Code project context
|
|
60
180
|
```
|
|
61
181
|
|
|
182
|
+
---
|
|
183
|
+
|
|
62
184
|
## Agents
|
|
63
185
|
|
|
64
186
|
| Agent | Purpose |
|
|
65
187
|
|-------|---------|
|
|
66
|
-
| `tsfpp-
|
|
67
|
-
| `tsfpp-
|
|
68
|
-
| `tsfpp-
|
|
69
|
-
| `tsfpp-
|
|
188
|
+
| `tsfpp-tdd` | The mandatory first step for new functionality. Writes a failing test suite that specifies the behaviour before any implementation exists. Verifies every test is red for assertion reasons, then hands off to `tsfpp-guarded-coding`. |
|
|
189
|
+
| `tsfpp-backfill-tests` | Writes tests for existing code that has no coverage. Reads the implementation, derives the implicit contract, writes passing tests, and reports uncovered edge cases and implementation gaps. |
|
|
190
|
+
| `tsfpp-guarded-coding` | Writes TSF++-compliant TypeScript. Infers the active layer from context (`core` · `api` · `dal` · `react` · `cli`); applies layer-specific constraints automatically. Refuses to start without failing tests in place. Runs `tsc --noEmit` after every file edit via a `PostToolUse` hook. |
|
|
191
|
+
| `tsfpp-audit` | Audits a target path, package, or layer for TSF++ violations. Supports focus areas: `types` · `boundary` · `complexity` · `loc` · `annotations` · `security` · `react` · `data` · `prelude` · `test` · `all`. Creates a structured markdown report in `docs/audits/` with per-slice checkboxes and a deviation register. |
|
|
192
|
+
| `tsfpp-refactor-engineer` | Reads an audit report and fixes violations slice by slice. Updates the report as it goes. Never resolves a violation by weakening a type. |
|
|
193
|
+
| `tsfpp-annotate` | Adds missing JSDoc blocks, module headers, inline comments (invariants, rejected alternatives, external contracts), `DEVIATION(N.M)` comments, paired `eslint-disable` annotations, and structured code markers (`TODO`, `FIXME`, `HACK`, `NOTE`, `OPTIMIZE`, `BUG`, `XXX`). Never touches runtime code. Uses the `/annotation-standard` skill. |
|
|
194
|
+
|
|
195
|
+
---
|
|
70
196
|
|
|
71
197
|
## Workflow
|
|
72
198
|
|
|
73
|
-
The agents
|
|
199
|
+
The agents hand off to each other. Each handoff is opt-in — the developer reviews and approves each transition via the handoff buttons in Copilot chat.
|
|
200
|
+
|
|
201
|
+
### New functionality (TDD first)
|
|
74
202
|
|
|
75
203
|
```
|
|
76
|
-
tsfpp-
|
|
77
|
-
→ tsfpp-
|
|
78
|
-
→ tsfpp-
|
|
79
|
-
→ tsfpp-
|
|
80
|
-
→ tsfpp-
|
|
204
|
+
tsfpp-tdd write failing tests
|
|
205
|
+
→ tsfpp-guarded-coding implement against the tests
|
|
206
|
+
→ tsfpp-audit verify compliance
|
|
207
|
+
→ tsfpp-refactor-engineer fix any violations
|
|
208
|
+
→ tsfpp-annotate document the result
|
|
81
209
|
```
|
|
82
210
|
|
|
83
|
-
|
|
211
|
+
### Existing code without tests
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
tsfpp-backfill-tests write passing tests from the existing contract
|
|
215
|
+
→ tsfpp-audit verify test compliance + coverage
|
|
216
|
+
→ tsfpp-refactor-engineer fix violations in tests or implementation
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Compliance audit only
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
tsfpp-audit audit target with chosen focus
|
|
223
|
+
→ tsfpp-refactor-engineer fix violations
|
|
224
|
+
→ tsfpp-annotate document deviations and markers
|
|
225
|
+
→ tsfpp-audit re-audit to verify fixes
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
84
229
|
|
|
85
230
|
## Instruction files
|
|
86
231
|
|
|
87
|
-
Instruction files are injected automatically by VS Code Copilot whenever a matching file is open.
|
|
232
|
+
Instruction files are injected automatically by VS Code Copilot whenever a matching file is open. No explicit invocation required.
|
|
88
233
|
|
|
89
234
|
| File | Active for | Content |
|
|
90
235
|
|------|-----------|---------|
|
|
91
236
|
| `tsfpp-base` | `**/*.ts` | Forbidden constructs, required patterns, size limits, ADT idioms, marker format |
|
|
92
|
-
| `tsfpp-prelude` | `**/*.ts` | Full `@tsfpp/prelude` API
|
|
93
|
-
| `tsfpp-
|
|
94
|
-
| `tsfpp-
|
|
237
|
+
| `tsfpp-prelude` | `**/*.ts` | Full `@tsfpp/prelude` API: `Option`, `Result`, `ReadonlyMap`, `ReadonlySet`, `List`, combinators, record decoding |
|
|
238
|
+
| `tsfpp-api` | Routes, handlers, api dirs | Handler shape, `@tsfpp/boundary` imports, Zod validation, middleware composition, status codes, security baseline |
|
|
239
|
+
| `tsfpp-react` | `**/*.tsx` | Component shape, discriminated union state, state elimination ladder, `useEffect` policy, TanStack Query, RHF+Zod, cva/cn styling |
|
|
240
|
+
| `tsfpp-testing` | `**/*.{test,spec}.{ts,tsx}` | AAA structure, factory usage, fast-check property tests, MSW, RTL query hierarchy, forbidden patterns |
|
|
241
|
+
| `trunk` | Git workflow contexts | Conventional Commits, branch naming, PR discipline, trunk-based development rules |
|
|
242
|
+
|
|
243
|
+
The workspace-level `copilot-instructions.md` is always active regardless of which file is open. It establishes the TSF++ axioms, the prelude-first principle, and the absolute non-negotiables.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Skills
|
|
248
|
+
|
|
249
|
+
Skills are loaded automatically by Copilot based on semantic match with what is being discussed. Unlike instruction files, skills are not file-scoped — they are injected into the context when the model determines they are relevant.
|
|
95
250
|
|
|
96
|
-
|
|
251
|
+
| Skill | Loaded when |
|
|
252
|
+
|-------|------------|
|
|
253
|
+
| `coding-standard` | Writing or reviewing TypeScript; checking rules; auditing compliance |
|
|
254
|
+
| `prelude-api` | Importing from or discussing `@tsfpp/prelude`; choosing between combinators |
|
|
255
|
+
| `boundary-api` | Writing handlers; discussing HTTP error mapping, pagination, or middleware |
|
|
256
|
+
| `react-coding-standard` | Writing React components, hooks, forms, or stores |
|
|
257
|
+
| `test-standard` | Writing or reviewing test files; discussing coverage or test structure |
|
|
258
|
+
| `annotation-standard` | Writing or reviewing any comment, JSDoc block, module header, code marker, or DEVIATION; discussing what to annotate and why |
|
|
259
|
+
|
|
260
|
+
Skills are distilled from the full standard documents — concise enough to fit in the model's context without crowding out code.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Prompts
|
|
265
|
+
|
|
266
|
+
Reusable slash commands available in Copilot chat.
|
|
267
|
+
|
|
268
|
+
| Prompt | Purpose |
|
|
269
|
+
|--------|---------|
|
|
270
|
+
| `/trunk-init-repo` | Initialises a git repository with `main` branch, `.gitignore`, an initial conventional commit, optional remote, and Husky hook activation. Run once after bootstrapping. |
|
|
271
|
+
| `/trunk-changelog` | Inspects the current working tree diff, derives the correct conventional commit message, and appends a matching entry to the `## [Unreleased]` section of `CHANGELOG.md`. |
|
|
272
|
+
| `/tsfpp-new-module` | Scaffolds a new TSF++ module with the correct file structure, barrel export, and JSDoc stubs. |
|
|
273
|
+
| `/tsfpp-boundary-review` | Reviews an HTTP handler against the full `@tsfpp/boundary` contract. |
|
|
274
|
+
|
|
275
|
+
---
|
|
97
276
|
|
|
98
277
|
## How agents reference the standard
|
|
99
278
|
|
|
100
|
-
Each agent reads
|
|
279
|
+
Each agent reads TSF++ standards directly from `node_modules/@tsfpp/standard/spec/` and the prelude API from `node_modules/@tsfpp/prelude/` at runtime. This means:
|
|
280
|
+
|
|
281
|
+
- The agent always enforces the version of the standard your project has installed.
|
|
282
|
+
- Upgrading `@tsfpp/standard` automatically updates what the agents enforce — no changes to agent files required.
|
|
283
|
+
- The agent files contain workflow logic, checklist structure, and layer-specific patterns — not rule content. Rule content lives in the standard packages.
|
|
101
284
|
|
|
102
|
-
|
|
103
|
-
- Upgrading `@tsfpp/standard` automatically updates what the agents enforce — no changes to the agent files required.
|
|
104
|
-
- The agent files themselves contain workflow logic, not rule content.
|
|
285
|
+
---
|
|
105
286
|
|
|
106
287
|
## Further reading
|
|
107
288
|
|
|
@@ -111,6 +292,8 @@ Each agent reads the TSF++ standards directly from `node_modules/@tsfpp/standard
|
|
|
111
292
|
- [`@tsfpp/eslint-config`](https://github.com/tsfpp/eslint-config) — ESLint flat config
|
|
112
293
|
- [`@tsfpp/tsconfig`](https://github.com/tsfpp/tsconfig) — TypeScript compiler presets
|
|
113
294
|
|
|
295
|
+
---
|
|
296
|
+
|
|
114
297
|
## Release process
|
|
115
298
|
|
|
116
299
|
Releases are automated with Release Please.
|
|
@@ -119,6 +302,8 @@ Releases are automated with Release Please.
|
|
|
119
302
|
2. Release Please opens or updates a release PR on each merge to `main`.
|
|
120
303
|
3. Merging the release PR publishes to npm, creates a GitHub release, and updates `CHANGELOG.md`.
|
|
121
304
|
|
|
305
|
+
---
|
|
306
|
+
|
|
122
307
|
## License
|
|
123
308
|
|
|
124
309
|
MIT
|