@ankhorage/devtools 1.21.3 → 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 (40) hide show
  1. package/README.md +43 -458
  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 -39
  11. package/dist/owner/synchronizeRenovateOwnerAsync.d.ts +1 -1
  12. package/dist/owner/synchronizeRenovateOwnerAsync.js +12 -33
  13. package/dist/policy/applyBunRuntimePolicy.d.ts +2 -4
  14. package/dist/policy/applyBunRuntimePolicy.js +5 -3
  15. package/dist/policy/bunRuntimePolicy.d.ts +2 -14
  16. package/dist/policy/bunRuntimePolicy.js +4 -24
  17. package/dist/policy/renderBunPolicyDocumentation.d.ts +1 -2
  18. package/dist/policy/renderBunPolicyDocumentation.js +7 -6
  19. package/dist/policy/resolvePkgvizAuditPolicyAsync.d.ts +1 -1
  20. package/dist/policy/resolvePkgvizAuditPolicyAsync.js +4 -6
  21. package/dist/tools/agents/index.js +4 -0
  22. package/dist/tools/package/index.js +16 -15
  23. package/dist/tools/skills/assets/ankhorage-coding-rules/SKILL.md +13 -7
  24. package/dist/tools/workflows/files/ci.yml +21 -1
  25. package/dist/tools/workflows/files/renovate.yml +1 -1
  26. package/dist/tools/workflows/index.js +6 -5
  27. package/dist/tools/workflows/renderRenovateConfigAsync.d.ts +1 -1
  28. package/dist/tools/workflows/renderRenovateConfigAsync.js +2 -19
  29. package/dist/tools/workflows/renderWorkflowAsync.js +4 -4
  30. package/examples/package/eslint.config.mjs +8 -1
  31. package/examples/package/package.json +13 -0
  32. package/examples/package/src/index.ts +1 -0
  33. package/examples/package/tsconfig.json +9 -0
  34. package/package.json +9 -12
  35. package/dist/policy/changesetsPolicy.d.ts +0 -19
  36. package/dist/policy/changesetsPolicy.js +0 -19
  37. package/dist/types/bunPolicy.d.ts +0 -5
  38. package/dist/types/bunPolicy.js +0 -1
  39. /package/dist/{features/apm-release-validation/adapters/inbound → cli}/runApmReleaseCommandAsync.d.ts +0 -0
  40. /package/dist/{features/structure-descriptor-generation/adapters/inbound → cli}/runStructureGenerationCommandAsync.d.ts +0 -0
@@ -1,19 +1,19 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import { dirname, resolve } from 'node:path';
4
+ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
4
5
  import { resolveApmReleaseCommandAsync } from '../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
5
6
  import { resolveStructureReleaseCommandAsync } from '../features/structure-descriptor-generation/adapters/outbound/resolveStructureReleaseCommandAsync.js';
6
7
  import { applyBunRuntimePolicy } from '../policy/applyBunRuntimePolicy.js';
7
- import { nodeRuntimePolicy } from '../policy/bunRuntimePolicy.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 policy artifacts. */
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
- const policy = await readTargetBunPolicyAsync(target);
16
- const definitions = await createManagedDefinitionsAsync(target, policy);
16
+ const definitions = await createManagedDefinitionsAsync(target);
17
17
  await assertDevtoolsTargetAsync(target);
18
18
  if (operation === 'sync') {
19
19
  await syncDefinitionsAsync(target, definitions);
@@ -34,18 +34,18 @@ async function assertDevtoolsTargetAsync(targetDirectory) {
34
34
  throw new Error('The Renovate owner sync target must be @ankhorage/devtools.');
35
35
  }
36
36
  }
