@codyswann/lisa 2.101.1 → 2.103.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/README.md +4 -0
- package/dist/utils/postinstall-trampoline.d.ts.map +1 -1
- package/dist/utils/postinstall-trampoline.js +10 -0
- package/dist/utils/postinstall-trampoline.js.map +1 -1
- 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/commands/wiki/install.md +7 -0
- package/plugins/lisa/rules/config-resolution.md +1 -0
- package/plugins/lisa/skills/wiki-install/SKILL.md +101 -0
- package/plugins/lisa/skills/wiki-install/agents/openai.yaml +4 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-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-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-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-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-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-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/scripts/wiki-status.mjs +310 -0
- package/plugins/src/base/commands/wiki/install.md +7 -0
- package/plugins/src/base/rules/config-resolution.md +1 -0
- package/plugins/src/base/skills/wiki-install/SKILL.md +101 -0
- package/plugins/src/wiki/scripts/wiki-status.mjs +310 -0
package/README.md
CHANGED
|
@@ -164,3 +164,7 @@ Useful ingestion requests:
|
|
|
164
164
|
- Ingest this design plan into the Lisa wiki.
|
|
165
165
|
- Ingest these meeting notes.
|
|
166
166
|
- Update the architecture overview from recent source changes.
|
|
167
|
+
|
|
168
|
+
### Adding a wiki to a downstream project
|
|
169
|
+
|
|
170
|
+
The wiki kernel ships as a separate plugin (`lisa-wiki`) that is `AVAILABLE` but not enabled by default. To bootstrap it in a project that already has Lisa installed, run `/lisa:wiki:install` (Claude) or `$lisa-wiki-install` (Codex). This shipped-with-base command flips `lisa-wiki@lisa` to enabled in `.claude/settings.json` and verifies the Codex skill overlay is current — but does not scaffold the wiki itself. After reload, follow up with `/setup:wiki` (Claude) or `$lisa-wiki-setup` (Codex) to create `wiki/` from `wiki/lisa-wiki.config.json`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postinstall-trampoline.d.ts","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAmC3C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AA6DD;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,OAAO,CASvC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uCAAuC,CACrD,MAAM,EAAE,OAAO,GACd,OAAO,
|
|
1
|
+
{"version":3,"file":"postinstall-trampoline.d.ts","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAmC3C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AA6DD;;;;;GAKG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,IAAI,OAAO,CASvC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uCAAuC,CACrD,MAAM,EAAE,OAAO,GACd,OAAO,CAYT;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,GACjB,SAAS,cAAc,EAAE,CAI3B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,GAAG,iBAAiB,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAMjB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,IAAI,CAAC,CAgCf"}
|
|
@@ -143,6 +143,16 @@ export function shouldSchedulePostinstallReconciliation(dryRun) {
|
|
|
143
143
|
return false;
|
|
144
144
|
if (isRunningAsTrampoline())
|
|
145
145
|
return false;
|
|
146
|
+
// Test runners (vitest, jest) frequently invoke `bun run` / `npm test` which
|
|
147
|
+
// sets `npm_package_json`, making `isRunningAsLifecycleScript()` true. The
|
|
148
|
+
// detached trampoline child then races against the test's temp-dir cleanup
|
|
149
|
+
// and dies with ENOENT when its cwd vanishes. Same principle as
|
|
150
|
+
// isRunningInCI's vitest/jest opt-out above — test runners are not real
|
|
151
|
+
// package-manager processes and the trampoline must not spawn from them.
|
|
152
|
+
if (readEnv("VITEST") !== undefined)
|
|
153
|
+
return false;
|
|
154
|
+
if (readEnv("JEST_WORKER_ID") !== undefined)
|
|
155
|
+
return false;
|
|
146
156
|
return isRunningAsLifecycleScript();
|
|
147
157
|
}
|
|
148
158
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postinstall-trampoline.js","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postinstall-trampoline.js","sourceRoot":"","sources":["../../src/utils/postinstall-trampoline.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"lodash": ">=4.18.1"
|
|
83
83
|
},
|
|
84
84
|
"name": "@codyswann/lisa",
|
|
85
|
-
"version": "2.
|
|
85
|
+
"version": "2.103.0",
|
|
86
86
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
87
87
|
"main": "dist/index.js",
|
|
88
88
|
"exports": {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Enable the LLM Wiki kernel (lisa-wiki plugin) in this project so its setup skill becomes discoverable. Edits .claude/settings.json to enable lisa-wiki@lisa and confirm the CodySwannGT/lisa marketplace, then verifies the Codex overlay (.codex/skills/lisa) already carries the wiki kernel. Does NOT scaffold the wiki itself — after install, reload the runtime and run /setup:wiki (Claude) or $lisa-wiki-setup (Codex)."
|
|
3
|
+
allowed-tools: ["Skill"]
|
|
4
|
+
argument-hint: ""
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Use the /lisa:wiki-install skill to enable the lisa-wiki plugin in this project. $ARGUMENTS
|
|
@@ -164,6 +164,7 @@ fi
|
|
|
164
164
|
|-------|----------|---------|-------|
|
|
165
165
|
| `tracker` | **yes** | — | Destination for ticket writes. One of `"jira"`, `"github"`, `"linear"`. Missing → fail with instruction to run the matching `/lisa:setup:*` skill. |
|
|
166
166
|
| `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. |
|
|
167
|
+
| `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. |
|
|
167
168
|
|
|
168
169
|
### Vendor sections
|
|
169
170
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-install
|
|
3
|
+
description: "Bootstrap the LLM Wiki kernel (lisa-wiki plugin) in a host project. Solves the chicken-and-egg gap: the base lisa plugin can install the wiki plugin so its setup skill becomes discoverable. Edits .claude/settings.json to enable lisa-wiki@lisa and confirm the CodySwannGT/lisa marketplace, then for Codex verifies whether the .codex/skills/lisa overlay already carries lisa-wiki-* skills (printed by Lisa's apply) and nudges the user to refresh the overlay if missing. Idempotent. Never auto-runs `lisa apply`. After this skill, reload the runtime and run /setup:wiki (Claude) or $lisa-wiki-setup (Codex) to scaffold the wiki itself."
|
|
4
|
+
allowed-tools: ["Bash", "Read", "Write", "Edit"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Install the LLM Wiki kernel
|
|
8
|
+
|
|
9
|
+
This skill makes the `lisa-wiki` plugin visible in the current project so its scaffolder (`/setup:wiki` / `$lisa-wiki-setup`) can run. It does **not** scaffold the wiki itself — that is the wiki plugin's job. This skill only flips the install bit and verifies the Codex overlay carries the kernel.
|
|
10
|
+
|
|
11
|
+
## Why this skill exists
|
|
12
|
+
|
|
13
|
+
`lisa-wiki` is published as `AVAILABLE` (not `INSTALLED_BY_DEFAULT`) in Lisa's marketplace. A downstream project that never enabled it has no way to discover the wiki's setup command — a chicken-and-egg bootstrap gap. Because the base `lisa` plugin is auto-enabled everywhere, putting the bootstrap here is what makes the wiki reachable.
|
|
14
|
+
|
|
15
|
+
## Asymmetry note
|
|
16
|
+
|
|
17
|
+
The two runtimes work differently:
|
|
18
|
+
|
|
19
|
+
- **Claude Code** loads plugin skills per-project through `.claude/settings.json` `enabledPlugins`. The wiki skills are invisible until `lisa-wiki@lisa` is enabled there. This skill makes that edit.
|
|
20
|
+
- **Codex** loads Lisa skills through an overlay path (`src/codex/skills-installer.ts` runs from `Lisa.apply()` and copies every built `plugins/<p>/skills/<n>/` into `.codex/skills/lisa/`). Wiki skills land in that overlay automatically every time the project re-applies Lisa. This skill does not mutate Codex config — it only checks whether the overlay carries `lisa-wiki-setup` and tells the user how to refresh if missing.
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
### Step 1 — Verify we are in a project, not Lisa itself
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cwd="$(pwd)"
|
|
28
|
+
if [ -f "$cwd/.lisa-source" ] || { [ -d "$cwd/plugins/src/base" ] && [ -f "$cwd/.claude-plugin/marketplace.json" ]; }; then
|
|
29
|
+
echo "This is the Lisa monorepo itself — the base plugin's wiki kernel already lives here."
|
|
30
|
+
echo "Run /setup:wiki to scaffold a wiki, or invoke this skill from inside a downstream project."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 2 — Enable lisa-wiki@lisa in the Claude project settings
|
|
36
|
+
|
|
37
|
+
Read `<cwd>/.claude/settings.json` (create with `{}` if absent). Then, using the **Edit** or **Write** tool with valid JSON (not jq one-liners that risk corrupting comments / formatting):
|
|
38
|
+
|
|
39
|
+
1. Ensure `enabledPlugins` is an object.
|
|
40
|
+
2. Set `enabledPlugins["lisa-wiki@lisa"] = true`.
|
|
41
|
+
3. Ensure `extraKnownMarketplaces` is an object containing the entry below if it is not already present (do **not** overwrite an existing entry):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"CodySwannGT/lisa": {
|
|
45
|
+
"source": {
|
|
46
|
+
"source": "github",
|
|
47
|
+
"repo": "CodySwannGT/lisa"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. Pretty-print the file with 2-space indentation and a trailing newline.
|
|
53
|
+
|
|
54
|
+
If `enabledPlugins["lisa-wiki@lisa"]` was already `true`, log "Claude: lisa-wiki@lisa already enabled — no change." If not, log "Claude: enabled lisa-wiki@lisa in .claude/settings.json."
|
|
55
|
+
|
|
56
|
+
**Do not** edit `.claude/settings.local.json` — that file is per-user and is not the right surface for a project-level enablement.
|
|
57
|
+
|
|
58
|
+
### Step 3 — Verify the Codex overlay carries lisa-wiki
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
codex_wiki_setup=".codex/skills/lisa/lisa-wiki-setup/SKILL.md"
|
|
62
|
+
if [ -f "$codex_wiki_setup" ]; then
|
|
63
|
+
echo "Codex: lisa-wiki overlay already present (found $codex_wiki_setup). Nothing to do for Codex."
|
|
64
|
+
else
|
|
65
|
+
echo "Codex: lisa-wiki skills not found in .codex/skills/lisa/."
|
|
66
|
+
if [ -d ".agents" ] || [ -d ".codex" ]; then
|
|
67
|
+
echo " → This project appears Codex-wired but its overlay is stale or predates lisa-wiki."
|
|
68
|
+
echo " → Refresh it by running your project's Lisa apply command, e.g.:"
|
|
69
|
+
echo " npx lisa --harness=codex ."
|
|
70
|
+
echo " (or whatever shape your project uses to apply Lisa for Codex)."
|
|
71
|
+
else
|
|
72
|
+
echo " → This project does not have a Codex installation yet (no .agents/ or .codex/). Skip if Codex parity is not needed."
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Do not** invoke `lisa apply` automatically. `lisa apply` rewrites more than the wiki overlay; it must remain the user's explicit choice.
|
|
78
|
+
|
|
79
|
+
### Step 4 — Hand off
|
|
80
|
+
|
|
81
|
+
Print:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Wiki kernel install complete.
|
|
85
|
+
|
|
86
|
+
Next steps:
|
|
87
|
+
• Reload your runtime so it picks up the newly-enabled plugin (Claude: /reload or restart the session; Codex: restart `codex`).
|
|
88
|
+
• Then run /setup:wiki (Claude) or $lisa-wiki-setup (Codex) to scaffold wiki/ in this project.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Rules
|
|
92
|
+
|
|
93
|
+
- Idempotent. Re-running produces no spurious changes once both flags are set.
|
|
94
|
+
- Project-scoped only. Never touch `~/.codex/config.toml` or any user-global config.
|
|
95
|
+
- Never run `lisa apply` on the user's behalf.
|
|
96
|
+
- Never edit `.claude/settings.local.json` — it is user-scoped overrides.
|
|
97
|
+
- Preserve human-authored content. Only flip the enablement key and add the marketplace entry if missing.
|
|
98
|
+
|
|
99
|
+
## Related
|
|
100
|
+
|
|
101
|
+
`lisa-wiki-setup` (the actual scaffolder, lives in the `lisa-wiki` plugin once enabled), `lisa-wiki-doctor`, `lisa-wiki-usage`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.0",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wiki-status.mjs — read-only Lisa wiki source freshness report.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node wiki-status.mjs [--wiki <wikiRoot>] [--config <path>] [--json]
|
|
6
|
+
* Exit 0 = report rendered. Missing wiki/config are represented in the report.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { loadConfig, walkFiles } from "./_wiki-lib.mjs";
|
|
12
|
+
|
|
13
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
const DEFAULT_STALE_AFTER_DAYS = 7;
|
|
15
|
+
|
|
16
|
+
const argv = process.argv.slice(2);
|
|
17
|
+
const flag = name => argv.includes(name);
|
|
18
|
+
const opt = name => {
|
|
19
|
+
const i = argv.indexOf(name);
|
|
20
|
+
return i !== -1 ? argv[i + 1] : undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function relToCwd(filePath) {
|
|
24
|
+
return path.relative(process.cwd(), filePath).replaceAll(path.sep, "/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeWikiPath(filePath) {
|
|
28
|
+
return filePath.replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseDate(value) {
|
|
32
|
+
if (!value || typeof value !== "string") return undefined;
|
|
33
|
+
const date = new Date(value);
|
|
34
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function newestDate(values) {
|
|
38
|
+
return values
|
|
39
|
+
.map(parseDate)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.sort((a, b) => b.getTime() - a.getTime())[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readTextSafe(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
return fs.readFileSync(filePath, "utf8");
|
|
47
|
+
} catch {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readJsonSafe(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function enabledConnectors(config) {
|
|
61
|
+
const connectors = config?.connectors ?? {};
|
|
62
|
+
return Object.entries(connectors)
|
|
63
|
+
.filter(([, connectorConfig]) => connectorConfig?.enabled !== false)
|
|
64
|
+
.filter(
|
|
65
|
+
([, connectorConfig]) => connectorConfig?.sideEffects !== "external-write"
|
|
66
|
+
)
|
|
67
|
+
.map(([name, connectorConfig]) => ({
|
|
68
|
+
name,
|
|
69
|
+
sideEffects: connectorConfig?.sideEffects ?? "unknown",
|
|
70
|
+
}))
|
|
71
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function logSections(logText) {
|
|
75
|
+
const lines = logText.split("\n");
|
|
76
|
+
const sections = [];
|
|
77
|
+
let current;
|
|
78
|
+
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
const heading = line.match(/^##\s+(\d{4}-\d{2}-\d{2})(?:\s+-\s+(.*))?$/);
|
|
81
|
+
if (heading) {
|
|
82
|
+
if (current) sections.push(current);
|
|
83
|
+
current = {
|
|
84
|
+
date: heading[1],
|
|
85
|
+
title: heading[2] ?? "",
|
|
86
|
+
lines: [line],
|
|
87
|
+
};
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (current) current.lines.push(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (current) sections.push(current);
|
|
94
|
+
return sections;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function connectorLogFacts(logText, connectorName) {
|
|
98
|
+
const sections = logSections(logText);
|
|
99
|
+
const escaped = connectorName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
100
|
+
const quoted = new RegExp(`\`${escaped}\``, "i");
|
|
101
|
+
const bare = new RegExp(`\\b${escaped}\\b`, "i");
|
|
102
|
+
|
|
103
|
+
for (const section of [...sections].reverse()) {
|
|
104
|
+
const lines = section.lines.filter(
|
|
105
|
+
line => quoted.test(line) || bare.test(line)
|
|
106
|
+
);
|
|
107
|
+
if (lines.length === 0) continue;
|
|
108
|
+
|
|
109
|
+
const lower = lines.join("\n").toLowerCase();
|
|
110
|
+
const status = lower.includes("skipped")
|
|
111
|
+
? "skipped"
|
|
112
|
+
: lower.includes("blocked") || lower.includes("failed")
|
|
113
|
+
? "blocked"
|
|
114
|
+
: lower.includes("ingested") || lower.includes("refreshed")
|
|
115
|
+
? "ingested"
|
|
116
|
+
: "observed";
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
date: section.date,
|
|
120
|
+
status,
|
|
121
|
+
reason: lines.map(line => line.replace(/^-\s*/, "").trim()).join(" "),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function stateFacts(wikiRoot, connectorName) {
|
|
129
|
+
const stateDir = path.join(wikiRoot, "state", connectorName);
|
|
130
|
+
const files = walkFiles(stateDir, { ext: ".json" });
|
|
131
|
+
const states = files
|
|
132
|
+
.map(filePath => ({ filePath, data: readJsonSafe(filePath) }))
|
|
133
|
+
.filter(item => item.data);
|
|
134
|
+
const dated = states
|
|
135
|
+
.map(item => ({
|
|
136
|
+
...item,
|
|
137
|
+
date:
|
|
138
|
+
parseDate(item.data.ingested_at) ??
|
|
139
|
+
parseDate(item.data.cursor?.lastIngest) ??
|
|
140
|
+
parseDate(item.data.updated_at),
|
|
141
|
+
}))
|
|
142
|
+
.sort((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0));
|
|
143
|
+
const latest = dated[0] ?? states[0];
|
|
144
|
+
const sourceNotes = new Set();
|
|
145
|
+
|
|
146
|
+
for (const item of states) {
|
|
147
|
+
const notes = item.data.source_notes ?? item.data.sourceNotes ?? [];
|
|
148
|
+
if (Array.isArray(notes)) {
|
|
149
|
+
for (const note of notes) {
|
|
150
|
+
if (typeof note === "string") sourceNotes.add(normalizeWikiPath(note));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (typeof item.data.source_note === "string") {
|
|
154
|
+
sourceNotes.add(normalizeWikiPath(item.data.source_note));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
stateFiles: files.map(filePath => relToCwd(filePath)),
|
|
160
|
+
latestObservedAt: latest?.date?.toISOString() ?? latest?.data?.ingested_at,
|
|
161
|
+
sourceNotes: [...sourceNotes],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sourceFacts(wikiRoot, connectorName, configuredSourceNotes) {
|
|
166
|
+
const sourceDir = path.join(wikiRoot, "sources", connectorName);
|
|
167
|
+
const markdownFiles = walkFiles(sourceDir, { ext: ".md" }).map(relToCwd);
|
|
168
|
+
const wikiParent = path.dirname(wikiRoot);
|
|
169
|
+
const sourceNoteExists = note =>
|
|
170
|
+
fs.existsSync(path.resolve(note)) ||
|
|
171
|
+
fs.existsSync(path.resolve(wikiParent, note));
|
|
172
|
+
const existingConfigured = configuredSourceNotes.filter(note =>
|
|
173
|
+
sourceNoteExists(note)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
sourceNotes:
|
|
178
|
+
existingConfigured.length > 0
|
|
179
|
+
? existingConfigured
|
|
180
|
+
: markdownFiles.map(normalizeWikiPath),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function nextActionFor(verdict, connectorName, reason) {
|
|
185
|
+
if (verdict === "fresh") return "No action needed.";
|
|
186
|
+
if (verdict === "skipped") {
|
|
187
|
+
return reason?.toLowerCase().includes("project-scoped memory")
|
|
188
|
+
? "Provide project-scoped memory for this repo, or accept the expected skip."
|
|
189
|
+
: `Review the skip reason, then run /lisa-wiki:ingest --source ${connectorName} when available.`;
|
|
190
|
+
}
|
|
191
|
+
if (verdict === "blocked") {
|
|
192
|
+
return `Resolve the blocker, then run /lisa-wiki:ingest --source ${connectorName}.`;
|
|
193
|
+
}
|
|
194
|
+
return `Run /lisa-wiki:ingest --source ${connectorName}.`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createWikiFreshnessReport({
|
|
198
|
+
configPath = "wiki/lisa-wiki.config.json",
|
|
199
|
+
wikiRoot,
|
|
200
|
+
now = new Date(),
|
|
201
|
+
staleAfterDays = DEFAULT_STALE_AFTER_DAYS,
|
|
202
|
+
} = {}) {
|
|
203
|
+
const { config, configPath: resolvedConfigPath } = loadConfig(configPath);
|
|
204
|
+
const resolvedWikiRoot = path.resolve(wikiRoot ?? config?.wikiRoot ?? "wiki");
|
|
205
|
+
const logPath = path.join(resolvedWikiRoot, "log.md");
|
|
206
|
+
const logText = readTextSafe(logPath);
|
|
207
|
+
const connectors = enabledConnectors(config);
|
|
208
|
+
const staleAfterMs = staleAfterDays * DAY_MS;
|
|
209
|
+
|
|
210
|
+
const items = connectors.map(connector => {
|
|
211
|
+
const state = stateFacts(resolvedWikiRoot, connector.name);
|
|
212
|
+
const sources = sourceFacts(
|
|
213
|
+
resolvedWikiRoot,
|
|
214
|
+
connector.name,
|
|
215
|
+
state.sourceNotes
|
|
216
|
+
);
|
|
217
|
+
const log = connectorLogFacts(logText, connector.name);
|
|
218
|
+
const observedDate =
|
|
219
|
+
newestDate([state.latestObservedAt, log?.date]) ??
|
|
220
|
+
newestDate(
|
|
221
|
+
sources.sourceNotes.map(note => note.match(/\d{4}-\d{2}-\d{2}/)?.[0])
|
|
222
|
+
);
|
|
223
|
+
const hasState = state.stateFiles.length > 0;
|
|
224
|
+
const hasSourceNotes = sources.sourceNotes.length > 0;
|
|
225
|
+
const isStale =
|
|
226
|
+
observedDate && now.getTime() - observedDate.getTime() > staleAfterMs;
|
|
227
|
+
|
|
228
|
+
const verdict =
|
|
229
|
+
log?.status === "blocked"
|
|
230
|
+
? "blocked"
|
|
231
|
+
: log?.status === "skipped" && !hasState && !hasSourceNotes
|
|
232
|
+
? "skipped"
|
|
233
|
+
: !hasState && !hasSourceNotes
|
|
234
|
+
? "never_ingested"
|
|
235
|
+
: !hasState || !hasSourceNotes || isStale
|
|
236
|
+
? "stale"
|
|
237
|
+
: "fresh";
|
|
238
|
+
|
|
239
|
+
const evidence = [
|
|
240
|
+
...sources.sourceNotes,
|
|
241
|
+
...state.stateFiles,
|
|
242
|
+
fs.existsSync(logPath) ? relToCwd(logPath) : undefined,
|
|
243
|
+
].filter(Boolean);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
connector: connector.name,
|
|
247
|
+
sideEffects: connector.sideEffects,
|
|
248
|
+
verdict,
|
|
249
|
+
evidence,
|
|
250
|
+
lastObserved: observedDate?.toISOString().slice(0, 10) ?? "unknown",
|
|
251
|
+
reason: verdict === "fresh" ? "" : (log?.reason ?? ""),
|
|
252
|
+
nextAction: nextActionFor(verdict, connector.name, log?.reason),
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
generatedAt: now.toISOString(),
|
|
258
|
+
configPath: relToCwd(resolvedConfigPath),
|
|
259
|
+
wikiRoot: relToCwd(resolvedWikiRoot),
|
|
260
|
+
items,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function renderWikiFreshnessReport(report) {
|
|
265
|
+
const lines = [
|
|
266
|
+
"# Lisa wiki source freshness",
|
|
267
|
+
"",
|
|
268
|
+
`Generated: ${report.generatedAt}`,
|
|
269
|
+
`Config: ${report.configPath}`,
|
|
270
|
+
`Wiki root: ${report.wikiRoot}`,
|
|
271
|
+
"",
|
|
272
|
+
"| Connector | Verdict | Last observed | Evidence | Next action |",
|
|
273
|
+
"| --- | --- | --- | --- | --- |",
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const item of report.items) {
|
|
277
|
+
const evidence =
|
|
278
|
+
item.evidence.length > 0
|
|
279
|
+
? item.evidence.slice(0, 3).join("<br>")
|
|
280
|
+
: "none";
|
|
281
|
+
lines.push(
|
|
282
|
+
`| ${item.connector} | ${item.verdict} | ${item.lastObserved} | ${evidence} | ${item.nextAction} |`
|
|
283
|
+
);
|
|
284
|
+
if (item.reason && item.verdict !== "fresh") {
|
|
285
|
+
lines.push(
|
|
286
|
+
`| ${item.connector} | reason | ${item.lastObserved} | log | ${item.reason} |`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
lines.push(
|
|
292
|
+
"",
|
|
293
|
+
"Integrity follow-up: run /lisa-wiki:lint separately for broken links, stale claims, or structure issues."
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return `${lines.join("\n")}\n`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
300
|
+
const report = createWikiFreshnessReport({
|
|
301
|
+
configPath: opt("--config") ?? "wiki/lisa-wiki.config.json",
|
|
302
|
+
wikiRoot: opt("--wiki"),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (flag("--json")) {
|
|
306
|
+
console.log(JSON.stringify(report, null, 2));
|
|
307
|
+
} else {
|
|
308
|
+
process.stdout.write(renderWikiFreshnessReport(report));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Enable the LLM Wiki kernel (lisa-wiki plugin) in this project so its setup skill becomes discoverable. Edits .claude/settings.json to enable lisa-wiki@lisa and confirm the CodySwannGT/lisa marketplace, then verifies the Codex overlay (.codex/skills/lisa) already carries the wiki kernel. Does NOT scaffold the wiki itself — after install, reload the runtime and run /setup:wiki (Claude) or $lisa-wiki-setup (Codex)."
|
|
3
|
+
allowed-tools: ["Skill"]
|
|
4
|
+
argument-hint: ""
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Use the /lisa:wiki-install skill to enable the lisa-wiki plugin in this project. $ARGUMENTS
|
|
@@ -164,6 +164,7 @@ fi
|
|
|
164
164
|
|-------|----------|---------|-------|
|
|
165
165
|
| `tracker` | **yes** | — | Destination for ticket writes. One of `"jira"`, `"github"`, `"linear"`. Missing → fail with instruction to run the matching `/lisa:setup:*` skill. |
|
|
166
166
|
| `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. |
|
|
167
|
+
| `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. |
|
|
167
168
|
|
|
168
169
|
### Vendor sections
|
|
169
170
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-install
|
|
3
|
+
description: "Bootstrap the LLM Wiki kernel (lisa-wiki plugin) in a host project. Solves the chicken-and-egg gap: the base lisa plugin can install the wiki plugin so its setup skill becomes discoverable. Edits .claude/settings.json to enable lisa-wiki@lisa and confirm the CodySwannGT/lisa marketplace, then for Codex verifies whether the .codex/skills/lisa overlay already carries lisa-wiki-* skills (printed by Lisa's apply) and nudges the user to refresh the overlay if missing. Idempotent. Never auto-runs `lisa apply`. After this skill, reload the runtime and run /setup:wiki (Claude) or $lisa-wiki-setup (Codex) to scaffold the wiki itself."
|
|
4
|
+
allowed-tools: ["Bash", "Read", "Write", "Edit"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Install the LLM Wiki kernel
|
|
8
|
+
|
|
9
|
+
This skill makes the `lisa-wiki` plugin visible in the current project so its scaffolder (`/setup:wiki` / `$lisa-wiki-setup`) can run. It does **not** scaffold the wiki itself — that is the wiki plugin's job. This skill only flips the install bit and verifies the Codex overlay carries the kernel.
|
|
10
|
+
|
|
11
|
+
## Why this skill exists
|
|
12
|
+
|
|
13
|
+
`lisa-wiki` is published as `AVAILABLE` (not `INSTALLED_BY_DEFAULT`) in Lisa's marketplace. A downstream project that never enabled it has no way to discover the wiki's setup command — a chicken-and-egg bootstrap gap. Because the base `lisa` plugin is auto-enabled everywhere, putting the bootstrap here is what makes the wiki reachable.
|
|
14
|
+
|
|
15
|
+
## Asymmetry note
|
|
16
|
+
|
|
17
|
+
The two runtimes work differently:
|
|
18
|
+
|
|
19
|
+
- **Claude Code** loads plugin skills per-project through `.claude/settings.json` `enabledPlugins`. The wiki skills are invisible until `lisa-wiki@lisa` is enabled there. This skill makes that edit.
|
|
20
|
+
- **Codex** loads Lisa skills through an overlay path (`src/codex/skills-installer.ts` runs from `Lisa.apply()` and copies every built `plugins/<p>/skills/<n>/` into `.codex/skills/lisa/`). Wiki skills land in that overlay automatically every time the project re-applies Lisa. This skill does not mutate Codex config — it only checks whether the overlay carries `lisa-wiki-setup` and tells the user how to refresh if missing.
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
### Step 1 — Verify we are in a project, not Lisa itself
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cwd="$(pwd)"
|
|
28
|
+
if [ -f "$cwd/.lisa-source" ] || { [ -d "$cwd/plugins/src/base" ] && [ -f "$cwd/.claude-plugin/marketplace.json" ]; }; then
|
|
29
|
+
echo "This is the Lisa monorepo itself — the base plugin's wiki kernel already lives here."
|
|
30
|
+
echo "Run /setup:wiki to scaffold a wiki, or invoke this skill from inside a downstream project."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Step 2 — Enable lisa-wiki@lisa in the Claude project settings
|
|
36
|
+
|
|
37
|
+
Read `<cwd>/.claude/settings.json` (create with `{}` if absent). Then, using the **Edit** or **Write** tool with valid JSON (not jq one-liners that risk corrupting comments / formatting):
|
|
38
|
+
|
|
39
|
+
1. Ensure `enabledPlugins` is an object.
|
|
40
|
+
2. Set `enabledPlugins["lisa-wiki@lisa"] = true`.
|
|
41
|
+
3. Ensure `extraKnownMarketplaces` is an object containing the entry below if it is not already present (do **not** overwrite an existing entry):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"CodySwannGT/lisa": {
|
|
45
|
+
"source": {
|
|
46
|
+
"source": "github",
|
|
47
|
+
"repo": "CodySwannGT/lisa"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. Pretty-print the file with 2-space indentation and a trailing newline.
|
|
53
|
+
|
|
54
|
+
If `enabledPlugins["lisa-wiki@lisa"]` was already `true`, log "Claude: lisa-wiki@lisa already enabled — no change." If not, log "Claude: enabled lisa-wiki@lisa in .claude/settings.json."
|
|
55
|
+
|
|
56
|
+
**Do not** edit `.claude/settings.local.json` — that file is per-user and is not the right surface for a project-level enablement.
|
|
57
|
+
|
|
58
|
+
### Step 3 — Verify the Codex overlay carries lisa-wiki
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
codex_wiki_setup=".codex/skills/lisa/lisa-wiki-setup/SKILL.md"
|
|
62
|
+
if [ -f "$codex_wiki_setup" ]; then
|
|
63
|
+
echo "Codex: lisa-wiki overlay already present (found $codex_wiki_setup). Nothing to do for Codex."
|
|
64
|
+
else
|
|
65
|
+
echo "Codex: lisa-wiki skills not found in .codex/skills/lisa/."
|
|
66
|
+
if [ -d ".agents" ] || [ -d ".codex" ]; then
|
|
67
|
+
echo " → This project appears Codex-wired but its overlay is stale or predates lisa-wiki."
|
|
68
|
+
echo " → Refresh it by running your project's Lisa apply command, e.g.:"
|
|
69
|
+
echo " npx lisa --harness=codex ."
|
|
70
|
+
echo " (or whatever shape your project uses to apply Lisa for Codex)."
|
|
71
|
+
else
|
|
72
|
+
echo " → This project does not have a Codex installation yet (no .agents/ or .codex/). Skip if Codex parity is not needed."
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Do not** invoke `lisa apply` automatically. `lisa apply` rewrites more than the wiki overlay; it must remain the user's explicit choice.
|
|
78
|
+
|
|
79
|
+
### Step 4 — Hand off
|
|
80
|
+
|
|
81
|
+
Print:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Wiki kernel install complete.
|
|
85
|
+
|
|
86
|
+
Next steps:
|
|
87
|
+
• Reload your runtime so it picks up the newly-enabled plugin (Claude: /reload or restart the session; Codex: restart `codex`).
|
|
88
|
+
• Then run /setup:wiki (Claude) or $lisa-wiki-setup (Codex) to scaffold wiki/ in this project.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Rules
|
|
92
|
+
|
|
93
|
+
- Idempotent. Re-running produces no spurious changes once both flags are set.
|
|
94
|
+
- Project-scoped only. Never touch `~/.codex/config.toml` or any user-global config.
|
|
95
|
+
- Never run `lisa apply` on the user's behalf.
|
|
96
|
+
- Never edit `.claude/settings.local.json` — it is user-scoped overrides.
|
|
97
|
+
- Preserve human-authored content. Only flip the enablement key and add the marketplace entry if missing.
|
|
98
|
+
|
|
99
|
+
## Related
|
|
100
|
+
|
|
101
|
+
`lisa-wiki-setup` (the actual scaffolder, lives in the `lisa-wiki` plugin once enabled), `lisa-wiki-doctor`, `lisa-wiki-usage`.
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* wiki-status.mjs — read-only Lisa wiki source freshness report.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node wiki-status.mjs [--wiki <wikiRoot>] [--config <path>] [--json]
|
|
6
|
+
* Exit 0 = report rendered. Missing wiki/config are represented in the report.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { loadConfig, walkFiles } from "./_wiki-lib.mjs";
|
|
12
|
+
|
|
13
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
const DEFAULT_STALE_AFTER_DAYS = 7;
|
|
15
|
+
|
|
16
|
+
const argv = process.argv.slice(2);
|
|
17
|
+
const flag = name => argv.includes(name);
|
|
18
|
+
const opt = name => {
|
|
19
|
+
const i = argv.indexOf(name);
|
|
20
|
+
return i !== -1 ? argv[i + 1] : undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function relToCwd(filePath) {
|
|
24
|
+
return path.relative(process.cwd(), filePath).replaceAll(path.sep, "/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeWikiPath(filePath) {
|
|
28
|
+
return filePath.replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseDate(value) {
|
|
32
|
+
if (!value || typeof value !== "string") return undefined;
|
|
33
|
+
const date = new Date(value);
|
|
34
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function newestDate(values) {
|
|
38
|
+
return values
|
|
39
|
+
.map(parseDate)
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.sort((a, b) => b.getTime() - a.getTime())[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readTextSafe(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
return fs.readFileSync(filePath, "utf8");
|
|
47
|
+
} catch {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readJsonSafe(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function enabledConnectors(config) {
|
|
61
|
+
const connectors = config?.connectors ?? {};
|
|
62
|
+
return Object.entries(connectors)
|
|
63
|
+
.filter(([, connectorConfig]) => connectorConfig?.enabled !== false)
|
|
64
|
+
.filter(
|
|
65
|
+
([, connectorConfig]) => connectorConfig?.sideEffects !== "external-write"
|
|
66
|
+
)
|
|
67
|
+
.map(([name, connectorConfig]) => ({
|
|
68
|
+
name,
|
|
69
|
+
sideEffects: connectorConfig?.sideEffects ?? "unknown",
|
|
70
|
+
}))
|
|
71
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function logSections(logText) {
|
|
75
|
+
const lines = logText.split("\n");
|
|
76
|
+
const sections = [];
|
|
77
|
+
let current;
|
|
78
|
+
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
const heading = line.match(/^##\s+(\d{4}-\d{2}-\d{2})(?:\s+-\s+(.*))?$/);
|
|
81
|
+
if (heading) {
|
|
82
|
+
if (current) sections.push(current);
|
|
83
|
+
current = {
|
|
84
|
+
date: heading[1],
|
|
85
|
+
title: heading[2] ?? "",
|
|
86
|
+
lines: [line],
|
|
87
|
+
};
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (current) current.lines.push(line);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (current) sections.push(current);
|
|
94
|
+
return sections;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function connectorLogFacts(logText, connectorName) {
|
|
98
|
+
const sections = logSections(logText);
|
|
99
|
+
const escaped = connectorName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
100
|
+
const quoted = new RegExp(`\`${escaped}\``, "i");
|
|
101
|
+
const bare = new RegExp(`\\b${escaped}\\b`, "i");
|
|
102
|
+
|
|
103
|
+
for (const section of [...sections].reverse()) {
|
|
104
|
+
const lines = section.lines.filter(
|
|
105
|
+
line => quoted.test(line) || bare.test(line)
|
|
106
|
+
);
|
|
107
|
+
if (lines.length === 0) continue;
|
|
108
|
+
|
|
109
|
+
const lower = lines.join("\n").toLowerCase();
|
|
110
|
+
const status = lower.includes("skipped")
|
|
111
|
+
? "skipped"
|
|
112
|
+
: lower.includes("blocked") || lower.includes("failed")
|
|
113
|
+
? "blocked"
|
|
114
|
+
: lower.includes("ingested") || lower.includes("refreshed")
|
|
115
|
+
? "ingested"
|
|
116
|
+
: "observed";
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
date: section.date,
|
|
120
|
+
status,
|
|
121
|
+
reason: lines.map(line => line.replace(/^-\s*/, "").trim()).join(" "),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function stateFacts(wikiRoot, connectorName) {
|
|
129
|
+
const stateDir = path.join(wikiRoot, "state", connectorName);
|
|
130
|
+
const files = walkFiles(stateDir, { ext: ".json" });
|
|
131
|
+
const states = files
|
|
132
|
+
.map(filePath => ({ filePath, data: readJsonSafe(filePath) }))
|
|
133
|
+
.filter(item => item.data);
|
|
134
|
+
const dated = states
|
|
135
|
+
.map(item => ({
|
|
136
|
+
...item,
|
|
137
|
+
date:
|
|
138
|
+
parseDate(item.data.ingested_at) ??
|
|
139
|
+
parseDate(item.data.cursor?.lastIngest) ??
|
|
140
|
+
parseDate(item.data.updated_at),
|
|
141
|
+
}))
|
|
142
|
+
.sort((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0));
|
|
143
|
+
const latest = dated[0] ?? states[0];
|
|
144
|
+
const sourceNotes = new Set();
|
|
145
|
+
|
|
146
|
+
for (const item of states) {
|
|
147
|
+
const notes = item.data.source_notes ?? item.data.sourceNotes ?? [];
|
|
148
|
+
if (Array.isArray(notes)) {
|
|
149
|
+
for (const note of notes) {
|
|
150
|
+
if (typeof note === "string") sourceNotes.add(normalizeWikiPath(note));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (typeof item.data.source_note === "string") {
|
|
154
|
+
sourceNotes.add(normalizeWikiPath(item.data.source_note));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
stateFiles: files.map(filePath => relToCwd(filePath)),
|
|
160
|
+
latestObservedAt: latest?.date?.toISOString() ?? latest?.data?.ingested_at,
|
|
161
|
+
sourceNotes: [...sourceNotes],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sourceFacts(wikiRoot, connectorName, configuredSourceNotes) {
|
|
166
|
+
const sourceDir = path.join(wikiRoot, "sources", connectorName);
|
|
167
|
+
const markdownFiles = walkFiles(sourceDir, { ext: ".md" }).map(relToCwd);
|
|
168
|
+
const wikiParent = path.dirname(wikiRoot);
|
|
169
|
+
const sourceNoteExists = note =>
|
|
170
|
+
fs.existsSync(path.resolve(note)) ||
|
|
171
|
+
fs.existsSync(path.resolve(wikiParent, note));
|
|
172
|
+
const existingConfigured = configuredSourceNotes.filter(note =>
|
|
173
|
+
sourceNoteExists(note)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
sourceNotes:
|
|
178
|
+
existingConfigured.length > 0
|
|
179
|
+
? existingConfigured
|
|
180
|
+
: markdownFiles.map(normalizeWikiPath),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function nextActionFor(verdict, connectorName, reason) {
|
|
185
|
+
if (verdict === "fresh") return "No action needed.";
|
|
186
|
+
if (verdict === "skipped") {
|
|
187
|
+
return reason?.toLowerCase().includes("project-scoped memory")
|
|
188
|
+
? "Provide project-scoped memory for this repo, or accept the expected skip."
|
|
189
|
+
: `Review the skip reason, then run /lisa-wiki:ingest --source ${connectorName} when available.`;
|
|
190
|
+
}
|
|
191
|
+
if (verdict === "blocked") {
|
|
192
|
+
return `Resolve the blocker, then run /lisa-wiki:ingest --source ${connectorName}.`;
|
|
193
|
+
}
|
|
194
|
+
return `Run /lisa-wiki:ingest --source ${connectorName}.`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function createWikiFreshnessReport({
|
|
198
|
+
configPath = "wiki/lisa-wiki.config.json",
|
|
199
|
+
wikiRoot,
|
|
200
|
+
now = new Date(),
|
|
201
|
+
staleAfterDays = DEFAULT_STALE_AFTER_DAYS,
|
|
202
|
+
} = {}) {
|
|
203
|
+
const { config, configPath: resolvedConfigPath } = loadConfig(configPath);
|
|
204
|
+
const resolvedWikiRoot = path.resolve(wikiRoot ?? config?.wikiRoot ?? "wiki");
|
|
205
|
+
const logPath = path.join(resolvedWikiRoot, "log.md");
|
|
206
|
+
const logText = readTextSafe(logPath);
|
|
207
|
+
const connectors = enabledConnectors(config);
|
|
208
|
+
const staleAfterMs = staleAfterDays * DAY_MS;
|
|
209
|
+
|
|
210
|
+
const items = connectors.map(connector => {
|
|
211
|
+
const state = stateFacts(resolvedWikiRoot, connector.name);
|
|
212
|
+
const sources = sourceFacts(
|
|
213
|
+
resolvedWikiRoot,
|
|
214
|
+
connector.name,
|
|
215
|
+
state.sourceNotes
|
|
216
|
+
);
|
|
217
|
+
const log = connectorLogFacts(logText, connector.name);
|
|
218
|
+
const observedDate =
|
|
219
|
+
newestDate([state.latestObservedAt, log?.date]) ??
|
|
220
|
+
newestDate(
|
|
221
|
+
sources.sourceNotes.map(note => note.match(/\d{4}-\d{2}-\d{2}/)?.[0])
|
|
222
|
+
);
|
|
223
|
+
const hasState = state.stateFiles.length > 0;
|
|
224
|
+
const hasSourceNotes = sources.sourceNotes.length > 0;
|
|
225
|
+
const isStale =
|
|
226
|
+
observedDate && now.getTime() - observedDate.getTime() > staleAfterMs;
|
|
227
|
+
|
|
228
|
+
const verdict =
|
|
229
|
+
log?.status === "blocked"
|
|
230
|
+
? "blocked"
|
|
231
|
+
: log?.status === "skipped" && !hasState && !hasSourceNotes
|
|
232
|
+
? "skipped"
|
|
233
|
+
: !hasState && !hasSourceNotes
|
|
234
|
+
? "never_ingested"
|
|
235
|
+
: !hasState || !hasSourceNotes || isStale
|
|
236
|
+
? "stale"
|
|
237
|
+
: "fresh";
|
|
238
|
+
|
|
239
|
+
const evidence = [
|
|
240
|
+
...sources.sourceNotes,
|
|
241
|
+
...state.stateFiles,
|
|
242
|
+
fs.existsSync(logPath) ? relToCwd(logPath) : undefined,
|
|
243
|
+
].filter(Boolean);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
connector: connector.name,
|
|
247
|
+
sideEffects: connector.sideEffects,
|
|
248
|
+
verdict,
|
|
249
|
+
evidence,
|
|
250
|
+
lastObserved: observedDate?.toISOString().slice(0, 10) ?? "unknown",
|
|
251
|
+
reason: verdict === "fresh" ? "" : (log?.reason ?? ""),
|
|
252
|
+
nextAction: nextActionFor(verdict, connector.name, log?.reason),
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
generatedAt: now.toISOString(),
|
|
258
|
+
configPath: relToCwd(resolvedConfigPath),
|
|
259
|
+
wikiRoot: relToCwd(resolvedWikiRoot),
|
|
260
|
+
items,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function renderWikiFreshnessReport(report) {
|
|
265
|
+
const lines = [
|
|
266
|
+
"# Lisa wiki source freshness",
|
|
267
|
+
"",
|
|
268
|
+
`Generated: ${report.generatedAt}`,
|
|
269
|
+
`Config: ${report.configPath}`,
|
|
270
|
+
`Wiki root: ${report.wikiRoot}`,
|
|
271
|
+
"",
|
|
272
|
+
"| Connector | Verdict | Last observed | Evidence | Next action |",
|
|
273
|
+
"| --- | --- | --- | --- | --- |",
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const item of report.items) {
|
|
277
|
+
const evidence =
|
|
278
|
+
item.evidence.length > 0
|
|
279
|
+
? item.evidence.slice(0, 3).join("<br>")
|
|
280
|
+
: "none";
|
|
281
|
+
lines.push(
|
|
282
|
+
`| ${item.connector} | ${item.verdict} | ${item.lastObserved} | ${evidence} | ${item.nextAction} |`
|
|
283
|
+
);
|
|
284
|
+
if (item.reason && item.verdict !== "fresh") {
|
|
285
|
+
lines.push(
|
|
286
|
+
`| ${item.connector} | reason | ${item.lastObserved} | log | ${item.reason} |`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
lines.push(
|
|
292
|
+
"",
|
|
293
|
+
"Integrity follow-up: run /lisa-wiki:lint separately for broken links, stale claims, or structure issues."
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return `${lines.join("\n")}\n`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
300
|
+
const report = createWikiFreshnessReport({
|
|
301
|
+
configPath: opt("--config") ?? "wiki/lisa-wiki.config.json",
|
|
302
|
+
wikiRoot: opt("--wiki"),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (flag("--json")) {
|
|
306
|
+
console.log(JSON.stringify(report, null, 2));
|
|
307
|
+
} else {
|
|
308
|
+
process.stdout.write(renderWikiFreshnessReport(report));
|
|
309
|
+
}
|
|
310
|
+
}
|