Now I have all the content. Let me compile the complete extraction. Here is every template string from every function across all 7 files:

---

## FILE 1: foundry-prompts-base.js

---
### AIMEAT_CONTEXT (foundry-prompts-base.js:17)
```
You are helping create an AIMEAT service. AIMEAT is an AI agent infrastructure protocol.

Available building blocks:
- CSM (Community Service Manifest): YAML defining data schemas, fields, consent rules, validation.
- MSM (Micro Service Manifest): YAML defining external API integrations, auth, endpoints.
- Extension: V8-sandboxed JavaScript logic with YAML manifest. Actions get ctx object with memory, wallet, consent, trust, fetch APIs.
- App: HTML/JS user interface published to the apps catalog.
- Memory: Key-value storage with namespace isolation.
- Translation: Per-locale i18n strings.
- Cortex: Client-side JS domain library (IIFE on AIMEAT namespace). Wraps extension APIs and memory reads into clean domain methods for apps.

Extensions run in an ISOLATED V8 sandbox with ONLY this API (no Node.js, no global fetch, no setTimeout, no require, no import):
  ctx.memory.get(key) → value or null (ALWAYS null-check before using: `|| []` or `|| {}`)
  ctx.memory.set(key, value) → void
  ctx.memory.search(prefix) → Array<{ key, value }> (NOT plain strings!)
  ctx.memory.delete(key) → boolean
  ctx.memory.getPublic(namespace, key) → value or null (read data from a DIFFERENT namespace)
    Use this to read: (a) another extension's public data, (b) the OWNER's shared data (memory components, translations, settings).
    Example — read another extension's data: await ctx.memory.getPublic('ext:other-ext', 'some.key')
    Example — read owner's shared data: await ctx.memory.getPublic(ctx.caller.gaii, 'lookup.data')
    ╔═══════════════════════════════════════════════════════════════════════════════╗
    ║  IMPORTANT: ctx.memory.get() ONLY reads from the extension's OWN namespace. ║
    ║  Data stored by memory components (seed data, settings, translations) lives  ║
    ║  in the OWNER's namespace — use ctx.memory.getPublic(ctx.caller.gaii, key)  ║
    ║  to access it. ctx.caller.gaii is the caller's GHII identity (e.g. "testuser@node-id"). ║
    ╚═══════════════════════════════════════════════════════════════════════════════╝
  ctx.fetch(url, { method, headers, body }) → { status, ok, text, headers }
    Use ctx.fetch for ALL HTTP requests. Global fetch() is NOT available.
    Response body is always .text (string) — parse JSON with JSON.parse(resp.text).
    Encoding is handled automatically — the runtime detects charset from: (1) Content-Type header,
    (2) XML prolog encoding attribute, (3) HTML meta charset tag. Falls back to UTF-8.
    You always get correct Unicode text — no manual decoding needed, even for ISO-8859-1 feeds.
  ctx.wallet.consume(amount, reason), ctx.wallet.getBalance()
  ctx.consent.check(gaii, scope), ctx.consent.require(gaii, scope)
  ctx.trust.getScore(gaii)
  ctx.caller = { gaii, owner, roles }
  ctx.config = extension config object (from manifest config section)
  ctx.instance = { id, config } (when called via instance endpoint)
  ctx.log.info/warn/error(msg, data)

AIMEAT Data Standards (MUST follow in ALL components):
  Dates/times: ISO 8601 ONLY — "2026-03-14T13:00:00.000Z". NEVER store RFC 2822 ("Sat, 14 Mar ..."), Unix timestamps, or locale-formatted dates. Convert all dates to ISO before storing.
  Memory keys: lowercase dot-namespaced — "items.by-date.2026-03-14". Dates in keys MUST use YYYY-MM-DD.
  IDs: URL-safe strings (kebab-case or hex hashes). No spaces, no special characters.
  Locale codes: BCP 47 — "fi", "en", "fi-FI", "en-US".
  Coordinates: { latitude: number, longitude: number } — WGS84 decimal degrees.
  Currency/amounts: integers (no floats) — morsels are whole numbers.
```

---
### INSTRUCTION_DISCLAIMER (foundry-prompts-base.js:69)
```
IMPORTANT: These are detailed instructions that you MUST read carefully and follow exactly. Every rule, constraint, format requirement, and example below exists for a reason. Do NOT skip sections, do NOT invent your own conventions, and do NOT deviate from the specified output format. If a rule says "MUST" or "NEVER", treat it as absolute.

```

---
### COMPONENT_TEMPLATES.csm (foundry-prompts-base.js:74)
```
${AIMEAT_CONTEXT}

Create a CSM (Community Service Manifest) YAML for: ${label}

${context}

## YAML STRING RULES (read this FIRST — violations cause parse errors)

Every string value MUST be on ONE line wrapped in double quotes. No exceptions.
NEVER use > or | (block scalars). NEVER leave strings unquoted.

WRONG — will crash the parser:
  description: > This is a multi-line folded string
  description: This has (parens) and special: chars
  description: |
    This is a literal block

CORRECT — always do this:
  description: "This has (parens) and special: chars all on one line"

## Structure

Return ONLY valid YAML in a yaml code block. Copy this structure EXACTLY:
```yaml
csm: "1.0"
service:
  name: kebab-case-name
  type: directory
  description: "What this service does — keep on ONE line in double quotes"
  version: "1.0"
schema_mode: open
data_schema:
  required:
    fieldName:
      type: string
    anotherField:
      type: number
  optional:
    optionalField:
      type: string
      enum: [value1, value2]
consent_requirements:
  visibility_default: public
  requires_consent: false
  consent_purpose: "Why consent is needed"
  data_retention: "365_days"
moderation:
  flags_enabled: true
  auto_hide_threshold: 5
  appeals_enabled: false
ui_hints:
  list_view: [fieldName, anotherField]
  detail_view: [fieldName, anotherField, optionalField]
  search_fields: [fieldName]
```

## Additional rules
- data_schema.required and data_schema.optional are MAPS (fieldName: {type: ...}), NOT arrays (- name: ...)
- data_schema.required MUST have at least one field
- Field types: string, number, integer, boolean, array, object
- All date/time fields MUST be type: string with description mentioning ISO 8601 format
- service.name: concise kebab-case identifier, 1-3 words. Use the project's own name when it has one. Do NOT invent generic abbreviations or add redundant suffixes like "-service" or "-monitor".
- ONLY include fields that exist in the raw source data. Computed/derived values (aggregates, scores, trends, risk levels, statistics) are calculated by extensions and stored in separate memory keys — they do NOT belong in the CSM data_schema.
- Avoid redundant fields. If the source provides a unique identifier (e.g. guid), do not add a second id field. If a value is always the same (e.g. single data source), do not include it as a field.
```

---
### COMPONENT_TEMPLATES.msm (foundry-prompts-base.js:139)
```
${AIMEAT_CONTEXT}

Create an MSM (Micro Service Manifest) YAML for: ${label}

${context}

## YAML STRING RULES (read this FIRST — violations cause parse errors)

Every string value MUST be on ONE line wrapped in double quotes. No exceptions.
NEVER use > or | (block scalars). NEVER leave strings unquoted.

WRONG: description: > This is a folded string
WRONG: description: This has (parens) and colons: here
CORRECT: description: "This has (parens) and colons: here — all on one line"

## Structure

Return ONLY valid YAML in a yaml code block. Copy this structure EXACTLY:
```yaml
msm: "1.0"
service:
  name: "Human Readable Service Name"
  description: "What this integration does — one line, double quoted"
  homepage: "https://api.example.com"
  category: data
  tags: [tag1, tag2]
auth:
  type: none
  param_name: ""
  env_var: ""
actions:
  - id: action-id
    display_name: "Human Readable Action Name"
    description: "What this action does"
    endpoint:
      method: GET
      url: "https://api.example.com/path?q={input.query}"
    input:
      query:
        type: string
        required: true
        description: "Search query"
    output:
      result:
        type: string
        description: "The result"
```

## Additional rules
- `service` section with `name`, `description`, `category` is REQUIRED
- `category` must be one of: data, utility, image, communication, analytics, analysis
- `auth.type` must be one of: bearer, query_param, oauth2, api_key, none
- For public APIs (RSS feeds, open data) use `auth.type: none` — many APIs don't require authentication
- `actions` is an array — each action needs: id, display_name, description, endpoint (method + url), input, output
- Each action output MUST have at least one field
```

---
### COMPONENT_TEMPLATES.extension (foundry-prompts-base.js:195)
```
${AIMEAT_CONTEXT}

Create an AIMEAT Extension for: ${label}

${context}

## YAML STRING RULES (read this FIRST — violations cause parse errors)

Every string value MUST be on ONE line wrapped in double quotes. No exceptions.
NEVER use > or | (block scalars). NEVER leave strings unquoted.

WRONG: description: > This is a folded string
WRONG: description: This has (parens) and colons: here
CORRECT: description: "This has (parens) and colons: here — all on one line"

${SANDBOX_CONSTRAINTS}

## ctx.memory API — CRITICAL details

### ctx.memory.get(key) — WILL CRASH if you use JSON.parse()

ctx.memory.get() returns the VALUE directly (already a JS object/array/value), or null.
It is NOT a string. Calling JSON.parse() on it will CRASH with "null is not valid JSON".

╔══════════════════════════════════════════════════════════════════════════╗
║  NEVER WRITE: JSON.parse(await ctx.memory.get(...))                    ║
║  NEVER WRITE: JSON.parse(ctx.memory.get(...))                          ║
║  NEVER WRITE: const x = JSON.parse(someMemoryValue)                   ║
║  These ALL crash. The value is ALREADY PARSED.                          ║
╚══════════════════════════════════════════════════════════════════════════╝

WRONG (CRASHES EVERY TIME):
  const data = JSON.parse(await ctx.memory.get("my.key"));  // "undefined" is not valid JSON

CORRECT:
  const data = await ctx.memory.get("my.key");
  if (!data) return { error: "No data found" };
  // data is already a JS object/array/value — use it directly

### ctx.memory.get() returns null when key does not exist — ALWAYS null-check!

╔══════════════════════════════════════════════════════════════════════════╗
║  ALWAYS check the return value before using it.                         ║
║  Arrays and objects from memory may be null on first run.              ║
╚══════════════════════════════════════════════════════════════════════════╝

WRONG (CRASHES on first run when no data exists yet):
  const index = await ctx.memory.get("items.__index");
  index.some(...)     // Cannot read properties of null (reading 'some')
  index.push(...)     // Cannot read properties of null (reading 'push')
  index.length        // Cannot read properties of null (reading 'length')

CORRECT:
  const index = await ctx.memory.get("items.__index") || [];
  index.some(...)     // works — falls back to empty array

CORRECT (for objects):
  const stats = await ctx.memory.get("daily.stats") || {};
  stats.count = (stats.count || 0) + 1;

### ctx.memory.set(key, value) stores any JSON-serializable value
  await ctx.memory.set("items.2026-03-14", { entries: [...], count: 5 });

### ctx.memory.search(prefix) returns objects, NOT strings — returns ALL matching keys (no pagination)
WARNING: search() loads ALL matching keys into memory at once. If your prefix matches thousands of keys, the V8 sandbox may run out of memory. Use specific prefixes (e.g., "items.by-date.2026-03-14" not "items.")

WRONG:
  const keys = await ctx.memory.search("prefix.");
  for (const key of keys) { await ctx.memory.get(key); }  // ERROR: key is {key,value} not string

CORRECT:
  const results = await ctx.memory.search("prefix.");
  for (const entry of results) {
    const key = entry.key;    // string
    const value = entry.value; // the stored value — already parsed, NOT a string
  }

## Output format — SINGLE block, copy-paste friendly

Return EVERYTHING in ONE code block. The YAML manifest first, then all JavaScript files separated by // actions/filename.js comments. The user will copy-paste the entire response at once.

```
metadata:
  name: kebab-case-name
  version: "1.0.0"
  description: "What this extension does — one line, double quoted"
  author: foundry
required_apis: [memory]
config: {}
limits:
  memory_mb: 128
  timeout_ms: 30000
  max_api_calls: 500
actions:
  - id: first-action
    description: "What this action does"
    method: POST
    path: /v1/ext/{name}/first-action
    auth: required
    input: {}
    output: {}
    script: first-action.js
  - id: second-action
    description: "Another action"
    method: POST
    path: /v1/ext/{name}/second-action
    auth: required
    input:
      type: object
      properties:
        name:
          type: string
      required: [name]
    output:
      type: object
    script: second-action.js
schedules: []

YAML actions format — EVERY action MUST have "- id:" as the FIRST key:
  CORRECT: - id: myAction        (id: is explicit key)
  WRONG:   - myAction            (bare value — causes YAML parse error)
  WRONG:   - myAction:           (colon after name — causes YAML parse error)
NEVER omit "id:" from any action entry. This is the #1 cause of validation failures.
// actions/action-id.js
export default async function(ctx, input) {
  // ── Reading from EXTERNAL APIs (ctx.fetch) ──
  // ctx.fetch() returns { ok, status, text, headers } — text is a RAW string, parse it yourself
  // Encoding is auto-detected (Content-Type header, XML prolog, HTML meta) — you get Unicode text
  const resp = await ctx.fetch('https://example.com/api');
  if (!resp.ok) {
    ctx.log.error('API request failed', { status: resp.status });
    return { error: 'Request failed with status ' + resp.status };
  }
  const data = JSON.parse(resp.text);  // ← correct: resp.text IS a string (for JSON APIs)
  // For XML/RSS: resp.text is already decoded Unicode — parse with regex or string methods

  // ── Reading from EXTENSION'S OWN MEMORY (ctx.memory.get) ──
  // ctx.memory.get() returns a JS value directly — NEVER use JSON.parse
  const stored = await ctx.memory.get("my.key");
  if (!stored) return { items: [] };  // ← stored is already an object, or null

  // ── Reading OWNER'S SHARED DATA (memory components, settings, translations) ──
  // Data stored by memory-1, memory-2 etc. lives in the OWNER's namespace, NOT the extension's.
  // Use getPublic(ctx.caller.gaii, key) to read it:
  const lookup = await ctx.memory.getPublic(ctx.caller.gaii, "lookup.data") || [];
  const settings = await ctx.memory.getPublic(ctx.caller.gaii, "settings.config") || {};

  // ── Writing to EXTENSION'S OWN MEMORY ──
  await ctx.memory.set("results.today", { items: data.results, fetchedAt: new Date().toISOString() });

  return { result: stored };
}
```

CRITICAL: Do NOT use separate code blocks. Put YAML manifest and ALL JavaScript files in ONE block.
Each JavaScript file MUST start with a comment line: // actions/{filename}.js

## Action path — instance vs non-instance

╔══════════════════════════════════════════════════════════════════════════╗
║  Most extensions are SINGLE-INSTANCE (no :instanceId in path).          ║
║  Use: /v1/ext/{name}/action-id                                          ║
║  NEVER add :instanceId unless the blueprint explicitly requires          ║
║  multi-instance support (e.g., per-store, per-tenant separation).       ║
╚══════════════════════════════════════════════════════════════════════════╝

- Default (single-instance): `path: /v1/ext/{name}/action-id`
- Multi-instance (only if needed): `path: /v1/ext/{name}/:instanceId/action-id`

## Scheduled Jobs (schedules section)

Extensions can declare recurring background jobs via `schedules` in the manifest.
AIMEAT's built-in scheduler runs these automatically — no browser needed.

╔══════════════════════════════════════════════════════════════════════════╗
║  If the blueprint has "schedules" for this extension, you MUST include  ║
║  a schedules section in the YAML manifest. This is the ONLY way to     ║
║  register recurring jobs — cortex and apps CANNOT schedule work.       ║
╚══════════════════════════════════════════════════════════════════════════╝

Format:
```yaml
schedules:
  - id: unique-job-id
    action: action-id-from-actions-list
    cron: "*/15 * * * *"
    description: "What this scheduled job does"
    instance_scope: false
    input: {}
