@ai-matrx/records 0.95.1 → 0.96.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/{client-DQWdJeFM.d.ts → client-D7bvxEZW.d.ts} +9 -0
  3. package/dist/{client-CEX4WdvX.d.cts → client-DKgbZUNt.d.cts} +9 -0
  4. package/dist/core/index.cjs +4 -0
  5. package/dist/core/index.cjs.map +1 -1
  6. package/dist/core/index.d.cts +3 -2
  7. package/dist/core/index.d.ts +3 -2
  8. package/dist/core/index.js +4 -0
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/data/index.cjs +4 -0
  11. package/dist/data/index.cjs.map +1 -1
  12. package/dist/data/index.d.cts +1 -1
  13. package/dist/data/index.d.ts +1 -1
  14. package/dist/data/index.js +4 -0
  15. package/dist/data/index.js.map +1 -1
  16. package/dist/{declareTable-Bkh3C1Ao.d.ts → declareTable-D_CiMnXM.d.ts} +1 -1
  17. package/dist/{declareTable-DeQ9_TMb.d.cts → declareTable-VoNRi_j_.d.cts} +1 -1
  18. package/dist/questions/index.d.cts +1 -1
  19. package/dist/questions/index.d.ts +1 -1
  20. package/dist/react/index.cjs +4 -0
  21. package/dist/react/index.cjs.map +1 -1
  22. package/dist/react/index.d.cts +9 -0
  23. package/dist/react/index.d.ts +9 -0
  24. package/dist/react/index.js +4 -0
  25. package/dist/react/index.js.map +1 -1
  26. package/dist/setup/index.cjs +4 -0
  27. package/dist/setup/index.cjs.map +1 -1
  28. package/dist/setup/index.d.cts +1 -1
  29. package/dist/setup/index.d.ts +1 -1
  30. package/dist/setup/index.js +4 -0
  31. package/dist/setup/index.js.map +1 -1
  32. package/dist/templates/index.cjs +124 -65
  33. package/dist/templates/index.cjs.map +1 -1
  34. package/dist/templates/index.d.cts +6 -1
  35. package/dist/templates/index.d.ts +6 -1
  36. package/dist/templates/index.js +124 -65
  37. package/dist/templates/index.js.map +1 -1
  38. package/dist/typed-table/index.cjs +4 -0
  39. package/dist/typed-table/index.cjs.map +1 -1
  40. package/dist/typed-table/index.d.cts +2 -2
  41. package/dist/typed-table/index.d.ts +2 -2
  42. package/dist/typed-table/index.js +4 -0
  43. package/dist/typed-table/index.js.map +1 -1
  44. package/dist/views/index.d.cts +1 -1
  45. package/dist/views/index.d.ts +1 -1
  46. package/package.json +2 -2
