makaron-persona-look-cli 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## What an Agent gets
6
6
 
7
- `preview` downloads the selected Persona source/approved auxiliary views and the Look source, plus a safe `selection.json` render brief. It is intentionally a **source-reference pack**, not a synthesized image. `render` is the delivery route: it sends those private inputs to the caller's authenticated Makaron CLI and writes exactly one full-body front/right-side/back turnaround sheet. The Persona is the only identity reference; the Look image's face is excluded.
7
+ `find` is the no-generation delivery route for a single library result: `--kind persona` returns one matched Persona image, and `--kind look` returns one matched Look image. It does not expose IDs or selection metadata. `preview` downloads the selected Persona source/approved auxiliary views and the Look source, plus a safe `selection.json` render brief. It is intentionally a **source-reference pack**, not a synthesized image. `render` is the combined Persona + Look delivery route: it sends those private inputs to the caller's authenticated Makaron CLI and writes exactly one full-body front/right-side/back turnaround sheet. The Persona is the only identity reference; the Look image's face is excluded.
8
8
 
9
9
  The raw `private-library/` is ignored by Git and is never included in the npm package. D1 holds catalog metadata, private R2 holds approved source bytes, and a Worker issues an individual credential to each enrolling Agent. The individual credential is stored only in the Agent's local mode-600 config file and is never printed by the CLI.
10
10
 
@@ -41,15 +41,22 @@ personlib remote sync --library ./private-library --json
41
41
 
42
42
  ## Agent natural-language route
43
43
 
44
- Once setup and `remote doctor` succeed, an Agent can fulfill a request such as “按这段文案获取对应图” with:
44
+ Once setup and `remote doctor` succeed, use the route that matches the request:
45
45
 
46
46
  ```bash
47
- personlib remote render --brief '20 秒嘻哈说唱女 rapper 舞台视频,红黑街头风、宽松工装、强节奏' --output-dir ./personlib-turnaround --json
47
+ # Persona-only: return one matching library image; no Makaron request.
48
+ personlib remote find --kind persona --brief '我要一个长相高冷的女生' --output-dir ./personlib-persona --json
49
+
50
+ # Look-only: return one matching library image; no Makaron request.
51
+ personlib remote find --kind look --brief '我要一套 Y2K 的妆造' --output-dir ./personlib-look --json
52
+
53
+ # Persona + Look: render the fused three-view image.
54
+ personlib remote render --brief '我要一个嘻哈风格的亚洲女生' --output-dir ./personlib-turnaround --json
48
55
  ```
49
56
 
50
- This is a paid external generation action: the Agent must first have working Makaron authentication and receive explicit approval to spend credits. The result is `turnaround.png` (or `.jpg`/`.webp`), plus `turnaround-plan.json`, `prompt_used.md`, and `qc_report.md`. The CLI never automatically retries a paid job. Use `--dry-run` to inspect the exact prompt and matching IDs without downloading inputs or submitting anything, or `--no-wait` to keep the returned Makaron run ID for later retrieval.
57
+ The third route is a paid external generation action. In the owner-approved direct-delivery mode, a combined Persona + Look request authorizes exactly one Makaron submission, so the Agent generates and returns the turnaround without a second confirmation step. The result is `turnaround.png` (or `.jpg`/`.webp`), plus `turnaround-plan.json`, `prompt_used.md`, and `qc_report.md`. The CLI removes only brand identifiers: visible logos, wordmarks, brand names, protected monograms, recognizable trademark symbols, and brand/team crests. Ordinary stripes, numbers, abstract graphics, and shoe construction remain part of the Look. The CLI never automatically retries a paid job. Use `--dry-run` only to inspect the exact prompt and matching IDs without downloading inputs or submitting anything, or `--no-wait` to keep the returned Makaron run ID for later retrieval.
51
58
 
52
- For a free reference-only check, retain the explicit preview command:
59
+ For an explicit free Persona + Look source-reference pack, retain `preview`:
53
60
 