```

Rules:
- `action` MUST reference an existing action id from the `actions` array
- `cron` uses standard 5-field cron syntax (minute hour day-of-month month day-of-week) OR the special value `@activate`
- `@activate` trigger: runs when the extension is activated AND on every server restart. Use for init/bootstrap jobs.
- `instance_scope: false` for single-instance extensions (most cases)
- `input` is optional static input passed to the action on each run
- Common patterns: `@activate` (init/bootstrap), `*/15 * * * *` (every 15 min), `0 2 * * *` (daily at 02:00), `0 */6 * * *` (every 6 hours)
- If a nightly job depends on data from a periodic job (e.g., aggregation needs fresh data), schedule it AFTER the last periodic run (e.g., collection at */15, aggregation at 02:00)
- If the blueprint has no "schedules" for this extension, set `schedules: []`

### @activate Init Pattern

If the extension collects or computes data, add an `@activate` scheduled job that:
1. Checks if data exists and is fresh (not stale)
2. If missing or stale, runs the init/collection logic
3. If data is already fresh, does nothing (returns early)

This solves the cold-start problem: after first activation or a server restart, the extension's data is immediately populated instead of waiting for the next cron tick.

Example @activate action script pattern:
```javascript
// Check if data already exists and is recent
const lastRun = await ctx.memory.get('last-ingest-timestamp');
if (lastRun) {
  const age = Date.now() - new Date(lastRun).getTime();
  if (age < 15 * 60 * 1000) return; // Data is fresh (< 15 min), skip
}
// Data is missing or stale — run the init/collection logic
// ... fetch data, store in memory ...
await ctx.memory.set('last-ingest-timestamp', new Date().toISOString());
```

Schedule entry:
```yaml
  - id: init-data
    action: init
    cron: "@activate"
    description: "Initialize/refresh data on activation and server restart"
    instance_scope: false
    input: {}
```

IMPORTANT: @activate actions MUST be idempotent — they will run multiple times (every restart). Always check existing data before overwriting.

### Init Action — Initialize Runtime Data Only

The init/@activate action should initialize extension-owned runtime data (watchlists, caches, logs).
Do NOT copy translations or settings to the extension namespace — cortex reads those
directly from the owner namespace via AIMEAT.data.get().

Pattern:
```javascript
// Initialize extension runtime data if not already present
const watchlist = await ctx.memory.get('watchlist.items');
if (!watchlist) {
  await ctx.memory.set('watchlist.items', []);
  ctx.log.info('Initialized empty watchlist');
}
```

Only initialize keys that the EXTENSION owns and writes to (runtime data).
Translations and settings live in the OWNER namespace — the cortex reads them directly.

## Additional rules
- `metadata` section MUST have: name, version, description, author
- `actions` array MUST NOT be empty — each action needs: id, method, path, script
- Each action's `script` field value must match a `// actions/{script}` comment below the YAML
- `limits.timeout_ms`: use 30000 for extensions that call external APIs, 5000 for memory-only
- `limits.max_api_calls`: use 500 for data collectors (many memory writes per run), 100 for simple actions
- All helper functions must be defined INSIDE the same script file — no imports, no cross-file references
- If two actions need the same helper (e.g., date parsing, data normalization), DUPLICATE the helper in BOTH script files — copy it exactly, do NOT refactor into a shared module
- NEVER reference functions from another action's script — each script runs in its own ISOLATED V8 sandbox scope
- When duplicating helpers across actions, keep them IDENTICAL — if you fix a bug in one copy, fix it in all copies
- NEVER call JSON.parse() on ctx.memory.get() results — they are already parsed JS values
- Always check for undefined/null before using memory values — on first run, NOTHING exists yet
- Always convert dates to ISO 8601 before storing in memory
- OWNER DATA: seed data (memory components), settings, and translations are in the OWNER's namespace
  Use `ctx.memory.getPublic(ctx.caller.gaii, key)` to read them — NOT `ctx.memory.get(key)`.
  `ctx.memory.get()` only reads from the extension's own `ext:{name}` namespace.
  Common pattern: `const data = await ctx.memory.getPublic(ctx.caller.gaii, "lookup.data") || [];`
${HTML_ENTITY_RULES}
```

---
### COMPONENT_TEMPLATES.app (foundry-prompts-base.js:468)

This is a function with dynamic construction. The returned template is:

```
${AIMEAT_CONTEXT}

Create an AIMEAT App (HTML/JS) for: ${label}

${context}

## CRITICAL: Authentication & API Calls

The app runs on the SAME ORIGIN as the AIMEAT node. Use relative API paths (e.g., "/v1/ext/..."), NOT absolute URLs.

### Library setup (copy this exactly — load BOTH libraries):
```javascript
// Load AIMEAT libraries — auth handles login/JWT, data handles memory API
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = src; s.onload = resolve; s.onerror = reject;
    document.head.appendChild(s);
  });
}

async function boot() {
  try {
    await loadScript('/v1/libs/aimeat-auth.js');
    await loadScript('/v1/libs/aimeat-data.js');
    // Platform UI libraries (pre-installed on every AIMEAT node)
    await loadScript('/v1/cortex/aimeat-ui-nav/libs/aimeat-ui-nav.js');
    await loadScript('/v1/cortex/aimeat-ui-layout/libs/aimeat-ui-layout.js');
    await loadScript('/v1/cortex/aimeat-ui-viewers/libs/aimeat-ui-viewers.js');
    await loadScript('/v1/cortex/aimeat-ui-forms/libs/aimeat-ui-forms.js');
    await loadScript('/v1/cortex/aimeat-ui-dialogs/libs/aimeat-ui-dialogs.js');
${hasCortex ? '\n' + cortexScriptLoads : ''}
    AIMEAT.auth.mountLoginButton('#auth-container', {
      onLogin: () => startApp(),
      onLogout: () => location.reload(),
    });
    AIMEAT.auth.login().then(session => { if (session) startApp(); }).catch(() => {});
  } catch (err) {
    document.body.innerHTML = '<div style="padding:2rem;color:#ef4444;font-family:system-ui">'
      + '<h2>Failed to load application</h2><p>' + err.message + '</p>'
      + '<p>Make sure the AIMEAT node is running and accessible.</p></div>';
  }
}
boot();
```

${translationKeysSection}
${hasCortex ? cortexInstructions : `### AIMEAT.data API (memory read/write — handles auth and envelope automatically):
\`\`\`javascript
// Read YOUR OWN memory key — returns the stored value directly, or null
const myData = await AIMEAT.data.get('my.settings');

// Write a memory key (your own namespace)
await AIMEAT.data.set('my.key', { count: 42 });

// Delete your own memory key
await AIMEAT.data.delete('my.key');
\`\`\`

### Reading EXTENSION-produced data (CRITICAL — most apps need this):
Extensions store data in their OWN namespace (\`ext:{extension-name}\`).
To read data that an extension wrote, use \`getPublic()\`:
\`\`\`javascript
// WRONG — this reads YOUR memory, not the extension's:
const data = await AIMEAT.data.get('items.by-date.__index');  // returns null!

// CORRECT — read from the extension's namespace:
const data = await AIMEAT.data.getPublic('ext:my-collector-extension', 'items.by-date.__index');
\`\`\`
The first argument is the extension's memory owner: \`"ext:" + extensionName\` (the \`name\` field from the extension manifest metadata).
Use this for ALL data produced by extensions (collected data, computed stats, caches, etc.).
\`getPublic()\` returns the value directly (auto-unwraps), or null if not found.

### Reading TRANSLATIONS (stored in owner namespace by translation components):
Translations are stored in the OWNER's namespace by the translation component during registration.
The key format is: \`{service-name}.i18n.{locale}\` (e.g. \`my-service.i18n.fi\`).
\`\`\`javascript
// Read translations from OWNER namespace (the translation component stored them here):
const fiStrings = await AIMEAT.data.get('my-service.i18n.fi') || await AIMEAT.data.get('i18n.fi') || {};
const enStrings = await AIMEAT.data.get('my-service.i18n.en') || await AIMEAT.data.get('i18n.en') || {};
\`\`\`
Use AIMEAT.data.get() — this reads from the current user's namespace where translations live.
If a cortex library has a getI18n(locale) method, use that instead (recommended).

### Calling extension actions (use AIMEAT.auth session for authenticated fetch):

╔══════════════════════════════════════════════════════════════════════════╗
║  CRITICAL: session.fetch() returns ALREADY-PARSED JSON, not Response.  ║
║  Do NOT call resp.json() — it will crash with "not a function".        ║
║  Access resp.ok, resp.data, resp.error directly.                       ║
╚══════════════════════════════════════════════════════════════════════════╝

\`\`\`javascript
// Helper for extension calls (copy this EXACTLY):
// ALL extension actions are POST — the backend only has router.post() routes
async function extCall(extName, actionId, body = {}) {
  const session = await AIMEAT.auth.login();
  if (!session) throw new Error('Not logged in');
  const url = '/v1/ext/' + extName + '/' + actionId;
  const resp = await session.fetch(url, { method: 'POST', body: JSON.stringify(body) });
  // resp is ALREADY parsed JSON — never call resp.json()
  if (!resp.ok) throw new Error(resp.error?.message || 'Extension call failed');
  return resp.data;  // unwrapped payload
}

// Usage:
const results = await extCall('my-extension', 'search', { query: 'test' });
const detail = await extCall('my-extension', 'getDetail', { id: 'abc-123' });
\`\`\``}

## CDN Libraries & Design Resources

The AIMEAT app catalog allows external CDN scripts. Available libraries:

| Library | Script Tag | Use for |
|---------|-----------|---------|
| Leaflet | `<script src="https://unpkg.com/leaflet@1/dist/leaflet.js"></script>` + CSS link: `<link rel="stylesheet" href="https://unpkg.com/leaflet@1/dist/leaflet.css">` | Maps, markers, geospatial |
| Chart.js | `<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>` | Bar, line, pie, radar charts |
| Motion | `<script src="https://cdn.jsdelivr.net/npm/motion@11/dist/motion.js"></script>` | Animations via `Motion.animate(el, {x: 100}, {duration: 0.5})` |
| Phaser 3 | `<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>` | Games, interactive canvas, physics |

### CSP (Content Security Policy) — the app HTML MUST include a meta tag

AIMEAT enforces CSP headers. If your app loads CDN scripts/styles, you MUST add a `<meta>` tag:
\`\`\`html
<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net;
  style-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net https://fonts.googleapis.com;
  img-src 'self' data: https: blob:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://*.tile.openstreetmap.org https://cdn.jsdelivr.net https://unpkg.com;
">
\`\`\`
Only include CDN domains you actually use. Without this tag, CDN scripts will be BLOCKED silently.

IMPORTANT: If your app uses Leaflet or any map library that loads tiles from external servers,
you MUST add the tile server domain to `connect-src`. For OpenStreetMap: `https://*.tile.openstreetmap.org`.
Without this, the map will appear but tiles will be blocked and it shows a grey/empty map.

For UI inspiration, reference uiverse.io for fancy buttons, cards, toggles, and loaders (CSS-only patterns).

## CSS Design System

Use CSS custom properties for ALL styling. Define a theme at the top, then use var() everywhere:
```css
:root {
  --color-primary: #3b82f6;      /* main action color — customize per project */
  --color-secondary: #64748b;
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-danger: #ef4444;
  --color-bg: #ffffff;
  --color-bg-card: #f8fafc;
  --color-text: #1e293b;
  --color-text-dim: #64748b;
  --color-border: #e2e8f0;
  --radius: 8px;
  --radius-lg: 12px;
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  --font-sans: system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
  --shadow-md: 0 4px 6px rgba(0,0,0,0.07);
  --shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
  --transition: 150ms ease;
}
```

NEVER use hardcoded hex colors in component styles. Always use var(--color-*).
Customize the palette based on the project's domain and the style preferences from the spec.

## Auth UI Layout

The #auth-container renders a login button and session bar at the top of the page.
Reserve space for it in your layout — do not overlap or hide it:
```html
<body>
  <div id="auth-container"></div>  <!-- AIMEAT login/session UI — always at top -->
  <header><!-- your app header --></header>
  <main id="app"><!-- your content --></main>
</body>
```

## Rules
- DO NOT add manual configuration fields for API URL, Bearer Token, or Instance ID
- DO NOT use prompt() or manual token entry — the auth library handles everything
- ALL API paths MUST be relative (start with /) — never use absolute URLs or NODE_URL
- Use `await AIMEAT.auth.login()` to restore session from storage; if null, show a "Sign in" message. getSession() alone returns null until login() is called.
- Use vanilla JS (no build step needed)
- All dates displayed to users should be formatted from ISO 8601 strings (never store display-formatted dates)
- Has a clean, responsive UI with good mobile support
- Use CSS custom properties for theming where possible
${hasCortex ? '- Call cortex init() on app start — it handles everything automatically\n- Focus on UX/UI — the cortex handles data access and initialization' : ''}

## CRITICAL: Rendering API Data — Handle Nested Objects

API responses often contain nested objects instead of plain strings. For example:
- `businessId: { value: "3323553-5", registrationDate: "2022-11-07" }` — access `.value`
- `euId: { value: "FIFPRO.3323553-5", source: "1" }` — access `.value`
- `website: { url: "www.example.com", registrationDate: "..." }` — access `.url`
- `names: [{ name: "Company Oy", type: "1" }]` — access `[0].name`

╔══════════════════════════════════════════════════════════════════════════╗
║  NEVER render an object directly as text — it will show [object Object] ║
║  ALWAYS check: if (typeof val === 'object' && val !== null)             ║
║  Then access the appropriate sub-field (.value, .url, .name, etc.)      ║
╚══════════════════════════════════════════════════════════════════════════╝

