@ankhorage/devtools 1.15.0 → 1.15.2

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.14.8](././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.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)
7
7
 
8
8
  Shared tooling, repository automation, runtime policies, and agent standards for Ankhorage TypeScript projects
9
9
 
@@ -440,6 +440,7 @@ Never declare an unpublished owner version. Project migration recovery is owned
440
440
  package's declared handlers; passing this release gate does not execute migrations against user
441
441
  projects, deploy production services, or prove compatibility with shipped native binaries.
442
442
 
443
+
443
444
  ## CLI
444
445
 
445
446
  Run and synchronize the shared development toolchain through the Ankh CLI.
@@ -1,5 +1,6 @@
1
1
  import { agentsManagedFiles } from '../tools/agents/index.js';
2
2
  import { eslintManagedFiles } from '../tools/eslint/managed.js';
3
+ import { gitignoreManagedFiles } from '../tools/gitignore/index.js';
3
4
  import { knipManagedFiles } from '../tools/knip/managed.js';
4
5
  import { inspectPackageManifest, readCurrentDevtoolsVersion, syncPackageManifest, } from '../tools/package/index.js';
5
6
  import { planBunDependencySync, syncBunDependencies, } from '../tools/package/syncBunDependencies.js';
@@ -90,7 +91,7 @@ function getManagedFiles(scope) {
90
91
  workflows: workflowManagedFiles,
91
92
  };
92
93
  if (scope === 'all') {
93
- return Object.values(definitionsByScope).flat();
94
+ return [...Object.values(definitionsByScope).flat(), ...gitignoreManagedFiles];
94
95
  }