54
61
  ```bash
55
62
  personlib remote preview --brief '20 秒嘻哈说唱女 rapper 舞台视频,红黑街头风、宽松工装、强节奏' --output-dir ./personlib-selection --json
@@ -118,6 +118,19 @@ async function select(env, { brief, personaId, lookId }) {
118
118
  return { persona, look, look_score: lookScore };
119
119
  }
120
120
 
121
+ async function selectSingle(env, { kind, brief }) {
122
+ if (!['persona', 'look'].includes(kind)) throw new Error("kind must be persona or look");
123
+ const table = kind === "persona" ? "persona_records" : "look_records";
124
+ const candidates = (await records(env, table)).map((record) => ({
125
+ record,
126
+ score: kind === "persona" ? scorePersona(record, brief) : scoreLook(record, brief)
127
+ })).sort((a, b) => b.score - a.score || a.record.id.localeCompare(b.record.id));
128
+ const selected = candidates[0];
129
+ if (!selected) throw new Error(`no ${kind === "persona" ? "Persona" : "Look"} records available`);
130
+ if (kind === "look" && selected.score < 6) throw new Error("no catalog Look matched this brief");
131
+ return selected.record;
132
+ }
133
+
121
134
  async function route(request, env) {
122
135
  const url = new URL(request.url);
123
136
  if (request.method === "POST" && url.pathname === "/v1/agents/setup") {
@@ -183,6 +196,19 @@ async function route(request, env) {
183
196
  return json({ composition: { persona: publicPersona(selected.persona), look: publicLook(selected.look), policies: { use_persona_as_only_identity_reference: true, look_source_face: "excluded", source_logos_and_wordmarks: "replace-with-original-generic-design" }, render_brief: buildRenderBrief(selected.persona, selected.look), render_status: "brief-only-no-generation-submitted" } });
184
197
  } catch (cause) { return error(cause.message, 404); }
185
198
  }
199
+ if (request.method === "POST" && url.pathname === "/v1/find") {
200
+ const body = await request.json().catch(() => undefined);
201
+ if (!body?.brief) return error("brief is required");
202
+ try {
203
+ const kind = String(body.kind || "");
204
+ const selected = await selectSingle(env, { kind, brief: String(body.brief) });
205
+ return json({
206
+ kind: `${kind}-reference`,
207
+ image_status: "library-reference-not-generated",
208
+ assets: [{ role: kind, asset_id: selected.source_asset_id, url: `/v1/assets/${encodeURIComponent(selected.source_asset_id)}` }]
209
+ });
210
+ } catch (cause) { return error(cause.message, 404); }
211
+ }
186
212
  if (request.method === "POST" && url.pathname === "/v1/preview") {
187
213
  const body = await request.json().catch(() => undefined);
188
214
  try {
@@ -101,6 +101,14 @@ test("owner sync, self-registration, recommendation, compose, and private previe
101
101
  assert.match(recommendation.render_brief, /do not use the Look reference face/i);
102
102
  assert.match(recommendation.render_brief, /urban transit/);
103
103
  assert.doesNotMatch(recommendation.render_brief, /undefined/);
104
+ response = await worker.fetch(request("/v1/find", { method: "POST", headers, body: JSON.stringify({ kind: "persona", brief: "长相高冷的成年女生" }) }), env);
105
+ const personaFind = await response.json();
106
+ assert.equal(response.status, 200);
107
+ assert.deepEqual(personaFind, { kind: "persona-reference", image_status: "library-reference-not-generated", assets: [{ role: "persona", asset_id: "P-001:source", url: "/v1/assets/P-001%3Asource" }] });
108
+ response = await worker.fetch(request("/v1/find", { method: "POST", headers, body: JSON.stringify({ kind: "look", brief: "地铁皮夹克通勤" }) }), env);
109
+ const lookFind = await response.json();
110
+ assert.equal(response.status, 200);
111
+ assert.deepEqual(lookFind, { kind: "look-reference", image_status: "library-reference-not-generated", assets: [{ role: "look", asset_id: "L-132:source", url: "/v1/assets/L-132%3Asource" }] });
104
112
  response = await worker.fetch(request("/v1/compose", { method: "POST", headers, body: JSON.stringify({ persona_id: "P-001", look_id: "L-132" }) }), env);
105
113
  assert.equal((await response.json()).composition.policies.look_source_face, "excluded");
106
114
  response = await worker.fetch(request("/v1/preview", { method: "POST", headers, body: JSON.stringify({ persona_id: "P-001", look_id: "L-132" }) }), env);
@@ -189,10 +189,11 @@ async function requestedReferencePack(args) {
189
189
  return { remote, payload };
190
190
  }
191
191
 
192
- async function downloadReferencePack(remote, payload, destination, args) {
192
+ async function downloadReferencePack(remote, payload, destination, args, { roles, writeSelection = true } = {}) {
193
193
  mkdirSync(destination, { recursive: true });
194
194
  const downloaded = [];
195
- for (const asset of payload.assets || []) {
195
+ const assets = (payload.assets || []).filter((asset) => !roles || roles.includes(asset.role));
196
+ for (const asset of assets) {
196
197
  const response = await fetch(`${remote.endpoint}${asset.url}`, { headers: { authorization: `Bearer ${remote.token}` } });
197
198
  if (!response.ok) throw new Error(`remote asset download failed (${response.status}): ${asset.asset_id}`);
198
199
  const extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp" }[response.headers.get("content-type")] || ".bin";
@@ -202,10 +203,25 @@ async function downloadReferencePack(remote, payload, destination, args) {
202
203
  writeFileSync(target, Buffer.from(await response.arrayBuffer()));
203
204
  downloaded.push({ role: asset.role, filename: name, path: target });
204
205
  }
205
- writeFileSync(join(destination, "selection.json"), `${JSON.stringify({ ...payload, assets: downloaded.map(({ path, ...asset }) => asset) }, null, 2)}\n`, "utf8");
206
+ if (writeSelection) writeFileSync(join(destination, "selection.json"), `${JSON.stringify({ ...payload, assets: downloaded.map(({ path, ...asset }) => asset) }, null, 2)}\n`, "utf8");
206
207
  return downloaded;
207
208
  }
208
209
 
210
+ async function find(args, output) {
211
+ const kind = getOption(args, "--kind");
212
+ if (!['persona', 'look'].includes(kind)) throw new Error("usage: personlib remote find --kind <persona|look> --brief TEXT --output-dir PATH");
213
+ const brief = getOption(args, "--brief");
214
+ if (!brief) throw new Error("personlib remote find requires --brief TEXT");
215
+ const destination = getOption(args, "--output-dir");
216
+ if (!destination) throw new Error("personlib remote find requires --output-dir PATH so it can return an image");
217
+ const remote = agentConnection(args);
218
+ const { payload } = await request(remote.endpoint, "/v1/find", { token: remote.token, method: "POST", json: { kind, brief } });
219
+ const absoluteDestination = resolve(destination);
220
+ const files = await downloadReferencePack(remote, payload, absoluteDestination, args, { roles: [kind], writeSelection: false });
221
+ if (files.length !== 1) throw new Error(`library search expected one ${kind} image, received ${files.length}`);
222
+ jsonOutput({ ok: true, retrieval: { kind: `${kind}-reference`, image_status: "library-reference-not-generated", output_dir: absoluteDestination, files: files.map(({ path }) => path) } }, output, args);
223
+ }
224
+
209
225
  async function preview(args, output) {
210
226
  const { remote, payload } = await requestedReferencePack(args);
211
227
  const destination = getOption(args, "--output-dir");
@@ -262,6 +278,8 @@ function providerImages(payload) {
262
278
  return [...new Map(images.map((item) => [item.url, item])).values()];
263
279
  }
264
280
 
281
+ const BRAND_IDENTIFIER_REMOVAL_CONTRACT = "Brand identifier removal: preserve the Look's silhouette, palette, material, styling energy, decorative stripes, abstract graphics, non-brand numbers, and ordinary shoe construction. Remove only a visible logo, wordmark, brand name, protected monogram, recognizable trademark symbol, or brand/team crest, replacing it with blank fabric or an original non-brand detail. Do not add any recognizable trademark symbol or branded text. If an element is not a brand identifier, retain it rather than simplifying the styling.";
282
+
265
283
  function turnaroundPrompt(payload) {
266
284
  if (!payload?.render_brief) throw new Error("remote selection did not include a render brief");
267
285
  return [
@@ -270,6 +288,7 @@ function turnaroundPrompt(payload) {
270
288
  "Layout: three equal vertical panels at identical scale: left is straight-on front view, center is exact right-side profile, right is back view. Show head-to-toe including both shoes in every panel.",
271
289
  "Identity rule: the Persona reference is the only identity reference. Preserve the same adult facial structure, skin traits, stable marks, apparent age, hairline, build, and proportions in all three panels. Never use the Look-reference face.",
272
290
  "Look rule: use the Look reference only for the selected outfit, hairstyle, makeup, accessories, palette, and material direction. Keep the exact same clothing, footwear, hairstyle, and accessories in every panel. Ignore any scene, person, text, logo, or watermark in the Look reference.",
291
+ BRAND_IDENTIFIER_REMOVAL_CONTRACT,
273
292
  "Use a seamless neutral light-grey studio background, even lighting, fixed eye-level camera, natural relaxed standing pose, clean anatomy, and no props.",
274
293
  "No text, labels, watermark, source logos, team marks, celebrity likenesses, or recognizable brand elements."
275
294
  ].join(" ");
@@ -298,6 +317,7 @@ async function renderTurnaround(args, output) {
298
317
  project: getOption(args, "--project") || "auto",
299
318
  output_dir: outputDir,
300
319
  source_policy: "persona-is-the-only-identity-reference",
320
+ brand_identifier_policy: "remove-identifiers-only",
301
321
  prompt
302
322
  };
303
323
  if (has(args, "--dry-run")) {
@@ -320,7 +340,7 @@ async function renderTurnaround(args, output) {
320
340
  const runId = providerRunId(submitted);
321
341
  const submission = { run_id: runId, project_id: providerBody(submitted).projectId || providerBody(submitted).project_id, project_url: providerBody(submitted).projectUrl || providerBody(submitted).project_url };
322
342
  if (has(args, "--no-wait")) {
323
- writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Requested asset: front-side-back turnaround sheet\n`, "utf8");
343
+ writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Requested asset: front-side-back turnaround sheet\n- Required check: no logo, wordmark, brand name, protected monogram, recognizable trademark symbol, or brand/team crest. Preserve ordinary stripes, numbers, abstract graphics, and shoe construction.\n`, "utf8");
324
344
  jsonOutput({ ok: true, submitted: true, render: { ...plan, ...submission, qc_state: "PENDING_VISUAL_QC" } }, output, args);
325
345
  return;
326
346
  }
@@ -333,7 +353,7 @@ async function renderTurnaround(args, output) {
333
353
  const media = await fetch(images[0].url);
334
354
  if (!media.ok) throw new Error(`Makaron image download failed (${media.status})`);
335
355
  writeFileSync(finalPath, Buffer.from(await media.arrayBuffer()));
336
- writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Asset: ${basename(finalPath)}\n- Required check: one adult Persona identity across front, exact side, and back views; one unchanged Look; no source Look face, logos, or text.\n- Retry policy: no automatic reroll.\n`, "utf8");
356
+ writeFileSync(join(outputDir, "qc_report.md"), `# QC report\n\n- State: PENDING_VISUAL_QC\n- Run ID: ${runId}\n- Asset: ${basename(finalPath)}\n- Required check: one adult Persona identity across front, exact side, and back views; one unchanged Look; no source Look face, logo, wordmark, brand name, protected monogram, recognizable trademark symbol, or brand/team crest. Preserve ordinary stripes, numbers, abstract graphics, and shoe construction.\n- Retry policy: no automatic reroll.\n`, "utf8");
337
357
  jsonOutput({ ok: true, render: { ...plan, ...submission, status: body.status || "completed", qc_state: "PENDING_VISUAL_QC", files: { turnaround: finalPath, plan: join(outputDir, "turnaround-plan.json"), prompt: join(outputDir, "prompt_used.md"), qc_report: join(outputDir, "qc_report.md") } } }, output, args);
