hafez 1.0.1 → 1.0.2

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
@@ -16,11 +16,9 @@ Claude Code (or any agent with shell access):
16
16
  ```text
17
17
  Set up Hafez, an agent-native knowledge vault (github.com/shrvn-r/hafez):
18
18
 
19
- 1. Install the CLI (needs Node.js 22+ and git):
20
- npm install -g https://github.com/shrvn-r/hafez/releases/latest/download/hafez.tgz
21
- On npm 12+, append --allow-remote=root --allow-scripts=better-sqlite3
22
- (npm 12 blocks URL-tarball installs and install scripts by default;
23
- the SQLite bindings need their install script).
19
+ 1. Install the CLI (needs Node.js 22+ and git): npm install -g hafez
20
+ On npm 12+, append --allow-scripts=better-sqlite3 (npm 12 blocks install
21
+ scripts by default; the SQLite bindings need theirs).
24
22
  2. Check git identity is configured (git config user.name and user.email);
25
23
  if not, ask me for a name and email and set them with git config --global.
26
24
  3. Create my vault — a git repo with entities/ and knowledge/ directories and an
@@ -43,8 +41,8 @@ The same thing by hand:
43
41
 
44
42
  ```bash
45
43
  # Install (Node.js 22+ and git required).
46
- # On npm 12+, append: --allow-remote=root --allow-scripts=better-sqlite3
47
- npm install -g https://github.com/shrvn-r/hafez/releases/latest/download/hafez.tgz
44
+ # On npm 12+, append: --allow-scripts=better-sqlite3
45
+ npm install -g hafez
48
46
 
49
47
  # One-time, if you've never used git on this machine:
50
48
  # git config --global user.name "Your Name"
@@ -61,9 +59,9 @@ mkdir -p ~/.claude/skills/hafez
61
59
  cp "$(npm root -g)/hafez/skill/SKILL.md" ~/.claude/skills/hafez/SKILL.md
62
60
  ```
63
61
 
64
- On npm ≤11, `npm install -g github:shrvn-r/hafez` also works (npm 12 disables
65
- git installs by default). An npm registry package is planned; GitHub is the
66
- distribution channel for now.
62
+ Prefer installing without the npm registry? The GitHub release tarball is the
63
+ same artifact: `npm install -g https://github.com/shrvn-r/hafez/releases/latest/download/hafez.tgz`
64
+ (npm 12+ additionally needs `--allow-remote=root`).
67
65
 
68
66
  ## Quick start
69
67
 
