@ankhorage/devtools 1.10.11 → 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.
@@ -1,29 +1,33 @@
1
1
  ---
2
2
  name: zora-designer
3
3
  description: >
4
- Design one application screen or an ordered screen series, audit a URL or supplied image
5
- evidence, and author an Ankhorage Templates manifest or starter using installed ZORA metadata
6
- and theme APIs. Use for category-driven design decisions, design audits and scoring, visual
7
- reconstruction with ZORA elements, or template creation. Do not use for illustration-only work.
4
+ Configure an owner-backed application design, generate one screen or a coherent screen series,
5
+ audit supplied evidence, and author portable Ankhorage templates. Use for category-driven design
6
+ decisions, ZORA screen generation, visual audits, or template creation.
8
7
  ---
9
8
 
10
9
  # ZORA Designer
11
10
 
12
- Design, audit, and author through the target repository's released owner APIs. Keep
13
- `zora-designer.md` as review evidence; the application manifest and owner contracts remain runtime
14
- authority.
11
+ Design, audit, and author through the target repository's released owner APIs. The complete
12
+ `AppManifest` is runtime authority; `zora-designer.md` and generated screen images are design
13
+ evidence.
15
14
 
16
15
  ## Route the request
17
16
 
18
- - `interactive`: resolve a design configuration without silently creating code or images.
19
- - `screen`: design one screen and, when requested, produce a concept image or implementation.
20
- - `screens`: design a coherent ordered series with shared navigation, state, and tokens.
21
- - `audit`: evaluate a URL, one supplied image, or an ordered image series with explicit evidence.
22
- - `template`: compose a canonical `AppManifest`; in Templates, scaffold the normal starter source
23
- and registry entry.
24
-
25
- Natural language is enough. Ask only when an unresolved choice would materially change the output.
26
- Read [workflow.md](references/workflow.md) for the ordered process and capability gates.
17
+ - `interactive`: run the progressive configuration conversation in
18
+ [workflow.md](references/workflow.md). Do not create code or images before confirmation.
19
+ - `screen`: resolve the configuration, then read [screens.md](references/screens.md) and design one
20
+ screen.
21
+ - `screens`: resolve the configuration, then read [screens.md](references/screens.md) and design an
22
+ ordered series with shared navigation, state, geometry, and tokens.
23
+ - `audit`: read [audit.md](references/audit.md) and evaluate supplied image or runtime evidence.
24
+ - `template`: resolve the configuration and screen model, then author one portable template through
25
+ the workflow below.
26
+
27
+ Natural language is enough. Treat short replies as answers to the current question, not permission
28
+ to infer later decisions. A reply such as “go on” advances to the next unresolved decision. Only an
29
+ explicit request such as “accept all recommended values” resolves the remaining recommendations at
30
+ once.
27
31
 
28
32
  ## Start with the owners
29
33
 
@@ -33,57 +37,60 @@ From the target repository, run:
33
37
  bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
