@hiveku-apps/sync 0.3.6 → 0.4.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/README.md CHANGED
@@ -177,10 +177,32 @@ cd projects/<slug>
177
177
  hiveku-sync pull-env # secrets → .env.local (git-ignored)
178
178
  # ...edit files, run the dev server...
179
179
  hiveku-sync status # diff local vs the deploy source
180
- hiveku-sync push # save changes to the deploy source
180
+ hiveku-sync push -m "Updated the pricing section on the Home page"
181
+ # save changes, then save them as one version
181
182
  hiveku-sync push --deploy development --wait # ...then deploy and wait
182
183
  ```
183
184
 
185
+ `pull-project` pages through the site (`project_vcs_checkout` with `limit`,
186
+ then `cursor`), so a site of any size comes down, including the ones over
187
+ 150 MB that cannot be sent in one answer. `--branch <name>` pulls that
188
+ branch's working tree instead of Your site; `.hiveku/projects.json` records
189
+ the branch, the version the pull came from (`head_commit_id`) and, on a
190
+ branch, its `working_tree_etag`. Later `push`, `status`, `commit`,
191
+ `versions` and `rollback` in that folder act on the same branch unless you
192
+ pass `--branch` (`--branch main` for Your site).
193
+
194
+ Each pull also records the files it wrote (`.hiveku/pulled/<slug>.json`). The
195
+ next pull moves the ones its tree no longer has (another branch's files, files
196
+ deleted on Hiveku) out of the project folder into
197
+ `.hiveku/set-aside/<slug>/<time>/`, so a push can never send them back; files
198
+ you created yourself are listed, never moved. The run hints go to the project
199
+ folder's `.hiveku/RUN.md`, which push never sends.
200
+
201
+ On a branch, `push` compares the branch's `working_tree_etag` with the one
202
+ recorded at pull time. If someone saved on the branch since, it lists the files
203
+ that may hold their work and changes nothing (exit 1); pull again, or pass
204
+ `--force` to overwrite them.
205
+
184
206
  **`push` writes the deploy source, lane-routed.** This is the important part.
185
207
  `deploy_site` builds from `builder_code_versions` (+ the S3/CDN asset store) —
186
208
  **not** a GitHub branch. So `push`:
@@ -195,17 +217,65 @@ hiveku-sync push --deploy development --wait # ...then deploy and wait
195
217
  - diffs correctly: code via a single `project_files_status` call (so
196
218
  `only_remote` stays reliable — chunking it produces phantom deletions), assets
197
219
  via `assets_list` (so unchanged images aren't re-uploaded every push).
220
+ - sends each batch again with only the files that did not land (a large batch
221
+ can stop at the route's time budget and name the rest in `remaining_paths`),
222
+ and lists every file that still did not save.
223
+ - with `--include-deletes`, deletes only files this folder could hold: never
224
+ dot files (`.gitignore`, `.ai/memory.md`), build folders, or files Hiveku
225
+ manages (`soul.md`, `data/marketing/...`), which a pull never brings down.
198
226
 
199
227
  Flags: `--include-deletes` (remove code paths missing locally), `--no-assets` /
200
- `--assets-all`, `--commit "msg"` (also record a Hiveku-native `main` commit),
201
- `--deploy <env> [--wait]`, `--dry-run`.
228
+ `--assets-all`, `-m "name"` (the version name; `--commit "name"` still works),
229
+ `--no-version`, `--branch <name>`, `--force`, `--deploy <env> [--wait]`,
230
+ `--dry-run`. Images, fonts and videos live in one asset store shared by Your
231
+ site and every branch, so a push to a branch says so before it uploads them
232
+ (and in `--dry-run`). `--deploy` publishes what that tier is set to publish:
233
+ Your site, unless the tier is bound to the branch.
202
234
 
203
235
  ```bash
204
236
  hiveku-sync deploy <development|staging|production> [<slug>] [--wait]
205
237
  hiveku-sync pull-env [<slug>] [--target development|staging|production]
