@ankhorage/devtools 2.0.0 → 2.0.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 (31) hide show
  1. package/README.md +42 -457
  2. package/dist/cli/bin/apm-release.js +1 -1
  3. package/dist/cli/bin/structure.js +1 -1
  4. package/dist/cli/commands/apm/sync.js +1 -1
  5. package/dist/cli/commands/apm/validate.js +1 -1
  6. package/dist/cli/commands/structure/build.js +1 -1
  7. package/dist/cli/commands/structure/check.js +1 -1
  8. package/dist/{features/apm-release-validation/adapters/inbound → cli}/runApmReleaseCommandAsync.js +2 -2
  9. package/dist/{features/structure-descriptor-generation/adapters/inbound → cli}/runStructureGenerationCommandAsync.js +1 -1
  10. package/dist/internal/readmeDocs.js +8 -40
  11. package/dist/owner/synchronizeRenovateOwnerAsync.d.ts +1 -1
  12. package/dist/owner/synchronizeRenovateOwnerAsync.js +3 -2
  13. package/dist/policy/applyBunRuntimePolicy.d.ts +1 -1
  14. package/dist/policy/applyBunRuntimePolicy.js +4 -4
  15. package/dist/policy/bunRuntimePolicy.d.ts +5 -0
  16. package/dist/policy/bunRuntimePolicy.js +5 -0
  17. package/dist/policy/renderBunPolicyDocumentation.js +4 -5
  18. package/dist/tools/agents/index.js +4 -0
  19. package/dist/tools/package/index.js +6 -4
  20. package/dist/tools/workflows/files/ci.yml +21 -1
  21. package/dist/tools/workflows/files/renovate.yml +1 -1
  22. package/dist/tools/workflows/index.js +3 -2
  23. package/dist/tools/workflows/renderRenovateConfigAsync.d.ts +1 -1
  24. package/dist/tools/workflows/renderRenovateConfigAsync.js +2 -19
  25. package/examples/package/eslint.config.mjs +8 -1
  26. package/examples/package/package.json +13 -0
  27. package/examples/package/src/index.ts +1 -0
  28. package/examples/package/tsconfig.json +9 -0
  29. package/package.json +7 -7
  30. /package/dist/{features/apm-release-validation/adapters/inbound → cli}/runApmReleaseCommandAsync.d.ts +0 -0
  31. /package/dist/{features/structure-descriptor-generation/adapters/inbound → cli}/runStructureGenerationCommandAsync.d.ts +0 -0
package/README.md CHANGED
@@ -3,485 +3,70 @@
3
3
 