338
358
  }
339
359
 
@@ -378,7 +398,8 @@ export async function runRemote(args, { output, readManifest }) {
378
398
  return authenticated(childArgs, output, "/v1/compose", { persona_id: personaId, look_id: lookId });
379
399
  }
380
400
  if (subcommand === "preview") return preview(args.slice(1), output);
401
+ if (subcommand === "find") return find(args.slice(1), output);
381
402
  if (subcommand === "render") return renderTurnaround(args.slice(1), output);
382
403
  if (subcommand === "sync") return sync(args.slice(1), output, { readManifest });
383
- throw new Error("usage: personlib remote <setup|doctor|recommend|compose|preview|render|sync>");
404
+ throw new Error("usage: personlib remote <setup|doctor|recommend|compose|preview|find|render|sync>");
384
405
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-persona-look-cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Portable Persona and Look library for Makaron agents",
5
5
  "private": false,
6
6
  "type": "module",
@@ -11,24 +11,23 @@ Persona is the stable identity reference: face structure, natural skin traits, s
11
11
 
12
12
  ## Natural-language routing
13
13
 
14
+ - Search-only rule: requests that ask only for a Persona or only for a Look do not authorize image generation. Return the matched reference image only—never expose IDs, match scores, or internal selection metadata—and do not call Makaron or `remote render`.
15
+ - “我要一个长相高冷的女生” is Persona-only search: use `personlib remote find --kind persona --brief TEXT --output-dir PATH --json`, then return only the Persona reference image. Do not generate a three-view image.
16
+ - “我要一套 Y2K 的妆造” is Look-only search: use `personlib remote find --kind look --brief TEXT --output-dir PATH --json`, then return only the Look reference image. Do not generate a three-view image.
14
17
  - “这张图进 Persona 库” is an owner-only intake request. Require clear owner authorization, then use `personlib intake --into persona --rights authorized`.
