@ersbeth/codelife 0.0.0-stage → 0.11.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.
Files changed (214) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +65 -2
  4. package/agents/evaluate.md +97 -0
  5. package/agents/ground.md +49 -0
  6. package/agents/implementer.md +49 -0
  7. package/agents/judge.md +128 -0
  8. package/agents/planner.md +45 -0
  9. package/agents/prerequisites.md +62 -0
  10. package/agents/reader.md +40 -0
  11. package/agents/reproduce.md +65 -0
  12. package/bin/codelife +4 -0
  13. package/package.json +38 -4
  14. package/presets/tsconfig.json +14 -0
  15. package/presets/vite.config.d.mts +3 -0
  16. package/presets/vite.config.mjs +49 -0
  17. package/scripts/audit/cadence.mjs +24 -0
  18. package/scripts/audit/close.mjs +57 -0
  19. package/scripts/audit/file.mjs +70 -0
  20. package/scripts/audit/fold.mjs +48 -0
  21. package/scripts/audit/lib.mjs +93 -0
  22. package/scripts/audit/prepare.mjs +180 -0
  23. package/scripts/audit/table.mjs +65 -0
  24. package/scripts/brainstorm/decided.mjs +6 -0
  25. package/scripts/brainstorm/lib.mjs +33 -0
  26. package/scripts/brainstorm/named.mjs +9 -0
  27. package/scripts/changelog.mjs +100 -0
  28. package/scripts/check-tag.mjs +19 -0
  29. package/scripts/checks.mjs +53 -0
  30. package/scripts/codelife.mjs +42 -0
  31. package/scripts/commit-msg.mjs +43 -0
  32. package/scripts/contract.mjs +179 -0
  33. package/scripts/former-form.mjs +170 -0
  34. package/scripts/git/branches.mjs +6 -0
  35. package/scripts/git/commit.mjs +40 -0
  36. package/scripts/git/diff.mjs +17 -0
  37. package/scripts/git/diff_lines.mjs +5 -0
  38. package/scripts/git/held.mjs +53 -0
  39. package/scripts/git/is_ancestor.mjs +28 -0
  40. package/scripts/git/lib.mjs +20 -0
  41. package/scripts/git/log_since.mjs +27 -0
  42. package/scripts/git/parent.mjs +51 -0
  43. package/scripts/git/show.mjs +4 -0
  44. package/scripts/git/status.mjs +25 -0
  45. package/scripts/git/submodules.mjs +5 -0
  46. package/scripts/init.mjs +21 -0
  47. package/scripts/install.mjs +32 -0
  48. package/scripts/lenses/A11Y.md +91 -0
  49. package/scripts/lenses/ARCH.md +90 -0
  50. package/scripts/lenses/BUG.md +52 -0
  51. package/scripts/lenses/BUILD.md +40 -0
  52. package/scripts/lenses/COMPAT.md +67 -0
  53. package/scripts/lenses/CONF.md +49 -0
  54. package/scripts/lenses/DEPS.md +43 -0
  55. package/scripts/lenses/DOC.md +73 -0
  56. package/scripts/lenses/FAIL.md +57 -0
  57. package/scripts/lenses/GOV.md +56 -0
  58. package/scripts/lenses/GUARD.md +47 -0
  59. package/scripts/lenses/INTENT.md +52 -0
  60. package/scripts/lenses/LEAN.md +49 -0
  61. package/scripts/lenses/LINES.md +48 -0
  62. package/scripts/lenses/PERF.md +61 -0
  63. package/scripts/lenses/PKG.md +58 -0
  64. package/scripts/lenses/SEC.md +49 -0
  65. package/scripts/lenses/STANDARDS.md +62 -0
  66. package/scripts/lenses/TYPE.md +56 -0
  67. package/scripts/lenses/VERIF.md +81 -0
  68. package/scripts/link.mjs +40 -0
  69. package/scripts/methods/cause.md +21 -0
  70. package/scripts/methods/comments.md +157 -0
  71. package/scripts/methods/description.md +67 -0
  72. package/scripts/methods/message.md +24 -0
  73. package/scripts/methods/red-green.md +28 -0
  74. package/scripts/methods/refusal.md +22 -0
  75. package/scripts/mr/open.mjs +219 -0
  76. package/scripts/node-floor.mjs +19 -0
  77. package/scripts/onboard/prep.mjs +93 -0
  78. package/scripts/overview/codelife-line.mjs +33 -0
  79. package/scripts/overview/next.mjs +76 -0
  80. package/scripts/overview/overview.mjs +308 -0
  81. package/scripts/package-manager.mjs +20 -0
  82. package/scripts/port/item.mjs +94 -0
  83. package/scripts/port/lib.mjs +194 -0
  84. package/scripts/port/prep.mjs +33 -0
  85. package/scripts/port/version.mjs +50 -0
  86. package/scripts/pre-commit.mjs +43 -0
  87. package/scripts/pre-push.mjs +53 -0
  88. package/scripts/prepare-lib.mjs +81 -0
  89. package/scripts/read-roadmap/lib.mjs +116 -0
  90. package/scripts/read-roadmap/moved.mjs +70 -0
  91. package/scripts/read-roadmap/range.mjs +15 -0
  92. package/scripts/release/cut.mjs +120 -0
  93. package/scripts/release/entry.mjs +52 -0
  94. package/scripts/release/lib.mjs +136 -0
  95. package/scripts/release/prep.mjs +58 -0
  96. package/scripts/review-mr/prepare.mjs +163 -0
  97. package/scripts/roadmap/assemble.mjs +269 -0
  98. package/scripts/roadmap/cut.mjs +88 -0
  99. package/scripts/roadmap/prepare.mjs +249 -0
  100. package/scripts/roadmap/roadmap.md +30 -0
  101. package/scripts/roadmap/write.mjs +53 -0
  102. package/scripts/search.mjs +29 -0
  103. package/scripts/setup/contract.mjs +25 -0
  104. package/scripts/setup/item.mjs +166 -0
  105. package/scripts/setup/lib.mjs +205 -0
  106. package/scripts/setup/prep.mjs +106 -0
  107. package/scripts/spec-form.mjs +444 -0
  108. package/scripts/spec.mjs +56 -0
  109. package/scripts/tags.mjs +27 -0
  110. package/scripts/target/target-app.md +134 -0
  111. package/scripts/target/target-base.md +127 -0
  112. package/scripts/target/target-lib.md +143 -0
  113. package/scripts/target/templates/audit/README.md +21 -0
  114. package/scripts/target/templates/characterisation/bundle.test.ts +43 -0
  115. package/scripts/target/templates/characterisation/declarations.test.ts +44 -0
  116. package/scripts/target/templates/characterisation/entries.ts +50 -0
  117. package/scripts/target/templates/characterisation/examples.test.ts +18 -0
  118. package/scripts/target/templates/characterisation/import.test.ts +22 -0
  119. package/scripts/target/templates/characterisation/surface.test.ts +68 -0
  120. package/scripts/target/templates/cliff.toml +61 -0
  121. package/scripts/target/templates/githooks/commit-msg +9 -0
  122. package/scripts/target/templates/githooks/pre-commit +9 -0
  123. package/scripts/target/templates/githooks/pre-push +9 -0
  124. package/scripts/target/templates/playground/index.html +11 -0
  125. package/scripts/target/templates/playground/main.ts +9 -0
  126. package/scripts/target/templates/spec/README.md +35 -0
  127. package/scripts/target/templates/spec/capability.md +36 -0
  128. package/scripts/target/templates/spec/glossary.md +5 -0
  129. package/scripts/target/templates/spec/journey.md +29 -0
  130. package/scripts/target/templates/spec/principles.md +15 -0
  131. package/scripts/target/templates/spec/users.md +15 -0
  132. package/scripts/target/templates/tsconfig.json +9 -0
  133. package/scripts/target/templates/vite.config.ts +4 -0
  134. package/scripts/text.mjs +6 -0
  135. package/scripts/tracker/account.mjs +5 -0
  136. package/scripts/tracker/auth.mjs +9 -0
  137. package/scripts/tracker/cli.version.mjs +5 -0
  138. package/scripts/tracker/close.mjs +20 -0
  139. package/scripts/tracker/create.mjs +47 -0
  140. package/scripts/tracker/issue.section.mjs +25 -0
  141. package/scripts/tracker/issue.update.mjs +13 -0
  142. package/scripts/tracker/issue.view.mjs +16 -0
  143. package/scripts/tracker/lib.mjs +130 -0
  144. package/scripts/tracker/list.mjs +14 -0
  145. package/scripts/tracker/list.whole.mjs +20 -0
  146. package/scripts/tracker/lot.row.mjs +42 -0
  147. package/scripts/tracker/mr.checkout_command.mjs +5 -0
  148. package/scripts/tracker/mr.comments.mjs +50 -0
  149. package/scripts/tracker/mr.create.mjs +12 -0
  150. package/scripts/tracker/mr.description.mjs +8 -0
  151. package/scripts/tracker/mr.fetch_head.mjs +5 -0
  152. package/scripts/tracker/mr.find_for_branch.mjs +9 -0
  153. package/scripts/tracker/mr.post.mjs +40 -0
  154. package/scripts/tracker/mr.update_description.mjs +8 -0
  155. package/scripts/tracker/mr.verdict.mjs +34 -0
  156. package/scripts/tracker/mr.view.mjs +20 -0
  157. package/scripts/tracker/none.mjs +79 -0
  158. package/scripts/tracker/note.mjs +12 -0
  159. package/scripts/tracker/reopen.mjs +8 -0
  160. package/scripts/tracker/search.mjs +8 -0
  161. package/scripts/tracker/search_id.mjs +18 -0
  162. package/scripts/tracker/templates/bug.md +35 -0
  163. package/scripts/tracker/templates/feature.md +40 -0
  164. package/scripts/tracker/templates/lot.md +46 -0
  165. package/scripts/tracker/title.join.mjs +6 -0
  166. package/scripts/tracker/title.leave.mjs +7 -0
  167. package/scripts/tracker/title.lot_of.mjs +8 -0
  168. package/scripts/tracker/title.search_id_filter.mjs +17 -0
  169. package/scripts/tracker/title.sentence_of.mjs +5 -0
  170. package/scripts/tracker/view.mjs +25 -0
  171. package/scripts/verbs.mjs +579 -0
  172. package/scripts/verify/BUILD.mjs +33 -0
  173. package/scripts/verify/DEPS.mjs +15 -0
  174. package/scripts/verify/GOV.mjs +34 -0
  175. package/scripts/verify/GUARD.mjs +34 -0
  176. package/scripts/verify/LINES.mjs +22 -0
  177. package/scripts/verify/PKG.mjs +56 -0
  178. package/scripts/verify/TYPE.mjs +34 -0
  179. package/scripts/verify/cut.mjs +73 -0
  180. package/scripts/verify/history.mjs +32 -0
  181. package/scripts/verify/lib.mjs +129 -0
  182. package/scripts/verify/report.mjs +104 -0
  183. package/scripts/verify/timing.mjs +19 -0
  184. package/scripts/verify.mjs +29 -0
  185. package/scripts/work/branch.mjs +81 -0
  186. package/scripts/work/close.mjs +74 -0
  187. package/scripts/work/member.mjs +73 -0
  188. package/scripts/work/prepare.mjs +116 -0
  189. package/scripts/work/reopen.mjs +40 -0
  190. package/scripts/work-feedback/prepare.mjs +96 -0
  191. package/skills/audit/SKILL.md +110 -0
  192. package/skills/audit/templates/index.md +36 -0
  193. package/skills/audit/templates/lens.md +44 -0
  194. package/skills/brainstorm/SKILL.md +144 -0
  195. package/skills/brainstorm/references/exit.md +57 -0
  196. package/skills/brainstorm/templates/note.md +79 -0
  197. package/skills/commit/SKILL.md +68 -0
  198. package/skills/mr/SKILL.md +82 -0
  199. package/skills/new-issue/SKILL.md +87 -0
  200. package/skills/onboard/SKILL.md +103 -0
  201. package/skills/onboard/templates/CLAUDE.md +31 -0
  202. package/skills/onboard/templates/CONTRIBUTING.md +34 -0
  203. package/skills/onboard/templates/README.md +30 -0
  204. package/skills/onboard/templates/SKILL.md +24 -0
  205. package/skills/onboard/templates/package-CLAUDE.md +15 -0
  206. package/skills/overview/SKILL.md +59 -0
  207. package/skills/port-toolchain/SKILL.md +127 -0
  208. package/skills/release/SKILL.md +78 -0
  209. package/skills/review-mr/SKILL.md +106 -0
  210. package/skills/review-mr/templates/pass.md +29 -0
  211. package/skills/roadmap/SKILL.md +137 -0
  212. package/skills/setup/SKILL.md +112 -0
  213. package/skills/work/SKILL.md +139 -0
  214. package/skills/work-feedback/SKILL.md +113 -0