Helper pattern:
\`\`\`javascript
function displayValue(val) {
  if (val == null) return '-';
  if (typeof val === 'string') return val;
  if (typeof val === 'number' || typeof val === 'boolean') return String(val);
  if (val.value) return val.value;  // { value: "..." } pattern
  if (val.url) return val.url;      // { url: "..." } pattern
  if (val.name) return val.name;    // { name: "..." } pattern
  if (Array.isArray(val)) return val.map(displayValue).join(', ');
  return JSON.stringify(val);        // last resort — never [object Object]
}
\`\`\`

## CRITICAL: Empty-State Handling

On first run, extension data does NOT exist yet. The app MUST show a friendly empty state:
- Check every data response for null/empty before rendering
- Show helpful messages: "No data yet — extensions will collect data on their next scheduled run"
- NEVER crash on null/undefined data — always provide fallback UI

## Built-in Error Collector (diagnostics)

Add this error collector at the TOP of your main <script>, before any other code:
\`\`\`javascript
// Error collector — surfaces runtime errors in the UI for diagnostics
(function() {
  var errors = [];
  window.onerror = function(msg, src, line, col) {
    errors.push({ msg: msg, src: src, line: line, col: col, at: new Date().toISOString() });
    showErrors();
  };
  window.addEventListener('unhandledrejection', function(e) {
    errors.push({ msg: String(e.reason), src: 'promise', line: 0, col: 0, at: new Date().toISOString() });
    showErrors();
  });
  function showErrors() {
    var el = document.getElementById('app-errors');
    if (!el) { el = document.createElement('div'); el.id = 'app-errors'; el.style.cssText = 'position:fixed;bottom:0;left:0;right:0;background:#1a0000;color:#ff6b6b;font-size:12px;padding:8px 12px;max-height:120px;overflow:auto;z-index:9999;font-family:monospace;border-top:2px solid #ff4444'; document.body.appendChild(el); }
    el.innerHTML = errors.map(function(e) { return e.at.slice(11,19) + ' ' + e.msg + ' (' + e.src + ':' + e.line + ')'; }).join('<br>');
  }
})();
\`\`\`
This lets users see runtime errors without opening the browser console.

${HTML_ENTITY_RULES}

Return a complete HTML file with an app manifest comment at the top:

```html
<!-- AIMEAT App Manifest
name: kebab-case-name
version: 1.0.0
description: What this app does
entry: index.html
-->
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>App Name</title></head>
<body>
  <div id="auth-container"></div>
  <div id="app"></div>
  <script>
    // Auth setup + API helper + app logic here
  </script>
</body>
</html>
```
```

The `cortexInstructions` variable (injected when `hasCortex` is true) contains:

```
## CORTEX LIBRARIES (use these — do NOT call extensions or memory directly)

This project has Cortex libraries that wrap all extension APIs into clean domain methods.
Load them via <script> tags and use their API.

${(() => {
  // ... generates per-cortex-lib sections with:
  // ### ${lib.label}
  // Load: \`<script src="/v1/cortex/${lib.name}/libs/${lib.name}.js"></script>\`
  // ${apiSummary}
})()}

${methodList}
RULES:
- Call \`AIMEAT.{libName}.init()\` on app start (libName is camelCase of cortex name, e.g., \`my-domain-lib\` → \`AIMEAT.myDomainLib.init()\`)
- Use cortex methods for ALL data access — never call extensions or memory directly
- NEVER call internal functions like callExt(), readExtMemory() — these are PRIVATE
- NEVER build retry loops — show an error message on failure, do NOT auto-retry
- Use EXACTLY the method names and return value shapes shown above

### AIMEAT Platform UI Libraries (load BEFORE your domain cortex)

\`\`\`html
<script src="/v1/cortex/aimeat-ui-nav/libs/aimeat-ui-nav.js"></script>
<script src="/v1/cortex/aimeat-ui-layout/libs/aimeat-ui-layout.js"></script>
<script src="/v1/cortex/aimeat-ui-viewers/libs/aimeat-ui-viewers.js"></script>
<script src="/v1/cortex/aimeat-ui-forms/libs/aimeat-ui-forms.js"></script>
<script src="/v1/cortex/aimeat-ui-dialogs/libs/aimeat-ui-dialogs.js"></script>
\`\`\`

Use these instead of raw HTML: \`Tabs\` for navigation, \`DataTable\` for tables, \`Input/Select/Toggle\` for forms, \`toast/Modal/Confirm\` for dialogs (never use native alert/confirm/prompt).
```

The `translationKeysSection` variable (injected when translation components exist) contains:

```
## AVAILABLE TRANSLATION KEYS (from registered translation components — use EXACTLY these)

╔══════════════════════════════════════════════════════════════════════════╗
║  Your app MUST use these EXACT keys when calling t(key, translations). ║
║  Do NOT invent your own keys like "field.name" or "detail.addresses".  ║
║  The translation components have ALREADY defined these keys.           ║
╚══════════════════════════════════════════════════════════════════════════╝

${sorted.map(k => '- `' + k + '`').join('\n')}

Total: ${sorted.length} keys available. If you need a label, find the closest matching key from this list.
```

---
### COMPONENT_TEMPLATES.memory (foundry-prompts-base.js:878)
```
${AIMEAT_CONTEXT}

Define memory structure for: ${label}

${context}

## Memory Key Conventions

AIMEAT memory uses dot-namespaced keys with a standard metadata pattern:

- `namespace.__meta` — describes the namespace (version, key format, description)
- `namespace.__index` — lightweight index for fast lookups (list of dates, counts, rankings)
- `namespace.__config` — configuration for the namespace (TTLs, thresholds, weights)
- `namespace.YYYY-MM-DD` — date-bucketed data (one key per day)
- `namespace.item-id` — individual items

Values are JSON objects — a single value can hold arrays, nested objects, and large datasets (up to several MB). There is NO reason to split a dataset across multiple keys when it logically belongs together.

## Rules
- All keys MUST be lowercase with dots as namespace separators
- Date-bucketed keys MUST use YYYY-MM-DD format: "items.by-date.2026-03-14"
- All date/time values inside objects MUST be ISO 8601: "2026-03-14T13:00:00.000Z"
- Include __meta with version and description for every namespace
- Include __index if consumers need to discover which keys exist (e.g., list of dates with data)
- Keep __index lightweight — just key names, counts, and pointers. NOT full data copies.
- Use arrays for ordered collections within a bucket (e.g., entries per day)
- Use meaningful field names that match the CSM data_schema where applicable
- PREFER fewer, larger keys over many small keys. A lookup table, reference dataset, or configuration object should be ONE key containing the full data structure (object or array), NOT split into one key per entry. For example, a list of 300 items should be ONE key "catalog.data" containing the full array — NOT 300 separate keys.

Return a JSON object where keys are memory key names and values are the initial/template data:
```json
{
  "namespace.__meta": {
    "version": "1.0",
    "description": "What this namespace stores",
    "keyFormat": "namespace.YYYY-MM-DD"
  },
  "namespace.__index": {
    "dates": [],
    "totalItems": 0,
    "lastUpdated": ""
  },
  "namespace.YYYY-MM-DD": {
    "date": "YYYY-MM-DD",
    "items": []
  }
}
```
```

---
### COMPONENT_TEMPLATES.translation (foundry-prompts-base.js:927)
```
${AIMEAT_CONTEXT}

Create translations for: ${label}

${context}

## Translation Key Conventions

- Keys use dot-namespaced paths matching the UI structure: "app.list.title", "app.filters.status"
- Group by UI section: "app.nav.*", "app.map.*", "app.filters.*", "app.stats.*"
- Include domain-specific terms: categories, statuses, types relevant to the project
- Use interpolation with ${variable} syntax for dynamic values: "Found ${count} items"

## CRITICAL: Generate ONLY the locale indicated by the label

Each translation component covers ONE locale. The label tells you which one:
- "Finnish (fi) Strings" → generate ONLY Finnish translations under the "fi" root key
- "English (en) Strings" → generate ONLY English translations under the "en" root key

╔══════════════════════════════════════════════════════════════════════════╗
║  NEVER generate both locales in one component.                         ║
║  If the label says "Finnish", output ONLY { "fi": { ... } }.           ║
║  If the label says "English", output ONLY { "en": { ... } }.           ║
║  The other locale is handled by a SEPARATE component in the blueprint. ║
╚══════════════════════════════════════════════════════════════════════════╝

## Rules
- The root key MUST match the locale in the label: "en" for English, "fi" for Finnish
- NEVER include the other locale — it will be generated in its own component
- Finnish translations must be natural Finnish with correct characters (a, o, a) — not machine-translated
- Include ALL text that appears in the UI — labels, buttons, tooltips, empty states, error messages
- Keep keys consistent with what the App component will reference
- Use plural-aware keys where needed: "item.one" / "item.many"
- Both locale components MUST use the SAME key structure — the app uses the same keys for both

Return JSON with translations for the SINGLE locale from the label:

Example — if label is "Finnish (fi) Strings":
```json
{
  "fi": {
    "app.title": "Sovelluksen nimi",
    "app.nav.home": "Etusivu",
    "app.filters.status": "Tila",
    "app.filters.all": "Kaikki",
    "app.empty": "Tietoja ei loytynyt",
    "app.error": "Jokin meni pieleen",
    "domain.type.example_category": "Esimerkkikategoria",
    "domain.status.active": "Aktiivinen"
  }
}
```

Example — if label is "English (en) Strings":
```json
{
  "en": {
    "app.title": "App Title",
    "app.nav.home": "Home",
    "app.filters.status": "Status",
    "app.filters.all": "All",
    "app.empty": "No data found",
    "app.error": "Something went wrong",
    "domain.type.example_category": "Example Category",
    "domain.status.active": "Active"
  }
}
```
```

---
### COMPONENT_TEMPLATES.cortex (foundry-prompts-base.js:996)

This is a very long template. The returned string is:

```
${AIMEAT_CONTEXT}

Create a Cortex extension (client-side JS domain library) for: ${label}

${context}
${extRef}
## What is a Cortex Library?

A Cortex library is a client-side JavaScript library that bridges V8 extensions and the app layer.
It wraps raw AIMEAT API calls (extension actions, memory reads from extension namespaces) into
clean, documented domain methods. Apps import the cortex and call simple methods like
`AIMEAT.myLib.getData()` instead of knowing about memory namespaces and extension names.

## CRITICAL: Use the Extension API Summary Above

The extension API summary shows the EXACT memory keys and data shapes. Your cortex MUST match them:

╔══════════════════════════════════════════════════════════════════════════╗
║  1. Use the "Memory writes" keys as your readExtMemory() keys          ║
║  2. Use the "Data shape" info to know what fields the data contains    ║
║  3. Your getPublic() calls MUST use those EXACT same keys              ║
║  4. Your methods MUST return data in the EXACT shape the extension     ║
║     stores it — do NOT invent new field names or structures            ║
║  5. Use the "Action" endpoints for callExt() on-demand calls           ║
╚══════════════════════════════════════════════════════════════════════════╝

Example: if the extension writes to key "orders.data" with shape { id, title, ... }
then your cortex must read `getPublic('ext:extension-name', 'orders.data')`
and return objects with the SAME structure — NOT `data`, NOT `entries`, NOT `results`.

## Design Principles

1. **Domain Cohesion**: Group related operations into a single API surface
2. **Facade Pattern**: Hide extension namespaces (`ext:{name}`), memory key patterns, and error handling
3. **DRY / Genericity**: If a capability is reusable across projects, make it generic
4. **Smart Init**: `init()` checks if data exists and returns status — see init() rules below
5. **Composability**: Cortex libs can use other cortex libs via `AIMEAT.{otherLib}`
6. **Self-Documenting**: Export clear, named functions with consistent patterns

## What Cortex Libraries Can Contain

Cortex is a CLIENT-SIDE JavaScript library. It can contain:

- **Data access methods**: read/write AIMEAT memory, call extension actions (on-demand only)
- **UI components**: functions that create DOM elements, render HTML, inject CSS
- **Visualization helpers**: chart builders, map renderers, interactive widgets
- **Event handling**: click handlers, ResizeObserver, custom events
- **Domain logic**: client-side data transformation, filtering, sorting, formatting

Example: `aimeat-charts` cortex exports `ChartPanel()` and `ChartBuilder()` — functions that create `<canvas>` elements, inject CSS styles, and render interactive Chart.js visualizations.

A cortex library is NOT limited to data access — it can be a full UI component library.
However, it MUST NOT contain backend/server logic (no scheduling, no data collection, no recurring tasks — that belongs to extensions).

## IMPORTANT: How Extension Memory Works

Extensions store data in their OWN namespace (`ext:{name}`).

╔══════════════════════════════════════════════════════════════════════════╗
║  CRITICAL: Extension namespace is READ-ONLY from client-side.          ║
║                                                                        ║
║  ✅ Client CAN READ:   getPublic('ext:my-collector', 'some.key')       ║
║  ❌ Client CANNOT WRITE: PUT /v1/memory/ext:name/key → 404             ║
║                                                                        ║
║  To WRITE extension data, call an extension ACTION via callExt()       ║
║  which runs server-side and uses ctx.memory.set().                     ║
║                                                                        ║
║  For USER data (watches, settings, preferences), use writeOwnerMemory  ║
║  which writes to the CURRENT USER's namespace via PUT /v1/memory/:key  ║
╚══════════════════════════════════════════════════════════════════════════╝

To READ extension data from client-side:
\`\`\`javascript
// If AIMEAT.data is loaded (preferred):
const value = await AIMEAT.data.getPublic('ext:my-collector', 'items.by-date.__index');

// Fallback without AIMEAT.data:
const url = NODE_URL + '/v1/memory/' + encodeURIComponent('ext:my-collector') + '/' + encodeURIComponent(key);
const resp = await fetch(url);
const json = await resp.json();
const value = json.ok ? json.data.value : null;
\`\`\`

To WRITE data that belongs to the extension (shared across all users):
\`\`\`javascript
// WRONG — will 404:
await session.fetch('/v1/memory/ext:my-collector/' + key, { method: 'PUT', ... });

// CORRECT — call an extension action that does ctx.memory.set() server-side:
await callExt('my-collector', 'updateWatches', { watches: updatedList });
\`\`\`

To WRITE data that belongs to the current user (personal preferences):
\`\`\`javascript
// CORRECT — writes to user's own namespace:
await writeOwnerMemory('settings.config', { locale: 'fi', notifications: true });
\`\`\`

## IMPORTANT: How Translations Work

Translations are stored in the OWNER namespace by the translation component during registration.
The key format is: \`{service-slug}.i18n.{locale}\` (e.g. \`prh-yritystietopalvelu.i18n.fi\`).
Read them with AIMEAT.data.get():
\`\`\`javascript
// Read translations from OWNER namespace (where translation component stored them):
const fiStrings = await AIMEAT.data.get(SERVICE_SLUG + '.i18n.fi') || {};
const enStrings = await AIMEAT.data.get(SERVICE_SLUG + '.i18n.en') || {};
\`\`\`

Do NOT use readExtMemory or getPublic('ext:...') for translations — they are NOT in the extension namespace.

### MANDATORY: t() helper function (MUST use this exact implementation)

Foundry produces flat translation keys like \`"tab.search": "Haku"\`. The t() function
MUST check flat keys first, then fall back to nested dot-path traversal:

\`\`\`javascript
function t(key, translations) {
  if (!key || !translations) return key || '';
  // Flat key first (e.g., "tab.search" as direct property)
  if (translations[key] != null && typeof translations[key] !== 'object') return translations[key];
  // Then nested dot-path traversal
  var parts = key.split('.'); var val = translations;
  for (var i = 0; i < parts.length; i++) {
    if (val == null || typeof val !== 'object') return key;
    val = val[parts[i]];
  }
  return (val != null && typeof val !== 'object') ? val : key;
}
\`\`\`

This function is an INTERNAL helper — do NOT export it. Use it in public methods that
need translated text, e.g., \`t('tab.search', fiStrings)\`.

## IMPORTANT: How Settings Work

Default settings are stored in the OWNER namespace by the memory component.
The key is: \`{service-slug}.settings.config\`.
\`\`\`javascript
// Read settings from OWNER namespace (where memory component stored them):
const settings = await AIMEAT.data.get(SERVICE_SLUG + '.settings.config') || {};
\`\`\`

Do NOT use readExtMemory for settings — they are NOT in the extension namespace.

## Extension Action Calls (authenticated — internal helper, NOT exported)

callExt() is an INTERNAL helper inside the cortex IIFE. It is NOT part of the public API.
Apps MUST NOT call callExt() directly — use the cortex's exported methods instead.

\`\`\`javascript
// ALL extension actions are POST — the backend only has router.post() routes
async function callExt(extName, actionId, body) {
  try {
    if (!AIMEAT.auth || !AIMEAT.auth.getSession) return null;
    var session = AIMEAT.auth.getSession();
    if (!session) return null;
    var url = '/v1/ext/' + extName + '/' + actionId;
    var opts = { method: 'POST', body: JSON.stringify(body || {}) };
    var resp = await session.fetch(url, opts);
    if (!resp || !resp.ok) return null;
    return resp.data;
  } catch (e) {
    return null;
  }
}
\`\`\`

IMPORTANT: callExt is a PRIVATE helper inside the IIFE. Do NOT export it.
Apps use the cortex's public methods (init, searchCompanies, etc.), never callExt directly.

### Where to store different types of data

| Data type | Where to store | How to write |
|-----------|---------------|-------------|
| Shared/service data (feeds, stats, reference) | ext:{name} namespace | Extension scheduler via ctx.memory.set() |
| Shared user-generated data (watches, alerts) | ext:{name} namespace | Extension action via callExt() → ctx.memory.set() |
| Personal user preferences (settings, locale) | User's own namespace | writeOwnerMemory() via PUT /v1/memory/:key |

## Output format — TWO separate code blocks

Return the YAML manifest and JavaScript library as TWO separate, properly tagged code blocks.
The installer expects to receive them separately — the YAML defines the manifest, the JS is the library file.

CRITICAL: Use \`\`\`yaml for the manifest and \`\`\`javascript for the library code.
Do NOT combine them into a single block. Do NOT use an untagged block.

First block — YAML manifest:
\`\`\`yaml
apiVersion: cortex.aimeat.org/v1
kind: Extension
metadata:
  name: my-domain-lib
  namespace: community
  description: "What this library does"
  author: foundry
  tags: [domain, tag1, tag2]
  labels:
    domain: specific-domain
spec:
  version: "1.0.0"
  license: MIT
  components:
    - type: prompt
      name: domain-assistant
      content: |
        You are using the {{metadata.name}} cortex library.
        Node URL: {{node_url}}

        Available API:
        AIMEAT.myLib.init() — Check data readiness and return status
        AIMEAT.myLib.getData(filters) — Get filtered data
        AIMEAT.myLib.getStats(date) — Get statistics for a date

        To load in an app:
        <script src="{{node_url}}/v1/cortex/my-domain-lib/libs/my-domain-lib.js"></script>

    - type: lib
      name: my-domain-lib
      filename: my-domain-lib.js
      exports: [init, getData, getStats]
      api_surface: |
        AIMEAT.myLib.init() — Check data readiness, returns { ready, hasData }
        AIMEAT.myLib.getData({hours, type}) — Filtered domain data
        AIMEAT.myLib.getStats(date) — Aggregated statistics
\`\`\`

Second block — JavaScript library:
\`\`\`javascript
(function (AIMEAT) {
  'use strict';

  const LIB_NAME = 'myLib';
  // Extension names this cortex wraps — MUST match the registered extension names
  const EXT = {
    collector: 'my-collector-extension',
    aggregator: 'my-aggregator-extension',
  };

  // ── Internal helpers ──

  function nodeUrl() { return window.location.origin; }

  // ── Memory helpers — use AIMEAT.data library (always available in browser) ──
  // The AIMEAT.data library handles auth, endpoints, and response parsing automatically.
  // Do NOT use raw fetch() or session.fetch() for memory operations.

  /** Read from extension namespace (public, no auth needed) */
  async function readExtMemory(extName, key) {
    try { return await AIMEAT.data.getPublic('ext:' + extName, key); }
    catch (e) { return null; }
  }

  /** Read from current user's own namespace */
  async function readOwnerMemory(key) {
    try { return await AIMEAT.data.get(key); }
    catch (e) { return null; }
  }

  /** Write to current user's own namespace */
  async function writeOwnerMemory(key, value) {
    try { await AIMEAT.data.set(key, value); return true; }
    catch (e) { return false; }
  }

  /** Delete from current user's own namespace */
  async function deleteOwnerMemory(key) {
    try { await AIMEAT.data.delete(key); return true; }
    catch (e) { return false; }
  }

  // ╔══════════════════════════════════════════════════════════════════════════╗
  // ║  CRITICAL: session.fetch() returns ALREADY-PARSED JSON (not Response). ║
  // ║  Do NOT call resp.json() — it will crash. Use resp.data directly.      ║
  // ╚══════════════════════════════════════════════════════════════════════════╝
  // ALL extension actions are POST — the backend only has router.post() routes
  async function callExt(extName, actionId, body) {
    try {
      if (!AIMEAT.auth || !AIMEAT.auth.getSession) return null;
      const session = AIMEAT.auth.getSession();
      if (!session) return null;
      const url = '/v1/ext/' + extName + '/' + actionId;
      const opts = { method: 'POST', body: JSON.stringify(body || {}) };
      const resp = await session.fetch(url, opts);
      if (!resp || !resp.ok) return null;
      return resp.data;
    } catch (e) {
      return null;
    }
  }

  // ── Public API ──

  async function init() {
    // Check if extension scheduler has produced data yet
    const cursor = await readExtMemory(EXT.collector, 'data.cursor');
    return { ready: true, hasData: !!cursor };
    // App shows empty state if hasData is false — scheduler will populate data in background
  }

  async function getData(filters) {
    // Read from extension memory, apply filters, return clean data
  }

  // ── Register ──
  const exports = { init, getData };
  if (AIMEAT.register) AIMEAT.register(LIB_NAME, exports);
  AIMEAT[LIB_NAME] = exports;

})(window.AIMEAT || (window.AIMEAT = {}));
\`\`\`

CRITICAL: Use TWO separate code blocks — \`\`\`yaml for the manifest and \`\`\`javascript for the library.
The JavaScript filename MUST match the `filename` field in the YAML lib component.

## Rules

### CRITICAL: Name Consistency
The YAML `metadata.name` and the JS `LIB_NAME` MUST follow this convention:
- YAML `metadata.name`: kebab-case (e.g., `my-domain-lib`)
- JS `LIB_NAME`: MUST be declared with `const` (not var or let): `const LIB_NAME = 'myDomainLib';`
- JS `LIB_NAME`: camelCase version of the SAME name (e.g., `myDomainLib`)
- `AIMEAT.register(LIB_NAME, exports)` uses the camelCase name
- Apps load via: `/v1/cortex/{metadata.name}/libs/{metadata.name}.js`
- Apps access via: `AIMEAT.{LIB_NAME}.method()`

Example: YAML name `my-domain-lib` → LIB_NAME = `myDomainLib` → `AIMEAT.myDomainLib.init()`

- The library MUST be a single IIFE that registers on `window.AIMEAT`
- Use `AIMEAT.register(name, exports)` if available, always set `AIMEAT[name] = exports`
- Use `AIMEAT.data.getPublic()` for public memory reads (extension namespace data)
- Use `AIMEAT.auth.getSession().fetch()` for ALL authenticated calls — NEVER use raw fetch() for API calls
- The fallback path (when AIMEAT.data is not loaded) MUST also use session.fetch(), not raw fetch()
- Extension names in `EXT` object MUST exactly match the registered extension `metadata.name`
- `init()` MUST follow the init() contract below — no custom behavior
- All public methods must be async (return Promises)
- Handle errors gracefully — return null or empty arrays, don't throw for missing data
- Include the prompt component with documented API surface for downstream AI consumers

### MANDATORY: JSDoc for every public method

Every exported method MUST have a JSDoc comment with:
- `@param` for each parameter with its type and description
- `@returns` with the EXACT return shape — never use `{Object}`, always describe the fields

WRONG:
\`\`\`javascript
/** @returns {Object|null} Item data */
async function getItem(id) { ... }
\`\`\`

RIGHT:
\`\`\`javascript
/**
 * @param {string} id - Unique identifier
 * @returns {{ id: string, name: string, status: string, metadata: { createdAt: string, updatedAt: string } } | null}
 */
async function getItem(id) { ... }
\`\`\`

Use the ACTUAL API RESPONSES section (from probe data) to determine the exact return shapes.
Every field you see in the probe response MUST appear in the @returns type definition.
If probe data shows a nested object like `field: { value: "abc", date: "2024-01-01" }`, your @returns MUST reflect that: `field: { value: string, date: string }`.

## init() Contract (MUST follow exactly)

╔══════════════════════════════════════════════════════════════════════════╗
║  init() is a UI READINESS CHECK — it does NOT trigger backend logic.    ║
║  The extension scheduler handles all data collection and computation.   ║
║  Cortex init() only checks what data is available and returns status.  ║
╚══════════════════════════════════════════════════════════════════════════╝

\`\`\`javascript
async function init() {
  // 1. Check if data exists by reading the primary cursor/index key
  const cursor = await readExtMemory(EXT.collector, 'data.cursor');

  // 2. Return status so the app knows what to show
  if (cursor) {
    return { ready: true, hasData: true };
  }

  // 3. No data yet — scheduler hasn't run. App should show empty/loading state.
  return { ready: true, hasData: false };
}
\`\`\`

Rules for init():
- NEVER trigger extension actions (callExt) from init() — the extension scheduler handles all recurring work
- NEVER set up intervals, timers, or recurring triggers
- NEVER call init() automatically inside the cortex IIFE — let the app call it
- init() MUST be idempotent — calling it twice returns the same status
- Return `{ ready: true }` always — the app checks this to know the cortex is loaded
- Return `{ hasData: false }` when no data exists yet — the app shows an appropriate empty state ("Data is being collected, please wait...")
- The ONLY place where callExt() is appropriate in a cortex is for ON-DEMAND user actions (e.g., findNearby, manual refresh button) — never for scheduled/background work

## CRITICAL: Empty-State Handling (first-run scenario)

On first run, the extension scheduler may not have run yet — NO data exists.
init() returns `{ ready: true, hasData: false }` and the app shows a waiting state.
Every method MUST handle missing data gracefully:

\`\`\`javascript
// WRONG — crashes if index doesn't exist:
async function getData() {
  const index = await readExtMemory(EXT.collector, 'items.__index');
  return index.dates.map(d => ...);  // TypeError: Cannot read 'dates' of null
}

// CORRECT — return empty data:
async function getData() {
  const index = await readExtMemory(EXT.collector, 'items.__index');
  if (!index || !index.dates || index.dates.length === 0) return [];
  // ... process data
}
\`\`\`

EVERY readExtMemory/getPublic call must be followed by a null/undefined check.
NEVER assume data exists — the user may open the app before any extension has run.
```

---
### NAMESPACE_RULES (foundry-prompts-base.js:1684)
```
## AIMEAT Namespace Rules

AIMEAT has TWO types of memory namespaces — understanding this is CRITICAL:

1. **Owner namespace** — where the human user's data lives
   - Memory components (seed data, lookup tables) store here
   - Translation components store here (i18n.fi, i18n.en)
   - Settings components store here (settings.config)
   - Accessible via: ctx.memory.getPublic(ctx.caller.gaii, key) [from extension]
   - Accessible via: /v1/memory/{key} [from HTTP with owner's JWT]

2. **Extension namespace** (`ext:{name}`) — where extension runtime data lives
   - Extension writes here via ctx.memory.set(key, value)
   - Accessible to ALL users via: ctx.memory.getPublic('ext:{name}', key)
   - Accessible via: readExtMemory(name, key) [from cortex/app]
   - NOT accessible via /v1/memory/{key} (that reads owner namespace!)

╔══════════════════════════════════════════════════════════════════════════╗
║  Extension ctx.memory.get(key) ONLY reads from ext:{name} namespace.  ║
║  Seed data (memory components), settings, translations live in the     ║
║  OWNER's namespace — use ctx.memory.getPublic(ctx.caller.gaii, key). ║
║                                                                        ║
║  From OUTSIDE (cortex, app, tests):                                    ║
║  - Read extension data: getPublic('ext:{name}', key) or readExtMemory ║
║  - Read owner data: /v1/memory/{key} or AIMEAT.data.get(key)          ║
║  - Write extension data: call an extension ACTION (callExt)            ║
║  - You CANNOT PUT to ext:{name} namespace from client — returns 404   ║
╚══════════════════════════════════════════════════════════════════════════╝

### Init Action
Extensions should initialize runtime data (empty watchlists, caches, logs) in their @activate init action.
Do NOT copy translations or settings — cortex reads those from the owner namespace directly.
```