34
38
  ```
35
39
 
36
- The helper loads public exports from installed `@ankhorage/templates`, `@ankhorage/zora/theme`, and
37
- `@ankhorage/zora/metadata`. If it reports a missing or outdated owner, update the released package
38
- through the repository's normal dependency workflow. Never copy a catalog, token inventory, color
39
- algorithm, theme compiler, component schema, or manifest implementation into this skill.
40
-
41
- Use owner terminology verbatim: `GeneratedColorRole`, `ThemeTokens.colors`, `ThemeSemantics`, and
42
- `SemanticColorToken`. Do not introduce aliases or naming cleanup here.
40
+ Use its installed owner output for categories, recommendations, harmonies, tone pairs, navigation
41
+ types, ZORA elements, events, recipes, and version provenance. Never copy owner catalogs, component
42
+ schemas, token inventories, color algorithms, action types, or manifest implementations into this
43
+ skill.
43
44
 
44
- ## Preserve decisions and evidence
45
+ Compile chosen values with the same helper before composing screens. Inspect both computed modes,
46
+ including their resolved Surface themes and all owner diagnostics. Never hand-calculate a value the
47
+ owner exposes.
45
48
 
46
- Resolve values in this order: current request, current session, verified project state, confirmed
47
- existing brief target, category preset, then safe global default. Record an origin for each value.
48
- Keep target design separate from observed runtime state and report drift rather than overwriting one
49
- with the other.
49
+ ## Preserve the complete UX
50
50
 
51
- For scoring, evidence limits, release gates, and finding rules, read
52
- [audit.md](references/audit.md). For deterministic `zora-designer.md` shape and serialization, read
53
- [artifact.md](references/artifact.md).
51
+ For every screen region, prefer the exact semantic ZORA element supported by current metadata.
52
+ Visual resemblance alone is insufficient. If no exact element exists, preserve the requested UX
53
+ with an obvious supported placeholder such as a secondary-surface `Box`, and record the capability
54
+ gap. Do not invent props, application components, or successful behavior.
54
55
 
55
- ## Compose only supported ZORA elements
56
+ Bind every interaction expressible by installed Contracts and ZORA event metadata. Leave an
57
+ unsupported interaction visibly present and explicitly unbound without blocking unrelated design
58
+ work. Release validation still decides whether the complete manifest is shippable.
56
59
 
57
- Inspect `ZORA_COMPONENT_META` and `ZORA_THEME_RECIPE_META`. Map a source region only when the
58
- element's semantic responsibility, structure, states/interactions, accessibility contract, props,
59
- events, and data requirements fit. Visual resemblance alone is insufficient.
60
+ ## Template output
60
61
 
61
- If no exact element fits:
62
+ A Templates repository template is exactly one portable unit:
62
63
 
63
- 1. use the metadata-backed `MissingElement` draft node at that location;
64
- 2. record the evidence, warning, requested capability, and application blocker;
65
- 3. create or link a ZORA owner issue;
66
- 4. stop release application until a released real element replaces it.
64
+ ```text
65
+ src/templates/categories/{appCategory}/{slug}/
66
+ createAppManifest.ts
67
+ assets/
68
+ screens/
69
+ images/
70
+ ```
67
71
 
68
- Never disguise the gap with a generic container, invented prop, or custom application workaround.
72
+ `createAppManifest.ts` default-exports a function returning the complete `AppManifest`.
73
+ `assets/screens/` contains design evidence only. Runtime media uses real application image regions
74
+ under `assets/images/`; rebuild text, controls, icons, surfaces, and layout with ZORA.
69
75
 
70
- ## Capability truthfulness
76
+ Scaffold only a reviewed, release-valid manifest:
71
77
 
72
- - Concept image output requires an available image-generation capability.
73
- - URL intake and authoritative runtime screenshots require a browser/runtime capture capability.
74
- - Image audits require the supplied original image or ordered series.
78
+ ```text
79
+ bun .agents/skills/zora-designer/scripts/scaffold-template.mjs scaffold-input.json
80
+ ```
75
81
 
76
- When a required capability or source is unavailable, report that deliverable or evidence scope as
77
- blocked. Do not simulate an image, browser observation, or runtime proof. Clearly distinguish
78
- generated concept images from authoritative runtime captures.
82
+ The helper creates the template directory and regenerates discovery from the filesystem. Do not add
83
+ category registries, seed definitions, fallback templates, compatibility paths, or per-template
84
+ barrels.
79
85
 
80
86
  ## Validate before handoff
81
87
 
82
- - Compile the category design and both theme modes through installed owner APIs.
83
- - Validate manifest composition and preserve every owner diagnostic.
84
- - Confirm every manifest node and recipe against current ZORA metadata.
85
- - Run deterministic audit arithmetic when an audit is in scope.
86
- - Keep screenshot-only behavior `not-assessable`.
87
- - Require a ready application gate before release; a score never overrides a blocker.
88
- - Update `zora-designer.md` and report target/runtime drift, unsupported capabilities, and owner
89
- issues.
88
+ - confirm the interactive decision sequence completed or the user explicitly accepted remaining
89
+ recommendations;
90
+ - compile light and dark independently through installed owner APIs;
91
+ - validate selected ZORA nodes, props, events, actions, and complete manifest contracts;
92
+ - run the screen composition gate from [screens.md](references/screens.md) before returning screen
93
+ output;
94
+ - keep unsupported interactions and capability gaps explicit;
95
+ - keep concept screens separate from runtime assets;
96
+ - for deterministic artifact shape, read [artifact.md](references/artifact.md).
@@ -67,8 +67,10 @@ Aggregate validation in this order: blocker, failure, not run, pass with non-blo
67
67
  pass. Resolve the application gate separately. A runtime blocker affects aggregate status only when
68
68
  runtime application is in the requested scope.
69
69
 
70
- Every unresolved `MissingElement` records a blocker, owner issue link when one exists, and the
71
- condition for replacing it with a released real ZORA element.
70
+ Every capability that lacks an exact metadata-supported ZORA element records a non-blocking
71
+ capability gap and uses a visible `Box` placeholder in the concept composition. An owner diagnostic
72
+ with `severity: error` is a blocker. Record an owner issue link when one exists and the condition
73
+ for replacing the placeholder with a released real ZORA element.
72
74
 
73
75
  ## Deterministic audit fields
74
76
 
@@ -89,5 +91,5 @@ state that it was not persisted.
89
91
 
90
92
  Before returning, confirm the artifact matches the requested mode, every resolved value has an
91
93
  origin, owner identifiers were inspected, exact measurements came from tools, invisible behavior
92
- was not passed from screenshots, gaps block application, and the manifest remains canonical runtime
93
- authority.
94
+ was not passed from screenshots, owner errors block release, capability gaps are explicit, and the
95
+ manifest remains canonical runtime authority.
@@ -0,0 +1,90 @@
1
+ # Screen and Screen-Series Design
2
+
3
+ Read this reference for `screen` and `screens` after the interactive configuration is confirmed.
4
+ Generated images are concept evidence; the manifest and ZORA metadata remain implementation
5
+ authority.
6
+
7
+ ## Preconditions
8
+
9
+ Do not generate or implement a screen until these are resolved:
10
+
11
+ - audience and primary task;
12
+ - platform, input modes, theme coverage, and an explicit target viewport or responsive range;
13
+ - primary color, harmony, per-mode tone pairs, typography/profile, and compiled owner themes;
14
+ - ordered screen list with one purpose and primary action per screen;
15
+ - navigator type, route topology, and back/cancel behavior.
16
+
17
+ If the user supplies only `mobile`, recommend a neutral 390 by 844 logical-point portrait concept
18
+ viewport. Ask about iOS- or Android-specific chrome only when it changes the requested result.
19
+
20
+ ## Compose the series before rendering
21
+
22
+ For every screen record:
23
+
24
+ 1. purpose, primary task, primary action, and success outcome;
25
+ 2. content hierarchy and exact visible copy;
26
+ 3. default plus relevant loading, empty, partial, error, offline, disabled, pressed, selected, and
27
+ focus states;
28
+ 4. route relationship and continuity of selected item, filters, progress, drafts, or other state;
29
+ 5. exact metadata-backed ZORA elements, supported events/actions, data needs, and capability gaps;
30
+ 6. safe areas, keyboard/overlay behavior, scroll ownership, and narrow/wide behavior;
31
+ 7. real image content that must become an application asset rather than screenshot UI.
32
+
33
+ Create one shared shell specification for a series: viewport, safe areas, gutters, header geometry,
34
+ navigation geometry, surface treatment, type roles, icon family, content density, and state styling.
35
+ Reuse it unchanged unless a screen has a documented immersive or modal exception.
36
+
37
+ ## Mobile geometry and typography gate
38
+
39
+ Use compiled Surface/ZORA tokens when they define these values. Where the owner leaves a design-
40
+ brief decision open, use these starting constraints and record them rather than asking an image
41
+ model to improvise:
42
+
43
+ - 16–24 logical-point horizontal gutters and preserved top/bottom safe areas;
44
+ - at least 44 by 44 pt touch bounds, or the stricter target-platform convention;
45
+ - screen title uses the semantic `h1` role, normally near 32/40 on mobile, one line when practical
46
+ and never more than two; reserve `display` for splash, onboarding, or genuine marketing moments;
47
+ - section titles use `h2`/`h3`; body content normally starts near 16/24; metadata never becomes tiny
48
+ merely to fit more content;
49
+ - prose measures approximately 45–75 characters per line, with reading surfaces optimized near the
50
+ comfortable middle of that range;
51
+ - bottom navigation uses one stable height, label baseline, icon family, selected treatment, and
52
+ safe-area inset across the series;
53
+ - a screen must expose its purpose, primary content, and primary action without competing giant
54
+ headings or decorative elements.
55
+
56
+ Inspect the skill-owned design rules before rendering:
57
+
58
+ ```text
59
+ bun .agents/skills/zora-designer/scripts/audit.mjs catalog
60
+ ```
61
+
62
+ Apply every relevant `generation` rule from that catalog. The rubric belongs to this skill; owner
63
+ catalogs and design-system algorithms do not.
64
+
65
+ ## Concept-image specification
66
+
67
+ Generate one image per distinct screen or state. Every prompt must include:
68
+
69
+ - exact logical viewport and portrait/landscape orientation;
70
+ - shared shell and active navigator item;
71
+ - resolved owner palette and type-role sizes, not only mood adjectives;
72
+ - exact screen title, copy that must be legible, hierarchy, components, and primary action;
73
+ - content quantity that fits the viewport at the declared type scale;
74
+ - invariants shared with the other screens;
75
+ - prohibitions against device frames, presentation boards, watermarks, illegible labels, invented
76
+ tabs, and oversized marketing typography on ordinary application screens.
77
+
78
+ For a series, keep the resolved configuration and shell verbatim across prompts. Do not generate a
79
+ single contact sheet as a substitute for individual screens unless the user explicitly requests one.
80
+
81
+ ## Review and iterate
82
+
83
+ Inspect each output at its original dimensions. Reject and regenerate a screen when its title,
84
+ navigation, copy, safe areas, hierarchy, component semantics, or geometry diverges from the brief.
85
+ Use one targeted correction per iteration. Review the full ordered series together for shared shell,
86
+ type scale, navigation, color allocation, state continuity, and visual rhythm.
87
+
88
+ A visual review can validate visible composition only. It cannot prove runtime actions,
89
+ accessibility semantics, focus order, text scaling, or responsive behavior; record those as
90
+ not-assessable until implementation evidence exists.
@@ -1,137 +1,95 @@
1
- # Design, Composition, and Template Workflow
2
-
3
- Use this workflow for `interactive`, `screen`, `screens`, and `template`. Audit uses the same
4
- compiled target baseline when a redesign is requested; it never fills unknown observed values with
5
- presets and pretends they were measured.
6
-
7
- ## 1. Establish mode, intent, and evidence
8
-
9
- Record the audience, primary task, requested screen or ordered series, platform, input modes,
10
- viewport constraints, target theme modes, available source artifacts, and requested deliverable.
11
- Inspect the current manifest, theme, installed packages, ZORA metadata, fonts, and existing
12
- `zora-designer.md` before prompting.
13
-
14
- For an input image or series, decompose each screen into semantic regions. Give each region a stable
15
- evidence ID, purpose, content/data responsibility, state and interaction needs, accessibility needs,
16
- and its relationship to other regions. Preserve image order and original dimensions. Do not map by
17
- appearance alone.
18
-
19
- ## 2. Build the design in dependency order
20
-
21
- Interactive questions and compiler calls follow this exact order. Skip a question only when the
22
- answer is already supplied or reliably discovered.
23
-
24
- 1. **Category and intent.** Read `APP_CATEGORIES` and `CATEGORY_PRESETS` from Templates. Show the
25
- preset's ordered primary-color and font-family recommendations and explain that they are starting
26
- points, not user stereotypes. Defer font activation until its availability and owning loader are
27
- verified.
28
- 2. **Primary color.** Offer the preset's first recommended primary color and any verified project
29
- brand color. Let the user accept or supply a color. The owner normalizes and validates it.
30
- 3. **Harmony.** Only after primary is resolved, show `recommendedHarmonies` and the installed owner
31
- harmony catalog. Preserve the selected canonical identifier.
32
- 4. **Per-mode tone combination.** Show the preset recommendation first, then read all valid choices
33
- from `TONE_PAIR_CATALOG`. Resolve light and dark independently with `resolveTonePair`; do not copy
34
- a list into prose or code.
35
- 5. **Generated colors.** Call `resolveCategoryDesignPreset` or `compileCategoryDesign`. Inspect
36
- generated owner roles and diagnostics; never hand-calculate secondary, accent, severity,
37
- background, neutral, or on-colors.
38
- 6. **Computed Surface output.** Inspect `computedTheme.light.surfaceTheme` and
39
- `computedTheme.dark.surfaceTheme`, including `ThemeTokens.colors`, `ThemeSemantics`, provenance,
40
- selections, and diagnostics. Record `GeneratedColorRole` and `SemanticColorToken` names exactly.
41
- 7. **Composition.** Resolve density, shape, layout, navigation, content hierarchy, states, and exact
42
- ZORA elements from metadata. Typography recommendations remain advisory until the chosen font is
43
- verified and the owning app/module can load it; never install or bundle a font implicitly.
44
- 8. **Confirmation and output.** Show high-impact choices, their origins, diagnostics, gaps, and the
45
- requested artifact. Persist runtime state only through the canonical manifest/contracts.
46
-
47
- Use separate light/dark tone pairs and owner compilation even when the two modes share a primary
48
- seed. Never generate dark mode by inversion.
49
-
50
- ## 3. Inspect installed owner APIs
51
-
52
- Run from the target repository:
1
+ # Owner-Backed Interactive and Template Workflow
53
2
 
54
- ```text
55
- bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
56
- ```
57
-
58
- The output reports installed versions, category presets, tone pairs, component metadata, recipe
59
- metadata, and required exports. An error names the missing package/export, minimum owner release,
60
- and update action. There is no fallback calculation.
61
-
62
- For deterministic composition, provide JSON to:
63
-
64
- ```text
65
- bun .agents/skills/zora-designer/scripts/owner-api.mjs compose design-input.json
66
- ```
67
-
68
- The input contains `category`, optional `theme` overrides, `navigator`, `screens`, and optional
69
- `regions`. Each region has `id`, `requestedCapability`, `screenId`, `parentNodeId`, an optional exact
70
- `component`, explicit `props`, and `evidenceId`. The helper:
71
-
72
- - compiles the category through Templates and the released ZORA compiler;
73
- - validates exact component names and props against `ZORA_COMPONENT_META`;
74
- - emits metadata-derived `MissingElement` nodes for unresolved regions;
75
- - composes the canonical manifest in draft mode while a gap exists;
76
- - reports `applicationGate: blocked` and owner actions for every gap;
77
- - preserves Templates and ZORA diagnostics.
78
-
79
- The agent remains responsible for semantic matching. A requested component name is an explicit
80
- decision to validate, not a fuzzy-search request.
3
+ Use this workflow for `interactive`, `screen`, `screens`, and `template`. Ask only about unresolved
4
+ decisions, one decision at a time. Show the owner recommendation first with a short reason. Do not
5
+ silently accept later recommendations because an earlier answer was brief.
81
6
 
82
- ## 4. Design one screen or a series
7
+ ## 1. Inspect intent and owners
83
8
 
84
- For each screen define purpose, information hierarchy, primary action, navigation relationship,
85
- data needs, and relevant default/loading/empty/partial/error/offline/success/disabled/hover/pressed/
86
- selected/focus states. Define narrow and wide behavior where applicable.
87
-
88
- For a series also define the shared shell, canonical route topology, state continuity, back/cancel
89
- behavior, shared recipes and terminology, and one common theme. A concept image series must remain
90
- ordered and visually coherent. Label every generated image as a concept; replace it with an actual
91
- runtime capture only after running the manifest.
92
-
93
- ## 5. Audit a URL, image, or series
94
-
95
- For a URL, capture the relevant modes, viewports, and states using a browser/runtime tool. For one
96
- image or a series, retain original dimensions and input order. Every evidence item records location,
97
- observation, evidence level, confidence factor, reproduction, and limitations.
98
-
99
- Read [audit.md](audit.md), create a criterion input JSON, and run:
9
+ Read repository instructions, an existing manifest or `zora-designer.md`, installed fonts, platform
10
+ configuration, and relevant screens. Run:
100
11
 
101
12
  ```text
