@tapestry-mud/cli 0.8.0 → 0.10.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
@@ -31,6 +31,9 @@ const { distTagSet, distTagList } = require('../src/commands/dist-tag');
31
31
  const { presetSet, presetDelete } = require('../src/commands/preset');
32
32
  const { trustAdd, trustList, trustRm } = require('../src/commands/trust');
33
33
  const { syncArea } = require('../src/commands/sync-area');
34
+ const { harvest } = require('../src/commands/harvest');
35
+ const { status } = require('../src/commands/status');
36
+ const { types } = require('../src/commands/types');
34
37
 
35
38
  const program = new Command();
36
39
 
@@ -60,7 +63,7 @@ program.configureHelp({
60
63
  },
61
64
  {
62
65
  title: 'Pack Authoring',
63
- commands: ['create', 'validate', 'pack', 'publish', 'unpublish', 'sync-area'],
66
+ commands: ['create', 'validate', 'pack', 'publish', 'unpublish', 'harvest', 'status', 'types'],
64
67
  },
65
68
  {
66
69
  title: 'Trusted Publishing',
@@ -361,12 +364,40 @@ program
361
364
  }
362
365
  });
363
366
 
364
- function runSyncArea(areaRef, opts) {
367
+ program
368
+ .command('status')
369
+ .description('Show world state per area (Clean / Edited / Fork / WIP)')
370
+ .option('--game-root <path>', 'Game root containing data/ (default: current dir)')
371
+ .action((opts) => {
372
+ try {
373
+ status({ cwd: process.cwd(), gameRoot: opts.gameRoot });
374
+ } catch (e) {
375
+ console.error(`error: ${e.message}`);
376
+ process.exit(1);
377
+ }
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
+
392
+ async function runHarvest(areaRef, opts) {
365
393
  try {
366
- syncArea(areaRef, {
394
+ await harvest(areaRef, {
367
395
  cwd: process.cwd(),
368
396
  gameRoot: opts.gameRoot,
369
397
  pack: opts.pack,
398
+ sink: opts.sink,
399
+ out: opts.out,
400
+ name: opts.name,
370
401
  force: opts.force,
371
402
  keepSidecars: opts.keepSidecars,
372
403
  bump: opts.major ? 'major' : opts.minor ? 'minor' : 'patch',
@@ -377,20 +408,38 @@ function runSyncArea(areaRef, opts) {
377
408
  }
378
409
  }
379
410
 
411
+ program
412
+ .command('harvest <areaRef>')
413
+ .description('Harvest an authored area into a portable pack (areaRef = namespace:area-id)')
414
+ .option('--sink <sink>', 'Output sink: file | git (auto-detected by default)')
415
+ .option('--out <path>', '(file sink) where the .tgz lands')
416
+ .option('--name <name>', '(file sink) override the synthesized pack name (@scope/pack)')
417
+ .option('--pack <dir>', 'Target pack directory (auto-detected from linked packs by default)')
418
+ .option('--game-root <path>', 'Game root containing data/ (default: current dir)')
419
+ .option('--keep-sidecars', 'Copy instead of move (leave the game-root side-cars in place)')
420
+ .option('--force', 'Overwrite pack files that diverge from the side-car')
421
+ .option('--minor', 'Bump the pack minor version (git sink only; default: patch)')
422
+ .option('--major', 'Bump the pack major version (git sink only; default: patch)')
423
+ .action(runHarvest);
424
+
425
+ // Deprecated: sync-area is now harvest --sink git.
380
426
  program
381
427
  .command('sync-area <areaRef>')
382
- .description('Commit a game-root authored area back into its pack (areaRef = namespace:area-id)')
428
+ .description('(deprecated) alias for harvest --sink git')
383
429
  .option('--pack <dir>', 'Target pack directory (auto-detected from linked packs by default)')
384
430
  .option('--game-root <path>', 'Game root containing data/ (default: current dir)')
385
431
  .option('--keep-sidecars', 'Copy instead of move (leave the game-root side-cars in place)')
386
432
  .option('--force', 'Overwrite pack files that diverge from the side-car')
387
433
  .option('--minor', 'Bump the pack minor version (default: patch)')
388
434
  .option('--major', 'Bump the pack major version (default: patch)')
389
- .action(runSyncArea);
435
+ .action((areaRef, opts) => {
436
+ console.warn('warning: `sync-area` is deprecated; use `harvest <area>` (auto-detects the git sink for an owned repo).');
437
+ runHarvest(areaRef, Object.assign({ sink: 'git' }, opts));
438
+ });
390
439
 
391
440
  program
392
441
  .command('export-area <areaRef>', { hidden: true })
393
- .description('(deprecated) alias for sync-area')
442
+ .description('(deprecated) alias for harvest --sink git')
394
443
  .option('--pack <dir>', 'Target pack directory (auto-detected from linked packs by default)')
395
444
  .option('--game-root <path>', 'Game root containing data/ (default: current dir)')
396
445
  .option('--keep-sidecars', 'Copy instead of move (leave the game-root side-cars in place)')
@@ -398,8 +447,8 @@ program
398
447
  .option('--minor', 'Bump the pack minor version (default: patch)')
399
448
  .option('--major', 'Bump the pack major version (default: patch)')
400
449
  .action((areaRef, opts) => {
401
- console.warn('warning: `export-area` is deprecated; use `sync-area`.');
402
- runSyncArea(areaRef, opts);
450
+ console.warn('warning: `export-area` is deprecated; use `harvest <area> --sink git`.');
451
+ runHarvest(areaRef, Object.assign({ sink: 'git' }, opts));
403
452
  });
404
453
 
405
454
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapestry-mud/cli",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "CLI for the Tapestry MUD engine",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,8 @@
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"
@@ -23,9 +24,11 @@
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-13 |
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,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,159 @@
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, calls `ensureContentGlobs` to additively add `area_definitions` and `rooms`
137
+ glob entries to `pack.yaml` if not already present. (src/lib/render-core.js:52)
138
+ (src/lib/pack-manifest.js:7-31)
139
+ - `reconcileDependencies` is a deliberate no-op: rooms are self-contained; the seam is wired
140
+ for a future slice that will scan for cross-pack references in mobs/items/quests.
141
+ (src/lib/render-core.js:63-65)
142
+
143
+ ### Side-car removal (default behavior)
144
+
145
+ After rendering, unless `--keep-sidecars` is given, `removeSideCars` deletes the room files
146
+ from the game-root side-car tree and prunes the `rooms/` and area directories if they become
147
+ empty. `area.yaml` in the game root is also deleted. (src/lib/render-core.js:69-88)
148
+
149
+ ## Rejected and Reverted
150
+
151
+ - `sync-area` was the original name for `harvest --sink git`. It prints a deprecation warning
152
+ and delegates to `harvest` with `sink: 'git'` forced.
153
+ (bin/tapestry.js:412-425)
154
+ - `export-area` is a hidden alias with the same deprecation path.
155
+ (bin/tapestry.js:428-439)
156
+
157
+ ## Change Log
158
+
159
+ - None on record.
@@ -0,0 +1 @@
1
+ {}