206
- hiveku-sync commit -m "msg" # legacy GitHub-branch commit (GitHub-connected projects only)
207
238
  ```
208
239
 
240
+ ## Versions
241
+
242
+ "Your site" is the project's main source: what the preview shows and what a
243
+ deploy ships. A save is live there at once, but it is not a **version** until
244
+ one is saved, and a version is what you can go back to.
245
+
246
+ - **`push` saves one version** after every file has landed:
247
+ `project_vcs_commit` with no files and no deletions (source `sync_cli`),
248
+ which saves everything on Your site (or the branch) not yet in a version,
249
+ from every writer (the Hiveku AI, the editor, other tools). So the name comes
250
+ from `-m`, otherwise from everything `project_vcs_status` says is waiting
251
+ ("Added the Pricing page and edited the Home page"), or only from the pages
252
+ the push changed on a server that cannot say. The first version after a
253
+ version from before versions of Your site is named by Hiveku ("Your site up
254
+ to <date>"). `--no-version` skips it.
255
+ - **Names are read by the site owner.** Describe what changed for visitors;
256
+ never file paths, file extensions, `fix:`-style prefixes or tool names.
257
+ A typed name that breaks this is refused before anything is written.
258
+ - **Exit codes:** `0` done; `1` some files did not save, or the push was
259
+ refused (they are listed, and there is no version and no deploy); `2` every
260
+ file IS saved but the version was not (the message says why and prints the
261
+ exact `hiveku-sync commit` that tries again); `3` the files and the version
262
+ ARE saved and only the deploy failed (`hiveku-sync deploy` tries again).
263
+
264
+ ```bash
265
+ hiveku-sync commit [-m "name"] # save what is waiting as one version (sends no files)
266
+ hiveku-sync commit --github -m "msg" # the old GitHub push (GitHub-connected projects only)
267
+ hiveku-sync versions [--limit 50] # the versions, newest first, and what is waiting
268
+ hiveku-sync rollback <version id> # dry run: what going back would change
269
+ ```
270
+
271
+ `rollback` is a dry run unless you add `--apply`, and `--apply` needs the
272
+ `--confirm` id the dry run printed (it prints the whole command), so it only goes
273
+ ahead if nobody changed the site since you looked. Rolling back writes a NEW
274
+ version equal to the old one, so it can be undone, saves unsaved changes as
275
+ their own version first, and never publishes: run `hiveku-sync deploy` after
276
+ it when the live site should match. Images, fonts and videos in the shared
277
+ asset store, the database, CMS entries and secrets are not part of versions.
278
+
209
279
  `pull-env` applies the same `_DEV` / `_PROD` / `_STAGING` suffix resolution the
210
280
  preview machine uses: for `development` it strips `_DEV` to the base key and
211
281
  skips `_PROD`/`_STAGING`; `--target production` does the inverse.
@@ -245,7 +315,7 @@ suggested it and when. The two account-memory rows (`account` and
245
315
  `account-suggestions`) are never written under `memory/`, even when an older
246
316
  server still lists them.
247
317
 
248
- Nothing uploads this file: `push`, `commit` and `status` leave any
318
+ Nothing uploads this file: `push`, `commit --github` and `status` leave any
249
319
  `hiveku-data/account/` folder out, and the next `pull` replaces it. To add a
250
320
  fact, suggest it with the `account_memory_append` tool; an owner keeps or
251
321
  removes it on the dashboard.
@@ -18,6 +18,7 @@ import {
18
18
  runProjectPush,
19
19
  } from '../src/projects.mjs';
20
20
  import { runPush, runDeploy, runPullEnv } from '../src/push.mjs';
21
+ import { runRollback, runVersionCommit, runVersions } from '../src/versions.mjs';
21
22
  import { pathToFileURL } from 'node:url';
22
23
 
23
24
  export function parseFlags(argv) {
@@ -34,6 +35,9 @@ export function parseFlags(argv) {
34
35
  'branch',
35
36
  'profile',
36
37
  'parent',
38
+ 'confirm',
39
+ 'fingerprint',
40
+ 'limit',
37
41
  ]);
38
42
  for (let i = 0; i < argv.length; i++) {
39
43
  const arg = argv[i];
@@ -89,27 +93,59 @@ Memory/rules/skills:
89
93
 
90
94
  Coder projects:
91
95
  projects List the account's coder projects.
92
- pull-project <slug-or-id> Pull a project's source into ./projects/<slug>/.
93
- [--branch=<name>] Records the project mapping in .hiveku/projects.json.
96
+ pull-project <slug-or-id> Pull Your site's files (or, with --branch, that
97
+ [--branch=<name>] branch's working tree) into ./projects/<slug>/,
98
+ page by page (any size). Records the project,
99
+ branch and version in .hiveku/projects.json.
100
+ Files an earlier pull wrote that are not in
101
+ this one move to .hiveku/set-aside/<slug>/.
94
102
  status [<slug>] [--json] Diff local ./projects/<slug>/ vs the deploy source
95
- (builder_code_versions). cd in to omit <slug>.
103
+ [--branch=<name>] (builder_code_versions), or the pulled branch.
104
+ cd in to omit <slug>.
96
105
 
97
106
  Ship a project (the agent-native deploy loop):
98
107
  push [<slug>] Save local changes to the DEPLOY SOURCE, lane-routed:
99
- [--include-deletes] images/fonts/video under public/<subdir>/ → the
100
- [--no-assets] [--assets-all] S3/CDN asset store; everything else → batched,
101
- [--commit "<msg>"] verified bulk_save. This is what deploy reads.
102
- [--deploy <env>] [--wait] --commit also records a Hiveku 'main' commit.
103
- [--dry-run] [--branch=<name>] --deploy <env> [--wait] deploys after the push.
108
+ [-m "<version name>"] images/fonts/video under public/<subdir>/ → the
109
+ [--no-version] S3/CDN asset store; everything else → batched,
110
+ [--include-deletes] verified bulk_save. This is what deploy reads.
111
+ [--no-assets] [--assets-all] Then saves ONE version of Your site (or of the
112
+ [--deploy <env>] [--wait] branch) named by -m, or from the pages changed.
113
+ [--dry-run] [--branch=<name>] Names are plain language for the site owner (no
114
+ [--force] file paths, extensions or "fix:" prefixes).
115
+ --no-version skips the version. --deploy <env>
116
+ [--wait] deploys after the push. --branch writes
117
+ to that branch (default: the branch pulled);
118
+ if someone saved on it since the pull, push
119
+ refuses unless --force.
120
+ Exit 0 done; 1 some files did not save or the
121
+ push was refused (no version, no deploy); 2
122
+ files ARE saved but the version was not (it
123
+ prints the \`commit\` that retries it); 3 files
124
+ and version ARE saved, the deploy failed.
104
125
  deploy <env> [<slug>] [--wait] Deploy to development | staging | production and
105
126
  (with --wait) poll to a terminal status.
106
127
  pull-env [<slug>] Write project secrets → .env.local (git-ignored),
107
128
  [--target <env>] with the same _DEV/_PROD/_STAGING resolution the
108
129
  preview uses. --target staging|production for the
109
130
  other tiers.
110
- commit [<slug>] -m "<msg>" GitHub-branch commit via project_commit (only for
111
- [--branch=<name>] GitHub-connected projects; most sites use push).
112
- [--include-deletes]
131
+
132
+ Versions (Your site = the project's main source; rollback never publishes):
133
+ commit [<slug>] [-m "<name>"] Save what is on Hiveku and not yet in a version as
134
+ [--branch=<name>] one version (no files are sent: push first).
135
+ [--dry-run] Without -m it is named from what changed. Exit 2
136
+ when the version could not be saved.
137
+ commit --github -m "<msg>" The old GitHub push via project_commit (only for
138
+ [--branch=<name>] GitHub-connected projects).
139
+ [--include-deletes]
140
+ versions [<slug>] [--branch=<b>] The versions, newest first, and what is waiting.
141
+ [--limit N] [--json]
142
+ rollback <version id> [<slug>] Show what going back to that version would change.
143
+ [--branch=<name>] Nothing changes (a dry run). It prints the exact
144
+ [--apply --confirm <id>] --apply command, with the --confirm id it saw;
145
+ [--fingerprint <fp>] applying writes a NEW version equal to the old one
146
+ [-m "<name>"] (so it can be undone) and is refused if the site
147
+ changed since the dry run. Publish separately
148
+ with deploy.
113
149
 
114
150
  help Show this message.
115
151
 
@@ -140,19 +176,25 @@ function userToolTablesLines({ userToolTables = [], userTablesWithoutApprovalMod
140
176
  return lines;
141
177
  }
142
178
 
143
- function commitMessageFrom(flags) {
144
- // --commit "msg" | --commit=msg | -m "msg" | --message "msg". A bare --commit
145
- // (boolean true) means "commit with no message" → fall back to the default.
179
+ export function commitMessageFrom(flags) {
180
+ // --commit "msg" | --commit=msg | -m "msg" | --message "msg": the version
181
+ // name. A bare --commit (boolean true, from before versions were the
182
+ // default) names nothing: the version is then named from what changed.
146
183
  const c = typeof flags['commit'] === 'string' ? flags['commit'] : undefined;
147
184
  const m = typeof flags['m'] === 'string' ? flags['m'] : undefined;
148
185
  const msg = typeof flags['message'] === 'string' ? flags['message'] : undefined;
149
186
  if (c) return c;
150
187
  if (m) return m;
151
188
  if (msg) return msg;
152
- if (flags['commit'] === true) return 'hiveku-sync: push';
153
189
  return undefined;
154
190
  }
155
191
 
192
+ /** A command's exit code, from the result object it returned (0 when it says nothing). */
193
+ function setExitCode(result) {
194
+ const code = result && typeof result.exit_code === 'number' ? result.exit_code : 0;
195
+ if (code) process.exitCode = code;
196
+ }
197
+
156
198
  async function main() {
157
199
  const [cmd, ...rest] = process.argv.slice(2);
158
200
  const { positional, flags } = parseFlags(rest);
@@ -257,12 +299,13 @@ async function main() {
257
299
  slug: positional[0],
258
300
  profile: flags['profile'],
259
301
  json: !!flags['json'],
302
+ branch: flags['branch'],
260
303
  });
261
304
  break;
262
305
  }
263
306
  case 'push': {
264
307
  const deployEnv = typeof flags['deploy'] === 'string' ? flags['deploy'] : undefined;
265
- await runPush({
308
+ const result = await runPush({
266
309
  slug: positional[0],
267
310
  profile: flags['profile'],
268
311
  commit: commitMessageFrom(flags),
@@ -273,7 +316,10 @@ async function main() {
273
316
  assetsAll: !!flags['assets-all'],
274
317
  dryRun: !!flags['dry-run'],
275
318
  branch: flags['branch'],
319
+ noVersion: !!flags['no-version'],
320
+ force: !!flags['force'],
276
321
  });
322
+ setExitCode(result);
277
323
  break;
278
324
  }
279
325
  case 'deploy': {
@@ -301,21 +347,63 @@ async function main() {
301
347
  break;
302
348
  }
303
349
  case 'commit': {
304
- // The legacy GitHub-branch commit path (project_commit). Requires a repo.
305
- const message = commitMessageFrom(flags);
306
- if (!message || message === 'hiveku-sync: push') {
307
- console.error('Error: commit requires -m "<commit message>"\n');
308
- console.error(HELP);
309
- process.exit(1);
350
+ if (flags['github']) {
351
+ // The old GitHub-branch path (project_commit). Requires a connected repo.
352
+ const message = commitMessageFrom(flags);
353
+ if (!message) {
354
+ console.error('Error: commit --github requires -m "<message>"\n');
355
+ console.error(HELP);
356
+ process.exit(1);
357
+ }
358
+ await runProjectPush({
359
+ slug: positional[0],
360
+ message,
361
+ branch: flags['branch'],
362
+ profile: flags['profile'],
363
+ includeDeletes: !!flags['include-deletes'],
364
+ dryRun: !!flags['dry-run'],
365
+ });
366
+ break;
310
367
  }
311
- await runProjectPush({
368
+ // Default: save what is on Hiveku as one version (no files sent).
369
+ const result = await runVersionCommit({
312
370
  slug: positional[0],
313
- message,
371
+ name: commitMessageFrom(flags),
314
372
  branch: flags['branch'],
315
373
  profile: flags['profile'],
316
- includeDeletes: !!flags['include-deletes'],
317
374
  dryRun: !!flags['dry-run'],
318
375
  });
376
+ setExitCode(result);
377
+ break;
378
+ }
379
+ case 'versions': {
380
+ const result = await runVersions({
381
+ slug: positional[0],
382
+ branch: flags['branch'],
383
+ limit: typeof flags['limit'] === 'string' ? flags['limit'] : undefined,
384
+ json: !!flags['json'],
385
+ profile: flags['profile'],
386
+ });
387
+ setExitCode(result);
388
+ break;
389
+ }
390
+ case 'rollback': {
391
+ if (!positional[0]) {
392
+ console.error('Error: rollback requires a version id (see `hiveku-sync versions`).\n');
393
+ console.error(HELP);
394
+ process.exit(1);
395
+ }
396
+ const result = await runRollback({
397
+ versionId: positional[0],
398
+ slug: positional[1],
399
+ branch: flags['branch'],
400
+ apply: !!flags['apply'],
401
+ head: typeof flags['confirm'] === 'string' ? flags['confirm'] : undefined,
402
+ fingerprint: typeof flags['fingerprint'] === 'string' ? flags['fingerprint'] : undefined,
403
+ name: commitMessageFrom(flags),
404
+ profile: flags['profile'],
405
+ });
406
+ setExitCode(result);
319
407
  break;
320
408
  }
321
409
  case 'help':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hiveku-apps/sync",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Sync a Hiveku account's memory/rules/skills + coder projects into a local folder, and ship projects (push/deploy/pull-env) from the terminal. Pairs with the Hiveku MCP at core.hiveku.com/mcp so Claude Code can drive Hiveku tools per-folder.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  {
2
- "source": "locus2012/hiveku-codex-plugin 0.1.16 (5b4e580), plugins/hiveku/.mcp.json: every hiveku.tools entry (all approval_mode \"prompt\")",
2
+ "source": "locus2012/hiveku-codex-plugin 0.1.16 (5b4e580), plugins/hiveku/.mcp.json: every hiveku.tools entry (all approval_mode \"prompt\"); plus project_vcs_rollback (Versions Wave 2, hiveku-codex-plugin feature/versions-wave2)",
3
3
  "why": "hiveku-sync writes its own [mcp_servers.hiveku] into a Codex folder. Codex then uses that entry, not the plugin's, for the server's connection and for every tool's approval mode, so these prompts must be written here too. Refresh this list whenever the Codex plugin's prompt list changes.",
4
4
  "tools": [
5
5
  "deploy_site",
@@ -13,6 +13,7 @@
13
13
  "project_vcs_stash",
14
14
  "project_vcs_prune",
15
15
  "project_vcs_branch_preview_teardown",
16
+ "project_vcs_rollback",
16
17
  "project_vcs_revert",
17
18
  "hiveku_batch",
18
19
  "project_checkpoint_restore",
package/src/codex.mjs CHANGED
@@ -43,7 +43,7 @@ import { loadDotenv, safeSlug } from './util.mjs';
43
43
  * The FIXED list of high-risk Hiveku tools that Codex asks about before
44
44
  * running. It is the Hiveku Codex plugin's own prompt list, copied name for
45
45
  * name (codex-prompt-tools.json says which release; 246 names at plugin
46
- * 0.1.16). It includes hiveku_batch, because Codex asks by the name of the tool
46
+ * 0.1.16, plus project_vcs_rollback from Versions Wave 2). It includes hiveku_batch, because Codex asks by the name of the tool
47
47
  * it calls and a batch carries other tools.
48
48
  *
49
49
  * It is a list of names, not a category. Every tool not on it runs without
@@ -72,11 +72,11 @@ import { loadDotenv, safeSlug } from './util.mjs';
72
72
  * default_tools_approval_mode = "approve" here, every Hiveku tool ran without
73
73
  * a prompt, with or without the plugin installed.
74
74
  *
75
- * Adding another prompt list (the unmerged Versions Wave 2 branch has
76
- * RESTORE_TOOLS, for example): put its names into this one Set, so each tool
75
+ * Adding another prompt list: put its names into this one Set, so each tool
77
76
  * still gets exactly one table. A second block of tables would define some
78
- * tables twice (four of Wave 2's five names are already here), and that makes
79
- * the whole config.toml invalid TOML.
77
+ * tables twice, and that makes the whole config.toml invalid TOML. (Versions
78
+ * Wave 2 did this: its five roll-back tools are in codex-prompt-tools.json,
79
+ * project_vcs_rollback included.)
80
80
  *
81
81
  * The user's own table wins: a listed tool that already has its own
82
82
  * [mcp_servers.hiveku.tools.<tool>] table outside the managed block gets no
@@ -148,6 +148,13 @@ Non-negotiables (also in CLAUDE.md, restated because they are load-bearing):
148
148
  — \`changed\` = they edited it, \`only_remote\` = they added files you lack). Before any tree-replace
149
149
  (\`delete_missing: true\`), \`dry_run\` first and READ the would-delete list — a file you did not send
150
150
  may be someone else's NEW work. Never blind-overwrite.
151
+ - **Version every change.** After saving and verifying, call \`project_vcs_commit({ project_id, message })\`
152
+ with NO files, once per change, before \`deploy_site\` (or \`hiveku-sync push -m "..."\`, which does both);
153
+ 409 \`nothing_to_commit\` means it is already a version: not an error.
154
+ The name is plain language for the site owner ("Updated the pricing section on the Home page"): never file
155
+ paths, extensions, \`fix:\` prefixes or tool names. Roll back with \`project_vcs_rollback\`: dry run first,
156
+ apply only after the user agrees, with the dry run's \`head_commit_id\` as \`expected_head_commit_id\` (on Your
157
+ site also its \`live_fingerprint\` as \`expected_live_fingerprint\`); publishing is a separate \`deploy_site\`.
151
158
  - **Keep ALL scratch work in \`.hiveku/tmp/\`** — never \`/tmp\` (shared across accounts), your home
152
159
  dir, or the repo root.
153
160
  - **Never ingest local agent config into a project push.** \`.codex/config.toml\`, \`.mcp.json\`, and
package/src/init.mjs CHANGED
@@ -96,7 +96,7 @@ Hiveku dashboard under Account memory.
96
96
 
97
97
  Then run \`hiveku-sync pull\` to refresh the local copies.
98
98
 
99
- ## Coder projects (pull / status / push)
99
+ ## Coder projects (pull / push / versions)
100
100
 
101
101
  Each account has zero or more coder projects (Next.js, Vite, Python Lambda,
102
102
  Expo). To work on one locally:
@@ -105,25 +105,54 @@ Expo). To work on one locally:
105
105
  hiveku-sync projects # list the account's projects
106
106
  hiveku-sync pull-project <slug-or-id> # snapshot into ./projects/<slug>/
107
107
  cd projects/<slug>
108
- # follow the run instructions in RUN.md (npm install && npm run dev for nextjs, etc.)
108
+ # follow the run instructions in .hiveku/RUN.md (npm install && npm run dev for nextjs, etc.)
109
109
  \`\`\`
110
110
 
111
- This is **not a git clone** — the local folder is a snapshot. To check whether
112
- edits are in sync with the Hiveku DB, and to commit changes back to the
113
- project's connected GitHub branch (same path the v3 sidebar uses):
111
+ This is **not a git clone** — the local folder is a snapshot of **Your site**
112
+ (the project's main source; \`--branch <name>\` pulls a branch instead).
113
+ \`push\` saves your edits to Hiveku and then saves them as ONE **version**, which
114
+ can be rolled back later:
114
115
 
115
116
  \`\`\`bash
116
- hiveku-sync status # diff local vs Hiveku DB
117
- hiveku-sync push -m "fix: header overlap" # save + commit changed files via MCP
117
+ hiveku-sync status # diff local vs Hiveku
118
+ hiveku-sync push -m "Updated the pricing section on the Home page"
119
+ hiveku-sync versions # the version history, newest first
120
+ hiveku-sync rollback <version id> # dry run: what going back would change
121
+ hiveku-sync deploy production # publishing is always its own step
118
122
  \`\`\`
119
123
 
120
- You can also ask Claude in this workspace to:
121
- - "Pull project <slug>"
122
- - "Show me which files are out of sync with Hiveku"
123
- - "Commit the changes in projects/<slug> to the dev branch with message '...'"
124
-
125
- Claude has the \`project_files_list\`, \`project_file_get\`, \`project_files_status\`,
126
- and \`project_commit\` MCP tools available under the **${profile}** profile.
124
+ - **Version names are read by the site owner.** Describe what changed for their
125
+ visitors ("Added a contact form to the About page"): never file paths, file
126
+ extensions, \`fix:\`-style prefixes or tool names. A version holds everything
127
+ waiting on Your site, including the Hiveku AI's and the editor's changes, so
128
+ without \`-m\` push names it from all of that; \`--no-version\` skips it.
129
+ - **One version per change the owner would recognise**: push as often as you
130
+ like with \`--no-version\`, then save the version once (\`hiveku-sync commit -m "..."\`)
131
+ before you deploy.
132
+ - **Exit codes:** 0 done; 1 some files did not save, or the push was refused
133
+ (listed; no version, no deploy); 2 every file IS saved but the version was not
134
+ (it prints the \`hiveku-sync commit\` that retries it); 3 the files and the
135
+ version ARE saved and only the deploy failed.
136
+ - **On a branch, someone else may have saved since you pulled.** push then
137
+ refuses and lists the files that may hold their work: pull again (it replaces
138
+ your local copies, so keep your edits somewhere first), or \`--force\` to
139
+ overwrite. A pull moves the files an earlier pull wrote that are not on what
140
+ it pulls now (another branch's, or deleted on Hiveku) out of the project folder
141
+ into \`.hiveku/set-aside/\`, so they are never pushed back by mistake.
142
+ - **Rolling back** writes a NEW version equal to the old one, so it can be undone,
143
+ and it never publishes. Run the dry run, show the owner what it changes, and only
144
+ after they agree run the \`--apply\` command it prints. Then offer \`deploy\`
145
+ separately.
146
+
147
+ Through the MCP tools the same loop is: save (\`project_files_bulk_save\` /
148
+ \`project_file_save\`), verify, then \`project_vcs_commit({ project_id, message })\`
149
+ with **no files** (one version, plain-language name; 409 \`nothing_to_commit\`
150
+ means it is already a version), then \`deploy_site\`.
151
+ \`project_vcs_status\` says whether changes are waiting to be saved as a version;
152
+ \`project_vcs_rollback\` rolls back (dry run by default; applying needs the dry
153
+ run's \`head_commit_id\` as \`expected_head_commit_id\`, and on Your site its
154
+ \`live_fingerprint\` as \`expected_live_fingerprint\`). The \`project_*\` tools come
155
+ with the \`dev\` and \`full\` profiles (this folder uses **${profile}**).
127
156
 
128
157
  ## When Hiveku itself gets in your way
129
158