4
4
  # @ankhorage/devtools
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v2.0.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v2.0.1](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
7
7
 
8
8
  Shared tooling, repository automation, and agent standards for Ankhorage TypeScript projects
9
9
 
10
10
  ## Usage
11
11
 
12
- Shared development tools and repository standards for Ankhorage TypeScript projects.
13
-
14
- ## What it owns
15
-
16
- `@ankhorage/policy` is the source of truth for canonical Ankhorage policy. `@ankhorage/devtools` is the execution and synchronization layer for these repository concerns:
17
-
18
- ```text
19
- src/
20
- ├── cli/
21
- ├── policy/
22
- └── tools/
23
- ├── agents/
24
- ├── skills/
25
- ├── eslint/
26
- ├── prettier/
27
- ├── knip/
28
- ├── package/
29
- ├── workflows/
30
- └── vscode/
31
- ```
32
-
33
- - `policy`: policy application/rendering adapters that consume `@ankhorage/policy`
34
- - `changesets`: package-resolved Changesets execution using centrally defined commands
35
- - `agents`: canonical repository `AGENTS.md` rendered from stable package identity
36
- - `skills`: immutable Ankhorage-owned repository skills under `.agents/skills/`
37
- - `eslint`: shared flat ESLint configuration, automatic project profiles, and the bundled ESLint runner
38
- - `prettier`: shared Prettier configuration and the bundled Prettier runner
39
- - `knip`: shared Knip configuration helpers and the bundled Knip runner
40
- - `package`: merge-aware synchronization of the shared `package.json` tooling and Bun runtime contract
41
- - `workflows`: canonical `.github/workflows/ci.yml` and `release.yml`
42
- - `vscode`: canonical `.vscode/settings.json` and `extensions.json`
43
-
44
- The package owns the supported Changesets CLI, ESLint, TypeScript ESLint, Prettier, Knip, security, React, React Hooks, React Native, import/sort, unused-import, and formatting-plugin versions used by consuming repositories. The Bun/Node runtime baseline and canonical Changesets/PKGViz policy values are owned by `@ankhorage/policy` and rendered by Devtools.
45
-
46
- ## Bootstrap
47
-
48
- For a repository that does not yet depend on the shared toolchain:
49
-
50
- ```bash
51
- bun add -D @ankhorage/devtools
52
- bunx @ankhorage/ankh devtools sync .
53
- ```
12
+ ### CLI
54
13
 
55
- After the first install, the normal workflow is:
14
+ Ankhorage packages expose their command-line interface through `ankh`. Use `ankh --help` to discover available package commands, or run a package command with `--help` for package-specific usage.
56
15
 
57
- ```bash
58
- ankh devtools sync
59
- ```
60
-
61
- The target path is optional and defaults to the current working directory.
62
-
63
- Synchronization ensures `@ankhorage/devtools` is declared using the version of the provider performing the sync, installs the standard package scripts, applies the managed Bun runtime policy, and removes direct devDependencies for tools/plugins owned by devtools. When package metadata changes, sync runs `bun install` so installed dependencies and `bun.lock` match the synchronized manifest. Unrelated package metadata, dependencies, and scripts are preserved.
64
-
65
- `devtools sync` does not upgrade the globally installed Bun executable. The managed version applies to repository metadata, Bun types, and GitHub workflows.
66
-
67
- ## Ankh provider
68
-
69
- The package is discovered under the `devtools` category and exposes these capabilities:
70
-
71
- - `devtools.apm.sync`
72
- - `devtools.apm.validate`
73
- - `devtools.lint`
74
- - `devtools.changeset`
75
- - `devtools.format`
76
- - `devtools.knip`
77
- - `devtools.sync`
78
- - `devtools.status`
79
- - `devtools.agents.sync`
80
- - `devtools.agents.status`
81
- - `devtools.skills.sync`
82
- - `devtools.skills.status`
83
- - `devtools.eslint.sync`
84
- - `devtools.eslint.status`
85
- - `devtools.prettier.sync`
86
- - `devtools.prettier.status`
87
- - `devtools.knip.sync`
88
- - `devtools.knip.status`
89
- - `devtools.package.sync`
90
- - `devtools.package.status`
91
- - `devtools.workflows.sync`
92
- - `devtools.workflows.status`
93
- - `devtools.vscode.sync`
94
- - `devtools.vscode.status`
95
-
96
- The canonical command prefix is always:
97
-
98
- ```bash
99
- ankh devtools ...
100
- ```
101
-
102
- ## Tool commands
16
+ ```zsh
17
+ # Install the Ankhorage CLI
18
+ bun add --global @ankhorage/ankh
103
19
 
104
- ```bash
105
- ankh devtools changeset -- status --since=origin/main
106
- ankh devtools lint -- --max-warnings=0 .
107
- ankh devtools format -- --check .
108
- ankh devtools knip -- --production
20
+ # Show usage information for devtools
21
+ ankh devtools --help
109
22
  ```
110
23
 
111
- These delegate to the same bundled tools as the package binaries:
112
-
113
- - `ankh devtools changeset` → `ankhorage-changeset`
114
- - `ankh devtools lint` → `ankhorage-eslint`
115
- - `ankh devtools format` → `ankhorage-prettier`
116
- - `ankh devtools knip` → `ankhorage-knip`
117
-
118
- The synchronized package scripts are:
119
-
120
- ```json
121
- {
122
- "scripts": {
123
- "changeset": "ankhorage-changeset",
124
- "changeset:status": "ankhorage-changeset status --since=origin/main",
125
- "lint": "ankhorage-eslint . --max-warnings=0",
126
- "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
127
- "format": "ankhorage-prettier --write .",
128
- "format:check": "ankhorage-prettier --check .",
129
- "knip:check": "ankhorage-knip",
130
- "version-packages": "ankhorage-changeset version"
131
- }
132
- }
133
- ```
134
-
135
- ## Repository synchronization
136
-
137
- Synchronize or inspect every managed concern:
138
-
139
- ```bash
140
- ankh devtools sync .
141
- ankh devtools status .
142
- ```
143
-
144
- Synchronize one concern:
145
-
146
- ```bash
147
- ankh devtools agents sync .
148
- ankh devtools skills sync .
149
- ankh devtools eslint sync .
150
- ankh devtools prettier sync .
151
- ankh devtools knip sync .
152
- ankh devtools package sync .
153
- ankh devtools workflows sync .
154
- ankh devtools vscode sync .
155
- ```
156
-
157
- Report one concern:
158
-
159
- ```bash
160
- ankh devtools agents status .
161
- ankh devtools skills status .
162
- ankh devtools eslint status .
163
- ankh devtools prettier status .
164
- ankh devtools knip status .
165
- ankh devtools package status .
166
- ankh devtools workflows status .
167
- ankh devtools vscode status .
168
- ```
169
-
170
- Preview synchronization without writing:
171
-
172
- ```bash
173
- ankh devtools sync . --dry-run
174
- ankh devtools agents sync . --dry-run
175
- ankh devtools skills sync . --dry-run
176
- ankh devtools eslint sync . --dry-run
177
- ankh devtools package sync . --dry-run
178
- ```
179
-
180
- A dry run reports `would create`, `would update`, and `would remove` actions without mutating files. `status` exits with code `1` when managed state has drifted and `0` when it is current.
181
-
182
- ## Synchronization guarantees
183
-
184
- Synchronization is deterministic and idempotent:
185
-
186
- - missing managed artifacts are created
187
- - outdated centrally owned artifacts are updated
188
- - stale files in Devtools-owned skill trees are removed
189
- - the managed Bun runtime version is applied consistently to package metadata and workflows
190
- - Changesets-enabled repositories use the Devtools-owned runner without a direct `@changesets/cli` declaration
191
- - package changes are followed by `bun install` after all managed files have been written, keeping installed dependencies and `bun.lock` synchronized without invalidating the running sync
192
- - current artifacts are left untouched
193
- - unrelated files and package fields are preserved
194
- - repeated sync produces only `unchanged` results
195
- - invalid target paths and write failures return a non-zero exit code
196
- - create-only repository extension files are never overwritten after creation
24
+ ### Standalone package ESLint configuration
197
25
 
198
- The canonical workflow, VS Code, and skill files are packaged with `@ankhorage/devtools`; synchronization does not fetch mutable files from GitHub at runtime.
199
-
200
- ## Managed agent instructions
201
-
202
- `ankh devtools agents sync` owns the repository-root `AGENTS.md` plus `CLAUDE.md` and `GEMINI.md` as symbolic links to that canonical file. The shared instructions are intentionally small and stable. The target repository's package name and description are rendered from `package.json`; the remaining content defines the unconditional current-architecture policy, directs structural work to the managed project-structure skill, and requires the canonical build, type, lint, Knip, Changeset, and formatting commands before pull request creation.
203
-
204
- Only the current Ankhorage architecture is supported. Managed instructions reject deprecated APIs, compatibility aliases, shims, dual old/new paths, historical-state fallbacks, and migrations whose sole purpose is obsolete state. A canonical cross-package change requires affected repositories to update to the latest released public API.
205
-
206
- Every Devtools-managed repository is standalone: its own checkout must be sufficient to install, build, test, and use it with declared dependencies and explicit configuration. Published packages are additionally consumer-agnostic and reusable outside Ankhorage. Sibling repositories/source imports, unpublished workspace/file/link coupling, hidden organization-local state, and hard assumptions about a consuming app, infrastructure, hosting provider, web server, container runtime, or deployment topology are architecture debt rather than supported exceptions.
207
-
208
- ## Managed repository skills
209
-
210
- `ankh devtools skills sync` owns the complete `.agents/skills/ankhorage-coding-rules/`, `.agents/skills/hexagonal-architecture/`, and `.agents/skills/ankhorage-project-structure/` trees from the immutable copies shipped in the Devtools release. It creates `.agents/` when missing, replaces stale files in those managed skills, and preserves every unrelated skill directory. The project-structure skill requires both the coding-rules and hexagonal-architecture skills before structural work can continue.
211
-
212
- `.agents/.devtools-manifest.json` records the source Devtools version and SHA-256 hashes for every managed skill file. That ownership record allows status and dry-run to report drift and lets later releases remove stale owned files without deleting repository-owned skills.
213
-
214
- Agent Skill scripts are always TypeScript files with a `.ts` extension and run with Bun. JavaScript skill scripts using `.js`, `.mjs`, or `.cjs` are not supported.
215
-
216
- ## ESLint profiles
217
-
218
- `createConfig()` defaults to `profile: 'auto'`.
219
-
220
- Automatic detection reads the consuming repository's `package.json` and delegates project trait detection to `@ankhorage/project-detector`. Dependency signals are considered across `dependencies`, `devDependencies`, and `peerDependencies`.
221
-
222
- Profile precedence is:
223
-
224
- ```text
225
- React Native / Expo
226
- ↓
227
- react-native
228
- ↓ includes
229
- react
230
- ↓ includes
231
- base
232
- ```
233
-
234
- A React or Next.js project selects `react`. A React Native or Expo project selects `react-native`. Everything else selects `base`.
235
-
236
- An unusual repository can opt out of automatic selection:
26
+ Configure ESLint for a standalone TypeScript package with the Devtools shared policy.
237
27
 
238
28
  ```js
239
- import { createConfig } from '@ankhorage/devtools/eslint';
240
-
241
29
  export default createConfig({
242
- files: ['src/**/*.ts'],
243
- profile: 'base',
30
+ tsconfigRootDir: __dirname,
244
31
  project: ['./tsconfig.json'],
245
- tsconfigRootDir: import.meta.dirname,
32
+ files: ['src/**/*.{ts,tsx}'],
246
33
  });
247
34
  ```
248
35
 
249
- The base profile enforces the shared TypeScript policy plus:
250
-
251
- - maximum 50 effective lines per function
252
- - maximum 300 effective lines per file
253
- - modified cyclomatic complexity maximum 15
254
- - security review for dynamic object access
255
- - rejection of non-literal `require()` calls
256
-
257
- The React profile adds React and React Hooks correctness rules. The React Native profile composes the React profile and adds the selected React Native style rules.
258
-
259
- ### Managed ESLint setup and local overrides
260
-
261
- `ankh devtools eslint sync` centrally owns `eslint.config.mjs` and creates `eslint.local.config.mjs` once. When the repository has a root `examples/` directory, synchronization also owns `eslint.examples.config.mjs`; repositories without public examples do not receive that file, and synchronization removes the managed wrapper when the directory is removed.
262
-
263
- The canonical wrapper uses automatic profile detection and appends repository-owned flat-config entries:
264
-
265
- ```js
266
- import { createConfig } from '@ankhorage/devtools/eslint';
267
- import localConfig from './eslint.local.config.mjs';
268
-
269
- const localEntries = Array.isArray(localConfig) ? localConfig : [localConfig];
270
-
271
- export default [
272
- ...createConfig({
273
- files: ['src/**/*.{ts,tsx}'],
274
- project: ['./tsconfig.json'],
275
- tsconfigRootDir: import.meta.dirname,
276
- }),
277
- ...localEntries,
278
- ];
279
- ```
280
-
281
- Use `eslint.local.config.mjs` for narrow repository-specific flat-config overrides, including temporary file-specific migration overrides. On first synchronization, an existing non-canonical `eslint.config.mjs` is preserved as the initial local config before the canonical wrapper is installed. Synchronization never overwrites that local file afterward.
36
+ This package contains 1 additional example. See the generated documentation for the complete set.
282
37
 
283
- Each public example lives in a named directory, such as `examples/basic-usage/*.ts`; example source files do not live directly under `examples/`. The examples wrapper uses root `tsconfig.eslint.json` and `tsconfig.json` when present and discovers TypeScript projects below `examples/`. This covers example directories included only by the root ESLint project as well as standalone applications with their own tsconfig. It applies the same shared policy and appends the same repository-owned local entries.
38
+ ## Configuration
284
39
 
285
- An existing consumer-owned `eslint.examples.config.mjs` requires explicit adoption before synchronization can replace it. Status, dry-run, and sync report an actionable error instead of overwriting or deleting it. Move its repository-specific overrides into `eslint.local.config.mjs`, preserving existing entries and the examples-only file scope; retain custom parser options there when needed. Remove the old examples config only after that transfer, then rerun sync and the examples lint. The generated wrapper carries a Devtools ownership marker and subsequent synchronization updates it normally. Do not mark an old consumer config as managed to bypass this transfer.
286
-
287
- Repositories can lint their independently runnable examples explicitly:
288
-
289
- ```bash
290
- ankhorage-eslint examples --config eslint.examples.config.mjs --max-warnings=0
291
- ```
292
-
293
- ## Prettier
294
-
295
- `ankh devtools prettier sync` owns `.prettierrc.js`, emits the correct ESM or CommonJS wrapper based on the repository's `package.json` module type, and creates `prettier.local.config.js` once for narrow repository-specific options.
296
-
297
- The consumer delegates formatting policy to:
298
-
299
- ```text
300
- @ankhorage/devtools/prettier
301
- ```
302
-
303
- The wrapper merges shared and local `overrides` in that order. On first synchronization, an existing non-canonical `.prettierrc.js` is preserved as `prettier.local.config.js`; former shared-only Devtools delegates become an empty local config. Later synchronization never overwrites the local file.
304
-
305
- ## Knip
306
-
307
- `ankh devtools knip sync` bootstraps `knip.config.ts` with:
40
+ ### Example
308
41
 
309
42
  ```ts
310
- import { createKnipConfig } from '@ankhorage/devtools/knip';
311
-
312
- export default createKnipConfig();
313
- ```
314
-
315
- `knip.config.ts` is create-only after bootstrap so repositories can retain narrow local entries, projects, ignores, binaries, dependencies, or switch to `createKnipMonorepoConfig()` without synchronization overwriting those extensions.
316
-
317
- ## Managed Bun runtime policy
318
-
319
- The canonical Bun policy is defined by `@ankhorage/policy` and consumed by Devtools package and workflow synchronization. The current released policy is:
320
-
321
- <!-- devtools-bun-policy:start -->
322
-
323
- ```text
324
- Bun runtime 1.4.2
325
- packageManager bun@1.4.2
326
- @types/bun ^1.4.1
327
- ```
328
-
329
- <!-- devtools-bun-policy:end -->
330
-
331
- `ankhorage/policy` owns and updates the canonical Bun and `@types/bun` literals through Renovate. A released Policy update reaches Devtools as a normal dependency update; Devtools then renders that policy into `packageManager`, `@types/bun`, workflow setup versions, this documentation block, and `bun.lock`. The trusted owner workflow uses `bun scripts/sync-renovate-owner.ts sync repository` to regenerate those artifacts and `bun scripts/sync-renovate-owner.ts status repository` to reject stale rendered state. Do not duplicate runtime policy literals in Devtools.
332
-
333
- ## Managed package contract
334
-
335
- `ankh devtools package sync` merge-updates `package.json` rather than replacing it.
336
-
337
- It owns:
338
-
339
- - the `@ankhorage/devtools` dependency version range
340
- - removal of direct consumer `@changesets/cli` dependencies
341
- - `packageManager` according to the managed Bun runtime policy
342
- - the `@types/bun` development dependency according to the managed Bun runtime policy
343
- - `lint`
344
- - `lint:fix`
345
- - `format`
346
- - `format:check`
347
- - `knip:check`
348
- - `changeset`, `changeset:status`, and `version-packages` for Changesets-enabled repositories
349
-
350
- For normal consumers, `@ankhorage/devtools` is a devDependency. `@ankhorage/ankh` keeps devtools as a runtime dependency because it loads the provider. Devtools itself participates in the Bun runtime policy without attempting to install itself as a consumer dependency.
351
-
352
- When this managed package contract changes, synchronization runs `bun install`. This updates installed dependencies and `bun.lock` before sync completes. It also removes direct dependencies for Changesets and direct devDependencies for tools and ESLint plugins already provided by `@ankhorage/devtools`. Unrelated scripts, dependencies, metadata, and repository-specific configuration remain unchanged.
353
-
354
- A repository participates in Changesets synchronization when `.changeset/config.json` exists or any of the canonical `changeset`, `changeset:status`, or `version-packages` script keys is present. This explicit rule migrates partially configured repositories while ensuring repositories without Changesets do not acquire release tooling. The repository continues to own `.changeset/config.json`, pending `.changeset/*.md` files, and its package release semantics. Direct `@changesets/cli` declarations, ambient `changeset` scripts, and `bunx changeset` workflow commands are not supported consumer forms.
355
-
356
- ## Managed GitHub Actions workflows
357
-
358
- `workflows` owns exactly:
359
-
360
- ```text
361
- .github/workflows/ci.yml
362
- .github/workflows/renovate.yml
363
- .github/workflows/release.yml
364
- ```
365
-
366
- CI and Release render their `bun-version` from `@ankhorage/policy/repository`, the same policy used for `package.json`. They also render Changesets status, version, and publish commands from that central policy. The CI workflow installs that Bun version with the frozen lockfile, builds before repository-provider validation, runs `bunx @ankhorage/ankh doctor validate .`, and conditionally runs lint, formatting, Knip, tests, typecheck, and the strict `changeset:status --since=origin/main` guard for pull requests. After a green change reaches `main`, Release uses the scoped Ankhorage Renovate Sync App token to apply Changesets versioning directly to `main` in a `chore(release)` `[skip ci]` commit and publishes without creating a second Version Packages pull request. Release publishing calls the Devtools-owned runner through the synchronized package script; it never resolves an ambient or mutable Changesets executable. Changesets v3 creates the local release tags; the managed workflow pushes those exact tags and creates any missing GitHub Releases directly, without parsing Changesets’ human-readable publish output.
367
-
368
- For the **first npm publication** of a new `@ankhorage/*` package, the organization publishing credential must be able to create/publish packages in the `@ankhorage` scope. With a granular npm token, grant package permission **Read and write (publish and stage)** to the `@ankhorage` scope or **All Packages**. If initial publication fails after Changesets has already pushed the release commit, correct the npm credential and rerun Release; the current unpublished version is reused and must not be bumped again. The managed workflow diagnoses this first-publish state separately from registry/network failures.
369
-
370
- The Renovate workflow accepts only same-repository branches created by `renovate[bot]`. It calls the SHA-pinned `ankhorage/renovate` workflow to add a release Changeset for runtime Ankhorage dependency updates or an empty Changeset for dev-only updates. The trusted workflow scopes the Ankhorage Renovate Sync GitHub App token to the current repository and uses it only for the validated commit, allowing every normal pull-request CI workflow to start without manual approval. It never checks out or executes pull-request code in the privileged `pull_request_target` context.
371
-
372
- ## Managed VS Code configuration
373
-
374
- `vscode` owns exactly:
375
-
376
- ```text
377
- .vscode/settings.json
378
- .vscode/extensions.json
379
- ```
380
-
381
- Unknown workflow and VS Code files are never deleted.
382
-
383
- ## Adding another managed concern
384
-
385
- A new concern should:
386
-
387
- 1. live in its own sibling directory under `src/tools`
388
- 2. define only the files and behavior it owns
389
- 3. expose deterministic status and synchronization
390
- 4. add provider commands under `ankh devtools`
391
- 5. include dry-run, status, and idempotence coverage
392
- 6. document its central ownership and repository-owned extension points
393
-
394
- ## Package-owned APM release gates
395
-
396
- A package opts in through `ankh.apm` in its `package.json`:
397
-
398
- ```json
399
- {
400
- "ankh": {
401
- "apm": { "protocolVersion": 1, "descriptor": "./apm/update.json" }
402
- }
403
- }
404
- ```
405
-
406
- Include the descriptor in `files` and expose any executable extension with an explicit public
407
- `exports` subpath. Use the released APM descriptor and extension types from `@ankhorage/apm/types`;
408
- Devtools does not own or duplicate the protocol. Add `test:apm` to run the owner's deterministic
409
- source-to-target fixtures, including skipped versions, unsupported old states, idempotency and
410
- interruption recovery. A no-migration release still declares supported history explicitly and
411
- can pass without an extension. Absence of metadata is reported as inapplicable, not migration-safe.
412
-
413
- ```sh
414
- ankh devtools apm sync .
415
- ankh devtools apm validate . --allow-owner-code --artifact /tmp/reviewed-owner.tgz
416
- ```
417
-
418
- The standalone `ankhorage-apm-release sync|validate` binary runs the same operations. The public
419
- `@ankhorage/devtools/apm-release` entrypoint exports `synchronizeApmReleaseDescriptorAsync`,
420
- `validatePackedApmReleaseAsync` and `validateApmReleaseCandidate`; types are available from
421
- `@ankhorage/devtools/types`. Programmatic callers can supply `previousDescriptors` to enforce
422
- unchanged migration checksums and `relatedDescriptors` for cross-owner prerequisites. Owners
423
- must include historical descriptors in their `test:apm` fixtures where their migrations require them.
424
-
425
- Validation packs with lifecycle scripts disabled, reads only the packed descriptor, checks owner
426
- identity, protocol/schema, migration/projection IDs, prerequisite graphs and public extension
427
- capabilities using APM's canonical validators. Invalid metadata is a failure, not an opt-out.
428
- `--allow-owner-code` is explicit permission to import the packed extension in a bounded child
429
- process. The child is **not a security sandbox**; run only trusted owner code. Registry metadata
430
- alone never grants this permission. Installed dependency resolution uses the repository's
431
- frozen dependency graph; the package's own code is loaded through native public export resolution
432
- from the extracted archive, never from the source checkout.
433
-
434
- `--artifact` creates a new file containing exactly the validated bytes and refuses an existing
435
- output. The JSON result includes SHA-512 integrity; `--expected-integrity` refuses a different
436
- archive. Keep the retained archive immutable. The managed release workflow versions with
437
- Changesets, synchronizes descriptor identity, rebuilds, runs `test:apm`, validates a fresh archive,
438
- and publishes that same archive using `npm publish --ignore-scripts`, without repacking.
439
- Authentication and network errors are not treated as an unpublished version. Packages without
440
- APM metadata retain their existing Changesets publication path.
441
-
442
- Producer order is: released APM protocol, validated owner package release, then consumer rollout.
443
- Never declare an unpublished owner version. Project migration recovery is owned by APM and the
444
- package's declared handlers; passing this release gate does not execute migrations against user
445
- projects, deploy production services, or prove compatibility with shipped native binaries.
446
-
447
-
448
- ## CLI
449
-
450
- Run and synchronize the shared development toolchain through the Ankh CLI.
451
-
452
- `ankh devtools changeset`, `ankh devtools lint`, `ankh devtools format`, and
453
- `ankh devtools knip` execute the bundled Changesets, ESLint, Prettier, and Knip versions.
454
- Repository synchronization is available through
455
- `ankh devtools sync` and `ankh devtools status`, with focused `agents`, `skills`, `eslint`,
456
- `prettier`, `knip`, `package`, `workflows`, and `vscode` sync/status subcommands.
457
- Owner packages can opt into deterministic structural metadata with
458
- `ankh devtools structure build` and verify committed evidence with `ankh devtools structure check`.
459
-
460
- Sync commands accept an optional target directory and `--dry-run`. Aggregate sync is
461
- deterministic and idempotent: canonical managed files and skill trees are created or updated,
462
- unrelated repository-local skills and create-only local extension files remain repository-owned,
463
- and package metadata is merge-updated without replacing unrelated fields.
464
-
465
- Fresh repositories can bootstrap the standard setup with `ankh devtools sync .` after adding
466
- `@ankhorage/devtools`. Existing ESLint configuration is preserved during first migration as a
467
- local extension before the canonical auto-detecting wrapper is installed.
468
-
469
- APM owner releases opt in through `ankh.apm`. `ankh devtools apm sync .` binds a valid
470
- descriptor to the selected Changesets version. `ankh devtools apm validate .` checks a
471
- script-free tarball; `--allow-owner-code` explicitly permits the isolated executable probe.
472
- `--artifact <path>` retains the exact accepted tarball and `--expected-integrity <SRI>`
473
- rejects changed bytes. Owners provide `test:apm` for source-to-target and recovery fixtures.
474
- CI runs that owner suite and the packed check. Release rebuilds after versioning, validates,
475
- then publishes the retained archive with scripts disabled through the existing release job.
476
- Packages without metadata keep their existing Changesets publish path unchanged.
477
-
478
- ```bash
479
- bunx @ankhorage/devtools ankhorage-apm-release
480
- bunx @ankhorage/devtools ankhorage-changeset
481
- bunx @ankhorage/devtools ankhorage-eslint
482
- bunx @ankhorage/devtools ankhorage-knip
483
- bunx @ankhorage/devtools ankhorage-prettier
484
- bunx @ankhorage/devtools ankhorage-structure
43
+ import { readFileSync } from 'node:fs';
44
+
45
+ import { defineParadoxConfig } from '@ankhorage/paradox';
46
+
47
+ import { renderBunPolicyDocumentation } from './src/policy/renderBunPolicyDocumentation.js';
48
+
49
+ export default defineParadoxConfig({
50
+ mode: 'write',
51
+ docs: {
52
+ usage: {
53
+ description: renderBunPolicyDocumentation(
54
+ readFileSync(new URL('./src/cli/usage.md', import.meta.url), 'utf8'),
55
+ ),
56
+ },
57
+ },
58
+ package: {
59
+ root: '.',
60
+ entrypoints: [
61
+ 'src/cli/index.ts',
62
+ 'src/tools/eslint/index.ts',
63
+ 'src/tools/knip/index.ts',
64
+ 'src/apmRelease.ts',
65
+ 'src/types/public.ts',
66
+ ],
67
+ },
68
+ output: { dir: './paradox' },
69
+ });
485
70
  ```
486
71
 
487
72
  ## Generated documentation
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runApmReleaseCommandAsync } from '../../features/apm-release-validation/adapters/inbound/runApmReleaseCommandAsync.js';
2
+ import { runApmReleaseCommandAsync } from '../runApmReleaseCommandAsync.js';
3
3
  const [operation, ...argv] = process.argv.slice(2);
4
4
  if (operation !== 'sync' && operation !== 'validate') {
5
5
  console.error('Usage: ankhorage-apm-release <sync|validate> [directory] [--allow-owner-code] [--artifact path] [--expected-integrity value]');
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runStructureGenerationCommandAsync } from '../../features/structure-descriptor-generation/adapters/inbound/runStructureGenerationCommandAsync.js';
2
+ import { runStructureGenerationCommandAsync } from '../runStructureGenerationCommandAsync.js';
3
3
  const [operation, ...argv] = process.argv.slice(2);
4
4
  if (operation !== 'build' && operation !== 'check') {
5
5
  console.error('Usage: ankhorage-structure <build|check> [directory]');
@@ -1,4 +1,4 @@
1
- import { runApmReleaseCommandAsync } from '../../../features/apm-release-validation/adapters/inbound/runApmReleaseCommandAsync.js';
1
+ import { runApmReleaseCommandAsync } from '../../runApmReleaseCommandAsync.js';
2
2
  /*** Adapt the public APM release sync command to the shared release boundary. */
3
3
  export async function sync(argv, context) {
4
4
  return runApmReleaseCommandAsync('sync', argv, context);
@@ -1,4 +1,4 @@
1
- import { runApmReleaseCommandAsync } from '../../../features/apm-release-validation/adapters/inbound/runApmReleaseCommandAsync.js';
1
+ import { runApmReleaseCommandAsync } from '../../runApmReleaseCommandAsync.js';
2
2
  /*** Adapt the public APM release validate command to the shared release boundary. */
3
3
  export async function validate(argv, context) {
4
4
  return runApmReleaseCommandAsync('validate', argv, context);
@@ -1,4 +1,4 @@
1
- import { runStructureGenerationCommandAsync } from '../../../features/structure-descriptor-generation/adapters/inbound/runStructureGenerationCommandAsync.js';
1
+ import { runStructureGenerationCommandAsync } from '../../runStructureGenerationCommandAsync.js';
2
2
  /*** Adapt the public structure build command to deterministic owner artifact generation. */
3
3
  export async function build(argv, context) {
4
4
  return runStructureGenerationCommandAsync('build', argv, context);
@@ -1,4 +1,4 @@
1
- import { runStructureGenerationCommandAsync } from '../../../features/structure-descriptor-generation/adapters/inbound/runStructureGenerationCommandAsync.js';
1
+ import { runStructureGenerationCommandAsync } from '../../runStructureGenerationCommandAsync.js';
2
2
  /*** Adapt the public structure check command to deterministic owner artifact verification. */
3
3
  export async function check(argv, context) {
4
4
  return runStructureGenerationCommandAsync('check', argv, context);
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
- import { synchronizeApmReleaseDescriptorAsync } from '../../composition/synchronizeApmReleaseDescriptorAsync.js';
3
- import { validatePackedApmReleaseAsync } from '../../composition/validatePackedApmReleaseAsync.js';
2
+ import { synchronizeApmReleaseDescriptorAsync } from '../features/apm-release-validation/composition/synchronizeApmReleaseDescriptorAsync.js';
3
+ import { validatePackedApmReleaseAsync } from '../features/apm-release-validation/composition/validatePackedApmReleaseAsync.js';
4
4
  /*** Parse release CLI input and report the same structured validation result on every entrypoint. */
5
5
  export async function runApmReleaseCommandAsync(operation, argv, context) {
6
6
  try {
@@ -1,5 +1,5 @@
1
1
  import { resolve } from 'node:path';
2
- import { synchronizeStructureArtifactForDirectoryAsync } from '../../composition/synchronizeStructureArtifactForDirectoryAsync.js';
2
+ import { synchronizeStructureArtifactForDirectoryAsync } from '../features/structure-descriptor-generation/composition/synchronizeStructureArtifactForDirectoryAsync.js';
3
3
  /*** Parse structure build/check CLI input and emit one machine-readable result. */
4
4
  export async function runStructureGenerationCommandAsync(operation, argv, context) {
5
5
  try {
@@ -1,44 +1,12 @@
1
- import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
2
1
  const REQUIRED_README_SNIPPETS = [
3
- 'ankh devtools lint',
4
- 'ankh devtools changeset',
5
- 'ankh devtools format',
6
- 'ankh devtools knip',
7
- 'ankh devtools sync',
8
- 'ankh devtools status',
9
- 'ankh devtools agents sync',
10
- 'ankh devtools skills sync',
11
- 'ankh devtools eslint sync',
12
- 'ankh devtools prettier sync',
13
- 'ankh devtools knip sync',
14
- 'ankh devtools package sync',
15
- 'ankh devtools workflows sync',
16
- 'ankh devtools vscode sync',
17
- 'devtools.eslint.sync',
18
- 'devtools.agents.sync',
19
- 'devtools.skills.sync',
20
- 'ankhorage-coding-rules',
21
- 'hexagonal-architecture',
22
- 'devtools.prettier.sync',
23
- 'devtools.knip.sync',
24
- 'devtools.package.sync',
25
- 'devtools.workflows.sync',
26
- 'devtools.vscode.sync',
27
- '--dry-run',
28
- "profile: 'auto'",
29
- '@ankhorage/project-detector',
30
- 'ankhorage-changeset',
31
- 'without creating a second Version Packages pull request',
32
- 'chore(release)',
33
- '.changeset/config.json',
34
- '<!-- devtools-bun-policy:start -->',
35
- '<!-- devtools-bun-policy:end -->',
36
- 'bun scripts/sync-renovate-owner.ts sync repository',
37
- 'bun scripts/sync-renovate-owner.ts status',
38
- '@ankhorage/policy',
39
- REPOSITORY_POLICY.runtime.bun.version,
40
- REPOSITORY_POLICY.runtime.bun.packageManager,
41
- REPOSITORY_POLICY.runtime.bun.typesRange,
2
+ '<!-- This file is generated by Paradox. Do not edit manually. -->',
3
+ '# @ankhorage/devtools',
4
+ '## Usage',
5
+ 'ankh devtools --help',
6
+ 'Standalone package ESLint configuration',
7
+ 'export default createConfig({',
8
+ '## Generated documentation',
9
+ 'paradox/index.html',
42
10
  ];
43
11
  export function getReadmeDocumentationErrors(readmeContents) {
44
12
  return REQUIRED_README_SNIPPETS.flatMap((snippet) => readmeContents.includes(snippet)
@@ -1,4 +1,4 @@
1
- /*** Synchronize or validate Renovate-owned Devtools artifacts from central repository policy. */
1
+ /*** Synchronize or validate Renovate-owned Devtools artifacts from Devtools-owned policy. */
2
2
  export declare function synchronizeRenovateOwnerAsync(operation: OwnerSyncOperation, targetDirectory: string, options?: OwnerSyncOptions): Promise<void>;
3
3
  type OwnerSyncOperation = 'status' | 'sync';
4
4
  interface OwnerSyncOptions {
@@ -5,11 +5,12 @@ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
5
5
  import { resolveApmReleaseCommandAsync } from '../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
6
6
  import { resolveStructureReleaseCommandAsync } from '../features/structure-descriptor-generation/adapters/outbound/resolveStructureReleaseCommandAsync.js';
7
7
  import { applyBunRuntimePolicy } from '../policy/applyBunRuntimePolicy.js';
8
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from '../policy/bunRuntimePolicy.js';
8
9
  import { renderBunPolicyDocumentation } from '../policy/renderBunPolicyDocumentation.js';
9
10
  import { readCurrentDoctorVersion } from '../tools/workflows/readCurrentDoctorVersion.js';
10
11
  import { renderRenovateWorkflowAsync } from '../tools/workflows/renderRenovateWorkflowAsync.js';
11
12
  import { renderWorkflowAsync, } from '../tools/workflows/renderWorkflowAsync.js';
12
- /*** Synchronize or validate Renovate-owned Devtools artifacts from central repository policy. */
13
+ /*** Synchronize or validate Renovate-owned Devtools artifacts from Devtools-owned policy. */
13
14
  export async function synchronizeRenovateOwnerAsync(operation, targetDirectory, options = {}) {
14
15
  const target = resolve(targetDirectory);
15
16
  const definitions = await createManagedDefinitionsAsync(target);
@@ -69,7 +70,7 @@ async function createWorkflowPolicyAsync(targetDirectory) {
69
70
  return {
70
71
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
71
72
  structureReleaseCommand: await resolveStructureReleaseCommandAsync(targetDirectory),
72
- bunVersion: REPOSITORY_POLICY.runtime.bun.version,
73
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
73
74
  doctorVersion: readCurrentDoctorVersion(),
74
75
  nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
75
76
  };
@@ -1,2 +1,2 @@
1
- /*** Apply the canonical Bun runtime policy to one package manifest. */
1
+ /*** Apply the Devtools-owned Bun runtime policy to one package manifest. */
2
2
  export declare function applyBunRuntimePolicy(manifest: Record<string, unknown>): Record<string, unknown>;
@@ -1,11 +1,11 @@
1
- import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
2
- /*** Apply the canonical Bun runtime policy to one package manifest. */
1
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from './bunRuntimePolicy.js';
2
+ /*** Apply the Devtools-owned Bun runtime policy to one package manifest. */
3
3
  export function applyBunRuntimePolicy(manifest) {
4
4
  const devDependencies = toRecord(manifest.devDependencies);
5
- devDependencies['@types/bun'] = REPOSITORY_POLICY.runtime.bun.typesRange;
5
+ devDependencies['@types/bun'] = DEVTOOLS_BUN_RUNTIME_POLICY.typesRange;
6
6
  return {
7
7
  ...manifest,
8
- packageManager: REPOSITORY_POLICY.runtime.bun.packageManager,
8
+ packageManager: DEVTOOLS_BUN_RUNTIME_POLICY.packageManager,
9
9
  devDependencies,
10
10
  };
11
11
  }
@@ -0,0 +1,5 @@
1
+ export declare const DEVTOOLS_BUN_RUNTIME_POLICY: {
2
+ readonly packageManager: "bun@1.4.2";
3
+ readonly typesRange: "^1.4.2";
4
+ readonly version: "1.4.2";
5
+ };
@@ -0,0 +1,5 @@
1
+ export const DEVTOOLS_BUN_RUNTIME_POLICY = {
2
+ packageManager: 'bun@1.4.2',
3
+ typesRange: '^1.4.2',
4
+ version: '1.4.2',
5
+ };
@@ -1,4 +1,4 @@
1
- import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
1
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from './bunRuntimePolicy.js';
2
2
  /*** Render the managed Bun policy section while preserving the surrounding guide. */
3
3
  export function renderBunPolicyDocumentation(readme) {
4
4
  const startIndex = readme.indexOf(README_POLICY_START);
@@ -15,11 +15,10 @@ export function renderBunPolicyDocumentation(readme) {
15
15
  }
16
16
  /*** Format canonical runtime and type-package versions for the guide. */
17
17
  function renderReadmePolicy() {
18
- const policy = REPOSITORY_POLICY.runtime.bun;
19
18
  return `\`\`\`text
20
- Bun runtime ${policy.version}
21
- packageManager ${policy.packageManager}
22
- @types/bun ${policy.typesRange}
19
+ Bun runtime ${DEVTOOLS_BUN_RUNTIME_POLICY.version}
20
+ packageManager ${DEVTOOLS_BUN_RUNTIME_POLICY.packageManager}
21
+ @types/bun ${DEVTOOLS_BUN_RUNTIME_POLICY.typesRange}
23
22
  \`\`\``;
24
23
  }
25
24
  const README_POLICY_END = '<!-- devtools-bun-policy:end -->';
@@ -127,6 +127,10 @@ bun run changeset
127
127
  bun run format
128
128
  \`\`\`
129
129
 
130
+ For repositories that use Changesets, run \`bun run changeset\` only for release-impacting work.
131
+ No Changeset means no release is requested. Never add an empty Changeset to satisfy CI; remove it
132
+ for a no-release pull request, or add explicit release intent before validation.
133
+
130
134
  `;
131
135
  }
132
136
  /*** Render the repository rule for executable Agent Skill scripts. */
@@ -7,8 +7,9 @@
7
7
  * `lint:fix`, `format`, `format:check`, and `knip:check` scripts are written.
8
8
  * Unrelated manifest fields, scripts, dependencies, and metadata are preserved.
9
9
  *
10
- * The Bun runtime policy comes from @ankhorage/policy for every repository, including devtools itself. Devtools skips
11
- * only its consumer dependency/script normalization so it never attempts to install itself.
10
+ * The Bun runtime synchronization contract is Devtools-owned for every repository, including
11
+ * Devtools itself. Devtools skips only its consumer dependency/script normalization so it never
12
+ * attempts to install itself.
12
13
  *
13
14
  * Status compares only the fields owned by this contract, so unrelated repository customization
14
15
  * does not count as drift. `--dry-run` reports whether `package.json` would be created or updated
@@ -21,6 +22,7 @@ import { readFile, writeFile } from 'node:fs/promises';
21
22
  import { resolve } from 'node:path';
22
23
  import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
23
24
  import { applyBunRuntimePolicy } from '../../policy/applyBunRuntimePolicy.js';
25
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from '../../policy/bunRuntimePolicy.js';
24
26
  const PACKAGE_PATH = 'package.json';
25
27
  const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
26
28
  const BUN_TYPES_PACKAGE_NAME = '@types/bun';
@@ -153,8 +155,8 @@ async function readPackageManifest(targetDirectory) {
153
155
  /*** Check the Bun package manager and Bun type dependency contract. */
154
156
  function hasCurrentBunRuntimePolicy(manifest) {
155
157
  const devDependencies = toRecord(manifest.devDependencies);
156
- return (manifest.packageManager === REPOSITORY_POLICY.runtime.bun.packageManager &&
157
- devDependencies[BUN_TYPES_PACKAGE_NAME] === REPOSITORY_POLICY.runtime.bun.typesRange);
158
+ return (manifest.packageManager === DEVTOOLS_BUN_RUNTIME_POLICY.packageManager &&
159
+ devDependencies[BUN_TYPES_PACKAGE_NAME] === DEVTOOLS_BUN_RUNTIME_POLICY.typesRange);
158
160
  }
159
161
  /*** Place Devtools in development dependencies for every consuming repository. */
160
162
  function applyDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion) {
@@ -107,10 +107,30 @@ jobs:
107
107
  - name: Check changesets
108
108
  if: github.event_name == 'pull_request'
109
109
  run: |
110
+ base_sha='${{ github.event.pull_request.base.sha }}'
111
+ head_sha='${{ github.event.pull_request.head.sha }}'
112
+ export ANKH_PR_CHANGESET_FILES
113
+ ANKH_PR_CHANGESET_FILES="$(git diff --name-only --diff-filter=AMCR "$base_sha" "$head_sha" -- .changeset)"
114
+ if [ -z "$ANKH_PR_CHANGESET_FILES" ]; then
115
+ echo "No pull-request-owned Changeset files; no release requested."
116
+ exit 0
117
+ fi
118
+ node -e '
119
+ const { readFileSync } = require("node:fs");
120
+ const files = process.env.ANKH_PR_CHANGESET_FILES.split("\n").filter(Boolean);
121
+ const empty = files.filter((file) => {
122
+ if (!/^\.changeset\/[^/]+\.md$/u.test(file) || file === ".changeset/README.md") return false;
123
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(readFileSync(file, "utf8"));
124
+ return match?.[1].trim() === "";
125
+ });
126
+ if (empty.length > 0) {
127
+ throw new Error("Empty Changesets are not supported. Remove " + empty.join(", ") + " for a no-release PR, or add release intent.");
128
+ }
129
+ '
110
130
  if node -e "const p=require('./package.json'); process.exit(p.scripts?.['changeset:status'] ? 0 : 1)"; then
111
131
  __ANKH_CHANGESETS_STATUS_COMMAND__
112
132
  else
113
- echo "No changeset:status script found; skipping."
133
+ echo "No changeset:status script found; skipping validation."
114
134
  fi
115
135
 
116
136
  # __ANKH_PKGVIZ_AUDIT_STEPS__
@@ -25,7 +25,7 @@ jobs:
25
25
  (github.event.pull_request.user.login == 'renovate[bot]' || github.event.pull_request.user.login == 'ankhorage-renovate-sync[bot]') &&
26
26
  github.event.pull_request.head.repo.full_name == github.repository &&
27
27
  startsWith(github.event.pull_request.head.ref, 'renovate/')
28
- uses: ankhorage/renovate/.github/workflows/changeset.yml@db48610ed5bc6a1191798b123ce86419571d7bc6
28
+ uses: ankhorage/renovate/.github/workflows/changeset.yml@60f0b8c853ecc2c3601d947e8c9414c8a89bd481
29
29
  with:
30
30
  renovate_sync_client_id: ${{ vars.ANKHORAGE_RENOVATE_SYNC_CLIENT_ID }}
31
31
  secrets:
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
4
4
  import { resolveApmReleaseCommandAsync } from '../../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
5
5
  import { resolveStructureReleaseCommandAsync } from '../../features/structure-descriptor-generation/adapters/outbound/resolveStructureReleaseCommandAsync.js';
6
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from '../../policy/bunRuntimePolicy.js';
6
7
  import { resolvePkgvizAuditPolicyAsync } from '../../policy/resolvePkgvizAuditPolicyAsync.js';
7
8
  import { readCurrentDoctorVersion } from './readCurrentDoctorVersion.js';
8
9
  import { renderRenovateConfigAsync } from './renderRenovateConfigAsync.js';
@@ -38,7 +39,7 @@ function createWorkflowDefinition(relativePath, sourcePath) {
38
39
  render: async (targetDirectory) => await renderWorkflowAsync(sourceUrl, {
39
40
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
40
41
  structureReleaseCommand: await resolveStructureReleaseCommandAsync(targetDirectory),
41
- bunVersion: REPOSITORY_POLICY.runtime.bun.version,
42
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
42
43
  doctorVersion: readCurrentDoctorVersion(),
43
44
  nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
44
45
  pkgvizAudit: await resolvePkgvizAuditPolicyAsync(targetDirectory),
@@ -53,7 +54,7 @@ function createRenovateWorkflowDefinition() {
53
54
  render: async (targetDirectory) => await renderRenovateWorkflowAsync(sourceUrl, targetDirectory, {
54
55
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
55
56
  structureReleaseCommand: await resolveStructureReleaseCommandAsync(targetDirectory),
56
- bunVersion: REPOSITORY_POLICY.runtime.bun.version,
57
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
57
58
  doctorVersion: readCurrentDoctorVersion(),
58
59
  nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
59
60
  }),
@@ -1,2 +1,2 @@
1
- /*** Render Renovate config while preserving repository-owned rules and removing obsolete Paradox metadata. */
1
+ /*** Render the current Renovate config while preserving supported repository-owned rules. */
2
2
  export declare function renderRenovateConfigAsync(targetDirectory: string): Promise<string>;
@@ -1,8 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  const TEMPLATE_URL = new URL('./files/renovate.json5', import.meta.url);
4
- const LEGACY_HEADER_PATTERN = /^\/\*\*\*([\s\S]*?)\*\/\n/u;
5
- /*** Render Renovate config while preserving repository-owned rules and removing obsolete Paradox metadata. */
4
+ /*** Render the current Renovate config while preserving supported repository-owned rules. */
6
5
  export async function renderRenovateConfigAsync(targetDirectory) {
7
6
  const targetPath = join(targetDirectory, 'renovate.json5');
8
7
  let current;
@@ -15,21 +14,5 @@ export async function renderRenovateConfigAsync(targetDirectory) {
15
14
  }
16
15
  throw error;
17
16
  }
18
- return migrateLegacyRenovateHeader(current);
19
- }
20
- /*** Convert the historical managed Paradox header to an ordinary comment without changing config data. */
21
- function migrateLegacyRenovateHeader(contents) {
22
- const match = LEGACY_HEADER_PATTERN.exec(contents);
23
- if (match === null)
24
- return contents;
25
- const [header, body = ''] = match;
26
- if (!body.includes('Repository configuration'))
27
- return contents;
28
- if (!body.includes('@usage') && !body.includes('@readme'))
29
- return contents;
30
- const migratedBody = body
31
- .split('\n')
32
- .filter((line) => !/^\s*\*\s+@(usage|readme)\s*$/u.test(line))
33
- .join('\n');
34
- return `/**${migratedBody}*/\n${contents.slice(header.length)}`;
17
+ return current;
35
18
  }
@@ -4,8 +4,15 @@ import { fileURLToPath } from 'node:url';
4
4
 
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
 
7
+ /***
8
+ * Configure ESLint for a standalone TypeScript package with the Devtools shared policy.
9
+ *
10
+ * @usage
11
+ * @readme
12
+ * @title Standalone package ESLint configuration
13
+ */
7
14
  export default createConfig({
8
15
  tsconfigRootDir: __dirname,
9
- project: ['./tsconfig.eslint.json'],
16
+ project: ['./tsconfig.json'],
10
17
  files: ['src/**/*.{ts,tsx}'],
11
18
  });
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@ankhorage/devtools-eslint-example",
3
+ "private": true,
4
+ "type": "module",
5
+ "devDependencies": {
6
+ "@ankhorage/devtools": "^2.0.0",
7
+ "typescript": "~6.0.3"
8
+ },
9
+ "scripts": {
10
+ "lint": "ankhorage-eslint . --max-warnings=0"
11
+ },
12
+ "packageManager": "bun@1.4.2"
13
+ }
@@ -0,0 +1 @@
1
+ export const exampleMessage = 'Standalone Devtools ESLint configuration.';
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "NodeNext",
4
+ "moduleResolution": "NodeNext",
5
+ "strict": true,
6
+ "target": "ES2024"
7
+ },
8
+ "include": ["src/**/*.ts"]
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Shared tooling, repository automation, and agent standards for Ankhorage TypeScript projects",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",
@@ -141,17 +141,17 @@
141
141
  "knip": "^6.38.0",
142
142
  "prettier": "^3.9.9",
143
143
  "typescript-eslint": "^8.70.1",
144
- "@ankhorage/contracts": "^22.8.1",
144
+ "@ankhorage/contracts": "^22.8.2",
145
145
  "typescript": "~6.0.3",
146
- "@ankhorage/policy": "^0.3.1"
146
+ "@ankhorage/policy": "^0.5.2"
147
147
  },
148
148
  "devDependencies": {
149
- "@ankhorage/paradox": "^0.1.26",
149
+ "@ankhorage/paradox": "^0.2.6",
150
150
  "@ankhorage/ankh": "^0.10.4",
151
- "@ankhorage/doctor": "0.11.3",
151
+ "@ankhorage/doctor": "0.11.8",
152
152
  "@techstark/opencv-js": "^5.0.0-release.1",
153
- "@types/bun": "^1.4.1",
154
- "@types/node": "^26.6.2",
153
+ "@types/bun": "^1.4.2",
154
+ "@types/node": "^26.6.3",
155
155
  "sharp": "^0.35.4"
156
156
  },
157
157
  "packageManager": "bun@1.4.2"