@kitelev/exocortex-cli 16.254.1 → 16.255.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 (3) hide show
  1. package/README.md +72 -1
  2. package/dist/index.js +271 -259
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -124,7 +124,7 @@ npx @kitelev/exocortex-cli apply <cmd> [path] [options]
124
124
  | `--vault <path>` | cwd | Path to Obsidian vault |
125
125
  | `--dry-run` | off | Evaluate precondition and preview; do not write |
126
126
  | `--yes` | off | Skip destructive-command confirmation |
127
- | `--input <json>` | — | JSON object forwarded to `service_call` groundings as `userInput` |
127
+ | `--input <json>` | — | JSON object forwarded to the grounding as `userInput`; its declared REQUIRED keys are checked before the grounding runs (see below) |
128
128
  | `--seed <uuid>` | — | Deterministic UID seed for test/replay |
129
129
  | `--frozen-clock <iso>` | — | Freeze clock to an ISO timestamp for test/replay |
130
130
  | `--json` | off | Emit a machine-readable `{command,target,created:[…]}` envelope |
@@ -134,6 +134,17 @@ npx @kitelev/exocortex-cli apply <cmd> [path] [options]
134
134
  **Behavior:**
135
135
 
136
136
  - The precondition is evaluated per target; a non-passing ASK aborts before the grounding runs.
137
+ - When the command's grounding declares an `exocmd__Grounding_inputSchema`, a REQUIRED key it
138
+ declares is checked **after the command resolves and its precondition passes, but before the
139
+ grounding runs** — on the dry-run and the executing path alike (req `656bd2d9`). The refusal
140
+ names the key, and fires only when nothing declared supplies it: the schema's own non-blank
141
+ `defaultValue`, a `propertyDefault` / `inheritanceRule` / `serviceCallPayload` /
142
+ `Grounding_isDefinedBy` anywhere in the grounding tree, or — for the engine-reserved `label` —
143
+ a `labelTemplate` / `omitLabel`. A grounding that declares no schema is not checked at all.
144
+ - ⛤ A key the schema does NOT declare is **accepted**, by design: passing an extra property key
145
+ through `--input` is a supported way to populate the asset a `create_instance` grounding builds
146
+ (`--input '{"label":"…","ems__Effort_blocker":"[[uid]]"}'`). Only `label`, `body` and
147
+ `plannedDate` are consumed by the engine itself rather than written as properties.
137
148
  - Commands marked `exocmd__Command_destructive: true` refuse to run without `--dry-run` or `--yes`.
138
149
  - Multi-target runs (stdin) use continue-on-error semantics and print a `N/M` summary; the exit code is `5` if any target failed.