95
96
  if (scope === 'package') {
96
97
  return [];
@@ -11,6 +11,13 @@ async function renderAgentsFile(targetDirectory) {
11
11
  const manifest = await readPackageManifest(targetDirectory);
12
12
  const packageName = readNonEmptyString(manifest.name) ?? 'Package name not declared';
13
13
  const description = readNonEmptyString(manifest.description) ?? 'No package description is declared.';
14
+ const sections = [
15
+ renderCurrentArchitectureInstructions(),
16
+ renderRequiredRepositoryInstructions(),
17
+ renderDocumentationInstructions(),
18
+ renderPullRequestInstructions(),
19
+ renderSkillScriptInstructions(),
20
+ ].join('');
14
21
  return `# AGENTS.md
15
22
 
16
23
  <!-- This file is managed by @ankhorage/devtools. -->
@@ -21,7 +28,11 @@ Package: \`${escapeInlineCode(packageName)}\`
21
28
 
22
29
  ${description}
23
30
 
24
- ## Current architecture only
31
+ ${sections}`;
32
+ }
33
+ /*** Render the current-architecture policy shared by every managed repository. */
34
+ function renderCurrentArchitectureInstructions() {
35
+ return `## Current architecture only
25
36
 
26
37
  Only the current Ankhorage architecture is valid. Do not add or retain deprecated APIs,
27
38
  compatibility aliases, shims, dual old/new paths, historical-state fallbacks, or migrations whose
@@ -34,27 +45,47 @@ published public APIs and declared dependencies, never sibling source files.
34
45
  Current-runtime error handling and canonical database or infrastructure migrations remain valid
35
46
  when they support states that the current architecture can intentionally produce.
36
47
 
37
- ## Required repository instructions
48
+ `;
49
+ }
50
+ /*** Render contextual repository skill-selection requirements. */
51
+ function renderRequiredRepositoryInstructions() {
52
+ return `## Required repository instructions
38
53
 
39
54
  Before changing any file, read this \`AGENTS.md\` completely and inspect \`.agents/skills/\`.
40
- For every change, load and follow both baseline repository skills before making changes:
55
+ Treat skill selection as a mandatory precondition to editing, then follow every selected skill
56
+ through validation and delivery.
57
+
58
+ - Load \`.agents/skills/ankhorage-coding-rules/SKILL.md\` for implementation, refactoring, testing,
59
+ review, or pull-request delivery work.
60
+ - Load \`.agents/skills/ankhorage-project-structure/SKILL.md\` when the task changes or reviews
61
+ directory ownership, package boundaries, public entrypoints, cross-repository ownership, type or
62
+ utility placement, or source architecture.
63
+ - Load every additional repository-local skill whose description or requirements match the task,
64
+ including skills required by any selected skill. These rules define mandatory minimums, not an
65
+ allow-list; do not skip a useful relevant skill merely because it is not named here.
66
+
67
+ Do not load unrelated skills merely because they are installed. If the task scope expands, inspect
68
+ \`.agents/skills/\` again and load the newly relevant skills before continuing. Do not substitute
69
+ remembered, globally installed, or generic guidance for the repository-local versions.
41
70
 
42
- 1. \`.agents/skills/ankhorage-coding-rules/SKILL.md\`
43
- 2. \`.agents/skills/ankhorage-project-structure/SKILL.md\`
44
-
45
- Load and follow every additional repository-local skill relevant to the task, including skills
46
- required by either baseline skill. Continue to follow these instructions and skills through
47
- validation and delivery; do not substitute remembered, globally installed, or generic guidance for
48
- the repository-local versions.
49
-
50
- ## Documentation
71
+ `;
72
+ }
73
+ /*** Render the release-owned generated documentation policy. */
74
+ function renderDocumentationInstructions() {
75
+ return `## Documentation
51
76
 
52
- \`README.md\` and the \`paradox/\` documentation tree are generated by \`@ankhorage/paradox\`.
53
- Never edit generated documentation directly. Update the owning Paradox \`/*** ... */\` comments in
54
- \`src\`, including \`@readme\` tags where the content belongs in the README, then run \`bun docs\`
55
- and commit the regenerated artifacts.
77
+ \`README.md\` and the configured Paradox output are generated release artifacts. Never edit them
78
+ manually, and do not regenerate or commit them in ordinary feature pull requests. Update the owning
79
+ Paradox \`/*** ... */\` comments in \`src\` and leading \`@usage\` comments in real
80
+ \`examples/<example>/...\` source files, plus any repository-owned manual documentation outside the
81
+ generated output. The managed release workflow runs \`bun run docs\` after the package version bump
82
+ and commits the regenerated artifacts in the release commit.
56
83
 
57
- ## Pull requests
84
+ `;
85
+ }
86
+ /*** Render canonical pull-request validation commands. */
87
+ function renderPullRequestInstructions() {
88
+ return `## Pull requests
58
89
 
59
90
  Before creating a pull request, run all of these commands in this order and resolve every failure:
60
91
 
@@ -67,13 +98,18 @@ bun run changeset
67
98
  bun run format
68
99
  \`\`\`
69
100
 
70
- ## Skill scripts
101
+ `;
102
+ }
103
+ /*** Render the repository rule for executable Agent Skill scripts. */
104
+ function renderSkillScriptInstructions() {
105
+ return `## Skill scripts
71
106
 
72
107
  Scripts inside an Agent Skill must always be TypeScript files with the \`.ts\` extension.
73
108
  JavaScript skill scripts using \`.js\`, \`.mjs\`, or \`.cjs\` are not allowed. Run TypeScript
74
109
  skill scripts with Bun.
75
110
  `;
76
111
  }
112
+ /*** Read the target package manifest used to personalize managed repository instructions. */
77
113
  async function readPackageManifest(targetDirectory) {
78
114
  try {
79
115
  const contents = await readFile(resolve(targetDirectory, 'package.json'), 'utf8');
@@ -90,15 +126,19 @@ async function readPackageManifest(targetDirectory) {
90
126
  throw error;
91
127
  }
92
128
  }
129
+ /*** Escape inline-code delimiters before interpolating repository metadata into Markdown. */
93
130
  function escapeInlineCode(value) {
94
131
  return value.replaceAll('`', '\\`');
95
132
  }
133
+ /*** Check whether an unknown failure carries a Node.js error code. */
96
134
  function isNodeError(error) {
97
135
  return error instanceof Error && 'code' in error;
98
136
  }
137
+ /*** Check whether an unknown value is a plain record-shaped object. */
99
138
  function isRecord(value) {
100
139
  return typeof value === 'object' && value !== null && !Array.isArray(value);
101
140
  }
141
+ /*** Read and trim a non-empty string value from package metadata. */
102
142
  function readNonEmptyString(value) {
103
143
  return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
104
144
  }
@@ -0,0 +1,10 @@
1
+ export declare const gitignoreManagedFiles: readonly [{
2
+ readonly relativePath: ".gitignore";
3
+ readonly render: typeof renderGitignore;
4
+ readonly isApplicable: typeof isGitignorePresent;
5
+ }];
6
+ /*** Preserve repository-owned ignore rules while keeping documentation directories trackable. */
7
+ declare function renderGitignore(targetDirectory: string): Promise<string>;
8
+ /*** Report whether a repository already owns a `.gitignore` file that can be normalized. */
9
+ declare function isGitignorePresent(targetDirectory: string): Promise<boolean>;
10
+ export {};
@@ -0,0 +1,43 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ const TRACKED_DOCUMENTATION_PATTERNS = new Set([
4
+ 'docs',
5
+ 'docs/',
6
+ '/docs',
7
+ '/docs/',
8
+ 'paradox',
9
+ 'paradox/',
10
+ '/paradox',
11
+ '/paradox/',
12
+ ]);
13
+ export const gitignoreManagedFiles = [
14
+ {
15
+ relativePath: '.gitignore',
16
+ render: renderGitignore,
17
+ isApplicable: isGitignorePresent,
18
+ },
19
+ ];
20
+ /*** Preserve repository-owned ignore rules while keeping documentation directories trackable. */
21
+ async function renderGitignore(targetDirectory) {
22
+ const contents = await readFile(resolve(targetDirectory, '.gitignore'), 'utf8');
23
+ return contents
24
+ .split('\n')
25
+ .filter((line) => !TRACKED_DOCUMENTATION_PATTERNS.has(line.trim()))
26
+ .join('\n');
27
+ }
28
+ /*** Report whether a repository already owns a `.gitignore` file that can be normalized. */
29
+ async function isGitignorePresent(targetDirectory) {
30
+ try {
31
+ await access(resolve(targetDirectory, '.gitignore'));
32
+ return true;
33
+ }
34
+ catch (error) {
35
+ if (isNodeError(error) && error.code === 'ENOENT')
36
+ return false;
37
+ throw error;
38
+ }
39
+ }
40
+ /*** Check whether an unknown failure carries a Node.js error code. */
41
+ function isNodeError(error) {
42
+ return error instanceof Error && 'code' in error;
43
+ }
@@ -1,3 +1,4 @@
1
+ /*** Defines the Prettier files and ignore policy managed for consuming repositories. */
1
2
  export declare const prettierManagedFiles: readonly [{
2
3
  readonly relativePath: "prettier.local.config.js";
3
4
  readonly render: typeof renderInitialLocalConfig;
@@ -5,7 +6,14 @@ export declare const prettierManagedFiles: readonly [{
5
6
  }, {
6
7
  readonly relativePath: ".prettierrc.js";
7
8
  readonly render: typeof renderPrettierConfig;
9
+ }, {
10
+ readonly relativePath: ".prettierignore";
11
+ readonly render: typeof renderPrettierIgnore;
8
12
  }];
13
+ /*** Renders the shared Prettier wrapper for the target package module type. */
9
14
  declare function renderPrettierConfig(targetDirectory: string): Promise<string>;
15
+ /*** Preserves an existing repository Prettier config as its initial local extension. */
10
16
  declare function renderInitialLocalConfig(targetDirectory: string): Promise<string>;
17
+ /*** Preserves repository-owned ignore entries while appending required generated-artifact ignores. */
18
+ declare function renderPrettierIgnore(targetDirectory: string): Promise<string>;
11
19
  export {};
@@ -26,6 +26,8 @@ const LEGACY_ESM_CONFIG = `export { default } from '@ankhorage/devtools/prettier
26
26
  `;
27
27
  const LEGACY_COMMONJS_CONFIG = `module.exports = require('@ankhorage/devtools/prettier');
28
28
  `;
29
+ const REQUIRED_PRETTIER_IGNORE_ENTRIES = ['node_modules', 'dist', 'README.md', 'paradox/'];
30
+ /*** Defines the Prettier files and ignore policy managed for consuming repositories. */
29
31
  export const prettierManagedFiles = [
30
32
  {
31
33
  relativePath: 'prettier.local.config.js',
@@ -36,10 +38,16 @@ export const prettierManagedFiles = [
36
38
  relativePath: '.prettierrc.js',
37
39
  render: renderPrettierConfig,
38
40
  },
41
+ {
42
+ relativePath: '.prettierignore',
43
+ render: renderPrettierIgnore,
44
+ },
39
45
  ];
46
+ /*** Renders the shared Prettier wrapper for the target package module type. */
40
47
  async function renderPrettierConfig(targetDirectory) {
41
48
  return (await readPackageType(targetDirectory)) === 'module' ? ESM_CONFIG : COMMONJS_CONFIG;
42
49
  }
50
+ /*** Preserves an existing repository Prettier config as its initial local extension. */
43
51
  async function renderInitialLocalConfig(targetDirectory) {
44
52
  const isModule = (await readPackageType(targetDirectory)) === 'module';
45
53
  try {
@@ -53,6 +61,25 @@ async function renderInitialLocalConfig(targetDirectory) {
53
61
  }
54
62
  return isModule ? EMPTY_ESM_LOCAL_CONFIG : EMPTY_COMMONJS_LOCAL_CONFIG;
55
63
  }
64
+ /*** Preserves repository-owned ignore entries while appending required generated-artifact ignores. */
65
+ async function renderPrettierIgnore(targetDirectory) {
66
+ const existing = await readFile(resolve(targetDirectory, '.prettierignore'), 'utf8').catch((error) => {
67
+ if (isNodeError(error) && error.code === 'ENOENT')
68
+ return '';
69
+ throw error;
70
+ });
71
+ const existingEntries = new Set(existing
72
+ .split(/\r?\n/u)
73
+ .map((line) => line.trim())
74
+ .filter((line) => line !== '' && !line.startsWith('#')));
75
+ const missingEntries = REQUIRED_PRETTIER_IGNORE_ENTRIES.filter((entry) => !existingEntries.has(entry));
76
+ if (missingEntries.length === 0) {
77
+ return existing.endsWith('\n') ? existing : `${existing}\n`;
78
+ }
79
+ const prefix = existing === '' ? '' : existing.endsWith('\n') ? existing : `${existing}\n`;
80
+ return `${prefix}${missingEntries.join('\n')}\n`;
81
+ }
82
+ /*** Reads whether the target package uses ESM or CommonJS package semantics. */
56
83
  async function readPackageType(targetDirectory) {
57
84
  try {
58
85
  const contents = await readFile(resolve(targetDirectory, 'package.json'), 'utf8');
@@ -66,12 +93,15 @@ async function readPackageType(targetDirectory) {
66
93
  throw error;
67
94
  }
68
95
  }
96
+ /*** Narrows an unknown JSON value to a non-array record. */
69
97
  function isRecord(value) {
70
98
  return typeof value === 'object' && value !== null && !Array.isArray(value);
71
99
  }
100
+ /*** Identifies Prettier wrappers that contain no repository-owned configuration. */
72
101
  function isSharedOnlyConfig(value) {
73
102
  return [ESM_CONFIG, COMMONJS_CONFIG, LEGACY_ESM_CONFIG, LEGACY_COMMONJS_CONFIG].includes(value);
74
103
  }
104
+ /*** Narrows an unknown error to a Node error carrying an error code. */
75
105
  function isNodeError(error) {
76
106
  return error instanceof Error && 'code' in error;
77
107
  }
@@ -45,6 +45,31 @@ exceptions or replaced by generic preferences from this skill.
45
45
  - Keep one canonical implementation. Do not add compatibility aliases, dual paths, historical-state
46
46
  fallbacks, or sibling-source imports.
47
47
 
48
+ ## Comments and Paradox documentation
49
+
50
+ - Give every named production function under `src` a concise Paradox `/*** ... */` comment,
51
+ including exported functions and internal named helpers. Inline callbacks are exempt.
52
+ - Use ordinary inline comments when they explain non-obvious intent, invariants, constraints, or the
53
+ reason behind a decision. Do not narrate self-explanatory code or duplicate what names and types
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.
58
+ - Add a supported Paradox tag only when it changes or usefully enriches generated documentation;
59
+ plain function descriptions do not need tags.
60
+ - 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.
65
+ - Update documentation sources in the pull request, including repository-owned manual documentation
66
+ outside the configured generated output. Never hand-edit generated README or Paradox artifacts.
67
+ - Generated documentation is release-owned. Ordinary feature pull requests must not regenerate or
68
+ commit `README.md` or the configured Paradox output. The managed release workflow runs
69
+ `bun run docs` after the package version bump and commits the result in the release commit.
70
+ - Run `bun run docs` locally only when the task explicitly changes or validates Paradox generation;
71
+ do not commit that generated diff from an ordinary feature pull request.
72
+
48
73
  ## Testing
49
74
 
50
75
  - Test observable behavior at the owning boundary. Keep tests deterministic and independent of
@@ -60,15 +85,6 @@ exceptions or replaced by generic preferences from this skill.
60
85
  full reruns without a substantive code or configuration change, stop cycling, inspect logs and
61
86
  artifacts, and report the blocker.
62
87
 
63
- ## Paradox documentation
64
-
65
- - Give every named production function under `src` a concise Paradox `/*** ... */` description,
66
- including internal helpers. Inline callbacks are exempt.
67
- - Add `@param`, `@returns`, `@example`, or `@readme` to public APIs only when the tag adds useful
68
- information.
69
- - Update documentation sources and regenerate tracked Paradox output. Never hand-edit generated
70
- README or Paradox artifacts.
71
-
72
88
  ## Public API and release quality
73
89
 
74
90
  - Export public symbols intentionally from canonical entrypoints and exercise those exports in
@@ -77,7 +93,8 @@ exceptions or replaced by generic preferences from this skill.
77
93
  public API changes.
78
94
  - Justify new dependencies and keep generated artifacts, lockfiles, and package metadata aligned
79
95
  with the repository's owning workflows.
80
- - Review the final diff, then run the applicable build, lint, test, typecheck, Knip, format, docs,
81
- changeset-status, and packaging checks.
96
+ - Review the final diff, then run the applicable build, lint, test, typecheck, Knip, format,
97
+ changeset-status, and packaging checks. Run documentation generation only when explicitly needed
98
+ to validate documentation tooling behavior.
82
99
  - Report the exact commands run and any omissions or failures. Do not imply a fully green handoff
83
100
  when a required check did not run or pass.
@@ -78,6 +78,14 @@ jobs:
78
78
  fi
79
79
  after_version="$(node -p "require('./package.json').version")"
80
80
 
81
+ if [ "$before_version" != "$after_version" ]; then
82
+ if node -e "const p=require('./package.json'); process.exit(p.scripts?.docs ? 0 : 1)"; then
83
+ bun run docs
84
+ else
85
+ echo "No docs script found; skipping."
86
+ fi
87
+ fi
88
+
81
89
  if git diff --quiet; then
82
90
  echo "versioned=false" >> "$GITHUB_OUTPUT"
83
91
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/devtools",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
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",
@@ -49,14 +49,15 @@
49
49
  "renovateSyncProtocol": 1
50
50
  },
51
51
  "keywords": [
52
- "typescript",
52
+ "ankhorage",
53
+ "developer-tools",
53
54
  "eslint",
54
- "prettier",
55
- "knip",
56
55
  "github-actions",
57
- "vscode",
56
+ "knip",
57
+ "prettier",
58
58
  "repository-sync",
59
- "developer-tools"
59
+ "typescript",
60
+ "vscode"
60
61
  ],
61
62
  "bin": {
62
63
  "ankhorage-changeset": "./dist/cli/bin/changeset.js",