15
18
  - “这张图进 Look 库” is an owner-only intake request. Use `personlib intake --into look --rights authorized`; extract only clothing, hair, makeup, accessory, palette, material, and scene information. Never use the source face as a new Persona.
16
- - “找一个人物” is a remote search request: use `personlib remote recommend --brief TEXT` after `personlib remote doctor` succeeds.
17
- - “为广告找一个人物” should use `personlib recommend --brief TEXT`. If there is no appropriate Look asset, use the returned temporary Look only for this request; do not save it unless the owner asks.
18
- - “按这段文案获取对应图” should use `personlib remote render --brief TEXT --output-dir PATH --json` on 小龙虾. The default engine is Makaron and the requested output is exactly one full-body front/right-side/back turnaround sheet of one adult Persona in one unchanged Look. Before that command, confirm a successful `personlib remote doctor`, local Makaron authentication, and the user's explicit approval to spend generation credits. Use `--dry-run` to show the chosen Persona/Look IDs and exact prompt without submitting.
19
+ - “找一个人物” and “为广告找一个人物” are Persona-only searches: use `personlib remote find --kind persona --brief TEXT --output-dir PATH --json`, then return only the Persona reference image. Do not expose the selected ID or create a temporary Look.
20
+ - A request that combines a person and a style—such as “我要一个嘻哈风格的亚洲女生”—is an image request and should use `personlib remote render --brief TEXT --output-dir PATH --json` on 小龙虾. Explicit wording such as “生成三视图”, “出图”, “生成这个人物”, or “做视频出图” follows the same route. The default engine is Makaron and the output is exactly one full-body front/right-side/back turnaround sheet of one adult Persona in one unchanged Look. In the owner-approved direct-delivery mode, that request authorizes exactly one paid submission: do not pause to display Persona/Look or request a second confirmation. Return the finished turnaround and QC result directly. Use `--dry-run` only when the user asks to preview the selection without generating.
19
21
  - “只看看匹配的参考” should use `personlib remote preview --brief TEXT --output-dir PATH --json`. This is free retrieval of the private Persona + Look reference pack, not a synthesized image.