@@ -10,10 +10,7 @@ export declare function cmdCreate(os: Hafez, args: string[], opts?: CommandOpts)
10
10
  export declare function cmdUpdate(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
11
11
  export declare function cmdLink(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
12
12
  export declare function cmdUnlink(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
13
- /**
14
- * Top-level capture command — aliases to the same handler used by
15
- * `hafez create inbox`. Added to align the CLI verb with the batch op name.
16
- */
13
+ /** Top-level capture command — aligns the CLI verb with the batch op name. */
17
14
  export declare function cmdCapture(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
18
15
  export declare function cmdPromote(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
19
16
  export declare function cmdValidate(os: Hafez, _args: string[], opts?: CommandOpts): Promise<string>;
@@ -107,27 +104,26 @@ export declare const BatchOperationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
107
104
  slug: z.ZodString;
108
105
  target: z.ZodString;
109
106
  relation: z.ZodEnum<{
110
- related: "related";
111
107
  parent: "parent";
108
+ related: "related";
112
109
  }>;
113
110
  }, z.core.$strict>, z.ZodObject<{
114
111
  op: z.ZodLiteral<"unlink">;
115
112
  slug: z.ZodString;
116
113
  target: z.ZodString;
117
114
  relation: z.ZodEnum<{
118
- related: "related";
119
115
  parent: "parent";
116
+ related: "related";
120
117
  }>;
121
118
  }, z.core.$strict>, z.ZodObject<{
122
119
  op: z.ZodLiteral<"promote">;
123
120
  slug: z.ZodString;
124
121
  target: z.ZodEnum<{
125
122
  entity: "entity";
126
- knowledge: "knowledge";
127
123
  project: "project";
124
+ knowledge: "knowledge";
128
125
  }>;
129
126
  }, z.core.$strict>], "op">;
130
127
  export declare function parseBatchInput(json: string): BatchOperation[];
131
128
  export declare function cmdBatch(os: Hafez, args: string[], opts?: CommandOpts): Promise<string>;
132
129
  export declare function cmdDigest(os: Hafez, _args: string[], _opts?: CommandOpts): Promise<string>;
133
- export declare function cmdMigrateKnowledgeV2(args: string[], vaultPath: string): Promise<void>;
@@ -5,7 +5,6 @@ import { ENTITY_TYPES, ENTITY_STATUSES, SESSION_LOG_TYPES, CONFIDENCE_LEVELS, VA
5
5
  import { formatBatchError } from './batch-errors.js';
6
6
  import { parseDigestInput, digest } from '../digest.js';
7
7
  import { formatEntityHeader, formatKnowledgeHeader, formatQueryTable, formatKnowledgeTable, formatSearchResults, formatValidation, formatStats, formatChangelog } from './format.js';
8
- import { migrateKnowledgeV2 } from '../migrate-knowledge-v2.js';
9
8
  function jsonOut(data) {
10
9
  return JSON.stringify(data, null, 2);
11
10
  }
@@ -105,16 +104,7 @@ export async function cmdSearch(os, args, opts = {}) {
105
104
  export async function cmdCreate(os, args, opts = {}) {
106
105
  const kind = args[0];
107
106
  if (!kind)
108
- throw new Error('Usage: hafez create <entity|knowledge|inbox> <name> [options]');
109
- if (kind === 'inbox') {
110
- const name = positionalArgs(args.slice(1)).join(' ');
111
- if (!name)
112
- throw new Error('Usage: hafez create inbox <name> [--notes "text"]');
113
- const notes = getFlag(args, '--notes');
114
- process.stderr.write('Warning: `hafez create inbox` is deprecated. Use `hafez capture <name>` instead.\n');
115
- const slug = await os.capture(name, notes);
116
- return opts.json ? jsonOut({ slug }) : slug;
117
- }
107
+ throw new Error('Usage: hafez create <entity|knowledge> <name> [options]');
118
108
  const name = positionalArgs(args.slice(1)).join(' ');
119
109
  if (!name)
120
110
  throw new Error(`Usage: hafez create ${kind} <name> [options]`);
@@ -147,7 +137,7 @@ export async function cmdCreate(os, args, opts = {}) {
147
137
  });
148
138
  return opts.json ? jsonOut({ slug }) : slug;
149
139
  }
150
- throw new Error(`Unknown kind: ${kind}. Use entity, knowledge, or inbox.`);
140
+ throw new Error(`Unknown kind: ${kind}. Use entity or knowledge.`);
151
141
  }
152
142
  export async function cmdUpdate(os, args, opts = {}) {
153
143
  const slug = args[0];
@@ -157,13 +147,11 @@ export async function cmdUpdate(os, args, opts = {}) {
157
147
  const status = getFlag(args, '--status');
158
148
  if (status)
159
149
  fields.status = status;
160
- // Legacy --next-action: deprecation notice
161
- const nextAction = getFlag(args, '--next-action');
162
- if (nextAction !== undefined) {
163
- process.stderr.write('Warning: --next-action is deprecated. Use --add-action instead.\n');
164
- fields.add_action = nextAction === '' ? undefined : nextAction;
165
- if (nextAction === '')
166
- fields.clear_actions = true;
150
+ // Removed flag must fail loudly, not silently drop the value: older
151
+ // installed skills/scripts still pass it, and "Updated <slug>" with the
152
+ // action discarded is a false success.
153
+ if (args.includes('--next-action')) {
154
+ throw new Error('--next-action was removed in v1.0.2. Use --add-action (and --clear-actions) instead.');
167
155
  }
168
156
  const currentState = getFlag(args, '--current-state');
169
157
  if (currentState)
@@ -235,18 +223,10 @@ export async function cmdUpdate(os, args, opts = {}) {
235
223
  }
236
224
  return lines.join('\n');
237
225
  }
238
- /**
239
- * Parse link/unlink args, supporting both the legacy positional form
240
- * (`hafez link <slug> <target> <parent|related>`) and the new
241
- * `--relation <value>` named flag. Positional remains accepted as a
242
- * deprecated alias — emits a one-line warning to stderr.
243
- */
244
226
  function parseLinkArgs(args, verb) {
245
- const relationFlag = getFlag(args, '--relation');
227
+ const relation = getFlag(args, '--relation');
246
228
  // After getFlag the relation value is still at its original positional slot
247
- // (getFlag does NOT splice), so strip it manually to prevent it from being
248
- // mistaken for a positional if someone passes both forms. Here we only
249
- // consume the flag once above.
229
+ // (getFlag does NOT splice), so strip it manually before reading positionals.
250
230
  const positionals = args.filter((a, i) => {
251
231
  if (a === '--relation')
252
232
  return false;
@@ -254,18 +234,10 @@ function parseLinkArgs(args, verb) {
254
234
  return false;
255
235
  return !a.startsWith('--');
256
236
  });
257
- const [slug, target, positionalRelation] = positionals;
258
- if (!slug || !target) {
237
+ const [slug, target] = positionals;
238
+ if (!slug || !target || !relation) {
259
239
  throw new Error(`Usage: hafez ${verb} <slug> <target> --relation <parent|related>`);
260
240
  }
261
- let relation = relationFlag;
262
- if (!relation) {
263
- if (!positionalRelation) {
264
- throw new Error(`Usage: hafez ${verb} <slug> <target> --relation <parent|related>`);
265
- }
266
- relation = positionalRelation;
267
- process.stderr.write(`Warning: positional relation is deprecated. Use: hafez ${verb} ${slug} ${target} --relation ${relation}\n`);
268
- }
269
241
  if (!['parent', 'related'].includes(relation)) {
270
242
  throw new Error(`Invalid relation: ${relation}. Must be parent or related.`);
271
243
  }
@@ -281,10 +253,7 @@ export async function cmdUnlink(os, args, opts = {}) {
281
253
  await os.unlink(slug, target, relation);
282
254
  return opts.json ? jsonOut({ slug, target, relation }) : `Unlinked ${slug} — ${target} (${relation})`;
283
255
  }
284
- /**
285
- * Top-level capture command — aliases to the same handler used by
286
- * `hafez create inbox`. Added to align the CLI verb with the batch op name.
287
- */
256
+ /** Top-level capture command — aligns the CLI verb with the batch op name. */
288
257
  export async function cmdCapture(os, args, opts = {}) {
289
258
  const name = positionalArgs(args).join(' ');
290
259
  if (!name)
@@ -584,28 +553,6 @@ export async function cmdDigest(os, _args, _opts = {}) {
584
553
  const ops = digest(result.data, existingSlugs);
585
554
  return JSON.stringify(ops, null, 2);
586
555
  }
587
- export async function cmdMigrateKnowledgeV2(args, vaultPath) {
588
- const apply = args.includes('--apply');
589
- const report = migrateKnowledgeV2(vaultPath, { apply });
590
- console.log(`## Migration: knowledge-v2 ${apply ? '(APPLIED)' : '(dry run)'}`);
591
- console.log(`- Sessions to move: ${report.sessionsToMove.length}`);
592
- for (const f of report.sessionsToMove)
593
- console.log(` - ${f}`);
594
- console.log(`- Empty notes to delete: ${report.emptyToDelete.length}`);
595
- for (const f of report.emptyToDelete)
596
- console.log(` - ${f}`);
597
- console.log(`- ## Insight → ## Synthesis: ${report.insightToRename.length}`);
598
- for (const f of report.insightToRename)
599
- console.log(` - ${f}`);
600
- console.log(`- ## Related to generate: ${report.relatedToGenerate.length}`);
601
- for (const f of report.relatedToGenerate)
602
- console.log(` - ${f}`);
603
- console.log(`- .gitignore needs update: ${report.gitignoreNeeded}`);
604
- if (apply && report.gitCommitFailed)
605
- console.log('⚠ Warning: git commit failed — changes are applied but not committed.');
606
- if (!apply)
607
- console.log('\nRun with --apply to execute migration.');
608
- }
609
556
  // --- Arg helpers ---
610
557
  function getFlag(args, flag) {
611
558
  const idx = args.indexOf(flag);
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { createHafez } from '../index.js';
6
6
  import { HafezError } from '../types.js';
7
- import { cmdRead, cmdQuery, cmdSearch, cmdCreate, cmdUpdate, cmdLink, cmdUnlink, cmdPromote, cmdValidate, cmdIndexRebuild, cmdBatch, cmdSync, cmdStats, cmdChangelog, cmdDigest, cmdMigrateKnowledgeV2, cmdCapture } from './commands.js';
7
+ import { cmdRead, cmdQuery, cmdSearch, cmdCreate, cmdUpdate, cmdLink, cmdUnlink, cmdPromote, cmdValidate, cmdIndexRebuild, cmdBatch, cmdSync, cmdStats, cmdChangelog, cmdDigest, cmdCapture } from './commands.js';
8
8
  import { cmdSchema } from './commands-schema.js';
9
9
  import { renderAgentHelp } from './help-agent.js';
10
10
  import { resolveVaultPath, noVaultMessage, cmdInit } from './resolve-vault.js';
@@ -38,7 +38,7 @@ Commands:
38
38
  read <slug> Read entity or knowledge note
39
39
  query [--filter ...] ... Filter entities/knowledge
40
40
  search <terms> Full-text search
41
- create <kind> <name> Create entity, knowledge, or inbox
41
+ create <kind> <name> Create entity or knowledge note
42
42
  capture <name> [--notes] Quick inbox capture
43
43
  promote <slug> <target> Promote capture→entity/project/knowledge, entity→project
44
44
  update <slug> [--status] Update an entity or knowledge note
@@ -56,12 +56,8 @@ Commands:
56
56
  digest Apply digest input (JSON stdin) → batch payload (JSON stdout)
57
57
  export --okf [--out <dir>] Export vault as an OKF v0.1 bundle (read-only)
58
58
  sync Pull remote changes and push local commits
59
- migrate next-actions Migrate next-action frontmatter to body sections
60
- migrate types Migrate vault to v2 type system (dry-run by default)
61
- migrate knowledge-v2 Migrate knowledge notes to v2 section structure (dry-run by default)
62
- --apply Execute migration (writes files, does not commit)
63
- link <slug> <target> <rel> Add a link
64
- unlink <slug> <target> <rel> Remove a link
59
+ link <slug> <target> --relation <rel> Add a link
60
+ unlink <slug> <target> --relation <rel> Remove a link
65
61
  stats Vault summary: counts, stale items, recents
66
62
  changelog --since <when> Git-derived change history (e.g. --since 7.days.ago)
67
63
  schema [op] [--examples] Machine-readable JSON schema for batch operations
@@ -184,64 +180,6 @@ export async function main(argv) {
184
180
  }
185
181
  return;
186
182
  }
187
- // migrate runs before Hafez instance creation
188
- if (command === 'migrate') {
189
- const subcommand = args[0];
190
- if (subcommand === 'next-actions') {
191
- const { migrateNextActions } = await import('../migrate-next-actions.js');
192
- const apply = args.includes('--apply');
193
- const report = migrateNextActions(vaultPath, apply);
194
- const lines = [];
195
- if (!apply)
196
- lines.push('DRY RUN — use --apply to execute');
197
- lines.push(`Candidates: ${report.candidates}`);
198
- if (apply)
199
- lines.push(`Migrated: ${report.migrated}`);
200
- for (const d of report.details)
201
- lines.push(` ${d}`);
202
- process.stdout.write(lines.join('\n') + '\n');
203
- return;
204
- }
205
- if (subcommand === 'types') {
206
- const { migrateTypes } = await import('../migrate-types.js');
207
- const apply = args.includes('--apply');
208
- const report = migrateTypes(vaultPath, apply);
209
- const lines = [];
210
- if (!apply) {
211
- lines.push('DRY RUN — use --apply to execute');
212
- lines.push('');
213
- }
214
- lines.push(`Type renames: ${report.typeChanges}`);
215
- lines.push(`Status renames: ${report.statusChanges}`);
216
- lines.push(`Subtypes added: ${report.knowledgeSubtypeAdded}`);
217
- lines.push(`Body restructures: ${report.bodySectionsRestructured}`);
218
- lines.push(`Malformed (skipped): ${report.malformed.length}`);
219
- if (report.malformed.length > 0) {
220
- lines.push('');
221
- lines.push('Malformed files (no type field):');
222
- for (const f of report.malformed)
223
- lines.push(` ⚠ ${f}`);
224
- }
225
- if (report.details.length > 0) {
226
- lines.push('');
227
- lines.push('Changes:');
228
- for (const d of report.details)
229
- lines.push(` ${d}`);
230
- }
231
- if (apply) {
232
- lines.push('');
233
- lines.push('Migration applied. Run `hafez index rebuild` to refresh the index.');
234
- }
235
- process.stdout.write(lines.join('\n') + '\n');
236
- return;
237
- }
238
- if (subcommand === 'knowledge-v2') {
239
- await cmdMigrateKnowledgeV2(args.slice(1), vaultPath);
240
- return;
241
- }
242
- process.stderr.write('Usage: hafez migrate <next-actions|types|knowledge-v2> [--apply]\n');
243
- process.exit(1);
244
- }
245
183
  // sync is handled specially — always runs, not gated by MUTATING_COMMANDS
246
184
  if (command === 'sync') {
247
185
  const os = createHafez({ vaultPath, git: { push: false } });
package/dist/db.js CHANGED
@@ -88,7 +88,9 @@ function hashContent(content) {
88
88
  }
89
89
  function scanDir(dirPath) {
90
90
  try {
91
- return readdirSync(dirPath).filter(f => f.endsWith('.md')).map(f => join(dirPath, f));
91
+ // Skip dotfiles: their slugs are rejected by resolveFilePath, so indexing
92
+ // them would list entities that read/update can never reach.
93
+ return readdirSync(dirPath).filter(f => f.endsWith('.md') && !f.startsWith('.')).map(f => join(dirPath, f));
92
94
  }
93
95
  catch {
94
96
  return [];
@@ -401,46 +403,65 @@ export function createIndex(vaultPath, opts) {
401
403
  // The db files are a disposable local cache — keep them out of the user's
402
404
  // `git status` without dirtying their vault with a tracked .gitignore.
403
405
  ensureLocalExclude(vaultPath);
406
+ function removeDbFiles() {
407
+ // The -wal/-shm siblings must go too: a fresh db next to a stale WAL is
408
+ // itself corrupt.
409
+ for (const suffix of ['', '-wal', '-shm']) {
410
+ try {
411
+ unlinkSync(dbPath + suffix);
412
+ }
413
+ catch { /* may not exist */ }
414
+ }
415
+ }
416
+ function openDb() {
417
+ const fresh = new Database(dbPath);
418
+ try {
419
+ fresh.pragma('journal_mode = WAL');
420
+ fresh.pragma('busy_timeout = 5000');
421
+ fresh.pragma('foreign_keys = ON');
422
+ fresh.exec(SCHEMA);
423
+ return fresh;
424
+ }
425
+ catch (err) {
426
+ // Close before the caller unlinks — on filesystems without POSIX
427
+ // delete-while-open semantics, unlinking an open db fails silently and
428
+ // recovery would just reopen the same corrupt file.
429
+ try {
430
+ fresh.close();
431
+ }
432
+ catch { /* already unusable */ }
433
+ throw err;
434
+ }
435
+ }
404
436
  let db;
405
437
  try {
406
- db = new Database(dbPath);
438
+ db = openDb();
407
439
  }
408
440
  catch {
409
- // Corrupted DB — delete and recreate
410
- try {
411
- unlinkSync(dbPath);
412
- }
413
- catch { }
414
- db = new Database(dbPath);
441
+ // Corrupted DB (garbage bytes, truncated WAL checkpoint). better-sqlite3
442
+ // doesn't touch the file until the first statement, so corruption surfaces
443
+ // at the pragma/exec inside openDb, not at the constructor — the whole
444
+ // open sequence must be inside this catch. The index is a disposable
445
+ // cache: delete and rebuild.
446
+ removeDbFiles();
447
+ db = openDb();
415
448
  }
416
- db.pragma('journal_mode = WAL');
417
- db.pragma('busy_timeout = 5000');
418
- db.pragma('foreign_keys = ON');
419
- db.exec(SCHEMA);
420
449
  // Schema migration: if next_action_count column missing, delete DB and recreate
421
450
  try {
422
451
  db.prepare('SELECT next_action_count FROM items LIMIT 0').run();
423
452
  }
424
453
  catch {
425
454
  db.close();
426
- unlinkSync(dbPath);
427
- db = new Database(dbPath);
428
- db.pragma('journal_mode = WAL');
429
- db.pragma('busy_timeout = 5000');
430
- db.pragma('foreign_keys = ON');
431
- db.exec(SCHEMA);
455
+ removeDbFiles();
456
+ db = openDb();
432
457
  }
433
458
  // Schema migration: if domain_entity column exists, delete DB and recreate
434
459
  try {
435
460
  db.prepare('SELECT domain_entity FROM items LIMIT 0').run();
436
461
  // Column exists — old schema, needs rebuild
437
462
  db.close();
438
- unlinkSync(dbPath);
439
- db = new Database(dbPath);
440
- db.pragma('journal_mode = WAL');
441
- db.pragma('busy_timeout = 5000');
442
- db.pragma('foreign_keys = ON');
443
- db.exec(SCHEMA);
463
+ removeDbFiles();
464
+ db = openDb();
444
465
  }
445
466
  catch {
446
467
  // Column doesn't exist — correct schema, continue
@@ -449,12 +470,8 @@ export function createIndex(vaultPath, opts) {
449
470
  const versionRow = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
450
471
  if (!versionRow || versionRow.value !== SCHEMA_VERSION) {
451
472
  db.close();
452
- unlinkSync(dbPath);
453
- db = new Database(dbPath);
454
- db.pragma('journal_mode = WAL');
455
- db.pragma('busy_timeout = 5000');
456
- db.pragma('foreign_keys = ON');
457
- db.exec(SCHEMA);
473
+ removeDbFiles();
474
+ db = openDb();
458
475
  db.prepare("INSERT INTO meta (key, value) VALUES ('schema_version', ?)").run(SCHEMA_VERSION);
459
476
  }
460
477
  // Prepared statements
@@ -496,9 +513,8 @@ export function createIndex(vaultPath, opts) {
496
513
  process.stderr.write(`Warning: skipping ${kind}/${slug}.md: missing required field(s): ${missing}\n`);
497
514
  return;
498
515
  }
499
- // Parse next actions from body (new), fall back to frontmatter (legacy pre-migration)
500
516
  const bodyActions = getNextActions(body);
501
- const nextAction = bodyActions.length > 0 ? bodyActions[0] : (fm['next-action'] ?? null);
517
+ const nextAction = bodyActions.length > 0 ? bodyActions[0] : null;
502
518
  const nextActionCount = bodyActions.length;
503
519
  const brief = getBrief(body)?.slice(0, 300) ?? null;
504
520
  const logs = parseSessionLog(body);
@@ -1,7 +1,7 @@
1
1
  // src/export-okf.ts
2
2
  // Read-only export of the vault as an OKF v0.1 bundle (entities + knowledge + sessions).
3
3
  // Spec: https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md
4
- // Pure sync function — no Hafez instance, index, git, or lock (mirrors migrate-*.ts).
4
+ // Pure sync function — no Hafez instance, index, git, or lock.
5
5
  // Re-export overwrites a prior bundle in place but never deletes: files for vault
6
6
  // docs renamed or removed since the last export linger (unreferenced by the
7
7
  // regenerated indexes) until the bundle directory is deleted and re-exported.
package/dist/git.d.ts CHANGED
@@ -8,5 +8,12 @@ export declare function gitSync(vaultPath: string): Promise<{
8
8
  pushed: boolean;
9
9
  remote: boolean;
10
10
  }>;
11
+ /**
12
+ * Unstage files (best effort). Used by batch rollback: a commit that fails
13
+ * after `git add` leaves the new blobs staged in .git/index, and the next
14
+ * successful commit would publish the rolled-back content under the wrong
15
+ * message. Clearing the index entries restores worktree/HEAD/index agreement.
16
+ */
17
+ export declare function gitUnstage(vaultPath: string, files: string[]): Promise<void>;
11
18
  export declare function gitChangelog(vaultPath: string, since: string): Promise<ChangelogEntry[]>;
12
19
  export declare function gitCommitAndPush(vaultPath: string, files: string[], message: string, config?: GitConfig): Promise<void>;