@mulmoclaude/core 0.23.0 → 0.24.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 (53) hide show
  1. package/assets/helps/collection-skills.md +39 -7
  2. package/assets/helps/custom-view.md +13 -9
  3. package/assets/skills-preset/mc-zenn/SKILL.md +31 -0
  4. package/dist/collection/core/completion.d.ts +18 -0
  5. package/dist/collection/core/deriveAll.d.ts +14 -9
  6. package/dist/collection/core/schema.d.ts +9 -4
  7. package/dist/collection/core/schemaZ.d.ts +97 -43
  8. package/dist/collection/index.cjs +2 -1
  9. package/dist/collection/index.cjs.map +1 -1
  10. package/dist/collection/index.d.ts +1 -0
  11. package/dist/collection/index.js +2 -2
  12. package/dist/collection/index.js.map +1 -1
  13. package/dist/collection/registry/server/index.cjs +1 -1
  14. package/dist/collection/registry/server/index.js +1 -1
  15. package/dist/collection/server/csvQuery.d.ts +13 -5
  16. package/dist/collection/server/csvStore.d.ts +2 -0
  17. package/dist/collection/server/index.cjs +4 -2
  18. package/dist/collection/server/index.d.ts +2 -1
  19. package/dist/collection/server/index.js +3 -3
  20. package/dist/collection/server/jsonlQuery.d.ts +4 -0
  21. package/dist/collection-watchers/index.cjs +5 -14
  22. package/dist/collection-watchers/index.cjs.map +1 -1
  23. package/dist/collection-watchers/index.js +2 -11
  24. package/dist/collection-watchers/index.js.map +1 -1
  25. package/dist/collection-watchers/reconciler.d.ts +2 -4
  26. package/dist/feeds/index.cjs +2 -2
  27. package/dist/feeds/index.js +2 -2
  28. package/dist/feeds/server/index.cjs +3 -3
  29. package/dist/feeds/server/index.js +3 -3
  30. package/dist/google/auth.d.ts +10 -1
  31. package/dist/google/authFlow.d.ts +1 -0
  32. package/dist/google/index.cjs +79 -38
  33. package/dist/google/index.cjs.map +1 -1
  34. package/dist/google/index.d.ts +1 -1
  35. package/dist/google/index.js +78 -39
  36. package/dist/google/index.js.map +1 -1
  37. package/dist/{ingestTypes-B-dXxUF9.js → ingestTypes-CFEgTItj.js} +2 -2
  38. package/dist/{ingestTypes-B-dXxUF9.js.map → ingestTypes-CFEgTItj.js.map} +1 -1
  39. package/dist/{ingestTypes-Cev9q77C.cjs → ingestTypes-Stc7iQyA.cjs} +2 -2
  40. package/dist/{ingestTypes-Cev9q77C.cjs.map → ingestTypes-Stc7iQyA.cjs.map} +1 -1
  41. package/dist/{promptSafety-rDWA9_JS.js → promptSafety-BLUcImzd.js} +54 -26
  42. package/dist/promptSafety-BLUcImzd.js.map +1 -0
  43. package/dist/{promptSafety-DRd15gQ6.cjs → promptSafety-DI_1Rvhk.cjs} +59 -25
  44. package/dist/promptSafety-DI_1Rvhk.cjs.map +1 -0
  45. package/dist/{server--FgDORd3.js → server-BpEo0FOd.js} +233 -57
  46. package/dist/server-BpEo0FOd.js.map +1 -0
  47. package/dist/{server-Q7ld-FlZ.cjs → server-CkysfLh4.cjs} +244 -56
  48. package/dist/server-CkysfLh4.cjs.map +1 -0
  49. package/package.json +1 -1
  50. package/dist/promptSafety-DRd15gQ6.cjs.map +0 -1
  51. package/dist/promptSafety-rDWA9_JS.js.map +0 -1
  52. package/dist/server--FgDORd3.js.map +0 -1
  53. package/dist/server-Q7ld-FlZ.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
