datanotes 3.1.0 → 3.2.0

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 (41) hide show
  1. package/dist/api/index.cjs +175 -8
  2. package/dist/api/index.js +175 -8
  3. package/dist/api/mcp.cjs +175 -8
  4. package/dist/api/mcp.js +175 -8
  5. package/dist/datanotes.cjs +591 -139
  6. package/dist/docs/api/operations/add_column.md +1 -0
  7. package/dist/docs/api/operations/add_constraint.md +2 -0
  8. package/dist/docs/api/operations/alter_column.md +1 -1
  9. package/dist/docs/api/operations/alter_table.md +3 -2
  10. package/dist/docs/api/operations/check_table.md +1 -1
  11. package/dist/docs/api/operations/create_table.md +3 -1
  12. package/dist/docs/api/operations/describe_table.md +1 -1
  13. package/dist/docs/api/operations/insert_row.md +2 -2
  14. package/dist/docs/api/operations/tags/rows-dml.md +1 -1
  15. package/dist/docs/api.md +2 -2
  16. package/dist/docs/guides/data-quality/check-constraints.md +14 -13
  17. package/dist/docs/guides/data-quality/hand-edits.md +10 -10
  18. package/dist/docs/guides/database-objects/bases.md +11 -11
  19. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +16 -10
  20. package/dist/docs/guides/database-objects/columns.md +38 -38
  21. package/dist/docs/guides/database-objects/relations.md +173 -36
  22. package/dist/docs/guides/database-objects/tables.md +80 -35
  23. package/dist/docs/guides/database-objects/views.md +44 -44
  24. package/dist/docs/guides/history/change-data-feed.md +34 -34
  25. package/dist/docs/guides/history/journal-and-undo.md +12 -12
  26. package/dist/docs/guides/history/restore.md +20 -20
  27. package/dist/docs/guides/history/table-history.md +31 -31
  28. package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
  29. package/dist/docs/guides/read-and-write/import-notes.md +21 -21
  30. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
  31. package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
  32. package/dist/docs/guides/read-and-write/write-rows.md +10 -10
  33. package/dist/docs/guides/search/text.md +1 -1
  34. package/dist/docs/reference/cli.md +1 -1
  35. package/dist/docs/reference/errors.md +1 -1
  36. package/dist/docs/reference/operations/auxiliary.md +2 -2
  37. package/dist/docs/reference/operations/rows.md +1 -1
  38. package/dist/docs/reference/operations/schema.md +15 -11
  39. package/dist/docs/reference/operations.md +28 -28
  40. package/dist/docs/release-notes.md +24 -0
  41. package/package.json +1 -1