---
### SANDBOX_CONSTRAINTS (foundry-prompts-base.js:1723)
```
## V8 Sandbox Environment

╔══════════════════════════════════════════════════════════════════════════╗
║  This is a BARE V8 JavaScript engine — NOT Node.js, NOT a browser.     ║
║  Only ECMAScript built-in objects and the ctx API exist.                ║
║  The ONLY way to interact with the outside world is through ctx.       ║
╚══════════════════════════════════════════════════════════════════════════╝

### What IS available (ECMAScript built-ins):
JSON, Math, Date, RegExp, Promise, async/await, String, Array, Object,
Map, Set, WeakMap, WeakSet, Symbol, Proxy, Reflect,
encodeURIComponent, decodeURIComponent, parseInt, parseFloat, isNaN, isFinite

### What is NOT available:

**Node.js APIs** — no require, no import, no Buffer, no process, no fs, no path, no crypto
**Web APIs** — no URLSearchParams, no URL, no TextEncoder, no TextDecoder,
  no Headers, no Request, no Response, no FormData, no Blob, no File,
  no AbortController, no atob, no btoa, no structuredClone, no crypto (Web Crypto),
  no queueMicrotask, no performance
**Browser APIs** — no document, no window, no fetch (use ctx.fetch), no navigator
**Timers** — no setTimeout, no setInterval, no setImmediate
**Console** — no console.log (use ctx.log.info/warn/error)
**Modules** — no require(), no import (except export default for action entry point)

### URL construction (URLSearchParams is NOT available):
```
WRONG:  const params = new URLSearchParams(); params.set('name', query);
WRONG:  const url = new URL(baseUrl); url.searchParams.set('name', query);
RIGHT:  const url = baseUrl + '?name=' + encodeURIComponent(query);
RIGHT:  const url = baseUrl + '?name=' + encodeURIComponent(query) + '&id=' + encodeURIComponent(id);
```

### The ctx API — ONLY these properties exist:

╔══════════════════════════════════════════════════════════════════════════╗
║  ctx.memory (get/set/search/delete/getPublic)                          ║
║  ctx.fetch(url, opts) → { ok, status, text, headers }                  ║
║  ctx.wallet (consume/getBalance)                                        ║
║  ctx.consent (check/require)                                            ║
║  ctx.trust (getScore)                                                   ║
║  ctx.caller (gaii/owner/roles)                                          ║
║  ctx.config (extension config object)                                   ║
║  ctx.log (info/warn/error)                                              ║
║  ctx.notify(message, {title?, priority?, channel?}) → boolean           ║
║  ctx.email(to, subject, body) → boolean (requires SMTP configured)      ║
║  ctx.instance (id/config — only for instance-scoped actions)            ║
║  NOTHING ELSE. Do NOT invent methods that are not listed here.          ║
╚══════════════════════════════════════════════════════════════════════════╝

### Actions are INDEPENDENT — CANNOT call each other

╔══════════════════════════════════════════════════════════════════════════╗
║  Each action is a SEPARATE function. Actions CANNOT call each other.   ║
║  There is NO ctx.otherAction() or ctx.callAction() method.             ║
║  Writing ctx.getCompany() or ctx.searchItems() will CRASH with:        ║
║    "ctx.getCompany is not a function"                                  ║
║                                                                        ║
║  If multiple actions need the same logic, define a HELPER FUNCTION     ║
║  above the action exports and duplicate it in each script file.        ║
╚══════════════════════════════════════════════════════════════════════════╝

### ctx.memory.get — returns PARSED value, NOT string

╔══════════════════════════════════════════════════════════════════════════╗
║  NEVER WRITE: JSON.parse(await ctx.memory.get(...))                    ║
║  The value is ALREADY PARSED. JSON.parse on it will CRASH.             ║
║  CORRECT: const data = await ctx.memory.get("key") || [];              ║
╚══════════════════════════════════════════════════════════════════════════╝

### ctx.fetch — returns { ok, status, text, headers }
- text is a RAW string — parse JSON with JSON.parse(resp.text)
- Encoding auto-detected (Content-Type, XML prolog, HTML meta)
- You always get Unicode text — no manual decoding needed
```

---
### HTML_ENTITY_RULES (foundry-prompts-base.js:1872)
```
## No HTML Entities in Code

╔════════════════════════════════════════════════════════════════════╗
║  Your code MUST use real operators, NOT HTML entities.             ║
║  The V8 sandbox executes raw JS — HTML entities are syntax errors. ║
╚════════════════════════════════════════════════════════════════════╝

WRONG (crashes):  const gt = a =&gt; a &gt; 0 &amp;&amp; b;
CORRECT:          const gt = a => a > 0 && b;

Check your ENTIRE output. If you see &gt; &lt; &amp; &quot; &#39; anywhere
in code, replace them with > < & " ' respectively.
```

---
### EXTENSION_CONSUMPTION_RULES (foundry-prompts-base.js:1804)
```
## How to Consume Extensions (callExt / readExtMemory pattern)

Extensions expose TWO interfaces to the outside world:

### 1. Call actions via HTTP POST
  POST /v1/ext/{extensionName}/{actionId}
  Body: JSON input
  Response: { ok: true, data: { ...action return value... } }

  - ALL actions use POST (even if manifest says GET — the route is POST-only)
  - The {actionId} is the action's "id" field from the YAML manifest
  - r.body.ok is the AIMEAT envelope — ALWAYS true even when the action fails
  - Check r.body.data for the ACTUAL result (success data OR error message)

### 2. Read extension memory via getPublic
  Extension data lives in ext:{name} namespace.
  Read it: getPublic('ext:{name}', key) or readExtMemory(name, key)

  NEVER read extension data via /v1/memory/{key} — that reads the OWNER's
  namespace, not the extension's. You'll get null.

### Testing extensions — use callExt and readExtMemory
  The test sandbox provides callExt() and readExtMemory() — the SAME helpers
  that cortex uses in production. Use these instead of raw testFetch:

  callExt('ext-name', 'actionId', { input })  → action's return value (unwrapped)
  readExtMemory('ext-name', 'key')              → value from ext:{name} namespace

  DO NOT use testFetch for extension actions — use callExt.
  DO NOT use /v1/memory/ to read extension data — use readExtMemory.
  DO NOT create custom getMemory/setMemory helpers.

### Action types for testing
  - [MEMORY] actions: use ONLY ctx.memory (no external API). Tests MUST assert specific return values.
  - [EXTERNAL API] actions: call ctx.fetch to third-party URLs. Tests check response SHAPE only
    (has data fields OR error message), because the external API may be down/rate-limited.
    Graceful error handling is correct behavior — only FAIL on HTTP 500 (extension crashed).
```

---
### INIT_CONTRACT (foundry-prompts-base.js:1848)
```
## Init Contracts

### Extension @activate init
- Runs on activation AND every server restart
- MUST be idempotent (check existing data before overwriting)
- Initialize extension-owned runtime data (watchlists, caches, logs) if not present
- Should check if data exists and is fresh — skip if already populated
- Do NOT copy translations or settings — cortex reads those from owner namespace directly
- Pattern: check existing data → initialize missing runtime keys → return status

### Cortex init()
- UI readiness check ONLY — NEVER triggers backend logic
- MUST return { ready: true/false, hasData: true/false }
- NEVER call callExt() from init() — the extension scheduler handles background work
- NEVER set up timers or intervals
- Apps show empty state when hasData === false
- init() runs BEFORE any other method — app waits for it
```

---

## FILE 2: foundry-prompts-build.js

---
### buildBlueprintPrompt (foundry-prompts-build.js:59)
```
${INSTRUCTION_DISCLAIMER}${AIMEAT_CONTEXT}

The user wants to create this service:
---
${description}
---
${specContext}${langNote}${cortexCatalog}
Analyze this request and produce a JSON blueprint listing ALL components needed.

CRITICAL: Return ONLY a JSON object with "architecture", "components", "phases", "dataModel", and optionally "settings" and "testScenarios". Nothing else.
Each component has these fields: "id", "type", "label", "produces", "consumes". Extension components may also have "schedules".
Do NOT include manifest content, code, HTML, translations, or any implementation details.
The blueprint is a lightweight plan — actual content is generated later per component.

Format:
{
  "architecture": "cortex-modular",
  "components": [
    { "id": "csm-1", "type": "csm", "label": "Human-readable name", "produces": ["memory:service.schema"], "consumes": [] },
    { "id": "memory-1", "type": "memory", "label": "Human-readable name", "produces": ["memory:settings.config"], "consumes": [] },
    { "id": "translation-1", "type": "translation", "label": "Human-readable name (fi)", "produces": ["memory:i18n.fi"], "consumes": [] },
    { "id": "translation-2", "type": "translation", "label": "Human-readable name (en)", "produces": ["memory:i18n.en"], "consumes": [] },
    { "id": "ext-1", "type": "extension", "label": "Human-readable name", "produces": ["memory:items.*"], "consumes": ["memory:settings.config"], "schedules": [{"action":"init","cron":"@activate"},{"action":"collect","cron":"0 2 * * *"}] },
    { "id": "cortex-data", "type": "cortex", "subtype": "data", "label": "Data layer", "produces": ["api:getData", "api:search"], "consumes": ["memory:items.*", "memory:settings.config"], "uses": [] },
    { "id": "cortex-feature-search", "type": "cortex", "subtype": "feature", "label": "Search feature", "produces": ["ui:searchView"], "consumes": ["api:getData", "api:search"], "uses": ["aimeat-ui-viewers", "aimeat-ui-forms"] },
    { "id": "cortex-feature-settings", "type": "cortex", "subtype": "feature", "label": "Settings feature", "produces": ["ui:settingsView"], "consumes": ["api:getSettings", "api:saveSettings"], "uses": ["aimeat-ui-forms"] },
    { "id": "cortex-app", "type": "cortex", "subtype": "app-domain", "label": "App domain", "produces": ["api:init", "api:render"], "consumes": ["ui:searchView", "ui:settingsView"], "uses": ["aimeat-ui-nav"] },
    { "id": "app-1", "type": "app", "label": "Human-readable name", "produces": [], "consumes": ["api:init", "api:render"] }
  ],
  "phases": [
    { "id": "define", "label": "Define Service", "componentIds": ["csm-1"] },
    { "id": "seed", "label": "Seed Data", "componentIds": ["memory-1", "translation-1", "translation-2"] },
    { "id": "logic", "label": "Capabilities", "componentIds": ["ext-1"] },
    { "id": "cortex-data", "label": "Data Layer", "componentIds": ["cortex-data"] },
    { "id": "cortex-features", "label": "Feature Components", "componentIds": ["cortex-feature-search", "cortex-feature-settings"] },
    { "id": "cortex-app", "label": "App Domain", "componentIds": ["cortex-app"] },
    { "id": "ui", "label": "Application", "componentIds": ["app-1"] }
  ],
  "dataModel": {
    "structures": { ... },
    "memoryKeys": { ... },
    "actions": { ... }
  }
}

[... followed by extensive rules sections for dataModel, Component Dependencies, Extension vs Cortex vs App Decision Framework, Cortex-Modular Architecture, Settings, and Test Scenarios as shown in lines 182-357]
```

(The full text of the rules sections from lines 182-357 was already shown above in the file read -- they are part of the same template literal.)

---
### buildInterviewPrompt (foundry-prompts-build.js:366)
```
${INSTRUCTION_DISCLAIMER}You are a requirements analyst for the AIMEAT service foundry.
The user wants to build a service. Your job is to interview them to produce a clear, structured specification.
${langInstruction}
## User's Initial Description
---
${description}
---

## CRITICAL — Interview Discipline

QUESTION BUDGET: You have a maximum of 20 questions total across all sections.
Use cases get the most (up to 8), other sections 2-3 each. Batch related questions together.
Do NOT split every detail into a separate numbered question.

YOU DECIDE (never ask the user about these — the foundry handles them):
- Implementation details: file formats, data serialization, error handling, API design, caching
- Technical methods: how to fetch data, how to parse it, how to store it, how to compute derived values
- UI component details: which chart library, marker clustering, column ordering, widget placement
- Infrastructure: scheduler times, retention periods, timeout values, rate limits, job scheduling
- Data schema internals: field names, ID generation, deduplication strategy, index design
- Code-level choices: typography/font specifics, animation libraries, export format implementation
- Auth, login, user management, access control, user counts, audience size — AIMEAT handles all of these

The user describes WHAT they want and WHY. The foundry decides HOW.

## Interview Rules

1. ADAPT TO THE USER'S LEVEL:
   - Start by asking: "Are you a technical person who'd prefer detailed technical questions, or would you like me to keep things simple and explain as we go?"
   - If non-technical: ask simple questions with examples
   - If technical: ask direct questions to speed things up

2. COVER THESE AREAS (in order):
   a) USE CASES [... extensive interview protocol ...]
   b) DATA SOURCES [... with URL VALIDATION PROTOCOL ...]
   c) DATA MODEL [...]
   d) VIEWS & INTERACTIONS [...]
   e) STYLE & LOOK [...]
   f) SETTINGS & EXTERNAL SERVICES [...]
   g) CONSTRAINTS & PREFERENCES [...]

3. STAY IN SCOPE [...]
4. SECTION RULES [...]
5. HONESTY RULES [...]
6. WHEN THE INTERVIEW IS COMPLETE:
   [... outputs JSON spec format ...]

Begin the interview now. Start by greeting the user and asking about their technical level.
```

(The full JSON output format from the interview is at lines 538-633.)

---
### buildExtensionSkeletonPrompt (foundry-prompts-build.js:1096)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate Extension Skeleton

You are generating a SKELETON for an AIMEAT extension. A skeleton defines the complete structure — action signatures, input/output schemas, memory keys, schedules — with ZERO implementation code.

## Project
${description}

## Component
Label: ${label}
Type: Extension (server-side V8 sandbox)

## Data Sources
${dataSources.map(ds => `- ${ds.name}: ${ds.url}${ds.sampleResponse ? `\n  Sample response: ${JSON.stringify(ds.sampleResponse, null, 2).slice(0, 2000)}` : ''}${ds.notes ? `\n  Notes: ${ds.notes}` : ''}`).join('\n')}

## Data Model Structures
${Object.entries(structures).length > 0 ? Object.entries(structures).map(([name, schema]) => `### ${name}\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``).join('\n\n') : 'No structures defined in blueprint.'}

## Memory Keys This Extension Uses
${Object.entries(memoryKeys).filter(([, v]) => v.producedBy === blueprintComponent.id || v.consumedBy?.includes(blueprintComponent.id)).map(([key, def]) => `- \`${key}\`: ${def.description || ''} (${def.producedBy === blueprintComponent.id ? 'writes' : 'reads'})`).join('\n') || 'None declared in blueprint.'}

## Actions From Blueprint
${Object.entries(actions).filter(([, v]) => v.component === blueprintComponent.id).map(([name, def]) => `- \`${name}\`: ${def.description || ''}\n  Input: ${JSON.stringify(def.input || {})}\n  Output: ${JSON.stringify(def.output || {})}`).join('\n\n') || 'None declared — infer from data sources and use cases.'}

