youiyoux 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { createRequire as __youiRequire } from "node:module"; const require = __youiRequire(import.meta.url);
2
2
  import {
3
+ PICTURE_SCALE,
3
4
  SCANNER_VERSION,
4
5
  buildTokens,
5
6
  checkInputFor,
@@ -12,10 +13,10 @@ import {
12
13
  ruleCapability,
13
14
  runChecks,
14
15
  scanProject,
16
+ screenSlug,
15
17
  systemPairs,
16
18
  tokenImpact
17
- } from "./chunk-3ZHGONNA.js";
18
- import "./chunk-S7UV34H5.js";
19
+ } from "./chunk-L5BV4PI7.js";
19
20
  import {
20
21
  BUILT_IN_CHECKS,
21
22
  COMPONENT_STATES,
@@ -23,6 +24,10 @@ import {
23
24
  ElementId,
24
25
  LIBRARY_DIR,
25
26
  LibraryEntry,
27
+ REQUEST_ID,
28
+ REQUEST_STATUSES,
29
+ RequestDoc,
30
+ RequestTarget,
26
31
  RuleCheck,
27
32
  RuleRollout,
28
33
  RuleSpec,
@@ -38,7 +43,7 @@ import {
38
43
  toPosix,
39
44
  tokenValueProblems,
40
45
  writeFileAtomic
41
- } from "./chunk-ZLRFD5JI.js";
46
+ } from "./chunk-C3TSTYP7.js";
42
47
  import {
43
48
  __toESM
44
49
  } from "./chunk-E4HNK7VF.js";
@@ -805,7 +810,7 @@ import { readFile as readFile2, stat } from "node:fs/promises";
805
810
  import { extname, join as join3, normalize, resolve, sep } from "node:path";
806
811
 
807
812
  // ../workflow/src/policy.ts
808
- var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff"]);
813
+ var PERSON_ONLY = /* @__PURE__ */ new Set(["approve", "handoff", "close"]);
809
814
  var WorkflowError = class extends Error {
810
815
  name = "WorkflowError";
811
816
  code;
@@ -817,7 +822,7 @@ var WorkflowError = class extends Error {
817
822
  }
818
823
  };
819
824
  function authorize(actor, action) {
820
- if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
825
+ if (PERSON_ONLY.has(action) && actor.kind !== "person") throw new WorkflowError("forbidden", action === "close" ? `an ${actor.kind} cannot close or reopen a request: mark it addressed, and a person closes it` : `an ${actor.kind} cannot ${action}: approving a design change is a person's decision`);
821
826
  }
822
827
 
823
828
  // ../workflow/src/http.ts
@@ -846,6 +851,9 @@ var CreateInput = external_exports.union([
846
851
  external_exports.discriminatedUnion("mode", [external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("edit"), ruleId: ElementId }), external_exports.object({ kind: external_exports.literal("rule").optional(), mode: external_exports.literal("create") })])
847
852
  ]);
848
853
  var SaveInput = external_exports.object({ revision: external_exports.number().int().positive(), content: external_exports.union([DraftContentInput, StyleContentInput, LibraryContentInput]) });
854
+ var RequestInput = external_exports.object({ kind: external_exports.enum(["comment", "change"]), body: external_exports.string().min(1).max(4e3), target: RequestTarget });
855
+ var ReplyInput = external_exports.object({ body: external_exports.string().min(1).max(4e3) });
856
+ var StatusInput = external_exports.object({ status: external_exports.enum(REQUEST_STATUSES), body: external_exports.string().max(4e3).optional() });
849
857
  function styleContent(content) {
850
858
  return { ...content, tokens: content.tokens.map((t) => Object.fromEntries(Object.entries(t).filter(([, v]) => v !== void 0))) };
851
859
  }
@@ -927,6 +935,31 @@ async function api(req, res, path, options) {
927
935
  if ("kind" in input && input.kind === "library") return send(res, 201, await workflow.create(actor, { kind: "library", content: input.content }));
928
936
  return send(res, 201, await workflow.create(actor, "kind" in input && input.kind === "style" ? { kind: "style", ...input.content ? { content: styleContent(input.content) } : {} } : input));
929
937
  }
