@ankhorage/devtools 1.8.4 → 1.9.0

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 +30 -3
  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 +6 -3
  29. package/package.json +10 -6
package/README.md CHANGED
@@ -11,6 +11,8 @@ src/
11
11
  ├── cli/
12
12
  ├── policy/
13
13
  └── tools/
14
+ ├── agents/
15
+ ├── skills/
14
16
  ├── eslint/
15
17
  ├── prettier/
16
18
  ├── knip/
@@ -21,6 +23,8 @@ src/
21
23
 
22
24
  - `policy`: shared repository runtime policy, including the canonical Bun version
23
25
  - `changesets`: package-resolved Changesets execution and release command policy
26
+ - `agents`: canonical repository `AGENTS.md` rendered from stable package identity
27
+ - `skills`: immutable Ankhorage-owned repository skills under `.agents/skills/`
24
28
  - `eslint`: shared flat ESLint configuration, automatic project profiles, and the bundled ESLint runner
25
29
  - `prettier`: shared Prettier configuration and the bundled Prettier runner
26
30
  - `knip`: shared Knip configuration helpers and the bundled Knip runner
@@ -61,6 +65,10 @@ The package is discovered under the `devtools` category and exposes these capabi
61
65
  - `devtools.knip`
62
66
  - `devtools.sync`
63
67
  - `devtools.status`
68
+ - `devtools.agents.sync`
69
+ - `devtools.agents.status`
70
+ - `devtools.skills.sync`
71
+ - `devtools.skills.status`
64
72
  - `devtools.eslint.sync`
65
73
  - `devtools.eslint.status`
66
74
  - `devtools.prettier.sync`
@@ -125,6 +133,8 @@ ankh devtools status .
125
133
  Synchronize one concern:
126
134
 
127
135
  ```bash
136
+ ankh devtools agents sync .
137
+ ankh devtools skills sync .
128
138
  ankh devtools eslint sync .
129
139
  ankh devtools prettier sync .
130
140
  ankh devtools knip sync .
@@ -136,6 +146,8 @@ ankh devtools vscode sync .
136
146
  Report one concern:
137
147
 
138
148
  ```bash
149
+ ankh devtools agents status .
150
+ ankh devtools skills status .
139
151
  ankh devtools eslint status .
140
152
  ankh devtools prettier status .
141
153
  ankh devtools knip status .
@@ -148,11 +160,13 @@ Preview synchronization without writing:
148
160
 
149
161
  ```bash
150
162
  ankh devtools sync . --dry-run
163
+ ankh devtools agents sync . --dry-run
164
+ ankh devtools skills sync . --dry-run
151
165
  ankh devtools eslint sync . --dry-run
152
166
  ankh devtools package sync . --dry-run
153
167
  ```
154
168
 
155
- A dry run reports `would create` and `would update` actions without mutating files. `status` exits with code `1` when managed state has drifted and `0` when it is current.
169
+ A dry run reports `would create`, `would update`, and `would remove` actions without mutating files. `status` exits with code `1` when managed state has drifted and `0` when it is current.
156
170
 
157
171
  ## Synchronization guarantees
158
172
 
@@ -160,6 +174,7 @@ Synchronization is deterministic and idempotent:
160
174
 
161
175
  - missing managed artifacts are created
162
176
  - outdated centrally owned artifacts are updated
177
+ - stale files in Devtools-owned skill trees are removed
163
178
  - the managed Bun runtime version is applied consistently to package metadata and workflows
164
179
  - Changesets-enabled repositories use the Devtools-owned runner without a direct `@changesets/cli` declaration
165
180
  - package changes are followed by `bun install` after all managed files have been written, keeping installed dependencies and `bun.lock` synchronized without invalidating the running sync
@@ -169,7 +184,19 @@ Synchronization is deterministic and idempotent:
169
184
  - invalid target paths and write failures return a non-zero exit code
170
185
  - create-only repository extension files are never overwritten after creation
171
186
 
