@tapestry-mud/cli 0.9.0 → 0.12.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.
package/CLAUDE.md ADDED
@@ -0,0 +1,42 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `@tapestry-mud/cli` is a Node.js CLI that manages Tapestry MUD game projects: scaffolding, pack management, registry operations, and engine lifecycle. Entry point: `bin/tapestry.js`. Commands are wired via `commander`.
8
+
9
+ ## Commands
10
+
11
+ ```sh
12
+ npm ci # install deps
13
+ npm test # run all tests (jest)
14
+ npx jest test/path/to/file.test.js # run a single test file
15
+ npx jest --testNamePattern "name" # run tests matching a name
16
+ ```
17
+
18
+ No build step. No lint config in this repo.
19
+
20
+ ## Running the CLI locally
21
+
22
+ ```sh
23
+ node bin/tapestry.js <command>
24
+ ```
25
+
26
+ ## Layout
27
+
28
+ ```
29
+ bin/ entry point (tapestry.js)
30
+ src/
31
+ commands/ one file per CLI command (~30 commands)
32
+ lib/ shared logic (auth, registry client, pack ops, etc.)
33
+ schema/ zod schemas
34
+ scaffold/ template files for `tapestry init`
35
+ util/ small helpers
36
+ specs/ capability specs -- source of truth for system behavior
37
+ test/ jest tests, mirrors src/ structure
38
+ ```
39
+
40
+ ## System behavior
41
+
42
+ All behavior is documented in `specs/`. Read the relevant spec before changing any command logic. `specs/README.md` lists the index and explains the format contract (anchored claims, change records, lint rules).
package/bin/tapestry.js CHANGED
@@ -33,6 +33,7 @@ const { trustAdd, trustList, trustRm } = require('../src/commands/trust');
33
33
  const { syncArea } = require('../src/commands/sync-area');
34
34
  const { harvest } = require('../src/commands/harvest');
35
35
  const { status } = require('../src/commands/status');
36
+ const { types } = require('../src/commands/types');
36
37
 
37
38
  const program = new Command();
38
39
 
@@ -62,7 +63,7 @@ program.configureHelp({
62
63
  },
63
64
  {
64
65
  title: 'Pack Authoring',
65
- commands: ['create', 'validate', 'pack', 'publish', 'unpublish', 'harvest', 'status'],
66
+ commands: ['create', 'validate', 'pack', 'publish', 'unpublish', 'harvest', 'status', 'types'],
66
67
  },
67
68
  {
68
69
  title: 'Trusted Publishing',
@@ -376,6 +377,18 @@ program
376
377
  }
377
378
  });
378
379
 
