@ankhorage/devtools 1.8.5 → 1.9.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 (29) hide show
  1. package/README.md +29 -2
  2. package/dist/cli/commands.d.ts +2 -2
  3. package/dist/cli/commands.js +4 -0
  4. package/dist/cli/index.d.ts +2 -2
  5. package/dist/cli/index.js +5 -5
  6. package/dist/cli/runRepositoryCommand.js +20 -0
  7. package/dist/internal/readmeDocs.js +5 -0
  8. package/dist/tools/agents/index.d.ts +6 -0
  9. package/dist/tools/agents/index.js +70 -0
  10. package/dist/tools/shared/managedFiles.d.ts +2 -2
  11. package/dist/tools/skills/assets/ankhorage-coding-rules/SKILL.md +81 -0
  12. package/dist/tools/skills/assets/ankhorage-coding-rules/agents/openai.yaml +7 -0
  13. package/dist/tools/skills/assets/ankhorage-project-structure/SKILL.md +90 -0
  14. package/dist/tools/skills/assets/ankhorage-project-structure/agents/openai.yaml +7 -0
  15. package/dist/tools/skills/assets/ankhorage-project-structure/references/cli.md +117 -0
  16. package/dist/tools/skills/assets/ankhorage-project-structure/references/expo-apps.md +43 -0
  17. package/dist/tools/skills/assets/ankhorage-project-structure/references/hexagonal-architecture.md +120 -0
  18. package/dist/tools/skills/assets/ankhorage-project-structure/references/migration.md +61 -0
  19. package/dist/tools/skills/assets/ankhorage-project-structure/references/repository-profiles.md +91 -0
  20. package/dist/tools/skills/assets/ankhorage-project-structure/references/skill-distribution.md +115 -0
  21. package/dist/tools/skills/assets/ankhorage-project-structure/references/studio.md +134 -0
  22. package/dist/tools/skills/assets/ankhorage-project-structure/references/ui-libraries.md +79 -0
  23. package/dist/tools/skills/assets/ankhorage-project-structure/references/utilities.md +62 -0
  24. package/dist/tools/skills/managed.d.ts +5 -0
  25. package/dist/tools/skills/managed.js +224 -0
  26. package/dist/tools/skills/manifest.d.ts +16 -0
  27. package/dist/tools/skills/manifest.js +95 -0
  28. package/dist/tools/workflows/files/renovate.yml +1 -1
  29. package/package.json +10 -6