37
- /*** Build the canonical owner-managed artifact definitions for the target repository. */
38
- async function createManagedDefinitionsAsync(targetDirectory, policy) {
37
+ /*** Build the owner-managed artifact definitions from released central policy. */
38
+ async function createManagedDefinitionsAsync(targetDirectory) {
39
39
  const manifest = JSON.parse(await readFile(resolve(targetDirectory, 'package.json'), 'utf8'));
40
40
  if (!isRecord(manifest)) {
41
41
  throw new Error('Devtools package.json must contain a JSON object.');
42
42
  }
43
43
  const readme = await readFile(resolve(targetDirectory, 'README.md'), 'utf8');
44
- const workflowPolicy = await createWorkflowPolicyAsync(targetDirectory, policy);
44
+ const workflowPolicy = await createWorkflowPolicyAsync(targetDirectory);
45
45
  return [
46
46
  {
47
47
  relativePath: 'package.json',
48
- contents: serializePackageManifest(applyBunRuntimePolicy(manifest, policy)),
48
+ contents: serializePackageManifest(applyBunRuntimePolicy(manifest)),
49
49
  },
50
50
  {
51
51
  relativePath: '.github/workflows/ci.yml',
@@ -61,18 +61,18 @@ async function createManagedDefinitionsAsync(targetDirectory, policy) {
61
61
  },
62
62
  {
63
63
  relativePath: 'README.md',
64
- contents: renderBunPolicyDocumentation(readme, policy),
64
+ contents: renderBunPolicyDocumentation(readme),
65
65
  },
66
66
  ];
67
67
  }
68
68
  /*** Build the self-hosted workflow policy used by Devtools owner synchronization. */
69
- async function createWorkflowPolicyAsync(targetDirectory, policy) {
69
+ async function createWorkflowPolicyAsync(targetDirectory) {
70
70
  return {
71
71
  apmReleaseCommand: await resolveApmReleaseCommandAsync(targetDirectory),
72
72
  structureReleaseCommand: await resolveStructureReleaseCommandAsync(targetDirectory),
73
- bunVersion: policy.version,
73
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
74
74
  doctorVersion: readCurrentDoctorVersion(),
75
- nodeVersion: nodeRuntimePolicy.setupVersion,
75
+ nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
76
76
  };
77
77
  }
78
78
  /*** Return owner-managed artifact paths whose current bytes differ from policy output. */
@@ -95,25 +95,6 @@ function isNodeError(error) {
95
95
  function isRecord(value) {
96
96
  return typeof value === 'object' && value !== null && !Array.isArray(value);
97
97
  }
98
- /*** Read the target repository's canonical Bun policy literals. */
99
- async function readTargetBunPolicyAsync(targetDirectory) {
100
- const contents = await readFile(resolve(targetDirectory, 'src/policy/bunRuntimePolicy.ts'), 'utf8');
101
- const matches = [...contents.matchAll(BUN_VERSION_PATTERN)];
102
- const version = matches.length === 1 ? matches[0]?.[1] : undefined;
103
- if (version === undefined) {
104
- throw new Error('Expected exactly one canonical BUN_VERSION literal in the target policy.');
105
- }
106
- const typesMatches = [...contents.matchAll(BUN_TYPES_VERSION_PATTERN)];
107
- const typesVersion = typesMatches.length === 1 ? typesMatches[0]?.[1] : undefined;
108
- if (typesVersion === undefined) {
109
- throw new Error('Expected exactly one canonical BUN_TYPES_VERSION literal in the target policy.');
110
- }
111
- return {
112
- packageManager: `bun@${version}`,
113
- typesRange: `^${typesVersion}`,
114
- version,
115
- };
116
- }
117
98
  /*** Synchronize or validate the target Bun lockfile through Bun itself. */
118
99
  async function runBunLockfileAsync(operation, targetDirectory) {
119
100
  const args = [
@@ -155,5 +136,3 @@ async function syncDefinitionsAsync(targetDirectory, definitions) {
155
136
  await writeFile(targetPath, contents, 'utf8');
156
137
  }
157
138
  }
158
- const BUN_VERSION_PATTERN = /const BUN_VERSION = '(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)';/gu;
159
- const BUN_TYPES_VERSION_PATTERN = /const BUN_TYPES_VERSION = '(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)';/gu;
@@ -1,4 +1,2 @@
1
- export declare function applyBunRuntimePolicy(manifest: Record<string, unknown>, policy: {
2
- readonly packageManager: string;
3
- readonly typesRange: string;
4
- }): Record<string, unknown>;
1
+ /*** Apply the Devtools-owned Bun runtime policy to one package manifest. */
2
+ export declare function applyBunRuntimePolicy(manifest: Record<string, unknown>): Record<string, unknown>;
@@ -1,9 +1,11 @@
1
- export function applyBunRuntimePolicy(manifest, policy) {
1
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from './bunRuntimePolicy.js';
2
+ /*** Apply the Devtools-owned Bun runtime policy to one package manifest. */
3
+ export function applyBunRuntimePolicy(manifest) {
2
4
  const devDependencies = toRecord(manifest.devDependencies);
3
- devDependencies['@types/bun'] = policy.typesRange;
5
+ devDependencies['@types/bun'] = DEVTOOLS_BUN_RUNTIME_POLICY.typesRange;
4
6
  return {
5
7
  ...manifest,
6
- packageManager: policy.packageManager,
8
+ packageManager: DEVTOOLS_BUN_RUNTIME_POLICY.packageManager,
7
9
  devDependencies,
8
10
  };
9
11
  }
@@ -1,17 +1,5 @@
1
- export declare const bunRuntimePolicy: {
1
+ export declare const DEVTOOLS_BUN_RUNTIME_POLICY: {
2
2
  readonly packageManager: "bun@1.4.2";
3
- readonly typesRange: "^1.4.1";
3
+ readonly typesRange: "^1.4.2";
4
4
  readonly version: "1.4.2";
5
5
  };
6
- /**
7
- * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
8
- *
9
- * Bun remains the repository package manager/primary command runtime where configured;
10
- * this policy makes Node-based tooling deterministic instead of inheriting the ambient
11
- * Node version from a CI runner image.
12
- */
13
- export declare const nodeRuntimePolicy: {
14
- readonly engineRange: "24.x";
15
- readonly major: 24;
16
- readonly setupVersion: "24";
17
- };
@@ -1,25 +1,5 @@
1
- /**
2
- * Canonical runtime/tooling policies for Ankhorage repositories.
3
- *
4
- * Import these from `@ankhorage/devtools/policy` when another package needs to inspect
5
- * the managed Bun or Node baseline without defining an independent version authority.
6
- */
7
- const BUN_VERSION = '1.4.2';
8
- const BUN_TYPES_VERSION = '1.4.1';
9
- export const bunRuntimePolicy = {
10
- packageManager: `bun@${BUN_VERSION}`,
11
- typesRange: `^${BUN_TYPES_VERSION}`,
12
- version: BUN_VERSION,
13
- };
14
- /**
15
- * Canonical Node LTS baseline for Node-based Ankhorage tooling and CI execution.
16
- *
17
- * Bun remains the repository package manager/primary command runtime where configured;
18
- * this policy makes Node-based tooling deterministic instead of inheriting the ambient
19
- * Node version from a CI runner image.
20
- */
21
- export const nodeRuntimePolicy = {
22
- engineRange: '24.x',
23
- major: 24,
24
- setupVersion: '24',
1
+ export const DEVTOOLS_BUN_RUNTIME_POLICY = {
2
+ packageManager: 'bun@1.4.2',
3
+ typesRange: '^1.4.2',
4
+ version: '1.4.2',
25
5
  };
@@ -1,3 +1,2 @@
1
- import type { BunPolicy } from '../types/bunPolicy.js';
2
1
  /*** Render the managed Bun policy section while preserving the surrounding guide. */
3
- export declare function renderBunPolicyDocumentation(readme: string, policy: BunPolicy): string;
2
+ export declare function renderBunPolicyDocumentation(readme: string): string;
@@ -1,5 +1,6 @@
1
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from './bunRuntimePolicy.js';
1
2
  /*** Render the managed Bun policy section while preserving the surrounding guide. */
2
- export function renderBunPolicyDocumentation(readme, policy) {
3
+ export function renderBunPolicyDocumentation(readme) {
3
4
  const startIndex = readme.indexOf(README_POLICY_START);
4
5
  const endIndex = readme.indexOf(README_POLICY_END);
5
6
  if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
@@ -9,15 +10,15 @@ export function renderBunPolicyDocumentation(readme, policy) {
9
10
  readme.includes(README_POLICY_END, endIndex + README_POLICY_END.length)) {
10
11
  throw new Error('README.md must contain exactly one Devtools Bun policy marker pair.');
11
12
  }
12
- const replacement = `${README_POLICY_START}\n\n${renderReadmePolicy(policy)}\n\n${README_POLICY_END}`;
13
+ const replacement = `${README_POLICY_START}\n\n${renderReadmePolicy()}\n\n${README_POLICY_END}`;
13
14
  return `${readme.slice(0, startIndex)}${replacement}${readme.slice(endIndex + README_POLICY_END.length)}`;
14
15
  }
15
16
  /*** Format canonical runtime and type-package versions for the guide. */
16
- function renderReadmePolicy(policy) {
17
+ function renderReadmePolicy() {
17
18
  return `\`\`\`text
18
- Bun runtime ${policy.version}
19
- packageManager ${policy.packageManager}
20
- @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}
21
22
  \`\`\``;
22
23
  }
23
24
  const README_POLICY_END = '<!-- devtools-bun-policy:end -->';
@@ -1,4 +1,4 @@
1
- /*** Resolve the centrally pinned PKGViz CI audit for repositories with analyzable source. */
1
+ /*** Resolve the central PKGViz CI audit for repositories with analyzable source. */
2
2
  export declare function resolvePkgvizAuditPolicyAsync(targetDirectory: string): Promise<PkgvizAuditPolicy | undefined>;
3
3
  interface PkgvizAuditPolicy {
4
4
  readonly artifactName: string;
@@ -1,14 +1,12 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- /*** Resolve the centrally pinned PKGViz CI audit for repositories with analyzable source. */
3
+ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
4
+ /*** Resolve the central PKGViz CI audit for repositories with analyzable source. */
4
5
  export async function resolvePkgvizAuditPolicyAsync(targetDirectory) {
5
6
  if (!(await hasSourceDirectoryAsync(targetDirectory)))
6
7
  return undefined;
7
- return {
8
- artifactName: 'pkgviz-audit',
9
- artifactPath: 'pkgviz-audit.json',
10
- command: 'bunx pkgviz@0.8.1 --out pkgviz-audit.json --rule cyclic-dependencies=block',
11
- };
8
+ const { artifactName, artifactPath, command } = REPOSITORY_POLICY.pkgvizAudit;
9
+ return { artifactName, artifactPath, command };
12
10
  }
13
11
  /*** Detect whether the managed repository has a source tree that PKGViz can inspect. */
14
12
  async function hasSourceDirectoryAsync(targetDirectory) {
@@ -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 is shared by 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
@@ -19,9 +20,9 @@
19
20
  import { existsSync, readFileSync } from 'node:fs';
20
21
  import { readFile, writeFile } from 'node:fs/promises';
21
22
  import { resolve } from 'node:path';
23
+ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
22
24
  import { applyBunRuntimePolicy } from '../../policy/applyBunRuntimePolicy.js';
23
- import { bunRuntimePolicy } from '../../policy/bunRuntimePolicy.js';
24
- import { changesetsPolicy } from '../../policy/changesetsPolicy.js';
25
+ import { DEVTOOLS_BUN_RUNTIME_POLICY } from '../../policy/bunRuntimePolicy.js';
25
26
  const PACKAGE_PATH = 'package.json';
26
27
  const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
27
28
  const BUN_TYPES_PACKAGE_NAME = '@types/bun';
@@ -85,26 +86,26 @@ export async function syncPackageManifest(targetDirectory, devtoolsVersion, opti
85
86
  /*** Apply the Devtools-owned package scripts, dependencies, and Bun runtime policy. */
86
87
  export function applyManagedPackageContract(manifest, devtoolsVersion, changesetsConfigExists = false) {
87
88
  if (manifest.name === DEVTOOLS_PACKAGE_NAME) {
88
- return applyBunRuntimePolicy(manifest, bunRuntimePolicy);
89
+ return applyBunRuntimePolicy(manifest);
89
90
  }
90
91
  const scripts = { ...toRecord(manifest.scripts) };
91
92
  const changesetsEnabled = isChangesetsEnabled(scripts, changesetsConfigExists);
92
93
  delete scripts.knip;
93
94
  Object.assign(scripts, STANDARD_SCRIPTS);
94
95
  if (changesetsEnabled) {
95
- Object.assign(scripts, changesetsPolicy.packageScripts);
96
+ Object.assign(scripts, REPOSITORY_POLICY.changesets.packageScripts);
96
97
  }
97
98
  const devDependencies = removeOwnedDependencies(toRecord(manifest.devDependencies));
98
99
  const dependencies = toRecord(manifest.dependencies);
99
- delete dependencies[changesetsPolicy.packageName];
100
- delete devDependencies[changesetsPolicy.packageName];
100
+ delete dependencies[REPOSITORY_POLICY.changesets.packageName];
101
+ delete devDependencies[REPOSITORY_POLICY.changesets.packageName];
101
102
  applyDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion);
102
103
  return applyBunRuntimePolicy({
103
104
  ...manifest,
104
105
  ...normalizedDependencies(manifest, dependencies),
105
106
  scripts,
106
107
  devDependencies,
107
- }, bunRuntimePolicy);
108
+ });
108
109
  }
109
110
  /*** Check whether the Devtools-owned package manifest fields match current policy. */
110
111
  export function isManagedPackageContractCurrent(manifest, devtoolsVersion, changesetsConfigExists = false) {
@@ -120,8 +121,8 @@ export function isManagedPackageContractCurrent(manifest, devtoolsVersion, chang
120
121
  const changesetsEnabled = isChangesetsEnabled(scripts, changesetsConfigExists);
121
122
  return (hasStandardScripts(scripts) &&
122
123
  DEVTOOLS_OWNED_DEV_DEPENDENCIES.every((name) => devDependencies[name] === undefined) &&
123
- dependencies[changesetsPolicy.packageName] === undefined &&
124
- devDependencies[changesetsPolicy.packageName] === undefined &&
124
+ dependencies[REPOSITORY_POLICY.changesets.packageName] === undefined &&
125
+ devDependencies[REPOSITORY_POLICY.changesets.packageName] === undefined &&
125
126
  hasCurrentChangesetsScripts(scripts, changesetsEnabled) &&
126
127
  hasCurrentDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion));
127
128
  }
@@ -154,8 +155,8 @@ async function readPackageManifest(targetDirectory) {
154
155
  /*** Check the Bun package manager and Bun type dependency contract. */
155
156
  function hasCurrentBunRuntimePolicy(manifest) {
156
157
  const devDependencies = toRecord(manifest.devDependencies);
157
- return (manifest.packageManager === bunRuntimePolicy.packageManager &&
158
- devDependencies[BUN_TYPES_PACKAGE_NAME] === bunRuntimePolicy.typesRange);
158
+ return (manifest.packageManager === DEVTOOLS_BUN_RUNTIME_POLICY.packageManager &&
159
+ devDependencies[BUN_TYPES_PACKAGE_NAME] === DEVTOOLS_BUN_RUNTIME_POLICY.typesRange);
159
160
  }
160
161
  /*** Place Devtools in development dependencies for every consuming repository. */
161
162
  function applyDevtoolsDependencyPlacement(dependencies, devDependencies, devtoolsVersion) {
@@ -188,12 +189,12 @@ function hasStandardScripts(scripts) {
188
189
  /*** Check Changesets scripts when the repository participates in Changesets. */
189
190
  function hasCurrentChangesetsScripts(scripts, changesetsEnabled) {
190
191
  return (!changesetsEnabled ||
191
- Object.entries(changesetsPolicy.packageScripts).every(([name, command]) => scripts[name] === command));
192
+ Object.entries(REPOSITORY_POLICY.changesets.packageScripts).every(([name, command]) => scripts[name] === command));
192
193
  }
193
194
  /*** Determine whether the repository participates in Changesets synchronization. */
194
195
  function isChangesetsEnabled(scripts, changesetsConfigExists) {
195
196
  return (changesetsConfigExists ||
196
- Object.keys(changesetsPolicy.packageScripts).some((scriptName) => scripts[scriptName] !== undefined));
197
+ Object.keys(REPOSITORY_POLICY.changesets.packageScripts).some((scriptName) => scripts[scriptName] !== undefined));
197
198
  }
198
199
  /*** Serialize a package manifest using the repository formatting contract. */
199
200
  function serializePackageManifest(manifest) {
@@ -52,16 +52,22 @@ exceptions or replaced by generic preferences from this skill.
52
52
  - Use ordinary inline comments when they explain non-obvious intent, invariants, constraints, or the
53
53
  reason behind a decision. Do not narrate self-explanatory code or duplicate what names and types
54
54
  already express.
55
- - Paradox documentation metadata belongs only inside `/*** ... */` comments. Use only supported
56
- Paradox tags: `@readme`, `@config`, `@example`, and `@usage`. Do not use JSDoc-only tags such as
57
- `@param` or `@returns` as Paradox metadata.
55
+ - Paradox documentation metadata belongs only inside `/*** ... */` comments. The canonical tag
56
+ vocabulary is owned by `@ankhorage/policy`: `@readme`, `@usage`, `@config`, `@title`,
57
+ `@see`, and `@security`. Unsupported tag-shaped lines are invalid; `@example` does not exist.
58
+ Do not use JSDoc-only tags such as `@param` or `@returns` as Paradox metadata.
59
+ - Paradox comments contain documentation prose and metadata only. Fenced or indented code blocks are
60
+ invalid; source examples come from real code. Inline code spans remain valid prose.
58
61
  - Add a supported Paradox tag only when it changes or usefully enriches generated documentation;
59
62
  plain function descriptions do not need tags.
60
63
  - README usage documentation must come from a real repository-root `examples/<example>/...` source
61
- file. Put `@usage` in that example file's leading `/*** ... */` comment so Paradox promotes the
62
- runnable example into the README Usage section. Do not create dedicated `readme-usage`,
63
- `usage-readme`, `readmeUsage`, or equivalent source modules whose only purpose is feeding README
64
- usage text.
64
+ file. A package that opts into `@usage` must have exactly one example combining `@usage`,
65
+ `@readme`, and `@title`; additional `@usage` examples remain full-doc-only. Do not create
66
+ dedicated `readme-usage`, `usage-readme`, `readmeUsage`, or equivalent source modules whose
67
+ only purpose is feeding README usage text.
68
+ - Configuration documentation is optional until a package opts in. Once it does, the canonical schema
69
+ is `src/types/config.ts` with exactly one root type/interface carrying `@config`, `@readme`,
70
+ and `@title`.
65
71
  - Update documentation sources in the pull request, including repository-owned manual documentation
66
72
  outside the configured generated output. Never hand-edit generated README or Paradox artifacts.
67
73
  - Generated documentation is release-owned. Ordinary feature pull requests must not regenerate or
@@ -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:
@@ -1,8 +1,9 @@
1
1
  import { access } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
3
4
  import { resolveApmReleaseCommandAsync } from '../../features/apm-release-validation/adapters/outbound/resolveApmReleaseCommandAsync.js';
4
5
  import { resolveStructureReleaseCommandAsync } from '../../features/structure-descriptor-generation/adapters/outbound/resolveStructureReleaseCommandAsync.js';
5
- import { bunRuntimePolicy, nodeRuntimePolicy } from '../../policy/bunRuntimePolicy.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,9 +39,9 @@ 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: bunRuntimePolicy.version,
42
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
42
43
  doctorVersion: readCurrentDoctorVersion(),
43
- nodeVersion: nodeRuntimePolicy.setupVersion,
44
+ nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
44
45
  pkgvizAudit: await resolvePkgvizAuditPolicyAsync(targetDirectory),
45
46
  }),
46
47
  };
@@ -53,9 +54,9 @@ 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: bunRuntimePolicy.version,
57
+ bunVersion: DEVTOOLS_BUN_RUNTIME_POLICY.version,
57
58
  doctorVersion: readCurrentDoctorVersion(),
58
- nodeVersion: nodeRuntimePolicy.setupVersion,
59
+ nodeVersion: REPOSITORY_POLICY.runtime.node.setupVersion,
59
60
  }),
60
61
  };
61
62
  }
@@ -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
  }
@@ -1,14 +1,14 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { changesetsPolicy } from '../../policy/changesetsPolicy.js';
2
+ import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
3
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
  const rendered = template
7
7
  .replaceAll(BUN_VERSION_TOKEN, policy.bunVersion)
8
8
  .replaceAll(PKGVIZ_AUDIT_STEPS_TOKEN, renderPkgvizAuditSteps(policy))
9
- .replaceAll(CHANGESETS_PUBLISH_COMMAND_TOKEN, changesetsPolicy.workflowCommands.publish)
10
- .replaceAll(CHANGESETS_STATUS_COMMAND_TOKEN, changesetsPolicy.workflowCommands.status)
11
- .replaceAll(CHANGESETS_VERSION_COMMAND_TOKEN, changesetsPolicy.workflowCommands.version)
9
+ .replaceAll(CHANGESETS_PUBLISH_COMMAND_TOKEN, REPOSITORY_POLICY.changesets.workflowCommands.publish)
10
+ .replaceAll(CHANGESETS_STATUS_COMMAND_TOKEN, REPOSITORY_POLICY.changesets.workflowCommands.status)
11
+ .replaceAll(CHANGESETS_VERSION_COMMAND_TOKEN, REPOSITORY_POLICY.changesets.workflowCommands.version)
12
12
  .replaceAll(DOCTOR_VERSION_TOKEN, policy.doctorVersion)
13
13
  .replaceAll('__ANKH_APM_RELEASE_COMMAND__', policy.apmReleaseCommand ?? './node_modules/.bin/ankhorage-apm-release')
14
14
  .replaceAll('__ANKH_STRUCTURE_RELEASE_COMMAND__', policy.structureReleaseCommand ?? './node_modules/.bin/ankhorage-structure')
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.21.3",
4
- "description": "Shared tooling, repository automation, runtime policies, and agent standards for Ankhorage TypeScript projects",
3
+ "version": "2.0.1",
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",
7
7
  "bugs": {
@@ -82,10 +82,6 @@
82
82
  "types": "./dist/tools/knip/index.d.ts",
83
83
  "import": "./dist/tools/knip/index.js"
84
84
  },
85
- "./policy": {
86
- "types": "./dist/policy/bunRuntimePolicy.d.ts",
87
- "import": "./dist/policy/bunRuntimePolicy.js"
88
- },
89
85
  "./prettier": {
90
86
  "import": "./dist/tools/prettier/index.cjs",
91
87
  "require": "./dist/tools/prettier/index.cjs",
@@ -145,16 +141,17 @@
145
141
  "knip": "^6.38.0",
146
142
  "prettier": "^3.9.9",
147
143
  "typescript-eslint": "^8.70.1",
148
- "@ankhorage/contracts": "^22.8.1",
149
- "typescript": "~6.0.3"
144
+ "@ankhorage/contracts": "^22.8.2",
145
+ "typescript": "~6.0.3",
146
+ "@ankhorage/policy": "^0.5.2"
150
147
  },
151
148
  "devDependencies": {
152
- "@ankhorage/paradox": "^0.1.26",
149
+ "@ankhorage/paradox": "^0.2.6",
153
150
  "@ankhorage/ankh": "^0.10.4",
154
- "@ankhorage/doctor": "0.11.0",
151
+ "@ankhorage/doctor": "0.11.8",
155
152
  "@techstark/opencv-js": "^5.0.0-release.1",
156
- "@types/bun": "^1.4.1",
157
- "@types/node": "^26.6.2",
153
+ "@types/bun": "^1.4.2",
154
+ "@types/node": "^26.6.3",
158
155
  "sharp": "^0.35.4"
159
156
  },
160
157
  "packageManager": "bun@1.4.2"
@@ -1,19 +0,0 @@
1
- export declare const changesetsPolicy: {
2
- readonly packageName: "@changesets/cli";
3
- readonly binaryName: "ankhorage-changeset";
4
- readonly packageScripts: {
5
- readonly changeset: "ankhorage-changeset";
6
- readonly 'changeset:status': "ankhorage-changeset status --since=origin/main";
7
- readonly 'version-packages': "ankhorage-changeset version";
8
- };
9
- readonly ownerPackageScripts: {
10
- readonly changeset: "bun src/cli/bin/changeset.ts";
11
- readonly 'changeset:status': "bun src/cli/bin/changeset.ts status --since=origin/main";
12
- readonly 'version-packages': "bun src/cli/bin/changeset.ts version";
13
- };
14
- readonly workflowCommands: {
15
- readonly status: "bun run changeset:status";
16
- readonly version: "bun run version-packages";
17
- readonly publish: "bun run changeset -- publish";
18
- };
19
- };