@su-record/vibe 4.2.29 → 4.2.30

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
9
- "version": "4.2.29"
9
+ "version": "4.2.30"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "vibe",
14
14
  "source": "./",
15
15
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
16
- "version": "4.2.29",
16
+ "version": "4.2.30",
17
17
  "category": "productivity"
18
18
  }
19
19
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe",
3
- "version": "4.2.29",
3
+ "version": "4.2.30",
4
4
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
5
5
  "author": {
6
6
  "name": "su-record",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe",
3
- "version": "4.2.29",
3
+ "version": "4.2.30",
4
4
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
5
5
  "author": {
6
6
  "name": "su-record",
@@ -18,15 +18,19 @@ const AUTO = '<!-- vibe:auto -->';
18
18
  /**
19
19
  * The wiki's pages, and the pages of each project's shared knowledge it shows through a folder link
20
20
  * (`projects/<name>/`). Those are kept in the project, so they resolve links and are reported as unlinked, not orphans.
21
+ * A folder reached twice (two links to one project) is read once; `aliases` maps the second path to the first so
22
+ * links through either resolve.
21
23
  */
22
24
  function wikiPages(dir) {
23
25
  const files = [];
24
26
  const shared = new Set();
25
- const seen = new Set();
27
+ const seen = new Map();
28
+ const aliases = [];
26
29
  let partial = false;
27
30
  const walk = (current, linked) => {
28
- if (seen.has(fs.realpathSync(current))) return;
29
- seen.add(fs.realpathSync(current));
31
+ const real = fs.realpathSync(current);
32
+ if (seen.has(real)) { aliases.push([current, seen.get(real)]); return; }
33
+ seen.set(real, current);
30
34
  for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
31
35
  if (entry.name.startsWith('.')) continue;
32
36
  if (files.length >= MAX_PAGES) { partial = true; return; }
@@ -37,7 +41,7 @@ function wikiPages(dir) {
37
41
  }
38
42
  };
39
43
  if (fs.existsSync(dir)) walk(dir, false);
40
- return { files: files.sort(), shared, partial };
44
+ return { files: files.sort(), shared, aliases, partial };
41
45
  }
42
46
 
43
47
  /** `[[target]]`, `[[target|label]]` and `[[target#heading]]` give `target`; links inside code are still counted. */
@@ -53,13 +57,19 @@ function pageTitle(text, file) {
53
57
  return stated || heading || path.basename(file, '.md');
54
58
  }
55
59
 
56
- /** Each page's name as a link target, and every name a link may use for it (path from the root or file name alone). */
57
- function names(dir, files) {
60
+ /**
61
+ * Each page's name as a link target, and every name a link may use for it (path from the root, the same path through
62
+ * another link to its folder, or file name alone).
63
+ */
64
+ function names(dir, files, aliases = []) {
58
65
  const name = file => path.relative(dir, file).split(path.sep).join('/').replace(/\.md$/, '');
59
66
  const known = new Map();
60
67
  for (const file of files) {
61
68
  known.set(name(file).toLowerCase(), name(file));
62
69
  known.set(path.basename(file, '.md').toLowerCase(), name(file));
70
+ for (const [alias, folder] of aliases) {
71
+ if (file.startsWith(folder + path.sep)) known.set(name(path.join(alias, path.relative(folder, file))).toLowerCase(), name(file));
72
+ }
63
73
  }
64
74
  return { name, resolve: target => known.get(target.replace(/\.md$/, '').toLowerCase()) };
65
75
  }
@@ -69,8 +79,8 @@ function names(dir, files) {
69
79
  * title. A link resolves the way Obsidian resolves it; a link from log.md finds a page but does not make it reachable.
70
80
  */
71
81
  function lintWiki(dir) {
72
- const { files, shared, partial } = wikiPages(dir);
73
- const { name, resolve } = names(dir, files);
82
+ const { files, shared, aliases, partial } = wikiPages(dir);
83
+ const { name, resolve } = names(dir, files, aliases);
74
84
  const broken = [], long = [], badType = [];
75
85
  const linked = new Set();
76
86
  const titles = new Map();
@@ -106,16 +116,16 @@ function fixWiki(dir, home) {
106
116
  const created = [];
107
117
  const log = path.join(dir, 'log.md');
108
118
  if (fs.existsSync(log)) {
109
- const { files } = wikiPages(dir);
110
- const { resolve } = names(dir, files);
119
+ const { files, aliases } = wikiPages(dir);
120
+ const { resolve } = names(dir, files, aliases);
111
121
  const roots = readNames(home);
112
122
  for (const target of new Set(wikiLinks(fs.readFileSync(log, 'utf8')))) {
113
123
  const project = /^projects\/([^/]+)$/.exec(target.replace(/\.md$/, ''))?.[1];
114
124
  if (project && resolve(target) === undefined && projectPage(dir, project, roots[project])) created.push(`projects/${project}`);
115
125
  }
116
126
  }
117
- const { files, shared } = wikiPages(dir);
118
- const { name, resolve } = names(dir, files);
127
+ const { files, shared, aliases } = wikiPages(dir);
128
+ const { name, resolve } = names(dir, files, aliases);
119
129
  const index = path.join(dir, 'index.md');
120
130
  const text = fs.existsSync(index) ? fs.readFileSync(index, 'utf8') : '# Index\n';
121
131
  const removed = [];
@@ -138,8 +148,8 @@ function fixWiki(dir, home) {
138
148
 
139
149
  /** Pages changed since `since` (index and log aside) and the pages they link to, for a review turn. */
140
150
  function reviewScope(dir, since) {
141
- const { files } = wikiPages(dir);
142
- const { name, resolve } = names(dir, files);
151
+ const { files, aliases } = wikiPages(dir);
152
+ const { name, resolve } = names(dir, files, aliases);
143
153
  const changed = files.filter(file => !ROOTS.has(name(file)) && fs.statSync(file).mtimeMs > since);
144
154
  const linked = new Set();
145
155
  for (const file of changed) for (const target of wikiLinks(fs.readFileSync(file, 'utf8'))) linked.add(resolve(target));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "4.2.29",
3
+ "version": "4.2.30",
4
4
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
5
5
  "type": "module",
6
6
  "bin": {