938
+ if (path === "/api/requests" && method === "GET") return send(res, 200, await (await workflow.requests()).list());
939
+ if (path === "/api/requests" && method === "POST") return send(res, 201, await (await workflow.requests()).create(actor, parse2(RequestInput, await readJson(req))));
940
+ const request = /^\/api\/requests\/(r-[0-9a-f]{10})(?:\/(replies|status|compare|before\.webp|after\.webp))?$/.exec(path);
941
+ if (request) {
942
+ const [, id, action] = request;
943
+ const store = await workflow.requests();
944
+ if (!action && method === "GET") return send(res, 200, await store.get(id));
945
+ if (action === "compare" && method === "GET") {
946
+ const { before, after } = await store.compare(id);
947
+ const view = (picture, which) => picture ? { ...picture, file: void 0, url: `/api/requests/${id}/${which}.webp` } : null;
948
+ return send(res, 200, { before: view(before, "before"), after: view(after, "after") });
949
+ }
950
+ if ((action === "before.webp" || action === "after.webp") && method === "GET") {
951
+ const picture = (await store.compare(id))[action === "before.webp" ? "before" : "after"];
952
+ if (!picture) throw new WorkflowError("not-found", `${id} has no ${action.replace(".webp", "")} picture`);
953
+ res.writeHead(200, { ...BASE_HEADERS, "content-type": "image/webp", "cache-control": "no-store" });
954
+ res.end(await readFile2(picture.file));
955
+ return;
956
+ }
957
+ if (action === "replies" && method === "POST") return send(res, 200, await store.reply(actor, id, parse2(ReplyInput, await readJson(req)).body));
958
+ if (action === "status" && method === "POST") {
959
+ const input = parse2(StatusInput, await readJson(req));
960
+ return send(res, 200, await store.setStatus(actor, id, input.status, input.body ?? ""));
961
+ }
962
+ }
930
963
  const match = /^\/api\/drafts\/(d-[0-9a-f]{10})(?:\/(preview|approve|rebase|handoff))?$/.exec(path);
931
964
  if (match) {
932
965
  const id = match[1];
@@ -1101,20 +1134,178 @@ function comparePreview(input) {
1101
1134
  };
1102
1135
  }
1103
1136
 
1104
- // ../workflow/src/service.ts
1137
+ // ../workflow/src/requests.ts
1105
1138
  import { randomBytes } from "node:crypto";