139
150
  - `--use-cache` (#4264): the triple store comes from `<vault>/.exocortex/cache/triples.json`
@@ -366,6 +377,66 @@ echo "# Content" | npx @kitelev/exocortex-cli create \
366
377
  --vault ~/vault
367
378
  ```
368
379
 
380
+ ### create-batch
381
+
382
+ Create many assets from one JSON file in **one invocation** (issue #4347). Every item goes through the same pipeline as `create` — the same guards, class resolution, status default, wikilink validation and co-location — but the vault-wide scans `create` pays for (class index, TBox walk, shape load, anchor and neighbour resolution) run **once per invocation** instead of once per item. Measured on a 35K-file vault (2026-09-25): one `create` took 17–29 s and read every vault file 4 times; a `create-batch` of 1, 20, 200 or 2,000 items took 26–34 s and read every vault file 3 times, whatever the batch size.
383
+
384
+ ```bash
385
+ npx @kitelev/exocortex-cli create-batch items.json --vault ~/vault
386
+ generate-items | npx @kitelev/exocortex-cli create-batch - --vault ~/vault --dry-run
387
+ ```
388
+
389
+ `items.json` is a JSON array; each item maps onto the `create` flags:
390
+
391
+ ```json
392
+ [
393
+ {
394
+ "class": "ems__Task",
395
+ "label": "Exercise 1.1",
396
+ "uid": "0b6a3c52-1f0e-4c3a-9d6e-6f1f4d7a2b10",
397
+ "properties": { "exo__Asset_isDefinedBy": "[[<ontology-uid>]]" }
398
+ },
399
+ {
400
+ "class": "ems__Task",
401
+ "label": "Step 1",
402
+ "aliases": ["First step"],
403
+ "properties": {
404
+ "ems__Effort_parent": "[[0b6a3c52-1f0e-4c3a-9d6e-6f1f4d7a2b10]]",
405
+ "exo__Asset_relates": ["[[<uid-a>]]", "[[<uid-b>]]"]
406
+ },
407
+ "body": "# Step 1\n\nText.",
408
+ "status": "Draft"
409
+ }
410
+ ]
411
+ ```
412
+
413
+ | Item key | `create` equivalent | Notes |
414
+ | ------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
415
+ | `class` | `--class` | **Required.** Short name or UUID |
416
+ | `label` | `--label` | **Required** |
417
+ | `uid` | — | Optional caller-chosen identity (canonical lower-case UUID); omitted → generated anew each run |
418
+ | `aliases` | `--aliases` | Array of strings |
419
+ | `properties` | `--property k=v` | Object; a string / number / boolean value is one flag, an array is the key repeated (multi-value). A number is written as JavaScript prints it (`1.0` → `1`); an integer beyond 2^53 is refused — pass exact text as a string |
420
+ | `body` | `--body-file` | Taken verbatim — no `\n` escape expansion |
421
+ | `status` | `--status <name>` | `false` ⇔ `--no-status` |
422
+ | `createdBy` | `--created-by` | Falls back to the batch-wide `--created-by`, then to the `create` default |
423
+
424
+ | Option | Default | Description |
425
+ | ---------------------------- | ------------- | --------------------------------------------------------------------------------------- |
426
+ | `<file>` | **required** | The JSON file, or `-` for stdin (read to the end, no time limit; a terminal is refused) |
427
+ | `--vault <path>` | cwd | Path to Obsidian vault |
428
+ | `--dry-run` | off | Plan and validate every item, preview each one's exact bytes (stderr), write nothing |
429
+ | `--created-by <uuid>` | — | Creator for items that set no `createdBy` |
430
+ | `--timezone <tz>` | `Asia/Almaty` | Timezone for timestamps |
431
+ | `--skip-wikilink-validation` | off | Skip wikilink existence validation |
432
+ | `--yes` | — | Accepted for symmetry (no-op) |
433
+
434
+ - **All-or-nothing.** Every item is planned and validated before the first write. If any item fails, **nothing** is written, stderr lists every failing item (`✗ item[<index>] "<label>": <reason>` — an item's first shape problem, or else its uid / anchor problems and its first planning failure) and the exit code is `2`. Messages from `create`'s own guards name the equivalent `create` flag. The filesystem is not transactional: an I/O error during the write phase stops the remaining writes, names the items already written and exits `5`.
435
+ - **Links inside the batch.** Give the target item a `uid` and link to it as `[[<uid>]]` (UUID form — a label-form link needs the target on disk); wikilink validation treats the batch's uids as existing. A `uid` that is malformed, repeated in the batch, or already the uid of an asset in the vault (by filename or by `exo__Asset_uid`) is refused — so re-running a file whose items carry `uid`s is refused instead of duplicating. ⚠ Items **without** `uid` get a fresh identity every run: re-running such a file creates them again.
436
+ - **Anchors must exist.** An `exo__Asset_isDefinedBy` that names another item of the same batch — or the item's own uid (a self-anchored ontology) — is refused, in every form the range guard resolves (wikilink, aliased, quoted or bare uid): the range guard and co-location read the anchor's file. Create the ontology first, in its own run.
437
+ - **Output.** On success stdout is one JSON array `[{uuid, path, label}]` in input order, and the command exits only after stdout and stderr have been flushed (safe to pipe); a reader that closes the pipe early (`| head`) does not change the exit code. `--dry-run` prints the planned mapping — for items without `uid` the uids are generated for that run. Diagnostics are prefixed with the item that raised them, printed once, and followed by the list of other items that raised them. `null` for an optional key means "absent"; a UTF-8 BOM is ignored.
438
+ - **Not in v1:** `--validate`, `--use-cache`, `--write-through` (run `validate schema --shapes-mode` after the batch); existing assets are never updated.
439
+
369
440
  ### resolve-inline-buttons
370
441
 
371
442
  Print the inline command button-set the plugin would render for an asset — binding-match (Layer A, class hierarchy) ∩ precondition-eval (Layer B). Alias: `resolve-buttons`. The authoritative button-visibility oracle (issue #3833): strictly more complete than `apply <cmd> --dry-run`, which checks the precondition only.