@codyswann/lisa 2.155.7 → 2.157.0
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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/rules/eager/wiki-knowledge-source.md +5 -3
- package/plugins/lisa/rules/reference/config-resolution.md +32 -0
- package/plugins/lisa/rules/reference/wiki-knowledge-source.md +10 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/eager/wiki-knowledge-source.md +5 -3
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +32 -0
- package/plugins/lisa-copilot/rules/reference/wiki-knowledge-source.md +10 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +32 -0
- package/plugins/lisa-cursor/rules/wiki-knowledge-source-reference.mdc +10 -3
- package/plugins/lisa-cursor/rules/wiki-knowledge-source.mdc +5 -3
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/scripts/ensure-wiki.mjs +267 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-ingest/SKILL.md +14 -1
- package/plugins/lisa-wiki/skills/lisa-wiki-query/SKILL.md +7 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/scripts/ensure-wiki.mjs +267 -0
- package/plugins/lisa-wiki-agy/skills/lisa-wiki-ingest/SKILL.md +14 -1
- package/plugins/lisa-wiki-agy/skills/lisa-wiki-query/SKILL.md +7 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/scripts/ensure-wiki.mjs +267 -0
- package/plugins/lisa-wiki-copilot/skills/lisa-wiki-ingest/SKILL.md +14 -1
- package/plugins/lisa-wiki-copilot/skills/lisa-wiki-query/SKILL.md +7 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/scripts/ensure-wiki.mjs +267 -0
- package/plugins/lisa-wiki-cursor/skills/lisa-wiki-ingest/SKILL.md +14 -1
- package/plugins/lisa-wiki-cursor/skills/lisa-wiki-query/SKILL.md +7 -1
- package/plugins/src/base/rules/eager/wiki-knowledge-source.md +5 -3
- package/plugins/src/base/rules/reference/config-resolution.md +32 -0
- package/plugins/src/base/rules/reference/wiki-knowledge-source.md +10 -3
- package/plugins/src/wiki/scripts/ensure-wiki.mjs +267 -0
- package/plugins/src/wiki/skills/lisa-wiki-ingest/SKILL.md +14 -1
- package/plugins/src/wiki/skills/lisa-wiki-query/SKILL.md +7 -1
|
@@ -8,7 +8,13 @@ description: Answer a question from the LLM Wiki with citations. Reads the index
|
|
|
8
8
|
Answer from the wiki, with citations, without changing it (by default).
|
|
9
9
|
|
|
10
10
|
## Workflow
|
|
11
|
-
|
|
11
|
+
0. **Resolve the wiki root.** Run `node scripts/ensure-wiki.mjs --json` and use the returned
|
|
12
|
+
`wikiRoot` as the base for every read below — never assume `wiki/`. A local wiki resolves
|
|
13
|
+
instantly (no-op); a wiki whose `.lisa.config.json` declares `wiki.source.url` is mirrored and
|
|
14
|
+
refreshed transparently first. The script is offline-tolerant (it proceeds with the existing
|
|
15
|
+
mirror and warns rather than blocking), so freshness is guaranteed here and the caller never has
|
|
16
|
+
to think about it.
|
|
17
|
+
1. Read `<wikiRoot>/index.md` to locate candidate pages; consult `<wikiRoot>/start-here.md` for orientation.
|
|
12
18
|
2. Drill into the relevant synthesis pages and their cited source notes.
|
|
13
19
|
3. Synthesize an answer. **Every claim cites its wiki page and/or source note.** If the wiki does not
|
|
14
20
|
support an answer, say so plainly rather than inventing one; suggest an `/ingest` that would fill
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# Wiki as Knowledge Source (load-bearing)
|
|
2
2
|
|
|
3
|
-
If the project has an LLM Wiki
|
|
3
|
+
If the project has an LLM Wiki, treat it as the canonical source of durable project knowledge. A project has a wiki when **either** a local `wiki/` directory with `index.md` exists **or** `.lisa.config.json` declares a `wiki.source` pointer to a remote wiki repo. Documentation rolls UP into that wiki; individual repos are not expected to carry their own prose docs beyond inline code comments.
|
|
4
|
+
|
|
5
|
+
You never have to fetch or freshness-check the wiki yourself: the query and ingest skills resolve the wiki root and guarantee it exists and is current (via `scripts/ensure-wiki.mjs`) as their own first step — a local wiki resolves instantly, a remote wiki is mirrored/refreshed transparently into a gitignored working copy. Just call the skill.
|
|
4
6
|
|
|
5
7
|
Before researching background, conventions, ownership, architecture, glossary, or "how/why does X work here":
|
|
6
8
|
|
|
7
|
-
1. **Consult the wiki first.**
|
|
9
|
+
1. **Consult the wiki first.** Use the wiki query skill (`/lisa-wiki-query`), which resolves the wiki root for you; for a local wiki you may also start from `wiki/index.md` directly.
|
|
8
10
|
2. **Use what the wiki says** as the authoritative answer when it covers the question — do not re-derive it from raw sources.
|
|
9
11
|
3. **Fall back to primary sources** (code, tickets, commit history, external docs) only when the wiki is silent, ambiguous, or contradicted by what you observe.
|
|
10
12
|
4. **Surface gaps.** If the wiki is wrong, stale, or missing knowledge that belongs there, flag it — and where the workflow supports it, capture the correction via `/lisa-wiki-ingest`.
|
|
11
13
|
|
|
12
14
|
The wiki documents knowledge; it does NOT override executable behavior. When wiki and running code disagree about what the system does, trust the code and treat the wiki as out of date.
|
|
13
15
|
|
|
14
|
-
If the project has
|
|
16
|
+
If the project has neither a local `wiki/` nor a `wiki.source` pointer, this rule does not apply.
|
|
15
17
|
|
|
16
18
|
Full prose: [reference/wiki-knowledge-source.md](../reference/wiki-knowledge-source.md).
|
|
@@ -167,6 +167,38 @@ fi
|
|
|
167
167
|
| `tracker` | **yes** | — | Destination for ticket writes. One of `"jira"`, `"github"`, `"linear"`. Missing → fail with instruction to run the matching `/lisa:setup:*` skill. |
|
|
168
168
|
| `source` | no | — | Default PRD source for batch skills (`/lisa:intake`) and arg-less single-PRD skills. One of `"notion"`, `"confluence"`, `"linear"`, `"github"`, `"jira"`. Explicit URLs/keys passed to a skill always win over `source`; this is a default, not a lock. |
|
|
169
169
|
| `usage` | no | — | Optional token/cost pricing metadata consumed by the `usage-accounting` rule. Missing pricing never blocks a lifecycle flow; Lisa records token counts with `estimated_cost: null` when no trustworthy price source is configured. |
|
|
170
|
+
| `wiki` | no | — | Wiki location for the `wiki-knowledge-source` rule. Omit for a local in-repo wiki (`wiki/`). See **Wiki source** below. |
|
|
171
|
+
|
|
172
|
+
### Wiki source (`wiki`)
|
|
173
|
+
|
|
174
|
+
Declares **where this repo's LLM Wiki lives** so the query/ingest skills can resolve and (for a remote wiki) mirror it. `wiki.source` has two shapes — **local** (`path`) and **remote** (`url`) — and the block belongs in the **consumer** repo's `.lisa.config.json`, not in `wiki/lisa-wiki.config.json` (which describes a wiki from the inside and is unavailable until a remote wiki is mirrored — chicken-and-egg). The whole `wiki` block is optional; omit it and the resolver falls back to the in-repo `wiki/` convention.
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
// local: an explicit path (optional — equivalent to the default convention)
|
|
178
|
+
"wiki": { "source": { "path": "wiki" } }
|
|
179
|
+
|
|
180
|
+
// remote: mirror a separate wiki repo
|
|
181
|
+
"wiki": {
|
|
182
|
+
"source": {
|
|
183
|
+
"url": "git@github.com:org/wiki.git",
|
|
184
|
+
"ref": "main",
|
|
185
|
+
"mirrorPath": ".lisa/wiki",
|
|
186
|
+
"subdir": "wiki"
|
|
187
|
+
},
|
|
188
|
+
"ttlSeconds": 300
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
| Field | Required | Default | Notes |
|
|
193
|
+
|-------|----------|---------|-------|
|
|
194
|
+
| `wiki.source.path` | no | `wiki` (via convention) | **Local** wiki root, relative to the repo. The explicit form of the in-repo default. Mutually exclusive with `url`. |
|
|
195
|
+
| `wiki.source.url` | no | — | Clone URL of a separate wiki repo. **Its presence selects REMOTE mode.** Mutually exclusive with `path`. |
|
|
196
|
+
| `wiki.source.ref` | no | remote HEAD | Branch/ref to mirror (remote only). |
|
|
197
|
+
| `wiki.source.mirrorPath` | no | `.lisa/wiki` | Where the gitignored mirror is materialized (remote only). `ensure-wiki` keeps this path gitignored automatically. |
|
|
198
|
+
| `wiki.source.subdir` | no | auto | Wiki root within the cloned repo (remote only). Auto-detected as `wiki/` if present, else the repo root. |
|
|
199
|
+
| `wiki.ttlSeconds` | no | `300` | Skip the refresh fetch if the mirror was synced more recently than this (remote only). |
|
|
200
|
+
|
|
201
|
+
`scripts/ensure-wiki.mjs` is the single resolver (`node scripts/ensure-wiki.mjs --json` → `{mode, wikiRoot, …}`). **LOCAL** mode (no `url`) is a no-op that resolves the wiki root in precedence order `wiki.source.path` → `wikiRoot` in `wiki/lisa-wiki.config.json` → `wiki`; **REMOTE** mode (`url` set) clones-if-missing, fast-forwards when stale, and is offline-tolerant (proceeds with the existing mirror and warns rather than blocking). Callers (`lisa-wiki-query`, `lisa-wiki-ingest`) invoke it as step 0 and never hardcode `wiki/`; the freshness guarantee is the tool's, not the caller's.
|
|
170
202
|
|
|
171
203
|
### Vendor sections
|
|
172
204
|
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
# Wiki as Knowledge Source
|
|
2
2
|
|
|
3
|
-
If this project has an LLM Wiki
|
|
3
|
+
If this project has an LLM Wiki, treat it as the canonical source of durable project knowledge. The wiki is curated and current; ad-hoc scraping of code, tickets, chat history, or stale READMEs is not.
|
|
4
|
+
|
|
5
|
+
A project has a wiki in one of two shapes:
|
|
6
|
+
|
|
7
|
+
- **Local** — a `wiki/` directory with an `index.md` lives in this repo (`wikiRoot` in `wiki/lisa-wiki.config.json`, default `wiki`).
|
|
8
|
+
- **Remote** — `.lisa.config.json` declares a `wiki.source` pointer (`url`, optional `ref` / `mirrorPath` / `subdir`) at the canonical wiki repo. The wiki is **not** committed into this repo; instead the query/ingest skills maintain a gitignored mirror of it. This is the model for an organization whose documentation rolls up into one shared wiki that every repo reads: each repo carries only inline code comments locally, and the full cross-repo knowledge is the mirrored wiki.
|
|
9
|
+
|
|
10
|
+
Either way, freshness is not your concern. The query and ingest skills run `scripts/ensure-wiki.mjs` as their own first step, which resolves the wiki root and — for a remote wiki — clones the mirror if missing and fast-forwards it when stale (subject to a short TTL, and tolerant of being offline: it proceeds with the existing mirror and warns rather than blocking). The freshness guarantee lives in the tool, not in the caller's discipline. Do **not** add a separate "make sure the wiki is current" step to your own workflow — calling the skill already does it.
|
|
4
11
|
|
|
5
12
|
Before researching project background, conventions, ownership, architecture, glossary terms, or "how/why does X work here":
|
|
6
13
|
|
|
7
|
-
1. Consult the wiki first
|
|
14
|
+
1. Consult the wiki first via the wiki query skill (`/lisa-wiki-query`, or the runtime's wiki query skill), which resolves the wiki root for you. For a local wiki you may also start from `wiki/index.md` and follow links.
|
|
8
15
|
2. Use what the wiki says as the authoritative answer when it covers the question. Do not re-derive it from raw sources when the wiki already documents it.
|
|
9
16
|
3. Fall back to primary sources (code, tickets, commit history, external docs) only when the wiki is silent, ambiguous, or contradicted by what you observe in the code.
|
|
10
17
|
4. If you find the wiki is wrong, stale, or missing knowledge that belongs there, surface the gap — and where the project's workflow supports it, capture the correction back into the wiki via its ingestion path (`/lisa-wiki-ingest` or equivalent) rather than leaving the knowledge only in this session.
|
|
11
18
|
|
|
12
19
|
The wiki documents knowledge; it does not override executable behavior. When the wiki and the running code disagree about what the system actually does, trust the code and treat the wiki as out of date. See the `documentation-source-paths` rule for how source-material directories relate to the wiki.
|
|
13
20
|
|
|
14
|
-
If the project has
|
|
21
|
+
If the project has neither a local `wiki/` nor a `wiki.source` pointer in `.lisa.config.json`, this rule does not apply.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ensure-wiki.mjs — resolve the project's wiki root, mirroring/refreshing a
|
|
4
|
+
* remote wiki when one is configured. Dependency-free (Node built-ins only),
|
|
5
|
+
* so it stays portable to any downstream repo that installs the plugin.
|
|
6
|
+
*
|
|
7
|
+
* This is the single resolver that `lisa-wiki-query` and `lisa-wiki-ingest`
|
|
8
|
+
* call as step 0 so they never hardcode `wiki/` and never have to know whether
|
|
9
|
+
* the wiki is local or remote. The mode decision lives HERE, not in the skills:
|
|
10
|
+
*
|
|
11
|
+
* - LOCAL — the wiki lives on the local filesystem (the common case). Resolve
|
|
12
|
+
* the wiki root, in precedence order, from `wiki.source.path`, else
|
|
13
|
+
* `wikiRoot` in `wiki/lisa-wiki.config.json`, else `wiki`. No
|
|
14
|
+
* network, a no-op. `wiki.source.path` is just the explicit form of
|
|
15
|
+
* what the convention resolves implicitly.
|
|
16
|
+
* - REMOTE — `.lisa.config.json` declares `wiki.source.url`. Maintain a
|
|
17
|
+
* gitignored mirror of that repo and return the wiki root inside
|
|
18
|
+
* it. Clone-if-missing, fetch+fast-forward when stale (TTL), and
|
|
19
|
+
* tolerate being offline (proceed with the existing mirror + warn).
|
|
20
|
+
*
|
|
21
|
+
* `url` (remote) and `path` (local) are the two shapes of `wiki.source`; `url`
|
|
22
|
+
* takes precedence if both are somehow present.
|
|
23
|
+
*
|
|
24
|
+
* Config (consumer repo `.lisa.config.json`, with `.lisa.config.local.json`
|
|
25
|
+
* overriding per the config-resolution rule):
|
|
26
|
+
*
|
|
27
|
+
* "wiki": {
|
|
28
|
+
* "source": {
|
|
29
|
+
* // LOCAL shape — optional; defaults to the in-repo `wiki/` convention:
|
|
30
|
+
* "path": "wiki", // local wiki root, relative to repo root
|
|
31
|
+
* // REMOTE shape (instead of path):
|
|
32
|
+
* "url": "git@github.com:org/wiki.git", // present => REMOTE mode
|
|
33
|
+
* "ref": "main", // default: remote HEAD / "main"
|
|
34
|
+
* "mirrorPath": ".lisa/wiki", // default; always gitignored
|
|
35
|
+
* "subdir": "wiki" // optional: wiki root within the repo
|
|
36
|
+
* },
|
|
37
|
+
* "ttlSeconds": 300 // skip the fetch if synced more recently
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* Usage: node ensure-wiki.mjs [--cwd <dir>] [--json] [--ttl <seconds>] [--offline]
|
|
41
|
+
* --cwd project dir to resolve config/mirror against (default cwd)
|
|
42
|
+
* --json emit {mode, wikiRoot, mirrored, fetched, stale, offline} on stdout
|
|
43
|
+
* --ttl override ttlSeconds (0 = always fetch)
|
|
44
|
+
* --offline never touch the network; use whatever is already on disk
|
|
45
|
+
*
|
|
46
|
+
* Output: the resolved absolute wiki root is the LAST line on stdout (so a
|
|
47
|
+
* caller can `WIKI_ROOT=$(node ensure-wiki.mjs | tail -1)`). All human-facing
|
|
48
|
+
* progress goes to stderr. Exit 0 = a usable wiki root was resolved; 1 = not.
|
|
49
|
+
*/
|
|
50
|
+
import fs from "node:fs";
|
|
51
|
+
import path from "node:path";
|
|
52
|
+
import { execFileSync } from "node:child_process";
|
|
53
|
+
|
|
54
|
+
const GITIGNORE_BEGIN = "# BEGIN: AI GUARDRAILS WIKI MIRROR";
|
|
55
|
+
const GITIGNORE_END = "# END: AI GUARDRAILS WIKI MIRROR";
|
|
56
|
+
const DEFAULT_MIRROR = ".lisa/wiki";
|
|
57
|
+
const DEFAULT_TTL_SECONDS = 300;
|
|
58
|
+
|
|
59
|
+
function log(msg) {
|
|
60
|
+
process.stderr.write(`${msg}\n`);
|
|
61
|
+
}
|
|
62
|
+
function fail(msg) {
|
|
63
|
+
log(`✗ ${msg}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
function readJsonSafe(file) {
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function git(args, cwd) {
|
|
74
|
+
return execFileSync("git", args, {
|
|
75
|
+
cwd,
|
|
76
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
77
|
+
encoding: "utf8",
|
|
78
|
+
}).trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── args ────────────────────────────────────────────────────────────────────
|
|
82
|
+
const argv = process.argv.slice(2);
|
|
83
|
+
function flagValue(name) {
|
|
84
|
+
const i = argv.indexOf(name);
|
|
85
|
+
return i !== -1 && argv[i + 1] ? argv[i + 1] : undefined;
|
|
86
|
+
}
|
|
87
|
+
const projectDir = path.resolve(flagValue("--cwd") ?? process.cwd());
|
|
88
|
+
const asJson = argv.includes("--json");
|
|
89
|
+
const offline = argv.includes("--offline");
|
|
90
|
+
const ttlOverride = flagValue("--ttl");
|
|
91
|
+
|
|
92
|
+
// ── resolve the wiki source from .lisa.config.json (+ .local override) ────────
|
|
93
|
+
const committed =
|
|
94
|
+
readJsonSafe(path.join(projectDir, ".lisa.config.json")) ?? {};
|
|
95
|
+
const local =
|
|
96
|
+
readJsonSafe(path.join(projectDir, ".lisa.config.local.json")) ?? {};
|
|
97
|
+
const wikiCfg = { ...(committed.wiki ?? {}), ...(local.wiki ?? {}) };
|
|
98
|
+
const source = {
|
|
99
|
+
...(committed.wiki?.source ?? {}),
|
|
100
|
+
...(local.wiki?.source ?? {}),
|
|
101
|
+
};
|
|
102
|
+
const ttlSeconds = Number(
|
|
103
|
+
ttlOverride ?? wikiCfg.ttlSeconds ?? DEFAULT_TTL_SECONDS
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (source.url !== undefined && typeof source.url !== "string") {
|
|
107
|
+
fail("`wiki.source.url` in .lisa.config.json must be a string");
|
|
108
|
+
}
|
|
109
|
+
if (source.path !== undefined && typeof source.path !== "string") {
|
|
110
|
+
fail("`wiki.source.path` in .lisa.config.json must be a string");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function emit(result) {
|
|
114
|
+
if (asJson) process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
115
|
+
else process.stdout.write(`${result.wikiRoot}\n`);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── LOCAL mode (no remote clone) ───────────────────────────────────────────────
|
|
120
|
+
// The wiki lives on the local filesystem. Its root is, in precedence order:
|
|
121
|
+
// 1. `wiki.source.path` — explicit override in .lisa.config.json
|
|
122
|
+
// 2. `wikiRoot` from wiki/lisa-wiki.config.json — the in-repo convention
|
|
123
|
+
// 3. `wiki` — the default
|
|
124
|
+
// No network, a no-op resolve. `wiki.source.path` is just the explicit form of
|
|
125
|
+
// the same thing the convention resolves implicitly.
|
|
126
|
+
if (!source.url) {
|
|
127
|
+
const wikiRoot = path.resolve(
|
|
128
|
+
projectDir,
|
|
129
|
+
source.path ??
|
|
130
|
+
readJsonSafe(path.join(projectDir, "wiki", "lisa-wiki.config.json"))
|
|
131
|
+
?.wikiRoot ??
|
|
132
|
+
"wiki"
|
|
133
|
+
);
|
|
134
|
+
if (!fs.existsSync(path.join(wikiRoot, "index.md"))) {
|
|
135
|
+
log(
|
|
136
|
+
`⚠ no index.md under ${wikiRoot} — check wiki.source.path, or run /lisa-wiki:setup if the wiki is not scaffolded yet`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
log(`✓ local wiki: ${wikiRoot}`);
|
|
140
|
+
emit({
|
|
141
|
+
mode: "local",
|
|
142
|
+
wikiRoot,
|
|
143
|
+
mirrored: false,
|
|
144
|
+
fetched: false,
|
|
145
|
+
stale: false,
|
|
146
|
+
offline,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── REMOTE mode ───────────────────────────────────────────────────────────────
|
|
151
|
+
const mirrorPath = path.resolve(
|
|
152
|
+
projectDir,
|
|
153
|
+
source.mirrorPath ?? DEFAULT_MIRROR
|
|
154
|
+
);
|
|
155
|
+
const ref = source.ref || "";
|
|
156
|
+
ensureGitignored(projectDir, source.mirrorPath ?? DEFAULT_MIRROR);
|
|
157
|
+
|
|
158
|
+
const isClone = fs.existsSync(path.join(mirrorPath, ".git"));
|
|
159
|
+
let fetched = false;
|
|
160
|
+
let stale = false;
|
|
161
|
+
|
|
162
|
+
if (!isClone) {
|
|
163
|
+
if (offline) fail(`offline and no mirror present at ${mirrorPath}`);
|
|
164
|
+
log(`↧ cloning wiki ${source.url} → ${mirrorPath}`);
|
|
165
|
+
try {
|
|
166
|
+
fs.mkdirSync(path.dirname(mirrorPath), { recursive: true });
|
|
167
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
168
|
+
if (ref) cloneArgs.push("--branch", ref);
|
|
169
|
+
cloneArgs.push(source.url, mirrorPath);
|
|
170
|
+
git(cloneArgs, projectDir);
|
|
171
|
+
fetched = true;
|
|
172
|
+
stampSync(mirrorPath);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
fail(`clone failed: ${String(e.message ?? e).split("\n")[0]}`);
|
|
175
|
+
}
|
|
176
|
+
} else if (offline) {
|
|
177
|
+
log("• offline — using existing mirror without fetching");
|
|
178
|
+
stale = true;
|
|
179
|
+
} else if (isFresh(mirrorPath, ttlSeconds)) {
|
|
180
|
+
log(`• mirror synced < ${ttlSeconds}s ago — skipping fetch`);
|
|
181
|
+
} else {
|
|
182
|
+
// Stale: fetch and hard-reset. The mirror is a read-only working copy of the
|
|
183
|
+
// canonical wiki — never edited in place — so resetting to the remote ref is
|
|
184
|
+
// safe and keeps it byte-identical to upstream.
|
|
185
|
+
try {
|
|
186
|
+
const branch =
|
|
187
|
+
ref || git(["rev-parse", "--abbrev-ref", "HEAD"], mirrorPath);
|
|
188
|
+
log(`↻ refreshing wiki mirror (${branch})`);
|
|
189
|
+
git(["fetch", "--depth", "1", "origin", branch], mirrorPath);
|
|
190
|
+
git(["reset", "--hard", `origin/${branch}`], mirrorPath);
|
|
191
|
+
fetched = true;
|
|
192
|
+
stampSync(mirrorPath);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// Offline-tolerant: a fetch failure must not block work when we already
|
|
195
|
+
// have a usable (if stale) copy on disk.
|
|
196
|
+
log(
|
|
197
|
+
`⚠ refresh failed (${String(e.message ?? e).split("\n")[0]}) — using stale mirror`
|
|
198
|
+
);
|
|
199
|
+
stale = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const wikiRoot = resolveWikiRootInMirror(mirrorPath, source.subdir);
|
|
204
|
+
if (!fs.existsSync(path.join(wikiRoot, "index.md"))) {
|
|
205
|
+
log(
|
|
206
|
+
`⚠ no index.md under ${wikiRoot} — check wiki.source.subdir / the wiki repo layout`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
log(`✓ remote wiki mirror: ${wikiRoot}`);
|
|
210
|
+
emit({ mode: "remote", wikiRoot, mirrored: true, fetched, stale, offline });
|
|
211
|
+
|
|
212
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
/** Locate the wiki content root inside a cloned wiki repo. */
|
|
215
|
+
function resolveWikiRootInMirror(mirror, subdir) {
|
|
216
|
+
if (subdir) return path.resolve(mirror, subdir);
|
|
217
|
+
if (fs.existsSync(path.join(mirror, "wiki", "index.md")))
|
|
218
|
+
return path.join(mirror, "wiki");
|
|
219
|
+
return mirror; // dedicated wiki repo with content at its root
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** TTL stamp lives under .git so it is never part of the wiki content. */
|
|
223
|
+
function stampPath(mirror) {
|
|
224
|
+
return path.join(mirror, ".git", "lisa-wiki-sync");
|
|
225
|
+
}
|
|
226
|
+
function stampSync(mirror) {
|
|
227
|
+
try {
|
|
228
|
+
fs.writeFileSync(stampPath(mirror), String(Date.now()));
|
|
229
|
+
} catch {
|
|
230
|
+
/* best-effort */
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function isFresh(mirror, ttl) {
|
|
234
|
+
if (ttl <= 0) return false;
|
|
235
|
+
let last = NaN;
|
|
236
|
+
try {
|
|
237
|
+
last = Number(fs.readFileSync(stampPath(mirror), "utf8"));
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
if (!Number.isFinite(last)) return false;
|
|
242
|
+
return Date.now() - last < ttl * 1000;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Idempotently keep the mirror path out of version control. */
|
|
246
|
+
function ensureGitignored(dir, relPath) {
|
|
247
|
+
const gitignorePath = path.join(dir, ".gitignore");
|
|
248
|
+
const line = `/${relPath.replace(/^\/+/, "").replace(/\/+$/, "")}/`;
|
|
249
|
+
const block = `${GITIGNORE_BEGIN}\n# Remote wiki mirror — gitignored working copy, managed by ensure-wiki.\n${line}\n${GITIGNORE_END}`;
|
|
250
|
+
const existing = fs.existsSync(gitignorePath)
|
|
251
|
+
? fs.readFileSync(gitignorePath, "utf8")
|
|
252
|
+
: null;
|
|
253
|
+
if (existing && existing.includes(line)) return; // already ignored (any block)
|
|
254
|
+
const start = existing?.indexOf(GITIGNORE_BEGIN) ?? -1;
|
|
255
|
+
let merged;
|
|
256
|
+
if (existing === null) {
|
|
257
|
+
merged = `${block}\n`;
|
|
258
|
+
} else if (start !== -1) {
|
|
259
|
+
const end = existing.indexOf(GITIGNORE_END, start) + GITIGNORE_END.length;
|
|
260
|
+
merged = `${existing.slice(0, start)}${block}${existing.slice(end)}`;
|
|
261
|
+
} else {
|
|
262
|
+
const base = existing.endsWith("\n") ? existing : `${existing}\n`;
|
|
263
|
+
merged = `${base}\n${block}\n`;
|
|
264
|
+
}
|
|
265
|
+
fs.writeFileSync(gitignorePath, merged);
|
|
266
|
+
log(`✓ ensured ${line} is gitignored`);
|
|
267
|
+
}
|
|
@@ -19,10 +19,23 @@ performs the shared, ordered pipeline.
|
|
|
19
19
|
run includes explicit external-write intent**.
|
|
20
20
|
- **Dry run:** `/ingest --dry-run` — list the sources a full ingest would run; perform no writes.
|
|
21
21
|
|
|
22
|
+
## Step 0 — resolve the wiki root (once per run)
|
|
23
|
+
|
|
24
|
+
Before anything else, run `node scripts/ensure-wiki.mjs --json` and use the returned `wikiRoot` as the
|
|
25
|
+
base for the whole pipeline — never hardcode `wiki/`. This is mode-agnostic by design:
|
|
26
|
+
|
|
27
|
+
- **Local wiki** (no `wiki.source` in `.lisa.config.json`) — resolves the in-repo wiki root instantly;
|
|
28
|
+
the branch sync below proceeds against **this** repo's remote, exactly as before.
|
|
29
|
+
- **Remote wiki** (`wiki.source.url` set) — `ensure-wiki` mirrors/refreshes the gitignored working copy
|
|
30
|
+
of the canonical wiki repo and returns its path. In this mode the "sync the branch" step below, and
|
|
31
|
+
the Commit/PR step (7), operate against the **wiki repo's own remote** (the mirror), not the host
|
|
32
|
+
project's `origin`. The host project repo is never written to.
|
|
33
|
+
|
|
22
34
|
## Before ingesting — sync the branch (once per run)
|
|
23
35
|
|
|
24
36
|
Run this **once per ingest invocation, before any source is processed** (skip for `--dry-run`, which
|
|
25
|
-
writes nothing). The point is to ingest on top of fresh state, never stale state.
|
|
37
|
+
writes nothing). The point is to ingest on top of fresh state, never stale state. Operate in the wiki
|
|
38
|
+
root resolved in Step 0 — the host repo for a local wiki, the mirror for a remote one.
|
|
26
39
|
|
|
27
40
|
1. **Resolve the default remote branch** — `gh repo view --json defaultBranchRef -q .defaultBranchRef.name`,
|
|
28
41
|
or `git remote show origin | sed -n 's/.*HEAD branch: //p'`, or the `origin/HEAD` symbolic ref. If
|
|
@@ -8,7 +8,13 @@ description: Answer a question from the LLM Wiki with citations. Reads the index
|
|
|
8
8
|
Answer from the wiki, with citations, without changing it (by default).
|
|
9
9
|
|
|
10
10
|
## Workflow
|
|
11
|
-
|
|
11
|
+
0. **Resolve the wiki root.** Run `node scripts/ensure-wiki.mjs --json` and use the returned
|
|
12
|
+
`wikiRoot` as the base for every read below — never assume `wiki/`. A local wiki resolves
|
|
13
|
+
instantly (no-op); a wiki whose `.lisa.config.json` declares `wiki.source.url` is mirrored and
|
|
14
|
+
refreshed transparently first. The script is offline-tolerant (it proceeds with the existing
|
|
15
|
+
mirror and warns rather than blocking), so freshness is guaranteed here and the caller never has
|
|
16
|
+
to think about it.
|
|
17
|
+
1. Read `<wikiRoot>/index.md` to locate candidate pages; consult `<wikiRoot>/start-here.md` for orientation.
|
|
12
18
|
2. Drill into the relevant synthesis pages and their cited source notes.
|
|
13
19
|
3. Synthesize an answer. **Every claim cites its wiki page and/or source note.** If the wiki does not
|
|
14
20
|
support an answer, say so plainly rather than inventing one; suggest an `/ingest` that would fill
|