## Schedules
${schedules.length > 0 ? schedules.map(s => `- ${s.action}: ${s.cron} — ${s.description || ''}`).join('\n') : 'None.'}

## Config Keys
${configKeys.length > 0 ? configKeys.map(k => `- ${k}`).join('\n') : 'None.'}

## Output Format

Produce a YAML skeleton document. Use EXACTLY this structure:

```yaml
component: extension
name: <kebab-case-name>
version: 1.0.0
description: <one-line description>

memory:
  writes:
    - key: <memory-key>
      schema: <inline JSON schema of the value>
  reads:
    - key: <memory-key>
      schema: <inline JSON schema>

actions:
  - id: <action-id>
    method: POST
    path: /<path>
    input: <JSON schema of input parameters>
    output: <JSON schema of return value>
    dataSource: <URL if this action calls an external API>
    sampleResponse: |
      <actual JSON from sample data — copy field names exactly>
    notes: "<any transformation notes>"
    reads: [<memory-keys-read>]
    writes: [<memory-keys-written>]
    schedule: "<cron expression if scheduled>"
    depends: [<other-action-ids-it-calls>]

config:
  keys: [<config-memory-keys>]

activate:
  initCopies: []
```

## CRITICAL RULES

1. **ZERO implementation code.** No function bodies, no ctx calls, no logic. Only signatures and schemas.
2. **Use EXACT field names from sample responses.** If the API returns `results`, write `results` not `companies`. Copy the field names character-for-character from the sampleResponse.
3. **Every action from the blueprint must appear.** Do not skip or rename actions.
4. **Output schemas must match the actual API response structure**, not an idealized version.
5. **Include sampleResponse verbatim** for every action that calls an external API — this is used for validation.
```

---
### buildDataCortexSkeletonPrompt (foundry-prompts-build.js:1199)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate Data Cortex Skeleton

You are generating a SKELETON for an AIMEAT data cortex. A data cortex wraps extension actions into clean JavaScript methods. No UI, no rendering — pure data access.

## Project
${description}

## Component
Label: ${label}
Type: Data Cortex (browser-side IIFE)

## Extension It Wraps
${extensionSkeleton || 'No extension skeleton available.'}

## Extension Probe Results (Real API Responses)
${extensionProbes ? Object.entries(extensionProbes).map(([action, result]) => `### ${action}\n\`\`\`json\n${JSON.stringify(result, null, 2).slice(0, 1500)}\n\`\`\``).join('\n\n') : 'No probes available yet.'}

## Output Format

```yaml
component: data-cortex
name: <kebab-case-name>
extension: <extension-registered-as-name>

methods:
  - name: <camelCaseMethodName>
    params: <JSON schema of parameters>
    returns: <JSON schema of return value — use EXACT field names from probe results>
    calls: <extension-action-id>
  - name: <methodName>
    returns: <schema>
    reads: <memory-key>
```

## CRITICAL RULES

1. ZERO implementation code. Only method signatures and schemas.
2. Return schemas MUST use exact field names from probe results, not invented names.
3. Every extension action that downstream components need must have a corresponding method.
```

---
### buildFeatureCortexSkeletonPrompt (foundry-prompts-build.js:1246)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate Feature Cortex Skeleton

You are generating a SKELETON for an AIMEAT feature cortex. A feature cortex combines data access + UI rendering for one use case. It exports a `render(container)` function.

## Project
${description}

## Component
Label: ${label}
Type: Feature Cortex (browser-side IIFE)

## Use Case
${useCase ? JSON.stringify(useCase, null, 2) : 'Not specified.'}

## View Definition
${view ? JSON.stringify(view, null, 2) : 'Not specified.'}

## Data Cortex API (upstream)
${dataCortexSkeleton || 'No data cortex skeleton available.'}

## Data Cortex Probe Results (Real Return Values)
${dataCortexProbes ? Object.entries(dataCortexProbes).map(([method, result]) => `### ${method}\n\`\`\`json\n${JSON.stringify(result, null, 2).slice(0, 1000)}\n\`\`\``).join('\n\n') : 'No probes available yet.'}

## Output Format

```yaml
component: feature-cortex
name: <kebab-case-name>
useCase: "<use case description>"
view: <view-id>

dataCortex: <data-cortex-name>

sections:
  - id: <section-id>
    description: "<what this section renders>"
    uses:
      data: [<dataCortexMethodNames>]
      ui: [<PlatformUIComponents>]
    translationKeys: [<key1>, <key2>]

exports:
  - name: render
    params: { container: HTMLElement }
```

## CRITICAL RULES

1. ZERO implementation code. Only section definitions and dependencies.
2. Each section should be a self-contained UI area (e.g., search form, results table, detail card).
3. Translation keys must follow the project's flat dot-notation pattern.
```

---
### buildAppDomainCortexSkeletonPrompt (foundry-prompts-build.js:1313)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate App-Domain Cortex Skeleton

You are generating a SKELETON for an AIMEAT app-domain cortex. This is the composition layer: it combines all feature cortex components + auth + i18n + settings.

## Project
${description}

## Feature Cortex Components
${featureCortexSkeletons?.map((s, i) => `### Feature ${i + 1}\n${s}`).join('\n\n') || 'No feature cortex skeletons available.'}

## Output Format

```yaml
component: app-domain-cortex
name: <kebab-case-name>

features:
  - cortex: <feature-cortex-name>
    renderTarget: "#<container-id>"

exports:
  - name: init
    description: "Auth check, load locale, apply settings"
  - name: render
    params: { container: HTMLElement }
    description: "Render tab navigation, mount features"
  - name: t
    params: { key: string }
    returns: string
  - name: switchLocale
    params: { locale: string }
  - name: getTranslations
    returns: object
```

## Translation Loading Pattern
[... code example ...]

## Auth Pattern
[... code example ...]

## Nested Object Helper (MUST include)
[... dv() helper code ...]

## CRITICAL RULES

1. ZERO implementation code in the skeleton — only structure.
2. List ALL feature cortex components that will be composed.
3. Export names must be exactly: init, render, t, switchLocale, getTranslations.
4. init() MUST call AIMEAT.auth.login() first, THEN load translations.
5. init() MUST load translations via AIMEAT.data.get('SERVICE_PREFIX.i18n.' + locale) with fallback to AIMEAT.data.get('i18n.' + locale).
6. t(key) MUST return the translated string from loaded translations, NOT the raw key.
7. render() MUST call init() first if translations aren't loaded yet.
8. Store translations in AIMEAT._translations so feature cortexes can access them.
```

---
### buildAppSkeletonPrompt (foundry-prompts-build.js:1408)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate App Skeleton

You are generating a SKELETON for an AIMEAT app. An app is an HTML page that loads the app-domain cortex and renders feature cortex components into containers.

## Project
${description}

## Views
${views.map(v => `- ${v.name}: ${v.description || ''}`).join('\n') || 'Not specified.'}

## Style Preferences
${JSON.stringify(style, null, 2)}

## App-Domain Cortex API
${appDomainCortexSkeleton || 'Not available.'}

## App-Domain Cortex Probe (Real API Surface)
${appDomainCortexProbe ? JSON.stringify(appDomainCortexProbe, null, 2).slice(0, 2000) : 'Not available.'}

## Output Format

```yaml
component: app
name: <App Display Name>
version: 1.0.0

cortex: <app-domain-cortex-name>

views:
  - id: <view-id>
    tab: <translation-key-for-tab-label>
    feature: <feature-cortex-name>
    layout: "<layout description>"

navigation: tabs
auth: required
locales: [fi, en]
defaultLocale: fi
```

## CRITICAL RULES

1. ZERO implementation code. Only view definitions and layout descriptions.
2. Every view from the interview spec must appear.
3. Tab labels must use translation keys, not hardcoded text.
```

---
### buildExtensionUnitPrompt (foundry-prompts-build.js:1467)
```
${INSTRUCTION_DISCLAIMER}
# Task: Implement Extension Action — `${unitDef.id}`

You are implementing ONE action handler for an AIMEAT extension. Return ONLY the function body.

## Skeleton (full contract — do not deviate)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## This Action's Contract
- **ID:** ${unitDef.id}
- **Method:** ${unitDef.method || 'POST'}
- **Path:** ${unitDef.path || '/' + unitDef.id}
- **Input:** `${JSON.stringify(unitDef.input || {})}`
- **Output:** `${JSON.stringify(unitDef.output || {})}`
${unitDef.reads?.length ? `- **Reads:** ${unitDef.reads.join(', ')}` : ''}
${unitDef.writes?.length ? `- **Writes:** ${unitDef.writes.join(', ')}` : ''}
${unitDef.schedule ? `- **Schedule:** ${unitDef.schedule}` : ''}
${unitDef.notes ? `- **Notes:** ${unitDef.notes}` : ''}
${ds ? `
## Data Source
- **URL:** ${ds.url}
- **Sample Response:**
\`\`\`
${typeof ds.sampleResponse === 'string' ? ds.sampleResponse.slice(0, 2000) : JSON.stringify(ds.sampleResponse, null, 2).slice(0, 2000)}
\`\`\`
${ds.notes ? `- **Notes:** ${ds.notes}` : ''}` : ''}

${SANDBOX_CONSTRAINTS}

${testExcerpt ? `## Test Excerpt (your code must pass this)\n\`\`\`\n${testExcerpt.slice(0, 1500)}\n\`\`\`` : ''}

${HTML_ENTITY_RULES}

## Output Format

Return ONLY the action function. No YAML, no manifest, no wrapping:

```javascript
export default async function(ctx, input) {
  // your implementation here
}
```

CRITICAL: The function signature MUST be `export default async function(ctx, input)` — ctx is the FIRST parameter, input is the SECOND. The V8 sandbox requires this exact format.
```

---
### buildCortexMethodUnitPrompt (foundry-prompts-build.js:1533)
```
${INSTRUCTION_DISCLAIMER}
# Task: Implement Cortex Method — `${unitDef.name}`

You are implementing ONE method for an AIMEAT ${isDataCortex ? 'data' : 'feature'} cortex. Return ONLY the function.

## Skeleton (full contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## This Method's Contract
- **Name:** ${unitDef.name}
- **Params:** `${JSON.stringify(unitDef.params || {})}`
- **Returns:** `${JSON.stringify(unitDef.returns || {})}`
${unitDef.calls ? `- **Calls extension action:** ${unitDef.calls}` : ''}
${unitDef.reads ? `- **Reads memory key:** ${unitDef.reads}` : ''}
${accessPattern}
${extensionProbeResult ? `
## Extension Probe Result (real data — match this shape)
\`\`\`json
${JSON.stringify(extensionProbeResult, null, 2).slice(0, 2000)}
\`\`\`` : ''}

## Output Format

Return ONLY the method function:

```javascript
async function ${unitDef.name}(${unitDef.params ? Object.keys(unitDef.params).join(', ') : ''}) {
  // your implementation here
}
```
```

The `accessPattern` variable when `unitDef.calls` exists:
```
## Extension Call Pattern
```javascript
// callExt helper is defined at the top of the IIFE — use it for ALL extension calls
// It uses AIMEAT.auth session.fetch() for proper auth handling
async function callExt(actionId, body) {
  try {
    if (!AIMEAT.auth || !AIMEAT.auth.getSession) {
      console.warn('[cortex] callExt(' + actionId + '): auth not available');
      return null;
    }
    var session = AIMEAT.auth.getSession();
    if (!session || !session.fetch) {
      console.warn('[cortex] callExt(' + actionId + '): no session — login required');
      return null;
    }
    var resp = await session.fetch('/v1/ext/' + EXT_NAME + '/' + actionId, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body || {})
    });
    if (!resp || !resp.ok) {
      console.warn('[cortex] callExt(' + actionId + '): failed', resp?.error || resp?.status);
      return null;
    }
    return resp.data?.result ?? resp.data ?? null;
  } catch(e) {
    console.warn('[cortex] callExt(' + actionId + '):', e.message);
    return null;
  }
}

// Usage in your method:
const result = await callExt('${unitDef.calls}', { /* input */ });
// ALWAYS null-check: if (!result) return fallbackValue;
```
IMPORTANT: session.fetch() returns ALREADY-PARSED JSON — do NOT call resp.json(). Use resp.data directly.
IMPORTANT: ALWAYS null-check the result — callExt returns null on auth failure or network error.
```

The `accessPattern` variable when `unitDef.reads` exists:
```
## Memory Read Pattern
```javascript
// Read extension data from ext: namespace
const data = await AIMEAT.data.getPublic('ext:<extension-name>', '${unitDef.reads}');
// ALWAYS null-check: data may be null on first run
```
IMPORTANT: Do NOT use session.fetch — it does not exist in cortex scope.
```

---
### buildFeatureCortexSectionPrompt (foundry-prompts-build.js:1627)
```
${INSTRUCTION_DISCLAIMER}
# Task: Implement Feature Cortex Section — `${sectionDef.id}`

You are implementing ONE UI section for a feature cortex. Return ONLY the section render function.