102
- bun .agents/skills/zora-designer/scripts/audit.mjs audit-input.json zora-designer.md
13
+ bun .agents/skills/zora-designer/scripts/owner-api.mjs inspect
103
14
  ```
104
15
 
105
- Invisible behavior remains `not-assessable` unless source, accessibility-tree, browser, or runtime
106
- evidence supports it. A recommended redesign baseline may use the category workflow but must be
107
- clearly separated from observed implementation.
108
-
109
- ## 6. Author a canonical template
110
-
111
- Use `composeCategoryAppManifest`, `validateTemplateManifest`, and
112
- `assertTemplateManifestReady`. Draft output may contain `MissingElement`; release output may not.
113
-
114
- When the target repository is `@ankhorage/templates`, scaffold reviewed composition output with:
16
+ The output is the choice source. Do not use remembered categories, color options, tone pairs,
17
+ navigator types, ZORA elements, or events. Resolve explicit user input before project state,
18
+ existing brief values, category recommendations, and global defaults. Record the origin of every
19
+ resolved decision.
20
+
21
+ ## 2. Ask in dependency order
22
+
23
+ Advance through this sequence. Skip only a value already supplied or reliably discovered.
24
+
25
+ 1. **Category and intent.** Resolve the canonical app category, product name, audience, and primary
26
+ task. For `screen` and `screens`, audience and primary task are required.
27
+ 2. **Platform and theme coverage.** Resolve mobile, desktop, or responsive scope; technology;
28
+ input modes; target viewport or content constraints; light, dark, or both; and the default mode
29
+ when relevant.
30
+ 3. **Primary color.** Show the category recommendations in owner order and any verified project
31
+ brand color. Let the user accept one or supply a CSS color. Preserve the brand seed; the owner
32
+ compiler selects accessible functional steps.
33
+ 4. **Harmony.** After primary is resolved, show the category recommendation first and only the
34
+ harmony identifiers reported by Color Theory. Explain the visual energy and complexity using the
35
+ owner catalog descriptions.
36
+ 5. **Tone pairs.** After harmony is resolved, offer the category's separate light and dark
37
+ recommendations, followed by mode-compatible owner entries. Explain that `X-on-Y` means accent
38
+ family on foundation family; structural foregrounds remain independently resolved semantic
39
+ tokens.
40
+ 6. **Typography and profile.** Resolve a verified body font, optional heading pairing, density,
41
+ shape, motion, and contrast target. Do not install fonts. Offer the category/profile
42
+ recommendation first and allow the user to accept the recommended system settings together.
43
+ 7. **Screens.** Ask for the ordered screen list and purpose of each screen. Then establish the
44
+ primary action, essential content, required data states, and continuity across the series. Do not
45
+ generate images yet.
46
+ 8. **Navigator.** After the screen topology exists, offer only Contracts navigator types and map
47
+ every route, initial route, hidden/detail route, back/cancel path, and primary-navigation label.
48
+ 9. **Compile and confirm.** Compile both theme modes through the owner helper. Summarize high-impact
49
+ decisions, origins, diagnostics, unsupported capabilities, screen order, and route topology. Ask
50
+ for confirmation before creating screen images, code, or a template.
51
+
52
+ If the user changes an earlier decision, invalidate and revisit only dependent later decisions.
53
+ Changing primary invalidates harmony-derived compilation and tone output but does not erase the
54
+ screen brief. Changing the screen list invalidates navigator confirmation.
55
+
56
+ ## 3. Compile owner output
57
+
58
+ Provide the resolved `category`, theme overrides, navigator, screens, and region decisions to:
115
59
 
116
60
  ```text