1139
+ import { existsSync } from "node:fs";
1140
+ import { copyFile, mkdir, readdir, readFile as readFile3, stat as stat2 } from "node:fs/promises";
1141
+ import { join as join4 } from "node:path";
1142
+ var REQUESTS_DIR = ".youi/requests";
1143
+ var FILE = /^(r-[0-9a-f]{10})\.json$/;
1144
+ var authorOf = (actor) => ({ kind: actor.kind, name: actor.name });
1145
+ var screensDir = (root) => join4(root, ".youi/screens");
1146
+ var RequestStore = class {
1147
+ dir;
1148
+ root;
1149
+ project;
1150
+ options;
1151
+ /** `root`: the product's root, where `.youi/` lives; `project`: its id. */
1152
+ constructor(root, project, options = {}) {
1153
+ this.dir = join4(root, REQUESTS_DIR);
1154
+ this.root = root;
1155
+ this.project = project;
1156
+ this.options = options;
1157
+ }
1158
+ now() {
1159
+ return (this.options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
1160
+ }
1161
+ file(id) {
1162
+ if (!REQUEST_ID.test(id)) throw new WorkflowError("bad-request", `"${id.slice(0, 40)}" is not a request id (r-<10 hex>)`);
1163
+ return join4(this.dir, `${id}.json`);
1164
+ }
1165
+ async write(request) {
1166
+ const checked = RequestDoc.parse(request);
1167
+ await writeFileAtomic(this.file(checked.id), `${JSON.stringify(checked, null, 2)}
1168
+ `);
1169
+ return checked;
1170
+ }
1171
+ /** Every request, newest first. */
1172
+ async list() {
1173
+ const names = await readdir(this.dir).catch(() => []);
1174
+ const requests = [];
1175
+ const unreadable = [];
1176
+ for (const name of names.sort()) {
1177
+ const match = FILE.exec(name);
1178
+ if (!match) continue;
1179
+ const file = join4(REQUESTS_DIR, name);
1180
+ let value;
1181
+ try {
1182
+ value = JSON.parse(await readFile3(join4(this.dir, name), "utf8"));
1183
+ } catch (error) {
1184
+ unreadable.push({ file, reason: error instanceof Error ? error.message.split("\n")[0] : String(error) });
1185
+ continue;
1186
+ }
1187
+ const result = RequestDoc.safeParse(value);
1188
+ if (!result.success) unreadable.push({ file, reason: result.error.issues.map((i) => `${i.path.join(".") || "file"}: ${i.message}`).join("; ") });
1189
+ else if (result.data.id !== match[1]) unreadable.push({ file, reason: "its id is not its file name" });
1190
+ else requests.push(result.data);
1191
+ }
1192
+ requests.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || a.id.localeCompare(b.id));
1193
+ return { requests, unreadable };
1194
+ }
1195
+ async get(id) {
1196
+ const text = await readFile3(this.file(id), "utf8").catch(() => null);
1197
+ if (text === null) throw new WorkflowError("not-found", `no request ${id}`);
1198
+ const parsed = RequestDoc.safeParse(JSON.parse(text));
1199
+ if (!parsed.success) throw new WorkflowError("invalid", `${REQUESTS_DIR}/${id}.json is not a valid youi.request/v1`, parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`));
1200
+ return parsed.data;
1201
+ }
1202
+ async create(actor, input) {
1203
+ authorize(actor, "draft");
1204
+ const body = input.body.trim();
1205
+ if (!body) throw new WorkflowError("invalid", "a request needs words: what to look at, or what to change");
1206
+ const at = this.now();
1207
+ const id = (this.options.newId ?? (() => `r-${randomBytes(5).toString("hex")}`))();
1208
+ const shot = join4(screensDir(this.root), `${screenSlug(input.target.route)}.light.webp`);
1209
+ let before;
1210
+ if (existsSync(shot)) {
1211
+ before = `${id}.before.webp`;
1212
+ await mkdir(this.dir, { recursive: true });
1213
+ await copyFile(shot, join4(this.dir, before));
1214
+ }
1215
+ return this.write({ schema: "youi.request/v1", id, project: this.project, kind: input.kind, status: "open", body, author: authorOf(actor), createdAt: at, updatedAt: at, target: input.target, ...before ? { before } : {}, thread: [] });
1216
+ }
1217
+ async reply(actor, id, body) {
1218
+ authorize(actor, "draft");
1219
+ const text = body.trim();
1220
+ if (!text) throw new WorkflowError("invalid", "a reply needs words");
1221
+ const request = await this.get(id);
1222
+ const at = this.now();
1223
+ return this.write({ ...request, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: text }] });
1224
+ }
1225
+ /**
1226
+ * Before and after for a request: the picture kept when it was filed, and the screen's picture
1227
+ * now, with the component found again on it by its file and line (from the new capture's places).
1228
+ */
1229
+ async compare(id) {
1230
+ const request = await this.get(id);
1231
+ const t = request.target;
1232
+ const before = request.before === `${request.id}.before.webp` && existsSync(join4(this.dir, request.before)) ? { file: join4(this.dir, request.before), box: t.box ?? null, page: t.page ?? null, at: request.createdAt } : null;
1233
+ const slug = screenSlug(t.route);
1234
+ const shot = join4(screensDir(this.root), `${slug}.light.webp`);
1235
+ const info = await stat2(shot).catch(() => null);
1236
+ if (!info) return { before, after: null };
1237
+ let box = t.box ?? null;
1238
+ let page = t.page ?? null;
1239
+ let found = false;
1240
+ try {
1241
+ const places = JSON.parse(await readFile3(join4(screensDir(this.root), `${slug}.places.json`), "utf8"));
1242
+ const again = t.file && t.line ? places.places?.find((p) => p.file === t.file && p.line === t.line && p.name === t.name) : void 0;
1243
+ if (places.size) page = places.size;
1244
+ if (again) {
1245
+ box = again.box;
1246
+ found = true;
1247
+ }
1248
+ } catch {
1249
+ }
1250
+ const since = request.thread.filter((e) => e.status === "open").at(-1)?.at ?? request.createdAt;
1251
+ const at = info.mtime.toISOString();
1252
+ return { before, after: { file: shot, box, page, at, sinceFiled: at > since, found } };
1253
+ }
1254
+ /**
1255
+ * Move a request on: `addressed` (anyone; an agent says what it did), or `closed` and `open`
1256
+ * (a person's: closing accepts the work, reopening says it is not done).
1257
+ */
1258
+ async setStatus(actor, id, status, body = "") {
1259
+ if (status !== "addressed") authorize(actor, "close");
1260
+ else authorize(actor, "draft");
1261
+ const request = await this.get(id);
1262
+ if (request.status === status) throw new WorkflowError("conflict", `${id} is already ${status}`);
1263
+ if (status === "addressed" && request.status === "closed") throw new WorkflowError("conflict", `${id} is closed: a person reopens it first`);
1264
+ const at = this.now();
1265
+ return this.write({ ...request, status, updatedAt: at, thread: [...request.thread, { author: authorOf(actor), at, body: body.trim(), status }] });
1266
+ }
1267
+ };
1268
+ function requestBrief(request, root) {
1269
+ const t = request.target;
1270
+ const picture = join4(root, ".youi/screens", `${screenSlug(t.route)}.light.webp`);
1271
+ const at = t.file && t.line ? `${t.file}:${t.line}` : null;
1272
+ const every = t.scope === "element" && t.element;
1273
+ const where = every ? `every use of ${t.element} (system_where_used lists them)` : at ? `${at}` : `the ${t.name} on ${t.route} (its line was not captured: find it from the picture and the route)`;
1274
+ const next = request.status === "closed" ? "Closed by a person: nothing to do." : request.status === "addressed" ? "Marked addressed: a person checks it and closes or reopens it. Reply only if asked." : request.kind === "comment" ? `A comment on ${where}: reply with request_reply if it asks something; change code only if it asks for a change.` : `Make the change at ${where}${t.lookalike && t.element ? `, using the System's ${t.element} rather than the hand-built lookalike where that is the fix` : ""}; capture the screen again (youiyoux screens capture --base <the app in development> --route ${t.route}) so the person sees it after; then call request_addressed with what you changed (file:line). Ask with request_reply first if it is unclear. A person closes it.`;
1275
+ return {
1276
+ id: request.id,
1277
+ kind: request.kind,
1278
+ status: request.status,
1279
+ ask: request.body,
1280
+ from: `${request.author.kind} ${request.author.name}`,
1281
+ createdAt: request.createdAt,
1282
+ route: t.route,
1283
+ component: t.name,
1284
+ element: t.element ?? null,
1285
+ lookalike: t.lookalike === true,
1286
+ at,
1287
+ scope: every ? `everywhere ${t.element} is used` : "this one, on this screen",
1288
+ picture: existsSync(picture) ? picture : null,
1289
+ box: t.box ? { x: Math.round(t.box.x * PICTURE_SCALE), y: Math.round(t.box.y * PICTURE_SCALE), width: Math.round(t.box.width * PICTURE_SCALE), height: Math.round(t.box.height * PICTURE_SCALE) } : null,
1290
+ thread: request.thread.map((e) => `${e.author.kind} ${e.author.name}${e.status ? ` (marked ${e.status})` : ""}: ${e.body || "(no words)"}`),
1291
+ next
1292
+ };
1293
+ }
1294
+
1295
+ // ../workflow/src/service.ts
1296
+ import { randomBytes as randomBytes2 } from "node:crypto";
1106
1297
 
1107
1298
  // ../workflow/src/style-preview.ts
1108
- import { mkdir, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
1299
+ import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, writeFile } from "node:fs/promises";
1109
1300
  import { tmpdir as tmpdir2 } from "node:os";
1110
- import { dirname, join as join4 } from "node:path";
1301
+ import { dirname, join as join5 } from "node:path";
1111
1302
  var MAX_LISTED = 5e3;
1112
1303
  async function buildFrom(project, tree) {
1113
- const root = await mkdtemp2(join4(tmpdir2(), "youi-style-"));
1304
+ const root = await mkdtemp2(join5(tmpdir2(), "youi-style-"));
1114
1305
  try {
1115
1306
  for (const [path, text] of tree) {
1116
- await mkdir(dirname(join4(root, path)), { recursive: true });
1117
- await writeFile(join4(root, path), text);
1307
+ await mkdir2(dirname(join5(root, path)), { recursive: true });
1308
+ await writeFile(join5(root, path), text);
1118
1309
  }
1119
1310
  return (await buildTokens(root, project.system.config, project.config.outputs)).tokens;
1120
1311
  } finally {
@@ -1271,7 +1462,7 @@ function previewSummary(draft) {
1271
1462
  const p = draft.preview;
1272
1463
  return p ? `changes ${p.totals.tokens} token(s) and ${p.totals.contracts} component(s): ${p.totals.sites} site(s) and ${p.totals.direct} direct use(s) in ${p.totals.files} file(s) on ${p.totals.routes} screen(s)` : "no preview";
1273
1464
  }
1274
- var randomId = (prefix) => `${prefix}-${randomBytes(5).toString("hex")}`;
1465
+ var randomId = (prefix) => `${prefix}-${randomBytes2(5).toString("hex")}`;
1275
1466
  var RuleWorkflow = class {
1276
1467
  options;
1277
1468
  execution;
@@ -1282,6 +1473,12 @@ var RuleWorkflow = class {
1282
1473
  this.options = options;
1283
1474
  this.execution = options.execution ?? scannerExecution;
1284
1475
  }
1476
+ /** The product's comments and change requests on its captured screens (requests.ts), in its `.youi/requests`. */
1477
+ async requests() {
1478
+ const { projectRoot, configPath, designRoot, productRoot } = this.options;
1479
+ const project = await loadProject(projectRoot, { ...configPath ? { configPath } : {}, ...designRoot ? { designRoot } : {}, ...productRoot ? { productRoot } : {} });
1480
+ return new RequestStore(project.root, project.config.id, this.options.now ? { now: this.options.now } : {});
1481
+ }
1285
1482
  now() {
1286
1483
  return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
1287
1484
  }
@@ -1925,8 +2122,8 @@ function shellQuote(path) {
1925
2122
  }
1926
2123
 
1927
2124
  // ../workflow/src/store.ts
1928
- import { mkdir as mkdir2, readdir, readFile as readFile3 } from "node:fs/promises";
1929
- import { join as join5 } from "node:path";
2125
+ import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4 } from "node:fs/promises";
2126
+ import { join as join6 } from "node:path";
1930
2127
  var DRAFT_ID = /^d-[0-9a-f]{10}$/;
1931
2128
  var FORMAT = "youiyoux.draft/v1";
1932
2129
  var COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
@@ -1951,18 +2148,18 @@ var FileStore = class {
1951
2148
  artifacts;
1952
2149
  constructor(dir) {
1953
2150
  this.location = dir;
1954
- this.drafts = join5(dir, "drafts");
1955
- this.artifacts = join5(dir, "handoffs");
2151
+ this.drafts = join6(dir, "drafts");
2152
+ this.artifacts = join6(dir, "handoffs");
1956
2153
  }
1957
2154
  async list() {
1958
- await mkdir2(this.drafts, { recursive: true });
1959
- const names = (await readdir(this.drafts)).filter((name) => /^d-[0-9a-f]{10}\.json$/.test(name)).sort();
2155
+ await mkdir3(this.drafts, { recursive: true });
2156
+ const names = (await readdir2(this.drafts)).filter((name) => /^d-[0-9a-f]{10}\.json$/.test(name)).sort();
1960
2157
  const drafts = [];
1961
2158
  const unreadable = [];
1962
2159
  for (const name of names) {
1963
- const file = join5(this.drafts, name);
2160
+ const file = join6(this.drafts, name);
1964
2161
  try {
1965
- drafts.push(decode(await readFile3(file, "utf8"), file));
2162
+ drafts.push(decode(await readFile4(file, "utf8"), file));
1966
2163
  } catch (error) {
1967
2164
  unreadable.push({ file, reason: error instanceof Error ? error.message : String(error) });
1968
2165
  }
@@ -1971,10 +2168,10 @@ var FileStore = class {
1971
2168
  }
1972
2169
  async get(id) {
1973
2170
  checkId(id);
1974
- const file = join5(this.drafts, `${id}.json`);
2171
+ const file = join6(this.drafts, `${id}.json`);
1975
2172
  let text;
1976
2173
  try {
1977
- text = await readFile3(file, "utf8");
2174
+ text = await readFile4(file, "utf8");
1978
2175
  } catch (error) {
1979
2176
  if (error.code === "ENOENT") return null;
1980
2177
  throw error;
@@ -1983,12 +2180,12 @@ var FileStore = class {
1983
2180
  }
1984
2181
  async put(draft) {
1985
2182
  checkId(draft.id);
1986
- await writeFileAtomic(join5(this.drafts, `${draft.id}.json`), `${JSON.stringify({ schema: FORMAT, draft }, null, 2)}
2183
+ await writeFileAtomic(join6(this.drafts, `${draft.id}.json`), `${JSON.stringify({ schema: FORMAT, draft }, null, 2)}
1987
2184
  `);
1988
2185
  }
1989
2186
  async putArtifact(name, content) {
1990
2187
  if (!/^[a-z0-9][a-z0-9.-]*$/.test(name)) throw new Error(`not an artifact name: ${name}`);
1991
- const file = join5(this.artifacts, name);
2188
+ const file = join6(this.artifacts, name);
1992
2189
  await writeFileAtomic(file, content);
1993
2190
  return file;
1994
2191
  }
@@ -2012,72 +2209,75 @@ var MemoryStore = class {
2012
2209
  return `memory:${name}`;
2013
2210
  }
2014
2211
  };
2212
+
2015
2213
  export {
2016
- DEFAULT_FILES,
2017
- DRAFT_ID,
2018
- DesignFiles,
2019
- RuleWorkflow as DesignWorkflow,
2020
- FileStore,
2021
- GitError,
2022
2214
  JsonEditError,
2023
- MAX_FINDINGS,
2024
- MAX_LISTED,
2025
- MAX_TITLE,
2026
- MemoryStore,
2027
- RuleWorkflow,
2028
- SESSION_HEADER,
2029
- WORKFLOW_VERSION,
2030
- WorkflowError,
2031
- adoptionRefusal,
2032
- authorize,
2033
- branchTip,
2034
- changesDigest,
2035
- comparePreview,
2036
- componentChanges,
2037
- contentDigest,
2038
- createHandler,
2039
- createReviewBranch,
2040
- defaultBranch,
2215
+ readJsonPath,
2216
+ setJsonPath,
2041
2217
  deleteJsonPath,
2042
- describeContrast,
2043
- describeLibrary,
2044
- describeStyle,
2045
- draftStatus,
2046
- effectiveStateEdits,
2047
- effectiveTokenEdits,
2048
- emptyStyleContent,
2218
+ DEFAULT_FILES,
2049
2219
  fieldsFromRule,
2220
+ newRuleFields,
2221
+ toRuleSpec,
2222
+ validateContent,
2223
+ contentDigest,
2224
+ renderRuleYaml,
2225
+ renderContract,
2226
+ draftStatus,
2227
+ componentChanges,
2228
+ GitError,
2050
2229
  git,
2051
- hasChanges,
2230
+ locate,
2231
+ defaultBranch,
2232
+ branchTip,
2052
2233
  headCommit,
2234
+ readAt,
2053
2235
  isAncestor,
2054
- libraryDigest,
2055
- libraryPath,
2056
- librarySlug,
2057
2236
  listAt,
2058
2237
  listTreeAt,
2059
- locate,
2060
- newContrastFailures,
2061
- newRuleFields,
2062
- normalizeLibraryContent,
2238
+ hasChanges,
2239
+ changesDigest,
2240
+ createReviewBranch,
2241
+ MAX_TITLE,
2242
+ emptyStyleContent,
2063
2243
  normalizeStyleContent,
2064
- previewStyle,
2065
- previewSummary,
2066
- readAt,
2067
- readJsonPath,
2068
- renderContract,
2069
- renderContractStates,
2070
- renderLibraryEntry,
2071
- renderRuleYaml,
2244
+ styleDigest,
2245
+ tokenPathOf,
2246
+ effectiveTokenEdits,
2247
+ validateStyleContent,
2248
+ effectiveStateEdits,
2072
2249
  renderTokenEdits,
2250
+ renderContractStates,
2251
+ styleSlug,
2252
+ describeStyle,
2253
+ DesignFiles,
2073
2254
  scannerExecution,
2255
+ WorkflowError,
2256
+ authorize,
2257
+ SESSION_HEADER,
2258
+ createHandler,
2074
2259
  serveWorkflow,
2075
- setJsonPath,
2076
- styleDigest,
2077
- styleSlug,
2078
- toRuleSpec,
2079
- tokenPathOf,
2080
- validateContent,
2260
+ normalizeLibraryContent,
2261
+ libraryDigest,
2262
+ libraryPath,
2263
+ librarySlug,
2264
+ adoptionRefusal,
2081
2265
  validateLibraryContent,
2082
- validateStyleContent
2266
+ renderLibraryEntry,
2267
+ describeLibrary,
2268
+ MAX_FINDINGS,
2269
+ comparePreview,
2270
+ REQUESTS_DIR,
2271
+ RequestStore,
2272
+ requestBrief,
2273
+ MAX_LISTED,
2274
+ previewStyle,
2275
+ WORKFLOW_VERSION,
2276
+ previewSummary,
2277
+ RuleWorkflow,
2278
+ describeContrast,
2279
+ newContrastFailures,
2280
+ DRAFT_ID,
2281
+ FileStore,
2282
+ MemoryStore
2083
2283
  };