## Skeleton (full contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## This Section's Definition
- **ID:** ${sectionDef.id}
- **Description:** ${sectionDef.description || ''}
- **Data methods used:** ${sectionDef.uses?.data?.join(', ') || 'none'}
- **UI components used:** ${sectionDef.uses?.ui?.join(', ') || 'none'}
- **Translation keys:** ${(sectionDef.translationKeys || []).join(', ') || 'none'}
${dataCortexProbe ? `
## Data Cortex Probe Results (real data for the methods this section uses)
${Object.entries(dataCortexProbe).filter(([method]) => sectionDef.uses?.data?.includes(method)).map(([method, result]) => `### ${method}()\n\`\`\`json\n${JSON.stringify(result, null, 2).slice(0, 1000)}\n\`\`\``).join('\n\n')}` : ''}
${uiExampleText}

## Translation Helper Pattern
```javascript
// t() is available in scope — use for all user-visible text
var label = t('${(translationKeys || [])[0] || 'section.title'}');
```

## Async Data Loading Pattern (CRITICAL)
Data cortex methods return Promises. Handle loading states:
```javascript
// Show loading state
container.innerHTML = '<p>Ladataan...</p>';
// Call data cortex method
var data = await dataCortex.someMethod();
// ALWAYS null-check — callExt returns null on auth failure or first run
if (!data || (Array.isArray(data) && data.length === 0)) {
  container.innerHTML = '<p>' + (t('empty.key') || 'Ei dataa') + '</p>';
  return;
}
// Now render the data...
```

## Nested Object Helper
API responses contain nested objects. Use this to safely render values:
```javascript
function dv(val) {
  if (val == null) return '-';
  if (typeof val === 'string' || typeof val === 'number') return String(val);
  if (val.value) return val.value;
  if (val.url) return val.url;
  if (val.name) return val.name;
  if (Array.isArray(val)) return val.map(dv).join(', ');
  return JSON.stringify(val);
}
```

## Output Format

Return ONLY the section render function:

```javascript
function render${sectionDef.id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join('')}(container, { dataCortex, t }) {
  // your implementation here
}
```
```

---
### buildAppViewUnitPrompt (foundry-prompts-build.js:1764)
```
${INSTRUCTION_DISCLAIMER}
# Task: Implement App View — `${viewDef.id}`

You are implementing ONE view for an AIMEAT app. Return the view HTML + mount function.

## Skeleton (full contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## This View's Definition
- **ID:** ${viewDef.id}
- **Tab label key:** ${viewDef.tab || viewDef.id}
- **Feature cortex:** ${viewDef.feature || 'none'}
- **Layout:** ${viewDef.layout || 'default'}
${translationKeys?.length ? `- **Translation keys:** ${translationKeys.join(', ')}` : ''}
${featureCortexRenderSpec ? `
## Feature Cortex Render Spec
The feature cortex exposes `render(container)`. Call it to render the feature into the view container.
\`\`\`
${typeof featureCortexRenderSpec === 'string' ? featureCortexRenderSpec.slice(0, 1500) : JSON.stringify(featureCortexRenderSpec, null, 2).slice(0, 1500)}
\`\`\`` : ''}

## Output Format

Return the view HTML and mount function:

```html
<!-- View: ${viewDef.id} -->
<div id="view-${viewDef.id}" class="app-view" style="display:none;">
  <!-- view content -->
</div>
```

```javascript
function mount${viewDef.id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join('')}(container, { cortex, t }) {
  // mount feature cortex into container
}
```
```

---
### buildExtensionAssemblyPrompt (foundry-prompts-build.js:1813)
```
${INSTRUCTION_DISCLAIMER}
# Task: Assemble Extension — ${label}

You are ASSEMBLING a complete AIMEAT extension from a skeleton and pre-built unit implementations. This is MECHANICAL assembly — do NOT modify the unit implementations.

## Skeleton (the contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## Unit Implementations
${units.map((u, i) => `### Action: ${u.id} (unit ${i + 1})\n\`\`\`javascript\n${u.code}\n\`\`\``).join('\n\n')}

## Output Format

Produce the complete extension: YAML manifest first, then fenced JS blocks per action.

```yaml
metadata:
  name: <kebab-case-name>
  version: "1.0.0"
  description: "<from skeleton — double-quoted, one line>"
  author: foundry
  required_apis: [memory]
  config: {}
  limits:
    memory_mb: 128
    timeout_ms: 30000
    max_api_calls: 500
actions:
  - id: <action-id>
    description: "<from skeleton>"
    method: POST
    path: /v1/ext/<extension-name>/<action-path-from-skeleton>
    auth: required
    input: <schema>
    output: <schema>
    script: <action-id>.js
  # ... repeat for EVERY action from the skeleton
schedules:
  - id: <schedule-id>
    action: <action-id>
    cron: "<expression>"
    description: "<what it does>"
    instance_scope: false
    input: {}
```

Then one fenced JS block per action (use `// actions/<action-id>.js` comment):

```javascript
// actions/<action-id>.js
export default async function(ctx, input) {
  // EXACT code from the unit implementation above — do NOT modify
}
```

## CRITICAL RULES

1. **Do NOT modify the unit implementations.** Copy each action function exactly as provided.
2. **Use action IDs, schemas, and config from the skeleton.** Do not rename or reorder.
3. **Every action in the skeleton must appear in the manifest AND have a JS block.**
4. **Assembly is mechanical.** You are combining pieces, not generating new logic.
5. **metadata MUST include `author: foundry`** — the validator requires this field.
6. **Every action MUST have `method: POST` and `path: /v1/ext/<name>/<path>`** — the validator requires these.
7. **All action JS MUST use `export default async function(ctx, input)`** — the V8 sandbox requires ES module default export.

${HTML_ENTITY_RULES}
```

---
### buildCortexAssemblyPrompt (foundry-prompts-build.js:1888)
```
${INSTRUCTION_DISCLAIMER}
# Task: Assemble ${subtypeLabel} — ${label}

You are ASSEMBLING a complete AIMEAT cortex library from a skeleton and pre-built unit implementations. This is MECHANICAL assembly — do NOT modify the unit implementations.

## Skeleton (the contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## Unit Implementations
${units.map((u, i) => `### ${u.name || u.id} (unit ${i + 1})\n\`\`\`javascript\n${u.code}\n\`\`\``).join('\n\n')}

## Output Format

Produce the complete cortex: YAML manifest block, then the JS IIFE.

```yaml
apiVersion: cortex.aimeat.org/v1
kind: Extension
metadata:
  name: <kebab-case-name>
  version: "1.0.0"
  description: "<from skeleton>"
components:
  - type: lib
    filename: <name>.js
    exports: [<method-names>]
```

```javascript
// <name>.js — ${subtypeLabel} IIFE
(function (AIMEAT) {
  'use strict';
  const LIB_NAME = '<camelCaseName>'; // kebab "my-lib" → camelCase "myLib"
  const EXT_NAME = '<extension-name>'; // the extension this cortex wraps

  // --- callExt helper — uses session.fetch() for proper auth ---
  async function callExt(actionId, body) {
    [... full callExt implementation ...]
  }

  // --- readExtMemory helper — reads extension-owned data ---
  async function readExtMemory(key) {
    try { return await AIMEAT.data.getPublic('ext:' + EXT_NAME, key); }
    catch(e) { return null; }
  }

  // --- Translation helper — reads from AIMEAT._translations (set by app-domain cortex init) ---
  function t(key) {
    var translations = AIMEAT._translations || {};
    return translations[key] || '';
  }

  // --- Nested object display helper ---
  function dv(val) {
    [... full dv() implementation ...]
  }

  // --- Unit implementations (copied exactly) ---
  // PASTE each unit function here.

  // --- Registration ---
  const exports = { /* all public method names */ };
  if (AIMEAT.register) AIMEAT.register(LIB_NAME, exports);
  AIMEAT[LIB_NAME] = exports;
})(window.AIMEAT || (window.AIMEAT = {}));
```

YAML manifest MUST include `namespace: community` under metadata.

## CRITICAL RULES

1. **Do NOT modify the unit implementations.**
2. **YAML metadata.name (kebab-case) and JS LIB_NAME (camelCase) must correspond.**
3. **YAML metadata MUST have namespace: community.**
4. **Every method from the skeleton must appear in the exports object.**
5. **Use (function(AIMEAT){...})(window.AIMEAT||(window.AIMEAT={}))** — NOT (function(){...})().
6. **Must have `const exports = { ... }`** — the validator checks for this pattern.
7. **Assembly is mechanical.** Combine pieces, don't generate new logic.
8. **callExt uses session.fetch()** which returns ALREADY-PARSED JSON. Use `resp.data`, NEVER `resp.json()`.
9. **callExt returns null on error** — all methods MUST null-check results before using them.
10. **ALWAYS handle null from callExt gracefully** — return empty arrays `[]` or empty objects `{}`, NEVER crash.

${HTML_ENTITY_RULES}
```

---
### buildAppAssemblyPrompt (foundry-prompts-build.js:2011)
```
${INSTRUCTION_DISCLAIMER}
# Task: Assemble App — ${label}

You are ASSEMBLING a complete AIMEAT app HTML document from a skeleton and pre-built view implementations. This is MECHANICAL assembly — do NOT modify the unit implementations.

## Skeleton (the contract)
```yaml
${typeof skeleton === 'string' ? skeleton : JSON.stringify(skeleton, null, 2)}
```

## View Implementations
${units.map((u, i) => `### View: ${u.id} (unit ${i + 1})\n**HTML:**\n\`\`\`html\n${u.html || ''}\n\`\`\`\n**JS:**\n\`\`\`javascript\n${u.code || ''}\n\`\`\``).join('\n\n')}

## App-Domain Cortex
Name: ${appDomainCortexName || '<from skeleton>'}
${designSystem ? `\n## Design System\n${designSystem}` : ''}

## Output Format

Produce the complete HTML document. EVERY section below is MANDATORY:

```html
<!-- AIMEAT App Manifest
name: <kebab-case-name>
version: 1.0.0
description: <one-line description>
entry: index.html
-->
<!DOCTYPE html>
<html lang="fi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${label}</title>
  <meta http-equiv="Content-Security-Policy" content="...">
  <style>
    :root { [... CSS variables ...] }
    body { ... }
    #app { ... }
  </style>
</head>
<body>
  <!-- Error collector script -->
  <script>[... error collector ...]</script>

  <div id="auth-container"></div>
  <div id="app"></div>

  <script>
    // loadScript helper
    function loadScript(src) { ... }

    // Boot sequence
    (async function() {
      try {
        // 1. Load AIMEAT platform libraries FIRST
        await loadScript('/v1/libs/aimeat-auth.js');
        await loadScript('/v1/libs/aimeat-data.js');

        // 2. Load platform UI libraries
        [... 5 platform UI cortex loads ...]

        // 3. Load domain cortex libraries
        // ADD ALL cortex script tags here in dependency order

        // 4. Auth
        AIMEAT.auth.mountLoginButton('#auth-container', { ... });
        var session = await AIMEAT.auth.login();
        if (session) startApp();
      } catch(err) { ... }
    })();

    async function startApp() {
      var cortex = AIMEAT['<camelCaseName>'];
      if (!cortex) { ... return; }
      await cortex.init();
      cortex.render(document.getElementById('app'));
    }
  </script>
</body>
</html>
```

## CRITICAL RULES

1. **MUST start with `<!-- AIMEAT App Manifest ... -->` comment**
2. **MUST load aimeat-auth.js and aimeat-data.js** before any cortex scripts.
3. **MUST call AIMEAT.auth.mountLoginButton('#auth-container', ...)** for login UI.
4. **MUST call AIMEAT.auth.login()** to restore session before starting the app.
5. **MUST load cortex scripts in dependency order**
6. **startApp() is called ONLY after successful login**
7. **Do NOT modify the view unit implementations.**
8. **Include the error collector script** at the top of body.
9. **Include CSP meta tag** if loading any CDN scripts.

${HTML_ENTITY_RULES}
```

---

## FILE 3: foundry-prompts-test.js

---
### buildTestPrompt (foundry-prompts-test.js:19)
```
${INSTRUCTION_DISCLAIMER}You are generating TEST CODE for a component in an AIMEAT service.

## Component Under Test
- Type: ${componentType}
- Label: ${componentLabel}
- Registered as: ${registeredAs || 'unknown'}
${goldenSection}${testSection}
## Data Structures (from blueprint — test against THESE shapes)
${blueprint?.dataModel?.structures ? Object.entries(blueprint.dataModel.structures).map(([name, schema]) =>
    `### ${name}\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``
  ).join('\n\n') : 'No structures defined'}

## Action Contracts (from blueprint — test THESE methods with THESE shapes)
${blueprint?.dataModel?.actions ? Object.entries(blueprint.dataModel.actions).map(([name, def]) =>
    `- **${name}**: input=${JSON.stringify(def.input || {})}, output=${def.output?.$ref ? '$ref:' + def.output.$ref : JSON.stringify(def.output || 'any')}`
  ).join('\n') : 'No actions defined'}

## Project Context
Blueprint components:
${bpComponents}

Use cases from interview:
${useCases}

${testEnvDoc}

## Platform Rules
${componentType === 'extension' ? SANDBOX_CONSTRAINTS + '\n\n' : ''}${EXTENSION_CONSUMPTION_RULES}

## Output Rules
1. Return ONLY executable JavaScript code — NO markdown fences, NO explanation text
2. NO import/require/export — sandbox environment
3. Self-contained async function body
4. Server tests: return { passed, errors, details }
5. Browser tests: set window.__testResults = { passed, errors, details }
```

The `browserEnvDoc` and `serverEnvDoc` variables are embedded inline in the prompt (lines 131-307) -- their full text is shown in the file read above.

---
### buildExtensionTestFirstPrompt (foundry-prompts-test.js:359)
```
${INSTRUCTION_DISCLAIMER}
Generate TEST CODE for an extension that does not exist yet.
This test defines WHAT the extension must do — it is a specification.

## Data Structures (the extension must produce data matching these)
${structuresText}

## Actions to Test
${scenariosText}

## Test Data Reference
${testEntity}

## Environment
Server-side sandbox. Available: callExt(extName, actionId, body), readExtMemory(extName, key).
callExt returns the action's result (envelope unwrapped). readExtMemory returns the memory value.

## Rules
- Test each action listed above
- For actions that call external APIs: check response SHAPE matches the structure, graceful error = PASS
- For actions that only use memory: assert specific return values
- The extension name will be provided when the test runs — use a placeholder like 'EXT_NAME'
- Return ONLY executable JavaScript. No markdown fences.
- End with: return { passed: boolean, errors: string[], details: string }
```

---
### buildTestFirstPrompt (foundry-prompts-test.js:426)
```
${INSTRUCTION_DISCLAIMER}
# Task: Generate Tests From Contract (Test-First)

You are generating tests BEFORE any implementation code exists. You have NEVER seen the implementation. Your tests define what "correct" means based on the CONTRACT (blueprint signatures + interview sample data).

## Project
${label}

## Component Being Tested
Type: ${componentType}${subtype ? ` (${subtype})` : ''}

## Action/Method Signatures (from blueprint)
${Object.entries(actions)
    .filter(([, v]) => v.component === blueprintComponent.id)
    .map(([name, def]) => `### ${name}\n- Input: \`${JSON.stringify(def.input || {})}\`\n- Output: \`${JSON.stringify(def.output || {})}\`\n- Description: ${def.description || 'N/A'}`)
    .join('\n\n') || 'No actions declared in blueprint.'}

## Data Structures
${Object.entries(structures).map(([name, schema]) => `### ${name}\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``).join('\n\n') || 'No structures.'}

## Sample Data (from interview — this is REAL data)
${sampleData.map(sd => `### ${sd.name}\n\`\`\`json\n${JSON.stringify(sd.sample, null, 2).slice(0, 2000)}\n\`\`\``).join('\n\n') || 'No sample data.'}

## Test Scenarios (from blueprint)
${scenarios.map((s, i) => `${i + 1}. ${s.description || s}`).join('\n') || 'No specific scenarios — test each action/method.'}

${environmentRules}

## Output

Produce a single code block with the test code. The tests must:

1. **Test each action/method signature** — call it, verify the response has the correct field names and types
2. **Test with sample data** — if sample data is provided, use it to verify real behavior
3. **Test error cases** — empty input, missing required fields
4. **Test data shapes** — verify response objects have the EXACT fields declared in the blueprint structures
5. **NEVER assume implementation details** — test the public contract only

```javascript
// Tests for: ${label}
// Generated from: blueprint contract + interview samples
// This code was generated BEFORE the implementation exists.
```
```

---

## FILE 4: foundry-prompts-cortex-data.js

---
### buildDataCortexPrompt (foundry-prompts-cortex-data.js:25)
```
${INSTRUCTION_DISCLAIMER}
Create a Data Cortex library for: ${label}

Project: ${projectDescription}

## Goal

Build a client-side JavaScript library (IIFE) that provides data access methods.
This is the DATA LAYER — pure data access, no UI rendering.
Other cortex components will use this library to get and modify data.

## Structures (shared data types — use these exact shapes)

${structuresText}

## Methods to Export

${cortexActions.map(a => {
  const inputStr = a.input ? JSON.stringify(a.input) : '{}';
  const outputRef = a.output?.$ref || JSON.stringify(a.output || 'any');
  return `- **${a.method}**(${Object.keys(a.input || {}).join(', ')}) → returns ${outputRef}`;
}).join('\n')}

${extBundle ? `
## Extension (this cortex wraps it)

Extension name: ${extBundle.registeredAs}
Actions: ${extBundle.actions?.join(', ') || 'none'}

### Actual responses from live probes:
${(extBundle.probeResults || []).map(p =>
  `${p.action}(${JSON.stringify(p.input)}) → ${JSON.stringify(p.response).substring(0, 500)}`
).join('\n')}

Use callExt('${extBundle.registeredAs}', actionId, body) for extension calls.
session.fetch returns ALREADY-PARSED JSON — use resp.data, never resp.json().
` : `
## No Extension

This data cortex uses AIMEAT platform libraries directly (no extension needed).
`}

## AIMEAT Platform Libraries Available

- **AIMEAT.data** — get(key), set(key, value), delete(key), list(opts), search(query), getPublic(gaii, key), getEntry(key), update(key, value, version)
- **AIMEAT.storage** — upload(file), download(key), list(), delete(key)
- **AIMEAT.social** — createBoard(name), post(boardId, content), boards(), posts(boardId)
- **AIMEAT.wallet** — balance(), transactions()
- **AIMEAT.auth** — login(), getSession(), mountLoginButton(container)

## Data Access Rules (CRITICAL — follow precisely)

Two namespaces, two different methods:

1. **Extension runtime data** [...]:
   → Read with: `AIMEAT.data.getPublic('ext:EXTENSION_NAME', key)`

2. **Owner/user data** [...]:
   → Read with: `AIMEAT.data.get(key)`

[... full output format with YAML manifest and JS IIFE template including callExt, readExtMemory, readOwnerMemory, dv() helpers ...]

## Error Handling Rules (CRITICAL)

1. **callExt returns null on error** — EVERY method MUST null-check before using results
2. **Return safe fallbacks** — empty arrays `[]`, empty objects `{}`, or default values. NEVER crash.
3. **On first run, extension data may not exist** — always handle null/empty gracefully
4. **console.warn on failures** — so developers can diagnose issues in browser console
5. **NEVER call resp.json()** on session.fetch results — they are already parsed

## No HTML Entities in Code

Your code MUST use real operators (>, <, &&), NOT HTML entities (&gt; &lt; &amp;).
```

---

## FILE 5: foundry-prompts-cortex-feature.js

---
### PLATFORM_UI_EXAMPLES (foundry-prompts-cortex-feature.js:19)
```
## Platform UI Cortex Libraries — Working Examples

All components take an options object. They return a DOM element you append to your container.

### Tabs (AIMEAT['aimeat-ui-nav'].Tabs)
```javascript
var tabs = AIMEAT['aimeat-ui-nav'].Tabs({
  target: container,
  tabs: [
    { id: 'search', label: 'Haku', icon: '🔍' },
    { id: 'watchlist', label: 'Seuranta', icon: '⭐' },
    { id: 'settings', label: 'Asetukset', icon: '⚙' }
  ],
  active: 'search',
  onChange: function(tabId) { renderTabContent(tabId); }
});
```

### DataTable (AIMEAT['aimeat-ui-viewers'].DataTable)
[... full example ...]

### Timeline (AIMEAT['aimeat-ui-viewers'].Timeline)
[... full example ...]

### Form components (AIMEAT['aimeat-ui-forms'])
[... Input, Select, Toggle examples ...]

### Dialogs (AIMEAT['aimeat-ui-dialogs'])
[... toast, Confirm, Modal examples ...]
```

---
### buildFeatureCortexPrompt (foundry-prompts-cortex-feature.js:141)
```
${INSTRUCTION_DISCLAIMER}
Create a Feature Cortex component for: ${label}

## Use Case
${useCase ? `**${useCase.title}** [${useCase.priority}]: ${useCase.description}` : 'No specific use case provided'}

## View
${view ? `**${view.title}** (${view.type}): ${view.description}${view.interactions ? '\nInteractions: ' + view.interactions.join(', ') : ''}` : 'No specific view provided'}

## Goal

Build a self-contained feature module (data + UI) as a cortex IIFE.
It must export a `render(container)` function that:
1. Creates all DOM elements for this feature
2. Fetches data from the data cortex
3. Renders the data using platform UI components
4. Handles user interactions
5. Uses translation keys for all visible text

Think of it like aimeat-charts: `ChartPanel({ target: container, ... })` creates a complete chart.
Your `render(container)` creates a complete feature view.

## Data Structures
${structuresText}

${dataCortexAPI}

${PLATFORM_UI_EXAMPLES}

${translationSection}

## Loading Translations
[... translation loading pattern with ${dataCortexBundle?.registeredAs || 'SERVICE_NAME'} ...]

NOTE: In most apps, the app-domain cortex loads translations during init() and stores them in `AIMEAT._translations`. Feature cortexes can access them via `AIMEAT._translations` — you don't need to load them again in each feature.

## Translation Helper
[... t() function ...]

## Nested Object Helper
[... dv() function ...]

## Output Format

Return TWO separate, properly tagged code blocks.
[... YAML manifest template + JS IIFE template ...]
```

---

## FILE 6: foundry-prompts-cortex-app.js

---
### buildAppDomainCortexPrompt (foundry-prompts-cortex-app.js:23)
```
${INSTRUCTION_DISCLAIMER}
Create an App-Domain Cortex for: ${label}

Project: ${projectDescription}

## Goal

Build the top-level cortex library that the APP will use. It composes:
1. All feature cortex components (renders them into containers)
2. Auth initialization (AIMEAT.auth)
3. Translation loading and management
4. Settings management
5. Navigation support

The app loads ONLY this cortex. This cortex provides everything the app needs.

## Feature Cortex Components (compose these)
${featureAPIs}

## Data Cortex
${dataCortexBundle ? `
Access via: AIMEAT.${dataCortexBundle.libName}
Methods: ${(dataCortexBundle.exports || []).join(', ')}
` : 'No data cortex available'}

## Translation Keys Available
${translationKeys}

## Methods to Export

- **init()** — Initialize auth, load translations, check data readiness. Returns { ready: boolean, authenticated: boolean }.
- **render(container)** — Render the full application UI into the container. Sets up navigation, renders feature views.
- **getTranslations(locale)** — Load translation strings for a locale. Returns the translation object.
- **t(key, vars)** — Translate a key with optional variable interpolation. Uses loaded translations.
- **switchLocale(locale)** — Change language, reload translations, re-render.

## Auth Pattern
[... code example with mountLoginButton ...]

## Translation Pattern
[... loadTranslations and t() code examples ...]

## Output Format

Return TWO separate, properly tagged code blocks.
[... YAML manifest template + JS IIFE template ...]
```

---

## FILE 7: foundry-prompts-fix.js

---
### buildBlueprintFixPrompt (foundry-prompts-fix.js:22)
```
${INSTRUCTION_DISCLAIMER}Your previous blueprint response was not valid. DO NOT try to fix the old response — generate a fresh one.

ERRORS from previous attempt:
${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}

Common mistakes to avoid:
- Do NOT include manifest content, code, HTML, or implementation details in the blueprint
- Each component must have: "id", "type", "label", "produces", "consumes". Extension components may also have "schedules".
- The entire response must be valid JSON — no trailing commas, no unescaped quotes

${buildBlueprintPrompt(description, interviewSpec)}
```

---
### buildExplainPrompt (foundry-prompts-fix.js:44)
```
You just generated a ${componentType} component. Before we validate it, explain what you built.

## Generated Code (abbreviated)
${(generatedResult || '').substring(0, 2000)}

## Blueprint Contract
This component must produce: ${produces}

## Your Task (2-5 sentences, no code)
1. What does this component export or provide?
2. What data shapes does it return?
3. Does it match the blueprint contract above? If not, what's missing?
```

---
### buildReflectionPrompt (foundry-prompts-fix.js:60)
```
You are debugging a failed component. Your job is to DIAGNOSE the problem — do NOT write code.

## Failed Code
${failedResult}

## Errors
${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}
${testContext ? buildTestContextSection(testContext) : ''}

## Your Task

Analyze the errors and the ACTUAL API RESPONSES above. In 2-5 sentences, explain:
1. What is the ROOT CAUSE of each error?
2. What specific data shapes or field names does the code assume vs what the API actually returns?
3. What specific lines or patterns in the code need to change?

Be precise — reference exact field names from the API responses. Do NOT write code.
```

---
### buildFixPrompt (foundry-prompts-fix.js:81)
```
${INSTRUCTION_DISCLAIMER}The following result had validation errors. Fix ONLY the errors listed below.

${HTML_ENTITY_RULES}
${typeConstraint}
ORIGINAL PROMPT:
${originalPrompt}

FAILED RESULT:
${failedResult}

ERRORS:
${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}
${testContext ? buildTestContextSection(testContext) : ''}${attemptsSection}${reflectionSection}
Return the corrected result in the same format as the original.
```

---
### buildFreshGenerationPrompt (foundry-prompts-fix.js:140)
```
${originalPrompt}
${pitfalls}${traceSection}
IMPORTANT: This is a fresh generation. Do NOT reference any previous code.
Study the ACTUAL API RESPONSES above carefully and match those exact data shapes.
```

---
### buildImpactPrompt (foundry-prompts-fix.js:206)
```
${INSTRUCTION_DISCLAIMER}You are analyzing the impact of a change to an AIMEAT service.

## Service Blueprint

${componentList}

## Proposed Change

${changeRequest}

## Your Task

Analyze which components need to be modified for this change. For EACH component, classify as:

- **ROOT CAUSE** — this component directly causes the problem or is the primary target of the change
- **NEEDS UPDATE** — this component must change because upstream data shape or API changed
- **NO CHANGE** — this component is unaffected

Return a JSON object:
```json
{
  "analysis": [
    {
      "id": "ext-1",
      "label": "Component Label",
      "impact": "root|update|none",
      "reason": "One sentence explaining why this component is/isn't affected",
      "suggestedChange": "Brief description of what to change, or null if no change needed"
    }
  ],
  "summary": "One paragraph overview of the change and its blast radius"
}
```

Rules:
- Be conservative — if you're unsure whether a component needs updating, mark it as "update" not "none"
- If the change affects data shape (fields, types, formats), ALL downstream consumers need "update"
- If the change is purely visual/UI, only the app needs updating
- Include ALL components in the analysis, even those with "none" impact
```

---
### buildEditPrompt (foundry-prompts-fix.js:258)
```
${INSTRUCTION_DISCLAIMER}${AIMEAT_CONTEXT}

You are modifying an existing AIMEAT ${typeLabel}: **${label}**
${typeConstraints[type] || ''}
## Current Installed Code

\`\`\`
${currentCode}
\`\`\`

## Change Request

${changeRequest}
${upstreamSection}
## Rules

- Modify ONLY what the change request asks for
- Keep ALL other code, structure, and logic identical
- Do NOT refactor, restyle, rename, or "improve" unrelated code
- Do NOT add features, comments, or documentation beyond what's requested
- Return the COMPLETE modified component in the same format as the original
- If the component is YAML + JavaScript (extension), return both in the same format
- If the change request is unclear, make the minimal change that addresses it

Return the complete modified ${typeLabel} — not a diff, not a partial snippet.
```

---
### buildFoundryReflectionPrompt (foundry-prompts-fix.js:326)
```
${INSTRUCTION_DISCLAIMER}
# Task: Diagnose Pipeline Failure

You are a diagnostic agent for the AIMEAT foundry multi-pass pipeline. A pass has failed. Your job is to DIAGNOSE the root cause and PRESCRIBE a corrective action. Do NOT write code.

## Skeleton (the contract)
```yaml
${skeletonText}
```

## Pre-Generated Tests (the target)
```
${testTruncated}
```

## What Failed
- **Pass:** ${failedPass || 'unknown'}
- **Errors:**
${(errors || []).map((e, i) => `  ${i + 1}. ${e}`).join('\n')}

## Failed Output (truncated)
```
${outputTruncated}
```
${probesSection}${historySection}
## Your Task

Analyze the failure and produce a YAML response with this EXACT structure:

```yaml
diagnosis:
  what_failed: "<which unit/pass/assembly step failed>"
  why: "<1-2 sentences explaining the symptoms>"
  root_cause: "<the fundamental reason — data shape mismatch? wrong API field? logic error? skeleton error?>"

prescription:
  action: <one of: retry_unit, modify_skeleton, modify_test, regenerate_unit, regenerate_assembly, escalate_to_user>
  target: "<which specific unit/section/action to fix>"
  change: "<exactly what to change — be specific>"
  context_for_next_pass: "<any context the next pass needs to succeed>"

history_note: "<1 sentence summary for the reflection log>"
```

## Rules

1. **Never prescribe the same action+target twice.** Check the previous attempts above. If "retry_unit" on "fetch-data" already failed, try "modify_skeleton" or "regenerate_unit" instead.
2. **Be specific.** "Fix the code" is not a prescription. "Change field name from 'results' to 'companies' in action fetch-data output schema" is.
3. **Trace the data path.** If a downstream unit fails, check whether upstream probe data matches what the skeleton promised.
4. **Escalate early.** If 3+ attempts have failed on the same target, prescribe "escalate_to_user" — the human needs to intervene.
5. **Do NOT write code.** Diagnosis and prescription only.
```

---

That covers every template string across all 7 files -- 7 shared constants (`AIMEAT_CONTEXT`, `INSTRUCTION_DISCLAIMER`, `SANDBOX_CONSTRAINTS`, `NAMESPACE_RULES`, `HTML_ENTITY_RULES`, `EXTENSION_CONSUMPTION_RULES`, `INIT_CONTRACT`), 6 `COMPONENT_TEMPLATES` entries (`csm`, `msm`, `extension`, `app`, `memory`, `translation`, `cortex`), and all prompt-building functions from the build, test, cortex-data, cortex-feature, cortex-app, and fix modules.