380
+ program
381
+ .command('types')
382
+ .description('Write the @tapestry/engine type declarations into types/ for ESM pack authoring')
383
+ .action(() => {
384
+ try {
385
+ types({});
386
+ } catch (e) {
387
+ console.error(`error: ${e.message}`);
388
+ process.exit(1);
389
+ }
390
+ });
391
+
379
392
  async function runHarvest(areaRef, opts) {
380
393
  try {
381
394
  await harvest(areaRef, {
@@ -398,15 +411,15 @@ async function runHarvest(areaRef, opts) {
398
411
  program
399
412
  .command('harvest <areaRef>')
400
413
  .description('Harvest an authored area into a portable pack (areaRef = namespace:area-id)')
401
- .option('--sink <sink>', 'Output sink: file | git (auto-detected by default)')
414
+ .option('--sink <sink>', 'Output sink: file | git | registry (auto-detected by default)')
402
415
  .option('--out <path>', '(file sink) where the .tgz lands')
403
- .option('--name <name>', '(file sink) override the synthesized pack name (@scope/pack)')
416
+ .option('--name <name>', '(file/registry sink) override the synthesized pack name (@scope/pack)')
404
417
  .option('--pack <dir>', 'Target pack directory (auto-detected from linked packs by default)')
405
418
  .option('--game-root <path>', 'Game root containing data/ (default: current dir)')
406
419
  .option('--keep-sidecars', 'Copy instead of move (leave the game-root side-cars in place)')
407
420
  .option('--force', 'Overwrite pack files that diverge from the side-car')
408
- .option('--minor', 'Bump the pack minor version (git sink only; default: patch)')
409
- .option('--major', 'Bump the pack major version (git sink only; default: patch)')
421
+ .option('--minor', 'Bump the pack minor version (git/registry sink, owned pack only; default: patch)')
422
+ .option('--major', 'Bump the pack major version (git/registry sink, owned pack only; default: patch)')
410
423
  .action(runHarvest);
411
424
 
412
425
  // Deprecated: sync-area is now harvest --sink git.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapestry-mud/cli",
3
- "version": "0.9.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for the Tapestry MUD engine",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,22 +10,25 @@
10
10
  "tapestry": "./bin/tapestry.js"
11
11
  },
12
12
  "scripts": {
13
- "test": "jest"
13
+ "test": "jest",
14
+ "spec-lint": "spec-lint specs/"
14
15
  },
15
16
  "publishConfig": {
16
17
  "access": "public"
17
18
  },
18
19
  "dependencies": {
19
20
  "commander": "11.1.0",
20
- "form-data": "4.0.5",
21
+ "form-data": "4.0.6",
21
22
  "inquirer": "8.2.7",
22
23
  "js-yaml": "4.1.1",
23
24
  "node-fetch": "2.7.0",
24
25
  "semver": "7.6.2",
25
26
  "tar": "7.5.15",
27
+ "typescript": "5.7.3",
26
28
  "zod": "3.22.4"
27
29
  },
28
30
  "devDependencies": {
31
+ "@tapestry-mud/spec-lint": "0.2.1",
29
32
  "jest": "29.7.0"
30
33
  }
31
34
  }
@@ -0,0 +1,59 @@
1
+ # Tapestry CLI -- specs
2
+
3
+ Capability specs for the Tapestry CLI. Each file describes one system's current behavior,
4
+ known constraints, and change history. This directory is the canonical, public source of
5
+ truth for how each system behaves now -- a fresh agent or contributor answers "how does X
6
+ behave?" from the relevant file alone.
7
+
8
+ ## Index
9
+
10
+ | Capability | File | Last Updated |
11
+ |------------|------|--------------|
12
+ | pack-lifecycle | [pack-lifecycle.md](pack-lifecycle.md) | 2026-06-20 |
13
+ | validate | [validate.md](validate.md) | 2026-06-13 |
14
+ | harvest | [harvest.md](harvest.md) | 2026-06-13 |
15
+ | registry-auth | [registry-auth.md](registry-auth.md) | 2026-06-13 |
16
+ | engine-management | [engine-management.md](engine-management.md) | 2026-06-13 |
17
+
18
+ ## Contract summary
19
+
20
+ Each capability spec has four required sections: Overview, Behavior, Rejected and Reverted,
21
+ Change Log. Change records live in `specs/changes/` and use the frontmatter fields `release:`
22
+ (the version that shipped it) and `specs:` (capability files touched).
23
+
24
+ Hotfixes, regressions, and dependency bumps owe no change record. Tombstones on any reversal
25
+ of shipped behavior are mandatory.
26
+
27
+ A capability spec is current if its Change Log references the latest shipped change record
28
+ that names it in `specs:`.
29
+
30
+ ## Format rules (mechanically linted)
31
+
32
+ - Behavior claims carry inline anchors in exactly one form: `(repo-relative/path/File.ext:123)`,
33
+ where the line part may be a single line `:123` or a range `:123-145`, and may be omitted only
34
+ for whole-file claims. Several anchors may share one set of parentheses, joined by `; `. A test
35
+ name in the same parentheses also counts. Lint pattern (the gate IS this regex, keep them in
36
+ sync): `\([@\w./\\-]+\.(js|mjs|cjs)(:\d+(-\d+)?)?[^)]*\)`. A file with no matches in its Behavior
37
+ section fails validation outright.
38
+ - An empty Rejected and Reverted section contains the single line `- None on record.` under the
39
+ heading (the heading itself is always present).
40
+ - Change Log is a one-line-per-record list, newest first: `- YYYY-MM-DD [slug](changes/...)`.
41
+ Not a table.
42
+
43
+ <!-- spec-lint:start -->
44
+ Mode: strict
45
+
46
+ Required sections: Overview, Behavior, Rejected and Reverted, Change Log
47
+
48
+ Anchor regex (Behavior): \([@\w./\\-]+\.(cs|js|ts|json|ya?ml|md)(:\d+(-\d+)?)?[^)]*\)
49
+
50
+ Empty-reversal sentinel: - None on record.
51
+
52
+ Change Log: list, newest-first by date, not a table. Empty is valid for unmodified capabilities.
53
+
54
+ Index sync: every capability .md on disk appears in README index; every indexed file exists on disk; index date matches file last-updated.
55
+
56
+ Currency: for each change record naming a capability, the top Change Log entry references that record and last-updated >= record date. A capability named by zero records may have an empty Change Log.
57
+
58
+ Tombstone: a change record with status:reverted requires a tombstone entry in the capability Rejected and Reverted (not the empty sentinel).
59
+ <!-- spec-lint:end -->
@@ -0,0 +1,28 @@
1
+ ---
2
+ release: 0.10.0
3
+ specs: [pack-lifecycle.md]
4
+ ---
5
+
6
+ # Pack ESM Build
7
+
8
+ ## Why
9
+
10
+ Packs are authored in TypeScript and ship native ES modules, but the CLI had no
11
+ build step: `pack` and `publish` archived whatever was on disk. An ESM pack
12
+ declares `content.scripts_format: esm` and points `content.scripts` at compiled
13
+ output under `dist/scripts/`, which is gitignored - so without a compile the
14
+ tarball shipped no runnable scripts. Pack authors also had no typed surface for
15
+ the engine API they call, so editors could not check `tapestry.*` usage.
16
+
17
+ ## What
18
+
19
+ - `pack` and `publish` compile an ESM pack's `scripts/**/*.ts` to
20
+ `dist/scripts/**/*.js` with the bundled `tsc` and the pack's `tsconfig.json`
21
+ before building the tarball. The compile runs before any network or auth work
22
+ in `publish`. Legacy packs (no `scripts_format: esm`) are a no-op, so the step
23
+ is backward compatible.
24
+ - New `tapestry types` command vendors the `@tapestry/engine` type definitions
25
+ into a project's `types/` so `tsc` and editors can type-check pack scripts
26
+ against the engine API surface the pack calls.
27
+ - `typescript` is pinned exactly as a CLI dependency (no caret) so the compile is
28
+ reproducible.
@@ -0,0 +1,140 @@
1
+ ---
2
+ capability: engine-management
3
+ last-updated: 2026-06-13
4
+ ---
5
+
6
+ # engine-management
7
+
8
+ Capability spec for acquiring, configuring, running, and stopping the Tapestry engine.
9
+
10
+ ## Overview
11
+
12
+ engine-management covers the lifecycle of the Tapestry engine process from the CLI's
13
+ perspective: reading the engine configuration from `tapestry.yaml`, fetching or building the
14
+ engine artifact, starting it, and stopping it. Three launch modes are supported: `docker`
15
+ (recommended; pulls and runs a container image), `binary` (downloads a pre-built platform
16
+ binary from GitHub Releases), and `source` (git-clones the engine and runs it via
17
+ `dotnet run`). Named channels (`stable`, `nightly`) resolve to concrete versions via the
18
+ registry for the docker mode.
19
+
20
+ Commands: `engine install`, `engine update`, `engine info`, `engine versions`, `start`, `stop`.
21
+
22
+ ## Behavior
23
+
24
+ ### Configuration
25
+
26
+ Engine configuration is read from the `engine` key in `tapestry.yaml`. The key must be an
27
+ object (not a plain string, which is the pack manifest format). Required fields are
28
+ `engine.version` and `engine.mode`; `mode` must be one of `docker`, `binary`, or `source`.
29
+ (src/lib/engine-manager.js:249-278)
30
+
31
+ Derived values from `readEngineConfig`: (src/lib/engine-manager.js:248-278)
32
+
33
+ - `image` defaults to `ghcr.io/tapestry-mud/tapestry` if not specified.
34
+ - `network` is null if not specified.
35
+ - `envFile` is null if not specified (`engine.env_file`).
36
+ - `installDir` is always `<cwd>/.tapestry-engine`.
37
+ - `projectName` is the manifest `name` lowercased and with non-alphanumeric/hyphen characters
38
+ replaced by `-`.
39
+
40
+ ### Channel resolution (docker mode only)
41
+
42
+ Channel names `stable` and `nightly` are the two recognized named channels.
43
+ (src/lib/engine-manager.js:6)
44
+
45
+ When the configured version is a named channel, `resolveDockerTag` fetches
46
+ `<registry>/v1/engine-channels/<channel>` and returns the `docker_tag` field.
47
+ (src/lib/engine-manager.js:14-35)
48
+
49
+ - If the registry is unreachable, the channel name is used as the tag directly with a warning.
50
+ (src/lib/engine-manager.js:19-22)
51
+ - If the registry returns 404, throws with a hint to run `engine versions`.
52
+ (src/lib/engine-manager.js:23-27)
53
+ - If the version string is not a named channel, it is used as the Docker tag directly.
54
+ (src/lib/engine-manager.js:13-14)
55
+
56
+ ### engine install
57
+
58
+ - **docker mode**: Calls `docker pull <image>:<version>`. If the tagged pull fails, falls back
59
+ to `docker pull <image>:latest` and re-tags to the requested version.
60
+ (src/lib/engine-manager.js:44-59)
61
+ - **binary mode**: Determines the platform (`linux`, `osx`, or `windows`), downloads the
62
+ engine tarball from
63
+ `https://github.com/tapestry-mud/tapestry/releases/download/v<version>/tapestry-<platform>.tar.gz`
64
+ using `curl`, extracts it to `.tapestry-engine/binary/<version>/`, then deletes the
65
+ downloaded tarball. (src/lib/engine-manager.js:124-151)
66
+ - **source mode**: `git clone`s `https://github.com/tapestry-mud/tapestry.git` into
67
+ `.tapestry-engine/source/`. Throws if the source directory already exists (use
68
+ `engine update` instead). (src/lib/engine-manager.js:180-194)
69
+
70
+ ### engine update
71
+
72
+ - **docker mode**: Same pull logic as install (re-pulls the tag).
73
+ (src/lib/engine-manager.js:294-298)
74
+ - **binary mode**: Re-runs `binaryInstall` for the configured version.
75
+ (src/lib/engine-manager.js:299-300)
76
+ - **source mode**: Runs `git pull` inside `.tapestry-engine/source/`; throws if the source
77
+ directory is absent. (src/lib/engine-manager.js:195-207)
78
+
79
+ ### engine info
80
+
81
+ Prints `Mode`, `Version`, and either `Image` (docker) or `Path` and `Status` (binary/source).
82
+ (src/commands/engine.js:13-23)
83
+
84
+ For binary and source modes, `installed` is true if the expected directory exists on disk.
85
+ (src/lib/engine-manager.js:174) (src/lib/engine-manager.js:226)
86
+
87
+ ### engine versions
88
+
89
+ Fetches `/v1/engine-channels` from the registry and prints a table of Channel, Version, and
90
+ Updated columns. Prints "No engine channels registered." if the list is empty.
91
+ (src/commands/engine-versions.js:7-31)
92
+
93
+ ### start
94
+
95
+ - Requires `packs/` directory and `server.yaml` in the project root; throws otherwise.
96
+ (src/lib/engine-manager.js:321-327)
97
+ - Creates `data/` directory if absent. (src/lib/engine-manager.js:328)
98
+ - **docker mode**: Calls `docker rm -f <containerName>` to clear any stale container, then
99
+ `docker run --detach` with port mappings `-p 4000:4000 -p 4001:4001`, volume mounts for
100
+ `packs/`, `server.yaml`, and `data/`, optional `--network` and `--env-file`, and any link
101
+ mounts from `dockerLinkMounts`. (src/lib/engine-manager.js:69-99)
102
+ - **binary mode**: Materializes links into `packs/`, spawns the engine binary detached, and
103
+ writes the PID to `.tapestry.pid`. (src/lib/engine-manager.js:154-171)
104
+ (src/lib/process-tracker.js:8-10)
105
+ - **source mode**: Materializes links, runs `dotnet run` in `.tapestry-engine/source/` with
106
+ `--packs` and `--config` arguments, detached, and writes the PID to `.tapestry.pid`.
107
+ (src/lib/engine-manager.js:208-222) (src/lib/process-tracker.js:8-10)
108
+ - **docker mode with `engine.env_file`**: Resolves the path relative to the project root and
109
+ throws if the file does not exist before starting. (src/lib/engine-manager.js:330-338)
110
+ - Container name for docker mode is `tapestry-<projectName>`. (src/lib/engine-manager.js:70)
111
+ - Prints "Engine started. Container: <name>" (docker) or "Engine started (PID <pid>)"
112
+ (binary/source) and the telnet/websocket addresses. (src/lib/engine-manager.js:96-99)
113
+ (src/lib/engine-manager.js:168-171)
114
+
115
+ ### stop
116
+
117
+ - **docker mode**: Runs `docker stop <containerName>` then `docker rm <containerName>`.
118
+ Throws if either command fails. (src/lib/engine-manager.js:101-112)
119
+ - **binary/source mode**: Reads the PID from `.tapestry.pid`, sends `SIGTERM`, and clears the
120
+ file. If the process is already gone when `SIGTERM` is sent, the error is swallowed and the
121
+ PID file is still cleared. Throws if no PID file is found.
122
+ (src/lib/engine-manager.js:232-243) (src/lib/process-tracker.js:14-20)
123
+ (src/lib/process-tracker.js:22-27)
124
+
125
+ ### Process tracker
126
+
127
+ - `writePid` writes the PID as a string to `<cwd>/.tapestry.pid`.
128
+ (src/lib/process-tracker.js:8-10)
129
+ - `readPid` reads and parses the PID; returns null if the file is absent or the value is not a
130
+ positive integer. (src/lib/process-tracker.js:13-20)
131
+ - `clearPid` deletes the file if present; no-op otherwise.
132
+ (src/lib/process-tracker.js:22-27)
133
+
134
+ ## Rejected and Reverted
135
+
136
+ - None on record.
137
+
138
+ ## Change Log
139
+
140
+ - None on record.
@@ -0,0 +1,169 @@
1
+ ---
2
+ capability: harvest
3
+ last-updated: 2026-06-13
4
+ ---
5
+
6
+ # harvest
7
+
8
+ Capability spec for world-state classification and the authoring-to-pack pipeline.
9
+
10
+ ## Overview
11
+
12
+ This capability covers two closely related concerns:
13
+
14
+ 1. **World-state** -- classifying each authored area in the game root's `data/areas/` tree
15
+ as Clean, Edited, Fork, or WIP by reading only on-disk side-car facts. The `tapestry status`
16
+ command is the user-facing view of this classification.
17
+
18
+ 2. **Harvest** -- promoting authored area content from the game-root side-car tree into a
19
+ pack. Two sinks exist: the `git` sink renders the content into a linked pack repository,
20
+ bumps the pack version, and commits; the `file` sink renders into a temp build directory
21
+ and produces a portable `.tgz` at the current version without bumping.
22
+
23
+ The `sync-area` command is a deprecated alias for `harvest --sink git`. The `export-area`
24
+ command is a hidden deprecated alias for the same.
25
+
26
+ The area reference format is `namespace:area-id` (e.g. `example-pack:village-green`).
27
+ (src/lib/pack-resolve.js:8-13)
28
+
29
+ ## Behavior
30
+
31
+ ### World-state classification
32
+
33
+ The state vocabulary is defined in `src/lib/world-state.js` and maps to four values:
34
+ (src/lib/world-state.js:11)
35
+
36
+ - **WIP** -- `area.yaml` exists and its `area.flags` array contains the string `wip`.
37
+ Checked before all other states; a WIP area is not visible to players.
38
+ (src/lib/world-state.js:14-22) (src/lib/world-state.js:51)
39
+ - **Clean** -- no room files exist under `data/areas/<area>/rooms/*.yaml`, OR all existing
40
+ room files are byte-for-byte equal (via JSON round-trip of YAML) to their counterparts in
41
+ the linked pack directory. (src/lib/world-state.js:57-59) (src/lib/world-state.js:64-70)
42
+ - **Edited** -- at least one room file under the side-car tree either has no counterpart in
43
+ the pack or differs from it. The pack must be owned (namespace resolves to a linked pack).
44
+ (src/lib/world-state.js:62-70)
45
+ - **Fork** -- the area's namespace does not resolve to any linked pack the user owns (the
46
+ area belongs to an official or foreign pack). (src/lib/world-state.js:63-65)
47
+
48
+ Namespace is inferred from the `id` field of the first room file (`ns:key` format).
49
+ (src/lib/world-state.js:44-49)
50
+
51
+ `computeAreaStates` iterates `data/areas/` directories alphabetically. It never queries the
52
+ registry and never reads the engine's runtime state. (src/lib/world-state.js:26-77)
53
+
54
+ #### status command
55
+
56
+ Prints one line per area: `<State padded to 7> <area-dir> (<namespace>) rooms:<count> [WIP]`.
57
+ (src/commands/status.js:13-15)
58
+
59
+ Prints a legend line: "Clean = shipped; Edited = ready to harvest; Fork = owned by another
60
+ pack; WIP = hidden from players." (src/commands/status.js:18)
61
+
62
+ Prints "Strict boot remains the authority; this is a lighter on-host view."
63
+ (src/commands/status.js:19)
64
+
65
+ With `--game-root <path>`, reads `data/areas/` from that path instead of the current
66
+ directory. (src/commands/status.js:5)
67
+
68
+ ### Harvest -- sink selection
69
+
70
+ `harvest` auto-detects the sink when `--sink` is not given: it calls `resolvePackDirOrNull`
71
+ for the area's namespace; if exactly one linked pack matches AND that directory is a git repo,
72
+ the git sink is used; otherwise the file sink is used.
73
+ (src/commands/harvest.js:15-19)
74
+
75
+ With `--sink git` or `--sink file`, the choice is explicit; any other value throws.
76
+ (src/commands/harvest.js:20-33)
77
+
78
+ ### Harvest -- git sink (`sync-area`)
79
+
80
+ The git sink delegates entirely to `syncArea`.
81
+ (src/commands/harvest.js:21-23)
82
+
83
+ - Requires a linked pack for the namespace; throws if none or multiple match.
84
+ (src/lib/pack-resolve.js:44-55)
85
+ - Requires `pack.yaml` in the target pack directory. (src/commands/sync-area.js:22-25)
86
+ - Asserts that the pack's namespace (derived from `manifest.name` by replacing `@scope/`
87
+ separator with `-`) matches the area namespace. Throws on mismatch.
88
+ (src/lib/render-core.js:91-101) (src/lib/pack-resolve.js:17-20)
89
+ - Calls `renderArea` to fold the side-car rooms into the pack (see below).
90
+ (src/commands/sync-area.js:29)
91
+ - Bumps `pack.yaml` version using `semver.inc` at the requested level (default: patch).
92
+ (src/lib/pack-manifest.js:71-85) (src/commands/sync-area.js:31)
93
+ - If the pack directory is a git repo, commits all changes with the message
94
+ `content(<area>): sync authored edits, bump <old> -> <new>`.
95
+ (src/commands/sync-area.js:33-38)
96
+ - If the pack directory is not a git repo, bumps but does not commit; warns.
97
+ (src/commands/sync-area.js:39-41)
98
+ - `--minor` and `--major` flags set the bump level; default is patch.
99
+ (bin/tapestry.js:406-408)
100
+ - Prints "To publish + deploy, push the pack repo" with the pack directory path.
101
+ (src/commands/sync-area.js:46-48)
102
+
103
+ ### Harvest -- file sink
104
+
105
+ The file sink never bumps the version; it captures an exact snapshot at the manifest's
106
+ current version. (src/lib/file-sink.js:19-21)
107
+
108
+ - If a linked pack exists for the namespace, copies the entire pack directory (excluding
109
+ `.git`, `node_modules`, `.DS_Store`, and `.tgz` files) into a temp build directory.
110
+ The rendered area content is then folded into that copy.
111
+ (src/lib/file-sink.js:30-39) (src/lib/tarball-builder.js:7)
112
+ - If no linked pack exists (hobbyist / no owned repo), synthesizes a minimal `pack.yaml`
113
+ using the namespace to derive `@scope/name`. (src/lib/file-sink.js:36-38)
114
+ (src/lib/pack-manifest.js:53-68)
115
+ - Calls `renderArea` to fold the side-car rooms into the temp build directory.
116
+ (src/lib/file-sink.js:39)
117
+ - Builds a `.tgz` from the temp dir. Output path defaults to `<shortName>-<version>.tgz`
118
+ in the current directory; overridden with `--out <path>`. (src/lib/file-sink.js:43-49)
119
+ - Cleans up the temp directory in a `finally` block. (src/lib/file-sink.js:56-58)
120
+ - Prints "This .tgz is a portable, installable pack" after completion.
121
+ (src/lib/file-sink.js:55-56)
122
+ - `--name <@scope/pack>` overrides the synthesized pack name (file sink only; only
123
+ meaningful when no linked pack is found). (src/lib/file-sink.js:36)
124
+
125
+ ### renderArea (shared by both sinks)
126
+
127
+ - Reads room YAML files from `<gameRoot>/data/areas/<area>/rooms/*.yaml`.
128
+ Throws if the directory is absent or empty. (src/lib/render-core.js:13-20)
129
+ - Copies `area.yaml` from the game-root side-car if present; creates a minimal one if absent
130
+ and the target does not already have one. (src/lib/render-core.js:26-35)
131
+ - Writes each room file to `<targetDir>/areas/<area>/rooms/<file>`, creating directories as
132
+ needed. (src/lib/render-core.js:37-50)
133
+ - Without `--force`: throws if a room file in the target differs from the incoming side-car.
134
+ (src/lib/render-core.js:41-45)
135
+ - With `--force`: overwrites divergent room files silently. (src/lib/render-core.js:40-49)
136
+ - After writing rooms, copies oracle side-car files: `places-oracle.yaml` at the area root and
137
+ any `*-oracle-table.yaml` files anywhere under the area tree (including inside `mobs/` and
138
+ `items/` subdirectories). The same divergence guard (throw without `--force`, overwrite with
139
+ `--force`) applies. (src/lib/render-core.js) (src/lib/pack-manifest.js)
140
+ - After copying oracle side-cars, copies all `*.yaml` files from `mobs/` and `items/` under the
141
+ area side-car tree into the corresponding `areas/<area>/mobs/` and `areas/<area>/items/`
142
+ directories in the target pack, including any `*-oracle-table.yaml` files in those directories.
143
+ The same divergence guard applies. (src/lib/render-core.js)
144
+ - After writing, calls `ensureContentGlobs` to additively add `area_definitions`, `rooms`,
145
+ `oracle_tables`, `places_oracle`, `mobs`, and `items` glob entries to `pack.yaml` if not
146
+ already present. (src/lib/render-core.js) (src/lib/pack-manifest.js)
147
+ - `reconcileDependencies` is a deliberate no-op: rooms are self-contained; the seam is wired
148
+ for a future slice that will scan for cross-pack references in mobs/items/quests.
149
+ (src/lib/render-core.js)
150
+
151
+ ### Side-car removal (default behavior)
152
+
153
+ After rendering, unless `--keep-sidecars` is given, `removeSideCars` deletes the room files
154
+ from the game-root side-car tree and prunes the `rooms/` and area directories if they become
155
+ empty. `area.yaml` in the game root is also deleted. (src/lib/render-core.js:69-88)
156
+
157
+ ## Rejected and Reverted
158
+
159
+ - `sync-area` was the original name for `harvest --sink git`. It prints a deprecation warning
160
+ and delegates to `harvest` with `sink: 'git'` forced.
161
+ (bin/tapestry.js:412-425)
162
+ - `export-area` is a hidden alias with the same deprecation path.
163
+ (bin/tapestry.js:428-439)
164
+
165
+ ## Change Log
166
+
167
+ - 2026-06-23 (0.11.0): renderArea carries oracle table side-cars (places-oracle.yaml, *-oracle-table.yaml)
168
+ and mints mob/item instance files (mobs/*.yaml, items/*.yaml) into the target pack alongside
169
+ area.yaml and rooms/. CONTENT_GLOBS gains oracle_tables, places_oracle, mobs, items.
@@ -0,0 +1 @@
1
+ {}