- const require_promptSafety = require("./promptSafety-DRd15gQ6.cjs");
2
+ const require_promptSafety = require("./promptSafety-DI_1Rvhk.cjs");
3
3
  const require_collection_paths = require("./collection/paths.cjs");
4
4
  const require_skill_bridge_index = require("./skill-bridge/index.cjs");
5
5
  let node_path = require("node:path");
@@ -795,17 +795,18 @@ function whereFragment(cond) {
795
795
  params: [cond.value]
796
796
  };
797
797
  }
798
- /** Compile a validated query. Returns the SQL (whose FIRST placeholder is
799
- * the CSV path — the executor binds it) and the where-value parameters
800
- * that follow it. Callers MUST have run `CollectionQueryZ` first; this
801
- * function trusts the shape (aliases already charset-checked, orderBy
802
- * membership already enforced). */
803
- function compileCsvQuery(query, primaryKey) {
798
+ /** Compile a validated query against `fromSql` (a table-function call
799
+ * whose FIRST placeholder is the source path — the executor binds it).
800
+ * Returns the SQL and the where-value parameters that follow the path.
801
+ * Callers MUST have run `CollectionQueryZ` first; this function trusts
802
+ * the shape (aliases already charset-checked, orderBy membership already
803
+ * enforced). */
804
+ function compileQuery(query, fromSql) {
804
805
  const groupBy = query.groupBy ?? [];
805
806
  const aggregates = Object.entries(query.aggregates ?? {});
806
807
  const selectList = [...groupBy.map(quoteIdent), ...aggregates.map(([alias, aggregate]) => `${aggregateExpr(aggregate)} AS ${quoteIdent(alias)}`)];
807
808
  const where = (query.where ?? []).map(whereFragment);
808
- const clauses = [`SELECT ${selectList.join(", ")}`, `FROM read_csv(${readCsvArgs(primaryKey)})`];
809
+ const clauses = [`SELECT ${selectList.join(", ")}`, `FROM ${fromSql}`];
809
810
  if (where.length > 0) clauses.push(`WHERE ${where.map((fragment) => fragment.sql).join(" AND ")}`);
810
811
  if (groupBy.length > 0) clauses.push(`GROUP BY ${groupBy.map(quoteIdent).join(", ")}`);
811
812
  const orderBy = (query.orderBy ?? []).map((order) => quoteIdent(order.field) + (order.dir === "desc" ? " DESC" : " ASC"));
@@ -816,6 +817,21 @@ function compileCsvQuery(query, primaryKey) {
816
817
  params: where.flatMap((fragment) => fragment.params)
817
818
  };
818
819
  }
820
+ /** Compile against a CSV file (the dataSource store's engine). */
821
+ function compileCsvQuery(query, primaryKey) {
822
+ return compileQuery(query, `read_csv(${readCsvArgs(primaryKey)})`);
823
+ }
824
+ /** Compile against a JSONL file of ENRICHED records — the file-backed
825
+ * collections' engine (see `jsonlQuery.ts`). No VARCHAR key pin needed:
826
+ * enriched record ids are already strings. `sample_size=-1` makes the
827
+ * schema inference scan EVERY line — with the default sample, a sparse
828
+ * optional/derived field first appearing past the sample would not be
829
+ * inferred as a column and the query would binder-error on it (Codex P2
830
+ * on #2165). The full scan costs nothing extra here: aggregation reads
831
+ * the whole file anyway. */
832
+ function compileJsonlQuery(query) {
833
+ return compileQuery(query, `read_json(?, format='newline_delimited', sample_size=-1)`);
834
+ }
819
835
  //#endregion
820
836
  //#region src/collection/server/csvStore.ts
821
837
  /** `list()` row cap. Over-cap files are truncated with a warn — the v1
@@ -1136,6 +1152,74 @@ function storeFor(collection, opts = {}) {
1136
1152
  };
1137
1153
  }
1138
1154
  //#endregion
1155
+ //#region src/collection/server/jsonlQuery.ts
1156
+ /** SQL semantics for an aggregate-only query over ZERO rows: one scalar
1157
+ * row (`count` = 0, everything else NULL) — the same shape the CSV path
1158
+ * produces for a header-only file, so callers reading `rows[0]` never
1159
+ * break on storage kind or emptiness. A grouped query over zero rows is
1160
+ * zero groups (`[]`), also matching SQL. Synthesized here because DuckDB
1161
+ * has no empty file to infer a schema from. */
1162
+ function emptyCollectionResult(query) {
1163
+ if ((query.groupBy?.length ?? 0) > 0) return [];
1164
+ const aggregates = Object.entries(query.aggregates ?? {});
1165
+ return [Object.fromEntries(aggregates.map(([alias, aggregate]) => [alias, aggregate.op === "count" ? 0 : null]))];
1166
+ }
1167
+ /** The SOURCE columns a query reads: groupBy columns, aggregate input
1168
+ * columns, and where fields. (orderBy resolves to groupBy columns or
1169
+ * aggregate ALIASES — never to a new source column — so it adds none.) */
1170
+ function referencedSourceColumns(query) {
1171
+ const columns = new Set(query.groupBy ?? []);
1172
+ for (const aggregate of Object.values(query.aggregates ?? {})) if (aggregate.column !== void 0) columns.add(aggregate.column);
1173
+ for (const cond of query.where ?? []) columns.add(cond.field);
1174
+ return [...columns];
1175
+ }
1176
+ /** Referenced columns that appear in NO row — a freshly-added optional
1177
+ * field, or a derived field whose inputs no record has yet. DuckDB only
1178
+ * infers columns from keys that occur in the JSONL, so without help a
1179
+ * valid query on such a field binder-errors here while the CSV path
1180
+ * (whose header always declares the column) returns NULLs. Padding the
1181
+ * FIRST line with `col: null` makes the column exist (full-scan
1182
+ * inference unions keys across lines) with matching NULL semantics. */
1183
+ function absentReferencedColumns(rows, query) {
1184
+ const present = /* @__PURE__ */ new Set();
1185
+ for (const row of rows) for (const key of Object.keys(row)) present.add(key);
1186
+ return referencedSourceColumns(query).filter((column) => !present.has(column));
1187
+ }
1188
+ /** Run a validated query over enriched collection rows. */
1189
+ async function runQueryOverRows(rows, query) {
1190
+ if (rows.length === 0) return emptyCollectionResult(query);
1191
+ await (0, node_fs_promises.mkdir)(cacheDir(), {
1192
+ recursive: true,
1193
+ mode: 448
1194
+ });
1195
+ const jsonlPath = node_path.default.join(cacheDir(), `q-${(0, node_crypto.randomBytes)(8).toString("hex")}.jsonl`);
1196
+ try {
1197
+ const nullPads = Object.fromEntries(absentReferencedColumns(rows, query).map((column) => [column, null]));
1198
+ const handle = await (0, node_fs_promises.open)(jsonlPath, "wx", 384);
1199
+ try {
1200
+ let first = true;
1201
+ for (const row of rows) {
1202
+ await handle.write(`${JSON.stringify(first ? {
1203
+ ...nullPads,
1204
+ ...row
1205
+ } : row)}\n`);
1206
+ first = false;
1207
+ }
1208
+ } finally {
1209
+ await handle.close();
1210
+ }
1211
+ const { sql, params } = compileJsonlQuery(query);
1212
+ return (await queryCsv(sql, [jsonlPath, ...params])).map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));
1213
+ } finally {
1214
+ await (0, node_fs_promises.unlink)(jsonlPath).catch((err) => {
1215
+ if (err.code !== "ENOENT") log.warn("collections", "temp JSONL cleanup failed", {
1216
+ path: jsonlPath,
1217
+ error: String(err)
1218
+ });
1219
+ });
1220
+ }
1221
+ }
1222
+ //#endregion
1139
1223
  //#region src/collection/core/recordZ.ts