172
- The canonical workflow and VS Code files are packaged with `@ankhorage/devtools`; synchronization does not fetch mutable files from GitHub at runtime.
187
+ The canonical workflow, VS Code, and skill files are packaged with `@ankhorage/devtools`; synchronization does not fetch mutable files from GitHub at runtime.
188
+
189
+ ## Managed agent instructions
190
+
191
+ `ankh devtools agents sync` owns the repository-root `AGENTS.md`. The shared instructions are intentionally small and stable. The target repository's package name and description are rendered from `package.json`; the remaining content defines the unconditional current-architecture policy and directs structural work to the managed project-structure skill.
192
+
193
+ Only the current Ankhorage architecture is supported. Managed instructions reject deprecated APIs, compatibility aliases, shims, dual old/new paths, historical-state fallbacks, and migrations whose sole purpose is obsolete state. A canonical cross-package change requires affected repositories to update to the latest released public API.
194
+
195
+ ## Managed repository skills
196
+
197
+ `ankh devtools skills sync` owns the complete `.agents/skills/ankhorage-coding-rules/` and `.agents/skills/ankhorage-project-structure/` trees from the immutable copies shipped in the Devtools release. It creates `.agents/` when missing, replaces stale files in those managed skills, and preserves every unrelated skill directory.
198
+
199
+ `.agents/.devtools-manifest.json` records the source Devtools version and SHA-256 hashes for every managed skill file. That ownership record allows status and dry-run to report drift and lets later releases remove stale owned files without deleting repository-owned skills.
173
200
 
174
201
  ## ESLint profiles
175
202
 
