rigjs 4.0.15 → 4.0.16

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.
@@ -40,7 +40,7 @@ export default async function wikiIngest(source: string, opts: IngestOpts): Prom
40
40
  print.error(`source not found: ${source}`);
41
41
  process.exit(1);
42
42
  }
43
- const guard = guardPath(absSource, target.root || target.path);
43
+ const guard = guardPath(absSource, target.root || target.path, target.root);
44
44
  if (!guard.ok) {
45
45
  print.error('refusing to ingest from a hidden or gitignored path.');
46
46
  // eslint-disable-next-line no-console
package/lib/wiki/init.ts CHANGED
@@ -229,6 +229,9 @@ export default function wikiInit(scope?: string): void {
229
229
  print.succeed(`vault initialized at ${shortPath(vaultDir)} (${scopeLabel})`);
230
230
  print.info(`next: edit ${shortPath(path.join(vaultDir, 'purpose.md'))} to describe what this wiki is for.`);
231
231
  print.info(`then run \`rig wiki sync\` from anywhere inside ${shortPath(cwd)} to ingest, update, and prune in one shot.`);
232
+ if (!fs.existsSync(path.join(scopeAbs, '.wikiignore'))) {
233
+ print.info(`tip: drop a \`.wikiignore\` (gitignore syntax) at ${shortPath(scopeAbs)} for paths the wiki should skip even though git tracks them (e.g. \`keychain/\`, \`secrets/\`).`);
234
+ }
232
235
  }
233
236
 
234
237
  function writeIfMissing(file: string, content: string) {
@@ -1,13 +1,15 @@
1
- // Shared guards for rig wiki — refuse to operate on hidden paths or
2
- // .gitignored content. The user must explicitly copy such files into a
3
- // visible, tracked location before they can become wiki sources.
1
+ // Shared guards for rig wiki — refuse to operate on hidden paths,
2
+ // .gitignored content, or .wikiignored content. The user must explicitly
3
+ // copy such files into a visible, tracked, non-wikiignored location
4
+ // before they can become wiki sources.
4
5
 
5
6
  import path from 'path';
6
7
  import { spawnSync } from 'child_process';
8
+ import { batchWikiIgnored } from './wikiignore';
7
9
 
8
10
  export interface PathGuardResult {
9
11
  ok: boolean;
10
- reason?: 'hidden' | 'gitignored';
12
+ reason?: 'hidden' | 'gitignored' | 'wikiignored';
11
13
  segment?: string; // for hidden: the offending segment
12
14
  detail?: string; // human-readable detail
13
15
  }
@@ -49,9 +51,14 @@ export function isGitignored(p: string, repoCwd: string): boolean | null {
49
51
 
50
52
  /**
51
53
  * Validate a path as a wiki source / target. Returns ok if visible AND
52
- * not gitignored. Use for `init` target and `ingest` source.
54
+ * not gitignored AND not wikiignored. Use for `init` target and `ingest`
55
+ * source.
56
+ *
57
+ * `vaultRoot` (optional) enables the `.wikiignore` check; pass the
58
+ * project root so layered `.wikiignore` files are consulted. Omit for
59
+ * pre-vault callers (e.g. `init` deciding where the vault dir lives).
53
60
  */
54
- export function guardPath(absPath: string, repoCwd: string): PathGuardResult {
61
+ export function guardPath(absPath: string, repoCwd: string, vaultRoot?: string): PathGuardResult {
55
62
  const seg = hiddenSegment(absPath);
56
63
  if (seg) {
57
64
  return {
@@ -69,6 +76,16 @@ export function guardPath(absPath: string, repoCwd: string): PathGuardResult {
69
76
  detail: '.gitignore matches this path',
70
77
  };
71
78
  }
79
+ if (vaultRoot) {
80
+ const wk = batchWikiIgnored([absPath], vaultRoot);
81
+ if (wk.has(absPath)) {
82
+ return {
83
+ ok: false,
84
+ reason: 'wikiignored',
85
+ detail: '.wikiignore matches this path',
86
+ };
87
+ }
88
+ }
72
89
  return { ok: true };
73
90
  }
74
91
 
@@ -77,7 +94,7 @@ export function refusalMessage(target: string, r: PathGuardResult): string {
77
94
  `refused: ${target}`,
78
95
  ` reason: ${r.reason} — ${r.detail}`,
79
96
  '',
80
- ' rig wiki refuses to operate on hidden files/dirs or .gitignored content.',
97
+ ' rig wiki refuses to operate on hidden files/dirs, .gitignored, or .wikiignored content.',
81
98
  ' If you really need this content, copy it to a visible, tracked location first:',
82
99
  '',
83
100
  ' cp -R <hidden-or-ignored> <wiki>/raw/manual-copy/ # then `rig wiki ingest`',
@@ -18,6 +18,7 @@ import print from '../print';
18
18
  import { requireVault, loadRigConfig, WikiEntry } from './config';
19
19
  import { isBinaryExtension } from './fileTypes';
20
20
  import { batchGitignored } from './gitignore';
21
+ import { batchWikiIgnored } from './wikiignore';
21
22
  import { adapters } from './agent/registry';
22
23
  import { default as wikiIngest } from './ingest';
23
24
 
@@ -105,10 +106,15 @@ function collectCandidates(entry: WikiEntry): Candidate[] {
105
106
  }
106
107
  }
107
108
  }
108
- // Gitignore filter via batch `git check-ignore --stdin -z` (best-effort,
109
- // silent fallback outside a git repo)
110
- const ignored = batchGitignored(out.map(c => c.abs));
111
- return out.filter(c => !ignored.has(c.abs));
109
+ // Two-layer ignore filter:
110
+ // 1. .gitignore multi-repo-aware via `git check-ignore --stdin`
111
+ // 2. .wikiignore same syntax, wiki-only (lets us hide paths git
112
+ // tracks intentionally, e.g. overmind's `keychain/`)
113
+ // A candidate is dropped if either layer matches.
114
+ const absList = out.map(c => c.abs);
115
+ const giIgnored = batchGitignored(absList);
116
+ const wkIgnored = batchWikiIgnored(absList, entry.root);
117
+ return out.filter(c => !giIgnored.has(c.abs) && !wkIgnored.has(c.abs));
112
118
  }
113
119
 
114
120
  async function classifyWithAgent(target: WikiEntry, candidates: Candidate[]): Promise<SurveyRow[]> {
@@ -0,0 +1,93 @@
1
+ // .wikiignore — wiki-only ignore file with the same syntax as .gitignore.
2
+ //
3
+ // Why a separate file? `.gitignore` is shared with git; many repos
4
+ // (including the overmind workspace) intentionally commit directories
5
+ // like `keychain/` that the wiki MUST NEVER ingest. Mixing wiki-only
6
+ // excludes into `.gitignore` would break that contract. `.wikiignore`
7
+ // is read only by the wiki walker.
8
+ //
9
+ // Lookup rule: starting from each candidate's directory, walk up
10
+ // looking for `.wikiignore`. Every `.wikiignore` found along the path
11
+ // up to (and including) `vaultRoot` contributes rules, with patterns
12
+ // resolved relative to the dir that file lives in — same as gitignore.
13
+ //
14
+ // Implementation note: we use the `ignore` package because it
15
+ // faithfully implements gitignore semantics (anchoring, negation,
16
+ // `**`, trailing slash). Inlining a hand-rolled matcher invites subtle
17
+ // bugs that would silently exfiltrate sensitive paths.
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import ignore, { Ignore } from 'ignore';
22
+
23
+ const FILENAME = '.wikiignore';
24
+
25
+ interface Layer {
26
+ dir: string; // dir the .wikiignore lives in
27
+ matcher: Ignore;
28
+ }
29
+
30
+ /**
31
+ * Returns the set of absolute paths matched by any `.wikiignore` file
32
+ * found in `vaultRoot` or any directory between a candidate and
33
+ * `vaultRoot` (inclusive). Paths above `vaultRoot` are NOT consulted.
34
+ */
35
+ export function batchWikiIgnored(absPaths: string[], vaultRoot: string): Set<string> {
36
+ const ignored = new Set<string>();
37
+ if (absPaths.length === 0) return ignored;
38
+
39
+ const root = path.resolve(vaultRoot);
40
+ const layerCache = new Map<string, Layer | null>();
41
+
42
+ // Build the ordered list of layers (root-most first) that apply to a
43
+ // given candidate. Layers above `root` are ignored.
44
+ function layersFor(abs: string): Layer[] {
45
+ const layers: Layer[] = [];
46
+ let dir = path.dirname(abs);
47
+ while (true) {
48
+ const cached = layerCache.get(dir);
49
+ if (cached !== undefined) {
50
+ if (cached) layers.push(cached);
51
+ } else {
52
+ const file = path.join(dir, FILENAME);
53
+ let layer: Layer | null = null;
54
+ if (fs.existsSync(file)) {
55
+ try {
56
+ const src = fs.readFileSync(file, 'utf8');
57
+ layer = { dir, matcher: ignore().add(src) };
58
+ } catch { /* unreadable — treat as no layer */ }
59
+ }
60
+ layerCache.set(dir, layer);
61
+ if (layer) layers.push(layer);
62
+ }
63
+ if (dir === root) break;
64
+ const parent = path.dirname(dir);
65
+ if (parent === dir) break; // hit filesystem root before vaultRoot
66
+ dir = parent;
67
+ }
68
+ // root-most should win on equal-specificity → reverse so root is first
69
+ return layers.reverse();
70
+ }
71
+
72
+ for (const abs of absPaths) {
73
+ if (!abs.startsWith(root + path.sep) && abs !== root) continue;
74
+ const layers = layersFor(abs);
75
+ if (layers.length === 0) continue;
76
+ // Each layer matches paths relative to its own dir, gitignore-style.
77
+ // A later layer's negation can override an earlier match.
78
+ let isIgnored = false;
79
+ for (const layer of layers) {
80
+ const rel = path.relative(layer.dir, abs);
81
+ if (!rel || rel.startsWith('..')) continue;
82
+ // `ignore` returns { ignored, unignored } via .test(); we mirror its
83
+ // semantics by chaining .test() so negations later in the same file
84
+ // can flip the bit back.
85
+ const res = layer.matcher.test(rel);
86
+ if (res.ignored) isIgnored = true;
87
+ else if (res.unignored) isIgnored = false;
88
+ }
89
+ if (isIgnored) ignored.add(abs);
90
+ }
91
+
92
+ return ignored;
93
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rigjs",
3
- "version": "4.0.15",
4
- "versionCode": 26052422,
3
+ "version": "4.0.16",
4
+ "versionCode": 26052423,
5
5
  "description": "A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.",
6
6
  "keywords": [
7
7
  "modular",
@@ -78,6 +78,7 @@
78
78
  "compare-versions": "^4.1.3",
79
79
  "dayjs": "^1.11.0",
80
80
  "dotenv": "^16.0.3",
81
+ "ignore": "^7.0.5",
81
82
  "inquirer": "7.3.3",
82
83
  "js-yaml": "^4.1.1",
83
84
  "json5": "2.1.3",