@@ -0,0 +1,127 @@
1
+ # The base — what every repository has
2
+
3
+ Whatever its kind. A library or an application adds a toolchain on top of this; a repository whose
4
+ `kind` is `none` stops here, and that is a complete answer, not a degraded one.
5
+
6
+ ## Branches
7
+
8
+ Gitflow, simplified. `main` is the default branch and is always shippable. `develop` is where
9
+ work lands. **Every work branch carries its issue number** — if the work deserves a branch it
10
+ deserves to be traceable, and when no issue exists yet, one is filed first, even for work already
11
+ done.
12
+ `feature/<issue>-<slug>` and `fix/<issue>-<slug>` are cut from `develop`;
13
+ `hotfix/<issue>-<slug>` from `main` or from a support branch; `lot/<umbrella>-<slug>` carries one
14
+ lot; `support/<major>.<peer>` carries an older peer line. Merge commits everywhere — no squash, no
15
+ fast-forward-only.
16
+
17
+ ## Tracker
18
+
19
+ The fixed labels — `bug`, `feature`, `severity-critical`, `severity-high`, `severity-mid`,
20
+ `severity-low`, `wontfix`, `lot` — the plugin's three issue templates at the forge's
21
+ native location, and the machine connected to the host `origin` names, one credential per host.
22
+
23
+ ## Scripts
24
+
25
+ Five, in `package.json`, with these exact names:
26
+
27
+ | Script | Runs |
28
+ |---|---|
29
+ | `verify:commit` | the fast tier: what must hold before every commit |
30
+ | `verify:push` | the complete tier: what must hold before a push; `/audit`'s baseline |
31
+ | `verify:release` | what must hold before a tag |
32
+ | `test:all` | the whole suite |
33
+ | `test:one` | one test file, passed as the argument |
34
+
35
+ A repository with no `package.json` at all gets a **private** one carrying the five scripts and an
36
+ `engines` floor — the runtime the port runs on, the Binaries table's rule absent an older
37
+ measurement — and a `package.json` that declares no `engines` gains one the same way, so the
38
+ floor that table defers to is one the port writes. A script with nothing to run prints
39
+ `nothing to run` and exits 0 — which is not the same as a **missing**
40
+ script: that is a *not ported* state, and the port writes it.
41
+
42
+ ## Hooks
43
+
44
+ Three, versioned in `.githooks/`, wired by `core.hooksPath` from the package's `scripts/install.mjs`, which the repository's
45
+ `prepare` runs and which also sets `fetch.prune`, the clone's other imposed local key, so that a
46
+ clone's view of the forge does not drift as branches are deleted at merge.
47
+ `codelife setup item hooks` writes the line; everywhere else, including here, names the keys.
48
+ Each hook is a stub that hands over to the package's script of the same name and refuses, naming
49
+ the install, when the package is absent:
50
+
51
+ | Hook | Runs | Why it exists |
52
+ |---|---|---|
53
+ | `pre-commit` | the specification's form, and no tier: `codelife verify commit` is the commit tier's one run | a commit by hand that breaks the specification is refused |
54
+ | `pre-push` | `verify:push`, or `verify:release` when what is pushed is a version tag | a red tip never reaches the remote |
55
+ | `commit-msg` | the Conventional Commits check, a plugin-provided shell script with no dependency | the changelog is generated from these messages |
56
+
57
+ They are **git** hooks, versioned and readable, not Claude Code hooks: they block a human and an
58
+ agent alike, whatever tool issues the git command. The plugin's own `hooks/` directory stays
59
+ empty by decision — nothing in codelife runs on its own.
60
+
61
+ ## Folders
62
+
63
+ | Path | Created by |
64
+ |---|---|
65
+ | `docs/audit/README.md` | the port — the campaign table, one row per campaign linking its report at the commit that closed it |
66
+ | `docs/audit/lenses/` | the port |
67
+ | `docs/brainstorm/` | the port — the dated notes `/brainstorm` leaves, pending until the specification absorbs them |
68
+ | `docs/spec/README.md` | the port — the specification's vision and its one index |
69
+ | `docs/spec/users.md`, `docs/spec/glossary.md`, `docs/spec/principles.md` | the port |
70
+ | `docs/spec/journeys/`, `docs/spec/product/`, `docs/spec/system/` | the port — a journey, and a capability under its domain, `product/<activity>/` or `system/<component>/`, is written from its template when a note first creates it |
71
+
72
+ ## Changelog
73
+
74
+ `CHANGELOG.md` at the root, generated by git-cliff from the commits with the plugin's
75
+ `cliff.toml`. Nothing above the generated/history marker is
76
+ written by hand — `scripts/changelog.mjs` is where that line is spelled, and
77
+ the only place it is: **anything the generator does not produce lives below it**. That covers a
78
+ repository arriving with a hand-written changelog, whose history stays below untouched, and equally
79
+ a repository with no history at all whose changelog carries explanatory prose — the marker is what
80
+ tells a regenerator where to stop, so a file that needs neither still gains nothing by omitting it.
81
+
82
+ ## Binaries
83
+
84
+ What the plugin needs from the machine, by name. *script*: a `verify:*` tier calls it, so a miss
85
+ turns a gate red. *skill*: a skill calls it, so a miss stops that skill and nothing else.
86
+ The plugin's own repository derives a prerequisites suite from this table and runs it before
87
+ every other suite, so a missing binary is named before the suite that would have met it first
88
+ reports a wrong cause. The **floor** is the lowest version the plugin's use is known to work on —
89
+ absent an older measurement, the version it was measured on — with its reason beside it; the same
90
+ suite compares each binary's `--version` against it. A floor is lowered only by re-measuring every
91
+ use that cites it. A cell reads `<version> — <reason>`, `none` — with or without a reason the
92
+ same way — or `engines`, and its first token is what is compared; a row naming several binaries
93
+ writes `` `name` <floor> `` per name,
94
+ `;`-separated. `none` is presence only. `engines` defers to `package.json`, the one place a
95
+ runtime floor lives.
96
+
97
+ | Binary | Class | Floor | Needed by |
98
+ |---|---|---|---|
99
+ | `git` | script | 2.23 — `switch`, which `/work` and `codelife release cut` run to put a run on its branch | every hook and every skill |
100
+ | `sh` | script | none — the clone's own `/bin/sh`, bash on macOS and dash on Debian, and no version compares across them: a parse check measures whichever is here, so the templates are held to both (measured under bash 3.2 and dash) | every hook the port writes runs under it |
101
+ | `node` | script | engines | the package manager runs on it, and every script, `spec-form.mjs` included, which a hook runs as `node <path>` on the installed package's file; each refuses to run below the floor and says so, through `scripts/node-floor.mjs` |
102
+ | `pnpm`, `npm` or `yarn` — the one the lockfile names | script | engines | the hooks run `verify:*` through it |
103
+ | `claude` | skill | 2.1.312 — a subagent dispatched with no model running on its dispatcher's, which keeps a campaign's lenses on their judge's Opus, measured there and on no older version | the plugin runs inside it |
104
+ | `glab` or `gh` — the one `tracker.kind` names | skill | `glab` 1.113.0 — every form the scripts under `scripts/tracker/` send was read and measured at it, and `label create --name` needs it; `gh` none — the thread reads were measured at 2.101.0, and a floor waits for a whole review measured on it | every `codelife` verb that reads or writes the tracker; `none` needs neither |
105
+ | `npx` | skill | none — it ships with `node`, whose floor is `engines`; measured at 10.9.3 | the `reproduce` agent's browser, through the server the plugin declares. The browser itself is the server's: at the pinned version its default channel is the machine's Google Chrome, which `npx playwright install chrome` fetches and nothing downloads on first use — an `.app` is not on `PATH`, so no row here can see it, and a miss surfaces as the server's own error, `Chromium distribution 'chrome' is not found` |
106
+
107
+ ## Marker
108
+
109
+ Every file the plugin writes opens with a `generated by codelife` marker, and the tail after it says
110
+ **who owns the file** — so a contributor who opens one reads its status from the file itself, rather
111
+ than looking it up in a list somebody has to maintain. Two owners — the plugin, or this
112
+ repository — spelled four ways between them, and no third owner:
113
+
114
+ | Tail | Meaning |
115
+ |---|---|
116
+ | `edit the plugin, not this file` | the plugin owns this copy; change it in the plugin and re-port |
117
+ | `edit the plugin's template, not this file` | the same, where the marker is a shell comment rather than an HTML one |
118
+ | `/<skill>` | this repository grows the file, and that skill is what writes it |
119
+ | `maintained by hand` | this repository grows the file, and no skill writes it |
120
+
121
+ A **template** carries the marker its *deployment* should carry, never one about itself: the reader
122
+ of the deployed file is who the marker is for.
123
+
124
+ A tail that names nobody — the bare `<!-- generated by codelife -->` — is the one thing this
125
+ vocabulary forbids, because it forces the reader back to a list. The plugin's own marker suite
126
+ derives its wordings and its by-hand tail from this table, and the skill names from the skills
127
+ themselves.
@@ -0,0 +1,143 @@
1
+ # A conforming library — on top of the base
2
+
3
+ Everything in `target-base.md` applies here in full; this file adds the toolchain, and nothing it
4
+ says replaces a base row.
5
+
6
+ A library is a package other repositories consume. That is the only thing distinguishing it from
7
+ an application here, and it decides every row below: the build is a `pack` rather than a bundle,
8
+ the output is versioned so a consumer can install from the forge, and the dev mode never imports
9
+ that output.
10
+
11
+ ## Package manager
12
+
13
+ pnpm, pinned in `packageManager`, with `minimumReleaseAge` set explicitly in `pnpm-workspace.yaml` —
14
+ `1440`, pnpm's own 24 hours, unless the repository already sets a value. Only an explicit value makes
15
+ pnpm refuse a release younger than the window; left at its default, pnpm writes an exclusion for it
16
+ and installs. A library with in-house dependencies is a workspace:
17
+ `pnpm-workspace.yaml` with `packages: [submodules/*]`, each in-house library a **root** submodule
18
+ initialised non-recursively, and `pnpm.overrides` sending every in-house package to `workspace:*`.
19
+ Submodules are the end state, not a transition, so a `file:`
20
+ specification is converted, never kept beside one.
21
+
22
+ `shamefully-hoist` is never applied to make a phantom dependency work: the phantom is declared, or
23
+ it is filed as a debt.
24
+
25
+ ## Toolchain
26
+
27
+ Vite+, invoked as `vp`: the `vite-plus` devDependency, installed with the repository's other
28
+ dependencies and run through its scripts — not a machine binary. One `vite.config.ts` at the root,
29
+ `defineConfig(mergeConfig(library({ entry }), { … }))`: the package's preset carries the `pack`
30
+ block and has Vite read `tsconfig.json`'s `paths`, which map the package name to `src/` for the
31
+ tests and any playground — one mapping the type checker and Vite share, one file, not one per mode. What a repository adds, a plugin or a test setting, goes in
32
+ the object `mergeConfig` takes; that merge concatenates arrays, so what varies from one library to
33
+ the next is a parameter of `library`, and a value the preset fixes is the target's, not one to
34
+ replace.
35
+
36
+ The target commits to the **configuration surface** and the `vp` scripts, not to Vite+ itself,
37
+ which is beta: if it stalls, the same configuration runs under Vite, Vitest, Oxlint and Oxfmt
38
+ installed separately, which changes this file and not the target.
39
+
40
+ ## Build
41
+
42
+ `vp pack`. ESM only — no CommonJS output and no dual package. Every dependency external, both
43
+ `dependencies` and `peerDependencies`, so a consumer resolves one copy of each. `dts: true`, so the
44
+ types ship with the build rather than being written by hand — emitted through the `typescript`
45
+ devDependency, without which `vp pack` refuses to build them.
46
+
47
+ The output is `dist/`, and `dist/` is **versioned**. A consumer installs
48
+ the library from the forge by tag, with no build step of its own, which is what a `prepare` script
49
+ building at install time cannot promise across package managers.
50
+
51
+ ## Types
52
+
53
+ `tsconfig.json` extending the package's `presets/tsconfig.json`, which fixes `strict`,
54
+ `verbatimModuleSyntax`, `moduleResolution: bundler` and `noEmit` — the repository keeps `target`,
55
+ `lib` and `include`. The emit belongs to `vp pack`, which is why the type checker does none. Checked by `vp check`, which type-checks only because the preset sets both
56
+ `lint.options.typeAware` and `lint.options.typeCheck`.
57
+
58
+ `target` and `lib` are the exception: they name the ECMAScript edition the library's runtime floor
59
+ supports — the floor its `engines` declares, and any browser it documents — so they differ from one
60
+ library to the next. The template's `ES2022` is where a library needing nothing newer starts; one
61
+ calling a later standard API raises both to its floor, which states what it needs and relaxes
62
+ nothing. Pinned to an edition either way, never `ESNext`, which moves every consumer's floor with
63
+ the next TypeScript release.
64
+
65
+ An error is fixed, never silenced by a blanket `any`; a `// @ts-expect-error` carries the reason it
66
+ is there, and an error worth keeping is worth an issue.
67
+
68
+ ## Lint and format
69
+
70
+ Oxlint and Oxfmt, both through `vp check`. No Biome, no ESLint, no Prettier — one formatter, so a
71
+ file's shape does not depend on which tool last touched it.
72
+
73
+ The linter is not swapped the way the formatter is: its value is the rules someone enabled after
74
+ being bitten, so the old one's are carried over rule by rule. Oxlint runs **type-aware** —
75
+ `lint.options.typeAware` in the vite preset — because without it every rule that needs the type
76
+ checker, `typescript/no-floating-promises` among them, is silent even set to `error`, and
77
+ `vp check` still reports no lint error.
78
+
79
+ ## Tests
80
+
81
+ Vitest through `vp test`. `test/` mirrors `src/`, so the test for a module is where its path says
82
+ it is and nothing has to be searched for.
83
+
84
+ Configured in `vite.config.ts`'s `test` block, like everything else. A suite whose environment
85
+ differs — a browser, a runtime flag, no mapping to the source — is an inline project in
86
+ `test.projects`, run alone with `vp test --project <name>`, and inherits the root's `resolve` and
87
+ plugins only through `extends: true`. Coverage is the root's: a suite's thresholds are keyed there
88
+ by the sources it covers, because a project's own `coverage` is ignored without a word.
89
+
90
+ The characterisation tests written when the repository was ported — the public surface, the import
91
+ of the current build, the examples turned into tests — are kept as the **floor**: they describe the
92
+ behaviour consumers already depend on, so they are extended, never weakened to let a change pass.
93
+
94
+ ## Dev mode
95
+
96
+ A `playground/` is **optional**: a library has one when it wants a page, and none otherwise — both
97
+ conform. A library whose behaviour a test shows better than a
98
+ page gains nothing from one but a file the type check keeps alive.
99
+
100
+ When there is one, it conforms on two points. It imports the library **by its package name**, which
101
+ `tsconfig.json`'s `paths` map to `src/` — never from `dist/`, never through a relative path
102
+ into `src/`: a playground reading the build shows the last
103
+ `pack`, not the working tree, which is the one thing a dev mode must not do. And the manifest carries
104
+ a `dev` script, `PLAYGROUND=1 vp dev`, which the configuration's `root` line reads to serve
105
+ `playground/`. The server starts from the package root, not with `vp dev playground`: Vite looks its
106
+ configuration up in the root it is given, so serving the folder loads no `vite.config.ts` and the
107
+ package name resolves to nothing. It is not a workspace package depending on the library.
108
+
109
+ ## Hooks
110
+
111
+ The base's three, and the stubs are the base's. The `pre-commit` script, reading `kind: lib` in the
112
+ contract, runs each `generated[]` entry whose `when` is not empty and stages its path with the
113
+ commit: `dist/` is versioned, so a commit touching `src/` without it leaves the build behind its
114
+ source in the same history — and a consumer installing that tag gets the old code from a repository
115
+ whose source says otherwise. A library still on its former bundler declares that bundler's command,
116
+ and nothing is built where nothing is declared.
117
+
118
+ The contract of a conforming library declares its output so:
119
+ `generated: [{ path: dist/, command: pnpm exec vp pack, when: [src/**] }]` — in a workspace, the
120
+ ported package's `dist/` and `pnpm --dir <package> exec vp pack`. Item `d` of the port writes it, in
121
+ the commit that replaces the build, since the command it names changes at that instant. The build
122
+ runs unconditionally, not behind a "did src/ change" test, since the build is the authority on
123
+ whether its output moved; stages with `-A` so that a file the build stopped producing is staged as
124
+ the deletion it is; and packs from the working tree, so when the index and the tree differ the
125
+ commit can carry output its own source does not.
126
+
127
+ ## Standard scripts
128
+
129
+ The base's five names, with a library's commands, verbatim:
130
+
131
+ | Script | Runs |
132
+ |---|---|
133
+ | `verify:commit` | `vp check && vp test` |
134
+ | `verify:push` | `vp check && vp test && vp pack` |
135
+ | `verify:release` | `pnpm verify:push` |
136
+ | `test:all` | `vp test` |
137
+ | `test:one` | `vp test` |
138
+
139
+ `test:one` takes the file as its argument. `pack` is in the push tier and not the commit tier: the
140
+ commit tier runs on every commit and a full build there costs more than it catches, while a push is
141
+ what makes `dist/` someone else's problem. `verify:release` is the push tier itself: the `pre-push`
142
+ hook and `/release` run it alone on a version tag, so it holds everything the push tier holds; the
143
+ tag naming the version is `/release`'s to check, and the publication job's.
@@ -0,0 +1,21 @@
1
+ <!-- generated by codelife — /audit -->
2
+ # Audits
3
+
4
+ One row per campaign in the table below, linking its report — an index and one file per lens —
5
+ **at the commit that closed it**. The folder
6
+ itself, `A<n>-<date>-full/` or `A<n>-<date>-targeted-<LENS>-<LENS>/`, lives here only while the
7
+ campaign runs: the close commits it complete, then a second commit deletes it and writes its row,
8
+ so the tree carries no findings true at a commit long gone. `/audit` reads the next campaign number
9
+ from this table. What has to change about a finding changes in its issue, which carries the finding
10
+ whole.
11
+
12
+ ## Campaigns
13
+
14
+ | Campaign | Date | Type | Commit | Findings | State |
15
+ |---|---|---|---|---|---|
16
+
17
+ `lenses/<id>.md` holds the domain lenses this repository adds to the plugin's, one file per lens.
18
+ Written from the plugin's template at the first campaign that needs one.
19
+
20
+ A full campaign is due every six months, and before a major; a targeted one before a minor.
21
+ `/overview` says when the last one was.
@@ -0,0 +1,43 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // The bundle's shape. A size that jumps is almost always a dependency that stopped being external
4
+ // and got bundled in — the one regression no behavioural test notices and every consumer pays for.
5
+ // Measured over all the JavaScript beside the entry points, shared chunks included, since that is
6
+ // where a newly bundled dependency usually lands.
7
+ //
8
+ // The tolerance is wide enough not to fire on noise and narrow enough to catch that. Widen it with
9
+ // a reason in the commit message, never to make a red run go away.
10
+ import { describe, expect, it } from 'vitest'
11
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
12
+ import { dirname, join, resolve } from 'node:path'
13
+ import { entries } from './entries'
14
+
15
+ const TOLERANCE = 0.1 // ±10%
16
+ const BASELINE = resolve(__dirname, 'bundle.baseline.json')
17
+
18
+ const javascript = (dir: string): string[] =>
19
+ readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
20
+ const path = join(dir, entry.name)
21
+ if (entry.isDirectory()) return javascript(path)
22
+ return /\.m?js$/.test(entry.name) ? [path] : []
23
+ })
24
+
25
+ const files = [...new Set(Object.values(entries()).map(dirname).flatMap(javascript))]
26
+ const bytes = files.reduce((total, file) => total + statSync(file).size, 0)
27
+
28
+ describe('the bundle', () => {
29
+ it('keeps its module format', () => {
30
+ // Recorded, not asserted: the build before the port may be CommonJS, and item `d`'s move to ES
31
+ // modules is then this snapshot's one deliberate update.
32
+ const format = (code: string) => (/\bmodule\.exports\b|\brequire\(/.test(code) ? 'commonjs' : 'es module')
33
+ expect([...new Set(files.map((file) => format(readFileSync(file, 'utf8'))))].sort()).toMatchSnapshot()
34
+ })
35
+
36
+ it('stays within the tolerance of its baseline size', () => {
37
+ // Recorded by the first run, the way a snapshot is, before the toolchain is replaced — and
38
+ // committed with the snapshots. Deleting it re-records it: do that only with a reason.
39
+ if (!existsSync(BASELINE)) writeFileSync(BASELINE, `${JSON.stringify({ bytes }, null, 2)}\n`)
40
+ const baseline = JSON.parse(readFileSync(BASELINE, 'utf8')).bytes
41
+ expect(Math.abs(bytes - baseline) / baseline).toBeLessThan(TOLERANCE)
42
+ })
43
+ })
@@ -0,0 +1,44 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // The declarations are what a consumer's compiler breaks on, and no other test reads them: the unit
4
+ // tests and the surface snapshot run JavaScript. Each entry point's declaration file is type-checked
5
+ // the way a consumer's project meets it — its own `lib`, and `skipLibCheck` off.
6
+ //
7
+ // A new bundler is where this goes red. Declaration *bundling* rewrites the entry modules into
8
+ // chunks, and a directive at the top of an entry — `/// <reference lib="esnext.disposable"
9
+ // preserve="true" />` — does not survive it: the build succeeds and the declarations no longer
10
+ // compile. Under `vp pack`, `pack.outputOptions.banner` puts the directive back on the entry chunks
11
+ // ending in `.d.ts`; `pack.dts.banner` is accepted and silently ignored.
12
+ import { describe, expect, it } from 'vitest'
13
+ import ts from 'typescript'
14
+ import { entries } from './entries'
15
+
16
+ // The edition of the library's runtime floor, as the target's `## Types` states it, set when this
17
+ // file is written: a consumer is not assumed to compile with more. Never raise it to make a red run
18
+ // go away — that is the consumer's build failing instead.
19
+ const EDITION = 'ES2022'
20
+
21
+ const declarations = entries('types')
22
+
23
+ describe('the shipped declarations', () => {
24
+ it('are declared for every entry point', () => {
25
+ expect(Object.keys(declarations).sort()).toEqual(Object.keys(entries()).sort())
26
+ })
27
+
28
+ it('type-check as a consumer compiles them', () => {
29
+ const program = ts.createProgram(Object.values(declarations), {
30
+ noEmit: true,
31
+ strict: true,
32
+ skipLibCheck: false,
33
+ lib: [`lib.${EDITION.toLowerCase()}.d.ts`, 'lib.dom.d.ts'],
34
+ target: ts.ScriptTarget[EDITION],
35
+ module: ts.ModuleKind.ESNext,
36
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
37
+ })
38
+ const diagnostics = ts.getPreEmitDiagnostics(program).map((d) => {
39
+ const where = d.file ? `${d.file.fileName}: ` : ''
40
+ return `${where}TS${d.code} ${ts.flattenDiagnosticMessageText(d.messageText, ' ')}`
41
+ })
42
+ expect(diagnostics).toEqual([])
43
+ })
44
+ })
@@ -0,0 +1,50 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // The entry points the package declares, read from its manifest: what a consumer can import, and so
4
+ // what every characterisation test walks. Read once here, so the tests cannot disagree about what
5
+ // the package ships — and a subpath added to `exports` is characterised without editing a test.
6
+ import { readFileSync } from 'node:fs'
7
+ import { resolve } from 'node:path'
8
+
9
+ type Target = string | null | Target[] | { [condition: string]: Target }
10
+
11
+ // What each kind of entry resolves through, and what a file of that kind looks like. A condition
12
+ // object is read in that order, an array through its first target that resolves; `require` alone is
13
+ // not an entry an ES consumer loads.
14
+ const kinds = {
15
+ code: { conditions: ['import', 'default'], file: /\.m?js$/ },
16
+ types: { conditions: ['types', 'import', 'default'], file: /\.d\.m?ts$/ },
17
+ }
18
+
19
+ function pick(target: Target, kind: (typeof kinds)[keyof typeof kinds]): string | undefined {
20
+ if (typeof target === 'string') return kind.file.test(target) ? target : undefined
21
+ if (Array.isArray(target)) return target.map((t) => pick(t, kind)).find(Boolean)
22
+ if (target === null) return undefined
23
+ for (const condition of kind.conditions) {
24
+ const file = condition in target ? pick(target[condition], kind) : undefined
25
+ if (file) return file
26
+ }
27
+ return undefined
28
+ }
29
+
30
+ /** Each declared subpath, and the absolute path of the JavaScript file — or with `'types'`, the
31
+ * declaration file — it resolves to. */
32
+ export function entries(kind: keyof typeof kinds = 'code'): Record<string, string> {
33
+ const root = resolve(__dirname, '../..')
34
+ const manifest = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
35
+ const declared = manifest.exports ?? manifest.main
36
+ // `exports` is either a map of subpaths, all starting with `.`, or the target of `.` itself.
37
+ const subpaths: Record<string, Target> =
38
+ declared && typeof declared === 'object' && !Array.isArray(declared) &&
39
+ Object.keys(declared).some((key) => key.startsWith('.'))
40
+ ? declared
41
+ : { '.': declared ?? null }
42
+ const found: Record<string, string> = {}
43
+ for (const [subpath, target] of Object.entries(subpaths)) {
44
+ // The manifest's top-level `types` answers for `.` when `exports` names no declaration.
45
+ const file = pick(target, kinds[kind]) ?? (kind === 'types' && subpath === '.' ? manifest.types : undefined)
46
+ // A pattern (`./*`) names no one file, and `./package.json` is not code.
47
+ if (file && !subpath.includes('*')) found[subpath] = resolve(root, file)
48
+ }
49
+ return found
50
+ }
@@ -0,0 +1,18 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // The usage the author thought worth showing, turned into tests. For most repositories this is the
4
+ // closest thing to a specification that exists, and until now it was prose nothing ran.
5
+ //
6
+ // One test per example, named after where it came from, so a failure says which piece of
7
+ // documentation is now wrong.
8
+ import { describe, expect, it } from 'vitest'
9
+
10
+ describe('the documented examples', () => {
11
+ it.todo('README.md — the opening example')
12
+ // Copy each example here verbatim first and watch it pass. Only then tidy it: an example edited
13
+ // on the way in is no longer evidence of what the documentation claims.
14
+
15
+ it('has at least one example under test', () => {
16
+ expect(true).toBe(true) // replace with the first real example, then delete this line
17
+ })
18
+ })
@@ -0,0 +1,22 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // A build that produces a file nothing can import fails no other test: the unit tests run against
4
+ // the source, and the surface snapshot reads the module without exercising it. This one loads the
5
+ // shipped artefact the way a consumer does and uses it once.
6
+ import { describe, expect, it } from 'vitest'
7
+ import { entries } from './entries'
8
+
9
+ describe('the shipped build', () => {
10
+ for (const [subpath, file] of Object.entries(entries())) {
11
+ it(`imports ${subpath}`, async () => {
12
+ const built = await import(file)
13
+ expect(Object.keys(built).length).toBeGreaterThan(0)
14
+ })
15
+ }
16
+
17
+ it('runs its main entry point once', async () => {
18
+ // Replace with the smallest real call the library offers — the one its README opens on.
19
+ const built = await import(entries()['.'])
20
+ expect(built).toBeDefined()
21
+ })
22
+ })
@@ -0,0 +1,68 @@
1
+ // generated by codelife — maintained by hand
2
+ //
3
+ // What a consumer breaks on. Written BEFORE the toolchain is replaced and run against the build as
4
+ // it stood then, so that a missing export after the port is a failure rather than a discovery.
5
+ //
6
+ // It reads the built package, not the source: the source can export something the build drops.
7
+ // Every entry point `exports` declares is read, each under its own subpath, so a build that drops
8
+ // an export from a secondary entry fails as surely as one that drops it from the root.
9
+ //
10
+ // The module object holds only what exists at runtime, so the declaration file is read too: an
11
+ // interface or a type alias has no binding, and its removal would change no other snapshot. Its
12
+ // names are held, not its text — types are rewritten on purpose by the port, and reformatted by
13
+ // declaration bundling, without a consumer's import breaking. A build that ships no declaration file
14
+ // gets the lines between the `declarations` markers left out, and the item says so.
15
+ // <declarations>
16
+ import ts from 'typescript'
17
+ // </declarations>
18
+ import { describe, expect, it } from 'vitest'
19
+ import { entries } from './entries'
20
+
21
+ const built = await Promise.all(
22
+ Object.entries(entries()).map(async ([subpath, file]) => [subpath, await import(file)] as const),
23
+ )
24
+
25
+ it('finds at least one entry point in the manifest', () => {
26
+ expect(built.length).toBeGreaterThan(0)
27
+ })
28
+
29
+ // <declarations>
30
+ const declarations = entries('types')
31
+ const program = ts.createProgram(Object.values(declarations), { noEmit: true, skipLibCheck: true })
32
+ const checker = program.getTypeChecker()
33
+
34
+ function declaredNames(file: string): string[] {
35
+ const symbol = checker.getSymbolAtLocation(program.getSourceFile(file)!)
36
+ return symbol ? checker.getExportsOfModule(symbol).map((s) => s.getName()).sort() : []
37
+ }
38
+ // </declarations>
39
+
40
+ for (const [subpath, module] of built) {
41
+ describe(`the public surface of ${subpath}`, () => {
42
+ it('exports the same names', () => {
43
+ expect(Object.keys(module).sort()).toMatchSnapshot()
44
+ })
45
+
46
+ it('exports the same kinds', () => {
47
+ const kinds = Object.fromEntries(
48
+ Object.entries(module).map(([name, value]) => [name, typeof value]),
49
+ )
50
+ expect(kinds).toMatchSnapshot()
51
+ })
52
+
53
+ it('keeps each function’s arity', () => {
54
+ const arities = Object.fromEntries(
55
+ Object.entries(module)
56
+ .filter(([, value]) => typeof value === 'function')
57
+ .map(([name, value]) => [name, (value as (...a: unknown[]) => unknown).length]),
58
+ )
59
+ expect(arities).toMatchSnapshot()
60
+ })
61
+
62
+ // <declarations>
63
+ it('declares the same names', () => {
64
+ expect(declaredNames(declarations[subpath])).toMatchSnapshot()
65
+ })
66
+ // </declarations>
67
+ })
68
+ }
@@ -0,0 +1,61 @@
1
+ # generated by codelife — edit the plugin's template, not this file
2
+ #
3
+ # git-cliff renders CHANGELOG.md from the commit messages. Only what a reader of
4
+ # this repository can observe gets an entry: features, fixes, performance. The
5
+ # rest — documentation, tooling, CI, refactors, tests, chores — shaped the work
6
+ # and is read in the git history, not in the changelog.
7
+
8
+ [changelog]
9
+ header = """
10
+ # Changelog
11
+
12
+ All notable changes to this project are documented here. The entries are generated
13
+ from the commit messages by git-cliff; the sections below the marker, when there is
14
+ one, are the hand-written history that preceded it.
15
+
16
+ """
17
+ body = """
18
+ {% if version %}\
19
+ {#- the version alone, under either tag spelling: `v0.1.0`, or the CLI's `<name>--v0.1.0` -#}
20
+ ## [{{ version | split(pat="--v") | last | trim_start_matches(pat="v") }}] — {{ timestamp | date(format="%Y-%m-%d") }}
21
+ {% else %}\
22
+ ## [Unreleased]
23
+ {% endif %}\
24
+ {% for group, commits in commits | group_by(attribute="group") %}
25
+ ### {{ group | upper_first }}
26
+ {% for commit in commits %}\
27
+ - {% if commit.scope %}**{{ commit.scope }}**: {% endif %}\
28
+ {{ commit.message | upper_first }}\
29
+ {% if commit.breaking %} — **breaking**{% endif %}
30
+ {% endfor %}\
31
+ {% endfor %}\n
32
+ """
33
+ footer = ""
34
+ trim = true
35
+
36
+ [git]
37
+ conventional_commits = true
38
+ # false, not true: with `true`, a commit that is not a Conventional Commit — every merge commit —
39
+ # is discarded *before* commit_parsers and its rejection is printed as a parse error, so the
40
+ # warning count grows by one per merge, forever, and can never signal anything. With `false` it
41
+ # reaches the parsers, where the catch-all `{ message = "^.*", skip = true }` below already drops
42
+ # it. Measured: identical output either way, and no warning.
43
+ filter_unconventional = false
44
+ filter_commits = true
45
+ tag_pattern = "(^|--)v[0-9]"
46
+ sort_commits = "oldest"
47
+
48
+ # The subject decides, and the Conventional Commits footer, never the prose. A parser that reads
49
+ # the body — `{ body = ".*security" }` was the tempting one — puts a design note that merely
50
+ # *discusses* security into the changelog, which is exactly the rule these entries exist to keep:
51
+ # what a reader of this repository can observe, nothing else. A security fix is a `fix`, and lands
52
+ # in Fixed. A break is observable whatever its type, so a breaking commit — `!` in the subject or a
53
+ # `BREAKING CHANGE:` footer, the two the bump counts — that no type above takes lands in Changed: a
54
+ # `refactor!` that retires a skill is news to whoever installs the package.
55
+ commit_parsers = [
56
+ { message = "^feat[(!:]", group = "Added" },
57
+ { message = "^fix[(!:]", group = "Fixed" },
58
+ { message = "^perf[(!:]", group = "Performance" },
59
+ { field = "breaking", pattern = "true", group = "Changed" },
60
+ { message = "^.*", skip = true },
61
+ ]
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # generated by codelife — edit the plugin's template, not this file
3
+ #
4
+ # Refuses a commit message that is not a Conventional Commit.
5
+ # It hands over to the package, and refuses when the package is not installed: git would run
6
+ # nothing else, and a guard that vanishes without a word is none.
7
+
8
+ [ -f node_modules/@ersbeth/codelife/scripts/commit-msg.mjs ] || { echo "codelife is not installed here: run your package manager's install (node_modules/@ersbeth/codelife is missing), then retry." >&2; exit 1; }
9
+ exec node node_modules/@ersbeth/codelife/scripts/commit-msg.mjs "$@"
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # generated by codelife — edit the plugin's template, not this file
3
+ #
4
+ # Holds the specification's form, and a library's dist/, before a commit.
5
+ # It hands over to the package, and refuses when the package is not installed: git would run
6
+ # nothing else, and a guard that vanishes without a word is none.
7
+
8
+ [ -f node_modules/@ersbeth/codelife/scripts/pre-commit.mjs ] || { echo "codelife is not installed here: run your package manager's install (node_modules/@ersbeth/codelife is missing), then retry." >&2; exit 1; }
9
+ exec node node_modules/@ersbeth/codelife/scripts/pre-commit.mjs "$@"
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # generated by codelife — edit the plugin's template, not this file
3
+ #
4
+ # Runs the verification tier before a push, so that a red tip never reaches the remote.
5
+ # It hands over to the package, and refuses when the package is not installed: git would run
6
+ # nothing else, and a guard that vanishes without a word is none.
7
+
8
+ [ -f node_modules/@ersbeth/codelife/scripts/pre-push.mjs ] || { echo "codelife is not installed here: run your package manager's install (node_modules/@ersbeth/codelife is missing), then retry." >&2; exit 1; }
9
+ exec node node_modules/@ersbeth/codelife/scripts/pre-push.mjs "$@"
@@ -0,0 +1,11 @@
1
+ <!-- generated by codelife — maintained by hand -->
2
+ <!doctype html>
3
+ <meta charset="utf-8" />
4
+ <title><package-name> playground</title>
5
+ <h1><package-name></h1>
6
+ <p>
7
+ This page imports the library from <code>src/</code> through the <code>paths</code> of
8
+ <code>tsconfig.json</code>, never from <code>dist/</code> — so what you see is the working tree.
9
+ </p>
10
+ <pre id="out"></pre>
11
+ <script type="module" src="./main.ts"></script>