@@ -0,0 +1,62 @@
1
+ # Utility Ownership and Unknown-Value Narrowing
2
+
3
+ Classify a helper before creating or moving it:
4
+
5
+ ```text
6
+ cross-repository, framework-neutral -> @ankhorage/utility/<category>
7
+ cross-domain inside one package -> src/utils/<functionName>.ts
8
+ one domain only -> src/<domain>/utils/<functionName>.ts
9
+ semantic/domain behavior -> owning domain, not utils
10
+ ```
11
+
12
+ Do not retain local copies while waiting for a Utility release. Follow the Utility PR, merge,
13
+ release, dependency-update sequence defined by `ankhorage-package-structure`.
14
+
15
+ ## Strong cross-repository candidates
16
+
17
+ Generic object and unknown-value operations commonly belong in focused Utility subpaths:
18
+
19
+ ```text
20
+ @ankhorage/utility/object
21
+ readOwnProperty
22
+ setOwnProperty
23
+ deleteOwnProperty
24
+ isRecord
25
+
26
+ @ankhorage/utility/value
27
+ asString
28
+ asNumber
29
+ asRecord
30
+ ```
31
+
32
+ Create public utilities only after confirming repetition and stable semantics across repositories.
33
+ Do not move feature payload parsers, provider response semantics, or domain validation merely
34
+ because they contain small type guards.
35
+
36
+ ## Absence and failure semantics
37
+
38
+ Use consistent meanings:
39
+
40
+ - `isRecord(value)` returns a type-guard boolean.
41
+ - `asRecord(value)` returns the narrowed record or `undefined`.
42
+ - `asString(value)` returns the string or `undefined`.
43
+ - `readOwnProperty(target, key)` returns the owned value or `undefined`.
44
+ - `parse<DomainValue>(value)` returns an explicit domain parse result when callers need to
45
+ distinguish absent, invalid, and valid values.
46
+
47
+ Reserve `null` for an intentional domain or serialized value. Do not use `null` as the generic
48
+ failure result for type narrowing when `undefined` expresses absence. When failure details matter,
49
+ use a discriminated result instead of alternating between `null` and `undefined`.
50
+
51
+ ## Avoid false utilities
52
+
53
+ Keep these with their owner:
54
+
55
+ - `readProjectDeployConfig`
56
+ - `readSecretPayload`
57
+ - `parseStudioModuleState`
58
+ - API response validation with feature-specific error policy
59
+ - helpers that encode manifest, route, auth, deploy, or provider semantics
60
+
61
+ A utility is not a place to hide complexity or shorten a file. It must represent a reusable,
62
+ cohesive capability with stable behavior.
@@ -0,0 +1,5 @@
1
+ import type { ManagedFileStatus, ManagedFileSyncResult } from '../shared/managedFiles.js';
2
+ export declare function inspectManagedSkills(targetDirectory: string, devtoolsVersion: string): Promise<readonly ManagedFileStatus[]>;
3
+ export declare function syncManagedSkills(targetDirectory: string, devtoolsVersion: string, options: {
4
+ readonly dryRun: boolean;
5
+ }): Promise<readonly ManagedFileSyncResult[]>;
@@ -0,0 +1,224 @@
1
+ import { lstat, mkdir, readdir, readFile, rmdir, unlink, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { assertManagedSkillPath, createManifestContents, isNodeError, MANIFEST_PATH, readManagedSkillsManifest, resolveManagedPath, SKILLS_ROOT, } from './manifest.js';
5
+ const CANONICAL_SKILL_NAMES = ['ankhorage-coding-rules', 'ankhorage-project-structure'];
6
+ export async function inspectManagedSkills(targetDirectory, devtoolsVersion) {
7
+ return (await createManagedSkillPlan(targetDirectory, devtoolsVersion)).statuses;
8
+ }
9
+ export async function syncManagedSkills(targetDirectory, devtoolsVersion, options) {
10
+ const plan = await createManagedSkillPlan(targetDirectory, devtoolsVersion);
11
+ const results = [];
12
+ for (const status of plan.statuses) {
13
+ const action = getSyncAction(status, options.dryRun);
14
+ if (!options.dryRun) {
15
+ await applySkillAction(targetDirectory, status, action, plan);
16
+ }
17
+ results.push({ relativePath: status.relativePath, action });
18
+ }
19
+ return results;
20
+ }
21
+ async function applySkillAction(targetDirectory, status, action, plan) {
22
+ if (action === 'unchanged') {
23
+ return;
24
+ }
25
+ const targetPath = resolveManagedPath(targetDirectory, status.relativePath);
26
+ await assertNoSymlinkSegments(targetDirectory, status.relativePath);
27
+ if (action === 'removed') {
28
+ await unlink(targetPath);
29
+ await removeEmptyParentDirectories(targetDirectory, dirname(targetPath));
30
+ return;
31
+ }
32
+ await mkdir(dirname(targetPath), { recursive: true });
33
+ const contents = status.relativePath === MANIFEST_PATH
34
+ ? plan.manifestContents
35
+ : plan.contentsByPath.get(status.relativePath);
36
+ if (contents === undefined) {
37
+ throw new Error(`Missing canonical skill contents for ${status.relativePath}.`);
38
+ }
39
+ await writeFile(targetPath, contents);
40
+ }
41
+ async function assertNoSymlinkSegments(targetDirectory, relativePath) {
42
+ let currentPath = targetDirectory;
43
+ for (const segment of relativePath.split('/')) {
44
+ currentPath = join(currentPath, segment);
45
+ try {
46
+ const stats = await lstat(currentPath);
47
+ if (stats.isSymbolicLink()) {
48
+ throw new Error(`Managed skill path must not contain symbolic links: ${relativePath}`);
49
+ }
50
+ }
51
+ catch (error) {
52
+ if (isNodeError(error) && error.code === 'ENOENT') {
53
+ return;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ }
59
+ async function collectCanonicalSkillFiles() {
60
+ const contentsByPath = new Map();
61
+ for (const skillName of CANONICAL_SKILL_NAMES) {
62
+ const sourceDirectory = fileURLToPath(new URL(`./assets/${skillName}/`, import.meta.url));
63
+ const files = await readDirectoryFiles(sourceDirectory);
64
+ assertSkillName(skillName, files);
65
+ for (const [skillRelativePath, contents] of files) {
66
+ contentsByPath.set(`${SKILLS_ROOT}/${skillName}/${skillRelativePath}`, contents);
67
+ }
68
+ }
69
+ return contentsByPath;
70
+ }
71
+ async function collectObsoletePaths(targetDirectory, desiredPaths, previousManifest) {
72
+ const obsoletePaths = new Set();
73
+ for (const skillName of CANONICAL_SKILL_NAMES) {
74
+ const skillRoot = `${SKILLS_ROOT}/${skillName}`;
75
+ for (const existingPath of await readTargetDirectoryFiles(targetDirectory, skillRoot)) {
76
+ if (!desiredPaths.has(existingPath)) {
77
+ obsoletePaths.add(existingPath);
78
+ }
79
+ }
80
+ }
81
+ if (previousManifest !== null) {
82
+ for (const [skillName, entry] of Object.entries(previousManifest.skills)) {
83
+ if (CANONICAL_SKILL_NAMES.includes(skillName)) {
84
+ continue;
85
+ }
86
+ for (const managedPath of Object.keys(entry.files)) {
87
+ if (await isRegularManagedFile(targetDirectory, managedPath)) {
88
+ obsoletePaths.add(managedPath);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ return [...obsoletePaths].sort();
94
+ }
95
+ async function createManagedSkillPlan(targetDirectory, devtoolsVersion) {
96
+ const previousManifest = await readManagedSkillsManifest(targetDirectory);
97
+ const contentsByPath = await collectCanonicalSkillFiles();
98
+ const desiredPaths = new Set(contentsByPath.keys());
99
+ const manifestContents = createManifestContents(contentsByPath, devtoolsVersion, CANONICAL_SKILL_NAMES);
100
+ const statuses = [];
101
+ for (const [relativePath, contents] of [...contentsByPath.entries()].sort()) {
102
+ statuses.push(await inspectDesiredFile(targetDirectory, relativePath, contents));
103
+ }
104
+ for (const relativePath of await collectObsoletePaths(targetDirectory, desiredPaths, previousManifest)) {
105
+ statuses.push({ relativePath, state: 'obsolete' });
106
+ }
107
+ statuses.push(await inspectDesiredFile(targetDirectory, MANIFEST_PATH, Buffer.from(manifestContents)));
108
+ return { contentsByPath, manifestContents, statuses };
109
+ }
110
+ function getSyncAction(status, dryRun) {
111
+ if (status.state === 'current') {
112
+ return 'unchanged';
113
+ }
114
+ if (status.state === 'obsolete') {
115
+ return dryRun ? 'would-remove' : 'removed';
116
+ }
117
+ if (status.state === 'missing') {
118
+ return dryRun ? 'would-create' : 'created';
119
+ }
120
+ return dryRun ? 'would-update' : 'updated';
121
+ }
122
+ async function inspectDesiredFile(targetDirectory, relativePath, canonicalContents) {
123
+ await assertNoSymlinkSegments(targetDirectory, relativePath);
124
+ try {
125
+ const targetContents = await readFile(resolveManagedPath(targetDirectory, relativePath));
126
+ return {
127
+ relativePath,
128
+ state: Buffer.compare(targetContents, canonicalContents) === 0 ? 'current' : 'outdated',
129
+ };
130
+ }
131
+ catch (error) {
132
+ if (isNodeError(error) && error.code === 'ENOENT') {
133
+ return { relativePath, state: 'missing' };
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+ async function isRegularManagedFile(targetDirectory, relativePath) {
139
+ assertManagedSkillPath(relativePath);
140
+ await assertNoSymlinkSegments(targetDirectory, relativePath);
141
+ try {
142
+ return (await lstat(resolveManagedPath(targetDirectory, relativePath))).isFile();
143
+ }
144
+ catch (error) {
145
+ if (isNodeError(error) && error.code === 'ENOENT') {
146
+ return false;
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+ async function readDirectoryFiles(directory, prefix = '') {
152
+ const files = new Map();
153
+ const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
154
+ for (const entry of entries) {
155
+ const entryPath = join(directory, entry.name);
156
+ const relativePath = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
157
+ if (entry.isDirectory()) {
158
+ for (const nestedFile of await readDirectoryFiles(entryPath, relativePath)) {
159
+ files.set(...nestedFile);
160
+ }
161
+ }
162
+ else if (entry.isFile()) {
163
+ files.set(relativePath, await readFile(entryPath));
164
+ }
165
+ else {
166
+ throw new Error(`Canonical skill assets must contain only files and directories: ${entryPath}`);
167
+ }
168
+ }
169
+ return files;
170
+ }
171
+ async function readTargetDirectoryFiles(targetDirectory, relativeDirectory) {
172
+ await assertNoSymlinkSegments(targetDirectory, relativeDirectory);
173
+ const directory = resolveManagedPath(targetDirectory, relativeDirectory);
174
+ let entries;
175
+ try {
176
+ entries = await readdir(directory, { withFileTypes: true });
177
+ }
178
+ catch (error) {
179
+ if (isNodeError(error) && error.code === 'ENOENT') {
180
+ return [];
181
+ }
182
+ throw error;
183
+ }
184
+ const files = [];
185
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
186
+ const relativePath = `${relativeDirectory}/${entry.name}`;
187
+ if (entry.isDirectory()) {
188
+ files.push(...(await readTargetDirectoryFiles(targetDirectory, relativePath)));
189
+ }
190
+ else if (entry.isFile()) {
191
+ files.push(relativePath);
192
+ }
193
+ else {
194
+ throw new Error(`Managed skill trees must not contain symbolic links: ${relativePath}`);
195
+ }
196
+ }
197
+ return files;
198
+ }
199
+ async function removeEmptyParentDirectories(targetDirectory, initialDirectory) {
200
+ const stopDirectory = resolveManagedPath(targetDirectory, SKILLS_ROOT);
201
+ let directory = initialDirectory;
202
+ while (directory.startsWith(`${stopDirectory}${sep}`)) {
203
+ try {
204
+ await rmdir(directory);
205
+ }
206
+ catch (error) {
207
+ if (isNodeError(error) && ['ENOENT', 'ENOTEMPTY'].includes(error.code ?? '')) {
208
+ return;
209
+ }
210
+ throw error;
211
+ }
212
+ directory = dirname(directory);
213
+ }
214
+ }
215
+ function assertSkillName(expectedName, files) {
216
+ const skillFile = files.get('SKILL.md');
217
+ if (skillFile === undefined) {
218
+ throw new Error(`Canonical skill is missing SKILL.md: ${expectedName}`);
219
+ }
220
+ const match = /^---\n[\s\S]*?^name:\s*(.+)$/mu.exec(Buffer.from(skillFile).toString('utf8'));
221
+ if (match?.[1]?.trim() !== expectedName) {
222
+ throw new Error(`Canonical skill directory and frontmatter names differ: ${expectedName}`);
223
+ }
224
+ }
@@ -0,0 +1,16 @@
1
+ export declare const MANIFEST_PATH = ".agents/.devtools-manifest.json";
2
+ export declare const SKILLS_ROOT = ".agents/skills";
3
+ interface ManagedSkillManifestEntry {
4
+ readonly files: Readonly<Record<string, string>>;
5
+ }
6
+ export interface ManagedSkillsManifest {
7
+ readonly schemaVersion: 1;
8
+ readonly sourceDevtoolsVersion: string;
9
+ readonly skills: Readonly<Record<string, ManagedSkillManifestEntry>>;
10
+ }
11
+ export declare function createManifestContents(contentsByPath: ReadonlyMap<string, Uint8Array>, devtoolsVersion: string, skillNames: readonly string[]): string;
12
+ export declare function readManagedSkillsManifest(targetDirectory: string): Promise<ManagedSkillsManifest | null>;
13
+ export declare function assertManagedSkillPath(relativePath: string): void;
14
+ export declare function resolveManagedPath(targetDirectory: string, relativePath: string): string;
15
+ export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
16
+ export {};
@@ -0,0 +1,95 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { posix, relative, resolve } from 'node:path';
4
+ export const MANIFEST_PATH = '.agents/.devtools-manifest.json';
5
+ export const SKILLS_ROOT = '.agents/skills';
6
+ const MANIFEST_SCHEMA_VERSION = 1;
7
+ export function createManifestContents(contentsByPath, devtoolsVersion, skillNames) {
8
+ const skills = Object.fromEntries(skillNames.map((skillName) => {
9
+ const prefix = `${SKILLS_ROOT}/${skillName}/`;
10
+ const files = Object.fromEntries([...contentsByPath.entries()]
11
+ .filter(([relativePath]) => relativePath.startsWith(prefix))
12
+ .sort(([left], [right]) => left.localeCompare(right))
13
+ .map(([relativePath, contents]) => [relativePath, sha256(contents)]));
14
+ return [skillName, { files }];
15
+ }));
16
+ const manifest = {
17
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
18
+ sourceDevtoolsVersion: devtoolsVersion,
19
+ skills,
20
+ };
21
+ return `${JSON.stringify(manifest, null, 2)}\n`;
22
+ }
23
+ export async function readManagedSkillsManifest(targetDirectory) {
24
+ try {
25
+ const contents = await readFile(resolveManagedPath(targetDirectory, MANIFEST_PATH), 'utf8');
26
+ return parseManagedSkillsManifest(JSON.parse(contents));
27
+ }
28
+ catch (error) {
29
+ if (isNodeError(error) && error.code === 'ENOENT') {
30
+ return null;
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+ export function assertManagedSkillPath(relativePath) {
36
+ assertSafeRelativePath(relativePath);
37
+ if (!relativePath.startsWith(`${SKILLS_ROOT}/`)) {
38
+ throw new Error(`Managed skill manifest path is outside ${SKILLS_ROOT}: ${relativePath}`);
39
+ }
40
+ }
41
+ export function resolveManagedPath(targetDirectory, relativePath) {
42
+ assertSafeRelativePath(relativePath);
43
+ const targetPath = resolve(targetDirectory, ...relativePath.split('/'));
44
+ const relativeTarget = relative(resolve(targetDirectory), targetPath);
45
+ if (relativeTarget.startsWith('..') || relativeTarget === '') {
46
+ throw new Error(`Managed path escapes the target repository: ${relativePath}`);
47
+ }
48
+ return targetPath;
49
+ }
50
+ export function isNodeError(error) {
51
+ return error instanceof Error && 'code' in error;
52
+ }
53
+ function parseManagedSkillsManifest(value) {
54
+ if (!isRecord(value) || value.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
55
+ throw new Error(`Unsupported managed skills manifest at ${MANIFEST_PATH}.`);
56
+ }
57
+ if (typeof value.sourceDevtoolsVersion !== 'string' || !isRecord(value.skills)) {
58
+ throw new Error(`Invalid managed skills manifest at ${MANIFEST_PATH}.`);
59
+ }
60
+ const skillEntries = Object.entries(value.skills).map(([skillName, rawEntry]) => {
61
+ if (!isSafeName(skillName) || !isRecord(rawEntry) || !isRecord(rawEntry.files)) {
62
+ throw new Error(`Invalid managed skill entry in ${MANIFEST_PATH}: ${skillName}`);
63
+ }
64
+ const files = Object.fromEntries(Object.entries(rawEntry.files).map(([relativePath, hash]) => {
65
+ assertManagedSkillPath(relativePath);
66
+ if (typeof hash !== 'string' || !/^sha256:[a-f0-9]{64}$/u.test(hash)) {
67
+ throw new Error(`Invalid managed skill hash in ${MANIFEST_PATH}: ${relativePath}`);
68
+ }
69
+ return [relativePath, hash];
70
+ }));
71
+ return [skillName, { files }];
72
+ });
73
+ return {
74
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
75
+ sourceDevtoolsVersion: value.sourceDevtoolsVersion,
76
+ skills: Object.fromEntries(skillEntries),
77
+ };
78
+ }
79
+ function assertSafeRelativePath(relativePath) {
80
+ if (relativePath === '' ||
81
+ relativePath !== posix.normalize(relativePath) ||
82
+ relativePath.startsWith('/') ||
83
+ relativePath.split('/').some((segment) => segment === '..' || segment === '')) {
84
+ throw new Error(`Unsafe managed path: ${relativePath}`);
85
+ }
86
+ }
87
+ function isRecord(value) {
88
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
89
+ }
90
+ function isSafeName(value) {
91
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value);
92
+ }
93
+ function sha256(contents) {
94
+ return `sha256:${createHash('sha256').update(contents).digest('hex')}`;
95
+ }
@@ -17,7 +17,7 @@ jobs:
17
17
  github.actor == 'renovate[bot]' &&
18
18
  github.event.pull_request.head.repo.full_name == github.repository &&
19
19
  startsWith(github.event.pull_request.head.ref, 'renovate/')
20
- uses: ankhorage/renovate/.github/workflows/changeset.yml@b9a44c350b71b4292c8da69eb469888de60b68be
20
+ uses: ankhorage/renovate/.github/workflows/changeset.yml@4deba0b1e900c4fa9886b76cc0a0fee55df0f4aa
21
21
  with:
22
22
  renovate_sync_client_id: ${{ vars.ANKHORAGE_RENOVATE_SYNC_CLIENT_ID }}
23
23
  secrets:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.8.5",
3
+ "version": "1.9.1",
4
4
  "description": "Shared development tools and repository standards for Ankhorage",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",
@@ -25,6 +25,10 @@
25
25
  "devtools.knip",
26
26
  "devtools.sync",
27
27
  "devtools.status",
28
+ "devtools.agents.sync",
29
+ "devtools.agents.status",
30
+ "devtools.skills.sync",
31
+ "devtools.skills.status",
28
32
  "devtools.eslint.sync",
29
33
  "devtools.eslint.status",
30
34
  "devtools.prettier.sync",
@@ -85,7 +89,7 @@
85
89
  "examples"
86
90
  ],
87
91
  "scripts": {
88
- "build": "rm -rf dist tsconfig.tsbuildinfo && tsc && mkdir -p dist/tools/prettier dist/tools/workflows dist/tools/vscode && cp src/tools/prettier/index.cjs dist/tools/prettier/index.cjs && cp -R src/tools/workflows/files dist/tools/workflows/files && cp -R src/tools/vscode/files dist/tools/vscode/files",
92
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc && mkdir -p dist/tools/prettier dist/tools/workflows dist/tools/vscode dist/tools/skills && cp src/tools/prettier/index.cjs dist/tools/prettier/index.cjs && cp -R src/tools/workflows/files dist/tools/workflows/files && cp -R src/tools/vscode/files dist/tools/vscode/files && cp -R src/tools/skills/assets dist/tools/skills/assets",
89
93
  "typecheck": "bun x tsc --noEmit -p tsconfig.test.json",
90
94
  "doctor": "ankhorage-doctor validate .",
91
95
  "knip:check": "knip",
@@ -102,7 +106,7 @@
102
106
  },
103
107
  "dependencies": {
104
108
  "@ankhorage/utility": "^0.2.0",
105
- "@changesets/cli": "^2.31.1",
109
+ "@changesets/cli": "^3.0.1",
106
110
  "@eslint/compat": "^2.1.0",
107
111
  "@eslint/js": "^10.0.1",
108
112
  "eslint": "^10.9.1",
@@ -113,7 +117,7 @@
113
117
  "eslint-plugin-react-hooks": "^7.1.1",
114
118
  "eslint-plugin-react-native": "^5.0.0",
115
119
  "eslint-plugin-security": "^4.0.1",
116
- "eslint-plugin-simple-import-sort": "^12.1.1",
120
+ "eslint-plugin-simple-import-sort": "^14.0.0",
117
121
  "eslint-plugin-unused-imports": "^4.4.1",
118
122
  "knip": "^6.33.0",
119
123
  "prettier": "^3.9.6",
@@ -123,8 +127,8 @@
123
127
  "@ankhorage/ankh": "^0.8.4",
124
128
  "@ankhorage/doctor": "0.10.4",
125
129
  "@types/bun": "^1.4.0",
126
- "@types/node": "^25.9.5",
127
- "typescript": "^5.9.3"
130
+ "@types/node": "^26.4.0",
131
+ "typescript": "~6.0.3"
128
132
  },
129
133
  "packageManager": "bun@1.4.0"
130
134
  }