@ankhorage/devtools 1.15.2 → 1.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/devtools
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v1.15.2](././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: v1.15.3](././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)
7
7
 
8
8
  Shared tooling, repository automation, runtime policies, and agent standards for Ankhorage TypeScript projects
9
9
 
@@ -1,3 +1,4 @@
1
+ /*** Synchronize or validate Renovate-owned Devtools policy artifacts. */
1
2
  export declare function synchronizeRenovateOwnerAsync(operation: OwnerSyncOperation, targetDirectory: string, options?: OwnerSyncOptions): Promise<void>;
2
3
  type OwnerSyncOperation = 'status' | 'sync';
3
4
  interface OwnerSyncOptions {
@@ -4,8 +4,10 @@ import { dirname, resolve } from 'node:path';
4
4
  import { applyBunRuntimePolicy } from '../policy/applyBunRuntimePolicy.js';
5
5
  import { nodeRuntimePolicy } from '../policy/bunRuntimePolicy.js';
6
6
  import { renderBunPolicyDocumentation } from '../policy/renderBunPolicyDocumentation.js';
7
+ import { readCurrentDoctorVersion } from '../tools/workflows/readCurrentDoctorVersion.js';
7
8
  import { renderRenovateWorkflowAsync } from '../tools/workflows/renderRenovateWorkflowAsync.js';
8
9
  import { renderWorkflowAsync } from '../tools/workflows/renderWorkflowAsync.js';
10
+ /*** Synchronize or validate Renovate-owned Devtools policy artifacts. */
9
11
  export async function synchronizeRenovateOwnerAsync(operation, targetDirectory, options = {}) {
10
12
  const target = resolve(targetDirectory);
11
13
  const policy = await readTargetBunPolicyAsync(target);
@@ -23,12 +25,14 @@ export async function synchronizeRenovateOwnerAsync(operation, targetDirectory,
23
25
  await (options.runLockfileAsync ?? runBunLockfileAsync)(operation, target);
24
26
  }
25
27
  }
28
+ /*** Assert that Renovate owner synchronization targets the Devtools repository. */
26
29
  async function assertDevtoolsTargetAsync(targetDirectory) {
27
30
  const manifest = JSON.parse(await readFile(resolve(targetDirectory, 'package.json'), 'utf8'));
28
31
  if (!isRecord(manifest) || manifest.name !== '@ankhorage/devtools') {
29
32
  throw new Error('The Renovate owner sync target must be @ankhorage/devtools.');
30
33
  }
31
34
  }
35
+ /*** Build the canonical owner-managed artifact definitions for the target repository. */
32
36
  async function createManagedDefinitionsAsync(targetDirectory, policy) {
33
37
  const manifest = JSON.parse(await readFile(resolve(targetDirectory, 'package.json'), 'utf8'));
34
38
  if (!isRecord(manifest)) {
@@ -37,6 +41,7 @@ async function createManagedDefinitionsAsync(targetDirectory, policy) {
37
41
  const readme = await readFile(resolve(targetDirectory, 'README.md'), 'utf8');
38
42
  const workflowPolicy = {
39
43
  bunVersion: policy.version,
44
+ doctorVersion: readCurrentDoctorVersion(),
40
45
  nodeVersion: nodeRuntimePolicy.setupVersion,
41
46
  };
42
47
  return [
@@ -62,6 +67,7 @@ async function createManagedDefinitionsAsync(targetDirectory, policy) {
62
67
  },
63
68
  ];
64
69
  }
70
+ /*** Return owner-managed artifact paths whose current bytes differ from policy output. */
65
71
  async function getOutdatedPathsAsync(targetDirectory, definitions) {
66
72
  const results = await Promise.all(definitions.map(async ({ contents, relativePath }) => {
67
73
  const current = await readFile(resolve(targetDirectory, relativePath), 'utf8').catch((error) => {
@@ -73,12 +79,15 @@ async function getOutdatedPathsAsync(targetDirectory, definitions) {
73
79
  }));
74
80
  return results.filter((relativePath) => relativePath !== null);
75
81
  }
82
+ /*** Narrow an unknown error to a Node error carrying an error code. */
76
83
  function isNodeError(error) {
77
84
  return error instanceof Error && 'code' in error;
78
85
  }
86
+ /*** Narrow an unknown JSON-like value to a non-array record. */
79
87
  function isRecord(value) {
80
88
  return typeof value === 'object' && value !== null && !Array.isArray(value);
81
89
  }
90
+ /*** Read the target repository's canonical Bun policy literals. */
82
91
  async function readTargetBunPolicyAsync(targetDirectory) {
83
92
  const contents = await readFile(resolve(targetDirectory, 'src/policy/bunRuntimePolicy.ts'), 'utf8');
84
93
  const matches = [...contents.matchAll(BUN_VERSION_PATTERN)];
@@ -97,6 +106,7 @@ async function readTargetBunPolicyAsync(targetDirectory) {
97
106
  version,
98
107
  };
99
108
  }
109
+ /*** Synchronize or validate the target Bun lockfile through Bun itself. */
100
110
  async function runBunLockfileAsync(operation, targetDirectory) {
101
111
  const args = [
102
112
  'install',
@@ -118,9 +128,11 @@ async function runBunLockfileAsync(operation, targetDirectory) {
118
128
  });
119
129
  });
120
130
  }
131
+ /*** Serialize a package manifest using repository formatting. */
121
132
  function serializePackageManifest(manifest) {
122
133
  return `${JSON.stringify(manifest, null, 2)}\n`;
123
134
  }
135
+ /*** Write changed owner-managed artifacts while preserving byte-identical files. */
124
136
  async function syncDefinitionsAsync(targetDirectory, definitions) {
125
137
  for (const { contents, relativePath } of definitions) {
126
138
  const targetPath = resolve(targetDirectory, relativePath);
@@ -1,8 +1,13 @@
1
1
  import type { ManagedFileStatus, ManagedFileSyncResult } from '../shared/managedFiles.js';
2
+ /*** Inspect whether the Devtools-owned package manifest contract is current. */
2
3
  export declare function inspectPackageManifest(targetDirectory: string, devtoolsVersion: string): Promise<ManagedFileStatus>;
4
+ /*** Synchronize the Devtools-owned package manifest fields. */
3
5
  export declare function syncPackageManifest(targetDirectory: string, devtoolsVersion: string, options: {
4
6
  readonly dryRun: boolean;
5
7
  }): Promise<ManagedFileSyncResult>;
8
+ /*** Apply the Devtools-owned package scripts, dependencies, and Bun runtime policy. */
6
9
  export declare function applyManagedPackageContract(manifest: Record<string, unknown>, devtoolsVersion: string, changesetsConfigExists?: boolean): Record<string, unknown>;
10
+ /*** Check whether the Devtools-owned package manifest fields match current policy. */
7
11
  export declare function isManagedPackageContractCurrent(manifest: Record<string, unknown>, devtoolsVersion: string, changesetsConfigExists?: boolean): boolean;
12
+ /*** Read the current Devtools package version used for consumer synchronization. */
8
13
  export declare function readCurrentDevtoolsVersion(): string;
@@ -2,10 +2,9 @@
2
2
  * Synchronize the consumer `package.json` contract owned by `@ankhorage/devtools`.
3
3
  *
4
4
  * Package synchronization is merge-aware. It installs the current `@ankhorage/devtools` version
5
- * as a development dependency for normal consumers, while `@ankhorage/ankh` keeps devtools as a
6
- * runtime dependency because the CLI loads it as a bundled core provider. Individually installed
7
- * toolchain packages that devtools now owns are removed, and the canonical `lint`, `lint:fix`,
8
- * `format`, `format:check`, and `knip:check` scripts are written.
5
+ * as a development dependency for every consumer, including `@ankhorage/ankh`. Individually
6
+ * installed toolchain packages that devtools now owns are removed, and the canonical `lint`,
7
+ * `lint:fix`, `format`, `format:check`, and `knip:check` scripts are written.
9
8
  * Unrelated manifest fields, scripts, dependencies, and metadata are preserved.
10
9
  *
11
10
  * The Bun runtime policy is shared by every repository, including devtools itself. Devtools skips
@@ -25,7 +24,6 @@ import { bunRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
25
24
  import { changesetsPolicy } from '../../policy/changesetsPolicy.js';
26
25
  const PACKAGE_PATH = 'package.json';
27
26
  const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
28
- const ANKH_PACKAGE_NAME = '@ankhorage/ankh';
29
27
  const BUN_TYPES_PACKAGE_NAME = '@types/bun';
30
28
  const CHANGESETS_CONFIG_PATH = '.changeset/config.json';
31
29
  const STANDARD_SCRIPTS = {
@@ -51,6 +49,7 @@ const DEVTOOLS_OWNED_DEV_DEPENDENCIES = [
51
49
  'prettier',
52
50
  'typescript-eslint',
53
51
  ];
52
+ /*** Inspect whether the Devtools-owned package manifest contract is current. */
54
53
  export async function inspectPackageManifest(targetDirectory, devtoolsVersion) {
55
54
  const snapshot = await readPackageManifest(targetDirectory);
56
55
  if (!snapshot.exists) {
@@ -63,6 +62,7 @@ export async function inspectPackageManifest(targetDirectory, devtoolsVersion) {
63
62
  : 'outdated',
64
63
  };
65
64
  }
65
+ /*** Synchronize the Devtools-owned package manifest fields. */
66
66
  export async function syncPackageManifest(targetDirectory, devtoolsVersion, options) {
67
67
  const snapshot = await readPackageManifest(targetDirectory);
68
68
  if (snapshot.exists &&
@@ -82,6 +82,7 @@ export async function syncPackageManifest(targetDirectory, devtoolsVersion, opti
82
82
  action: snapshot.exists ? 'updated' : 'created',
83
83
  };
84
84
  }
85
+ /*** Apply the Devtools-owned package scripts, dependencies, and Bun runtime policy. */
85
86
  export function applyManagedPackageContract(manifest, devtoolsVersion, changesetsConfigExists = false) {
86
87
  if (manifest.name === DEVTOOLS_PACKAGE_NAME) {
87
88
  return applyBunRuntimePolicy(manifest, bunRuntimePolicy);
@@ -97,7 +98,7 @@ export function applyManagedPackageContract(manifest, devtoolsVersion, changeset
97
98
  const dependencies = toRecord(manifest.dependencies);
98
99
  delete dependencies[changesetsPolicy.packageName];
99
100
  delete devDependencies[changesetsPolicy.packageName];
100
- applyDevtoolsDependencyPlacement(manifest, dependencies, devDependencies, devtoolsVersion);
101
+ applyDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion);
101
102
  return applyBunRuntimePolicy({
102
103
  ...manifest,
103
104
  ...normalizedDependencies(manifest, dependencies),
@@ -105,6 +106,7 @@ export function applyManagedPackageContract(manifest, devtoolsVersion, changeset
105
106
  devDependencies,
106
107
  }, bunRuntimePolicy);
107
108
  }
109
+ /*** Check whether the Devtools-owned package manifest fields match current policy. */
108
110
  export function isManagedPackageContractCurrent(manifest, devtoolsVersion, changesetsConfigExists = false) {
109
111
  if (!hasCurrentBunRuntimePolicy(manifest)) {
110
112
  return false;
@@ -121,8 +123,9 @@ export function isManagedPackageContractCurrent(manifest, devtoolsVersion, chang
121
123
  dependencies[changesetsPolicy.packageName] === undefined &&
122
124
  devDependencies[changesetsPolicy.packageName] === undefined &&
123
125
  hasCurrentChangesetsScripts(scripts, changesetsEnabled) &&
124
- hasCurrentDevtoolsDependencyPlacement(manifest, dependencies, devDependencies, devtoolsVersion));
126
+ hasCurrentDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion));
125
127
  }
128
+ /*** Read the current Devtools package version used for consumer synchronization. */
126
129
  export function readCurrentDevtoolsVersion() {
127
130
  const parsed = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'));
128
131
  if (!isRecord(parsed) || typeof parsed.version !== 'string' || parsed.version.trim() === '') {
@@ -130,6 +133,7 @@ export function readCurrentDevtoolsVersion() {
130
133
  }
131
134
  return parsed.version;
132
135
  }
136
+ /*** Read one package manifest snapshot and Changesets applicability state. */
133
137
  async function readPackageManifest(targetDirectory) {
134
138
  const changesetsConfigExists = existsSync(resolve(targetDirectory, CHANGESETS_CONFIG_PATH));
135
139
  try {
@@ -147,62 +151,63 @@ async function readPackageManifest(targetDirectory) {
147
151
  throw error;
148
152
  }
149
153
  }
154
+ /*** Check the Bun package manager and Bun type dependency contract. */
150
155
  function hasCurrentBunRuntimePolicy(manifest) {
151
156
  const devDependencies = toRecord(manifest.devDependencies);
152
157
  return (manifest.packageManager === bunRuntimePolicy.packageManager &&
153
158
  devDependencies[BUN_TYPES_PACKAGE_NAME] === bunRuntimePolicy.typesRange);
154
159
  }
155
- function applyDevtoolsDependencyPlacement(manifest, dependencies, devDependencies, devtoolsVersion) {
156
- const versionRange = `^${devtoolsVersion}`;
157
- if (manifest.name === ANKH_PACKAGE_NAME) {
158
- dependencies[DEVTOOLS_PACKAGE_NAME] = versionRange;
159
- delete devDependencies[DEVTOOLS_PACKAGE_NAME];
160
- return;
161
- }
162
- devDependencies[DEVTOOLS_PACKAGE_NAME] = versionRange;
160
+ /*** Place Devtools in development dependencies for every consuming repository. */
161
+ function applyDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion) {
162
+ devDependencies[DEVTOOLS_PACKAGE_NAME] = `^${devtoolsVersion}`;
163
163
  delete dependencies[DEVTOOLS_PACKAGE_NAME];
164
164
  }
165
- function hasCurrentDevtoolsDependencyPlacement(manifest, dependencies, devDependencies, devtoolsVersion) {
166
- const versionRange = `^${devtoolsVersion}`;
167
- if (manifest.name === ANKH_PACKAGE_NAME) {
168
- return (dependencies[DEVTOOLS_PACKAGE_NAME] === versionRange &&
169
- devDependencies[DEVTOOLS_PACKAGE_NAME] === undefined);
170
- }
171
- return (devDependencies[DEVTOOLS_PACKAGE_NAME] === versionRange &&
165
+ /*** Check that Devtools is development-only at the current synchronized version. */
166
+ function hasCurrentDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion) {
167
+ return (devDependencies[DEVTOOLS_PACKAGE_NAME] === `^${devtoolsVersion}` &&
172
168
  dependencies[DEVTOOLS_PACKAGE_NAME] === undefined);
173
169
  }
170
+ /*** Remove direct development dependencies owned by the Devtools package. */
174
171
  function removeOwnedDependencies(devDependencies) {
175
172
  for (const dependencyName of DEVTOOLS_OWNED_DEV_DEPENDENCIES) {
176
173
  delete devDependencies[dependencyName];
177
174
  }
178
175
  return devDependencies;
179
176
  }
177
+ /*** Preserve an existing dependencies object or omit a newly empty one. */
180
178
  function normalizedDependencies(manifest, dependencies) {
181
179
  return Object.keys(dependencies).length === 0 && manifest.dependencies === undefined
182
180
  ? {}
183
181
  : { dependencies };
184
182
  }
183
+ /*** Check the canonical development-tool scripts. */
185
184
  function hasStandardScripts(scripts) {
186
185
  return (scripts.knip === undefined &&
187
186
  Object.entries(STANDARD_SCRIPTS).every(([name, command]) => scripts[name] === command));
188
187
  }
188
+ /*** Check Changesets scripts when the repository participates in Changesets. */
189
189
  function hasCurrentChangesetsScripts(scripts, changesetsEnabled) {
190
190
  return (!changesetsEnabled ||
191
191
  Object.entries(changesetsPolicy.packageScripts).every(([name, command]) => scripts[name] === command));
192
192
  }
193
+ /*** Determine whether the repository participates in Changesets synchronization. */
193
194
  function isChangesetsEnabled(scripts, changesetsConfigExists) {
194
195
  return (changesetsConfigExists ||
195
196
  Object.keys(changesetsPolicy.packageScripts).some((scriptName) => scripts[scriptName] !== undefined));
196
197
  }
198
+ /*** Serialize a package manifest using the repository formatting contract. */
197
199
  function serializePackageManifest(manifest) {
198
200
  return `${JSON.stringify(manifest, null, 2)}\n`;
199
201
  }
202
+ /*** Copy a JSON-like value to a mutable record or return an empty record. */
200
203
  function toRecord(value) {
201
204
  return isRecord(value) ? { ...value } : {};
202
205
  }
206
+ /*** Narrow an unknown JSON-like value to a non-array record. */
203
207
  function isRecord(value) {
204
208
  return typeof value === 'object' && value !== null && !Array.isArray(value);
205
209
  }
210
+ /*** Narrow an unknown error to a Node error carrying an error code. */
206
211
  function isNodeError(error) {
207
212
  return error instanceof Error && 'code' in error;
208
213
  }
@@ -49,7 +49,7 @@ jobs:
49
49
  fi
50
50
 
51
51
  - name: Validate Ankhorage repository
52
- run: bunx @ankhorage/ankh doctor validate .
52
+ run: BUN_INSTALL_CACHE_DIR="${RUNNER_TEMP}/doctor-cache" bunx @ankhorage/doctor@__ANKH_DOCTOR_VERSION__ validate .
53
53
 
54
54
  - name: Run lint
55
55
  run: |
@@ -2,6 +2,7 @@ import { access } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { resolveApmReleaseCommandAsync } from '../../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
4
4
  import { bunRuntimePolicy, nodeRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
5
+ import { readCurrentDoctorVersion } from './readCurrentDoctorVersion.js';
5
6
  import { renderRenovateWorkflowAsync } from './renderRenovateWorkflowAsync.js';
6
7
  import { renderWorkflowAsync } from './renderWorkflowAsync.js';
7
8
  export const workflowManagedFiles = [
@@ -27,6 +28,7 @@ export const workflowManagedFiles = [
27
28
  mode: 'create-only',
28
29
  },
29
30
  ];
31
+ /*** Create a managed workflow definition rendered from current shared runtime policies. */
30
32
  function createWorkflowDefinition(relativePath, sourcePath) {
31
33
  const sourceUrl = new URL(sourcePath, import.meta.url);
32
34
  return {
@@ -34,6 +36,7 @@ function createWorkflowDefinition(relativePath, sourcePath) {
34
36
  render: async (targetDirectory) => await renderWorkflowAsync(sourceUrl, {
35
37
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
36
38
  bunVersion: bunRuntimePolicy.version,
39
+ doctorVersion: readCurrentDoctorVersion(),
37
40
  nodeVersion: nodeRuntimePolicy.setupVersion,
38
41
  }),
39
42
  };
@@ -46,6 +49,7 @@ function createRenovateWorkflowDefinition() {
46
49
  render: async (targetDirectory) => await renderRenovateWorkflowAsync(sourceUrl, targetDirectory, {
47
50
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
48
51
  bunVersion: bunRuntimePolicy.version,
52
+ doctorVersion: readCurrentDoctorVersion(),
49
53
  nodeVersion: nodeRuntimePolicy.setupVersion,
50
54
  }),
51
55
  };
@@ -0,0 +1,2 @@
1
+ /*** Read the exact Doctor version owned by the current Devtools package release. */
2
+ export declare function readCurrentDoctorVersion(): string;
@@ -0,0 +1,15 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { isRecord } from '@ankhorage/utility/object';
3
+ const DOCTOR_PACKAGE_NAME = '@ankhorage/doctor';
4
+ /*** Read the exact Doctor version owned by the current Devtools package release. */
5
+ export function readCurrentDoctorVersion() {
6
+ const parsed = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'));
7
+ if (!isRecord(parsed) || !isRecord(parsed.devDependencies)) {
8
+ throw new Error('Devtools package.json must define development dependencies.');
9
+ }
10
+ const version = parsed.devDependencies[DOCTOR_PACKAGE_NAME];
11
+ if (typeof version !== 'string' || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(version)) {
12
+ throw new Error(`Devtools must pin ${DOCTOR_PACKAGE_NAME} to one exact version.`);
13
+ }
14
+ return version;
15
+ }
@@ -1,7 +1,8 @@
1
1
  export interface WorkflowPolicy {
2
2
  readonly bunVersion: string;
3
+ readonly doctorVersion: string;
3
4
  readonly nodeVersion: string;
4
5
  readonly apmReleaseCommand?: string;
5
6
  }
6
- /*** Renders one workflow template with the canonical runtime and Changesets policy. */
7
+ /*** Renders one workflow template with the canonical runtime, Doctor, and Changesets policy. */
7
8
  export declare function renderWorkflowAsync(sourceUrl: URL, policy: WorkflowPolicy): Promise<string>;
@@ -1,6 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { changesetsPolicy } from '../../policy/changesetsPolicy.js';
3
- /*** Renders one workflow template with the canonical runtime and Changesets policy. */
3
+ /*** Renders one workflow template with the canonical runtime, Doctor, and Changesets policy. */
4
4
  export async function renderWorkflowAsync(sourceUrl, policy) {
5
5
  const template = await readFile(sourceUrl, 'utf8');
6
6
  return template
@@ -8,6 +8,7 @@ export async function renderWorkflowAsync(sourceUrl, policy) {
8
8
  .replaceAll(CHANGESETS_PUBLISH_COMMAND_TOKEN, changesetsPolicy.workflowCommands.publish)
9
9
  .replaceAll(CHANGESETS_STATUS_COMMAND_TOKEN, changesetsPolicy.workflowCommands.status)
10
10
  .replaceAll(CHANGESETS_VERSION_COMMAND_TOKEN, changesetsPolicy.workflowCommands.version)
11
+ .replaceAll(DOCTOR_VERSION_TOKEN, policy.doctorVersion)
11
12
  .replaceAll('__ANKH_APM_RELEASE_COMMAND__', policy.apmReleaseCommand ?? './node_modules/.bin/ankhorage-apm-release')
12
13
  .replaceAll(NODE_VERSION_TOKEN, policy.nodeVersion);
13
14
  }
@@ -15,4 +16,5 @@ const BUN_VERSION_TOKEN = '__ANKH_BUN_VERSION__';
15
16
  const CHANGESETS_PUBLISH_COMMAND_TOKEN = '__ANKH_CHANGESETS_PUBLISH_COMMAND__';
16
17
  const CHANGESETS_STATUS_COMMAND_TOKEN = '__ANKH_CHANGESETS_STATUS_COMMAND__';
17
18
  const CHANGESETS_VERSION_COMMAND_TOKEN = '__ANKH_CHANGESETS_VERSION_COMMAND__';
19
+ const DOCTOR_VERSION_TOKEN = '__ANKH_DOCTOR_VERSION__';
18
20
  const NODE_VERSION_TOKEN = '__ANKH_NODE_VERSION__';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.15.2",
3
+ "version": "1.15.3",
4
4
  "description": "Shared tooling, repository automation, runtime policies, and agent standards for Ankhorage TypeScript projects",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/ankhorage/devtools#readme",
@@ -144,7 +144,7 @@
144
144
  "devDependencies": {
145
145
  "@ankhorage/paradox": "^0.1.26",
146
146
  "@ankhorage/ankh": "^0.8.11",
147
- "@ankhorage/doctor": "0.10.8",
147
+ "@ankhorage/doctor": "0.10.14",
148
148
  "@techstark/opencv-js": "^5.0.0-release.1",
149
149
  "@types/bun": "^1.4.1",
150
150
  "@types/node": "^26.4.1",