@@ -370,7 +370,7 @@ var init_catalog = __esm({
370
370
  },
371
371
  {
372
372
  "name": "describe_table",
373
- "description": "Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualified`, `name`,\n`catalog`, `schema`, `description`, `aliases`, `layout` (`folder`, `row_folder`: the folder pattern\nof new rows, `archive`), `options` (`partition`, `filename`, `archive`, `exclude`), typed `columns`\nwith label/aliases/description and the dense flag, `untyped_fields`, `row_count`, and when set:\n`tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name \u2192 where), `row_template`, `history`,\n`autolink_target`, `fields` (their values).\n\nBefore adding a column (e.g. a new metric), read `columns` and match your concept against\n`name`, `label`, `aliases` and `description`; reuse an existing column when it means the same.\n\n`missing_descriptions` (`{table, columns}`), present only when something has no description (a table\ndefined before descriptions were required), names what lacks one: write them with alter_table\n`description` and alter_column `changes.description`, asking the user when the meaning is unclear.",
373
+ "description": "Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualified`, `name`,\n`catalog`, `schema`, `description`, `aliases`, `layout` (`folder`, `row_folder`: the folder pattern\nof new rows, `archive`), `options` (`partition`, `filename`, `archive`, `exclude`), typed `columns`\nwith label/aliases/description and the dense flag, `untyped_fields`, `row_count`, and when set:\n`tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name \u2192 where), `constraint_hints` (their own hints, name \u2192 hint), `instructions` (how to fill a row: follow them before you write one), `row_template`, `history`,\n`autolink_target`, `fields` (their values).\n\nBefore adding a column (e.g. a new metric), read `columns` and match your concept against\n`name`, `label`, `aliases` and `description`; reuse an existing column when it means the same.\n\n`missing_descriptions` (`{table, columns}`), present only when something has no description (a table\ndefined before descriptions were required), names what lacks one: write them with alter_table\n`description` and alter_column `changes.description`, asking the user when the meaning is unclear.",
374
374
  "inputSchema": {
375
375
  "additionalProperties": false,
376
376
  "properties": {
@@ -493,6 +493,10 @@ var init_catalog = __esm({
493
493
  "inverse": {
494
494
  "type": "string"
495
495
  },
496
+ "acyclic": {
497
+ "type": "boolean",
498
+ "description": "Link column to its own table whose links never form a cycle (a write that closes one is refused)."
499
+ },
496
500
  "ref_where": {
497
501
  "type": "object",
498
502
  "description": "Link column: the where filter every linked row must match.",
@@ -602,6 +606,17 @@ var init_catalog = __esm({
602
606
  },
603
607
  "description": "Its CHECK constraints: name \u2192 the where every row must match."
604
608
  },
609
+ "constraint_hints": {
610
+ "type": "object",
611
+ "additionalProperties": {
612
+ "type": "string"
613
+ },
614
+ "description": "The hint of each CHECK that has one: name \u2192 what to do when a row breaks it."
615
+ },
616
+ "instructions": {
617
+ "type": "string",
618
+ "description": "How to fill a row of the table (alter_table instructions): follow them before you write one."
619
+ },
605
620
  "row_template": {
606
621
  "type": "string",
607
622
  "description": "The text of every new row's note."
@@ -635,7 +650,7 @@ var init_catalog = __esm({
635
650
  },
636
651
  {
637
652
  "name": "create_table",
638
- "description": 'Create a table: `name`, in `catalog` and `schema` (default: the database\'s defaults); the definition\ngoes in metastore.yaml, the rows in `<root>/<catalog>/<schema>/<name>/`. Names: lowercase letters,\ndigits, `_` (no dots: pass the catalog and the schema as their own arguments).\n- `description` (required): what a row of the table records. Every column needs its own `description`\n too, what it holds: the table is refused otherwise, with every missing one listed. Descriptions are how\n agents and people understand the schema, so write them for a reader who never saw the notes. The\n engine writes those of `id` and of `identity` "uuid" / "created" columns.\n- `columns`: specs like `{"name": "kind", "type": "enum", "values": ["a","b"], "required": true,\n "description": "What kind of entry the row is"}`.\n Types: string, integer, number, boolean, date, datetime, enum, link, list (+ `items`), json.\n Attributes: required, default (`"{now}"`/`"{today}"`), min, max, step, values, `ordered` (enum in\n order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),\n unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),\n `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table\'s rows),\n `hierarchy: true` (one parent link to its own table), `dense` (written on every row),\n `inverse` on a link column: the column of the `ref` table that links back (`friends` \u2194\n `friends`, `children` \u2194 `parents`, `partner` \u2194 `partner`); writes keep both sides in step.\n- `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).\n- The table\'s options, as alter_table names them: `aliases` (how agents find it, with the\n description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the\n database\'s setting), `autolink_target` (true / false: may automatic links link its rows? default:\n yes, unless its rows are named by a date or a time, as with `time_column`).\n- `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.\n- `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and\n describe_table return them, alter_table changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
653
+ "description": 'Create a table: `name`, in `catalog` and `schema` (default: the database\'s defaults); the definition\ngoes in metastore.yaml, the rows in `<root>/<catalog>/<schema>/<name>/`. Names: lowercase letters,\ndigits, `_` (no dots: pass the catalog and the schema as their own arguments).\n- `description` (required): what a row of the table records. Every column needs its own `description`\n too, what it holds: the table is refused otherwise, with every missing one listed. Descriptions are how\n agents and people understand the schema, so write them for a reader who never saw the notes. The\n engine writes those of `id` and of `identity` "uuid" / "created" columns.\n- `columns`: specs like `{"name": "kind", "type": "enum", "values": ["a","b"], "required": true,\n "description": "What kind of entry the row is"}`.\n Types: string, integer, number, boolean, date, datetime, enum, link, list (+ `items`), json.\n Attributes: required, default (`"{now}"`/`"{today}"`), min, max, step, values, `ordered` (enum in\n order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),\n unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),\n `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table\'s rows),\n `hierarchy: true` (one parent link to its own table), `acyclic: true` (links to rows of its own table that never loop back, such as tasks blocked by tasks; with `inverse`, the rule holds on both sides), `dense` (written on every row),\n `inverse` on a link column: the column of the `ref` table that links back (`friends` \u2194\n `friends`, `children` \u2194 `parents`, `partner` \u2194 `partner`); writes keep both sides in step.\n- `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).\n- The table\'s options, as alter_table names them: `aliases` (how agents find it, with the\n description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the\n database\'s setting), `autolink_target` (true / false: may automatic links link its rows? default:\n yes, unless its rows are named by a date or a time, as with `time_column`).\n- `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.\n- `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.\n- `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and\n describe_table return them, alter_table changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
639
654
  "inputSchema": {
640
655
  "additionalProperties": false,
641
656
  "properties": {
@@ -796,6 +811,18 @@ var init_catalog = __esm({
796
811
  "default": null,
797
812
  "description": "Markdown body of new rows; {column} placeholders are filled from the row."
798
813
  },
814
+ "instructions": {
815
+ "anyOf": [
816
+ {
817
+ "type": "string"
818
+ },
819
+ {
820
+ "type": "null"
821
+ }
822
+ ],
823
+ "default": null,
824
+ "description": "How to fill a row, for agents (Markdown): which columns first, what to ask the user. describe_table returns them."
825
+ },
799
826
  "fields": {
800
827
  "anyOf": [
801
828
  {
@@ -933,6 +960,10 @@ var init_catalog = __esm({
933
960
  "type": "string",
934
961
  "description": "The CHECK's name (code check)."
935
962
  },
963
+ "hint": {
964
+ "type": "string",
965
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
966
+ },
936
967
  "code": {
937
968
  "type": "string",
938
969
  "enum": [
@@ -1342,6 +1373,10 @@ var init_catalog = __esm({
1342
1373
  "type": "string",
1343
1374
  "description": "The CHECK's name (code check)."
1344
1375
  },
1376
+ "hint": {
1377
+ "type": "string",
1378
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
1379
+ },
1345
1380
  "code": {
1346
1381
  "type": "string",
1347
1382
  "enum": [
@@ -1621,7 +1656,7 @@ var init_catalog = __esm({
1621
1656
  },
1622
1657
  {
1623
1658
  "name": "add_column",
1624
- "description": 'Add a typed column. Sparse by default (written only on rows that have a value \u2014 use for\nmetrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label":\n"Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`).\n`description` is required: what the column holds, for the agents and people who read the schema\n(the engine writes it for `identity` "uuid" / "created" columns).\n`dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add\na column whose meaning an existing column (name/label/aliases/description) already covers.\nColumn names are permanent identifiers; only `rename_column` changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
1659
+ "description": 'Add a typed column. Sparse by default (written only on rows that have a value \u2014 use for\nmetrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label":\n"Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`).\n`description` is required: what the column holds, for the agents and people who read the schema\n(the engine writes it for `identity` "uuid" / "created" columns).\n`dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add\na column whose meaning an existing column (name/label/aliases/description) already covers.\nColumn names are permanent identifiers; only `rename_column` changes them.\nEnum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", \u2026}`): describe_table shows them and a refused value lists them.\nGenerated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).\nFiltered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.\nAcyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
1625
1660
  "inputSchema": {
1626
1661
  "additionalProperties": false,
1627
1662
  "properties": {
@@ -1742,6 +1777,10 @@ var init_catalog = __esm({
1742
1777
  "type": "string",
1743
1778
  "description": "The CHECK's name (code check)."
1744
1779
  },
1780
+ "hint": {
1781
+ "type": "string",
1782
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
1783
+ },
1745
1784
  "code": {
1746
1785
  "type": "string",
1747
1786
  "enum": [
@@ -2144,6 +2183,10 @@ var init_catalog = __esm({
2144
2183
  "type": "string",
2145
2184
  "description": "The CHECK's name (code check)."
2146
2185
  },
2186
+ "hint": {
2187
+ "type": "string",
2188
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
2189
+ },
2147
2190
  "code": {
2148
2191
  "type": "string",
2149
2192
  "enum": [
@@ -2543,6 +2586,10 @@ var init_catalog = __esm({
2543
2586
  "type": "string",
2544
2587
  "description": "The CHECK's name (code check)."
2545
2588
  },
2589
+ "hint": {
2590
+ "type": "string",
2591
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
2592
+ },
2546
2593
  "code": {
2547
2594
  "type": "string",
2548
2595
  "enum": [
@@ -2822,7 +2869,7 @@ var init_catalog = __esm({
2822
2869
  },
2823
2870
  {
2824
2871
  "name": "alter_column",
2825
- "description": 'Change a column\'s attributes (type, min/max/step, values, required, default, label,\naliases, description, note, `ref_where`, `ref_by` (see list_link_targets); `null` removes an attribute, except\n`description`, which changes but never goes (null or "" is refused); `dense` true/false). Refused with\nthe list of violating rows when existing values do not satisfy the new definition. A\ndifferent scale means a different column: add a new one instead of rescaling. To rename one\nenum value use `rename_value`. Dropping an enum value that a `ref_by` (of any table) allows as a\ntarget level is refused, naming it.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
2872
+ "description": 'Change a column\'s attributes (type, min/max/step, values, required, default, label,\naliases, description, note, `ref_where`, `ref_by` (see list_link_targets), `acyclic` (refused while the rows already form a cycle: they are listed); `null` removes an attribute, except\n`description`, which changes but never goes (null or "" is refused); `dense` true/false). Refused with\nthe list of violating rows when existing values do not satisfy the new definition. A\ndifferent scale means a different column: add a new one instead of rescaling. To rename one\nenum value use `rename_value`. Dropping an enum value that a `ref_by` (of any table) allows as a\ntarget level is refused, naming it.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
2826
2873
  "inputSchema": {
2827
2874
  "additionalProperties": false,
2828
2875
  "properties": {
@@ -2947,6 +2994,10 @@ var init_catalog = __esm({
2947
2994
  "type": "string",
2948
2995
  "description": "The CHECK's name (code check)."
2949
2996
  },
2997
+ "hint": {
2998
+ "type": "string",
2999
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
3000
+ },
2950
3001
  "code": {
2951
3002
  "type": "string",
2952
3003
  "enum": [
@@ -3226,7 +3277,7 @@ var init_catalog = __esm({
3226
3277
  },
3227
3278
  {
3228
3279
  "name": "alter_table",
3229
- "description": 'Alter a table: move it (`catalog`, `schema`), rename it (`name`), change\n`partition` / `filename` patterns, `description` (changes, never goes: "" is refused), `aliases`,\n`exclude` folders, `row_template`\n("" removes it), `history` (true | false | "default"), `autolink_target`\n(true | false | "default": may automatic links link its rows? default: yes, unless its rows are named by\na date or a time) or `tags` (key/value\nlabels merged into the table\'s, e.g. `{"domain": "health"}`; a `null` value removes that tag;\n`clear_tags: true` removes every tag first, so with `tags` the table keeps exactly those),\n`archive` (folder name inside the table folder) or `fields` (untyped fields, name \u2192 default; a\n`null` value removes one). Rows, archive and transaction log follow; links are updated; renamed\nrows keep their old name in `aliases`. A change of metadata only (tags, description, aliases,\nhistory, autolink_target, row_template, fields) rewrites the table entry alone: no row is moved or rewritten. Refused\nwhen a row cannot fill a pattern. Renaming or moving the table is refused while a CHECK that follows\nlinks, or a `ref_where` / `ref_by`, of another table reads it through a link (link targets are not\nrewritten): the conflicts name them. Use `dry_run` to see `planned_moves`; undo with undo_migration.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
3280
+ "description": 'Alter a table: move it (`catalog`, `schema`), rename it (`name`), change\n`partition` / `filename` patterns, `description` (changes, never goes: "" is refused), `aliases`,\n`exclude` folders, `row_template`\n("" removes it), `instructions` (how to fill a row, for agents; "" removes them), `history` (true | false | "default"), `autolink_target`\n(true | false | "default": may automatic links link its rows? default: yes, unless its rows are named by\na date or a time) or `tags` (key/value\nlabels merged into the table\'s, e.g. `{"domain": "health"}`; a `null` value removes that tag;\n`clear_tags: true` removes every tag first, so with `tags` the table keeps exactly those),\n`archive` (folder name inside the table folder) or `fields` (untyped fields, name \u2192 default; a\n`null` value removes one). Rows, archive and transaction log follow; links are updated; renamed\nrows keep their old name in `aliases`. A change of metadata only (tags, description, aliases,\nhistory, autolink_target, row_template, instructions, fields) rewrites the table entry alone: no row is moved or rewritten. Refused\nwhen a row cannot fill a pattern. Renaming or moving the table is refused while a CHECK that follows\nlinks, or a `ref_where` / `ref_by`, of another table reads it through a link (link targets are not\nrewritten): the conflicts name them. Use `dry_run` to see `planned_moves`; undo with undo_migration.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
3230
3281
  "inputSchema": {
3231
3282
  "additionalProperties": false,
3232
3283
  "properties": {
@@ -3376,6 +3427,18 @@ var init_catalog = __esm({
3376
3427
  "default": null,
3377
3428
  "description": 'Markdown body of new rows; "" removes it.'
3378
3429
  },
3430
+ "instructions": {
3431
+ "anyOf": [
3432
+ {
3433
+ "type": "string"
3434
+ },
3435
+ {
3436
+ "type": "null"
3437
+ }
3438
+ ],
3439
+ "default": null,
3440
+ "description": 'How to fill a row, for agents (Markdown); "" removes them.'
3441
+ },
3379
3442
  "tags": {
3380
3443
  "anyOf": [
3381
3444
  {
@@ -3536,6 +3599,10 @@ var init_catalog = __esm({
3536
3599
  "type": "string",
3537
3600
  "description": "The CHECK's name (code check)."
3538
3601
  },
3602
+ "hint": {
3603
+ "type": "string",
3604
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
3605
+ },
3539
3606
  "code": {
3540
3607
  "type": "string",
3541
3608
  "enum": [
@@ -3992,6 +4059,10 @@ var init_catalog = __esm({
3992
4059
  "type": "string",
3993
4060
  "description": "The CHECK's name (code check)."
3994
4061
  },
4062
+ "hint": {
4063
+ "type": "string",
4064
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
4065
+ },
3995
4066
  "code": {
3996
4067
  "type": "string",
3997
4068
  "enum": [
@@ -4393,6 +4464,10 @@ var init_catalog = __esm({
4393
4464
  "type": "string",
4394
4465
  "description": "The CHECK's name (code check)."
4395
4466
  },
4467
+ "hint": {
4468
+ "type": "string",
4469
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
4470
+ },
4396
4471
  "code": {
4397
4472
  "type": "string",
4398
4473
  "enum": [
@@ -5455,6 +5530,10 @@ var init_catalog = __esm({
5455
5530
  "type": "string",
5456
5531
  "description": "The CHECK's name (code check)."
5457
5532
  },
5533
+ "hint": {
5534
+ "type": "string",
5535
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
5536
+ },
5458
5537
  "code": {
5459
5538
  "type": "string",
5460
5539
  "enum": [
@@ -5861,6 +5940,10 @@ var init_catalog = __esm({
5861
5940
  "type": "string",
5862
5941
  "description": "The CHECK's name (code check)."
5863
5942
  },
5943
+ "hint": {
5944
+ "type": "string",
5945
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
5946
+ },
5864
5947
  "code": {
5865
5948
  "type": "string",
5866
5949
  "enum": [
@@ -6140,7 +6223,7 @@ var init_catalog = __esm({
6140
6223
  },
6141
6224
  {
6142
6225
  "name": "insert_row",
6143
- "description": "Insert a row (new note). Values are validated against the columns; the note path comes from\nthe table's `path`/`filename` patterns (e.g. an event logged today about last night lands in\nlast night's day folder when you set `occurred_at` to last night). `body` is the note text.\nGenerated / identity columns are computed (do not pass them); CHECK constraints are enforced.\nReturns `migration_id` (undo) and the created path in `planned_creates`.\n`body` links the notes it mentions by name, at their first mention (automatic links; off with\n`autolink: false` or the database's `autolink` setting). `links_added` lists the links made;\n`ambiguous_mentions` lists the mentions left as text with the notes they may name: read those,\nthen link the right one with update_row and `autolink: false`, or leave the text.",
6226
+ "description": "Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from\nthe table's `path`/`filename` patterns (e.g. an event logged today about last night lands in\nlast night's day folder when you set `occurred_at` to last night). `body` is the note text.\nGenerated / identity columns are computed (do not pass them); CHECK constraints are enforced.\nReturns `migration_id` (undo) and the created path in `planned_creates`.\n`body` links the notes it mentions by name, at their first mention (automatic links; off with\n`autolink: false` or the database's `autolink` setting). `links_added` lists the links made;\n`ambiguous_mentions` lists the mentions left as text with the notes they may name: read those,\nthen link the right one with update_row and `autolink: false`, or leave the text.",
6144
6227
  "inputSchema": {
6145
6228
  "additionalProperties": false,
6146
6229
  "properties": {
@@ -6295,6 +6378,10 @@ var init_catalog = __esm({
6295
6378
  "type": "string",
6296
6379
  "description": "The CHECK's name (code check)."
6297
6380
  },
6381
+ "hint": {
6382
+ "type": "string",
6383
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
6384
+ },
6298
6385
  "code": {
6299
6386
  "type": "string",
6300
6387
  "enum": [
@@ -6733,6 +6820,10 @@ var init_catalog = __esm({
6733
6820
  "type": "string",
6734
6821
  "description": "The CHECK's name (code check)."
6735
6822
  },
6823
+ "hint": {
6824
+ "type": "string",
6825
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
6826
+ },
6736
6827
  "code": {
6737
6828
  "type": "string",
6738
6829
  "enum": [
@@ -7246,6 +7337,10 @@ var init_catalog = __esm({
7246
7337
  "type": "string",
7247
7338
  "description": "The CHECK's name (code check)."
7248
7339
  },
7340
+ "hint": {
7341
+ "type": "string",
7342
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
7343
+ },
7249
7344
  "code": {
7250
7345
  "type": "string",
7251
7346
  "enum": [
@@ -7677,6 +7772,10 @@ var init_catalog = __esm({
7677
7772
  "type": "string",
7678
7773
  "description": "The CHECK's name (code check)."
7679
7774
  },
7775
+ "hint": {
7776
+ "type": "string",
7777
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
7778
+ },
7680
7779
  "code": {
7681
7780
  "type": "string",
7682
7781
  "enum": [
@@ -8125,6 +8224,10 @@ var init_catalog = __esm({
8125
8224
  "type": "string",
8126
8225
  "description": "The CHECK's name (code check)."
8127
8226
  },
8227
+ "hint": {
8228
+ "type": "string",
8229
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
8230
+ },
8128
8231
  "code": {
8129
8232
  "type": "string",
8130
8233
  "enum": [
@@ -8585,6 +8688,10 @@ var init_catalog = __esm({
8585
8688
  "type": "string",
8586
8689
  "description": "The CHECK's name (code check)."
8587
8690
  },
8691
+ "hint": {
8692
+ "type": "string",
8693
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
8694
+ },
8588
8695
  "code": {
8589
8696
  "type": "string",
8590
8697
  "enum": [
@@ -9093,7 +9200,7 @@ var init_catalog = __esm({
9093
9200
  },
9094
9201
  {
9095
9202
  "name": "check_table",
9096
- "description": "Validate every row against the table (required, types, ranges, enums, links, unknown\ncolumns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). A CHECK violation has code `check` and the CHECK's name as `constraint` (every other code names its `column`), as in the `violations` of list_changes. Use it to find rows edited by hand that break the schema. Without `table`, every table is checked: each row then names its `table`, `tables_checked` counts the tables checked, and a table that cannot be checked is listed in `errors` ({table, error}) while the others are. The rows with violations are paged: `limit` (default 500, at most 5000) from `offset`; `total` counts them all. (Hand edits are also checked as they happen: see `violations` in list_changes.)",
9203
+ "description": "Validate every row against the table (required, types, ranges, enums, links, unknown\ncolumns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, cycles of hierarchy and acyclic columns, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). A CHECK violation has code `check` and the CHECK's name as `constraint` (every other code names its `column`), as in the `violations` of list_changes. Use it to find rows edited by hand that break the schema. Without `table`, every table is checked: each row then names its `table`, `tables_checked` counts the tables checked, and a table that cannot be checked is listed in `errors` ({table, error}) while the others are. The rows with violations are paged: `limit` (default 500, at most 5000) from `offset`; `total` counts them all. (Hand edits are also checked as they happen: see `violations` in list_changes.)",
9097
9204
  "inputSchema": {
9098
9205
  "additionalProperties": false,
9099
9206
  "properties": {
@@ -9206,6 +9313,10 @@ var init_catalog = __esm({
9206
9313
  "type": "string",
9207
9314
  "description": "The CHECK's name (code check)."
9208
9315
  },
9316
+ "hint": {
9317
+ "type": "string",
9318
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
9319
+ },
9209
9320
  "code": {
9210
9321
  "type": "string",
9211
9322
  "enum": [
@@ -9659,6 +9770,10 @@ var init_catalog = __esm({
9659
9770
  "type": "string",
9660
9771
  "description": "The CHECK's name (code check)."
9661
9772
  },
9773
+ "hint": {
9774
+ "type": "string",
9775
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
9776
+ },
9662
9777
  "code": {
9663
9778
  "type": "string",
9664
9779
  "enum": [
@@ -10102,6 +10217,10 @@ var init_catalog = __esm({
10102
10217
  "type": "string",
10103
10218
  "description": "The CHECK's name (code check)."
10104
10219
  },
10220
+ "hint": {
10221
+ "type": "string",
10222
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
10223
+ },
10105
10224
  "code": {
10106
10225
  "type": "string",
10107
10226
  "enum": [
@@ -11185,6 +11304,10 @@ var init_catalog = __esm({
11185
11304
  "type": "string",
11186
11305
  "description": "The CHECK's name (code check)."
11187
11306
  },
11307
+ "hint": {
11308
+ "type": "string",
11309
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
11310
+ },
11188
11311
  "code": {
11189
11312
  "type": "string",
11190
11313
  "enum": [
@@ -12622,6 +12745,10 @@ var init_catalog = __esm({
12622
12745
  "type": "string",
12623
12746
  "description": "The CHECK's name (code check)."
12624
12747
  },
12748
+ "hint": {
12749
+ "type": "string",
12750
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
12751
+ },
12625
12752
  "code": {
12626
12753
  "type": "string",
12627
12754
  "enum": [
@@ -13032,6 +13159,10 @@ var init_catalog = __esm({
13032
13159
  "type": "string",
13033
13160
  "description": "The CHECK's name (code check)."
13034
13161
  },
13162
+ "hint": {
13163
+ "type": "string",
13164
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
13165
+ },
13035
13166
  "code": {
13036
13167
  "type": "string",
13037
13168
  "enum": [
@@ -13498,7 +13629,7 @@ var init_catalog = __esm({
13498
13629
  },
13499
13630
  {
13500
13631
  "name": "add_constraint",
13501
- "description": 'ALTER TABLE ADD CONSTRAINT `name` CHECK: `check` is a where filter every row must match,\ne.g. `{"value": {"gte": 1, "lte": 5}}`. A condition on a column the row leaves empty passes, like SQL;\ninside `or` / `and` / `not` an empty value is tested as it is (`{"exists": true}` there fails).\nA key may follow a link column to the row it names, as in queries: `{"parent.kind": "project"}`\n(further dots follow further links). An empty link passes at the top level and fails inside `or` / `and` /\n`not`; only the first link of a longer path is skipped when empty (`parent.parent.kind` fails for a row\nwhose parent has no parent). A link to a row that does not exist fails; on a list of links every linked\nrow must match. `not` turns that into a pass: `{"not": {"parent.kind": "area"}}` passes a link to a\nmissing row, so add `"parent.kind": {"exists": true}` to require a real one. Links resolve as in the\nvault (by path, from the row\'s note: same-named rows in two folders stay apart); renaming or moving a\nlinked row keeps them (they follow it).\nRefused while existing rows violate it (they are listed); then enforced on every write, by the same rule.\nA write that changes a linked row re-checks the rows that link to it and is refused, listing them, when\nit breaks them, naming the changed row (in a batch, re-link them first: each step is checked on the vault\nthe earlier ones left); the inverse links a write adds, undo_migration and restore_table are checked too.\nrename_column / rename_value follow into CHECKs; drop_column of a column a CHECK reads is refused.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
13632
+ "description": 'ALTER TABLE ADD CONSTRAINT `name` CHECK: `check` is a where filter every row must match,\ne.g. `{"value": {"gte": 1, "lte": 5}}`. A condition on a column the row leaves empty passes, like SQL;\ninside `or` / `and` / `not` an empty value is tested as it is (`{"exists": true}` there fails).\nA key may follow a link column to the row it names, as in queries: `{"parent.kind": "project"}`\n(further dots follow further links). An empty link passes at the top level and fails inside `or` / `and` /\n`not`; only the first link of a longer path is skipped when empty (`parent.parent.kind` fails for a row\nwhose parent has no parent). A link to a row that does not exist fails; on a list of links every linked\nrow must match. `not` turns that into a pass: `{"not": {"parent.kind": "area"}}` passes a link to a\nmissing row, so add `"parent.kind": {"exists": true}` to require a real one. Links resolve as in the\nvault (by path, from the row\'s note: same-named rows in two folders stay apart); renaming or moving a\nlinked row keeps them (they follow it).\nRefused while existing rows violate it (they are listed); then enforced on every write, by the same rule.\nA write that changes a linked row re-checks the rows that link to it and is refused, listing them, when\nit breaks them, naming the changed row (in a batch, re-link them first: each step is checked on the vault\nthe earlier ones left); the inverse links a write adds, undo_migration and restore_table are checked too.\nrename_column / rename_value follow into CHECKs; drop_column of a column a CHECK reads is refused.\n`hint`: what to do when a row breaks the rule, in your words; a refused write puts it first in its hint, and each violation of the CHECK carries it.\n\nThe schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).',
13502
13633
  "inputSchema": {
13503
13634
  "additionalProperties": false,
13504
13635
  "properties": {
@@ -13512,6 +13643,18 @@ var init_catalog = __esm({
13512
13643
  "additionalProperties": true,
13513
13644
  "type": "object"
13514
13645
  },
13646
+ "hint": {
13647
+ "anyOf": [
13648
+ {
13649
+ "type": "string"
13650
+ },
13651
+ {
13652
+ "type": "null"
13653
+ }
13654
+ ],
13655
+ "default": null,
13656
+ "description": "What a refused write tells the agent when a row breaks this CHECK: how to fix the row. It comes first in the refusal's hint; describe_table lists it in constraint_hints."
13657
+ },
13515
13658
  "dry_run": {
13516
13659
  "default": false,
13517
13660
  "type": "boolean"
@@ -13622,6 +13765,10 @@ var init_catalog = __esm({
13622
13765
  "type": "string",
13623
13766
  "description": "The CHECK's name (code check)."
13624
13767
  },
13768
+ "hint": {
13769
+ "type": "string",
13770
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
13771
+ },
13625
13772
  "code": {
13626
13773
  "type": "string",
13627
13774
  "enum": [
@@ -14020,6 +14167,10 @@ var init_catalog = __esm({
14020
14167
  "type": "string",
14021
14168
  "description": "The CHECK's name (code check)."
14022
14169
  },
14170
+ "hint": {
14171
+ "type": "string",
14172
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
14173
+ },
14023
14174
  "code": {
14024
14175
  "type": "string",
14025
14176
  "enum": [
@@ -14485,6 +14636,10 @@ var init_catalog = __esm({
14485
14636
  "type": "string",
14486
14637
  "description": "The CHECK's name (code check)."
14487
14638
  },
14639
+ "hint": {
14640
+ "type": "string",
14641
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
14642
+ },
14488
14643
  "code": {
14489
14644
  "type": "string",
14490
14645
  "enum": [
@@ -14879,6 +15034,10 @@ var init_catalog = __esm({
14879
15034
  "type": "string",
14880
15035
  "description": "The CHECK's name (code check)."
14881
15036
  },
15037
+ "hint": {
15038
+ "type": "string",
15039
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
15040
+ },
14882
15041
  "code": {
14883
15042
  "type": "string",
14884
15043
  "enum": [
@@ -15304,6 +15463,10 @@ var init_catalog = __esm({
15304
15463
  "type": "string",
15305
15464
  "description": "The CHECK's name (code check)."
15306
15465
  },
15466
+ "hint": {
15467
+ "type": "string",
15468
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
15469
+ },
15307
15470
  "code": {
15308
15471
  "type": "string",
15309
15472
  "enum": [
@@ -15716,6 +15879,10 @@ var init_catalog = __esm({
15716
15879
  "type": "string",
15717
15880
  "description": "The CHECK's name (code check)."
15718
15881
  },
15882
+ "hint": {
15883
+ "type": "string",
15884
+ "description": "The CHECK's own hint (code check), when the constraint has one: how to fix the row."
15885
+ },
15719
15886
  "code": {
15720
15887
  "type": "string",
15721
15888
  "enum": [
@@ -19023,13 +19190,13 @@ var require_stringifyCollection = __commonJS({
19023
19190
  const stringify3 = flow ? stringifyFlowCollection : stringifyBlockCollection;
19024
19191
  return stringify3(collection, ctx, options);
19025
19192
  }
19026
- function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
19193
+ function stringifyBlockCollection({ comment, items: items2 }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
19027
19194
  const { indent, options: { commentString } } = ctx;
19028
19195
  const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null });
19029
19196
  let chompKeep = false;
19030
19197
  const lines = [];
19031
- for (let i = 0; i < items.length; ++i) {
19032
- const item = items[i];
19198
+ for (let i = 0; i < items2.length; ++i) {
19199
+ const item = items2[i];
19033
19200
  let comment2 = null;
19034
19201
  if (identity.isNode(item)) {
19035
19202
  if (!chompKeep && item.spaceBefore)
@@ -19072,7 +19239,7 @@ ${indent}${line}` : "\n";
19072
19239
  onChompKeep();
19073
19240
  return str3;
19074
19241
  }
19075
- function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) {
19242
+ function stringifyFlowCollection({ items: items2 }, ctx, { flowChars, itemIndent }) {
19076
19243
  const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
19077
19244
  itemIndent += indentStep;
19078
19245
  const itemCtx = Object.assign({}, ctx, {
@@ -19083,8 +19250,8 @@ ${indent}${line}` : "\n";
19083
19250
  let reqNewline = false;
19084
19251
  let linesAtValue = 0;
19085
19252
  const lines = [];
19086
- for (let i = 0; i < items.length; ++i) {
19087
- const item = items[i];
19253
+ for (let i = 0; i < items2.length; ++i) {
19254
+ const item = items2[i];
19088
19255
  let comment = null;
19089
19256
  if (identity.isNode(item)) {
19090
19257
  if (item.spaceBefore)
@@ -19115,7 +19282,7 @@ ${indent}${line}` : "\n";
19115
19282
  reqNewline = true;
19116
19283
  let str3 = stringify2.stringify(item, itemCtx, () => comment = null);
19117
19284
  reqNewline || (reqNewline = lines.length > linesAtValue || str3.includes("\n"));
19118
- if (i < items.length - 1) {
19285
+ if (i < items2.length - 1) {
19119
19286
  str3 += ",";
19120
19287
  } else if (ctx.options.trailingComma) {
19121
19288
  if (ctx.options.lineWidth > 0) {
@@ -19172,9 +19339,9 @@ var require_YAMLMap = __commonJS({
19172
19339
  var identity = require_identity();
19173
19340
  var Pair = require_Pair();
19174
19341
  var Scalar = require_Scalar();
19175
- function findPair(items, key2) {
19342
+ function findPair(items2, key2) {
19176
19343
  const k = identity.isScalar(key2) ? key2.value : key2;
19177
- for (const it of items) {
19344
+ for (const it of items2) {
19178
19345
  if (identity.isPair(it)) {
19179
19346
  if (it.key === key2 || it.key === k)
19180
19347
  return it;
@@ -21122,12 +21289,12 @@ var require_util_map_includes = __commonJS({
21122
21289
  "node_modules/yaml/dist/compose/util-map-includes.js"(exports2) {
21123
21290
  "use strict";
21124
21291
  var identity = require_identity();
21125
- function mapIncludes(ctx, items, search) {
21292
+ function mapIncludes(ctx, items2, search) {
21126
21293
  const { uniqueKeys } = ctx.options;
21127
21294
  if (uniqueKeys === false)
21128
21295
  return false;
21129
21296
  const isEqual = typeof uniqueKeys === "function" ? uniqueKeys : (a, b) => a === b || identity.isScalar(a) && identity.isScalar(b) && a.value === b.value;
21130
- return items.some((pair) => isEqual(pair.key, search));
21297
+ return items2.some((pair) => isEqual(pair.key, search));
21131
21298
  }
21132
21299
  exports2.mapIncludes = mapIncludes;
21133
21300
  }
@@ -24835,16 +25002,26 @@ function unknownOperator(bad, operators) {
24835
25002
  function violationGuidance(code) {
24836
25003
  return VIOLATION_GUIDANCE[code];
24837
25004
  }
25005
+ function guidanceOfViolation(v) {
25006
+ return v.code === "cycle" && typeof v.message === "string" && ACYCLIC_CYCLE.test(v.message) ? ACYCLIC_GUIDANCE : violationGuidance(v.code);
25007
+ }
24838
25008
  function undoGuidance(r) {
24839
25009
  if (r.failed.length > 0) return guide("Some files could not be written back: undo_migration again once they are free, or repair them by hand.", "undo");
24840
25010
  if (r.conflicts.some((c) => /already undone/.test(c.message))) return guide("This write is undone already: to redo it, undo the undo (its migration_id is in list_migrations).", "undo");
24841
25011
  return guide("Later changes touched the same notes: undo those first (list_migrations lists them, newest first), or change the rows with update_row.", "undoConflict");
24842
25012
  }
24843
25013
  function refusalGuidance(r) {
24844
- const codes = [...new Set((r.violations ?? []).flatMap((rv) => rv.violations.map((v) => v.code)))];
24845
- if (codes.length > 0) {
24846
- const gs = codes.slice(0, 3).map(violationGuidance);
24847
- return { hint: `${[...new Set(gs.map((g) => g.hint))].join(" ")} Then call ${r.op} again (dry_run: true checks without writing).`, docs: gs[0].docs };
25014
+ const all = (r.violations ?? []).flatMap((rv) => rv.violations);
25015
+ const kinds = /* @__PURE__ */ new Map();
25016
+ for (const v of all) {
25017
+ const g = guidanceOfViolation(v);
25018
+ const key2 = g === ACYCLIC_GUIDANCE ? "cycle:acyclic" : v.code;
25019
+ if (!kinds.has(key2)) kinds.set(key2, g);
25020
+ }
25021
+ if (kinds.size > 0) {
25022
+ const gs = [...kinds.values()].slice(0, 3);
25023
+ const own2 = [...new Set(all.map((v) => v.hint).filter((h) => typeof h === "string" && h.trim() !== ""))].slice(0, 3);
25024
+ return { hint: `${[...own2, ...new Set(gs.map((g) => g.hint))].join(" ")} Then call ${r.op} again (dry_run: true checks without writing).`, docs: gs[0].docs };
24848
25025
  }
24849
25026
  if ((r.failed?.length ?? 0) > 0) {
24850
25027
  if (r.rolled_back === true) return guide(`A file could not be written (another program may hold it): the engine put back what it had written, so nothing changed. Close that program, then call ${r.op} again.`, "failedWrite");
@@ -24856,7 +25033,7 @@ function refusalGuidance(r) {
24856
25033
  if (known) return known[1](r.op);
24857
25034
  return guide(`Resolve what the message names, then call ${r.op} again; dry_run: true checks without writing.`, "errors");
24858
25035
  }
24859
- var DOCS_BASE2, DOCS, GuidedError, fold2, quoted, GENERATED_HINT, OPERATOR_SYNONYMS, VIOLATION_GUIDANCE, CONFLICTS;
25036
+ var DOCS_BASE2, DOCS, GuidedError, fold2, quoted, GENERATED_HINT, OPERATOR_SYNONYMS, VIOLATION_GUIDANCE, ACYCLIC_CYCLE, ACYCLIC_GUIDANCE, CONFLICTS;
24860
25037
  var init_hints = __esm({
24861
25038
  "src/core/hints.ts"() {
24862
25039
  "use strict";
@@ -24887,6 +25064,8 @@ var init_hints = __esm({
24887
25064
  linkFilters: "/guides/read-and-write/read-and-filter.md#filter-rows-by-a-link",
24888
25065
  vocabulary: "/guides/database-objects/relations.md#take-allowed-values-from-another-table",
24889
25066
  tree: "/guides/database-objects/relations.md#build-a-tree-of-rows-with-a-parent-link",
25067
+ acyclic: "/guides/database-objects/relations.md#keep-dependencies-free-of-cycles-with-acyclic",
25068
+ instructions: "/guides/database-objects/tables.md#tell-agents-how-to-fill-a-row-with-instructions",
24890
25069
  checkLinks: "/guides/data-quality/check-constraints.md#write-a-check-rule-that-follows-links",
24891
25070
  linkTargets: "/guides/database-objects/relations.md#limit-link-targets-with-ref_by-and-ref_where",
24892
25071
  inverse: "/guides/database-objects/relations.md#repair-two-way-links-with-align_relations",
@@ -25021,13 +25200,18 @@ var init_hints = __esm({
25021
25200
  unknown_column: guide("Write only the table's columns: describe_table lists them. To keep a new front matter key, add_column first.", "describeTable"),
25022
25201
  check: guide("The row breaks a CHECK constraint of the table (named in the message, with its rule): change the values so the rule holds. describe_table lists the table's constraints.", "check"),
25023
25202
  generated: guide("A generated column is computed by the engine: do not write it; recompute_columns sets it again.", "generated"),
25024
- cycle: guide("A row cannot be its own ancestor: point the hierarchy column to a row that is not below this one.", "tree"),
25203
+ cycle: guide("A row cannot be its own ancestor in a hierarchy column, nor depend on itself through an acyclic column: the message shows the path. Point the column to a row that is not on that path.", "tree"),
25025
25204
  inverse: guide("Both sides of a two-way link must agree: write one side, or run align_relations to repair them.", "inverse"),
25026
25205
  unreadable: guide(
25027
25206
  "The note's front matter is not valid YAML (the message quotes the parser), so every read leaves the row out: open the note and fix the YAML, such as a quote or bracket left open or a line without `key: value`. read_note shows its text.",
25028
25207
  "unreadable"
25029
25208
  )
25030
25209
  };
25210
+ ACYCLIC_CYCLE = /^(would close|is on) a cycle in "/;
25211
+ ACYCLIC_GUIDANCE = guide(
25212
+ "A row cannot depend on itself through an acyclic column: the message shows the cycle. Remove one of its links, or link to a row that is not on it; check_table lists the cycles already there.",
25213
+ "acyclic"
25214
+ );
25031
25215
  CONFLICTS = [
25032
25216
  [/^row changed: expected/, (op) => guide(`The row changed since you read it: read it again (select_rows), then retry ${op} with "expect" set to what you read.`, "expect")],
25033
25217
  [/^"where" matched \d+ rows, expected \d+/, () => guide(`Check the rows with select_rows and the same "where", then narrow it or change expect_count.`, "updateRows")],
@@ -25539,17 +25723,17 @@ function parseColumn(raw, index, reserved = /* @__PURE__ */ new Set()) {
25539
25723
  `"type" must be one of: ${[...SCALAR_COLUMN_TYPES, "list"].join(", ")}`
25540
25724
  );
25541
25725
  }
25542
- let items;
25726
+ let items2;
25543
25727
  if (type === "list") {
25544
25728
  const it = entry.items ?? "string";
25545
25729
  if (!isScalarType(it)) {
25546
25730
  throw new MalformedColumnError(index, `"items" must be a scalar column type`);
25547
25731
  }
25548
- items = it;
25732
+ items2 = it;
25549
25733
  } else if (entry.items !== void 0 && entry.items !== null) {
25550
25734
  throw new MalformedColumnError(index, `"items" is only valid for list columns`);
25551
25735
  }
25552
- const effective = items ?? type;
25736
+ const effective = items2 ?? type;
25553
25737
  const required2 = entry.required ?? false;
25554
25738
  if (typeof required2 !== "boolean") {
25555
25739
  throw new MalformedColumnError(index, `"required" must be true or false`);
@@ -25596,7 +25780,7 @@ function parseColumn(raw, index, reserved = /* @__PURE__ */ new Set()) {
25596
25780
  const spec = {
25597
25781
  name,
25598
25782
  type,
25599
- ...items !== void 0 ? { items } : {},
25783
+ ...items2 !== void 0 ? { items: items2 } : {},
25600
25784
  required: required2,
25601
25785
  ...min !== void 0 ? { min } : {},
25602
25786
  ...max !== void 0 ? { max } : {},
@@ -25651,6 +25835,24 @@ function parseColumn(raw, index, reserved = /* @__PURE__ */ new Set()) {
25651
25835
  if (effective !== "link" || !ref2) throw new MalformedColumnError(index, `"inverse" needs a link column (or list of links) with "ref"`);
25652
25836
  spec.inverse = entry.inverse.trim();
25653
25837
  }
25838
+ if (entry.acyclic !== void 0 && entry.acyclic !== null) {
25839
+ if (typeof entry.acyclic !== "boolean") throw new MalformedColumnError(index, `"acyclic" must be true or false`, guide("Set acyclic: true to refuse writes that close a cycle, or leave it out.", "acyclic"));
25840
+ if (entry.acyclic && (effective !== "link" || !ref2)) {
25841
+ throw new MalformedColumnError(
25842
+ index,
25843
+ `"acyclic" needs a link column (or list of links) with "ref" to its own table`,
25844
+ guide('Use acyclic on a link column with ref: "self", such as tasks blocked by tasks.', "acyclic")
25845
+ );
25846
+ }
25847
+ if (entry.acyclic && spec.inverse === name) {
25848
+ throw new MalformedColumnError(
25849
+ index,
25850
+ `"acyclic" cannot be set on a column that is its own inverse: every link would close a cycle`,
25851
+ guide("Name another column of the table as the inverse, such as blocks for blocked_by, or drop acyclic.", "acyclic")
25852
+ );
25853
+ }
25854
+ if (entry.acyclic) spec.acyclic = true;
25855
+ }
25654
25856
  if (entry.vocabulary !== void 0 && entry.vocabulary !== null) {
25655
25857
  if (typeof entry.vocabulary !== "string" || entry.vocabulary.trim() === "") {
25656
25858
  throw new MalformedColumnError(index, `"vocabulary" must be a table name`);
@@ -25807,12 +26009,14 @@ var init_table_loader = __esm({
25807
26009
  init_types_table();
25808
26010
  init_validate();
25809
26011
  init_message();
26012
+ init_hints();
25810
26013
  init_db_paths();
25811
26014
  MalformedColumnError = class extends Error {
25812
- constructor(index, message) {
26015
+ constructor(index, message, guidance) {
25813
26016
  super(`columns[${index}]: ${message}`);
25814
26017
  this.index = index;
25815
26018
  this.name = "MalformedColumnError";
26019
+ if (guidance) Object.assign(this, guidance);
25816
26020
  }
25817
26021
  };
25818
26022
  MalformedTableOptionsError = class extends Error {
@@ -25843,6 +26047,7 @@ var init_table_loader = __esm({
25843
26047
  "generated",
25844
26048
  "identity",
25845
26049
  "hierarchy",
26050
+ "acyclic",
25846
26051
  "vocabulary",
25847
26052
  "inverse",
25848
26053
  "ref_where",
@@ -26040,6 +26245,19 @@ function tableTags(entry) {
26040
26245
  const pairs = isRecord(tags) ? Object.entries(tags).map(([k, v]) => [k, text5(v)]) : (Array.isArray(tags) ? tags : [tags]).map((k) => [text5(k).trim(), ""]).filter(([k]) => k !== "");
26041
26246
  return pairs.length === 0 ? void 0 : Object.fromEntries(pairs);
26042
26247
  }
26248
+ function constraintHints(entry) {
26249
+ const out = {};
26250
+ const raw = entry.constraint_hints;
26251
+ if (!isRecord(raw)) return out;
26252
+ const checks = entry.constraints ?? {};
26253
+ for (const [name, hint] of Object.entries(raw)) {
26254
+ if (typeof hint === "string" && hint.trim() !== "" && Object.prototype.hasOwnProperty.call(checks, name)) out[name] = hint.trim();
26255
+ }
26256
+ return out;
26257
+ }
26258
+ function tableInstructions(entry) {
26259
+ return typeof entry.instructions === "string" && entry.instructions.trim() !== "" ? entry.instructions : void 0;
26260
+ }
26043
26261
  function withTableTags(entry, tags) {
26044
26262
  const { tags: _old, ...rest } = entry;
26045
26263
  const out = {};
@@ -27304,12 +27522,12 @@ async function appendDatabaseMove(app, move) {
27304
27522
  `);
27305
27523
  CACHE2.delete(app);
27306
27524
  }
27307
- async function eachLimit(items, limit, fn) {
27525
+ async function eachLimit(items2, limit, fn) {
27308
27526
  let next = 0;
27309
27527
  const worker = async () => {
27310
- while (next < items.length) await fn(items[next++]);
27528
+ while (next < items2.length) await fn(items2[next++]);
27311
27529
  };
27312
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
27530
+ await Promise.all(Array.from({ length: Math.min(limit, items2.length) }, worker));
27313
27531
  }
27314
27532
  function moveLinks(text5, from, to, entries = []) {
27315
27533
  const prefix = (t) => t === "" ? "" : `${t}/`;
@@ -27766,9 +27984,9 @@ function buildInverseSpecs(model) {
27766
27984
  return out;
27767
27985
  }
27768
27986
  function linkTargets(app, value, source) {
27769
- const items = Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value];
27987
+ const items2 = Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value];
27770
27988
  const out = [];
27771
- for (const v of items) {
27989
+ for (const v of items2) {
27772
27990
  const m = typeof v === "string" ? LINK_RE.exec(v.trim()) : null;
27773
27991
  if (!m) continue;
27774
27992
  const dest = app.metadataCache.getFirstLinkpathDest(m[1].trim(), source)?.path;
@@ -29509,22 +29727,22 @@ async function storeFilesSince(app, moves) {
29509
29727
  function atOnceFor(count2) {
29510
29728
  return count2 >= WRITE_CONCURRENCY.from ? WRITE_CONCURRENCY.atOnce : 1;
29511
29729
  }
29512
- async function inOrder(items, pathOf, fn, limit = atOnceFor(items.length)) {
29513
- const results = new Array(items.length);
29730
+ async function inOrder(items2, pathOf, fn, limit = atOnceFor(items2.length)) {
29731
+ const results = new Array(items2.length);
29514
29732
  let next = 0;
29515
29733
  let stop = false;
29516
29734
  const worker = async () => {
29517
- while (!stop && next < items.length) {
29735
+ while (!stop && next < items2.length) {
29518
29736
  const i = next++;
29519
29737
  try {
29520
- results[i] = { ok: true, written: await fn(items[i]) };
29738
+ results[i] = { ok: true, written: await fn(items2[i]) };
29521
29739
  } catch (e) {
29522
29740
  results[i] = { ok: false, error: messageOf(e) };
29523
29741
  stop = true;
29524
29742
  }
29525
29743
  }
29526
29744
  };
29527
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
29745
+ await Promise.all(Array.from({ length: Math.min(limit, items2.length) }, worker));
29528
29746
  const first2 = results.findIndex((r) => r !== void 0 && !r.ok);
29529
29747
  const kept2 = /* @__PURE__ */ new Set();
29530
29748
  if (first2 >= 0) {
@@ -29543,7 +29761,7 @@ async function inOrder(items, pathOf, fn, limit = atOnceFor(items.length)) {
29543
29761
  if (r?.ok && (first2 < 0 || i < first2 || kept2.has(i))) done.push(r.written.value);
29544
29762
  });
29545
29763
  const f = first2 >= 0 ? results[first2] : null;
29546
- return { done, failed: f ? { path: pathOf(items[first2]), error: f.error } : null };
29764
+ return { done, failed: f ? { path: pathOf(items2[first2]), error: f.error } : null };
29547
29765
  }
29548
29766
  async function moveFile(app, from, to) {
29549
29767
  if (inStore(from)) return storeRename(app, from, to);
@@ -31269,10 +31487,10 @@ function equalTo(e) {
31269
31487
  if (typeof e === "string") return (a) => typeof a === "string" ? a === e : JSON.stringify(a) === json3;
31270
31488
  return (a) => JSON.stringify(a) === json3;
31271
31489
  }
31272
- function memberOf(items) {
31490
+ function memberOf(items2) {
31273
31491
  const strings2 = /* @__PURE__ */ new Set();
31274
31492
  const json3 = /* @__PURE__ */ new Set();
31275
- for (const i of items) {
31493
+ for (const i of items2) {
31276
31494
  if (typeof i === "string") strings2.add(i);
31277
31495
  json3.add(JSON.stringify(i));
31278
31496
  }
@@ -31739,14 +31957,14 @@ var init_check_links = __esm({
31739
31957
  if (!via) throw new OperationInputError(`where "${col}": "${head}" is not a link column with ref`);
31740
31958
  const key2 = `${t.catalog.ref.full}|${via.ref}`;
31741
31959
  const target = this.tables.get(key2);
31742
- const byPath2 = target ? this.rows.get(target.catalog.ref.full) : void 0;
31743
- if (!target || !byPath2) throw new OperationInputError(this.missing.get(key2) ?? `CHECK links not loaded for "${col}"`);
31960
+ const byPath3 = target ? this.rows.get(target.catalog.ref.full) : void 0;
31961
+ if (!target || !byPath3) throw new OperationInputError(this.missing.get(key2) ?? `CHECK links not loaded for "${col}"`);
31744
31962
  const pred = compileWhere({ [col.slice(dot2 + 1)]: cond }, target, this.settings, rowGetter(null, target, this.settings), this);
31745
31963
  return (row) => {
31746
31964
  const v = fmGet(row.ordered, head);
31747
31965
  const paths = (Array.isArray(v) ? v : [v]).filter((x) => !isEmptyValue(x)).map((x) => this.resolve(x, row.file.path));
31748
31966
  return paths.length > 0 && paths.every((p) => {
31749
- const r = p == null ? void 0 : byPath2.get(p);
31967
+ const r = p == null ? void 0 : byPath3.get(p);
31750
31968
  return r !== void 0 && pred(r);
31751
31969
  });
31752
31970
  };
@@ -31989,8 +32207,8 @@ function findRow(rows, ref2, table) {
31989
32207
  const given = ref2.trim();
31990
32208
  const want = (parseWikilink(given) ?? given).replace(/^\/+/, "").replace(/\.md$/i, "");
31991
32209
  const stem2 = (r) => r.file.path.replace(/\.md$/i, "");
31992
- const byPath2 = rows.find((r) => r.file.path === given || stem2(r) === want);
31993
- if (byPath2) return byPath2;
32210
+ const byPath3 = rows.find((r) => r.file.path === given || stem2(r) === want);
32211
+ if (byPath3) return byPath3;
31994
32212
  const hits = want.includes("/") ? rows.filter((r) => stem2(r).endsWith(`/${want}`)) : rows.filter((r) => basenameOf(r.file.path) === want);
31995
32213
  if (hits.length > 1) {
31996
32214
  throw new OperationInputError(
@@ -32179,6 +32397,8 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
32179
32397
  const s = await tableIn(app, settings, metastore, ref2.full);
32180
32398
  for (const [name, where] of Object.entries(s.catalog.entry.constraints)) {
32181
32399
  if (!followsLinks2(where)) continue;
32400
+ const checkHint = constraintHints(s.catalog.entry)[name];
32401
+ const ownHint = checkHint ? { hint: checkHint } : {};
32182
32402
  await after.prepare(s, where);
32183
32403
  const rows = after.rowsOf(s);
32184
32404
  const restored = renewed.has(`${ref2.full}|CHECK ${name}`);
@@ -32202,7 +32422,7 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
32202
32422
  if (passes(r)) continue;
32203
32423
  const message = `violates CHECK ${name}: ${JSON.stringify(where)}`;
32204
32424
  if (own2 === void 0 && restored) {
32205
- add(r.file.path, { constraint: name, code: "check", message: `${message} (CHECK ${name} comes back with this change)` });
32425
+ add(r.file.path, { constraint: name, code: "check", message: `${message} (CHECK ${name} comes back with this change)`, ...ownHint });
32206
32426
  continue;
32207
32427
  }
32208
32428
  if (own2 === void 0) {
@@ -32210,10 +32430,10 @@ async function linkedCheckViolationsImpl(app, settings, cs, consequential = /* @
32210
32430
  await before.prepare(s, where);
32211
32431
  passed = compileCheck(where, s, settings, before);
32212
32432
  }
32213
- if (passed(r)) add(r.file.path, { constraint: name, code: "check", message: `${message}${suffix(r, heads)}` });
32433
+ if (passed(r)) add(r.file.path, { constraint: name, code: "check", message: `${message}${suffix(r, heads)}`, ...ownHint });
32214
32434
  continue;
32215
32435
  }
32216
- add(own2, { constraint: name, code: "check", message: consequential.has(own2) ? `${message}${suffix(r, heads, own2)}` : message });
32436
+ add(own2, { constraint: name, code: "check", message: consequential.has(own2) ? `${message}${suffix(r, heads, own2)}` : message, ...ownHint });
32217
32437
  }
32218
32438
  }
32219
32439
  }
@@ -32240,17 +32460,177 @@ var init_changeset_checks = __esm({
32240
32460
  }
32241
32461
  });
32242
32462
 
32463
+ // src/core/acyclic.ts
32464
+ var acyclic_exports = {};
32465
+ __export(acyclic_exports, {
32466
+ acyclicPairs: () => acyclicPairs,
32467
+ acyclicViolations: () => acyclicViolations,
32468
+ changeSetCycles: () => changeSetCycles,
32469
+ cycleThrough: () => cycleThrough
32470
+ });
32471
+ function acyclicPairs(columns, table) {
32472
+ const target = (c) => c.ref === "self" ? table : c.ref;
32473
+ return columns.filter((c) => c.acyclic === true).map((c) => {
32474
+ const declared = c.inverse !== void 0 && c.inverse !== c.name ? c.inverse : void 0;
32475
+ const back = declared ?? columns.find((x) => x.name !== c.name && x.inverse === c.name && target(x) === target(c))?.name;
32476
+ return { column: c.name, inverse: back !== void 0 && columns.some((x) => x.name === back) ? back : null };
32477
+ });
32478
+ }
32479
+ function graphOf(pair, rows) {
32480
+ const names2 = new Set(rows.map((r) => basenameOf(r.path)));
32481
+ const out = /* @__PURE__ */ new Map();
32482
+ const add = (from, to) => {
32483
+ if (!names2.has(from) || !names2.has(to)) return;
32484
+ let s = out.get(from);
32485
+ if (!s) out.set(from, s = /* @__PURE__ */ new Set());
32486
+ s.add(to);
32487
+ };
32488
+ for (const r of rows) {
32489
+ const self = basenameOf(r.path);
32490
+ for (const v of items(fmGet(r.ordered, pair.column))) {
32491
+ const to = linkName(v);
32492
+ if (to !== null) add(self, to);
32493
+ }
32494
+ if (pair.inverse !== null) {
32495
+ for (const v of items(fmGet(r.ordered, pair.inverse))) {
32496
+ const from = linkName(v);
32497
+ if (from !== null) add(from, self);
32498
+ }
32499
+ }
32500
+ }
32501
+ return out;
32502
+ }
32503
+ function cycleThrough(graph, start) {
32504
+ const prev = /* @__PURE__ */ new Map();
32505
+ const seen = /* @__PURE__ */ new Set([start]);
32506
+ const queue = [start];
32507
+ while (queue.length > 0) {
32508
+ const u = queue.shift();
32509
+ for (const v of [...graph.get(u) ?? []].sort()) {
32510
+ if (v === start) {
32511
+ const chain = [u];
32512
+ while (chain[chain.length - 1] !== start) chain.push(prev.get(chain[chain.length - 1]));
32513
+ return [...chain.reverse(), start];
32514
+ }
32515
+ if (!seen.has(v)) {
32516
+ seen.add(v);
32517
+ prev.set(v, u);
32518
+ queue.push(v);
32519
+ }
32520
+ }
32521
+ }
32522
+ return null;
32523
+ }
32524
+ function acyclicViolations(columns, table, rows, opts) {
32525
+ const pairs = acyclicPairs(columns, table).filter((p) => opts.involving === void 0 || p.column === opts.involving || p.inverse === opts.involving);
32526
+ const out = /* @__PURE__ */ new Map();
32527
+ const head = opts.wording === "write" ? "would close a cycle in" : "is on a cycle in";
32528
+ for (const pair of pairs) {
32529
+ const after = graphOf(pair, rows);
32530
+ const before = opts.before ? graphOf(pair, opts.before) : null;
32531
+ for (const r of [...rows].sort(byPath2)) {
32532
+ if (opts.only && !opts.only.has(r.path)) continue;
32533
+ const name = basenameOf(r.path);
32534
+ const cycle = cycleThrough(after, name);
32535
+ if (cycle === null || before !== null && cycleThrough(before, name) !== null) continue;
32536
+ const v = { column: pair.column, code: "cycle", message: `${head} "${pair.column}" (${cycle.join(" \u2192 ")})` };
32537
+ out.set(r.path, [...out.get(r.path) ?? [], v]);
32538
+ }
32539
+ }
32540
+ return [...out].map(([path19, violations]) => ({ path: path19, violations })).sort(byPath2);
32541
+ }
32542
+ function acyclicTables(model) {
32543
+ let out = ACYCLIC_TABLES.get(model);
32544
+ if (!out) {
32545
+ const set = /* @__PURE__ */ new Set();
32546
+ const has = (cols) => cols.some((c) => c.acyclic === true);
32547
+ for (const { ref: ref2, entry } of catalogTables(model)) {
32548
+ const bases = baseChain(model.bases, entry.bases ?? []);
32549
+ if (has(entry.columns) || bases.some((b) => has(model.bases[b].columns))) set.add(ref2.full);
32550
+ }
32551
+ ACYCLIC_TABLES.set(model, out = set);
32552
+ }
32553
+ return out;
32554
+ }
32555
+ async function changeSetCycles(app, settings, cs) {
32556
+ if (cs.texts.some((t) => t.path === metastorePath(settings))) return [];
32557
+ const model = cachedMetastore(app, settings) ?? (await loadMetastore(app, settings)).model;
32558
+ const tables = acyclicTables(model);
32559
+ if (tables.size === 0) return [];
32560
+ const keep = /* @__PURE__ */ new Set([settings.tableKey]);
32561
+ const tableOf = (o) => rowTableFull(model, fmGet(o, settings.tableKey));
32562
+ const moved = new Map(cs.moves.map((m) => [m.from, m.to]));
32563
+ const created = cs.creates.map((c) => ({ path: c.path, ordered: readOrderedFromMarkdown(c.content, keep).ordered }));
32564
+ const touched = /* @__PURE__ */ new Map();
32565
+ const note = (full, x) => {
32566
+ if (full === null || !tables.has(full)) return;
32567
+ touched.set(full, [...touched.get(full) ?? [], x]);
32568
+ };
32569
+ for (const e of cs.edits) note(tableOf(e.after), { path: e.path, final: moved.get(e.path) ?? e.path, before: e.before, after: e.after });
32570
+ for (const c of created) note(tableOf(c.ordered), { path: c.path, final: c.path, before: null, after: c.ordered });
32571
+ if (touched.size === 0) return [];
32572
+ return phase("engine.checks", async () => {
32573
+ const out = [];
32574
+ for (const [full, list2] of [...touched].sort(([a], [b]) => a < b ? -1 : 1)) {
32575
+ const t = await loadTable(app, settings, full);
32576
+ const pairs = acyclicPairs(t.def.columns, full);
32577
+ const cols = pairs.flatMap((p) => p.inverse === null ? [p.column] : [p.column, p.inverse]);
32578
+ const value = (o, c) => JSON.stringify(o === null ? null : fmGet(o, c) ?? null);
32579
+ if (!list2.some((x) => cols.some((c) => value(x.before, c) !== value(x.after, c)))) continue;
32580
+ const before = (await loadRows(app, settings, t)).map((r) => ({ path: r.file.path, ordered: r.ordered }));
32581
+ const after = new Map(before.map((r) => [r.path, r.ordered]));
32582
+ for (const e of cs.edits) {
32583
+ if (tableOf(e.after) === full) after.set(e.path, e.after);
32584
+ else after.delete(e.path);
32585
+ }
32586
+ const archive = `${t.def.layout.archive}/`;
32587
+ for (const m of cs.moves) {
32588
+ const o = after.get(m.from);
32589
+ if (o === void 0) continue;
32590
+ after.delete(m.from);
32591
+ if (!m.to.startsWith(archive)) after.set(m.to, o);
32592
+ }
32593
+ for (const p of cs.deletes) after.delete(p);
32594
+ for (const c of created) if (tableOf(c.ordered) === full) after.set(c.path, c.ordered);
32595
+ const reportAs = new Map(list2.filter((x) => after.has(x.final)).map((x) => [x.final, x.path]));
32596
+ const found = acyclicViolations(t.def.columns, full, [...after].map(([path19, ordered]) => ({ path: path19, ordered })), {
32597
+ only: new Set(reportAs.keys()),
32598
+ before,
32599
+ wording: "write"
32600
+ });
32601
+ for (const v of found) out.push({ path: reportAs.get(v.path), violations: v.violations });
32602
+ }
32603
+ return out;
32604
+ }, { "datanotes.checks.kind": "acyclic" });
32605
+ }
32606
+ var items, byPath2, ACYCLIC_TABLES;
32607
+ var init_acyclic = __esm({
32608
+ "src/core/acyclic.ts"() {
32609
+ "use strict";
32610
+ init_engine();
32611
+ init_frontmatter();
32612
+ init_metastore();
32613
+ init_path_templates();
32614
+ init_tracing();
32615
+ init_where();
32616
+ items = (v) => Array.isArray(v) ? v : v === null || v === void 0 ? [] : [v];
32617
+ byPath2 = (a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
32618
+ ACYCLIC_TABLES = /* @__PURE__ */ new WeakMap();
32619
+ }
32620
+ });
32621
+
32243
32622
  // src/core/row-checks.ts
32244
32623
  function constraintViolationsImpl(t, settings, path19, ordered, links) {
32245
32624
  const raw = t.catalog.entry.constraints;
32246
32625
  if (!raw || typeof raw !== "object") return [];
32247
32626
  const out = [];
32627
+ const hints = constraintHints(t.catalog.entry);
32248
32628
  for (const [name, where] of Object.entries(raw)) {
32249
32629
  if (!where || typeof where !== "object" || Array.isArray(where)) continue;
32250
32630
  if (!links && followsLinks2(where)) continue;
32251
32631
  try {
32252
32632
  if (!compileCheck(where, t, settings, links)({ file: { path: path19 }, ordered })) {
32253
- out.push({ constraint: name, code: "check", message: `violates CHECK ${name}: ${JSON.stringify(where)}` });
32633
+ out.push({ constraint: name, code: "check", message: `violates CHECK ${name}: ${JSON.stringify(where)}`, ...hints[name] ? { hint: hints[name] } : {} });
32254
32634
  }
32255
32635
  } catch (e) {
32256
32636
  out.push({ constraint: name, code: "check", message: `CHECK ${name} is invalid: ${messageOf(e)}` });
@@ -32289,14 +32669,14 @@ function checkViolation(v) {
32289
32669
  async function checkOne(app, settings, table, everyUnreadable) {
32290
32670
  const t = await loadTable(app, settings, table);
32291
32671
  const violations = [];
32292
- const byPath2 = /* @__PURE__ */ new Map();
32672
+ const byPath3 = /* @__PURE__ */ new Map();
32293
32673
  const report = (path19, v) => {
32294
- const existing = byPath2.get(path19);
32674
+ const existing = byPath3.get(path19);
32295
32675
  if (existing) existing.violations.push(...v);
32296
32676
  else {
32297
32677
  const row = { path: path19, violations: v };
32298
32678
  violations.push(row);
32299
- byPath2.set(path19, row);
32679
+ byPath3.set(path19, row);
32300
32680
  }
32301
32681
  };
32302
32682
  const links = new CheckLinks(app, settings);
@@ -32321,6 +32701,7 @@ async function checkOne(app, settings, table, everyUnreadable) {
32321
32701
  const back = await missingBackLinks(app, settings, t.catalog.model, t.catalog.ref.full, rows.map((r) => ({ path: r.file.path, ordered: r.ordered })));
32322
32702
  for (const [path19, list2] of back) report(path19, list2.map((x) => ({ column: x.column, code: "inverse", message: x.message, value: x.value })));
32323
32703
  for (const h of await hierarchyViolations(app, settings, t, rows.map((r) => ({ path: r.file.path, ordered: r.ordered })), rows)) report(h.path, h.violations);
32704
+ for (const a of acyclicViolations(t.def.columns, t.catalog.ref.full, rows.map((r) => ({ path: r.file.path, ordered: r.ordered })), { wording: "check" })) report(a.path, a.violations);
32324
32705
  const unreadable = everyUnreadable && everyUnreadable.model === t.catalog.model ? everyUnreadable.rows.filter((u) => u.table === t.catalog.ref.full) : await unreadableRows(app, settings, t.catalog.model, t.catalog.ref.full);
32325
32706
  for (const u of unreadable) violations.push({ path: u.path, violations: [{ code: "unreadable", message: unreadableMessage(u.error) }] });
32326
32707
  return { table: t.def.name, rows: rows.length + unreadable.length, violations };
@@ -32393,6 +32774,7 @@ var init_row_checks = __esm({
32393
32774
  init_check_links();
32394
32775
  init_link_targets();
32395
32776
  init_message();
32777
+ init_acyclic();
32396
32778
  init_unreadable();
32397
32779
  init_tracing();
32398
32780
  CHECK_LIMIT = 500;
@@ -32459,7 +32841,7 @@ async function ownViolations(app, settings, t, rows, links) {
32459
32841
  const targetChecks = [];
32460
32842
  for (const c of filteredLinkColumns(t)) targetChecks.push(await linkTargetCheck(t, settings, links, c));
32461
32843
  const computed = computedColumnNames(t);
32462
- const whole = t.def.columns.some((c) => c.unique && !computed.has(c.name) || c.hierarchy);
32844
+ const whole = t.def.columns.some((c) => c.unique && !computed.has(c.name) || c.hierarchy || c.acyclic === true);
32463
32845
  if (whole) await links.prepare(t, null);
32464
32846
  const all = whole ? links.rowsOf(t) : [];
32465
32847
  const own2 = new Set(rows.map((r) => r.file.path));
@@ -32467,6 +32849,7 @@ async function ownViolations(app, settings, t, rows, links) {
32467
32849
  uniqueViolations(t.def.columns, all.map((r) => ({ path: r.file.path, ordered: r.ordered }))).filter((u) => own2.has(u.path)).map((u) => [u.path, u.violations])
32468
32850
  );
32469
32851
  const cycles = whole ? await hierarchyViolations(app, settings, t, rows.map((r) => ({ path: r.file.path, ordered: r.ordered })), all) : [];
32852
+ const loops = whole ? acyclicViolations(t.def.columns, t.catalog.ref.full, all.map((r) => ({ path: r.file.path, ordered: r.ordered })), { only: own2, wording: "check" }) : [];
32470
32853
  const out = /* @__PURE__ */ new Map();
32471
32854
  for (const r of rows) {
32472
32855
  const path19 = r.file.path;
@@ -32475,17 +32858,18 @@ async function ownViolations(app, settings, t, rows, links) {
32475
32858
  ...unique.get(path19) ?? [],
32476
32859
  ...constraintViolations(t, settings, path19, r.ordered, links),
32477
32860
  ...targetChecks.flatMap((check) => check(r)),
32478
- ...cycles.filter((c) => c.path === path19).flatMap((c) => c.violations)
32861
+ ...cycles.filter((c) => c.path === path19).flatMap((c) => c.violations),
32862
+ ...loops.filter((c) => c.path === path19).flatMap((c) => c.violations)
32479
32863
  ];
32480
32864
  out.set(path19, v.filter((x) => x.code === "check" || !computed.has(x.column)));
32481
32865
  }
32482
32866
  return out;
32483
32867
  }
32484
32868
  async function checkExternalEdits(app, settings, input) {
32485
- const byPath2 = /* @__PURE__ */ new Map();
32869
+ const byPath3 = /* @__PURE__ */ new Map();
32486
32870
  const tableOf = /* @__PURE__ */ new Map();
32487
32871
  const model = input.length > 0 ? (await loadMetastore(app, settings))?.model : void 0;
32488
- if (!model) return { byPath: byPath2, loaded: [], tableOf };
32872
+ if (!model) return { byPath: byPath3, loaded: [], tableOf };
32489
32873
  const tp = settings.tableKey;
32490
32874
  const edits = /* @__PURE__ */ new Map();
32491
32875
  for (const e of input) {
@@ -32553,15 +32937,15 @@ async function checkExternalEdits(app, settings, input) {
32553
32937
  }
32554
32938
  const add = (at, v, full) => {
32555
32939
  if (!v) return;
32556
- byPath2.set(at, [...byPath2.get(at), v]);
32940
+ byPath3.set(at, [...byPath3.get(at), v]);
32557
32941
  tableOf.set(v.path, full);
32558
32942
  };
32559
32943
  const byTable = /* @__PURE__ */ new Map();
32560
32944
  for (const [path19, { row, full }] of now) {
32561
32945
  byTable.set(full, [...byTable.get(full) ?? [], row]);
32562
- byPath2.set(path19, []);
32946
+ byPath3.set(path19, []);
32563
32947
  }
32564
- for (const e of edits.values()) if (!now.has(e.path) && gone.has(e.oldPath ?? e.path)) byPath2.set(e.path, []);
32948
+ for (const e of edits.values()) if (!now.has(e.path) && gone.has(e.oldPath ?? e.path)) byPath3.set(e.path, []);
32565
32949
  for (const [full, rows] of byTable) {
32566
32950
  const t = await table(full);
32567
32951
  const found = await ownViolations(app, settings, t, rows, after);
@@ -32687,7 +33071,7 @@ async function checkExternalEdits(app, settings, input) {
32687
33071
  }
32688
33072
  }
32689
33073
  const loaded = /* @__PURE__ */ new Set([...after.loadedTables(), ...before?.loadedTables() ?? []]);
32690
- return { byPath: byPath2, loaded: [...loaded].sort(), tableOf };
33074
+ return { byPath: byPath3, loaded: [...loaded].sort(), tableOf };
32691
33075
  }
32692
33076
  async function checkChangedTables(app, settings, tables) {
32693
33077
  const byTable = /* @__PURE__ */ new Map();
@@ -32721,6 +33105,7 @@ var init_external_edits = __esm({
32721
33105
  init_check_links();
32722
33106
  init_link_targets();
32723
33107
  init_row_checks();
33108
+ init_acyclic();
32724
33109
  init_message();
32725
33110
  }
32726
33111
  });
@@ -32844,7 +33229,7 @@ async function rowsInVaultOrder(app, settings, t, keep, bodies) {
32844
33229
  return out.sort((a, b) => a.file.path.localeCompare(b.file.path));
32845
33230
  }
32846
33231
  function uniqueViolations(columns, rows) {
32847
- const byPath2 = /* @__PURE__ */ new Map();
33232
+ const byPath3 = /* @__PURE__ */ new Map();
32848
33233
  const rowOf = /* @__PURE__ */ new Map();
32849
33234
  for (const r of rows) if (!rowOf.has(r.path)) rowOf.set(r.path, r);
32850
33235
  for (const col of columns.filter((c) => c.unique)) {
@@ -32860,13 +33245,13 @@ function uniqueViolations(columns, rows) {
32860
33245
  if (paths.length < 2) continue;
32861
33246
  for (const p of paths) {
32862
33247
  const others = paths.filter((x) => x !== p).join(", ");
32863
- let list2 = byPath2.get(p);
32864
- if (!list2) byPath2.set(p, list2 = []);
33248
+ let list2 = byPath3.get(p);
33249
+ if (!list2) byPath3.set(p, list2 = []);
32865
33250
  list2.push({ column: col.name, code: "unique", message: `must be unique (also used by ${others})`, value: fmGet(rowOf.get(p).ordered, col.name) });
32866
33251
  }
32867
33252
  }
32868
33253
  }
32869
- return [...byPath2.entries()].map(([path19, violations]) => ({ path: path19, violations }));
33254
+ return [...byPath3.entries()].map(([path19, violations]) => ({ path: path19, violations }));
32870
33255
  }
32871
33256
  function uniqueKey(v) {
32872
33257
  if (isEmptyValue(v)) return null;
@@ -32893,6 +33278,7 @@ function serializeColumn(c) {
32893
33278
  if (c.generated !== void 0) out.generated = c.generated;
32894
33279
  if (c.identity !== void 0) out.identity = c.identity;
32895
33280
  if (c.hierarchy) out.hierarchy = true;
33281
+ if (c.acyclic) out.acyclic = true;
32896
33282
  if (c.vocabulary !== void 0) out.vocabulary = c.vocabulary;
32897
33283
  if (c.inverse !== void 0) out.inverse = c.inverse;
32898
33284
  if (c.ref_where !== void 0) out.ref_where = c.ref_where;
@@ -32955,6 +33341,8 @@ async function finish(app, settings, cs, conflicts, violations, opts) {
32955
33341
  const { linkedCheckViolations: linkedCheckViolations2 } = await Promise.resolve().then(() => (init_changeset_checks(), changeset_checks_exports));
32956
33342
  violations = [...violations, ...await linkedCheckViolations2(app, settings, cs, mirrored)];
32957
33343
  }
33344
+ const { changeSetCycles: changeSetCycles2 } = await Promise.resolve().then(() => (init_acyclic(), acyclic_exports));
33345
+ violations = [...violations, ...await changeSetCycles2(app, settings, cs)];
32958
33346
  }
32959
33347
  const base = emptyResult(cs.op, dryRun);
32960
33348
  base.planned_edits = plannedFrom(cs);
@@ -33161,12 +33549,12 @@ async function externalViolationCounts(app, settings, changes) {
33161
33549
  }
33162
33550
  const out = /* @__PURE__ */ new Map();
33163
33551
  if (edits.length > 0) {
33164
- const { byPath: byPath2, tableOf } = await checkExternalEdits2(app, settings, edits.map(({ after: _after, ...e }) => e));
33552
+ const { byPath: byPath3, tableOf } = await checkExternalEdits2(app, settings, edits.map(({ after: _after, ...e }) => e));
33165
33553
  const seen = /* @__PURE__ */ new Map();
33166
33554
  for (const e of edits) {
33167
33555
  for (const full of countsIn.get(e.path)) {
33168
33556
  if (!seen.has(full)) seen.set(full, /* @__PURE__ */ new Set());
33169
- for (const v of byPath2.get(e.path) ?? []) if (tableOf.get(v.path) === full) seen.get(full).add(JSON.stringify(v));
33557
+ for (const v of byPath3.get(e.path) ?? []) if (tableOf.get(v.path) === full) seen.get(full).add(JSON.stringify(v));
33170
33558
  }
33171
33559
  }
33172
33560
  for (const [full, list2] of seen) out.set(full, list2.size);
@@ -33204,7 +33592,7 @@ function addSchemaChange(cs, t, after, settings) {
33204
33592
  const before = new Map(t.catalog.entry.columns.map((c) => [String(c.name), c]));
33205
33593
  const touched = entry.columns.filter((c) => {
33206
33594
  const prev = before.get(String(c.name));
33207
- return !prev || prev.ref !== c.ref || prev.hierarchy !== c.hierarchy || prev.type !== c.type || prev.items !== c.items || prev.vocabulary !== c.vocabulary || JSON.stringify(prev.ref_by) !== JSON.stringify(c.ref_by);
33595
+ return !prev || prev.ref !== c.ref || prev.hierarchy !== c.hierarchy || prev.acyclic !== c.acyclic || prev.type !== c.type || prev.items !== c.items || prev.vocabulary !== c.vocabulary || JSON.stringify(prev.ref_by) !== JSON.stringify(c.ref_by);
33208
33596
  });
33209
33597
  checkColumnRefs(t.catalog.model, ref2.full, touched, ref2.full, entry.columns);
33210
33598
  def = tableDefinitionFromOrdered(ref2, catalogTablePath(settings, ref2.full), entryToOrdered(ref2, entry, settings, t.catalog.model), settings);
@@ -33244,6 +33632,12 @@ function checkColumnRefs(model, owner, columns, where, own2 = columns) {
33244
33632
  if (c.hierarchy === true && ref2 !== owner) {
33245
33633
  throw new OperationInputError(`${where}.${String(c.name)}: a hierarchy column must point at its own table (ref: self)`);
33246
33634
  }
33635
+ if (c.acyclic === true && ref2 !== owner) {
33636
+ throw new OperationInputError(
33637
+ `${where}.${String(c.name)}: an acyclic column must point at its own table (ref: self)`,
33638
+ guide(`Set ref: "self": acyclic keeps the links between rows of one table free of cycles, such as tasks blocked by tasks.`, "acyclic")
33639
+ );
33640
+ }
33247
33641
  if (typeof c.inverse === "string") checkInverse(model, owner, columns, c, ref2, where);
33248
33642
  }
33249
33643
  }
@@ -33343,6 +33737,9 @@ function assertColumnName(name, what) {
33343
33737
  return name;
33344
33738
  }
33345
33739
  async function createTableImpl(app, settings, input, opts = {}) {
33740
+ if (input.instructions !== void 0 && input.instructions !== null && typeof input.instructions !== "string") {
33741
+ throw new OperationInputError(`"instructions" must be text: how to fill a row of the table`, guide(`Pass instructions as Markdown text, e.g. "1. Ask which film and when.", or leave them out.`, "instructions"));
33742
+ }
33346
33743
  const proposal = input.description === null || input.columns.some((c) => c?.description === null);
33347
33744
  {
33348
33745
  const given = input.columns.find((c) => c.name === SYSTEM_ID_COLUMN);
@@ -33421,6 +33818,7 @@ async function createTableImpl(app, settings, input, opts = {}) {
33421
33818
  tableDefinitionFromOrdered(ref2, pseudo, cat, settings);
33422
33819
  const entry = withTableTags(orderedToEntry(cat, void 0, settings), tags);
33423
33820
  if (typeof input.rowTemplate === "string" && input.rowTemplate.trim() !== "") entry.row_template = input.rowTemplate;
33821
+ if (typeof input.instructions === "string" && input.instructions.trim() !== "") entry.instructions = input.instructions;
33424
33822
  if (typeof linkTarget2 === "boolean") entry.options = { ...entry.options, autolink_target: linkTarget2 };
33425
33823
  if (typeof input.history === "boolean") entry.options = { ...entry.options, history: input.history };
33426
33824
  checkColumnRefs(model, ref2.full, entry.columns, ref2.full);
@@ -33508,6 +33906,16 @@ async function addColumnImpl(app, settings, input, opts = {}) {
33508
33906
  violations.push(...uniqueViolations([spec], (await loadRows(app, settings, t)).map((r) => ({ path: r.file.path, ordered: r.ordered }))));
33509
33907
  }
33510
33908
  violations.push(...await filteredLinkViolations(app, settings, t, [...t.def.columns, spec], spec));
33909
+ const { acyclicPairs: acyclicPairs2, acyclicViolations: acyclicViolations2 } = await Promise.resolve().then(() => (init_acyclic(), acyclic_exports));
33910
+ const withNew = [...t.def.columns, spec];
33911
+ if (acyclicPairs2(withNew, t.catalog.ref.full).some((p) => p.column === spec.name || p.inverse === spec.name)) {
33912
+ const rowsNow = (await loadRows(app, settings, t)).map((r) => {
33913
+ const has = r.ordered.some(([k]) => k === spec.name);
33914
+ const value = has ? fmGet(r.ordered, spec.name) : dense ? fill : void 0;
33915
+ return { path: r.file.path, ordered: value === void 0 ? r.ordered : fmSet(r.ordered, spec.name, value) };
33916
+ });
33917
+ violations.push(...acyclicViolations2(withNew, t.catalog.ref.full, rowsNow, { involving: spec.name, wording: "check" }));
33918
+ }
33511
33919
  return finish(app, settings, cs, [], violations, opts);
33512
33920
  }
33513
33921
  function withRenamedLevels(c, table, from, to) {
@@ -33700,6 +34108,7 @@ async function alterColumnImpl(app, settings, input, opts = {}) {
33700
34108
  else merged[k] = v;
33701
34109
  }
33702
34110
  const spec = parseColumn(merged, idx, META_KEYS);
34111
+ const checked = spec.ref === "self" ? { ...spec, ref: t.catalog.ref.full } : spec;
33703
34112
  const columns = t.def.columns.map((c, i) => i === idx ? spec : c);
33704
34113
  const reserved = META_KEYS;
33705
34114
  let after = withColumns(t.ordered, columns);
@@ -33726,13 +34135,19 @@ async function alterColumnImpl(app, settings, input, opts = {}) {
33726
34135
  violations.push({ path: r.file.path, violations: [{ column: spec.name, code: "required", message: requiredMessage(spec) }] });
33727
34136
  }
33728
34137
  } else {
33729
- violations.push({ path: r.file.path, violations: validateValue(spec, v, validationContextFor(app, settings, r.file.path)) });
34138
+ violations.push({ path: r.file.path, violations: validateValue(checked, v, validationContextFor(app, settings, r.file.path)) });
33730
34139
  }
33731
34140
  if (makeDense && !has) {
33732
34141
  cs.edits.push({ path: r.file.path, before: r.ordered, after: fmAdd(r.ordered, spec.name, fill ?? null) });
33733
34142
  }
33734
34143
  }
33735
34144
  violations.push(...await filteredLinkViolations(app, settings, t, columns, spec));
34145
+ const { acyclicPairs: acyclicPairs2, acyclicViolations: acyclicViolations2 } = await Promise.resolve().then(() => (init_acyclic(), acyclic_exports));
34146
+ if (acyclicPairs2(columns, t.catalog.ref.full).some((p) => p.column === spec.name || p.inverse === spec.name)) {
34147
+ const afterOf = new Map(cs.edits.map((e) => [e.path, e.after]));
34148
+ const rowsNow = existing.map((r) => ({ path: r.file.path, ordered: afterOf.get(r.file.path) ?? r.ordered }));
34149
+ violations.push(...acyclicViolations2(columns, t.catalog.ref.full, rowsNow, { involving: spec.name, wording: "check" }));
34150
+ }
33736
34151
  const text5 = cs.texts.find((x) => x.path === t.file.path).after;
33737
34152
  const conflicts = levelTargetConflicts(parseMetastore(text5), t.catalog.ref.full, spec.name).map((message) => ({ path: t.file.path, message }));
33738
34153
  return finish(app, settings, cs, conflicts, violations, opts);
@@ -34729,10 +35144,10 @@ var init_otlp = __esm({
34729
35144
  this.timer.unref?.();
34730
35145
  }
34731
35146
  }
34732
- push(items) {
35147
+ push(items2) {
34733
35148
  const room = this.maxBatch * 8 - this.buffer.length;
34734
- if (room < items.length) this.dropped += items.length - Math.max(room, 0);
34735
- for (let i = 0; i < Math.min(items.length, Math.max(room, 0)); i++) this.buffer.push(items[i]);
35149
+ if (room < items2.length) this.dropped += items2.length - Math.max(room, 0);
35150
+ for (let i = 0; i < Math.min(items2.length, Math.max(room, 0)); i++) this.buffer.push(items2[i]);
34736
35151
  if (this.buffer.length >= this.maxBatch) void this.flush();
34737
35152
  }
34738
35153
  resource() {
@@ -48436,14 +48851,14 @@ var require_codegen = __commonJS({
48436
48851
  return replaceName(expr);
48437
48852
  if (!canOptimize(expr))
48438
48853
  return expr;
48439
- return new code_1._Code(expr._items.reduce((items, c) => {
48854
+ return new code_1._Code(expr._items.reduce((items2, c) => {
48440
48855
  if (c instanceof code_1.Name)
48441
48856
  c = replaceName(c);
48442
48857
  if (c instanceof code_1._Code)
48443
- items.push(...c._items);
48858
+ items2.push(...c._items);
48444
48859
  else
48445
- items.push(c);
48446
- return items;
48860
+ items2.push(c);
48861
+ return items2;
48447
48862
  }, []));
48448
48863
  function replaceName(n) {
48449
48864
  const c = constants2[n.str];
@@ -48601,7 +49016,7 @@ var require_util = __commonJS({
48601
49016
  mergeNames: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true && ${from} !== undefined`, () => gen.assign(to, (0, codegen_1._)`${from} === true ? true : ${to} > ${from} ? ${to} : ${from}`)),
48602
49017
  mergeToName: (gen, from, to) => gen.if((0, codegen_1._)`${to} !== true`, () => gen.assign(to, from === true ? true : (0, codegen_1._)`${to} > ${from} ? ${to} : ${from}`)),
48603
49018
  mergeValues: (from, to) => from === true ? true : Math.max(from, to),
48604
- resultToName: (gen, items) => gen.var("items", items)
49019
+ resultToName: (gen, items2) => gen.var("items", items2)
48605
49020
  })
48606
49021
  };
48607
49022
  function evaluatedPropsToName(gen, ps) {
@@ -49109,13 +49524,13 @@ var require_defaults = __commonJS({
49109
49524
  var codegen_1 = require_codegen();
49110
49525
  var util_1 = require_util();
49111
49526
  function assignDefaults(it, ty) {
49112
- const { properties, items } = it.schema;
49527
+ const { properties, items: items2 } = it.schema;
49113
49528
  if (ty === "object" && properties) {
49114
49529
  for (const key2 in properties) {
49115
49530
  assignDefault(it, key2, properties[key2].default);
49116
49531
  }
49117
- } else if (ty === "array" && Array.isArray(items)) {
49118
- items.forEach((sch, i) => assignDefault(it, i, sch.default));
49532
+ } else if (ty === "array" && Array.isArray(items2)) {
49533
+ items2.forEach((sch, i) => assignDefault(it, i, sch.default));
49119
49534
  }
49120
49535
  }
49121
49536
  exports2.assignDefaults = assignDefaults;
@@ -49919,11 +50334,11 @@ var require_validate = __commonJS({
49919
50334
  gen.return((0, codegen_1._)`${names_1.default.errors} === 0`);
49920
50335
  }
49921
50336
  }
49922
- function assignEvaluated({ gen, evaluated, props, items }) {
50337
+ function assignEvaluated({ gen, evaluated, props, items: items2 }) {
49923
50338
  if (props instanceof codegen_1.Name)
49924
50339
  gen.assign((0, codegen_1._)`${evaluated}.props`, props);
49925
- if (items instanceof codegen_1.Name)
49926
- gen.assign((0, codegen_1._)`${evaluated}.items`, items);
50340
+ if (items2 instanceof codegen_1.Name)
50341
+ gen.assign((0, codegen_1._)`${evaluated}.items`, items2);
49927
50342
  }
49928
50343
  function schemaKeywords(it, types, typeErrors, errsCount) {
49929
50344
  const { gen, schema, data, allErrors, opts, self } = it;
@@ -50386,12 +50801,12 @@ var require_compile = __commonJS({
50386
50801
  validate2.source = { validateName, validateCode, scopeValues: gen._values };
50387
50802
  }
50388
50803
  if (this.opts.unevaluated) {
50389
- const { props, items } = schemaCxt;
50804
+ const { props, items: items2 } = schemaCxt;
50390
50805
  validate2.evaluated = {
50391
50806
  props: props instanceof codegen_1.Name ? void 0 : props,
50392
- items: items instanceof codegen_1.Name ? void 0 : items,
50807
+ items: items2 instanceof codegen_1.Name ? void 0 : items2,
50393
50808
  dynamicProps: props instanceof codegen_1.Name,
50394
- dynamicItems: items instanceof codegen_1.Name
50809
+ dynamicItems: items2 instanceof codegen_1.Name
50395
50810
  };
50396
50811
  if (validate2.source)
50397
50812
  validate2.source.evaluated = (0, codegen_1.stringify)(validate2.evaluated);
@@ -52401,8 +52816,8 @@ var require_ref = __commonJS({
52401
52816
  it.items = util_1.mergeEvaluated.items(gen, schEvaluated.items, it.items);
52402
52817
  }
52403
52818
  } else {
52404
- const items = gen.var("items", (0, codegen_1._)`${source}.evaluated.items`);
52405
- it.items = util_1.mergeEvaluated.items(gen, items, it.items, codegen_1.Name);
52819
+ const items2 = gen.var("items", (0, codegen_1._)`${source}.evaluated.items`);
52820
+ it.items = util_1.mergeEvaluated.items(gen, items2, it.items, codegen_1.Name);
52406
52821
  }
52407
52822
  }
52408
52823
  }
@@ -52942,28 +53357,28 @@ var require_additionalItems = __commonJS({
52942
53357
  error: error2,
52943
53358
  code(cxt) {
52944
53359
  const { parentSchema, it } = cxt;
52945
- const { items } = parentSchema;
52946
- if (!Array.isArray(items)) {
53360
+ const { items: items2 } = parentSchema;
53361
+ if (!Array.isArray(items2)) {
52947
53362
  (0, util_1.checkStrictMode)(it, '"additionalItems" is ignored when "items" is not an array of schemas');
52948
53363
  return;
52949
53364
  }
52950
- validateAdditionalItems(cxt, items);
53365
+ validateAdditionalItems(cxt, items2);
52951
53366
  }
52952
53367
  };
52953
- function validateAdditionalItems(cxt, items) {
53368
+ function validateAdditionalItems(cxt, items2) {
52954
53369
  const { gen, schema, data, keyword, it } = cxt;
52955
53370
  it.items = true;
52956
53371
  const len = gen.const("len", (0, codegen_1._)`${data}.length`);
52957
53372
  if (schema === false) {
52958
- cxt.setParams({ len: items.length });
52959
- cxt.pass((0, codegen_1._)`${len} <= ${items.length}`);
53373
+ cxt.setParams({ len: items2.length });
53374
+ cxt.pass((0, codegen_1._)`${len} <= ${items2.length}`);
52960
53375
  } else if (typeof schema == "object" && !(0, util_1.alwaysValidSchema)(it, schema)) {
52961
- const valid = gen.var("valid", (0, codegen_1._)`${len} <= ${items.length}`);
53376
+ const valid = gen.var("valid", (0, codegen_1._)`${len} <= ${items2.length}`);
52962
53377
  gen.if((0, codegen_1.not)(valid), () => validateItems(valid));
52963
53378
  cxt.ok(valid);
52964
53379
  }
52965
53380
  function validateItems(valid) {
52966
- gen.forRange("i", items.length, len, (i) => {
53381
+ gen.forRange("i", items2.length, len, (i) => {
52967
53382
  cxt.subschema({ keyword, dataProp: i, dataPropType: util_1.Type.Num }, valid);
52968
53383
  if (!it.allErrors)
52969
53384
  gen.if((0, codegen_1.not)(valid), () => gen.break());
@@ -56985,6 +57400,10 @@ async function addConstraintImpl(app, settings, input, opts = {}) {
56985
57400
  if (!input.check || typeof input.check !== "object" || Array.isArray(input.check) || Object.keys(input.check).length === 0) {
56986
57401
  throw new OperationInputError(`"check" must be a non-empty where filter`, guide(`A CHECK is a where filter every row must match, e.g. {"or": [{"status": "to watch"}, {"rating": {"exists": true}}]}.`, "check"));
56987
57402
  }
57403
+ if (input.hint !== void 0 && input.hint !== null && typeof input.hint !== "string") {
57404
+ throw new OperationInputError(`"hint" must be text: what to do when a row breaks the CHECK`, guide(`Pass hint as one or two sentences, e.g. "A watched film needs a rating: ask for it.", or leave it out.`, "check"));
57405
+ }
57406
+ const hint = typeof input.hint === "string" && input.hint.trim() !== "" ? input.hint.trim() : void 0;
56988
57407
  if (cat.entry.constraints?.[input.name]) throw new OperationInputError(`constraint "${input.name}" already exists; drop it first`, guide(`Choose another name, or drop_constraint "${input.name}" first to replace it; describe_table lists the constraints.`, "check"));
56989
57408
  const links = new CheckLinks(app, settings);
56990
57409
  await links.prepare(t, input.check);
@@ -56993,7 +57412,13 @@ async function addConstraintImpl(app, settings, input, opts = {}) {
56993
57412
  for (const r of await loadRows(app, settings, t)) {
56994
57413
  if (!passes(r)) conflicts.push({ path: r.file.path, message: `violates CHECK ${input.name}` });
56995
57414
  }
56996
- const entry = { ...cat.entry, constraints: { ...cat.entry.constraints ?? {}, [input.name]: input.check } };
57415
+ const hints = { ...constraintHints(cat.entry), ...hint ? { [input.name]: hint } : {} };
57416
+ const { constraint_hints: _h, ...withoutHints } = cat.entry;
57417
+ const entry = {
57418
+ ...withoutHints,
57419
+ constraints: { ...cat.entry.constraints ?? {}, [input.name]: input.check },
57420
+ ...Object.keys(hints).length > 0 ? { constraint_hints: hints } : {}
57421
+ };
56997
57422
  const cs = emptyChangeSet("add_constraint", `add constraint ${input.name} to ${cat.ref.full}`, t.def.schemaPath);
56998
57423
  cs.texts.push({ path: t.file.path, before: cat.text, after: setTableEntry(cat.text, cat.ref, entry) });
56999
57424
  return finish(app, settings, cs, conflicts, [], opts);
@@ -57005,8 +57430,13 @@ async function dropConstraintImpl(app, settings, input, opts = {}) {
57005
57430
  throw new OperationInputError(`${cat.ref.full} has no constraint "${input.name}"`, unknownName(input.name, Object.keys(cat.entry.constraints ?? {}), `constraints of ${cat.ref.full}`, "describe_table lists them.", "check"));
57006
57431
  }
57007
57432
  const { [input.name]: _gone, ...rest } = cat.entry.constraints;
57008
- const { constraints: _c, ...base } = cat.entry;
57009
- const entry = Object.keys(rest).length > 0 ? { ...base, constraints: rest } : base;
57433
+ const { [input.name]: _x, ...hintsLeft } = constraintHints(cat.entry);
57434
+ const { constraints: _c, constraint_hints: _h, ...base } = cat.entry;
57435
+ const entry = {
57436
+ ...base,
57437
+ ...Object.keys(rest).length > 0 ? { constraints: rest } : {},
57438
+ ...Object.keys(hintsLeft).length > 0 ? { constraint_hints: hintsLeft } : {}
57439
+ };
57010
57440
  const cs = emptyChangeSet("drop_constraint", `drop constraint ${input.name} from ${cat.ref.full}`, t.def.schemaPath);
57011
57441
  cs.texts.push({ path: t.file.path, before: cat.text, after: setTableEntry(cat.text, cat.ref, entry) });
57012
57442
  return finish(app, settings, cs, [], [], opts);
@@ -57148,7 +57578,8 @@ var INFO_COLUMNS = {
57148
57578
  ["comment", "string"],
57149
57579
  ["row_count", "integer"],
57150
57580
  ["latest_version", "integer"],
57151
- ["tags", "json"]
57581
+ ["tags", "json"],
57582
+ ["instructions", "string"]
57152
57583
  ],
57153
57584
  columns: [
57154
57585
  ["table_catalog", "string"],
@@ -57164,10 +57595,11 @@ var INFO_COLUMNS = {
57164
57595
  ["is_identity", "string"],
57165
57596
  ["is_unique", "string"],
57166
57597
  ["inherited_from", "string"],
57167
- ["is_hierarchy", "string"]
57598
+ ["is_hierarchy", "string"],
57599
+ ["is_acyclic", "string"]
57168
57600
  ],
57169
57601
  views: [["table_catalog", "string"], ["table_schema", "string"], ["table_name", "string"], ["view_definition", "string"], ["comment", "string"], ["aliases", "list"], ["labels", "string"]],
57170
- check_constraints: [["table_catalog", "string"], ["table_schema", "string"], ["table_name", "string"], ["constraint_name", "string"], ["check_clause", "string"]]
57602
+ check_constraints: [["table_catalog", "string"], ["table_schema", "string"], ["table_name", "string"], ["constraint_name", "string"], ["check_clause", "string"], ["hint", "string"]]
57171
57603
  };
57172
57604
  function informationSchemaTable(ref2) {
57173
57605
  const parts = ref2.trim().split(".");
@@ -57210,7 +57642,8 @@ async function informationRows(app, settings, name) {
57210
57642
  comment: entry.description ?? null,
57211
57643
  row_count: rows.get(ref2.full).length,
57212
57644
  latest_version: latest,
57213
- tags: tableTags(entry) ?? null
57645
+ tags: tableTags(entry) ?? null,
57646
+ instructions: tableInstructions(entry) ?? null
57214
57647
  });
57215
57648
  }
57216
57649
  for (const { ref: ref2, view } of catalogViews(model)) {
@@ -57223,7 +57656,8 @@ async function informationRows(app, settings, name) {
57223
57656
  comment: view.description ?? null,
57224
57657
  row_count: null,
57225
57658
  latest_version: null,
57226
- tags: null
57659
+ tags: null,
57660
+ instructions: null
57227
57661
  });
57228
57662
  }
57229
57663
  return out;
@@ -57245,7 +57679,8 @@ async function informationRows(app, settings, name) {
57245
57679
  is_identity: c.identity ? String(c.identity) : "NO",
57246
57680
  is_unique: c.unique ? "YES" : "NO",
57247
57681
  inherited_from: from.get(String(c.name)) ?? null,
57248
- is_hierarchy: c.hierarchy ? "YES" : "NO"
57682
+ is_hierarchy: c.hierarchy ? "YES" : "NO",
57683
+ is_acyclic: c.acyclic ? "YES" : "NO"
57249
57684
  }));
57250
57685
  });
57251
57686
  case "views":
@@ -57259,15 +57694,17 @@ async function informationRows(app, settings, name) {
57259
57694
  labels: view.labels ? JSON.stringify(view.labels) : null
57260
57695
  }));
57261
57696
  case "check_constraints":
57262
- return tables.flatMap(
57263
- ({ ref: ref2, entry }) => Object.entries(entry.constraints ?? {}).map(([n, where]) => ({
57697
+ return tables.flatMap(({ ref: ref2, entry }) => {
57698
+ const hintsOf = constraintHints(entry);
57699
+ return Object.entries(entry.constraints ?? {}).map(([n, where]) => ({
57264
57700
  table_catalog: ref2.catalog,
57265
57701
  table_schema: ref2.schema,
57266
57702
  table_name: ref2.table,
57267
57703
  constraint_name: n,
57268
- check_clause: JSON.stringify(where)
57269
- }))
57270
- );
57704
+ check_clause: JSON.stringify(where),
57705
+ hint: hintsOf[n] ?? null
57706
+ }));
57707
+ });
57271
57708
  }
57272
57709
  }
57273
57710
  async function informationSchemaTableData(app, settings, name) {
@@ -57493,7 +57930,7 @@ async function defineBaseImpl(app, settings, input, opts = {}) {
57493
57930
  const violations = await planInheritedRows(app, settings, next, after, tablesInheriting(next, input.name), rewrite, cs);
57494
57931
  return finish(app, settings, cs, [], [...violations, ...await inheritedLinkViolations(app, settings, cs)], opts);
57495
57932
  }
57496
- var CONSTRAINT_KEYS = ["required", "unique", "min", "max", "step", "values", "ordered", "ref", "generated", "identity", "default", "hierarchy", "ref_where", "ref_by"];
57933
+ var CONSTRAINT_KEYS = ["required", "unique", "min", "max", "step", "values", "ordered", "ref", "generated", "identity", "default", "hierarchy", "acyclic", "ref_where", "ref_by"];
57497
57934
  async function setTableBasesImpl(app, settings, input, opts = {}) {
57498
57935
  const t = await loadTable(app, settings, input.table);
57499
57936
  const cat = requireCatalog(t, "Inheritance");
@@ -57864,8 +58301,8 @@ function groupSelect(t, rows, join, input, known, specs, limit, offset, get) {
57864
58301
  let keys = [[]];
57865
58302
  for (const e of groupExprs) {
57866
58303
  const v = e.eval(q);
57867
- const items = Array.isArray(v) ? [...new Set(v.map((x) => groupDisplay(x)))] : [groupDisplay(v)];
57868
- const options = items.length > 0 ? items : [null];
58304
+ const items2 = Array.isArray(v) ? [...new Set(v.map((x) => groupDisplay(x)))] : [groupDisplay(v)];
58305
+ const options = items2.length > 0 ? items2 : [null];
57869
58306
  keys = keys.flatMap((k) => options.map((o) => [...k, o]));
57870
58307
  }
57871
58308
  for (const key2 of keys) {
@@ -58425,7 +58862,7 @@ function mentionsIn(text5, frag, ix, self) {
58425
58862
  if (matches2.length === 0) continue;
58426
58863
  const inMatch = /* @__PURE__ */ new Set();
58427
58864
  for (const m of matches2) for (let t = m.ti; t <= m.tj; t++) inMatch.add(t);
58428
- const items = [];
58865
+ const items2 = [];
58429
58866
  for (let g = 0; g < matches2.length; ) {
58430
58867
  const group = [matches2[g]];
58431
58868
  let end = matches2[g].end;
@@ -58439,16 +58876,16 @@ function mentionsIn(text5, frag, ix, self) {
58439
58876
  const tj = Math.max(...group.map((m) => m.tj));
58440
58877
  if (longest.start === start && longest.end === end) {
58441
58878
  const kind = longest.self ? "self" : longest.cands.length > 1 ? "same_name" : "clear";
58442
- items.push({ frag, start, end, keys: [longest.key], cands: longest.cands, kind, ti, tj });
58879
+ items2.push({ frag, start, end, keys: [longest.key], cands: longest.cands, kind, ti, tj });
58443
58880
  } else {
58444
58881
  const cands = [...new Set(group.flatMap((m) => m.cands))].sort();
58445
- items.push({ frag, start, end, keys: group.map((m) => m.key), cands, kind: cands.length === 0 ? "self" : "overlap", ti, tj });
58882
+ items2.push({ frag, start, end, keys: group.map((m) => m.key), cands, kind: cands.length === 0 ? "self" : "overlap", ti, tj });
58446
58883
  }
58447
58884
  }
58448
- for (let i = 0; i < items.length; ) {
58885
+ for (let i = 0; i < items2.length; ) {
58449
58886
  let j = i;
58450
- while (j + 1 < items.length && items[j].kind !== "self" && items[j + 1].kind !== "self" && PHRASE_SEP.test(text5.slice(items[j].end, items[j + 1].start))) j++;
58451
- const run = items.slice(i, j + 1);
58887
+ while (j + 1 < items2.length && items2[j].kind !== "self" && items2[j + 1].kind !== "self" && PHRASE_SEP.test(text5.slice(items2[j].end, items2[j + 1].start))) j++;
58888
+ const run = items2.slice(i, j + 1);
58452
58889
  const cands = [...new Set(run.flatMap((m) => m.cands))].sort();
58453
58890
  if (run.length > 1 && cands.length > 1) {
58454
58891
  out.push({ frag, start: run[0].start, end: run.at(-1).end, keys: run.flatMap((m) => m.keys), cands, kind: "adjacent" });
@@ -58681,7 +59118,7 @@ async function verifyUniqueValues(app, settings) {
58681
59118
  const v = VALUES.get(app).get(full);
58682
59119
  const rows = await loadRows(app, settings, t);
58683
59120
  const want = new Map(rows.map((r) => [r.file.path, v.columns.map((c) => uniqueKey(fmGet(r.ordered, c)))]));
58684
- const show = (byPath2) => JSON.stringify([...byPath2].sort(([a], [b]) => a < b ? -1 : 1));
59121
+ const show = (byPath3) => JSON.stringify([...byPath3].sort(([a], [b]) => a < b ? -1 : 1));
58685
59122
  if (show(v.byPath) !== show(want)) out.push(`unique values of ${full} differ from its rows`);
58686
59123
  const byKey = v.columns.map((_, i) => {
58687
59124
  const m = /* @__PURE__ */ new Map();
@@ -59464,6 +59901,7 @@ init_path_templates();
59464
59901
  init_computed_columns();
59465
59902
  init_where();
59466
59903
  init_message();
59904
+ init_hints();
59467
59905
  init_descriptions();
59468
59906
  function escapeRegExp(s) {
59469
59907
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -59478,7 +59916,7 @@ function archivedRowFiles(app, settings, t) {
59478
59916
  return rowTableFull(model, fm?.[settings.tableKey]) === full;
59479
59917
  });
59480
59918
  }
59481
- async function relocateTable(app, settings, t, dest, changes, opts, properties = {}, rowTemplate2, tags, fields = {}) {
59919
+ async function relocateTable(app, settings, t, dest, changes, opts, properties = {}, rowTemplate2, tags, fields = {}, instructions) {
59482
59920
  const conflicts = [];
59483
59921
  const merged = { ...serializeOptions(t.def.options)[0] };
59484
59922
  for (const [k, v] of Object.entries(changes)) {
@@ -59518,6 +59956,8 @@ async function relocateTable(app, settings, t, dest, changes, opts, properties =
59518
59956
  }
59519
59957
  if (rowTemplate2 === null || typeof rowTemplate2 === "string" && rowTemplate2.trim() === "") delete entry.row_template;
59520
59958
  else if (typeof rowTemplate2 === "string") entry.row_template = rowTemplate2;
59959
+ if (instructions === null || typeof instructions === "string" && instructions.trim() === "") delete entry.instructions;
59960
+ else if (typeof instructions === "string") entry.instructions = instructions;
59521
59961
  const after = setTableEntries(text5, [...t.catalog.ref.full !== ref2.full ? [[t.catalog.ref, null]] : [], [ref2, tags === void 0 ? entry : withTableTags(entry, tags)]]);
59522
59962
  const from = t.catalog.ref.full;
59523
59963
  cs = emptyChangeSet("alter_table", from === ref2.full ? `alter table ${from}` : `move table ${from} to ${ref2.full}`, catalogTablePath(settings, ref2.full));
@@ -59612,6 +60052,9 @@ function fieldsInput(t, settings, value) {
59612
60052
  return out;
59613
60053
  }
59614
60054
  async function alterTableImpl(app, settings, input, opts = {}) {
60055
+ if (input.instructions !== void 0 && input.instructions !== null && typeof input.instructions !== "string") {
60056
+ throw new OperationInputError(`"instructions" must be text: how to fill a row of the table`, guide(`Pass instructions as Markdown text, e.g. "1. Ask which film and when.", or leave them out; "" removes them.`, "instructions"));
60057
+ }
59615
60058
  const t = await loadTable(app, settings, input.table);
59616
60059
  const cfg = await readDatabaseConfig(app, settings);
59617
60060
  const schema = assertIdentifier("schema", input.schema ?? t.def.schema);
@@ -59661,7 +60104,7 @@ async function alterTableImpl(app, settings, input, opts = {}) {
59661
60104
  return relocateTable(app, settings, t, { catalogRef: refOf(catalog, schema, name) }, changes, opts, {
59662
60105
  ...input.history !== void 0 ? { history: input.history } : {},
59663
60106
  ...input.autolinkTarget !== void 0 ? { autolink_target: input.autolinkTarget } : {}
59664
- }, input.rowTemplate, tags, fields);
60107
+ }, input.rowTemplate, tags, fields, input.instructions);
59665
60108
  }
59666
60109
  var alterTable = instrumented("alter_table", alterTableImpl, (i) => ({ table: i.table, schema: i.schema, name: i.name }));
59667
60110
 
@@ -60743,13 +61186,13 @@ function inferColumns(app, settings, observed, files, maxEnum) {
60743
61186
  if (kind === null && kinds.has("list")) kind = "list";
60744
61187
  if (kind === null) kind = "string";
60745
61188
  if (kind === "list") {
60746
- const items = filled.flatMap((x) => Array.isArray(x.value) ? x.value : [x.value]).filter((v) => !isEmpty(v));
60747
- const itemKind = mergeKinds(new Set(items.map(kindOf))) ?? "string";
61189
+ const items2 = filled.flatMap((x) => Array.isArray(x.value) ? x.value : [x.value]).filter((v) => !isEmpty(v));
61190
+ const itemKind = mergeKinds(new Set(items2.map(kindOf))) ?? "string";
60748
61191
  col.type = "list";
60749
- const itemDistinct = [...new Set(items.map((v) => String(v).trim()))];
60750
- if (itemKind === "string" && itemDistinct.length > 0 && itemDistinct.length <= maxEnum && items.length >= 3 && itemDistinct.length * 2 <= items.length) {
61192
+ const itemDistinct = [...new Set(items2.map((v) => String(v).trim()))];
61193
+ if (itemKind === "string" && itemDistinct.length > 0 && itemDistinct.length <= maxEnum && items2.length >= 3 && itemDistinct.length * 2 <= items2.length) {
60751
61194
  col.items = "enum";
60752
- col.values = frequencyOrder(items.map((v) => String(v).trim()));
61195
+ col.values = frequencyOrder(items2.map((v) => String(v).trim()));
60753
61196
  } else {
60754
61197
  col.items = itemKind === "list" ? "string" : itemKind;
60755
61198
  }
@@ -60990,6 +61433,8 @@ async function describeTable(app, settings, table) {
60990
61433
  const o = def.options;
60991
61434
  const e = t.catalog.entry;
60992
61435
  const tags = tableTags(e);
61436
+ const hints = constraintHints(e);
61437
+ const instructions = tableInstructions(e);
60993
61438
  const missing = missingDescriptions(o.description, def.columns);
60994
61439
  return {
60995
61440
  qualified: t.catalog.ref.full,
@@ -61012,6 +61457,8 @@ async function describeTable(app, settings, table) {
61012
61457
  ...missing ? { missing_descriptions: missing } : {},
61013
61458
  ...e.bases && e.bases.length > 0 ? { bases: [...e.bases] } : {},
61014
61459
  ...e.constraints && Object.keys(e.constraints).length > 0 ? { constraints: e.constraints } : {},
61460
+ ...Object.keys(hints).length > 0 ? { constraint_hints: hints } : {},
61461
+ ...instructions !== void 0 ? { instructions } : {},
61015
61462
  ...typeof e.row_template === "string" ? { row_template: e.row_template } : {},
61016
61463
  ...typeof e.options?.history === "boolean" ? { history: e.options.history } : {},
61017
61464
  ...typeof e.options?.autolink_target === "boolean" ? { autolink_target: e.options.autolink_target } : {},
@@ -61476,17 +61923,17 @@ var SemanticIndex = class {
61476
61923
  const staged = /* @__PURE__ */ new Map();
61477
61924
  const empty = new Int8Array(0);
61478
61925
  const flush2 = async () => {
61479
- const items = queue.filter((x) => staged.has(x.path));
61926
+ const items2 = queue.filter((x) => staged.has(x.path));
61480
61927
  queue = [];
61481
- if (items.length === 0) return;
61928
+ if (items2.length === 0) return;
61482
61929
  try {
61483
- const vectors = await embed(this.transport, cfg, items.map((x) => x.text));
61484
- items.forEach((x, j) => {
61930
+ const vectors = await embed(this.transport, cfg, items2.map((x) => x.text));
61931
+ items2.forEach((x, j) => {
61485
61932
  staged.get(x.path).vectors[x.i] = normalize(vectors[j]);
61486
61933
  });
61487
61934
  } catch (e) {
61488
61935
  const error2 = messageOf(e);
61489
- for (const x of new Set(items.map((y) => y.path))) {
61936
+ for (const x of new Set(items2.map((y) => y.path))) {
61490
61937
  staged.delete(x);
61491
61938
  result.failed.push({ path: x, error: error2 });
61492
61939
  this.pending.add(x);
@@ -62781,12 +63228,12 @@ var ChangeFeed = class {
62781
63228
  };
62782
63229
  });
62783
63230
  try {
62784
- const { byPath: byPath2, tableOf } = await checkExternalEdits(this.app, s, edits);
63231
+ const { byPath: byPath3, tableOf } = await checkExternalEdits(this.app, s, edits);
62785
63232
  const rows = /* @__PURE__ */ new Set();
62786
63233
  const tables = /* @__PURE__ */ new Set();
62787
63234
  let n = 0;
62788
63235
  for (const [path19, i] of last) {
62789
- const violations = byPath2.get(path19) ?? [];
63236
+ const violations = byPath3.get(path19) ?? [];
62790
63237
  out[i] = { ...out[i], ...capped(violations) };
62791
63238
  n += violations.length;
62792
63239
  for (const v of violations) {
@@ -63837,6 +64284,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
63837
64284
  if (target !== void 0) options2.autolink_target = target;
63838
64285
  const history2 = optBool(b, "history");
63839
64286
  const template = text3(b, "row_template");
64287
+ const instructions = text3(b, "instructions");
63840
64288
  return opResponse(
63841
64289
  await createTable(
63842
64290
  app,
@@ -63853,6 +64301,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
63853
64301
  ...obj(b, "fields") ? { fields: obj(b, "fields") } : {},
63854
64302
  columns: list(b, "columns"),
63855
64303
  ...template !== void 0 ? { rowTemplate: template } : {},
64304
+ ...instructions !== void 0 ? { instructions } : {},
63856
64305
  ...obj(b, "tags") ? { tags: obj(b, "tags") } : {}
63857
64306
  },
63858
64307
  runOpts(b)
@@ -63869,6 +64318,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
63869
64318
  const target = b.autolink_target;
63870
64319
  const description = text3(b, "description");
63871
64320
  const template = text3(b, "row_template");
64321
+ const instructions = text3(b, "instructions");
63872
64322
  const archive = text3(b, "archive");
63873
64323
  const partition = text3(b, "partition");
63874
64324
  return opResponse(
@@ -63889,6 +64339,8 @@ function createHandlers(app, getSettings, saveSettings = async () => {
63889
64339
  ...target === "default" ? { autolinkTarget: null } : typeof target === "boolean" ? { autolinkTarget: target } : {},
63890
64340
  // "" removes the template
63891
64341
  ...template !== void 0 ? { rowTemplate: template === "" ? null : template } : {},
64342
+ // "" removes the instructions
64343
+ ...instructions !== void 0 ? { instructions: instructions === "" ? null : instructions } : {},
63892
64344
  ...obj(b, "tags") ? { tags: obj(b, "tags") } : {},
63893
64345
  ...optBool(b, "clear_tags") === true ? { clearTags: true } : {},
63894
64346
  ...archive !== void 0 ? { archive } : {},
@@ -63936,7 +64388,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
63936
64388
  );
63937
64389
  },
63938
64390
  set_table_bases: async (b) => opResponse(await setTableBases(app, s(), { table: str(b, "table"), bases: strings(b, "bases") }, runOpts(b))),
63939
- add_constraint: async (b) => opResponse(await addConstraint(app, s(), { table: str(b, "table"), name: str(b, "name"), check: obj(b, "check") }, runOpts(b))),
64391
+ add_constraint: async (b) => opResponse(await addConstraint(app, s(), { table: str(b, "table"), name: str(b, "name"), check: obj(b, "check"), ...text3(b, "hint") !== void 0 ? { hint: text3(b, "hint") } : {} }, runOpts(b))),
63940
64392
  drop_constraint: async (b) => opResponse(await dropConstraint(app, s(), { table: str(b, "table"), name: str(b, "name") }, runOpts(b))),
63941
64393
  create_view: async (b) => opResponse(
63942
64394
  await createView(
@@ -71693,7 +72145,7 @@ function setUp(o, vault, home, explicit, self) {
71693
72145
  // src/node/service.ts
71694
72146
  init_local();
71695
72147
  init_hints();
71696
- var VERSION = true ? "3.1.0" : "dev";
72148
+ var VERSION = true ? "3.2.0" : "dev";
71697
72149
  function parsePort(raw, from = "--port") {
71698
72150
  const n = /^\s*\d+\s*$/.test(raw) ? Number(raw) : NaN;
71699
72151
  if (n === 0 || Number.isInteger(n) && n >= 1024 && n <= 65535) return n;
@@ -72731,7 +73183,7 @@ Docs: https://datanotes.dev`;
72731
73183
 
72732
73184
  // src/node/cli.ts
72733
73185
  init_message();
72734
- var VERSION2 = true ? "3.1.0" : "dev";
73186
+ var VERSION2 = true ? "3.2.0" : "dev";
72735
73187
  var consoleError = console.error;
72736
73188
  async function main5(argv = process.argv.slice(2)) {
72737
73189
  const [command, ...rest] = argv;