117
- bun .agents/skills/zora-designer/scripts/scaffold-template.mjs scaffold-input.json
61
+ bun .agents/skills/zora-designer/scripts/owner-api.mjs compose design-input.json
118
62
  ```
119
63
 
120
- The scaffold input provides the ready manifest, category, template ID, label, description, and
121
- target root. The helper verifies the Templates repository and owner validation, creates a normal
122
- variant `manifest.ts`, `template.ts`, and `index.ts`, and updates the category registry import and
123
- definition deterministically. It refuses existing targets, unsafe identifiers, blocked manifests,
124
- and non-Templates repositories. Review and validate the generated production diff; do not hand-hide
125
- a systemic skill or owner defect.
126
-
127
- ## 7. Delivery gates
128
-
129
- Before application or release:
130
-
131
- - every visible/interactive region has an exact metadata-supported element;
132
- - every gap has evidence and a linked ZORA owner issue;
133
- - both modes compile without owner errors;
134
- - the manifest is owner-validated and release-ready;
135
- - required concept-image, runtime-capture, or supplied-image capabilities were actually available;
136
- - target and observed state are clearly separated;
137
- - `zora-designer.md` is deterministic and contains no runtime authority or copied owner catalogs.
64
+ Use owner-returned theme configuration, generated roles, computed Surface themes, diagnostics,
65
+ component metadata, and manifest composition. Light and dark are independent compilations; never
66
+ derive dark mode by inversion. A failed owner diagnostic remains visible. Do not replace it with
67
+ local math or an undocumented token.
68
+
69
+ For every region, validate its exact component name, props, allowed-parent relationship, and event
70
+ metadata. Use supported navigation and actions when available. Represent missing capability without
71
+ removing the intended flow or inventing a contract.
72
+
73
+ ## 4. Produce the requested deliverable
74
+
75
+ - `interactive`: write or return the confirmed `zora-designer.md`; do not produce screen images or
76
+ implementation unless requested.
77
+ - `screen` or `screens`: read [screens.md](screens.md), create the screen specification, then produce
78
+ only the requested image, code, or manifest deliverable.
79
+ - `template`: author a complete `AppManifest`, validate it in release mode, generate or extract every
80
+ required runtime image, and run the Templates scaffolder.
81
+
82
+ For design-first templates, store reference images under `assets/screens/` and crop only real image
83
+ content into `assets/images/`. For direct authoring, generate application images directly under
84
+ `assets/images/`. Runtime media never references a complete screen image.
85
+
86
+ ## 5. Delivery checks
87
+
88
+ - every decision is explicit, discovered, or carries a recorded default origin;
89
+ - both theme modes compile without hidden local fallback calculations;
90
+ - the screen model and navigator agree;
91
+ - every selected ZORA node and prop is metadata-supported;
92
+ - supported events/actions are bound and unsupported behavior is recorded;
93
+ - screen generation passes the composition gate;
94
+ - release templates contain one default-exported `createAppManifest()` and bundled media paths;
95
+ - the complete manifest validates against the installed owner contract.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { access, readdir, writeFile } from 'node:fs/promises';
4
+ import { join, resolve } from 'node:path';
5
+
6
+ import { loadOwnerApis } from './owner-api.mjs';
7
+
8
+ const CATEGORY_ROOT = 'src/templates/categories';
9
+
10
+ /*** Regenerate the portable template catalog from canonical template directories. */
11
+ export async function generateTemplateCatalog(targetDirectory = process.cwd(), appCategories) {
12
+ const root = resolve(targetDirectory);
13
+ const canonicalAppCategories =
14
+ appCategories ?? (await loadOwnerApis(root)).contracts.APP_CATEGORIES;
15
+ const categoriesRoot = join(root, CATEGORY_ROOT);
16
+ const definitions = [];
17
+
18
+ for (const categoryEntry of await readDirectories(categoriesRoot)) {
19
+ const category = categoryEntry.name.replaceAll('-', '_');
20
+ if (!canonicalAppCategories.includes(category)) {
21
+ throw new Error(`Unknown app category directory: ${categoryEntry.name}`);
22
+ }
23
+
24
+ const categoryRoot = join(categoriesRoot, categoryEntry.name);
25
+ for (const templateEntry of await readDirectories(categoryRoot)) {
26
+ const manifestPath = join(categoryRoot, templateEntry.name, 'createAppManifest.ts');
27
+ if (!(await pathExists(manifestPath))) continue;
28
+ definitions.push({
29
+ category,
30
+ categoryDirectory: categoryEntry.name,
31
+ slug: templateEntry.name,
32
+ });
33
+ }
34
+ }
35
+
36
+ definitions.sort((left, right) =>
37
+ `${left.category}/${left.slug}`.localeCompare(`${right.category}/${right.slug}`),
38
+ );
39
+
40
+ const imports = definitions
41
+ .map(
42
+ (definition, index) =>
43
+ `import createAppManifest${index} from './categories/${definition.categoryDirectory}/${definition.slug}/createAppManifest';`,
44
+ )
45
+ .join('\n');
46
+ const entries = definitions
47
+ .map(
48
+ (definition, index) => ` {
49
+ category: '${definition.category}',
50
+ slug: '${definition.slug}',
51
+ sourceRoot: '${CATEGORY_ROOT}/${definition.categoryDirectory}/${definition.slug}',
52
+ createAppManifest: createAppManifest${index},
53
+ },`,
54
+ )
55
+ .join('\n');
56
+ const definitionsSource = definitions.length === 0 ? '[]' : `[\n${entries}\n]`;
57
+
58
+ const source = `${imports}${imports ? '\n\n' : ''}import type { TemplateDefinition } from './catalog';
59
+
60
+ export const TEMPLATE_DEFINITIONS: readonly TemplateDefinition[] = ${definitionsSource};
61
+ `;
62
+
63
+ const outputPath = join(root, 'src/templates/catalog.generated.ts');
64
+ await writeFile(outputPath, source, 'utf8');
65
+ return { outputPath, templateCount: definitions.length };
66
+ }
67
+
68
+ /*** Read child directories in stable lexical order. */
69
+ async function readDirectories(directory) {
70
+ return (await readdir(directory, { withFileTypes: true }))
71
+ .filter((entry) => entry.isDirectory())
72
+ .sort((left, right) => left.name.localeCompare(right.name));
73
+ }
74
+
75
+ /*** Return whether a filesystem path exists. */
76
+ async function pathExists(filePath) {
77
+ try {
78
+ await access(filePath);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ if (import.meta.main) {
86
+ const targetDirectory = process.argv[2] ?? process.cwd();
87
+ const result = await generateTemplateCatalog(targetDirectory);
88
+ console.log(`Generated ${result.templateCount} template catalog entries.`);
89
+ }
@@ -5,11 +5,23 @@ import { dirname, join, parse, resolve } from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
 
7
7
  const OWNER_RELEASES = {
8
+ colorTheory: { packageName: '@ankhorage/color-theory', minimumVersion: '0.3.0' },
9
+ contracts: { packageName: '@ankhorage/contracts', minimumVersion: '8.2.0' },
8
10
  templates: { packageName: '@ankhorage/templates', minimumVersion: '8.0.0' },
9
11
  zora: { packageName: '@ankhorage/zora', minimumVersion: '4.0.0' },
10
12
  };
11
13
 
12
14
  const OWNER_REQUIREMENTS = {
15
+ colorTheory: {
16
+ ...OWNER_RELEASES.colorTheory,
17
+ specifier: '@ankhorage/color-theory',
18
+ exports: ['COLOR_HARMONIES', 'COLOR_HARMONY_CATALOG'],
19
+ },
20
+ contracts: {
21
+ ...OWNER_RELEASES.contracts,
22
+ specifier: '@ankhorage/contracts',
23
+ exports: ['APP_CATEGORIES', 'NAVIGATOR_TYPES'],
24
+ },
13
25
  templates: {
14
26
  ...OWNER_RELEASES.templates,
15
27
  specifier: '@ankhorage/templates',
@@ -45,10 +57,14 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
45
57
  }
46
58
 
47
59
  return {
60
+ colorTheory: loaded.colorTheory.module,
61
+ contracts: loaded.contracts.module,
48
62
  templates: loaded.templates.module,
49
63
  zoraTheme: loaded.zoraTheme.module,
50
64
  zoraMetadata: loaded.zoraMetadata.module,
51
65
  versions: {
66
+ colorTheory: loaded.colorTheory.version,
67
+ contracts: loaded.contracts.version,
52
68
  templates: loaded.templates.version,
53
69
  zora: loaded.zoraTheme.version,
54
70
  },
@@ -58,22 +74,36 @@ export async function loadOwnerApis(targetDirectory = process.cwd()) {
58
74
  /*** Return installed catalogs and metadata names without copying owner definitions. */
59
75
  export async function inspectOwnerApis(targetDirectory = process.cwd()) {
60
76
  const owners = await loadOwnerApis(targetDirectory);
77
+ const componentMetadata = Object.values(owners.zoraMetadata.ZORA_COMPONENT_META);
61
78
  return {
62
79
  versions: owners.versions,
63
- categories: Object.values(owners.templates.CATEGORY_PRESETS).map((preset) => ({
64
- category: preset.category,
65
- label: preset.label,
66
- recommendedPrimaryColors: preset.recommendedPrimaryColors,
67
- recommendedHarmonies: preset.recommendedHarmonies,
68
- tonePairs: preset.tonePairs,
69
- })),
80
+ appCategories: owners.contracts.APP_CATEGORIES,
81
+ categoryPresets: owners.templates.CATEGORY_PRESETS,
82
+ harmonyIds: owners.colorTheory.COLOR_HARMONIES,
83
+ harmonies: owners.colorTheory.COLOR_HARMONY_CATALOG,
70
84
  tonePairs: owners.templates.TONE_PAIR_CATALOG,
71
- components: Object.keys(owners.zoraMetadata.ZORA_COMPONENT_META).sort(),
85
+ navigatorTypes: owners.contracts.NAVIGATOR_TYPES,
86
+ components: componentMetadata.map((meta) => meta.name).sort(),
87
+ events: componentMetadata
88
+ .flatMap((meta) =>
89
+ Object.values(meta.events ?? {}).map((event) => ({
90
+ component: meta.name,
91
+ eventType: event.eventType,
92
+ label: event.label,
93
+ description: event.description,
94
+ payloadFields: event.payloadFields,
95
+ })),
96
+ )
97
+ .sort((left, right) =>
98
+ `${left.component}:${left.eventType}`.localeCompare(
99
+ `${right.component}:${right.eventType}`,
100
+ ),
101
+ ),
72
102
  themeRecipes: Object.keys(owners.zoraMetadata.ZORA_THEME_RECIPE_META).sort(),
73
103
  };
74
104
  }
75
105
 
76
- /*** Compile category design, validate region nodes, and compose one canonical draft or release manifest. */
106
+ /*** Compose one design without turning missing runtime/UI capabilities into a design blocker. */
77
107
  export async function composeDesign(input, targetDirectory = process.cwd()) {
78
108
  const owners = await loadOwnerApis(targetDirectory);
79
109
  assertRecord(input, 'Design input');
@@ -90,7 +120,6 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
90
120
  const design = owners.templates.compileCategoryDesign(input.category, input.theme ?? {});
91
121
  const { computedTheme, ...resolvedDesign } = design;
92
122
  const requestedAuthoringState = input.authoringState === 'release' ? 'release' : 'draft';
93
- const authoringState = regionResult.gaps.length === 0 ? requestedAuthoringState : 'draft';
94
123
  const composition = owners.templates.composeCategoryAppManifest({
95
124
  category: input.category,
96
125
  name: input.name,
@@ -103,14 +132,14 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
103
132
  modules: input.modules,
104
133
  modulesConfig: input.modulesConfig,
105
134
  theme: input.theme,
106
- authoringState,
135
+ authoringState: requestedAuthoringState,
107
136
  });
108
137
  const ownerDiagnostics = [
109
138
  ...design.diagnostics,
110
139
  ...computedTheme.diagnostics,
111
140
  ...composition.diagnostics,
112
141
  ];
113
- const blocked = regionResult.gaps.length > 0 || composition.status === 'blocked';
142
+ const blockers = ownerDiagnostics.filter((diagnostic) => diagnostic.severity === 'error');
114
143
 
115
144
  return {
116
145
  owners: owners.versions,
@@ -118,14 +147,15 @@ export async function composeDesign(input, targetDirectory = process.cwd()) {
118
147
  computedTheme,
119
148
  composition,
120
149
  regionDiagnostics: regionResult.diagnostics,
150
+ capabilityGaps: regionResult.gaps,
121
151
  ownerDiagnostics,
122
152
  requestedAuthoringState,
123
- applicationGate: blocked ? 'blocked' : 'pass',
124
- blockers: regionResult.gaps,
153
+ applicationGate: composition.status === 'blocked' ? 'blocked' : 'pass',
154
+ blockers,
125
155
  };
126
156
  }
127
157
 
128
- /*** Resolve explicit region-to-component decisions and insert validated nodes into cloned screens. */
158
+ /*** Resolve explicit region decisions using exact metadata or a visible non-blocking Box placeholder. */
129
159
  export function resolveRegionNodes(screens, regions, componentMeta) {
130
160
  const resolvedScreens = structuredClone(screens);
131
161
  const diagnostics = [];
@@ -152,7 +182,6 @@ export function resolveRegionNodes(screens, regions, componentMeta) {
152
182
  return { screens: resolvedScreens, diagnostics, gaps };
153
183
  }
154
184
 
155
- /*** Resolve one exact metadata-backed component or the owner-defined MissingElement placeholder. */
156
185
  function resolveRegionNode(region, componentMeta) {
157
186
  const component = typeof region.component === 'string' ? region.component : null;
158
187
  const meta = component === null ? null : componentMeta[component];
@@ -170,36 +199,30 @@ function resolveRegionNode(region, componentMeta) {
170
199
  };
171
200
  }
172
201
 
173
- const missingMeta = componentMeta.MissingElement;
174
- if (!missingMeta || missingMeta.manifestPolicy?.kind !== 'unresolved-element') {
175
- throw new Error(
176
- 'Installed @ankhorage/zora metadata does not expose the canonical MissingElement contract.',
177
- );
202
+ const placeholderMeta = componentMeta.Box;
203
+ if (!placeholderMeta?.directManifestNode) {
204
+ throw new Error('Installed @ankhorage/zora metadata does not expose Box as a manifest node.');
178
205
  }
179
- const props = {
180
- ...(missingMeta.blueprint?.defaultProps ?? {}),
181
- requestedCapability: region.requestedCapability,
182
- reason:
183
- typeof region.reason === 'string' && region.reason.trim() !== ''
184
- ? region.reason
185
- : `No exact metadata-supported ZORA element was selected for region "${region.id}".`,
186
- ...(typeof region.evidenceId === 'string' ? { evidenceId: region.evidenceId } : {}),
187
- };
188
- assertSupportedProps(props, missingMeta, region.id);
206
+ const reason =
207
+ typeof region.reason === 'string' && region.reason.trim() !== ''
208
+ ? region.reason
209
+ : `No exact metadata-supported ZORA element was selected for region "${region.id}".`;
189
210
  const gap = {
190
- id: `missing-element:${region.id}`,
191
- scope: 'composition',
192
- owner: '@ankhorage/zora',
211
+ id: `capability-gap:${region.id}`,
212
+ scope: 'capability',
193
213
  regionId: region.id,
194
214
  requestedCapability: region.requestedCapability,
195
215
  evidenceId: region.evidenceId ?? null,
196
- ownerIssueUrl: region.ownerIssueUrl ?? null,
197
- reason: props.reason,
198
- unblockCondition: 'Replace MissingElement with a released exact ZORA element and revalidate.',
216
+ reason,
199
217
  };
200
218
  return {
201
- node: { id: region.id, type: missingMeta.name, props },
202
- diagnostic: { regionId: region.id, status: 'missing', component: missingMeta.name, ...gap },
219
+ node: { id: region.id, type: placeholderMeta.name, props: {} },
220
+ diagnostic: {
221
+ regionId: region.id,
222
+ status: 'placeholder',
223
+ component: placeholderMeta.name,
224
+ ...gap,
225
+ },
203
226
  gap,
204
227
  };
205
228
  }
@@ -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
- /*** Validate and scaffold one ready authored manifest into the normal Templates variant layout. */
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', 'templateId', 'label', 'description']) {
13
+ for (const field of ['targetDirectory', 'category', 'slug']) {
13
14
  assertNonEmptyString(input[field], field);
14
15
  }
15
- if (
16
- !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(input.templateId) ||
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
- const categoryDirectoryName = input.category.replaceAll('_', '-');
38
+
43
39
  const categoryDirectory = resolve(
44
40
  targetDirectory,
45
- 'src/templates/starter/categories',
46
- categoryDirectoryName,
41
+ 'src/templates/categories',
42
+ input.category.replaceAll('_', '-'),
47
43
  );
48
44
  assertInside(targetDirectory, categoryDirectory);
49
- const variantDirectory = resolve(categoryDirectory, input.templateId);
50
- assertInside(categoryDirectory, variantDirectory);
51
- if (await pathExists(variantDirectory)) {
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, variantDirectory)}`,
50
+ `Template source already exists: ${relative(targetDirectory, templateDirectory)}`,
54
51
  );