@@ -313,7 +340,7 @@ A repository participates in Changesets synchronization when `.changeset/config.
313
340
 
314
341
  CI and Release render their `bun-version` from the same managed Bun runtime policy used for `package.json`. They also render Changesets status, version, and publish commands from the same policy that owns the synchronized package scripts. The CI workflow installs that Bun version with the frozen lockfile, builds before repository-provider validation, runs `bunx @ankhorage/ankh doctor validate .`, and conditionally runs lint, formatting, Knip, tests, typecheck, and Changesets checks. Ordinary pull requests run the strict `changeset:status --since=origin/main` guard. A same-repository `changeset-release/main` pull request instead runs `bun run changeset -- status --since=HEAD`, validating the Version Packages metadata from the detached release checkout without requiring a local `main` ref or another unreleased Changeset after the release Changeset was consumed. A fork using that branch name remains subject to the ordinary strict guard. Release publishing calls the Devtools-owned runner through the synchronized package script; it never resolves an ambient or mutable Changesets executable.
315
342
 
316
- The Renovate workflow accepts only same-repository branches created by `renovate[bot]`. It calls the SHA-pinned `ankhorage/renovate` workflow to add a release Changeset for runtime Ankhorage dependency updates or an empty Changeset for dev-only updates, then dispatches CI for that bot-authored commit. It never checks out or executes pull-request code in the privileged `pull_request_target` context.
343
+ The Renovate workflow accepts only same-repository branches created by `renovate[bot]`. It calls the SHA-pinned `ankhorage/renovate` workflow to add a release Changeset for runtime Ankhorage dependency updates or an empty Changeset for dev-only updates. The trusted workflow scopes the Ankhorage Renovate Sync GitHub App token to the current repository and uses it only for the validated commit, allowing every normal pull-request CI workflow to start without manual approval. It never checks out or executes pull-request code in the privileged `pull_request_target` context.
317
344
 
318
345
  ## Managed VS Code configuration
319
346
 
@@ -1,7 +1,7 @@
1
1
  export type DevtoolsToolName = 'changeset' | 'format' | 'knip' | 'lint';
2
- type DevtoolsManagedScope = 'all' | 'eslint' | 'knip' | 'package' | 'prettier' | 'vscode' | 'workflows';
2
+ type DevtoolsManagedScope = 'agents' | 'all' | 'eslint' | 'knip' | 'package' | 'prettier' | 'skills' | 'vscode' | 'workflows';
3
3
  type DevtoolsManagedOperation = 'status' | 'sync';
4
- type DevtoolsCapability = 'devtools.changeset' | 'devtools.format' | 'devtools.knip' | 'devtools.lint' | 'devtools.status' | 'devtools.sync' | 'devtools.eslint.status' | 'devtools.eslint.sync' | 'devtools.knip.status' | 'devtools.knip.sync' | 'devtools.package.status' | 'devtools.package.sync' | 'devtools.prettier.status' | 'devtools.prettier.sync' | 'devtools.vscode.status' | 'devtools.vscode.sync' | 'devtools.workflows.status' | 'devtools.workflows.sync';
4
+ type DevtoolsCapability = 'devtools.changeset' | 'devtools.format' | 'devtools.knip' | 'devtools.lint' | 'devtools.status' | 'devtools.sync' | 'devtools.agents.status' | 'devtools.agents.sync' | 'devtools.eslint.status' | 'devtools.eslint.sync' | 'devtools.knip.status' | 'devtools.knip.sync' | 'devtools.package.status' | 'devtools.package.sync' | 'devtools.prettier.status' | 'devtools.prettier.sync' | 'devtools.skills.status' | 'devtools.skills.sync' | 'devtools.vscode.status' | 'devtools.vscode.sync' | 'devtools.workflows.status' | 'devtools.workflows.sync';
5
5
  interface DevtoolsCommandBase {
6
6
  readonly path: readonly [string, ...string[]];
7
7
  readonly capability: DevtoolsCapability;
@@ -5,6 +5,10 @@ const DEVTOOLS_COMMANDS = [
5
5
  externalCommand('knip', 'devtools.knip', 'Run the shared Knip toolchain.', 'knip', 'knip'),
6
6
  repositoryCommand(['sync'], 'devtools.sync', 'Synchronize all managed repository setup.', 'all', 'sync'),
7
7
  repositoryCommand(['status'], 'devtools.status', 'Report drift for all managed repository setup.', 'all', 'status'),
8
+ repositoryCommand(['agents', 'sync'], 'devtools.agents.sync', 'Synchronize the canonical repository agent instructions.', 'agents', 'sync'),
9
+ repositoryCommand(['agents', 'status'], 'devtools.agents.status', 'Report drift for the canonical repository agent instructions.', 'agents', 'status'),
10
+ repositoryCommand(['skills', 'sync'], 'devtools.skills.sync', 'Synchronize canonical repository-local agent skills.', 'skills', 'sync'),
11
+ repositoryCommand(['skills', 'status'], 'devtools.skills.status', 'Report drift for canonical repository-local agent skills.', 'skills', 'status'),
8
12
  repositoryCommand(['eslint', 'sync'], 'devtools.eslint.sync', 'Synchronize the shared ESLint setup.', 'eslint', 'sync'),
9
13
  repositoryCommand(['eslint', 'status'], 'devtools.eslint.status', 'Report drift for the shared ESLint setup.', 'eslint', 'status'),
10
14
  repositoryCommand(['prettier', 'sync'], 'devtools.prettier.sync', 'Synchronize the shared Prettier setup.', 'prettier', 'sync'),
@@ -2,10 +2,10 @@ declare const provider: {
2
2
  id: string;
3
3
  category: string;
4
4
  version: string;
5
- capabilities: ("devtools.changeset" | "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync")[];
5
+ capabilities: ("devtools.changeset" | "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.agents.status" | "devtools.agents.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.skills.status" | "devtools.skills.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync")[];
6
6
  commands: {
7
7
  path: string[];
8
- capability: "devtools.changeset" | "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync";
8
+ capability: "devtools.changeset" | "devtools.format" | "devtools.knip" | "devtools.lint" | "devtools.status" | "devtools.sync" | "devtools.agents.status" | "devtools.agents.sync" | "devtools.eslint.status" | "devtools.eslint.sync" | "devtools.knip.status" | "devtools.knip.sync" | "devtools.package.status" | "devtools.package.sync" | "devtools.prettier.status" | "devtools.prettier.sync" | "devtools.skills.status" | "devtools.skills.sync" | "devtools.vscode.status" | "devtools.vscode.sync" | "devtools.workflows.status" | "devtools.workflows.sync";
9
9
  summary: string;
10
10
  }[];
11
11
  handlers: {
package/dist/cli/index.js CHANGED
@@ -4,13 +4,13 @@
4
4
  * `ankh devtools changeset`, `ankh devtools lint`, `ankh devtools format`, and
5
5
  * `ankh devtools knip` execute the bundled Changesets, ESLint, Prettier, and Knip versions.
6
6
  * Repository synchronization is available through
7
- * `ankh devtools sync` and `ankh devtools status`, with focused `eslint`, `prettier`, `knip`,
8
- * `package`, `workflows`, and `vscode` sync/status subcommands.
7
+ * `ankh devtools sync` and `ankh devtools status`, with focused `agents`, `skills`, `eslint`,
8
+ * `prettier`, `knip`, `package`, `workflows`, and `vscode` sync/status subcommands.
9
9
  *
10
10
  * Sync commands accept an optional target directory and `--dry-run`. Aggregate sync is
11
- * deterministic and idempotent: canonical managed files are created or updated, create-only
12
- * local extension files remain repository-owned, and package metadata is merge-updated without
13
- * replacing unrelated fields.
11
+ * deterministic and idempotent: canonical managed files and skill trees are created or updated,
12
+ * unrelated repository-local skills and create-only local extension files remain repository-owned,
13
+ * and package metadata is merge-updated without replacing unrelated fields.
14
14
  *
15
15
  * Fresh repositories can bootstrap the standard setup with `ankh devtools sync .` after adding
16
16
  * `@ankhorage/devtools`. Existing ESLint configuration is preserved during first migration as a
@@ -1,9 +1,11 @@
1
+ import { agentsManagedFiles } from '../tools/agents/index.js';
1
2
  import { eslintManagedFiles } from '../tools/eslint/managed.js';
2
3
  import { knipManagedFiles } from '../tools/knip/managed.js';
3
4
  import { inspectPackageManifest, readCurrentDevtoolsVersion, syncPackageManifest, } from '../tools/package/index.js';
4
5
  import { planBunDependencySync, syncBunDependencies, } from '../tools/package/syncBunDependencies.js';
5
6
  import { prettierManagedFiles } from '../tools/prettier/managed.js';
6
7
  import { inspectManagedFiles, resolveManagedTargetDirectory, syncManagedFiles, } from '../tools/shared/managedFiles.js';
8
+ import { inspectManagedSkills, syncManagedSkills } from '../tools/skills/managed.js';
7
9
  import { vscodeManagedFiles } from '../tools/vscode/index.js';
8
10
  import { workflowManagedFiles } from '../tools/workflows/index.js';
9
11
  export async function runRepositoryCommand(command, argv, context) {
@@ -47,6 +49,9 @@ async function runStatus(scope, targetDirectory, devtoolsVersion, context) {
47
49
  statuses.push(await inspectPackageManifest(targetDirectory, devtoolsVersion));
48
50
  }
49
51
  statuses.push(...(await inspectManagedFiles(targetDirectory, getManagedFiles(scope))));
52
+ if (scope === 'all' || scope === 'skills') {
53
+ statuses.push(...(await inspectManagedSkills(targetDirectory, devtoolsVersion)));
54
+ }
50
55
  writeStatusOutput(statuses, context);
51
56
  return { exitCode: statuses.some((status) => status.state !== 'current') ? 1 : 0 };
52
57
  }
@@ -59,6 +64,9 @@ async function runSync(scope, targetDirectory, devtoolsVersion, dryRun, context)
59
64
  packageDependenciesChanged = packageResult.action !== 'unchanged';
60
65
  }
61
66
  results.push(...(await syncManagedFiles(targetDirectory, getManagedFiles(scope), { dryRun })));
67
+ if (scope === 'all' || scope === 'skills') {
68
+ results.push(...(await syncManagedSkills(targetDirectory, devtoolsVersion, { dryRun })));
69
+ }
62
70
  if (packageDependenciesChanged) {
63
71
  if (dryRun) {
64
72
  results.push(planBunDependencySync(targetDirectory));
@@ -73,9 +81,11 @@ async function runSync(scope, targetDirectory, devtoolsVersion, dryRun, context)
73
81
  }
74
82
  function getManagedFiles(scope) {
75
83
  const definitionsByScope = {
84
+ agents: agentsManagedFiles,
76
85
  eslint: eslintManagedFiles,
77
86
  knip: knipManagedFiles,
78
87
  prettier: prettierManagedFiles,
88
+ skills: [],
79
89
  vscode: vscodeManagedFiles,
80
90
  workflows: workflowManagedFiles,
81
91
  };
@@ -95,6 +105,9 @@ function writeStatusOutput(statuses, context) {
95
105
  else if (status.state === 'missing') {
96
106
  context.writeStdout(`+ ${status.relativePath} missing\n`);
97
107
  }
108
+ else if (status.state === 'obsolete') {
109
+ context.writeStdout(`- ${status.relativePath} obsolete\n`);
110
+ }
98
111
  else {
99
112
  context.writeStdout(`✗ ${status.relativePath} outdated\n`);
100
113
  }
@@ -113,6 +126,9 @@ function getActionPrefix(action) {
113
126
  if (action === 'created' || action === 'would-create') {
114
127
  return '+';
115
128
  }
129
+ if (action === 'removed' || action === 'would-remove') {
130
+ return '-';
131
+ }
116
132
  return '↻';
117
133
  }
118
134
  function formatAction(action) {
@@ -121,10 +137,14 @@ function formatAction(action) {
121
137
  return 'created';
122
138
  case 'updated':
123
139
  return 'updated';
140
+ case 'removed':
141
+ return 'removed';
124
142
  case 'unchanged':
125
143
  return 'unchanged';
126
144
  case 'would-create':
127
145
  return 'would create';
146
+ case 'would-remove':
147
+ return 'would remove';
128
148
  case 'would-update':
129
149
  return 'would update';
130
150
  }
@@ -6,6 +6,8 @@ const REQUIRED_README_SNIPPETS = [
6
6
  'ankh devtools knip',
7
7
  'ankh devtools sync',
8
8
  'ankh devtools status',
9
+ 'ankh devtools agents sync',
10
+ 'ankh devtools skills sync',
9
11
  'ankh devtools eslint sync',
10
12
  'ankh devtools prettier sync',
11
13
  'ankh devtools knip sync',
@@ -13,6 +15,9 @@ const REQUIRED_README_SNIPPETS = [
13
15
  'ankh devtools workflows sync',
14
16
  'ankh devtools vscode sync',
15
17
  'devtools.eslint.sync',
18
+ 'devtools.agents.sync',
19
+ 'devtools.skills.sync',
20
+ 'ankhorage-coding-rules',
16
21
  'devtools.prettier.sync',
17
22
  'devtools.knip.sync',
18
23
  'devtools.package.sync',
@@ -0,0 +1,6 @@
1
+ export declare const agentsManagedFiles: readonly [{
2
+ readonly relativePath: "AGENTS.md";
3
+ readonly render: typeof renderAgentsFile;
4
+ }];
5
+ declare function renderAgentsFile(targetDirectory: string): Promise<string>;
6
+ export {};
@@ -0,0 +1,70 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ export const agentsManagedFiles = [
4
+ {
5
+ relativePath: 'AGENTS.md',
6
+ render: renderAgentsFile,
7
+ },
8
+ ];
9
+ async function renderAgentsFile(targetDirectory) {
10
+ const manifest = await readPackageManifest(targetDirectory);
11
+ const packageName = readNonEmptyString(manifest.name) ?? 'Package name not declared';
12
+ const description = readNonEmptyString(manifest.description) ?? 'No package description is declared.';
13
+ return `# AGENTS.md
14
+
15
+ <!-- This file is managed by @ankhorage/devtools. -->
16
+
17
+ ## Repository
18
+
19
+ Package: \`${escapeInlineCode(packageName)}\`
20
+
21
+ ${description}
22
+
23
+ ## Current architecture only
24
+
25
+ Only the current Ankhorage architecture is valid. Do not add or retain deprecated APIs,
26
+ compatibility aliases, shims, dual old/new paths, historical-state fallbacks, or migrations whose
27
+ sole purpose is supporting obsolete states. Remove superseded implementations instead.
28
+
29
+ When a canonical change affects another repository, update that repository to the latest released
30
+ public API instead of preserving compatibility locally. Cross-package usage must go through
31
+ published public APIs and declared dependencies, never sibling source files.
32
+
33
+ Current-runtime error handling and canonical database or infrastructure migrations remain valid
34
+ when they support states that the current architecture can intentionally produce.
35
+
36
+ ## Project structure
37
+
38
+ For directory ownership, package boundaries, architectural profiles, ports and adapters, public
39
+ entrypoints, or cross-repository structural work, load and follow
40
+ \`.agents/skills/ankhorage-project-structure/SKILL.md\`.
41
+ `;
42
+ }
43
+ async function readPackageManifest(targetDirectory) {
44
+ try {
45
+ const contents = await readFile(resolve(targetDirectory, 'package.json'), 'utf8');
46
+ const parsed = JSON.parse(contents);
47
+ if (!isRecord(parsed)) {
48
+ throw new Error('package.json must contain a JSON object.');
49
+ }
50
+ return parsed;
51
+ }
52
+ catch (error) {
53
+ if (isNodeError(error) && error.code === 'ENOENT') {
54
+ return {};
55
+ }
56
+ throw error;
57
+ }
58
+ }
59
+ function escapeInlineCode(value) {
60
+ return value.replaceAll('`', '\\`');
61
+ }
62
+ function isNodeError(error) {
63
+ return error instanceof Error && 'code' in error;
64
+ }
65
+ function isRecord(value) {
66
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
67
+ }
68
+ function readNonEmptyString(value) {
69
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
70
+ }
@@ -7,8 +7,8 @@ export interface ManagedFileDefinition {
7
7
  readonly render?: ManagedFileRenderer;
8
8
  readonly mode?: ManagedFileMode;
9
9
  }