20
- - “都市通勤 / 职场 / 杂志感” should use `personlib recommend --brief TEXT`; prefer catalog office, commute, workwear, and magazine-editorial tags before creating a temporary Look.
21
- - “晚宴 / 红毯 / 高级派对” should use `personlib recommend --brief TEXT`; prefer adult gala, red-carpet, black-tie, cocktail, and luxury-party Looks without retaining a source event or person identity.
22
- - “运动休闲时尚” should use `personlib recommend --brief TEXT`; prefer adult athleisure, retro sport, track, court, and sport-luxe Looks without retaining team, school, or brand identity.
23
- - “给这个人物换妆造” means keep the Persona and select or create a Look.
24
- - “用这个人物生成” means compose Persona first, Look second, then use `personlib remote render --persona P-xxx --look L-xxx --output-dir PATH --json` through the caller's Makaron authentication.
22
+ - “都市通勤 / 职场 / 杂志感”, “晚宴 / 红毯 / 高级派对”, and “运动休闲时尚” are Look-only searches: use `personlib remote find --kind look --brief TEXT --output-dir PATH --json`, then return only the Look reference image. Do not generate a person.
23
+ - “给这个人物换妆造” and “用这个人物生成” are combined Persona + Look requests: select the matching internal records, then use `personlib remote render --brief TEXT --output-dir PATH --json`. Return only the finished turnaround to the user; never expose internal IDs.
25
24
 