55
52
  }
56
53
 
57
- const registryPath = join(categoryDirectory, 'index.ts');
58
- const registrySource = await readFile(registryPath, 'utf8');
59
- const symbol = toPascalCase(input.templateId);
60
- const factoryBase = symbol.endsWith('Starter') ? symbol.slice(0, -'Starter'.length) : symbol;
61
- const factoryName = `create${factoryBase}StarterTemplate`;
62
- const manifestName = `AUTHORED_${toConstantCase(input.templateId)}_MANIFEST`;
63
- const registrySourceUpdated = updateCategoryRegistry(registrySource, {
64
- templateId: input.templateId,
65
- label: input.label,
66
- description: input.description,
67
- factoryName,
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
- registryPath: relative(targetDirectory, registryPath),
80
- createdFiles: Object.keys(files).map((fileName) =>
81
- relative(targetDirectory, join(variantDirectory, fileName)),
82
- ),
83
- factoryName,
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
- /*** Create normal manifest, factory, and entrypoint source for one authored starter variant. */
88
- function createTemplateFiles({ manifest, manifestName, factoryName }) {
89
- const manifestSource = `import type { AppManifest } from '@ankhorage/contracts';
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
- import type { TemplateSeed } from '../../../starter.types';
96
- import { ${manifestName} } from './manifest';
81
+ const manifest = ${JSON.stringify(manifest, null, 2)} satisfies AppManifest;
97
82
 
98
- /*** Create the authored starter while applying the caller's canonical app identity and theme. */
99
- export function ${factoryName}(seed: TemplateSeed): AppManifest {
100
- const theme = seed.theme ?? ${manifestName}.themes[0];
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(parentDirectory, childPath) {
178
- const relativePath = relative(parentDirectory, childPath);
179
- if (relativePath === '' || relativePath.startsWith(`..${sep}`) || relativePath === '..') {
180
- throw new Error(`Scaffold path escapes its owner directory: ${childPath}`);
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 already exists. */
185
- async function pathExists(path) {
103
+ /*** Return whether a filesystem path exists. */
104
+ async function pathExists(filePath) {
186
105
  try {
187
- await access(path);
106
+ await access(filePath);
188
107
  return true;
189
- } catch (error) {
190
- if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return false;
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) throw new Error('Usage: scaffold-template.mjs <scaffold-input.json>');
213
- const input = JSON.parse(await readFile(inputPath, 'utf8'));
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.11",
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",