10
- type ManagedFileState = 'current' | 'missing' | 'outdated';
11
- type ManagedFileSyncAction = 'unchanged' | 'created' | 'updated' | 'would-create' | 'would-update';
10
+ type ManagedFileState = 'current' | 'missing' | 'obsolete' | 'outdated';
11
+ export type ManagedFileSyncAction = 'unchanged' | 'created' | 'removed' | 'updated' | 'would-create' | 'would-remove' | 'would-update';
12
12
  export interface ManagedFileStatus {
13
13
  readonly relativePath: string;
14
14
  readonly state: ManagedFileState;
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: ankhorage-coding-rules
3
+ description: >
4
+ Implement, refactor, test, review, or hand off code changes in Ankhorage repositories. Use for
5
+ task branches and pull requests, canonical lint compliance, behavior-focused testing, Paradox
6
+ function documentation, validation, and release-quality delivery. Use ankhorage-project-structure
7
+ as well when the task changes package ownership, public entrypoints, or source architecture.
8
+ ---
9
+
10
+ # Ankhorage Coding Rules
11
+
12
+ Deliver focused changes that satisfy the repository's current canonical policy and are ready for
13
+ review. Read the repository `AGENTS.md`, package scripts, public exports, representative tests, and
14
+ documentation ownership before editing.
15
+
16
+ Repository instructions may add stricter requirements. They must not be bypassed with local
17
+ exceptions or replaced by generic preferences from this skill.
18
+
19
+ ## Git and delivery
20
+
21
+ - For a new task, fetch the latest `origin/main` and create a fresh task branch from it. Never
22
+ implement directly on `main`.
23
+ - Continue existing pull-request work on its existing branch. Do not create a nested task branch.
24
+ - Preserve unrelated or dirty user work. Use an isolated worktree when changing branches would
25
+ disturb it.
26
+ - Keep the diff within the requested issue. Do not mix opportunistic cleanup into the change.
27
+ - Deliver coding work through a pull request. If remote writes are unavailable or unauthorized,
28
+ prepare the branch and report the pending push or PR explicitly.
29
+ - Describe the change, architectural effect, validation, public API and changeset impact,
30
+ documentation impact, and linked issue in the PR body.
31
+
32
+ ## Canonical lint compliance
33
+
34
+ - New and materially changed code must satisfy the canonical ESLint configuration.
35
+ - Never add, widen, or depend on `eslint.local.config.mjs` exceptions. Existing exceptions are
36
+ removable migration debt, not policy for new work.
37
+ - Do not weaken rules or add suppression comments, unsafe casts, or similar bypasses merely to make
38
+ validation pass.
39
+ - Resolve size, complexity, and related findings around cohesive responsibilities and clear data
40
+ flow. Do not split a function or file mechanically just to cross a threshold.
41
+ - Prefer reusable pure transformations, immutable values, and explicit inputs and outputs. Keep
42
+ unavoidable mutation and external side effects at clear boundaries.
43
+ - Keep one canonical implementation. Do not add compatibility aliases, dual paths, historical-state
44
+ fallbacks, or sibling-source imports.
45
+
46
+ ## Testing
47
+
48
+ - Test observable behavior at the owning boundary. Keep tests deterministic and independent of
49
+ execution order, ambient state, and live network services.
50
+ - When testing dependency declarations, assert the required semver range shape or a shared policy
51
+ value instead of a Renovate-managed literal version. Assert an exact version only when the pin is
52
+ itself the contract under test.
53
+ - Follow the range-shape patterns in the
54
+ [Devtools repository sync test](https://github.com/ankhorage/devtools/blob/main/src/cli/runRepositoryCommand.test.ts)
55
+ and [ZORA PR #313](https://github.com/ankhorage/zora/pull/313).
56
+ - Run focused unit or integration tests before full E2E and smoke suites.
57
+ - Do not rerun an unchanged E2E or smoke failure without a new hypothesis. After two unsuccessful
58
+ full reruns without a substantive code or configuration change, stop cycling, inspect logs and
59
+ artifacts, and report the blocker.
60
+
61
+ ## Paradox documentation
62
+
63
+ - Give every named production function under `src` a concise Paradox `/*** ... */` description,
64
+ including internal helpers. Inline callbacks are exempt.
65
+ - Add `@param`, `@returns`, `@example`, or `@readme` to public APIs only when the tag adds useful
66
+ information.
67
+ - Update documentation sources and regenerate tracked Paradox output. Never hand-edit generated
68
+ README or Paradox artifacts.
69
+
70
+ ## Public API and release quality
71
+
72
+ - Export public symbols intentionally from canonical entrypoints and exercise those exports in
73
+ tests. Do not introduce consumer deep imports.
74
+ - Add the repository-required changeset for published behavior, package shape, dependency, or
75
+ public API changes.
76
+ - Justify new dependencies and keep generated artifacts, lockfiles, and package metadata aligned
77
+ with the repository's owning workflows.
78
+ - Review the final diff, then run the applicable build, lint, test, typecheck, Knip, format, docs,
79
+ changeset-status, and packaging checks.
80
+ - Report the exact commands run and any omissions or failures. Do not imply a fully green handoff
81
+ when a required check did not run or pass.
@@ -0,0 +1,7 @@
1
+ interface:
2
+ display_name: 'Ankhorage Coding Rules'
3
+ short_description: 'Implement and deliver Ankhorage changes'
4
+ default_prompt: 'Use $ankhorage-coding-rules to implement and deliver this Ankhorage change.'
5
+
6
+ policy:
7
+ allow_implicit_invocation: true
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: ankhorage-project-structure
3
+ description: >
4
+ Design, review, or migrate the source structure of Ankhorage repositories and generated
5
+ applications. Use for directory ownership, package boundaries, architectural profiles,
6
+ ports and adapters, package CLI layout, repository skill distribution, public entrypoints,
7
+ or cross-repository cleanup.
8
+ ---
9
+
10
+ # Ankhorage Project Structure
11
+
12
+ Apply a repository profile instead of forcing one folder tree onto every package.
13
+
14
+ Before deciding structure:
15
+
16
+ 1. Read the repository `AGENTS.md`, `package.json`, exports, source tree, and representative tests.
17
+ 2. Load `ankhorage-coding-rules` and `ankhorage-package-structure` when available.
18
+ 3. Identify the repository's owning capability and its profile below.
19
+ 4. Identify current public subpaths and cross-package release boundaries.
20
+
21
+ Repository-specific rules may refine this skill, but must not silently reverse package ownership
22
+ or dependency direction.
23
+
24
+ ## Primary boundary
25
+
26
+ An Ankhorage repository/package is the primary bounded capability and independently released
27
+ unit. Do not create a generic `src/features/` layer that imitates package boundaries.
28
+
29
+ - Independently bindable capabilities belong in standalone packages.
30
+ - Substantial responsibilities inside a package use cohesive domain directories directly under
31
+ `src/`.
32
+ - Cross-package access uses published APIs and declared dependencies, never sibling source.
33
+ - A package owns its application behavior; adapters belonging to another package are not copied
34
+ or proxied locally.
35
+
36
+ ## Select a profile
37
+
38
+ Read [repository-profiles.md](references/repository-profiles.md), select one primary profile, and
39
+ apply only the references routed by that profile.
40
+
41
+ | Profile | Typical repositories | Required references |
42
+ | ---------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
43
+ | Application, engine, or hybrid | Studio, Runtime, Orchestrator, Deploy, Infra | [hexagonal-architecture.md](references/hexagonal-architecture.md) |
44
+ | React Native / Expo application | generated apps, Studio app host | [hexagonal-architecture.md](references/hexagonal-architecture.md), [expo-apps.md](references/expo-apps.md) |
45
+ | Component or design-system library | ZORA, Surface | [ui-libraries.md](references/ui-libraries.md) |
46
+ | Contracts or value library | Contracts and other portable models | [repository-profiles.md](references/repository-profiles.md) |
47
+ | Utility library | Utility | [utilities.md](references/utilities.md) |
48
+ | Platform/provider adapter | Expo Runtime, Supabase packages, orchestrator modules | [repository-profiles.md](references/repository-profiles.md) |
49
+ | Tooling or CLI package | Ankh, Doctor, Devtools and any package exposing Ankh commands | [cli.md](references/cli.md) |
50
+
51
+ When a package declares an Ankh provider or changes `src/cli/`, always read
52
+ [cli.md](references/cli.md), regardless of its primary profile.
53
+
54
+ When the task is a structural cleanup or migration, also read
55
+ [migration.md](references/migration.md). For Studio specifically, read
56
+ [studio.md](references/studio.md).
57
+
58
+ When adding, distributing, or synchronizing repository-local agent skills, read
59
+ [skill-distribution.md](references/skill-distribution.md).
60
+
61
+ ## Universal invariants
62
+
63
+ - Keep only intentional package entrypoints and required declaration shims directly under `src/`.
64
+ - Keep one abstraction level and responsibility among siblings.
65
+ - Prefer domain ownership over technical dumping grounds such as `common`, `core`, `helpers`,
66
+ `misc`, or `shared`.
67
+ - `app/`, `cli/`, `host/`, and `platform/` are package-level edges or composition areas, not
68
+ alternate owners of domain behavior.
69
+ - Ports are owned by the application/domain code that requires the capability.
70
+ - Adapters depend inward; domain/application code does not import concrete adapters.
71
+ - Add a port only for a real external or replaceable boundary. Do not wrap every function in
72
+ architecture ceremony.
73
+ - Colocate focused unit tests. Put cross-domain acceptance, E2E, smoke infrastructure, and large
74
+ fixtures outside production source.
75
+ - Public package subpaths may point to nested source. Do not keep files at `src/` merely because
76
+ they are exported.
77
+ - Preserve one canonical implementation. Do not add legacy paths, compatibility barrels, or
78
+ duplicate APIs to make a migration appear smaller.
79
+
80
+ ## Ownership decision
81
+
82
+ For every file or new symbol, decide in order:
83
+
84
+ 1. Which Ankhorage package owns the capability?
85
+ 2. Which internal domain owns it?
86
+ 3. Is it core policy, application orchestration, a required port, an edge adapter, composition,
87
+ or a public entrypoint?
88
+ 4. Is it reusable enough to belong in `@ankhorage/utility` instead?
89
+
90
+ If any answer is unclear, resolve ownership before moving or creating code.
@@ -0,0 +1,7 @@
1
+ interface:
2
+ display_name: 'Ankhorage Project Structure'
3
+ short_description: 'Structure and migrate Ankhorage repositories'
4
+ default_prompt: 'Use $ankhorage-project-structure to design or review this Ankhorage repository structure.'
5
+
6
+ policy:
7
+ allow_implicit_invocation: true