1140
1224
  /** The emptiness rule shared by `required` and the "only check present
1141
1225
  * values" gate. NOT a truthiness check — `0` and `false` are filled. */
@@ -1421,6 +1505,32 @@ var WhenZ = zod.z.object({
1421
1505
  field: zod.z.string().trim().min(1),
1422
1506
  in: zod.z.array(zod.z.string().trim().min(1)).min(1)
1423
1507
  });
1508
+ var ValueRefZ = zod.z.object({
1509
+ record: zod.z.string().trim().min(1).optional(),
1510
+ field: zod.z.string().trim().min(1)
1511
+ });
1512
+ var WhereCondZ = zod.z.object({
1513
+ field: zod.z.string().trim().min(1),
1514
+ op: zod.z.enum([
1515
+ "eq",
1516
+ "ne",
1517
+ "in",
1518
+ "gt",
1519
+ "gte",
1520
+ "lt",
1521
+ "lte",
1522
+ "contains"
1523
+ ]),
1524
+ value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
1525
+ valueFrom: ValueRefZ.optional()
1526
+ }).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
1527
+ message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
1528
+ path: ["value"]
1529
+ }).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
1530
+ message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
1531
+ path: ["value"]
1532
+ });
1533
+ var WhereZ = zod.z.array(WhereCondZ);
1424
1534
  var fieldBase = {
1425
1535
  label: zod.z.string().min(1),
1426
1536
  primary: zod.z.boolean().optional(),
@@ -1631,6 +1741,30 @@ var ToggleFieldZ = zod.z.object({
1631
1741
  onValue: zod.z.string().trim().min(1),
1632
1742
  offValue: zod.z.string().trim().min(1)
1633
1743
  });
1744
+ /** A computed boolean: the record matched against a `where` predicate
1745
+ * (the same AND-of-conditions shape `dynamicIcon` uses; evaluated by
1746
+ * `./where`'s `matchesWhere`). Generic state summaries — isDone,
1747
+ * isPassed, isQualified — become declared fields, so every
1748
+ * field-driven mechanism (table cells, detail view, completion via
1749
+ * `completionField`) picks them up without special cases. Never
1750
+ * stored; computed by `deriveAll` on both server and client in the
1751
+ * same saturation loop as `derived`, so a flag may read derived /
1752
+ * rollup values — and other flags (`matchesWhere` stringifies, so
1753
+ * `eq "true"` composes). Deliberately a STRUCTURED field, not formula
1754
+ * syntax — the derived evaluator's no-string-literals boundary stays
1755
+ * untouched (same reasoning as `rollup`). Cross-record
1756
+ * `valueFrom.record` is rejected here: per-record evaluation has no
1757
+ * `recordsById`, so it could only ever silently never-match.
1758
+ * `where` condition fields are validated to exist by a schema-level
1759
+ * refine below (a field can't see its siblings). */
1760
+ var FlagFieldZ = zod.z.object({
1761
+ type: zod.z.literal("flag"),
1762
+ ...fieldBase,
1763
+ where: WhereZ.min(1)
1764
+ }).refine((spec) => spec.where.every((cond) => cond.valueFrom?.record === void 0), {
1765
+ message: "a flag's `where` cannot use `valueFrom.record` (cross-record references are unresolvable in per-record evaluation); use a literal `value` or a same-record `valueFrom` (field-to-field)",
1766
+ path: ["where"]
1767
+ });
1634
1768
  var FieldSpecZ = zod.z.discriminatedUnion("type", [
1635
1769
  ScalarFieldZ,
1636
1770
  RefFieldZ,
@@ -1641,7 +1775,8 @@ var FieldSpecZ = zod.z.discriminatedUnion("type", [
1641
1775
  EmbedFieldZ,
1642
1776
  BacklinksFieldZ,
1643
1777
  RollupFieldZ,
1644
- ToggleFieldZ
1778
+ ToggleFieldZ,
1779
+ FlagFieldZ
1645
1780
  ]);
1646
1781
  var actionBase = {
1647
1782
  id: zod.z.string().trim().min(1),
@@ -1790,32 +1925,6 @@ var AgentIngestZ = zod.z.object({
1790
1925
  * discovered from `<workspace>/feeds/` are REQUIRED to carry it (gated by
1791
1926
  * `acceptParsedSchema`). */
1792
1927
  var IngestZ = zod.z.discriminatedUnion("kind", [DeclarativeIngestZ, AgentIngestZ]);
1793
- var ValueRefZ = zod.z.object({
1794
- record: zod.z.string().trim().min(1).optional(),
1795
- field: zod.z.string().trim().min(1)
1796
- });
1797
- var WhereCondZ = zod.z.object({
1798
- field: zod.z.string().trim().min(1),
1799
- op: zod.z.enum([
1800
- "eq",
1801
- "ne",
1802
- "in",
1803
- "gt",
1804
- "gte",
1805
- "lt",
1806
- "lte",
1807
- "contains"
1808
- ]),
1809
- value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
1810
- valueFrom: ValueRefZ.optional()
1811
- }).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
1812
- message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
1813
- path: ["value"]
1814
- }).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
1815
- message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
1816
- path: ["value"]
1817
- });
1818
- var WhereZ = zod.z.array(WhereCondZ);
1819
1928
  var DynamicIconSourceZ = zod.z.object({
1820
1929
  collection: zod.z.string().trim().min(1),
1821
1930
  from: zod.z.enum([
@@ -1910,12 +2019,12 @@ function fieldDrivenFromFieldCarried(schema) {
1910
2019
  * workspace-relative and containment-checked exactly like `dataPath`. The
1911
2020
  * row-id column is the schema's existing `primaryKey` — there is
1912
2021
  * deliberately no second key concept here.
1913
- * See plans/feat-collection-csv-duckdb-source.md. */
2022
+ * See plans/done/feat-collection-csv-duckdb-source.md. */
1914
2023
  var DataSourceZ = zod.z.object({
1915
2024
  type: zod.z.literal("csv"),
1916
2025
  path: zod.z.string().min(1)
1917
2026
  });
1918
- var CollectionSchemaZ = zod.z.object({
2027
+ var BareCollectionSchemaZ = zod.z.object({
1919
2028
  title: zod.z.string().min(1),
1920
2029
  icon: zod.z.string().min(1),
1921
2030
  dataPath: zod.z.string().min(1).optional(),
@@ -1975,8 +2084,11 @@ var CollectionSchemaZ = zod.z.object({
1975
2084
  }).refine((schema) => collectCurrencyFieldRefs(schema.fields).every((name) => CODE_FIELD_TYPES.has(schema.fields[name]?.type ?? "")), {
1976
2085
  message: "a money field's `currencyField` must name a top-level `string`, `text`, or `enum` field that holds the currency code",
1977
2086
  path: ["fields"]
1978
- }).refine((schema) => schema.completionField === void 0 === (schema.completionDoneValues === void 0), {
1979
- message: "schema `completionField` and `completionDoneValues` must be declared together (both set, or both omitted)",
2087
+ }).refine((schema) => {
2088
+ if (schema.completionField !== void 0 && schema.fields[schema.completionField]?.type === "flag") return schema.completionDoneValues === void 0;
2089
+ return schema.completionField === void 0 === (schema.completionDoneValues === void 0);
2090
+ }, {
2091
+ message: "schema `completionField` and `completionDoneValues` must be declared together (both set, or both omitted) — unless `completionField` names a `flag` field, in which case `completionDoneValues` must be omitted (done ⇔ the flag matches)",
1980
2092
  path: ["completionField"]
1981
2093
  }).refine((schema) => schema.completionField === void 0 || schema.fields[schema.completionField] !== void 0, {
1982
2094
  message: "schema `completionField` must name a top-level field declared in `fields`",
@@ -1987,6 +2099,22 @@ var CollectionSchemaZ = zod.z.object({
1987
2099
  }).refine((schema) => Object.values(schema.fields).every((field) => field.when === void 0 || schema.fields[field.when.field] !== void 0), {
1988
2100
  message: "a field's `when.field` must name a top-level field declared in `fields`",
1989
2101
  path: ["fields"]
2102
+ }).refine((schema) => Object.values(schema.fields).every((field) => field.type !== "flag" || field.where.every((cond) => schema.fields[cond.field] !== void 0 && (cond.valueFrom === void 0 || schema.fields[cond.valueFrom.field] !== void 0))), {
2103
+ message: "a flag field's `where` conditions must name top-level fields declared in `fields` (both `field` and a same-record `valueFrom.field`)",
2104
+ path: ["fields"]
2105
+ }).refine((schema) => {
2106
+ const spec = schema.completionField === void 0 ? void 0 : schema.fields[schema.completionField];
2107
+ if (spec?.type !== "flag") return true;
2108
+ return spec.where.every((cond) => [cond.field, ...cond.valueFrom ? [cond.valueFrom.field] : []].every((name) => {
2109
+ const target = schema.fields[name];
2110
+ return target !== void 0 && !require_promptSafety.COMPUTED_TYPES.has(target.type);
2111
+ }));
2112
+ }, {
2113
+ message: "a `flag` named by `completionField` may only reference STORED fields in its `where` — completion is evaluated against the raw record (before deriveAll), where computed values (derived/rollup/toggle/flag/embed/backlinks) are absent",
2114
+ path: ["completionField"]
2115
+ }).refine((schema) => schema.spawn === void 0 || schema.spawn.when !== void 0 || schema.fields[schema.completionField ?? ""]?.type !== "flag", {
2116
+ message: "a schema whose `completionField` names a `flag` field must declare an explicit `spawn.when` (the spawn-inert check cannot statically evaluate a flag's `where`)",
2117
+ path: ["spawn"]
1990
2118
  }).refine((schema) => Object.values(schema.fields).every((field) => {
1991
2119
  if (field.type !== "embed" || field.idField === void 0) return true;
1992
2120
  const target = schema.fields[field.idField];
@@ -2061,6 +2189,46 @@ var CollectionSchemaZ = zod.z.object({
2061
2189
  message: "schema `views` must have unique `id`s",
2062
2190
  path: ["views"]
2063
2191
  });
2192
+ var PROTOTYPE_KEYS = [
2193
+ "__proto__",
2194
+ "constructor",
2195
+ "prototype"
2196
+ ];
2197
+ /** The first own prototype-sensitive key of `value`, or null. */
2198
+ function ownPrototypeKey(value) {
2199
+ if (value === null || typeof value !== "object") return null;
2200
+ for (const key of PROTOTYPE_KEYS) if (Object.hasOwn(value, key)) return key;
2201
+ return null;
2202
+ }
2203
+ /** Dotted path of the first prototype-sensitive field name in the raw
2204
+ * schema input — top-level `fields`, each table field's `of`, and each
2205
+ * action's `params` (the three records that DEFINE names) — or null. */
2206
+ function prototypeFieldKeyPath(input) {
2207
+ if (input === null || typeof input !== "object") return null;
2208
+ const schema = input;
2209
+ const bad = ownPrototypeKey(schema.fields);
2210
+ if (bad !== null) return `fields.${bad}`;
2211
+ for (const [key, spec] of Object.entries(schema.fields ?? {})) {
2212
+ const badSub = ownPrototypeKey(spec?.of);
2213
+ if (badSub !== null) return `fields.${key}.of.${badSub}`;
2214
+ }
2215
+ for (const [listName, list] of [["actions", schema.actions], ["collectionActions", schema.collectionActions]]) for (const action of Array.isArray(list) ? list : []) {
2216
+ const badParam = ownPrototypeKey(action?.params);
2217
+ if (badParam !== null) return `${listName}.params.${badParam}`;
2218
+ }
2219
+ return null;
2220
+ }
2221
+ var CollectionSchemaZ = zod.z.preprocess((input, ctx) => {
2222
+ const bad = prototypeFieldKeyPath(input);
2223
+ if (bad !== null) {
2224
+ ctx.addIssue({
2225
+ code: "custom",
2226
+ message: `'${bad}': field names must not be prototype-sensitive keys (\`__proto__\`, \`constructor\`, \`prototype\`)`
2227
+ });
2228
+ return zod.z.NEVER;
2229
+ }
2230
+ return input;
2231
+ }, BareCollectionSchemaZ);
2064
2232
  //#endregion
2065
2233
  //#region src/collection/server/discovery.ts
2066
2234
  function applyFeedSchemaDefaults(parsed, slug) {
@@ -2621,17 +2789,15 @@ function successorId(sourceId, next) {
2621
2789
  }
2622
2790
  /** True iff `item` satisfies the spawn predicate. With an explicit
2623
2791
  * `when`, matches `String(item[when.field]) ∈ when.in`. Without one,
2624
- * defaults to the completion-done condition. Self-contained (no import
2625
- * from notifications.ts) to keep the module graph acyclic. */
2792
+ * defaults to the completion-done condition the shared, flag-aware
2793
+ * `itemIsDone` (core/completion; downhill import, so the module graph
2794
+ * stays acyclic). */
2626
2795
  function matchesWhen(when, schema, item) {
2627
2796
  if (when) {
2628
2797
  const raw = item[when.field];
2629
2798
  return raw !== void 0 && raw !== null && when.in.includes(String(raw));
2630
2799
  }
2631
- const { completionField, completionDoneValues } = schema;
2632
- if (!completionField || !completionDoneValues) return false;
2633
- const raw = item[completionField];
2634
- return raw !== void 0 && raw !== null && completionDoneValues.includes(String(raw));
2800
+ return require_promptSafety.itemIsDone(schema, item);
2635
2801
  }
2636
2802
  /** Resolve the literal `every` that applies to `sourceItem`. Literal-arm
2637
2803
  * `spawn.every` passes through unchanged. Field-driven `spawn.every` reads
@@ -3176,17 +3342,27 @@ async function putOneItem(collection, record, mode, deps) {
3176
3342
  if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
3177
3343
  return reject(itemId, "write refused: the collection's data dir escapes the workspace");
3178
3344
  }
3179
- /** Aggregation over a dataSource collection's WHOLE file via the
3180
- * structured query DSL (`core/queryZ.ts`) — the paved road for counts /
3181
- * sums / group-bys that `getItems` (row-capped, unaggregated) can't
3182
- * answer honestly. File-backed collections have no query engine yet:
3183
- * refuse with a pointer instead of silently emulating. */
3345
+ /** Aggregation over a collection via the structured query DSL
3346
+ * (`core/queryZ.ts`) — the paved road for counts / sums / group-bys
3347
+ * that a row listing can't answer honestly. Two engines behind one
3348
+ * shape: a dataSource collection queries its CSV natively through the
3349
+ * store (`store.query`, uncapped whole-file scan); a file-backed
3350
+ * collection aggregates its ENRICHED records (computed fields —
3351
+ * `derived` / `rollup` / `toggle` — are real columns) through the same
3352
+ * compiled SQL over a temp JSONL (`runQueryOverRows`). */
3184
3353
  async function handleQueryItems(collection, queryArg, deps) {
3185
- const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
3186
- if (!store.query) return `manageCollection: '${collection.slug}' is file-backed — queryItems currently supports only dataSource (CSV) collections; use getItems (with \`fields\`) instead.`;
3187
3354
  const parsed = CollectionQueryZ.safeParse(queryArg);
3188
3355
  if (!parsed.success) return `manageCollection: \`query\` rejected — fix and retry:\n${parsed.error.issues.slice(0, 20).map((issue) => `- ${issue.path.map(String).join(".") || "(root)"}: ${require_promptSafety.defangForPrompt(issue.message)}`).join("\n")}`;
3189
- const rows = await store.query(parsed.data);
3356
+ const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
3357
+ if (store.query) {
3358
+ const rows = await store.query(parsed.data);
3359
+ return JSON.stringify({
3360
+ collection: collection.slug,
3361
+ count: rows.length,
3362
+ rows
3363
+ });
3364
+ }
3365
+ const rows = await runQueryOverRows(await enrichItems(collection, await store.list(), deps), parsed.data);
3190
3366
  return JSON.stringify({
3191
3367
  collection: collection.slug,
3192
3368
  count: rows.length,
@@ -3316,7 +3492,7 @@ async function handlePutSchema(slug, schemaArg, deps) {
3316
3492
  written: true
3317
3493
  });
3318
3494
  }
3319
- var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference, then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` validates every row against the schema before writing (required fields, enum values, primaryKey = record id) and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits. On a dataSource (CSV) collection, answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` — it scans the WHOLE file, while getItems is row-capped, so an aggregate computed from getItems output can be silently wrong on large files.";
3495
+ var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference, then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` validates every row against the schema before writing (required fields, enum values, primaryKey = record id) and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits. Answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` on ANY collection on a dataSource (CSV) collection it scans the whole file (getItems is row-capped, so aggregates computed from its output can be silently wrong on large files); on a file-backed collection it aggregates the enriched records, so computed fields (derived/rollup/toggle) are queryable columns.";
3320
3496
  /** Validate getItems' optional `ids`/`fields` args, then delegate. */
3321
3497
  async function dispatchGetItems(collection, args, deps) {
3322
3498
  const ids = optionalStringArray(args.ids, "ids");
@@ -3399,7 +3575,7 @@ var MANAGE_COLLECTION_DEFINITION = {
3399
3575
  },
3400
3576
  query: {
3401
3577
  type: "object",
3402
- description: "queryItems (dataSource/CSV collections only): a structured aggregation query over the WHOLE file — `{ groupBy?: [\"col\"], aggregates?: { alias: { op: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\", column? } }, where?: [{ field, op, value }], orderBy?: [{ field, dir? }], limit? }`. At least one of groupBy/aggregates. Runs uncapped over the full file (unlike getItems), so use it for counts / sums / group-bys on large CSVs."
3578
+ description: "queryItems: a structured aggregation query — `{ groupBy?: [\"col\"], aggregates?: { alias: { op: \"count\"|\"sum\"|\"avg\"|\"min\"|\"max\", column? } }, where?: [{ field, op, value }], orderBy?: [{ field, dir? }], limit? }`. At least one of groupBy/aggregates. On a dataSource (CSV) collection it scans the WHOLE file uncapped (unlike getItems); on a file-backed collection it aggregates the ENRICHED records, so computed fields (derived/rollup/toggle) are queryable columns. Use it for counts / sums / averages / group-bys instead of arithmetic over getItems output."
3403
3579
  }
3404
3580
  },
3405
3581
  required: ["action"]
@@ -3522,6 +3698,12 @@ Object.defineProperty(exports, "compileCsvQuery", {
3522
3698
  return compileCsvQuery;
3523
3699
  }
3524
3700
  });
3701
+ Object.defineProperty(exports, "compileJsonlQuery", {
3702
+ enumerable: true,
3703
+ get: function() {
3704
+ return compileJsonlQuery;
3705
+ }
3706
+ });
3525
3707
  Object.defineProperty(exports, "compileRecordZ", {
3526
3708
  enumerable: true,
3527
3709
  get: function() {
@@ -3786,6 +3968,12 @@ Object.defineProperty(exports, "resolveTemplatePath", {
3786
3968
  return resolveTemplatePath;
3787
3969
  }
3788
3970
  });
3971
+ Object.defineProperty(exports, "runQueryOverRows", {
3972
+ enumerable: true,
3973
+ get: function() {
3974
+ return runQueryOverRows;
3975
+ }
3976
+ });
3789
3977
  Object.defineProperty(exports, "safeRecordId", {
3790
3978
  enumerable: true,
3791
3979
  get: function() {
@@ -3859,4 +4047,4 @@ Object.defineProperty(exports, "writeItem", {
3859
4047
  }
3860
4048
  });
3861
4049
 
3862
- //# sourceMappingURL=server-Q7ld-FlZ.cjs.map
4050
+ //# sourceMappingURL=server-CkysfLh4.cjs.map