26
25
  ## Access boundary
27
26
 
28
- Any enrolled OpenClaw Agent may use credentialed `remote doctor`, `remote recommend`, `remote compose`, `remote preview`, and `remote render`. It may not call `remote sync`, upload source images, modify tags, delete assets, or publish the bundle. `remote render` additionally needs the Agent's own already-authenticated Makaron CLI and explicit credit-spend approval. Open enrollment is an owner-selected access rule; never request or print API keys, Agent tokens, owner tokens, or Makaron credentials. `preview` returns source references only; `render` returns the turnaround and preserves its plan, prompt, run ID, and QC record.
27
+ Any enrolled OpenClaw Agent may use credentialed `remote doctor`, `remote find`, `remote recommend`, `remote compose`, `remote preview`, and `remote render`. It may not call `remote sync`, upload source images, modify tags, delete assets, or publish the bundle. `remote render` additionally needs the Agent's own already-authenticated Makaron CLI. In owner-approved direct-delivery mode, a combined Persona + Look request is approval for exactly one paid submission; do not ask a second confirmation. Open enrollment is an owner-selected access rule; never request or print API keys, Agent tokens, owner tokens, or Makaron credentials. `find` returns one library image; `preview` returns source references only; `render` returns the turnaround and preserves its plan, prompt, run ID, and QC record.
29
28
 
30
29
  ## Current local-admin milestone
31
30
 
32
- The current package supports local Persona and Look intake, list, show, integrity validation, `recommend --brief TEXT`, `fetch --brief TEXT --output-dir PATH`, and `compose --persona P-xxx --look L-xxx`; it also ships the remote Worker/client implementation. The remote commands need a deployed private Worker and a successful top-level `setup` before use; do not claim that the current host is live until `remote doctor` succeeds against its Worker URL. `remote render` obtains the selected private inputs just in time, calls Makaron with the Persona as the sole identity reference, and asks for one neutral-studio front/right-side/back turnaround. It keeps internal inputs in `_internal_references/` for traceability but only the finished turnaround is customer-ready. Reject an output that is not one three-view adult turnaround, mixes identities or Looks, uses the Look source face, or includes source text/logos. Record `PASS`, `REROLL`, or `BLOCKED`; do not submit a hidden retry.
31
+ The current package supports local Persona and Look intake, list, show, integrity validation, `recommend --brief TEXT`, `fetch --brief TEXT --output-dir PATH`, `compose --persona P-xxx --look L-xxx`, and no-generation `remote find --kind persona|look --brief TEXT --output-dir PATH`; it also ships the remote Worker/client implementation. The `find` route returns only one requested reference image, with no IDs or selection metadata. The remote commands need a deployed private Worker and a successful top-level `setup` before use; do not claim that the current host is live until `remote doctor` succeeds against its Worker URL. `remote render` obtains the selected private inputs just in time, calls Makaron with the Persona as the sole identity reference, and asks for one neutral-studio front/right-side/back turnaround. It retains the Look's silhouette, palette, materials, energy, ordinary stripes, numbers, abstract graphics, and shoe construction. It removes only brand identifiers: logos, wordmarks, brand names, protected monograms, recognizable trademark symbols, and brand/team crests. It keeps internal inputs in `_internal_references/` for traceability but only the finished turnaround is customer-ready. Reject an output that is not one three-view adult turnaround, mixes identities or Looks, uses the Look source face, or includes a brand identifier. Record `PASS`, `REROLL`, or `BLOCKED`; do not submit a hidden retry.
33
32
 
34
33
  When a source has preppy, uniform-adjacent, or youth-coded styling, store only an adult office-core/editorial interpretation. Do not use a student identity, school context, or sexualized youth styling.