rigjs 4.0.14 → 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.
- package/.claude/skills/rig-wiki/SKILL.md +28 -2
- package/RIG_WIKI_SKILL.md +28 -2
- package/built/index.js +171 -174
- package/lib/wiki/indexCmd.ts +1 -1
- package/lib/wiki/ingest.ts +2 -2
- package/lib/wiki/init.ts +4 -4
- package/lib/wiki/pathGuard.ts +24 -7
- package/lib/wiki/qmd.ts +64 -9
- package/lib/wiki/query.ts +1 -1
- package/lib/wiki/rebuild.ts +3 -3
- package/lib/wiki/survey.ts +10 -4
- package/lib/wiki/wikiignore.ts +93 -0
- package/package.json +3 -2
package/lib/wiki/indexCmd.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface IndexOpts { force?: boolean; }
|
|
|
7
7
|
export default async function wikiIndex(opts: IndexOpts): Promise<void> {
|
|
8
8
|
const t = requireVault();
|
|
9
9
|
print.start(`qmd embed: ${t.name}`);
|
|
10
|
-
const res = await qmdEmbed(t.name, t.path, { force: !!opts.force });
|
|
10
|
+
const res = await qmdEmbed(t.name, t.path, t.root, { force: !!opts.force });
|
|
11
11
|
if (res.ok) print.succeed(`qmd embed: ${t.name} done`);
|
|
12
12
|
else {
|
|
13
13
|
print.error(`qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
|
package/lib/wiki/ingest.ts
CHANGED
|
@@ -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
|
|
@@ -112,7 +112,7 @@ export default async function wikiIngest(source: string, opts: IngestOpts): Prom
|
|
|
112
112
|
|
|
113
113
|
// Real ingest — trigger incremental embed.
|
|
114
114
|
print.info(`applied ${applied.length} file change${applied.length === 1 ? '' : 's'}; rejected ${rejected.length}.`);
|
|
115
|
-
const embedRes = await qmdEmbed(target.name, target.path);
|
|
115
|
+
const embedRes = await qmdEmbed(target.name, target.path, target.root);
|
|
116
116
|
if (!embedRes.ok) {
|
|
117
117
|
print.warn(`qmd embed failed after ingest: ${embedRes.stderr.trim().slice(0, 300)}`);
|
|
118
118
|
print.warn('your wiki content is committed to disk; only the vector index is stale.');
|
package/lib/wiki/init.ts
CHANGED
|
@@ -109,10 +109,7 @@ cases.
|
|
|
109
109
|
const SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'];
|
|
110
110
|
|
|
111
111
|
const GITIGNORE_TMPL = `# rig wiki — local-only artifacts (do not commit)
|
|
112
|
-
#
|
|
113
|
-
.qmd/index.sqlite*
|
|
114
|
-
.qmd/*.sqlite-wal
|
|
115
|
-
.qmd/*.sqlite-shm
|
|
112
|
+
# (vector cache lives outside the vault at ~/.rig/<project>/wiki/)
|
|
116
113
|
# auto-generated reports
|
|
117
114
|
lint-report-*.md
|
|
118
115
|
# daemon proposal queue (per-machine)
|
|
@@ -232,6 +229,9 @@ export default function wikiInit(scope?: string): void {
|
|
|
232
229
|
print.succeed(`vault initialized at ${shortPath(vaultDir)} (${scopeLabel})`);
|
|
233
230
|
print.info(`next: edit ${shortPath(path.join(vaultDir, 'purpose.md'))} to describe what this wiki is for.`);
|
|
234
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
|
+
}
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
function writeIfMissing(file: string, content: string) {
|
package/lib/wiki/pathGuard.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// Shared guards for rig wiki — refuse to operate on hidden paths
|
|
2
|
-
// .gitignored content. The user must explicitly
|
|
3
|
-
//
|
|
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`
|
|
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 .
|
|
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`',
|
package/lib/wiki/qmd.ts
CHANGED
|
@@ -9,8 +9,13 @@
|
|
|
9
9
|
// - Embed: Qwen3-Embedding-0.6B (~610MB, sets QMD_EMBED_MODEL)
|
|
10
10
|
// - Rerank: Qwen3-Reranker-0.6B (~610MB, sets QMD_RERANK_MODEL)
|
|
11
11
|
//
|
|
12
|
-
// Per-wiki SQLite DB lives at `~/.rig
|
|
13
|
-
//
|
|
12
|
+
// Per-wiki SQLite DB lives at `~/.rig/<project>/wiki/<wiki>.sqlite`, where
|
|
13
|
+
// `<project>` is the `name` from the nearest `package.json` walking up from
|
|
14
|
+
// the vault root (fallback: vault-root basename). The extra `wiki/` segment
|
|
15
|
+
// reserves room for other per-project rig artifacts as siblings. Legacy
|
|
16
|
+
// `~/.rig/cache/qmd/<wiki>.sqlite` is migrated lazily on first open.
|
|
17
|
+
//
|
|
18
|
+
// Model GGUFs still cache in `~/.cache/qmd/models/` (hardcoded inside qmd).
|
|
14
19
|
//
|
|
15
20
|
// Concurrency note: qmd's `setConfigSource` is module-global; serialize
|
|
16
21
|
// store lifetimes by opening + closing inside each call.
|
|
@@ -71,10 +76,58 @@ function qmdVersion(): string {
|
|
|
71
76
|
return 'unknown';
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the project name for a vault: read the `name` field from the
|
|
81
|
+
* nearest `package.json` walking up from `vaultRoot`. Falls back to
|
|
82
|
+
* `basename(vaultRoot)` if no package.json with a usable name is found.
|
|
83
|
+
* Scoped names (`@scope/foo`) are flattened to `scope_foo`.
|
|
84
|
+
*/
|
|
85
|
+
function resolveProjectName(vaultRoot: string): string {
|
|
86
|
+
let dir = path.resolve(vaultRoot);
|
|
87
|
+
while (true) {
|
|
88
|
+
const pkg = path.join(dir, 'package.json');
|
|
89
|
+
if (fs.existsSync(pkg)) {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(fs.readFileSync(pkg, 'utf8'));
|
|
92
|
+
if (typeof parsed.name === 'string' && parsed.name.trim()) {
|
|
93
|
+
return sanitizeSegment(parsed.name.trim());
|
|
94
|
+
}
|
|
95
|
+
} catch { /* malformed package.json — keep walking */ }
|
|
96
|
+
}
|
|
97
|
+
const parent = path.dirname(dir);
|
|
98
|
+
if (parent === dir) break;
|
|
99
|
+
dir = parent;
|
|
100
|
+
}
|
|
101
|
+
return sanitizeSegment(path.basename(vaultRoot)) || 'unknown';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sanitizeSegment(s: string): string {
|
|
105
|
+
// npm scoped names use `/`; flatten so we don't create unintended nesting.
|
|
106
|
+
// Also defang filesystem-hostile chars and leading dots.
|
|
107
|
+
return s
|
|
108
|
+
.replace(/^@/, '')
|
|
109
|
+
.replace(/[/\\]/g, '_')
|
|
110
|
+
.replace(/[<>:"|?*\x00-\x1f]/g, '_')
|
|
111
|
+
.replace(/^\.+/, '');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function dbPathFor(wikiName: string, vaultRoot: string): string {
|
|
115
|
+
const projectName = resolveProjectName(vaultRoot);
|
|
116
|
+
const dir = path.join(paths.home, projectName, 'wiki');
|
|
76
117
|
fs.mkdirSync(dir, { recursive: true });
|
|
77
|
-
|
|
118
|
+
const target = path.join(dir, `${wikiName}.sqlite`);
|
|
119
|
+
// One-shot migration from the legacy flat layout.
|
|
120
|
+
const legacy = path.join(paths.cache, 'qmd', `${wikiName}.sqlite`);
|
|
121
|
+
if (!fs.existsSync(target) && fs.existsSync(legacy)) {
|
|
122
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
123
|
+
const src = legacy + suffix;
|
|
124
|
+
if (fs.existsSync(src)) {
|
|
125
|
+
try { fs.renameSync(src, target + suffix); }
|
|
126
|
+
catch { /* cross-device? leave the legacy file in place */ }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return target;
|
|
78
131
|
}
|
|
79
132
|
|
|
80
133
|
async function loadQmd(): Promise<{ createStore: any }> {
|
|
@@ -90,12 +143,13 @@ async function loadQmd(): Promise<{ createStore: any }> {
|
|
|
90
143
|
export async function qmdEmbed(
|
|
91
144
|
wikiName: string,
|
|
92
145
|
dir: string,
|
|
146
|
+
vaultRoot: string,
|
|
93
147
|
opts: { force?: boolean } = {}
|
|
94
148
|
): Promise<{ ok: boolean; stderr: string }> {
|
|
95
149
|
try {
|
|
96
150
|
const { createStore } = await loadQmd();
|
|
97
151
|
const store = await createStore({
|
|
98
|
-
dbPath: dbPathFor(wikiName),
|
|
152
|
+
dbPath: dbPathFor(wikiName, vaultRoot),
|
|
99
153
|
config: {
|
|
100
154
|
collections: { [wikiName]: { path: dir, pattern: '**/*.md' } },
|
|
101
155
|
},
|
|
@@ -134,6 +188,7 @@ export interface QmdHit {
|
|
|
134
188
|
export async function qmdQuery(
|
|
135
189
|
q: string,
|
|
136
190
|
wikiName: string,
|
|
191
|
+
vaultRoot: string,
|
|
137
192
|
opts: { limit?: number; candidateLimit?: number; rerank?: boolean } = {}
|
|
138
193
|
): Promise<QmdHit[] | null> {
|
|
139
194
|
const limit = opts.limit ?? 10;
|
|
@@ -142,7 +197,7 @@ export async function qmdQuery(
|
|
|
142
197
|
|
|
143
198
|
try {
|
|
144
199
|
const { createStore } = await loadQmd();
|
|
145
|
-
const store = await createStore({ dbPath: dbPathFor(wikiName) });
|
|
200
|
+
const store = await createStore({ dbPath: dbPathFor(wikiName, vaultRoot) });
|
|
146
201
|
try {
|
|
147
202
|
const raw = await store.searchVector(q, { limit: candidateLimit, collection: wikiName });
|
|
148
203
|
const candidates: QmdHit[] = Array.isArray(raw) ? raw.map(normalizeHit) : [];
|
|
@@ -191,8 +246,8 @@ function normalizeHit(h: any): QmdHit {
|
|
|
191
246
|
}
|
|
192
247
|
|
|
193
248
|
/** Wipe the per-wiki SQLite store on disk. Caller should then qmdEmbed. */
|
|
194
|
-
export function qmdResetStore(wikiName: string): void {
|
|
195
|
-
const p = dbPathFor(wikiName);
|
|
249
|
+
export function qmdResetStore(wikiName: string, vaultRoot: string): void {
|
|
250
|
+
const p = dbPathFor(wikiName, vaultRoot);
|
|
196
251
|
for (const suffix of ['', '-wal', '-shm']) {
|
|
197
252
|
const f = p + suffix;
|
|
198
253
|
if (fs.existsSync(f)) fs.rmSync(f, { force: true });
|
package/lib/wiki/query.ts
CHANGED
|
@@ -30,7 +30,7 @@ export default async function wikiQuery(q: string, opts: QueryOpts): Promise<voi
|
|
|
30
30
|
const target = requireVault();
|
|
31
31
|
|
|
32
32
|
const limit = Math.max(1, Math.min(50, opts.limit || 10));
|
|
33
|
-
const hits = await qmdQuery(q, target.name, { limit, rerank: opts.rerank !== false });
|
|
33
|
+
const hits = await qmdQuery(q, target.name, target.root, { limit, rerank: opts.rerank !== false });
|
|
34
34
|
if (hits === null) {
|
|
35
35
|
print.error('qmd query failed. Run `rig wiki index` first to (re)build the vector store.');
|
|
36
36
|
process.exit(1);
|
package/lib/wiki/rebuild.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Use cases:
|
|
4
4
|
// 1. New device: source markdown is checked out, but ~/.rig/state.db and
|
|
5
|
-
// ~/.rig
|
|
5
|
+
// ~/.rig/<project>/wiki/<wiki>.sqlite are empty. Rebuild populates both.
|
|
6
6
|
// 2. Switched embedding model: old vectors are now meaningless. Rebuild
|
|
7
7
|
// re-embeds against the current QMD_EMBED_MODEL.
|
|
8
8
|
// 3. Local cache corruption: nuke and start over.
|
|
@@ -27,10 +27,10 @@ export default async function wikiRebuild(opts: RebuildOpts): Promise<void> {
|
|
|
27
27
|
const del = db.prepare('DELETE FROM source_sha WHERE wiki = ?').run(t.name);
|
|
28
28
|
print.info(` cleared ${del.changes} source_sha rows for ${t.name}`);
|
|
29
29
|
|
|
30
|
-
qmdResetStore(t.name);
|
|
30
|
+
qmdResetStore(t.name, t.root);
|
|
31
31
|
|
|
32
32
|
if (!opts.skipEmbed) {
|
|
33
|
-
const res = await qmdEmbed(t.name, t.path, { force: true });
|
|
33
|
+
const res = await qmdEmbed(t.name, t.path, t.root, { force: true });
|
|
34
34
|
if (res.ok) print.info(` qmd embed: ${t.name} done`);
|
|
35
35
|
else {
|
|
36
36
|
print.error(` qmd embed: ${t.name} failed: ${res.stderr.trim()}`);
|
package/lib/wiki/survey.ts
CHANGED
|
@@ -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
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
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.
|
|
4
|
-
"versionCode":
|
|
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",
|