@staix/vault-mem 0.1.2 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.3] - 2026-10-30
4
+
5
+ - `vmem tidy --log` fixes legacy log lines that fail lint L09: a trailing seconds field is dropped, and any other malformed line is kept verbatim behind `# legacy: `.
6
+
3
7
  ## [0.1.2] - 2026-10-30
4
8
 
5
9
  - `vmem move` accepts an existing legacy-named note (lint L13) as its source and may rewrite inbound links in other legacy-named notes, so the finding can be fixed with a normal rename. Destinations still need a valid stem.
package/README.md CHANGED
@@ -247,6 +247,6 @@ Runtime changes intentionally make `release:verify` report stale historical rele
247
247
  | `--topics` | append `## Topics` listing the frontmatter topics where no such heading exists; mismatched sections are reported |
248
248
  | `--connections` | fold `## Connections (...)` headings into `## Relevant Notes` |
249
249
  | `--secrets` | replace V06 secret matches with `[redacted]`: in the body the match itself, in frontmatter the whole field value (the key stays, so the YAML stays valid). A match outside a field value, such as a YAML comment, is reported for manual repair. The preview names note and line, never the value. PII in personal vaults is left alone |
250
- | `--log` | replace secret-looking substrings and email addresses in existing log lines with `[redacted]`, under the log lock (refused with V06 when `denyPaths` covers the log); legacy lines are otherwise kept byte for byte. Git history is not rewritten: rotate exposed credentials |
250
+ | `--log` | replace secret-looking substrings and email addresses in existing log lines with `[redacted]`, under the log lock (refused with V06 when `denyPaths` covers the log). A line that fails the lint L09 structure check loses a trailing seconds field when that makes it valid, and is otherwise kept verbatim behind `# legacy: ` (readers skip `#` lines); no time, zone or TYPE is guessed. Other lines are kept byte for byte. Git history is not rewritten: rotate exposed credentials |
251
251
 
252
252
  `--map <file>` overrides defaults with strict JSON `{"enums": {"confidence|status|type": {"from": "to"}}, "keys": {"key": "drop" | "keep" | {"rename": "key"}}}` (at most 64 KiB and 1000 entries). For `tidy`, `--log` selects the log rule; tidy always writes its own `LINT` event.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/vault-mem",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Validated Obsidian-compatible knowledge vaults for agents and people",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/tidy.ts CHANGED
@@ -29,7 +29,7 @@ export interface TidyResult {
29
29
  notes: { path: string; changes: string[] }[];
30
30
  /** Values and keys left unchanged because no rule or map entry covers them. */
31
31
  reported: { path: string; message: string }[];
32
- log?: { lines: number; redactions: number; changes: string[] };
32
+ log?: { lines: number; redactions: number; formats: number; changes: string[] };
33
33
  fingerprint?: string;
34
34
  written: string[];
35
35
  issues: WriteIssue[];
@@ -271,17 +271,32 @@ function readLogFile(root: string): string | null {
271
271
  return buffer.subarray(0, read).toString("utf8");
272
272
  } finally { closeSync(fd); }
273
273
  }
274
- function sanitizeLog(text: string): { text: string; lines: number; redactions: number; changes: string[] } {
275
- let lines = 0, redactions = 0;
274
+ /** The lint L09 structure check (kept identical to vault-lint.ts). */
275
+ const LOG_STRUCTURE = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})\s+(\S+)\s+(.*)$/;
276
+ /** Fix a legacy line that fails L09: drop a seconds field when that makes it valid, otherwise comment it out verbatim. */
277
+ function normalizeLogLine(line: string): string | undefined {
278
+ const body = line.endsWith("\r") ? line.slice(0, -1) : line;
279
+ if (!body.trim() || body.startsWith("#") || LOG_STRUCTURE.test(body)) return undefined;
280
+ const seconds = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}):\d{2}(\s.*)$/.exec(body);
281
+ if (seconds && LOG_STRUCTURE.test(seconds[1]! + seconds[2]!)) return seconds[1]! + seconds[2]! + line.slice(body.length);
282
+ return `# legacy: ${line}`;
283
+ }
284
+ function sanitizeLog(text: string): { text: string; lines: number; redactions: number; formats: number; changes: string[] } {
285
+ let lines = 0, redactions = 0, formats = 0;
276
286
  const changes: string[] = [];
277
287
  const out = text.split("\n").map((line, index) => {
278
288
  const secret = redact(line);
279
289
  let hits = secret.hits, next = secret.text;
280
290
  next = next.replace(new RegExp(EMAIL_PATTERN.source, "gi"), () => { hits++; return "[redacted]"; });
281
- if (hits) { lines++; redactions += hits; changes.push(`log: line ${index + 1}, ${hits} redaction${hits === 1 ? "" : "s"}`); }
291
+ const normalized = normalizeLogLine(next);
292
+ if (normalized !== undefined) { next = normalized; formats++; }
293
+ if (hits || normalized !== undefined) {
294
+ lines++; redactions += hits;
295
+ changes.push(`log: line ${index + 1}, ${[hits ? `${hits} redaction${hits === 1 ? "" : "s"}` : "", normalized !== undefined ? "format" : ""].filter(Boolean).join(", ")}`);
296
+ }
282
297
  return next;
283
298
  }).join("\n");
284
- return { text: out, lines, redactions, changes };
299
+ return { text: out, lines, redactions, formats, changes };
285
300
  }
286
301
  /** Replace the log under its own lock, atomically (the one sanctioned in-place rewrite of the append-only log). */
287
302
  function rewriteLog(root: string, expected: string, next: string): void {
@@ -339,14 +354,14 @@ export function tidyVault(home: string, request: TidyRequest, options: { env?: N
339
354
  }
340
355
  }
341
356
  result.notes = planned.map(change => ({ path: change.path, changes: change.changes }));
342
- let logPlan: { before: string; text: string; lines: number; redactions: number; changes: string[] } | undefined;
357
+ let logPlan: { before: string; text: string; lines: number; redactions: number; formats: number; changes: string[] } | undefined;
343
358
  if (rules.includes("log") && isDenied(join(root, "log"), config.denyPaths)) return { ...result, issues: [issue("V06", "the vault log is denied by the path policy; drop --log")] };
344
359
  if (rules.includes("log")) {
345
360
  const before = readLogFile(root);
346
361
  if (before !== null) {
347
362
  const sanitized = sanitizeLog(before);
348
- result.log = { lines: sanitized.lines, redactions: sanitized.redactions, changes: sanitized.changes };
349
- if (sanitized.redactions) { logPlan = { before, ...sanitized }; result.counts.log = sanitized.redactions; }
363
+ result.log = { lines: sanitized.lines, redactions: sanitized.redactions, formats: sanitized.formats, changes: sanitized.changes };
364
+ if (sanitized.text !== before) { logPlan = { before, ...sanitized }; result.counts.log = sanitized.redactions + sanitized.formats; }
350
365
  }
351
366
  }
352
367
  // Validate every planned note with the writer (dry-run). A note the writer would refuse is reported and skipped