@ankhorage/devtools 1.10.10 → 1.10.12
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/dist/tools/skills/assets/ankhorage-project-structure/SKILL.md +21 -32
- package/dist/tools/skills/assets/ankhorage-project-structure/references/studio.md +94 -48
- package/dist/tools/skills/assets/zora-designer/SKILL.md +63 -56
- package/dist/tools/skills/assets/zora-designer/references/artifact.md +6 -4
- package/dist/tools/skills/assets/zora-designer/references/screens.md +90 -0
- package/dist/tools/skills/assets/zora-designer/references/workflow.md +84 -126
- package/dist/tools/skills/assets/zora-designer/scripts/generate-template-catalog.mjs +89 -0
- package/dist/tools/skills/assets/zora-designer/scripts/owner-api.mjs +62 -39
- package/dist/tools/skills/assets/zora-designer/scripts/scaffold-template.mjs +59 -139
- package/package.json +3 -3
- package/dist/tools/skills/assets/ankhorage-project-structure/references/expo-apps.md +0 -43
- package/dist/tools/skills/assets/ankhorage-project-structure/references/hexagonal-architecture.md +0 -120
- package/dist/tools/skills/assets/ankhorage-project-structure/references/repository-profiles.md +0 -91
- package/dist/tools/skills/assets/ankhorage-project-structure/references/ui-libraries.md +0 -79
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { access, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
|
|
7
|
+
import { generateTemplateCatalog } from './generate-template-catalog.mjs';
|
|
7
8
|
import { loadOwnerApis } from './owner-api.mjs';
|
|
8
9
|
|
|
9
|
-
/***
|
|
10
|
+
/*** Scaffold one complete portable template and refresh filesystem discovery. */
|
|
10
11
|
export async function scaffoldTemplate(input) {
|
|
11
12
|
assertRecord(input, 'Scaffold input');
|
|
12
|
-
for (const field of ['targetDirectory', 'category', '
|
|
13
|
+
for (const field of ['targetDirectory', 'category', 'slug']) {
|
|
13
14
|
assertNonEmptyString(input[field], field);
|
|
14
15
|
}
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
['default', 'starter'].includes(input.templateId)
|
|
18
|
-
) {
|
|
19
|
-
throw new Error('templateId must be a non-reserved kebab-case identifier.');
|
|
16
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(input.slug)) {
|
|
17
|
+
throw new Error('slug must be a kebab-case identifier.');
|
|
20
18
|
}
|
|
21
19
|
assertRecord(input.manifest, 'manifest');
|
|
22
20
|
|
|
@@ -30,165 +28,85 @@ export async function scaffoldTemplate(input) {
|
|
|
30
28
|
if (input.manifest.metadata?.category !== input.category) {
|
|
31
29
|
throw new Error('Scaffold category must match manifest.metadata.category.');
|
|
32
30
|
}
|
|
31
|
+
if (input.manifest.metadata?.slug !== input.slug) {
|
|
32
|
+
throw new Error('Scaffold slug must match manifest.metadata.slug.');
|
|
33
|
+
}
|
|
33
34
|
|
|
34
35
|
const owners = await loadOwnerApis(targetDirectory);
|
|
35
36
|
const composition = owners.templates.validateTemplateManifest(input.manifest, 'release');
|
|
36
|
-
if (composition.status !== 'ready') {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Manifest is not release-ready: ${composition.diagnostics.map((item) => item.message).join('; ')}`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
37
|
const manifest = owners.templates.assertTemplateManifestReady(composition);
|
|
42
|
-
|
|
38
|
+
|
|
43
39
|
const categoryDirectory = resolve(
|
|
44
40
|
targetDirectory,
|
|
45
|
-
'src/templates/
|
|
46
|
-
|
|
41
|
+
'src/templates/categories',
|
|
42
|
+
input.category.replaceAll('_', '-'),
|
|
47
43
|
);
|
|
48
44
|
assertInside(targetDirectory, categoryDirectory);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
|
|
46
|
+
const templateDirectory = resolve(categoryDirectory, input.slug);
|
|
47
|
+
assertInside(categoryDirectory, templateDirectory);
|
|
48
|
+
if (await pathExists(templateDirectory)) {
|
|
52
49
|
throw new Error(
|
|
53
|
-
`Template source already exists: ${relative(targetDirectory,
|
|
50
|
+
`Template source already exists: ${relative(targetDirectory, templateDirectory)}`,
|
|
54
51
|
);
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
69
|
-
const files = createTemplateFiles({ manifest, manifestName, factoryName });
|
|
70
|
-
|
|
71
|
-
await mkdir(variantDirectory, { recursive: true });
|
|
72
|
-
for (const [fileName, contents] of Object.entries(files)) {
|
|
73
|
-
await writeFile(join(variantDirectory, fileName), contents);
|
|
74
|
-
}
|
|
75
|
-
await writeFile(registryPath, registrySourceUpdated);
|
|
54
|
+
const screensDirectory = join(templateDirectory, 'assets', 'screens');
|
|
55
|
+
const imagesDirectory = join(templateDirectory, 'assets', 'images');
|
|
56
|
+
await mkdir(screensDirectory, { recursive: true });
|
|
57
|
+
await mkdir(imagesDirectory, { recursive: true });
|
|
58
|
+
await writeFile(
|
|
59
|
+
join(templateDirectory, 'createAppManifest.ts'),
|
|
60
|
+
createManifestSource(manifest),
|
|
61
|
+
'utf8',
|
|
62
|
+
);
|
|
63
|
+
await rm(join(categoryDirectory, '.gitkeep'), { force: true });
|
|
64
|
+
await generateTemplateCatalog(targetDirectory, owners.contracts.APP_CATEGORIES);
|
|
76
65
|
|
|
77
66
|
return {
|
|
78
67
|
targetDirectory,
|
|
79
|
-
|
|
80
|
-
createdFiles:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
templateDirectory: relative(targetDirectory, templateDirectory),
|
|
69
|
+
createdFiles: [relative(targetDirectory, join(templateDirectory, 'createAppManifest.ts'))],
|
|
70
|
+
assetDirectories: [
|
|
71
|
+
relative(targetDirectory, screensDirectory),
|
|
72
|
+
relative(targetDirectory, imagesDirectory),
|
|
73
|
+
],
|
|
84
74
|
};
|
|
85
75
|
}
|
|
86
76
|
|
|
87
|
-
/***
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
export const ${manifestName} = ${JSON.stringify(manifest, null, 2)} satisfies AppManifest;
|
|
92
|
-
`;
|
|
93
|
-
const templateSource = `import type { AppManifest } from '@ankhorage/contracts';
|
|
77
|
+
/*** Serialize one complete manifest as the template's canonical default export. */
|
|
78
|
+
function createManifestSource(manifest) {
|
|
79
|
+
return `import type { AppManifest } from '@ankhorage/contracts';
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
import { ${manifestName} } from './manifest';
|
|
81
|
+
const manifest = ${JSON.stringify(manifest, null, 2)} satisfies AppManifest;
|
|
97
82
|
|
|
98
|
-
/*** Create the
|
|
99
|
-
export function
|
|
100
|
-
|
|
101
|
-
if (theme === undefined) {
|
|
102
|
-
throw new Error('The authored template requires one resolved theme.');
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
...${manifestName},
|
|
106
|
-
metadata: {
|
|
107
|
-
...${manifestName}.metadata,
|
|
108
|
-
name: seed.appName,
|
|
109
|
-
slug: seed.slug,
|
|
110
|
-
version: seed.version ?? ${manifestName}.metadata.version,
|
|
111
|
-
themeId: theme.id,
|
|
112
|
-
},
|
|
113
|
-
themes: [theme],
|
|
114
|
-
activeThemeId: theme.id,
|
|
115
|
-
};
|
|
83
|
+
/*** Create the complete portable application manifest for this template. */
|
|
84
|
+
export default function createAppManifest(): AppManifest {
|
|
85
|
+
return structuredClone(manifest);
|
|
116
86
|
}
|
|
117
87
|
`;
|
|
118
|
-
return {
|
|
119
|
-
'index.ts': `export { ${factoryName} } from './template';\n`,
|
|
120
|
-
'manifest.ts': manifestSource,
|
|
121
|
-
'template.ts': templateSource,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/*** Add one stable import and definition to an existing category registry. */
|
|
126
|
-
function updateCategoryRegistry(source, definition) {
|
|
127
|
-
const importLine = `import { ${definition.factoryName} } from './${definition.templateId}';`;
|
|
128
|
-
if (source.includes(`id: '${definition.templateId}'`) || source.includes(importLine)) {
|
|
129
|
-
throw new Error(`Template is already registered: ${definition.templateId}`);
|
|
130
|
-
}
|
|
131
|
-
const exportMarker = '\nexport const ';
|
|
132
|
-
const exportIndex = source.indexOf(exportMarker);
|
|
133
|
-
if (exportIndex < 0) {
|
|
134
|
-
throw new Error('Category registry does not expose its canonical template array.');
|
|
135
|
-
}
|
|
136
|
-
const prefixLines = source.slice(0, exportIndex).trimEnd().split('\n');
|
|
137
|
-
const relativeImports = [
|
|
138
|
-
...prefixLines.filter((line) => /^import .* from '\.\//u.test(line)),
|
|
139
|
-
importLine,
|
|
140
|
-
].sort((left, right) => left.localeCompare(right));
|
|
141
|
-
const preservedPrefix = prefixLines.filter((line) => !/^import .* from '\.\//u.test(line));
|
|
142
|
-
const withImport = `${[...preservedPrefix, ...relativeImports].join('\n')}\n${source.slice(exportIndex + 1)}`;
|
|
143
|
-
const closeMarker = '] satisfies readonly CategoryStarterTemplateDefinition[];';
|
|
144
|
-
const closeIndex = withImport.indexOf(closeMarker);
|
|
145
|
-
if (closeIndex < 0) {
|
|
146
|
-
throw new Error('Category registry is missing its canonical definition-array terminator.');
|
|
147
|
-
}
|
|
148
|
-
const entry = ` {
|
|
149
|
-
id: '${escapeSingleQuoted(definition.templateId)}',
|
|
150
|
-
label: '${escapeSingleQuoted(definition.label)}',
|
|
151
|
-
description: '${escapeSingleQuoted(definition.description)}',
|
|
152
|
-
create: ${definition.factoryName},
|
|
153
|
-
},
|
|
154
|
-
`;
|
|
155
|
-
return `${withImport.slice(0, closeIndex)}${entry}${withImport.slice(closeIndex)}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/*** Convert kebab-case identifiers to a PascalCase source symbol. */
|
|
159
|
-
function toPascalCase(value) {
|
|
160
|
-
return value
|
|
161
|
-
.split('-')
|
|
162
|
-
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
|
|
163
|
-
.join('');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/*** Convert kebab-case identifiers to an uppercase constant name. */
|
|
167
|
-
function toConstantCase(value) {
|
|
168
|
-
return value.replaceAll('-', '_').toUpperCase();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/*** Escape content placed in generated single-quoted TypeScript strings. */
|
|
172
|
-
function escapeSingleQuoted(value) {
|
|
173
|
-
return value.replaceAll('\\', '\\\\').replaceAll("'", "\\'");
|
|
174
88
|
}
|
|
175
89
|
|
|
176
90
|
/*** Assert that a resolved output remains inside its declared owner directory. */
|
|
177
|
-
function assertInside(
|
|
178
|
-
const relativePath = relative(
|
|
179
|
-
if (
|
|
180
|
-
|
|
91
|
+
function assertInside(parentPath, childPath) {
|
|
92
|
+
const relativePath = relative(parentPath, childPath);
|
|
93
|
+
if (
|
|
94
|
+
relativePath === '' ||
|
|
95
|
+
relativePath === '..' ||
|
|
96
|
+
relativePath.startsWith(`..${sep}`) ||
|
|
97
|
+
relativePath.startsWith('../')
|
|
98
|
+
) {
|
|
99
|
+
throw new Error(`Template path escapes its owner directory: ${childPath}`);
|
|
181
100
|
}
|
|
182
101
|
}
|
|
183
102
|
|
|
184
|
-
/*** Return whether a filesystem path
|
|
185
|
-
async function pathExists(
|
|
103
|
+
/*** Return whether a filesystem path exists. */
|
|
104
|
+
async function pathExists(filePath) {
|
|
186
105
|
try {
|
|
187
|
-
await access(
|
|
106
|
+
await access(filePath);
|
|
188
107
|
return true;
|
|
189
|
-
} catch
|
|
190
|
-
|
|
191
|
-
throw error;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
192
110
|
}
|
|
193
111
|
}
|
|
194
112
|
|
|
@@ -209,8 +127,10 @@ function assertNonEmptyString(value, label) {
|
|
|
209
127
|
/*** Run deterministic Templates source scaffolding from one JSON input. */
|
|
210
128
|
async function main() {
|
|
211
129
|
const [inputPath] = process.argv.slice(2);
|
|
212
|
-
if (!inputPath)
|
|
213
|
-
|
|
130
|
+
if (!inputPath) {
|
|
131
|
+
throw new Error('Usage: scaffold-template.mjs <scaffold-input.json>');
|
|
132
|
+
}
|
|
133
|
+
const input = JSON.parse(await readFile(resolve(inputPath), 'utf8'));
|
|
214
134
|
console.log(JSON.stringify(await scaffoldTemplate(input), null, 2));
|
|
215
135
|
}
|
|
216
136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/devtools",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.12",
|
|
4
4
|
"description": "Shared development tools and repository standards for Ankhorage",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/devtools#readme",
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"version-packages": "bun src/cli/bin/changeset.ts version"
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
|
-
"@ankhorage/utility": "^0.
|
|
108
|
+
"@ankhorage/utility": "^0.3.0",
|
|
109
109
|
"@changesets/cli": "^3.0.1",
|
|
110
|
-
"@eslint/compat": "^2.1.
|
|
110
|
+
"@eslint/compat": "^2.1.1",
|
|
111
111
|
"@eslint/js": "^10.0.1",
|
|
112
112
|
"eslint": "^10.9.1",
|
|
113
113
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Expo and React Native Applications
|
|
2
|
-
|
|
3
|
-
An Expo application is an independently installable application and may use ports-and-adapters
|
|
4
|
-
principles for its application behavior. Expo Router route files are framework-owned inbound
|
|
5
|
-
adapters, not the home of domain logic.
|
|
6
|
-
|
|
7
|
-
## Structure
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
app/ # Expo Router route tree
|
|
11
|
-
src/
|
|
12
|
-
app/ # providers and app composition
|
|
13
|
-
<domain>/ # application-owned domains
|
|
14
|
-
domain/
|
|
15
|
-
application/
|
|
16
|
-
ports/
|
|
17
|
-
platform/ # app-local native/web adapters only
|
|
18
|
-
utils/ # app-wide internal utilities
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Keep route files thin: resolve route parameters and navigation context, invoke or render the owning
|
|
22
|
-
application boundary, and declare route-specific framework configuration. Substantial UI belongs
|
|
23
|
-
with its owning domain or reusable UI package.
|
|
24
|
-
|
|
25
|
-
## Ankhorage ownership
|
|
26
|
-
|
|
27
|
-
- Use Contracts for portable authored state.
|
|
28
|
-
- Use Runtime for manifest/action/data execution.
|
|
29
|
-
- Use ZORA and Surface for reusable UI.
|
|
30
|
-
- Use Expo Runtime and provider packages for platform integration.
|
|
31
|
-
- Do not copy package behavior into the generated app merely to avoid a public API or release.
|
|
32
|
-
- Do not import Studio source or rely on the Studio workspace.
|
|
33
|
-
|
|
34
|
-
## Standalone lifecycle
|
|
35
|
-
|
|
36
|
-
Each generated app owns its package manifest, lockfile, installation, validation, build, and
|
|
37
|
-
deployment inputs. A parent dashboard may invoke commands with the app as `cwd`, but must not
|
|
38
|
-
install it through a hidden shared workspace contract.
|
|
39
|
-
|
|
40
|
-
## Platform variants
|
|
41
|
-
|
|
42
|
-
Use `.native`, `.web`, `.ios`, and `.android` variants only when the platform behavior genuinely
|
|
43
|
-
differs. Keep the portable contract in the unsuffixed module and concrete behavior at the edge.
|
package/dist/tools/skills/assets/ankhorage-project-structure/references/hexagonal-architecture.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Ankhorage Ports and Adapters
|
|
2
|
-
|
|
3
|
-
Use ports-and-adapters principles for application, engine, service, and hybrid packages. The
|
|
4
|
-
Ankhorage package is the hexagon; internal domains are not independent feature packages.
|
|
5
|
-
|
|
6
|
-
## Purpose
|
|
7
|
-
|
|
8
|
-
Protect stable package policy from React, Expo, HTTP, Fastify, Bun, filesystem, process, database,
|
|
9
|
-
provider SDK, and test-harness details. The useful rule is dependency direction, not a mandatory
|
|
10
|
-
folder ceremony.
|
|
11
|
-
|
|
12
|
-
```text
|
|
13
|
-
package edge / adapter -> application -> domain
|
|
14
|
-
|
|
|
15
|
-
v
|
|
16
|
-
required ports
|
|
17
|
-
|
|
18
|
-
concrete adapter -> required port
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Roles
|
|
22
|
-
|
|
23
|
-
- **Domain:** pure rules, values, invariants, and deterministic transformations owned by the
|
|
24
|
-
package.
|
|
25
|
-
- **Application:** command-independent use cases and orchestration of domain behavior.
|
|
26
|
-
- **Port:** a capability contract required by application/domain code to reach an external edge.
|
|
27
|
-
- **Inbound adapter:** converts CLI, HTTP, UI, Runtime, worker, or test input into an application
|
|
28
|
-
invocation.
|
|
29
|
-
- **Outbound adapter:** implements a required port using filesystem, process, provider, storage,
|
|
30
|
-
network, Expo, or another package.
|
|
31
|
-
- **Composition:** selects implementations and wires adapters to application operations.
|
|
32
|
-
|
|
33
|
-
Ports belong beside the application/domain code that needs them. Do not create a global
|
|
34
|
-
`src/ports/` dumping ground.
|
|
35
|
-
|
|
36
|
-
## Package-level edges
|
|
37
|
-
|
|
38
|
-
Ankhorage preserves recognizable package edges:
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
src/app/ React or React Native composition and package-wide UI entrypoints
|
|
42
|
-
src/cli/ one package-level Ankh provider
|
|
43
|
-
src/host/ Bun/Node/Fastify/filesystem composition and shared host infrastructure
|
|
44
|
-
src/platform/ native, web, Expo, or provider-specific implementations
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Internal domain behavior must not migrate into these directories merely because an adapter calls
|
|
48
|
-
it. Edges translate and compose; domains own behavior.
|
|
49
|
-
|
|
50
|
-
## Domain-first organization
|
|
51
|
-
|
|
52
|
-
Substantial domains may use role directories:
|
|
53
|
-
|
|
54
|
-
```text
|
|
55
|
-
src/
|
|
56
|
-
projects/
|
|
57
|
-
contracts/
|
|
58
|
-
domain/
|
|
59
|
-
application/
|
|
60
|
-
ports/
|
|
61
|
-
deploy/
|
|
62
|
-
contracts/
|
|
63
|
-
domain/
|
|
64
|
-
application/
|
|
65
|
-
ports/
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Small domains remain flat while their siblings have the same role. Introduce role directories
|
|
69
|
-
when definitions, parsers, use cases, adapters, or utilities begin mixing at one level.
|
|
70
|
-
|
|
71
|
-
Do not use:
|
|
72
|
-
|
|
73
|
-
```text
|
|
74
|
-
src/features/
|
|
75
|
-
src/common/
|
|
76
|
-
src/core/
|
|
77
|
-
src/shared/
|
|
78
|
-
src/ports/
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
unless a repository has an explicit, narrower meaning that cannot be represented by an owning
|
|
82
|
-
domain or package edge.
|
|
83
|
-
|
|
84
|
-
## When a port is justified
|
|
85
|
-
|
|
86
|
-
Create a port when at least one is true:
|
|
87
|
-
|
|
88
|
-
- more than one real adapter exists or is planned by current architecture;
|
|
89
|
-
- deterministic tests need to replace a side effect;
|
|
90
|
-
- the dependency is volatile or provider-specific;
|
|
91
|
-
- the same application operation is invoked through multiple inbound edges;
|
|
92
|
-
- the capability crosses a package, process, storage, network, platform, or credential boundary.
|
|
93
|
-
|
|
94
|
-
Do not create a port merely because a function calls another function. Pure utilities, component
|
|
95
|
-
composition, value transformations, and React-local presentation state usually do not need ports.
|
|
96
|
-
|
|
97
|
-
## React and React Native
|
|
98
|
-
|
|
99
|
-
React/RN UI is an inbound edge. Components may collect input, render state, and invoke application
|
|
100
|
-
actions. Provider execution and durable business rules remain outside components and hooks.
|
|
101
|
-
|
|
102
|
-
- Keep navigation route modules thin.
|
|
103
|
-
- Keep UI-specific transient state near the UI.
|
|
104
|
-
- Move reusable business decisions and cross-interface operations into the owning domain or
|
|
105
|
-
application layer.
|
|
106
|
-
- Inject values, callbacks, or capability interfaces into reusable UI rather than importing
|
|
107
|
-
concrete providers.
|
|
108
|
-
|
|
109
|
-
## Composition roots
|
|
110
|
-
|
|
111
|
-
Keep wiring explicit and limited to package entrypoints such as app startup, host creation, CLI
|
|
112
|
-
provider construction, or a focused factory. Do not use ambient service locators or hidden mutable
|
|
113
|
-
registries as dependency injection.
|
|
114
|
-
|
|
115
|
-
## Testing
|
|
116
|
-
|
|
117
|
-
- Test domain and application behavior with deterministic inputs and fake ports.
|
|
118
|
-
- Test concrete adapters against their real protocol boundary.
|
|
119
|
-
- Test user-facing flows through inbound edges only where the integration adds evidence.
|
|
120
|
-
- Do not duplicate the full acceptance matrix for every internal refactor.
|
package/dist/tools/skills/assets/ankhorage-project-structure/references/repository-profiles.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Repository Profiles
|
|
2
|
-
|
|
3
|
-
Select one primary profile from the repository's actual ownership and consumers. A package may
|
|
4
|
-
also expose secondary edges such as CLI or Expo without changing its primary profile.
|
|
5
|
-
|
|
6
|
-
## Application, engine, or hybrid
|
|
7
|
-
|
|
8
|
-
Use for packages that coordinate use cases, state transitions, external systems, or several
|
|
9
|
-
delivery mechanisms.
|
|
10
|
-
|
|
11
|
-
Typical structure:
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
src/
|
|
15
|
-
index.ts
|
|
16
|
-
<domain>/
|
|
17
|
-
contracts/
|
|
18
|
-
domain/
|
|
19
|
-
application/
|
|
20
|
-
ports/
|
|
21
|
-
app/ # optional React/RN edge and composition
|
|
22
|
-
cli/ # optional package-level Ankh edge
|
|
23
|
-
host/ # optional Bun/Node/HTTP/filesystem edge
|
|
24
|
-
platform/ # optional native/web/provider edge
|
|
25
|
-
utils/ # internal cross-domain utilities only
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Create role subdirectories only when a domain has enough distinct responsibilities to need them.
|
|
29
|
-
Do not pre-create empty `contracts`, `domain`, `application`, or `ports` directories.
|
|
30
|
-
|
|
31
|
-
## Contracts or value library
|
|
32
|
-
|
|
33
|
-
Contracts own portable shape and structural validation, not provider execution.
|
|
34
|
-
|
|
35
|
-
```text
|
|
36
|
-
src/
|
|
37
|
-
index.ts
|
|
38
|
-
<domain>/
|
|
39
|
-
contracts/
|
|
40
|
-
parsers/
|
|
41
|
-
constants/
|
|
42
|
-
tests colocated with their owner
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
- Keep modules deterministic and side-effect free.
|
|
46
|
-
- Type definitions and structural parsers change together.
|
|
47
|
-
- Provider readiness, network state, filesystem state, and UI behavior stay in their owner.
|
|
48
|
-
- Avoid `Record<string, unknown>` escape hatches when a canonical concept can be modeled.
|
|
49
|
-
|
|
50
|
-
## Platform or provider adapter
|
|
51
|
-
|
|
52
|
-
These packages deliberately implement an external technology boundary.
|
|
53
|
-
|
|
54
|
-
```text
|
|
55
|
-
src/
|
|
56
|
-
index.ts
|
|
57
|
-
contracts/ # provider-facing public configuration when owned here
|
|
58
|
-
planning/ # pure capability/configuration planning
|
|
59
|
-
adapters/ # concrete provider/platform implementations
|
|
60
|
-
composition/ # factories or provider registration
|
|
61
|
-
cli/ # only when this package exposes Ankh commands
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
- Depend on portable contracts or ports from lower-level owning packages.
|
|
65
|
-
- Do not redefine the application/domain model locally.
|
|
66
|
-
- Keep provider SDK values from leaking through portable public contracts.
|
|
67
|
-
- Separate build-time planning from runtime execution when both exist.
|
|
68
|
-
|
|
69
|
-
## Tooling package
|
|
70
|
-
|
|
71
|
-
Tooling packages may be command-centric but still keep parsing, policy, and side effects distinct.
|
|
72
|
-
|
|
73
|
-
```text
|
|
74
|
-
src/
|
|
75
|
-
cli/
|
|
76
|
-
policy/ # deterministic rules and diagnostics
|
|
77
|
-
application/ # command-independent operations
|
|
78
|
-
adapters/ # filesystem, process, GitHub, registry, etc.
|
|
79
|
-
composition/
|
|
80
|
-
index.ts
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
The package remains the boundary. Do not create internal pseudo-packages beneath `features/`.
|
|
84
|
-
|
|
85
|
-
## Generated or standalone application
|
|
86
|
-
|
|
87
|
-
A generated application is an independently installable and buildable project. It is not a
|
|
88
|
-
workspace child of Studio and must not depend on Studio-local source or installation state.
|
|
89
|
-
|
|
90
|
-
Use the Expo application profile when applicable. Application-specific domains live under `src/`;
|
|
91
|
-
route entrypoints remain thin.
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# UI and Design-System Libraries
|
|
2
|
-
|
|
3
|
-
ZORA and Surface are layered reusable UI libraries, not hexagonal applications. From an
|
|
4
|
-
application's perspective, they help build the inbound UI adapter; internally they use component
|
|
5
|
-
ownership and dependency direction rather than application ports and use cases.
|
|
6
|
-
|
|
7
|
-
## Recommended layers
|
|
8
|
-
|
|
9
|
-
```text
|
|
10
|
-
src/
|
|
11
|
-
foundation/
|
|
12
|
-
theme/
|
|
13
|
-
layout/
|
|
14
|
-
primitives/
|
|
15
|
-
components/
|
|
16
|
-
patterns/
|
|
17
|
-
registry/
|
|
18
|
-
internal/
|
|
19
|
-
index.ts
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Use only the layers owned by the package. Surface and ZORA must not duplicate the same abstraction.
|
|
23
|
-
|
|
24
|
-
Dependency direction moves from composed UI toward stable foundations:
|
|
25
|
-
|
|
26
|
-
```text
|
|
27
|
-
patterns -> components -> primitives/foundation
|
|
28
|
-
patterns -> layout -> foundation
|
|
29
|
-
components -> theme -> foundation
|
|
30
|
-
registry -> component/pattern metadata
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Lower layers do not import higher layers. Components do not import the global registry.
|
|
34
|
-
|
|
35
|
-
## Component ownership
|
|
36
|
-
|
|
37
|
-
Colocate artifacts that change with the component:
|
|
38
|
-
|
|
39
|
-
```text
|
|
40
|
-
components/
|
|
41
|
-
Button/
|
|
42
|
-
Button.tsx
|
|
43
|
-
Button.types.ts
|
|
44
|
-
Button.metadata.ts
|
|
45
|
-
Button.test.tsx
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Keep a component as a single file while it has one homogeneous responsibility. Introduce its
|
|
49
|
-
directory when tests, metadata, platform variants, styles, or private helpers justify it.
|
|
50
|
-
|
|
51
|
-
Component-specific authoring metadata belongs beside the component. Registry composition may be
|
|
52
|
-
central, but metadata must not become a parallel model detached from implementation and props.
|
|
53
|
-
|
|
54
|
-
## Patterns
|
|
55
|
-
|
|
56
|
-
Patterns are reusable UI solutions, not application use cases. Group substantial collections by
|
|
57
|
-
UI capability:
|
|
58
|
-
|
|
59
|
-
```text
|
|
60
|
-
patterns/
|
|
61
|
-
auth/
|
|
62
|
-
content/
|
|
63
|
-
navigation/
|
|
64
|
-
onboarding/
|
|
65
|
-
settings/
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
A pattern may accept state, values, errors, and callbacks. It must not execute Supabase, HTTP,
|
|
69
|
-
filesystem, deployment, or application authorization behavior.
|
|
70
|
-
|
|
71
|
-
## Platform behavior
|
|
72
|
-
|
|
73
|
-
Keep provider and platform execution in the owning adapter package unless it is an unavoidable
|
|
74
|
-
peer-backed UI implementation. Prefer injected values/callbacks and platform-neutral component
|
|
75
|
-
contracts. Expo Runtime and provider packages own application/platform integration.
|
|
76
|
-
|
|
77
|
-
Do not introduce `domain`, `application`, `ports`, or `adapters` for ordinary components. A complex
|
|
78
|
-
subsystem such as an editor, canvas, or data grid may use internal model/platform boundaries when
|
|
79
|
-
real complexity warrants them.
|