@mulmoclaude/core 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/helps/collection-skills.md +12 -4
- package/assets/helps/custom-view.md +13 -9
- package/assets/skills-preset/mc-zenn/SKILL.md +31 -0
- package/dist/collection/registry/server/index.cjs +1 -1
- package/dist/collection/registry/server/index.js +1 -1
- package/dist/collection/server/csvQuery.d.ts +13 -5
- package/dist/collection/server/csvStore.d.ts +2 -0
- package/dist/collection/server/index.cjs +3 -1
- package/dist/collection/server/index.d.ts +2 -1
- package/dist/collection/server/index.js +2 -2
- package/dist/collection/server/jsonlQuery.d.ts +4 -0
- package/dist/collection-watchers/index.cjs +1 -1
- package/dist/collection-watchers/index.js +1 -1
- package/dist/feeds/server/index.cjs +1 -1
- package/dist/feeds/server/index.js +1 -1
- package/dist/google/auth.d.ts +10 -1
- package/dist/google/authFlow.d.ts +1 -0
- package/dist/google/index.cjs +79 -38
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +1 -1
- package/dist/google/index.js +78 -39
- package/dist/google/index.js.map +1 -1
- package/dist/{server--FgDORd3.js → server-D2ibbixF.js} +113 -19
- package/dist/server-D2ibbixF.js.map +1 -0
- package/dist/{server-Q7ld-FlZ.cjs → server-Jaogu3Ky.cjs} +124 -18
- package/dist/server-Jaogu3Ky.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/server--FgDORd3.js.map +0 -1
- package/dist/server-Q7ld-FlZ.cjs.map +0 -1
|
@@ -792,17 +792,18 @@ function whereFragment(cond) {
|
|
|
792
792
|
params: [cond.value]
|
|
793
793
|
};
|
|
794
794
|
}
|
|
795
|
-
/** Compile a validated query
|
|
796
|
-
* the
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
800
|
-
|
|
795
|
+
/** Compile a validated query against `fromSql` (a table-function call
|
|
796
|
+
* whose FIRST placeholder is the source path — the executor binds it).
|
|
797
|
+
* Returns the SQL and the where-value parameters that follow the path.
|
|
798
|
+
* Callers MUST have run `CollectionQueryZ` first; this function trusts
|
|
799
|
+
* the shape (aliases already charset-checked, orderBy membership already
|
|
800
|
+
* enforced). */
|
|
801
|
+
function compileQuery(query, fromSql) {
|
|
801
802
|
const groupBy = query.groupBy ?? [];
|
|
802
803
|
const aggregates = Object.entries(query.aggregates ?? {});
|
|
803
804
|
const selectList = [...groupBy.map(quoteIdent), ...aggregates.map(([alias, aggregate]) => `${aggregateExpr(aggregate)} AS ${quoteIdent(alias)}`)];
|
|
804
805
|
const where = (query.where ?? []).map(whereFragment);
|
|
805
|
-
const clauses = [`SELECT ${selectList.join(", ")}`, `FROM
|
|
806
|
+
const clauses = [`SELECT ${selectList.join(", ")}`, `FROM ${fromSql}`];
|
|
806
807
|
if (where.length > 0) clauses.push(`WHERE ${where.map((fragment) => fragment.sql).join(" AND ")}`);
|
|
807
808
|
if (groupBy.length > 0) clauses.push(`GROUP BY ${groupBy.map(quoteIdent).join(", ")}`);
|
|
808
809
|
const orderBy = (query.orderBy ?? []).map((order) => quoteIdent(order.field) + (order.dir === "desc" ? " DESC" : " ASC"));
|
|
@@ -813,6 +814,21 @@ function compileCsvQuery(query, primaryKey) {
|
|
|
813
814
|
params: where.flatMap((fragment) => fragment.params)
|
|
814
815
|
};
|
|
815
816
|
}
|
|
817
|
+
/** Compile against a CSV file (the dataSource store's engine). */
|
|
818
|
+
function compileCsvQuery(query, primaryKey) {
|
|
819
|
+
return compileQuery(query, `read_csv(${readCsvArgs(primaryKey)})`);
|
|
820
|
+
}
|
|
821
|
+
/** Compile against a JSONL file of ENRICHED records — the file-backed
|
|
822
|
+
* collections' engine (see `jsonlQuery.ts`). No VARCHAR key pin needed:
|
|
823
|
+
* enriched record ids are already strings. `sample_size=-1` makes the
|
|
824
|
+
* schema inference scan EVERY line — with the default sample, a sparse
|
|
825
|
+
* optional/derived field first appearing past the sample would not be
|
|
826
|
+
* inferred as a column and the query would binder-error on it (Codex P2
|
|
827
|
+
* on #2165). The full scan costs nothing extra here: aggregation reads
|
|
828
|
+
* the whole file anyway. */
|
|
829
|
+
function compileJsonlQuery(query) {
|
|
830
|
+
return compileQuery(query, `read_json(?, format='newline_delimited', sample_size=-1)`);
|
|
831
|
+
}
|
|
816
832
|
//#endregion
|
|
817
833
|
//#region src/collection/server/csvStore.ts
|
|
818
834
|
/** `list()` row cap. Over-cap files are truncated with a warn — the v1
|
|
@@ -1133,6 +1149,74 @@ function storeFor(collection, opts = {}) {
|
|
|
1133
1149
|
};
|
|
1134
1150
|
}
|
|
1135
1151
|
//#endregion
|
|
1152
|
+
//#region src/collection/server/jsonlQuery.ts
|
|
1153
|
+
/** SQL semantics for an aggregate-only query over ZERO rows: one scalar
|
|
1154
|
+
* row (`count` = 0, everything else NULL) — the same shape the CSV path
|
|
1155
|
+
* produces for a header-only file, so callers reading `rows[0]` never
|
|
1156
|
+
* break on storage kind or emptiness. A grouped query over zero rows is
|
|
1157
|
+
* zero groups (`[]`), also matching SQL. Synthesized here because DuckDB
|
|
1158
|
+
* has no empty file to infer a schema from. */
|
|
1159
|
+
function emptyCollectionResult(query) {
|
|
1160
|
+
if ((query.groupBy?.length ?? 0) > 0) return [];
|
|
1161
|
+
const aggregates = Object.entries(query.aggregates ?? {});
|
|
1162
|
+
return [Object.fromEntries(aggregates.map(([alias, aggregate]) => [alias, aggregate.op === "count" ? 0 : null]))];
|
|
1163
|
+
}
|
|
1164
|
+
/** The SOURCE columns a query reads: groupBy columns, aggregate input
|
|
1165
|
+
* columns, and where fields. (orderBy resolves to groupBy columns or
|
|
1166
|
+
* aggregate ALIASES — never to a new source column — so it adds none.) */
|
|
1167
|
+
function referencedSourceColumns(query) {
|
|
1168
|
+
const columns = new Set(query.groupBy ?? []);
|
|
1169
|
+
for (const aggregate of Object.values(query.aggregates ?? {})) if (aggregate.column !== void 0) columns.add(aggregate.column);
|
|
1170
|
+
for (const cond of query.where ?? []) columns.add(cond.field);
|
|
1171
|
+
return [...columns];
|
|
1172
|
+
}
|
|
1173
|
+
/** Referenced columns that appear in NO row — a freshly-added optional
|
|
1174
|
+
* field, or a derived field whose inputs no record has yet. DuckDB only
|
|
1175
|
+
* infers columns from keys that occur in the JSONL, so without help a
|
|
1176
|
+
* valid query on such a field binder-errors here while the CSV path
|
|
1177
|
+
* (whose header always declares the column) returns NULLs. Padding the
|
|
1178
|
+
* FIRST line with `col: null` makes the column exist (full-scan
|
|
1179
|
+
* inference unions keys across lines) with matching NULL semantics. */
|
|
1180
|
+
function absentReferencedColumns(rows, query) {
|
|
1181
|
+
const present = /* @__PURE__ */ new Set();
|
|
1182
|
+
for (const row of rows) for (const key of Object.keys(row)) present.add(key);
|
|
1183
|
+
return referencedSourceColumns(query).filter((column) => !present.has(column));
|
|
1184
|
+
}
|
|
1185
|
+
/** Run a validated query over enriched collection rows. */
|
|
1186
|
+
async function runQueryOverRows(rows, query) {
|
|
1187
|
+
if (rows.length === 0) return emptyCollectionResult(query);
|
|
1188
|
+
await mkdir(cacheDir(), {
|
|
1189
|
+
recursive: true,
|
|
1190
|
+
mode: 448
|
|
1191
|
+
});
|
|
1192
|
+
const jsonlPath = path.join(cacheDir(), `q-${randomBytes(8).toString("hex")}.jsonl`);
|
|
1193
|
+
try {
|
|
1194
|
+
const nullPads = Object.fromEntries(absentReferencedColumns(rows, query).map((column) => [column, null]));
|
|
1195
|
+
const handle = await open(jsonlPath, "wx", 384);
|
|
1196
|
+
try {
|
|
1197
|
+
let first = true;
|
|
1198
|
+
for (const row of rows) {
|
|
1199
|
+
await handle.write(`${JSON.stringify(first ? {
|
|
1200
|
+
...nullPads,
|
|
1201
|
+
...row
|
|
1202
|
+
} : row)}\n`);
|
|
1203
|
+
first = false;
|
|
1204
|
+
}
|
|
1205
|
+
} finally {
|
|
1206
|
+
await handle.close();
|
|
1207
|
+
}
|
|
1208
|
+
const { sql, params } = compileJsonlQuery(query);
|
|
1209
|
+
return (await queryCsv(sql, [jsonlPath, ...params])).map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));
|
|
1210
|
+
} finally {
|
|
1211
|
+
await unlink(jsonlPath).catch((err) => {
|
|
1212
|
+
if (err.code !== "ENOENT") log.warn("collections", "temp JSONL cleanup failed", {
|
|
1213
|
+
path: jsonlPath,
|
|
1214
|
+
error: String(err)
|
|
1215
|
+
});
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
//#endregion
|
|
1136
1220
|
//#region src/collection/core/recordZ.ts
|
|
1137
1221
|
/** The emptiness rule shared by `required` and the "only check present
|
|
1138
1222
|
* values" gate. NOT a truthiness check — `0` and `false` are filled. */
|
|
@@ -3173,17 +3257,27 @@ async function putOneItem(collection, record, mode, deps) {
|
|
|
3173
3257
|
if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
|
|
3174
3258
|
return reject(itemId, "write refused: the collection's data dir escapes the workspace");
|
|
3175
3259
|
}
|
|
3176
|
-
/** Aggregation over a
|
|
3177
|
-
*
|
|
3178
|
-
*
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3260
|
+
/** Aggregation over a collection via the structured query DSL
|
|
3261
|
+
* (`core/queryZ.ts`) — the paved road for counts / sums / group-bys
|
|
3262
|
+
* that a row listing can't answer honestly. Two engines behind one
|
|
3263
|
+
* shape: a dataSource collection queries its CSV natively through the
|
|
3264
|
+
* store (`store.query`, uncapped whole-file scan); a file-backed
|
|
3265
|
+
* collection aggregates its ENRICHED records (computed fields —
|
|
3266
|
+
* `derived` / `rollup` / `toggle` — are real columns) through the same
|
|
3267
|
+
* compiled SQL over a temp JSONL (`runQueryOverRows`). */
|
|
3181
3268
|
async function handleQueryItems(collection, queryArg, deps) {
|
|
3182
|
-
const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
|
|
3183
|
-
if (!store.query) return `manageCollection: '${collection.slug}' is file-backed — queryItems currently supports only dataSource (CSV) collections; use getItems (with \`fields\`) instead.`;
|
|
3184
3269
|
const parsed = CollectionQueryZ.safeParse(queryArg);
|
|
3185
3270
|
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)"}: ${defangForPrompt(issue.message)}`).join("\n")}`;
|
|
3186
|
-
const
|
|
3271
|
+
const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
|
|
3272
|
+
if (store.query) {
|
|
3273
|
+
const rows = await store.query(parsed.data);
|
|
3274
|
+
return JSON.stringify({
|
|
3275
|
+
collection: collection.slug,
|
|
3276
|
+
count: rows.length,
|
|
3277
|
+
rows
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
const rows = await runQueryOverRows(await enrichItems(collection, await store.list(), deps), parsed.data);
|
|
3187
3281
|
return JSON.stringify({
|
|
3188
3282
|
collection: collection.slug,
|
|
3189
3283
|
count: rows.length,
|
|
@@ -3313,7 +3407,7 @@ async function handlePutSchema(slug, schemaArg, deps) {
|
|
|
3313
3407
|
written: true
|
|
3314
3408
|
});
|
|
3315
3409
|
}
|
|
3316
|
-
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.
|
|
3410
|
+
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.";
|
|
3317
3411
|
/** Validate getItems' optional `ids`/`fields` args, then delegate. */
|
|
3318
3412
|
async function dispatchGetItems(collection, args, deps) {
|
|
3319
3413
|
const ids = optionalStringArray(args.ids, "ids");
|
|
@@ -3396,7 +3490,7 @@ var MANAGE_COLLECTION_DEFINITION = {
|
|
|
3396
3490
|
},
|
|
3397
3491
|
query: {
|
|
3398
3492
|
type: "object",
|
|
3399
|
-
description: "queryItems
|
|
3493
|
+
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."
|
|
3400
3494
|
}
|
|
3401
3495
|
},
|
|
3402
3496
|
required: ["action"]
|
|
@@ -3411,6 +3505,6 @@ function makeManageCollectionTool(deps = {}) {
|
|
|
3411
3505
|
};
|
|
3412
3506
|
}
|
|
3413
3507
|
//#endregion
|
|
3414
|
-
export {
|
|
3508
|
+
export { isRegularFile as $, validateCollectionRecords as A, decodeCsvRecordId as B, discoverCollections as C, CollectionSchemaZ as D, toSummary as E, collectionWritable as F, compileJsonlQuery as G, encodeCsvRecordId as H, readOnlyRefusal as I, MAX_QUERY_ROWS as J, CollectionQueryZ as K, storeFor as L, compileRecordZ as M, recordFieldProblem as N, applyMutateAction as O, runQueryOverRows as P, generateItemId as Q, MAX_CSV_ROWS as R, acceptParsedSchema as S, setCollectionChangePublisher as St, toDetail as T, normalizeCsvValue as U, dedupeByRecordId as V, compileCsvQuery as W, buildCollectionActionSeedPrompt as X, buildActionSeedPrompt as Y, deleteItem as Z, errorMessage as _, collectionsRegistriesConfigPath as _t, deleteCollection as a, readSkillTemplate as at, buildWorkspaceOntology as b, log as bt, computeSuccessor as c, writeFileAtomic as ct, isTriggerDue as d, isContainedInWorkspace as dt, listItems as et, maybeSpawnSuccessor as f, itemFilePath as ft, ONE_SECOND_MS as g, safeSlugName as gt, successorId as h, safeRecordId as ht, deleteCustomView as i, readItem as it, validateRecordObject as j, firstMutateParamProblem as k, daysInMonth as l, SCHEMA_FILE$1 as lt, resolveEvery as m, resolveTemplatePath as mt, MAX_UNSELECTIVE_ITEMS as n, readCustomViewHtml as nt, deleteCollectionRefusalMessage as o, resolveCreateItemId as ot, parseCivil as p, resolveDataDir as pt, DEFAULT_QUERY_ROWS as q, makeManageCollectionTool as r, readCustomViewI18n as rt, advanceTriggerDate as s, writeItem as st, MAX_SCHEMA_ISSUES as t, promptPathsFor as tt, formatCivil as u, isContainedInRoot as ut, computeCollectionIcon as v, configureCollectionHost as vt, loadCollection as w, schemaRelations as x, publishCollectionChange as xt, enrichItems as y, getWorkspaceRoot as yt, csvRowToItem as z };
|
|
3415
3509
|
|
|
3416
|
-
//# sourceMappingURL=server
|
|
3510
|
+
//# sourceMappingURL=server-D2ibbixF.js.map
|