@@ -28,6 +28,7 @@ __export(templates_exports, {
28
28
  CHOICE_COLORS: () => CHOICE_COLORS,
29
29
  CHOICE_COLORS_DISTINCT_UP_TO: () => CHOICE_COLORS_DISTINCT_UP_TO,
30
30
  DEFAULT_TEMPLATE_AGENT: () => DEFAULT_TEMPLATE_AGENT,
31
+ DESCRIBE_RESULT_KIND: () => DESCRIBE_RESULT_KIND,
31
32
  DESIGNATED_REAL_NUMBERS: () => DESIGNATED_REAL_NUMBERS,
32
33
  ENTITY_REFERENCE_KINDS: () => ENTITY_REFERENCE_KINDS,
33
34
  FORMULA_FUNCTION_NAMES: () => FORMULA_FUNCTION_NAMES,
@@ -85,6 +86,7 @@ __export(templates_exports, {
85
86
  deferredRelations: () => deferredRelations,
86
87
  derivedFieldSpec: () => derivedFieldSpec,
87
88
  describeCheck: () => describeCheck,
89
+ describeResultJsonSchema: () => describeResultJsonSchema,
88
90
  describeSpec: () => describeSpec,
89
91
  describeVocabulary: () => describeVocabulary,
90
92
  digestSpec: () => digestSpec,
@@ -113,6 +115,7 @@ __export(templates_exports, {
113
115
  numberedStreet: () => numberedStreet,
114
116
  packageDescribeResult: () => packageDescribeResult,
115
117
  parseAt: () => parseAt,
118
+ parseDescribeTemplate: () => parseDescribeTemplate,
116
119
  parseGateFormula: () => parseGateFormula,
117
120
  parseNumeric: () => parseNumeric,
118
121
  pathOfAt: () => pathOfAt,
@@ -1861,9 +1864,9 @@ var ACCOUNT_DOOR_PATTERNS = [
1861
1864
  instead: "Call create_fixture_user / fixture_user (aidream/aidream/testing/persona.py) or createFixtureUser / withFixtureUser (scripts/lib/persona.mjs). A rolled-back raw insert is allowed with a trailing matrx-fixture:rollback-only <reason> on the same line."
1862
1865
  }
1863
1866
  ];
1864
- function findPlaceholders(text) {
1867
+ function findPlaceholders(text2) {
1865
1868
  const hits = [];
1866
- const lines = text.split("\n");
1869
+ const lines = text2.split("\n");
1867
1870
  for (const line of lines) {
1868
1871
  if (ALLOW_PRAGMA.test(line)) continue;
1869
1872
  for (const pattern of PLACEHOLDER_PATTERNS) {
@@ -2729,8 +2732,8 @@ function bookingColumnsProblem(tables, booking) {
2729
2732
  if (clash) return `The booking page adds a "${label}" column to "${t.name}", which already has one ("${clash.key}"). Rename that column, or key it "${key}".`;
2730
2733
  }
2731
2734
  const status = t.fields.find((f) => f.key === "status");
2732
- const words = new Set((status?.choices ?? []).map((c) => c.toLowerCase()));
2733
- if (status && (!words.has("booked") || !words.has("cancelled"))) {
2735
+ const words2 = new Set((status?.choices ?? []).map((c) => c.toLowerCase()));
2736
+ if (status && (!words2.has("booked") || !words2.has("cancelled"))) {
2734
2737
  return `The booking page writes "booked" and "cancelled" into "${t.name}"'s status, and its choices do not hold both.`;
2735
2738
  }
2736
2739
  return null;
@@ -2848,8 +2851,8 @@ var q = (n, d) => {
2848
2851
  };
2849
2852
  var qAdd = (a, b) => q(a.n * b.d + b.n * a.d, a.d * b.d);
2850
2853
  var qCmp = (a, b) => a.n * b.d === b.n * a.d ? 0 : a.n * b.d < b.n * a.d ? -1 : 1;
2851
- function parseNumeric(text) {
2852
- const m = /^\s*([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?\s*$/.exec(text);
2854
+ function parseNumeric(text2) {
2855
+ const m = /^\s*([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?\s*$/.exec(text2);
2853
2856
  if (!m || !m[2] && !m[3]) return null;
2854
2857
  const frac = m[3] ?? "";
2855
2858
  let n = BigInt(`${m[1] === "-" ? "-" : ""}${m[2] || "0"}${frac}` || "0");
@@ -2899,9 +2902,9 @@ function unwrap(v) {
2899
2902
  return v && typeof v === "object" && !Array.isArray(v) && "value" in v ? unwrap(v.value) : v;
2900
2903
  }
2901
2904
  function boundMoment(raw) {
2902
- const text = raw.trim();
2903
- if (!text) return null;
2904
- const at = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(text) ? `${text}T00:00:00Z` : text);
2905
+ const text2 = raw.trim();
2906
+ if (!text2) return null;
2907
+ const at = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(text2) ? `${text2}T00:00:00Z` : text2);
2905
2908
  return Number.isNaN(at) ? null : at;
2906
2909
  }
2907
2910
  function storeFilterMatches(row, filter, _timeZone) {
@@ -2939,8 +2942,8 @@ function toFx(n) {
2939
2942
  return { k: "call", f: n.name, args: n.args.map(toFx) };
2940
2943
  }
2941
2944
  }
2942
- function parseGateFormula(text) {
2943
- const parsed = (0, import_formula.parseFormula)(text);
2945
+ function parseGateFormula(text2) {
2946
+ const parsed = (0, import_formula.parseFormula)(text2);
2944
2947
  return parsed.ok ? toFx(parsed.ast) : parsed.error;
2945
2948
  }
2946
2949
  function formulaRefs(n) {
@@ -3290,8 +3293,8 @@ function capabilitiesUsed(spec) {
3290
3293
  }
3291
3294
  var DESIGNATED_REAL_NUMBERS = ["9498072145", "9496662578", "9497027626"];
3292
3295
  var DIGIT_ZEROS = [1632, 1776, 1984, 2406, 2534, 2662, 2790, 2918, 3046, 3174, 3302, 3430, 3664, 3792, 3872, 4160, 6112, 6160, 65296];
3293
- function normalizeForPhones(text) {
3294
- return text.normalize("NFKC").replace(/\p{Nd}/gu, (c) => {
3296
+ function normalizeForPhones(text2) {
3297
+ return text2.normalize("NFKC").replace(/\p{Nd}/gu, (c) => {
3295
3298
  const cp = c.codePointAt(0) ?? 48;
3296
3299
  const zero = DIGIT_ZEROS.find((z) => cp >= z && cp <= z + 9);
3297
3300
  return zero === void 0 ? c : String(cp - zero);
@@ -3305,8 +3308,8 @@ function nanpBad(area, exchange, line) {
3305
3308
  const n = Number(line);
3306
3309
  return exchange !== "555" || n < 100 || n > 199 || DESIGNATED_REAL_NUMBERS.includes(`${area}${exchange}${line}`);
3307
3310
  }
3308
- function phonesOutsideFictionalRange(text, phoneField = false) {
3309
- const t = normalizeForPhones(text);
3311
+ function phonesOutsideFictionalRange(text2, phoneField = false) {
3312
+ const t = normalizeForPhones(text2);
3310
3313
  const out = [];
3311
3314
  for (const m of t.matchAll(INTL)) {
3312
3315
  const digits = m[0].replace(/\D/g, "");
@@ -3360,11 +3363,11 @@ var PUBLIC_MAIL_DOMAINS = [
3360
3363
  "163.com",
3361
3364
  "web.de"
3362
3365
  ];
3363
- function domainsIn(text) {
3366
+ function domainsIn(text2) {
3364
3367
  const found = /* @__PURE__ */ new Set();
3365
- for (const m of text.matchAll(/[a-z0-9._%+-]+@([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase());
3366
- for (const m of text.matchAll(/\bhttps?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase().replace(/^www\./, ""));
3367
- for (const m of text.matchAll(/\bwww\.([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase().replace(/\.$/, ""));
3368
+ for (const m of text2.matchAll(/[a-z0-9._%+-]+@([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase());
3369
+ for (const m of text2.matchAll(/\bhttps?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase().replace(/^www\./, ""));
3370
+ for (const m of text2.matchAll(/\bwww\.([a-z0-9-]+(?:\.[a-z0-9-]+)+)/gi)) found.add((m[1] ?? "").toLowerCase().replace(/\.$/, ""));
3368
3371
  return [...found];
3369
3372
  }
3370
3373
  function numberedStreet(line) {
@@ -3450,7 +3453,7 @@ function peopleOf(spec) {
3450
3453
  function proseOf(spec) {
3451
3454
  return templateText(spec).filter(([at]) => !at.startsWith("tables ("));
3452
3455
  }
3453
- var words8 = (text) => text.toLowerCase().replace(/\{\{[^}]*\}\}/g, " ").split(/[^a-z0-9']+/).filter(Boolean);
3456
+ var words8 = (text2) => text2.toLowerCase().replace(/\{\{[^}]*\}\}/g, " ").split(/[^a-z0-9']+/).filter(Boolean);
3454
3457
  var FOREIGN_INDEX = /* @__PURE__ */ new WeakMap();
3455
3458
  function foreignIndexOf(other) {
3456
3459
  let ix = FOREIGN_INDEX.get(other);
@@ -3470,8 +3473,8 @@ function foreignIndexOf(other) {
3470
3473
  }
3471
3474
  function foreignTextProblems(spec, library) {
3472
3475
  const problems = [];
3473
- const text = templateText(spec);
3474
- const hay = text.map(([at, v]) => [at, v.toLowerCase()]);
3476
+ const text2 = templateText(spec);
3477
+ const hay = text2.map(([at, v]) => [at, v.toLowerCase()]);
3475
3478
  const all = hay.map(([, v]) => v).join("\n");
3476
3479
  const esc = (w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3477
3480
  const find = (word) => {
@@ -3481,9 +3484,9 @@ function foreignTextProblems(spec, library) {
3481
3484
  return hay.find(([, v]) => re.test(v));
3482
3485
  };
3483
3486
  const mine = proseOf(spec).map(([at, w]) => {
3484
- const words = words8(w);
3487
+ const words2 = words8(w);
3485
3488
  const runs = [];
3486
- for (let i = 0; i + 8 <= words.length; i += 1) runs.push(words.slice(i, i + 8).join(" "));
3489
+ for (let i = 0; i + 8 <= words2.length; i += 1) runs.push(words2.slice(i, i + 8).join(" "));
3487
3490
  return [at, runs];
3488
3491
  });
3489
3492
  for (const other of library.specs) {
@@ -3518,8 +3521,8 @@ var SYNONYM_WORDS = {
3518
3521
  function labelMeans(f, sameAs) {
3519
3522
  const label = norm(f.label);
3520
3523
  if (sameAs.includes(label)) return true;
3521
- const words = new Set(label.split(" "));
3522
- return Object.values(SYNONYM_WORDS).some((group) => group.some((w) => words.has(w)) && sameAs.some((s) => group.some((w) => s.split(" ").includes(w))));
3524
+ const words2 = new Set(label.split(" "));
3525
+ return Object.values(SYNONYM_WORDS).some((group) => group.some((w) => words2.has(w)) && sameAs.some((s) => group.some((w) => s.split(" ").includes(w))));
3523
3526
  }
3524
3527
  var sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
3525
3528
  var sortedJson = (v) => JSON.stringify(Object.keys(v).sort().map((k) => [k, v[k]]));
@@ -4111,8 +4114,8 @@ function validateTemplate(raw, options = {}) {
4111
4114
  for (const by of dim.default?.by ?? []) if (!dimKeys.has(by.split(":")[0] ?? "")) say(`${at}.default.by`, `The first screen groups by "${by}", which is not a dimension.`);
4112
4115
  for (const show of dim.default?.show ?? []) if (!inferredMeasures.has(show) && !newMeasures.has(show)) say(`${at}.default.show`, `The first screen shows "${show}", which is not a measure of this table.`);
4113
4116
  });
4114
- const checkMerge = (at, table, text2) => {
4115
- for (const m of text2.matchAll(MERGE_TOKEN)) {
4117
+ const checkMerge = (at, table, text3) => {
4118
+ for (const m of text3.matchAll(MERGE_TOKEN)) {
4116
4119
  const key = m[1] ?? "";
4117
4120
  const f = fieldOf(table, key);
4118
4121
  if (!f) say(at, `{{${key}}} does not resolve: the store merges only the fields of "${table}" (no today, no business details).`);
@@ -5020,7 +5023,7 @@ function validateTemplate(raw, options = {}) {
5020
5023
  const f = fieldOf(table, key);
5021
5024
  if (f?.absoluteDates) say(`tables (${table}).fields (${key})`, `"${f.label}" holds history (absoluteDates), so it cannot drive ${where}: schedule dates are relative to install day.`);
5022
5025
  }
5023
- const text = JSON.stringify(raw);
5026
+ const text2 = JSON.stringify(raw);
5024
5027
  const isPhoneField = (f) => f.parityType === "phone" || /(^|_)(phone|mobile|cell|fax|tel)(_|$)/.test(f.key) || /\b(phone|mobile|cell|fax)\b/.test(norm(f.label));
5025
5028
  const phoneHits = /* @__PURE__ */ new Set();
5026
5029
  for (const p of phonesOutsideFictionalRange(raw.business?.phone ?? "", true)) phoneHits.add(p);
@@ -5038,7 +5041,7 @@ function validateTemplate(raw, options = {}) {
5038
5041
  for (const d of raw.provenance?.fictionalDomains ?? []) {
5039
5042
  if (PUBLIC_MAIL_DOMAINS.includes(d.toLowerCase())) say("provenance.fictionalDomains", `"${d}" is a public mail provider real people use, so it is never fictional. Give each person a mailbox on an invented domain.`);
5040
5043
  }
5041
- for (const domain of /* @__PURE__ */ new Set([...domainsIn(text), ...websites])) {
5044
+ for (const domain of /* @__PURE__ */ new Set([...domainsIn(text2), ...websites])) {
5042
5045
  if (!declaredDomains.has(domain)) say("provenance.fictionalDomains", `"${domain}" is used but not declared fictional. List every invented domain in provenance.fictionalDomains (each is checked not to resolve).`);
5043
5046
  }
5044
5047
  const streets = new Set((raw.provenance?.fictionalStreets ?? []).map((s) => s.toLowerCase()));
@@ -5081,10 +5084,10 @@ function validateTemplate(raw, options = {}) {
5081
5084
  if (seeded) say(`tables (${t.token}).fields (${f.key})`, `"${f.label}" is computed by the store (${f.parityType}), and ${seeded} seed row(s) write a value into it; leave it to the store.`);
5082
5085
  }
5083
5086
  }
5084
- const label = (at, text2, kind) => {
5085
- if (!text2) return;
5086
- const hit = findBannedLabel(text2, kind);
5087
- if (hit) say(at, `"${text2}" is ${hit.why}. Use the practitioner's own word.`);
5087
+ const label = (at, text3, kind) => {
5088
+ if (!text3) return;
5089
+ const hit = findBannedLabel(text3, kind);
5090
+ if (hit) say(at, `"${text3}" is ${hit.why}. Use the practitioner's own word.`);
5088
5091
  };
5089
5092
  for (const t of own) {
5090
5093
  label(`tables (${t.token}).name`, t.name, "table");
@@ -5117,9 +5120,9 @@ function validateTemplate(raw, options = {}) {
5117
5120
  for (const [k, v] of Object.entries(r.values ?? {})) {
5118
5121
  const f = byKey.get(k);
5119
5122
  if (!f || isRelation(f) || f.parityType === "email" || f.parityType === "url" || f.parityType === "phone") continue;
5120
- const text2 = unwrap(v);
5121
- if (typeof text2 === "string" && !RELATIVE_DATE.test(text2) && STALE.test(text2)) {
5122
- say(`tables (${t.token}).rows (${r.key}).${k}`, `"${text2}" says "${STALE.exec(text2)?.[0]}", which is only true on the day it was written. Put the date in a date field written relative to install day ("@today+1b 10:00"), and keep the text free of "today" and "next week".`);
5123
+ const text3 = unwrap(v);
5124
+ if (typeof text3 === "string" && !RELATIVE_DATE.test(text3) && STALE.test(text3)) {
5125
+ say(`tables (${t.token}).rows (${r.key}).${k}`, `"${text3}" says "${STALE.exec(text3)?.[0]}", which is only true on the day it was written. Put the date in a date field written relative to install day ("@today+1b 10:00"), and keep the text free of "today" and "next week".`);
5123
5126
  }
5124
5127
  }
5125
5128
  }
@@ -5127,14 +5130,14 @@ function validateTemplate(raw, options = {}) {
5127
5130
  for (const t of own) {
5128
5131
  for (const f of t.fields) {
5129
5132
  const at = `tables (${t.token}).fields (${f.key})`;
5130
- const text2 = `${f.key} ${norm(f.label)}`;
5133
+ const text3 = `${f.key} ${norm(f.label)}`;
5131
5134
  const e = effective(t.token, f);
5132
5135
  const floor = sensitivityFloorOf(t, f);
5133
5136
  if (floor === "restricted" && !(e.sensitivity === "restricted" && e.contextPolicy === "exclude")) {
5134
5137
  say(at, `"${f.label}" is the kind of value that is restricted and kept from the AI (restricted + exclude).`);
5135
5138
  } else if (floor === "confidential" && SENSITIVITY_RANK.indexOf(e.sensitivity) < 2) {
5136
5139
  say(at, `"${f.label}" reaches a person or a home, so it is at least confidential.`);
5137
- } else if (/note/.test(text2) && e.sensitivity === "public") {
5140
+ } else if (/note/.test(text3) && e.sensitivity === "public") {
5138
5141
  say(at, `"${f.label}" is somebody's notes; notes are never public.`);
5139
5142
  }
5140
5143
  }
@@ -5319,8 +5322,8 @@ function validateTemplate(raw, options = {}) {
5319
5322
  const status2 = fieldOf(e.table, "status");
5320
5323
  if (!status2) say(at, `A booking table needs a choice field keyed \`status\` whose choices include "booked" and "cancelled"; "${e.table}" has none. Add it, so a held time can be confirmed and given back.`);
5321
5324
  else if (isChoice(status2)) {
5322
- const words = (status2.choices ?? []).map((c) => c.trim().toLowerCase());
5323
- const missing = ["booked", "cancelled"].filter((w) => !words.includes(w));
5325
+ const words2 = (status2.choices ?? []).map((c) => c.trim().toLowerCase());
5326
+ const missing = ["booked", "cancelled"].filter((w) => !words2.includes(w));
5324
5327
  if (missing.length) say(`${at}`, `This table's Status is a list of choices, and it has no choice called ${missing.join(" or ")} \u2014 the booking page writes those words (custom.booking_declare refuses).`);
5325
5328
  }
5326
5329
  const columns = bookingColumnsProblem(tables, e);
@@ -5440,9 +5443,9 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5440
5443
  if (agent?.question?.expect) {
5441
5444
  const qq = agent.question;
5442
5445
  const ask = norm(qq.ask);
5443
- const words = (t) => norm(t).split(" ").filter((w) => w.length >= 4);
5446
+ const words2 = (t) => norm(t).split(" ").filter((w) => w.length >= 4);
5444
5447
  const askWords = new Set(ask.split(" "));
5445
- const mentions = (t) => !!norm(t) && ask.includes(norm(t)) || words(t).some((w) => ask.includes(w.replace(/s$/, ""))) || words(t).length === 0 && norm(t).split(" ").filter(Boolean).some((w) => w.length >= 3 && (askWords.has(w) || askWords.has(`${w}s`)));
5448
+ const mentions = (t) => !!norm(t) && ask.includes(norm(t)) || words2(t).some((w) => ask.includes(w.replace(/s$/, ""))) || words2(t).length === 0 && norm(t).split(" ").filter(Boolean).some((w) => w.length >= 3 && (askWords.has(w) || askWords.has(`${w}s`)));
5446
5449
  for (const [k, v] of Object.entries(qq.expect.filter ?? {})) {
5447
5450
  if (typeof v === "string" && !mentions(v) && !ask.includes(norm(v))) say("agent.question.ask", `It filters on ${k} = "${v}", and the question never says so.`);
5448
5451
  }
@@ -5524,9 +5527,9 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5524
5527
  if (!t) return false;
5525
5528
  if (says.includes(norm(t))) return true;
5526
5529
  const long = [...new Set(norm(t).split(" ").filter((w) => w.length >= 4))];
5527
- const words = long.length ? long : [...new Set(norm(t).split(" ").filter((w) => w.length >= 2))];
5530
+ const words2 = long.length ? long : [...new Set(norm(t).split(" ").filter((w) => w.length >= 2))];
5528
5531
  const said = new Set(says.split(" "));
5529
- return words.length > 0 && words.filter((w) => said.has(w) || said.has(`${w}s`)).length >= Math.min(2, words.length);
5532
+ return words2.length > 0 && words2.filter((w) => said.has(w) || said.has(`${w}s`)).length >= Math.min(2, words2.length);
5530
5533
  };
5531
5534
  if (!a) return;
5532
5535
  if (a.kind === "move" || a.kind === "edit" || a.kind === "archive" || a.kind === "restore") {
@@ -5627,9 +5630,9 @@ ${e.body}`.matchAll(MERGE_TOKEN)].map((m) => m[1]));
5627
5630
  const LABEL_MAX = 40;
5628
5631
  const IMPLEMENTATION = /\b(?:the\s+)?(?:ai|a\.i\.|agent|assistant|llm|model)\b[^.]*\b(?:reads?|sees?|never|cannot|can't|won't|doesn't|does not|is kept)\b|\b(?:never|not)\b[^.]*\b(?:ai|agent)\b|\bthe store\b|\bthis field\b|\bthis column\b|\bthe booking page writes\b|\bwrites? (?:a |the )?(?:booked )?time here\b|\bcustom\.[a-z_]+|\{\{|\b[a-z]+_[a-z0-9_]+\b|\b(?:sum|avg|count|min|max)\s*\(|\s=\s/i;
5629
5632
  const STAFF_VOICE = /\bwe\b|\bour system\b/i;
5630
- const screen = (at, text2, slot, opts = {}) => {
5631
- if (!text2?.trim()) return;
5632
- const t = text2.trim();
5633
+ const screen = (at, text3, slot, opts = {}) => {
5634
+ if (!text3?.trim()) return;
5635
+ const t = text3.trim();
5633
5636
  if (slot === "label" && (t.length > LABEL_MAX || t.split(/\s+/).length > 6)) say(at, `"${t}" is a label (${t.length} characters); keep it to a few words \u2014 the name of the thing, no explanation.`);
5634
5637
  if (slot === "secondary" && (t.length > 60 || sentences(t) > 1)) say(at, `"${t}" is ${t.length} characters; help text is one line of at most 60, one sentence.`);
5635
5638
  if (slot === "tooltip" && (t.length > 140 || sentences(t) > 1)) say(at, `"${t.slice(0, 60)}\u2026" is ${t.length} characters; a description is one sentence of at most 140.`);
@@ -6004,14 +6007,14 @@ var holdsPeople = (t) => t.subject ? t.subject === "person" : t.fields.some((f)
6004
6007
  var GOOD_OUTCOME = /^(completed?|done|paid|approved|won|closed won|booked|confirmed|delivered|resolved|published|signed|passed|hired|accepted)$/i;
6005
6008
  var BAD_OUTCOME = /^(cancell?ed|skipped|lost|closed lost|denied|rejected|no[- ]show|failed|overdue|declined|void(ed)?|refunded|churned|expired|blocked)$/i;
6006
6009
  function sensitivityFloorOf(table, f) {
6007
- const text = `${f.key} ${norm(f.label)}`;
6010
+ const text2 = `${f.key} ${norm(f.label)}`;
6008
6011
  const billingTable = /(invoice|bill|statement|receipt|payment|charge|fee|order|account)/i.test(`${table.token} ${table.labelSingular ?? ""}`);
6009
6012
  const criminalCharges = /(^|_)charges?($|_)/.test(f.key) && behaviorOf(f) === "text" && !billingTable;
6010
6013
  const phoneLike = behaviorOf(f) === "text" && /\b(cell|mobile|phone)\b/.test(norm(f.label));
6011
6014
  const personal = PERSONAL_ONLY.test(f.key) || /\b(medical conditions?|medications?|diagnos[ie]s)\b/.test(norm(f.label));
6012
6015
  const floored = (RESTRICTED_KEY.test(f.key) || RESTRICTED_LABEL.test(norm(f.label)) || criminalCharges) && (!personal || holdsPeople(table));
6013
6016
  if (floored) return "restricted";
6014
- if (f.parityType === "phone" || phoneLike || f.parityType === "email" || /address|street/.test(text)) return "confidential";
6017
+ if (f.parityType === "phone" || phoneLike || f.parityType === "email" || /address|street/.test(text2)) return "confidential";
6015
6018
  return null;
6016
6019
  }
6017
6020
 
@@ -6497,6 +6500,7 @@ function describeSpec(template) {
6497
6500
  s.specVersion = 1;
6498
6501
  if (typeof s.version !== "number") s.version = 1;
6499
6502
  if (typeof s.audience !== "string") s.audience = "organization";
6503
+ if (s.audience === "personal") s.audience = "individual";
6500
6504
  if (typeof s.cleanupTag !== "string" && typeof s.id === "string") s.cleanupTag = `describe:${s.id}`;
6501
6505
  for (const k of ["relationships", "sharedBlocks", "views", "forms", "dimensions", "extras"]) if (!Array.isArray(s[k])) s[k] = [];
6502
6506
  if (Array.isArray(s.tables)) s.tables = s.tables.map(describeTable);
@@ -6539,9 +6543,9 @@ function stemWord(w) {
6539
6543
  if (s.length > 4 && (s.endsWith("y") || s.endsWith("e"))) s = s.slice(0, -1);
6540
6544
  return s;
6541
6545
  }
6542
- function meaningWords(text) {
6543
- const words = text.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.toLowerCase()).filter((w) => !FILLER.has(w)).map(stemWord).filter(Boolean);
6544
- return [...new Set(words)].sort();
6546
+ function meaningWords(text2) {
6547
+ const words2 = text2.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.toLowerCase()).filter((w) => !FILLER.has(w)).map(stemWord).filter(Boolean);
6548
+ return [...new Set(words2)].sort();
6545
6549
  }
6546
6550
  var same = (a, b) => a.length > 0 && a.length === b.length && a.every((w, i) => w === b[i]);
6547
6551
  function sameMeaning(a, b) {
@@ -7081,31 +7085,31 @@ function fixFormulaRefs(spec, fixes) {
7081
7085
  if (f.parityType !== "formula" || typeof f.formula !== "string") continue;
7082
7086
  const others = t.fields.filter((x) => x.key !== f.key);
7083
7087
  const labelFor = (name) => others.find((x) => x.label === name)?.label ?? others.find((x) => x.key === name)?.label ?? others.find((x) => same3(x.key, name))?.label ?? others.find((x) => same3(x.label, name))?.label;
7084
- const text = f.formula.replace(/^\s*=\s*/, "");
7088
+ const text2 = f.formula.replace(/^\s*=\s*/, "");
7085
7089
  let out = "";
7086
7090
  let i = 0;
7087
- while (i < text.length) {
7088
- const c = text[i];
7091
+ while (i < text2.length) {
7092
+ const c = text2[i];
7089
7093
  if (c === "{") {
7090
- const j = text.indexOf("}", i);
7094
+ const j = text2.indexOf("}", i);
7091
7095
  if (j < 0) {
7092
- out += text.slice(i);
7096
+ out += text2.slice(i);
7093
7097
  break;
7094
7098
  }
7095
- const name = text.slice(i + 1, j).trim();
7099
+ const name = text2.slice(i + 1, j).trim();
7096
7100
  out += `{${labelFor(name) ?? name}}`;
7097
7101
  i = j + 1;
7098
7102
  } else if (c === "'" || c === '"') {
7099
- const j = text.indexOf(c, i + 1);
7103
+ const j = text2.indexOf(c, i + 1);
7100
7104
  if (j < 0) {
7101
- out += text.slice(i);
7105
+ out += text2.slice(i);
7102
7106
  break;
7103
7107
  }
7104
- out += text.slice(i, j + 1);
7108
+ out += text2.slice(i, j + 1);
7105
7109
  i = j + 1;
7106
7110
  } else if (/[A-Za-z_]/.test(c)) {
7107
- const m = /^[A-Za-z_][A-Za-z_0-9]*/.exec(text.slice(i))[0];
7108
- const after = text.slice(i + m.length).trimStart()[0];
7111
+ const m = /^[A-Za-z_][A-Za-z_0-9]*/.exec(text2.slice(i))[0];
7112
+ const after = text2.slice(i + m.length).trimStart()[0];
7109
7113
  const label = after === "(" ? void 0 : labelFor(m);
7110
7114
  out += label ? `{${label}}` : m;
7111
7115
  i += m.length;
@@ -7837,7 +7841,7 @@ function describeVocabulary() {
7837
7841
  field_shape: FIELD_SHAPE_FACTS,
7838
7842
  formula_shape: FORMULA_SHAPE_FACTS,
7839
7843
  relationship_shape: RELATIONSHIP_SHAPE_FACTS,
7840
- phone_rule: "seed phone numbers are fictional: 555-0100 to 555-0199 in North America (e.g. (813) 555-0142) or a UK Ofcom drama number (e.g. 020 7946 0123); never a real-looking number.",
7844
+ phone_rule: "seed phone numbers are fictional: 555-0100 to 555-0199 (e.g. (813) 555-0142); never a real-looking number, and never a number from a country the person did not name.",
7841
7845
  team_rule: "the team (name, role, work email, mobile, start date) is the shared team_member block; do not declare a table with those fields \u2014 keep a table of your own to just name and role, or point a relation field at team_member.",
7842
7846
  title_field: "a table's titleField is the key of a plain text-like field of that same table (a name, a title) \u2014 never a link to another table, a formula or a timed date"
7843
7847
  };
@@ -7900,4 +7904,59 @@ function safeReuses(spec, reuses, existingTables) {
7900
7904
  const out = { ...spec, tables: spec.tables.map((t) => rename.has(t.token) ? { ...t, ...rename.get(t.token) } : t) };
7901
7905
  return { spec: out, reuses: kept, notes, converted };
7902
7906
  }
7907
+
7908
+ // src/templates/describe/result-schema.ts
7909
+ var DESCRIBE_RESULT_KIND = "describe_template_result";
7910
+ var text = { type: "string" };
7911
+ var words = (list) => ({ type: "string", enum: [...list] });
7912
+ var jsonText = { type: "string", description: "one JSON object, as text" };
7913
+ var jsonTextList = { type: "array", items: jsonText };
7914
+ var DESCRIBE_TEXT_PARTS = ["tables", "relationships", "views", "forms", "extras"];
7915
+ function describeResultJsonSchema() {
7916
+ return {
7917
+ type: "object",
7918
+ required: ["__kind", "template", "notes", "reuses"],
7919
+ properties: {
7920
+ __kind: { type: "string", enum: [DESCRIBE_RESULT_KIND] },
7921
+ template: {
7922
+ type: "object",
7923
+ required: ["id", "useCase", "industry", "vertical", "job", "teaches", "business", "tables", "forms"],
7924
+ properties: {
7925
+ id: text,
7926
+ useCase: text,
7927
+ industry: words(INDUSTRY_GROUPS),
7928
+ vertical: text,
7929
+ job: words(TEMPLATE_JOBS),
7930
+ audience: words(TEMPLATE_AUDIENCES),
7931
+ teaches: words(TEACHES_FEATURES),
7932
+ business: jsonText,
7933
+ tables: jsonTextList,
7934
+ relationships: jsonTextList,
7935
+ views: jsonTextList,
7936
+ forms: jsonTextList,
7937
+ extras: jsonTextList
7938
+ }
7939
+ },
7940
+ notes: { type: "array", items: text },
7941
+ reuses: { type: "array", items: { type: "object", required: ["token", "existing_table_id"], properties: { token: text, existing_table_id: text } } }
7942
+ }
7943
+ };
7944
+ }
7945
+ function open(value, at) {
7946
+ if (typeof value !== "string") return value;
7947
+ try {
7948
+ return JSON.parse(value);
7949
+ } catch {
7950
+ throw new Error(`${at} was not valid JSON text.`);
7951
+ }
7952
+ }
7953
+ function parseDescribeTemplate(template) {
7954
+ const out = { ...template };
7955
+ if ("business" in out) out.business = open(out.business, "business");
7956
+ for (const k of DESCRIBE_TEXT_PARTS) {
7957
+ const v = out[k];
7958
+ if (Array.isArray(v)) out[k] = v.map((x, i) => open(x, `${k}[${i}]`));
7959
+ }
7960
+ return out;
7961
+ }
7903
7962
  //# sourceMappingURL=index.cjs.map