@codyswann/lisa 2.24.0 → 2.25.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.
Files changed (142) hide show
  1. package/.claude-plugin/marketplace.json +6 -0
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-wiki/.claude-plugin/plugin.json +8 -0
  18. package/plugins/lisa-wiki/.codex-plugin/plugin.json +32 -0
  19. package/plugins/lisa-wiki/ci/lisa-wiki-validate.yml +32 -0
  20. package/plugins/lisa-wiki/commands/add-ingest.md +6 -0
  21. package/plugins/lisa-wiki/commands/add-role.md +6 -0
  22. package/plugins/lisa-wiki/commands/doctor.md +6 -0
  23. package/plugins/lisa-wiki/commands/ingest.md +6 -0
  24. package/plugins/lisa-wiki/commands/lint.md +6 -0
  25. package/plugins/lisa-wiki/commands/migrate.md +6 -0
  26. package/plugins/lisa-wiki/commands/onboard-me.md +6 -0
  27. package/plugins/lisa-wiki/commands/query.md +6 -0
  28. package/plugins/lisa-wiki/commands/setup.md +6 -0
  29. package/plugins/lisa-wiki/schema/lisa-wiki-config.schema.json +118 -0
  30. package/plugins/lisa-wiki/schema/wiki-structure.schema.json +51 -0
  31. package/plugins/lisa-wiki/scripts/_wiki-lib.mjs +185 -0
  32. package/plugins/lisa-wiki/scripts/diff-guard.mjs +116 -0
  33. package/plugins/lisa-wiki/scripts/ingest-git.mjs +189 -0
  34. package/plugins/lisa-wiki/scripts/ingest-memory.mjs +130 -0
  35. package/plugins/lisa-wiki/scripts/ingest-roles.mjs +85 -0
  36. package/plugins/lisa-wiki/scripts/ingest_slack_channel.py +329 -0
  37. package/plugins/lisa-wiki/scripts/lint-wiki.mjs +320 -0
  38. package/plugins/lisa-wiki/scripts/mcp-doctor.mjs +72 -0
  39. package/plugins/lisa-wiki/scripts/render-contract.mjs +107 -0
  40. package/plugins/lisa-wiki/scripts/rewrite-refs.mjs +144 -0
  41. package/plugins/lisa-wiki/scripts/slack_oauth_user.py +179 -0
  42. package/plugins/lisa-wiki/scripts/validate-config.mjs +232 -0
  43. package/plugins/lisa-wiki/scripts/verify-migration.mjs +199 -0
  44. package/plugins/lisa-wiki/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
  45. package/plugins/lisa-wiki/skills/lisa-wiki-add-role/SKILL.md +30 -0
  46. package/plugins/lisa-wiki/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
  47. package/plugins/lisa-wiki/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
  48. package/plugins/lisa-wiki/skills/lisa-wiki-connector-git/SKILL.md +25 -0
  49. package/plugins/lisa-wiki/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
  50. package/plugins/lisa-wiki/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
  51. package/plugins/lisa-wiki/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
  52. package/plugins/lisa-wiki/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
  53. package/plugins/lisa-wiki/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
  54. package/plugins/lisa-wiki/skills/lisa-wiki-connector-web/SKILL.md +23 -0
  55. package/plugins/lisa-wiki/skills/lisa-wiki-doctor/SKILL.md +47 -0
  56. package/plugins/lisa-wiki/skills/lisa-wiki-ingest/SKILL.md +43 -0
  57. package/plugins/lisa-wiki/skills/lisa-wiki-lint/SKILL.md +32 -0
  58. package/plugins/lisa-wiki/skills/lisa-wiki-migrate/SKILL.md +43 -0
  59. package/plugins/lisa-wiki/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
  60. package/plugins/lisa-wiki/skills/lisa-wiki-query/SKILL.md +30 -0
  61. package/plugins/lisa-wiki/skills/lisa-wiki-setup/SKILL.md +45 -0
  62. package/plugins/lisa-wiki/skills/lisa-wiki-usage/SKILL.md +50 -0
  63. package/plugins/lisa-wiki/templates/agents/role-agent.claude.md +16 -0
  64. package/plugins/lisa-wiki/templates/agents/role-agent.codex.toml +15 -0
  65. package/plugins/lisa-wiki/templates/index.md +17 -0
  66. package/plugins/lisa-wiki/templates/llm-wiki-contract.md +60 -0
  67. package/plugins/lisa-wiki/templates/log.md +8 -0
  68. package/plugins/lisa-wiki/templates/page-types/architecture.md +18 -0
  69. package/plugins/lisa-wiki/templates/page-types/concept.md +18 -0
  70. package/plugins/lisa-wiki/templates/page-types/decision.md +18 -0
  71. package/plugins/lisa-wiki/templates/page-types/entity.md +19 -0
  72. package/plugins/lisa-wiki/templates/page-types/open-question.md +18 -0
  73. package/plugins/lisa-wiki/templates/page-types/playbook.md +18 -0
  74. package/plugins/lisa-wiki/templates/page-types/project.md +19 -0
  75. package/plugins/lisa-wiki/templates/page-types/requirement.md +19 -0
  76. package/plugins/lisa-wiki/templates/page-types/staff.md +26 -0
  77. package/plugins/lisa-wiki/templates/start-here.md +24 -0
  78. package/plugins/lisa-wiki/templates/state-readme.md +20 -0
  79. package/plugins/src/wiki/.claude-plugin/plugin.json +6 -0
  80. package/plugins/src/wiki/ci/lisa-wiki-validate.yml +32 -0
  81. package/plugins/src/wiki/commands/add-ingest.md +6 -0
  82. package/plugins/src/wiki/commands/add-role.md +6 -0
  83. package/plugins/src/wiki/commands/doctor.md +6 -0
  84. package/plugins/src/wiki/commands/ingest.md +6 -0
  85. package/plugins/src/wiki/commands/lint.md +6 -0
  86. package/plugins/src/wiki/commands/migrate.md +6 -0
  87. package/plugins/src/wiki/commands/onboard-me.md +6 -0
  88. package/plugins/src/wiki/commands/query.md +6 -0
  89. package/plugins/src/wiki/commands/setup.md +6 -0
  90. package/plugins/src/wiki/schema/lisa-wiki-config.schema.json +118 -0
  91. package/plugins/src/wiki/schema/wiki-structure.schema.json +51 -0
  92. package/plugins/src/wiki/scripts/_wiki-lib.mjs +185 -0
  93. package/plugins/src/wiki/scripts/diff-guard.mjs +116 -0
  94. package/plugins/src/wiki/scripts/ingest-git.mjs +189 -0
  95. package/plugins/src/wiki/scripts/ingest-memory.mjs +130 -0
  96. package/plugins/src/wiki/scripts/ingest-roles.mjs +85 -0
  97. package/plugins/src/wiki/scripts/ingest_slack_channel.py +329 -0
  98. package/plugins/src/wiki/scripts/lint-wiki.mjs +320 -0
  99. package/plugins/src/wiki/scripts/mcp-doctor.mjs +72 -0
  100. package/plugins/src/wiki/scripts/render-contract.mjs +107 -0
  101. package/plugins/src/wiki/scripts/rewrite-refs.mjs +144 -0
  102. package/plugins/src/wiki/scripts/slack_oauth_user.py +179 -0
  103. package/plugins/src/wiki/scripts/validate-config.mjs +232 -0
  104. package/plugins/src/wiki/scripts/verify-migration.mjs +199 -0
  105. package/plugins/src/wiki/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
  106. package/plugins/src/wiki/skills/lisa-wiki-add-role/SKILL.md +30 -0
  107. package/plugins/src/wiki/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
  108. package/plugins/src/wiki/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
  109. package/plugins/src/wiki/skills/lisa-wiki-connector-git/SKILL.md +25 -0
  110. package/plugins/src/wiki/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
  111. package/plugins/src/wiki/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
  112. package/plugins/src/wiki/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
  113. package/plugins/src/wiki/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
  114. package/plugins/src/wiki/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
  115. package/plugins/src/wiki/skills/lisa-wiki-connector-web/SKILL.md +23 -0
  116. package/plugins/src/wiki/skills/lisa-wiki-doctor/SKILL.md +47 -0
  117. package/plugins/src/wiki/skills/lisa-wiki-ingest/SKILL.md +43 -0
  118. package/plugins/src/wiki/skills/lisa-wiki-lint/SKILL.md +32 -0
  119. package/plugins/src/wiki/skills/lisa-wiki-migrate/SKILL.md +43 -0
  120. package/plugins/src/wiki/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
  121. package/plugins/src/wiki/skills/lisa-wiki-query/SKILL.md +30 -0
  122. package/plugins/src/wiki/skills/lisa-wiki-setup/SKILL.md +45 -0
  123. package/plugins/src/wiki/skills/lisa-wiki-usage/SKILL.md +50 -0
  124. package/plugins/src/wiki/templates/agents/role-agent.claude.md +16 -0
  125. package/plugins/src/wiki/templates/agents/role-agent.codex.toml +15 -0
  126. package/plugins/src/wiki/templates/index.md +17 -0
  127. package/plugins/src/wiki/templates/llm-wiki-contract.md +60 -0
  128. package/plugins/src/wiki/templates/log.md +8 -0
  129. package/plugins/src/wiki/templates/page-types/architecture.md +18 -0
  130. package/plugins/src/wiki/templates/page-types/concept.md +18 -0
  131. package/plugins/src/wiki/templates/page-types/decision.md +18 -0
  132. package/plugins/src/wiki/templates/page-types/entity.md +19 -0
  133. package/plugins/src/wiki/templates/page-types/open-question.md +18 -0
  134. package/plugins/src/wiki/templates/page-types/playbook.md +18 -0
  135. package/plugins/src/wiki/templates/page-types/project.md +19 -0
  136. package/plugins/src/wiki/templates/page-types/requirement.md +19 -0
  137. package/plugins/src/wiki/templates/page-types/staff.md +26 -0
  138. package/plugins/src/wiki/templates/start-here.md +24 -0
  139. package/plugins/src/wiki/templates/state-readme.md +20 -0
  140. package/scripts/build-plugins.sh +29 -21
  141. package/scripts/check-plugins-sync.sh +1 -1
  142. package/scripts/generate-codex-plugin-artifacts.mjs +22 -0
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * validate-config.mjs — dependency-free validator for wiki/lisa-wiki.config.json.
4
+ *
5
+ * Enforces the constraints described in schema/lisa-wiki-config.schema.json without
6
+ * requiring a JSON-Schema runtime, so it is portable to any downstream repo that
7
+ * installs the lisa-wiki plugin (no ajv / node_modules assumptions).
8
+ *
9
+ * Usage: node validate-config.mjs [path-to-config]
10
+ * default path: wiki/lisa-wiki.config.json (relative to cwd)
11
+ * Exit code 0 = valid, 1 = invalid or unreadable.
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+
16
+ const MODES = ["embedded", "wrapper", "standalone", "subdir"];
17
+ const SIDE_EFFECTS = ["read-only-ingest", "repo-write", "external-write"];
18
+ const RETENTION = [
19
+ "raw-ok",
20
+ "sanitized-note-only",
21
+ "metadata-only",
22
+ "external-pointer-only",
23
+ ];
24
+ const SENSITIVITY = ["public", "internal", "confidential", "restricted"];
25
+ const SOURCE_LAYOUT = ["by-system", "by-category"];
26
+ const README_MODE = ["rich", "stub", "preserve"];
27
+
28
+ const configPath = path.resolve(
29
+ process.argv[2] ?? "wiki/lisa-wiki.config.json"
30
+ );
31
+ const errors = [];
32
+ const err = msg => errors.push(msg);
33
+
34
+ function isObject(v) {
35
+ return typeof v === "object" && v !== null && !Array.isArray(v);
36
+ }
37
+ function isStringArray(v) {
38
+ return Array.isArray(v) && v.every(x => typeof x === "string");
39
+ }
40
+ function checkEnum(value, allowed, label) {
41
+ if (value !== undefined && !allowed.includes(value)) {
42
+ err(`${label}: "${String(value)}" is not one of ${allowed.join(" | ")}`);
43
+ }
44
+ }
45
+ function checkType(value, type, label) {
46
+ if (value !== undefined && typeof value !== type) {
47
+ err(
48
+ `${label}: expected ${type}, got ${Array.isArray(value) ? "array" : typeof value}`
49
+ );
50
+ }
51
+ }
52
+
53
+ if (!fs.existsSync(configPath)) {
54
+ console.error(`✗ config not found: ${configPath}`);
55
+ process.exit(1);
56
+ }
57
+
58
+ let config;
59
+ try {
60
+ config = JSON.parse(fs.readFileSync(configPath, "utf8"));
61
+ } catch (e) {
62
+ console.error(`✗ config is not valid JSON: ${e.message}`);
63
+ process.exit(1);
64
+ }
65
+
66
+ if (!isObject(config)) {
67
+ console.error("✗ config must be a JSON object");
68
+ process.exit(1);
69
+ }
70
+
71
+ // Required
72
+ for (const key of ["schemaVersion", "org", "mode", "wikiRoot", "categories"]) {
73
+ if (config[key] === undefined) err(`missing required field: ${key}`);
74
+ }
75
+ checkType(config.schemaVersion, "string", "schemaVersion");
76
+ checkType(config.org, "string", "org");
77
+ checkType(config.displayName, "string", "displayName");
78
+ checkType(config.purpose, "string", "purpose");
79
+ checkEnum(config.mode, MODES, "mode");
80
+ checkType(config.wikiRoot, "string", "wikiRoot");
81
+ if (
82
+ typeof config.wikiRoot === "string" &&
83
+ (path.isAbsolute(config.wikiRoot) ||
84
+ config.wikiRoot.split(/[\\/]/).includes(".."))
85
+ ) {
86
+ err(
87
+ 'wikiRoot: must be a relative path inside the repo (no absolute paths, no ".." traversal)'
88
+ );
89
+ }
90
+ checkType(config.frontmatter, "boolean", "frontmatter");
91
+ if (
92
+ config.categories !== undefined &&
93
+ !(isStringArray(config.categories) && config.categories.length > 0)
94
+ ) {
95
+ err("categories: must be a non-empty array of strings");
96
+ }
97
+ checkEnum(config.sourceRetention, RETENTION, "sourceRetention");
98
+ checkType(config.contaminationTerms, "object", "contaminationTerms");
99
+ if (
100
+ config.contaminationTerms !== undefined &&
101
+ !isStringArray(config.contaminationTerms)
102
+ ) {
103
+ err("contaminationTerms: must be an array of strings");
104
+ }
105
+
106
+ if (config.sources !== undefined) {
107
+ if (!isObject(config.sources)) err("sources: must be an object");
108
+ else checkEnum(config.sources.layout, SOURCE_LAYOUT, "sources.layout");
109
+ }
110
+ if (config.git !== undefined) {
111
+ if (!isObject(config.git)) err("git: must be an object");
112
+ else {
113
+ checkType(config.git.prPerIngestion, "boolean", "git.prPerIngestion");
114
+ checkType(config.git.autoMerge, "boolean", "git.autoMerge");
115
+ checkType(config.git.targetBranch, "string", "git.targetBranch");
116
+ checkType(config.git.branchPrefix, "string", "git.branchPrefix");
117
+ }
118
+ }
119
+ if (config.readme !== undefined) {
120
+ if (!isObject(config.readme)) err("readme: must be an object");
121
+ else checkEnum(config.readme.mode, README_MODE, "readme.mode");
122
+ }
123
+ if (config.sensitivity !== undefined) {
124
+ if (!isObject(config.sensitivity)) err("sensitivity: must be an object");
125
+ else {
126
+ checkType(config.sensitivity.enabled, "boolean", "sensitivity.enabled");
127
+ checkEnum(config.sensitivity.default, SENSITIVITY, "sensitivity.default");
128
+ }
129
+ }
130
+ if (config.documentation !== undefined) {
131
+ if (!isObject(config.documentation)) err("documentation: must be an object");
132
+ else {
133
+ checkType(config.documentation.absorb, "boolean", "documentation.absorb");
134
+ if (
135
+ config.documentation.keepInPlace !== undefined &&
136
+ !isStringArray(config.documentation.keepInPlace)
137
+ ) {
138
+ err("documentation.keepInPlace: must be an array of strings");
139
+ }
140
+ }
141
+ }
142
+ if (config.onboarding !== undefined) {
143
+ if (!isObject(config.onboarding)) err("onboarding: must be an object");
144
+ else
145
+ checkType(
146
+ config.onboarding.allowAudienceNote,
147
+ "boolean",
148
+ "onboarding.allowAudienceNote"
149
+ );
150
+ }
151
+
152
+ if (config.connectors !== undefined) {
153
+ if (!isObject(config.connectors))
154
+ err("connectors: must be an object (name -> connector config)");
155
+ else {
156
+ for (const [name, c] of Object.entries(config.connectors)) {
157
+ if (!isObject(c)) {
158
+ err(`connectors.${name}: must be an object`);
159
+ continue;
160
+ }
161
+ checkType(c.enabled, "boolean", `connectors.${name}.enabled`);
162
+ if (c.sideEffects === undefined) {
163
+ err(
164
+ `connectors.${name}: missing required field "sideEffects" (every connector must declare its side-effect class so full ingest can skip external-write)`
165
+ );
166
+ } else {
167
+ checkEnum(
168
+ c.sideEffects,
169
+ SIDE_EFFECTS,
170
+ `connectors.${name}.sideEffects`
171
+ );
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ if (config.customConnectors !== undefined) {
178
+ if (!Array.isArray(config.customConnectors))
179
+ err("customConnectors: must be an array");
180
+ else {
181
+ config.customConnectors.forEach((c, i) => {
182
+ if (!isObject(c)) {
183
+ err(`customConnectors[${i}]: must be an object`);
184
+ return;
185
+ }
186
+ for (const k of ["name", "skill", "sourceSystem", "sideEffects"]) {
187
+ if (c[k] === undefined)
188
+ err(`customConnectors[${i}]: missing required field "${k}"`);
189
+ }
190
+ checkEnum(
191
+ c.sideEffects,
192
+ SIDE_EFFECTS,
193
+ `customConnectors[${i}].sideEffects`
194
+ );
195
+ });
196
+ }
197
+ }
198
+
199
+ if (config.staff !== undefined) {
200
+ if (!Array.isArray(config.staff)) err("staff: must be an array");
201
+ else {
202
+ config.staff.forEach((s, i) => {
203
+ if (!isObject(s)) {
204
+ err(`staff[${i}]: must be an object`);
205
+ return;
206
+ }
207
+ for (const k of ["id", "role"]) {
208
+ if (s[k] === undefined)
209
+ err(`staff[${i}]: missing required field "${k}"`);
210
+ }
211
+ checkEnum(s.sensitivity, SENSITIVITY, `staff[${i}].sensitivity`);
212
+ if (s.owns !== undefined) {
213
+ if (!isObject(s.owns)) err(`staff[${i}].owns: must be an object`);
214
+ else {
215
+ for (const ok of ["categories", "connectors", "skills"]) {
216
+ if (s.owns[ok] !== undefined && !isStringArray(s.owns[ok])) {
217
+ err(`staff[${i}].owns.${ok}: must be an array of strings`);
218
+ }
219
+ }
220
+ }
221
+ }
222
+ });
223
+ }
224
+ }
225
+
226
+ if (errors.length > 0) {
227
+ console.error(`✗ ${path.relative(process.cwd(), configPath)} is invalid:`);
228
+ for (const e of errors) console.error(` - ${e}`);
229
+ process.exit(1);
230
+ }
231
+
232
+ console.log(`✓ ${path.relative(process.cwd(), configPath)} is valid.`);
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * verify-migration.mjs — the deterministic half of /doctor. Dependency-free.
4
+ *
5
+ * Composes validate-config + lint-wiki into a grouped report and writes
6
+ * <wikiRoot>/state/migration/doctor-report.json with an overall verdict. Groups D
7
+ * (runtime surfaces) and E (functional smoke) are SKIPPED here — the lisa-wiki-doctor
8
+ * SKILL performs those and merges its results.
9
+ *
10
+ * Usage: node verify-migration.mjs [--wiki <root>] [--config <path>] [--migration]
11
+ * Exit 0 = READY or READY_WITH_WARNINGS, 1 = NOT_READY.
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { spawnSync } from "node:child_process";
16
+ import { fileURLToPath } from "node:url";
17
+ import { loadConfig } from "./_wiki-lib.mjs";
18
+
19
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
20
+ const argv = process.argv.slice(2);
21
+ const opt = n => {
22
+ const i = argv.indexOf(n);
23
+ return i !== -1 ? argv[i + 1] : undefined;
24
+ };
25
+ const migration = argv.includes("--migration");
26
+ const configPath = opt("--config");
27
+ const { config } = loadConfig(configPath);
28
+ const wikiRoot = path.resolve(opt("--wiki") ?? config?.wikiRoot ?? "wiki");
29
+
30
+ const groups = { A: [], B: [], C: [], D: [], E: [], F: [], G: [] };
31
+ const add = (g, id, status, message) => groups[g].push({ id, status, message });
32
+
33
+ function runNode(script, args) {
34
+ const res = spawnSync("node", [path.join(scriptDir, script), ...args], {
35
+ encoding: "utf8",
36
+ });
37
+ return {
38
+ status: res.status ?? 1,
39
+ stdout: res.stdout ?? "",
40
+ stderr: (res.stderr ?? "") + (res.error ? `\n${res.error.message}` : ""),
41
+ };
42
+ }
43
+
44
+ // --- A. structure & config ------------------------------------------------
45
+ const vc = runNode("validate-config.mjs", [
46
+ configPath ?? path.join(wikiRoot, "lisa-wiki.config.json"),
47
+ ]);
48
+ add(
49
+ "A",
50
+ "config-valid",
51
+ vc.status === 0 ? "PASS" : "FAIL",
52
+ vc.status === 0
53
+ ? "config validates"
54
+ : `config invalid: ${(vc.stderr || vc.stdout).trim().split("\n").slice(0, 6).join("; ")}`
55
+ );
56
+ add(
57
+ "A",
58
+ "schema-version",
59
+ config?.schemaVersion ? "PASS" : "FAIL",
60
+ config?.schemaVersion
61
+ ? `schemaVersion ${config.schemaVersion}`
62
+ : "schemaVersion missing"
63
+ );
64
+ add(
65
+ "A",
66
+ "readme-mode",
67
+ config?.readme?.mode ? "PASS" : "WARN",
68
+ config?.readme?.mode
69
+ ? `readme.mode ${config.readme.mode}`
70
+ : "readme.mode not recorded (asked by /setup)"
71
+ );
72
+ add(
73
+ "A",
74
+ "purpose",
75
+ config?.purpose ? "PASS" : "WARN",
76
+ config?.purpose
77
+ ? "purpose set"
78
+ : "purpose not set (asked by /setup; feeds onboarding + contract)"
79
+ );
80
+
81
+ // --- A/B. lint (structure -> A, everything else -> B) ---------------------
82
+ const lint = runNode("lint-wiki.mjs", [
83
+ "--wiki",
84
+ wikiRoot,
85
+ ...(configPath ? ["--config", configPath] : []),
86
+ "--json",
87
+ ]);
88
+ let lintReport;
89
+ try {
90
+ lintReport = JSON.parse(lint.stdout);
91
+ } catch {
92
+ add(
93
+ "B",
94
+ "lint-run",
95
+ "FAIL",
96
+ `lint-wiki did not produce JSON: ${(lint.stderr || lint.stdout).trim().slice(0, 200)}`
97
+ );
98
+ }
99
+ if (lintReport) {
100
+ const structureItems = lintReport.items.filter(i => i.group === "structure");
101
+ const otherItems = lintReport.items.filter(i => i.group !== "structure");
102
+ if (structureItems.length === 0)
103
+ add("A", "structure", "PASS", "structure conforms to the manifest");
104
+ for (const i of structureItems)
105
+ add(
106
+ "A",
107
+ `structure:${i.group}`,
108
+ i.status,
109
+ `${i.message}${i.file ? ` (${i.file})` : ""}`
110
+ );
111
+ if (otherItems.length === 0)
112
+ add("B", "integrity", "PASS", "no integrity/safety findings");
113
+ for (const i of otherItems)
114
+ add(
115
+ "B",
116
+ `${i.group}`,
117
+ i.status,
118
+ `${i.message}${i.file ? ` (${i.file})` : ""}`
119
+ );
120
+ }
121
+
122
+ // --- C/D/E/F/G: deterministic-light + delegated to the doctor skill --------
123
+ add(
124
+ "C",
125
+ "no-loss",
126
+ "SKIP",
127
+ "no-loss/parity needs the migration manifest from /migrate; dangling-link check is covered in B"
128
+ );
129
+ add(
130
+ "D",
131
+ "runtime",
132
+ "SKIP",
133
+ "runtime surfaces (commands/skills/subagents/MCP) verified by the lisa-wiki-doctor skill"
134
+ );
135
+ add(
136
+ "E",
137
+ "smoke",
138
+ "SKIP",
139
+ "functional smoke tests (ingest/query/lint/onboard) performed by the lisa-wiki-doctor skill"
140
+ );
141
+ add(
142
+ "F",
143
+ "mode",
144
+ config?.mode ? "PASS" : "FAIL",
145
+ config?.mode ? `mode: ${config.mode}; wikiRoot resolves` : "mode not set"
146
+ );
147
+ add(
148
+ "G",
149
+ "git-ci-dist",
150
+ "SKIP",
151
+ "git/CI/distribution checks performed by the lisa-wiki-doctor skill and CI"
152
+ );
153
+
154
+ // --- verdict --------------------------------------------------------------
155
+ const all = Object.values(groups).flat();
156
+ const isBlockingWarn = (item, group) =>
157
+ migration && (group === "A" || group === "B") && item.status === "WARN";
158
+ let verdict = "READY";
159
+ if (all.some(i => i.status === "FAIL")) verdict = "NOT_READY";
160
+ else if (
161
+ Object.entries(groups).some(([g, items]) =>
162
+ items.some(i => isBlockingWarn(i, g))
163
+ )
164
+ )
165
+ verdict = "NOT_READY";
166
+ else if (all.some(i => i.status === "WARN")) verdict = "READY_WITH_WARNINGS";
167
+
168
+ const reportObj = {
169
+ tool: "verify-migration",
170
+ deterministic: true,
171
+ wikiRoot: path.relative(process.cwd(), wikiRoot) || ".",
172
+ mode: config?.mode ?? null,
173
+ migration,
174
+ generatedAt: new Date().toISOString(),
175
+ verdict,
176
+ subset: "deterministic",
177
+ note: "Deterministic subset only. Groups C (no-loss/parity), D (runtime surfaces), E (functional smoke), and G (git/CI/distribution) are completed by the lisa-wiki-doctor skill, which merges its results into this report.",
178
+ groups,
179
+ };
180
+
181
+ const outPath = path.join(wikiRoot, "state", "migration", "doctor-report.json");
182
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
183
+ fs.writeFileSync(outPath, `${JSON.stringify(reportObj, null, 2)}\n`);
184
+
185
+ const counts = all.reduce(
186
+ (acc, i) => ((acc[i.status] = (acc[i.status] ?? 0) + 1), acc),
187
+ {}
188
+ );
189
+ console.log(
190
+ `verdict: ${verdict} (${["PASS", "WARN", "FAIL", "SKIP"].map(s => `${counts[s] ?? 0} ${s}`).join(", ")})`
191
+ );
192
+ console.log(`report → ${path.relative(process.cwd(), outPath)}`);
193
+ console.log(
194
+ " (deterministic subset — C/D/E/G completed by the /doctor skill)"
195
+ );
196
+ for (const i of all.filter(i => i.status === "FAIL" || i.status === "WARN")) {
197
+ console.log(` ${i.status === "FAIL" ? "✗" : "⚠"} [${i.id}] ${i.message}`);
198
+ }
199
+ process.exit(verdict === "NOT_READY" ? 1 : 0);
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: lisa-wiki-add-ingest
3
+ description: Scaffold a project-specific "front-door" ingest skill that does something unique (classify a source, fetch from a special system, stamp domain frontmatter) and then chains into /ingest. Use when a project needs a bespoke ingestion path that the core connectors do not cover — instead of forking the kernel. The generated skill enriches and delegates; the kernel still owns synthesis, index, log, verify, state, and PR.
4
+ ---
5
+
6
+ # lisa-wiki-add-ingest
7
+
8
+ Generate a thin, project-local front-door ingest skill so a project can extend ingestion **without
9
+ forking** the kernel. The front-door does only the unique part, then hands enriched parameters to
10
+ `/ingest`.
11
+
12
+ ## Workflow
13
+ 1. **Interview** the project: a short name; what the source is; which `wiki/sources/<sourceSystem>/`
14
+ bucket and page type/frontmatter it should produce; whether it merely *enriches/classifies* an
15
+ input or also *fetches* from an external system; and its **side-effect class**
16
+ (`read-only-ingest` | `repo-write` | `external-write`).
17
+ 2. **Generate** the front-door skill on both runtimes —
18
+ `.claude/skills/lisa-wiki-local-<name>/SKILL.md` and `.agents/skills/lisa-wiki-local-<name>/SKILL.md`.
19
+ Its body does the unique step, then **delegates to the `lisa-wiki-ingest` skill** (Claude facade
20
+ `/ingest`) passing the bucket/type/metadata. If it fetches, it writes only a sanitized source note
21
+ (+ run metadata) and lets the kernel do synthesis/index/log/verify/state/PR.
22
+ 3. **Register** it in `wiki/lisa-wiki.config.json` under `customConnectors`
23
+ (`{ name, skill, sourceSystem, stateFile, sideEffects }`). `/ingest` dispatches **only** to
24
+ registered names — no auto-discovery.
25
+
26
+ ## Rules (the front-door contract)
27
+ - A generated front-door writes **only** its source note + run metadata. It must not write synthesis
28
+ pages, `index.md`, `log.md`, or final state — the kernel does those, in order, after it returns.
29
+ - `external-write` front-doors require config opt-in **and** explicit per-run intent, and their PRs
30
+ never auto-merge.
31
+ - Side effects outside the declared class are a hard failure (enforced by the touched-file guard).
32
+
33
+ ## Related
34
+ `lisa-wiki-ingest` (what it chains into), `lisa-wiki-add-role`, `lisa-wiki-doctor`.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: lisa-wiki-add-role
3
+ description: Scaffold a domain-expert "digital staff" role over the wiki — a dual-runtime subagent (Claude + Codex) plus a staff doc page — from a config.staff[] entry. Use when a project wants a role-scoped expert (e.g. Legal, Finance, Sales) whose knowledge is a slice of the wiki. The plugin only SETS UP the subagent; whether it is ever invoked, scheduled, or routed is out of scope.
4
+ ---
5
+
6
+ # lisa-wiki-add-role
7
+
8
+ Turn a role definition into two generated artifacts: a documentation page in the wiki and a runnable,
9
+ brain-pointed subagent on both runtimes. **Running the subagent (invocation, scheduling, Telegram /
10
+ agent-team routing, private notebooks) is out of scope** — this skill only creates it.
11
+
12
+ ## Workflow
13
+ 1. **Resolve the role** from a `config.staff[]` entry (or interview to add one): `id`, `role`,
14
+ `expertise`, `owns` (categories / connectors / skills), `sensitivity`.
15
+ 2. **Doc page:** generate `wiki/staff/<id>.md` describing the role, its owned domain, and who it
16
+ reports to. This page is wiki content and is itself ingestible (the `roles` connector).
17
+ 3. **Subagents (dual-runtime), rendered from the role-agent templates:**
18
+ - Claude: `.claude/agents/<id>.md`.
19
+ - Codex: `.codex/agents/<id>.toml` (keys `name`, `description`, `developer_instructions`; optional
20
+ `model`, `model_reasoning_effort`, `sandbox_mode`).
21
+ 4. **Brain-pointed, not baked:** the subagent's instructions say *"your domain is `wiki/<owned>/`;
22
+ `/query` it first, contribute via `/ingest`; stay in your lane."* It points at the live wiki so it
23
+ never goes stale. Only its one-line `description` is synthesized from the wiki at generation time.
24
+
25
+ ## Rules
26
+ - v1 instructs lane-keeping but does not *enforce* per-role write isolation (deferred).
27
+ - Setup seeds the starter roster by delegating here per `config.staff[]` entry.
28
+
29
+ ## Related
30
+ `lisa-wiki-setup` (seeds the roster), `lisa-wiki-add-ingest`, `lisa-wiki-onboard-me`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: lisa-wiki-connector-confluence
3
+ description: Produce sanitized Confluence source notes for lisa-wiki ingest via the Atlassian MCP. Use only when lisa-wiki-ingest routes to the confluence connector. Read-only; tenant-guarded.
4
+ ---
5
+
6
+ # lisa-wiki-connector-confluence
7
+
8
+ Skill-driven connector (Atlassian MCP). Writes ONLY source notes under `wiki/sources/confluence/` and
9
+ emits a proposed cursor; the kernel does the rest.
10
+
11
+ ## Flow
12
+ 1. Confirm `connectors.confluence.enabled` and `sideEffects: read-only-ingest`.
13
+ 2. **Tenant guard:** verify the Atlassian connection matches `connectors.confluence.tenantGuard`
14
+ (same site/cloudId discipline as jira). Abort on mismatch.
15
+ 3. **Window:** read `wiki/state/confluence/*.json`. First run → configured window; incremental →
16
+ pages updated since the cursor. Scope to the configured spaces.
17
+ 4. **Fetch (read-only)** page content + metadata via the Atlassian MCP. Never edit Confluence.
18
+ 5. **Write source notes** under `wiki/sources/confluence/<YYYY-MM-DD>-confluence-ingest.md` with
19
+ frontmatter and citations (`Source: <space>/<page>`). Redact secrets; honor retention/sensitivity.
20
+ 6. **Emit run metadata** (proposed cursor, counts) to the handoff file; return to `lisa-wiki-ingest`.
21
+
22
+ ## Rules
23
+ - Abort on tenant mismatch; do not invent pages/decisions; weak evidence → open-questions.
24
+ - Disabled if the Atlassian MCP is absent (`scripts/mcp-doctor.mjs`).
25
+ - Writes only source notes + handoff meta; the kernel advances state.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: lisa-wiki-connector-docs
3
+ description: Ingest a local document (PDF, DOCX, Markdown, text) into a sanitized source note for lisa-wiki ingest. Use only when lisa-wiki-ingest routes to the docs connector (a file-path input). Uses available local converters; no heavy bundled dependency.
4
+ ---
5
+
6
+ # lisa-wiki-connector-docs
7
+
8
+ Skill-driven connector. Converts a local document to markdown using whatever converter is available,
9
+ writes a source note, and hands off; the kernel does the rest.
10
+
11
+ ## Converters (no bundled heavy dependency)
12
+ - **Markdown / text**: ingest directly.
13
+ - **PDF**: `pdftotext` (Poppler) if available, else `pandoc`.
14
+ - **DOCX**: `pandoc` if available, else `textutil` (macOS).
15
+ If no suitable converter is installed, **skip that source and record a `docs` doctor finding** (the
16
+ file could not be converted) so the user can install a converter — a targeted `/ingest <file>` reports
17
+ the skip rather than pretending success. Missing converters are connector-specific, never a
18
+ plugin-install blocker.
19
+
20
+ ## Flow
21
+ 1. Confirm `connectors.docs.enabled` and `sideEffects: read-only-ingest`.
22
+ 2. Resolve the input file; pick the converter by extension; convert to markdown (read-only).
23
+ 3. Write a source note under `wiki/sources/docs/<YYYY-MM-DD>-<slug>.md` with frontmatter
24
+ (`type: source`, dates, `source_system: docs`, original filename) and the converted, reader-safe
25
+ text. Redact secrets; honor `sourceRetention`/`sensitivity`.
26
+ 4. Emit run metadata (source-note path) to the handoff file; return to `lisa-wiki-ingest`.
27
+
28
+ ## Rules
29
+ - Read-only on the source document; never modify the original.
30
+ - Writes only the source note + handoff meta; the kernel advances state.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: lisa-wiki-connector-git
3
+ description: Produce sanitized git/PR-history source notes for lisa-wiki ingest. Use only when lisa-wiki-ingest routes to the git connector (self repo or a registered project). Read-only — never checks out, fetches, or mutates the target repo.
4
+ ---
5
+
6
+ # lisa-wiki-connector-git
7
+
8
+ A universal, deterministic connector backed by `scripts/ingest-git.mjs`. It writes ONLY a source note
9
+ under `wiki/sources/git/` (or a per-project path) and emits a proposed cursor; the kernel performs
10
+ synthesis/index/log/verify/state/PR.
11
+
12
+ ## Flow
13
+ 1. Confirm `connectors.git` is enabled and `read-only-ingest`.
14
+ 2. For the self repo (and each registered project under `projects/`), run:
15
+ ```
16
+ node "${PLUGIN_ROOT}/scripts/ingest-git.mjs" --repo <path> --slug <name> \
17
+ --config wiki/lisa-wiki.config.json --source-dir wiki/sources/git \
18
+ --state wiki/state/git/<slug>.json --emit-meta wiki/state/handoff/git-<slug>-<runId>.json
19
+ ```
20
+ 3. Hand the emitted source-note paths + proposed cursor back to `lisa-wiki-ingest`.
21
+
22
+ ## Rules
23
+ - Read-only: only `git log`/`rev-parse`/`gh pr list` against the repo; never checkout/fetch/reset/pull.
24
+ - In wrapper/standalone mode, the registered child repos are read-only inputs and are never staged.
25
+ - Writes only its source note + handoff meta; the kernel advances `wiki/state/git/<slug>.json`.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: lisa-wiki-connector-jira
3
+ description: Produce sanitized JIRA source notes for lisa-wiki ingest via the Atlassian MCP. Use only when lisa-wiki-ingest routes to the jira connector. Read-only; tenant-guarded.
4
+ ---
5
+
6
+ # lisa-wiki-connector-jira
7
+
8
+ Skill-driven connector (Atlassian MCP). Writes ONLY source notes under `wiki/sources/jira/` and emits
9
+ a proposed cursor; the kernel does synthesis/index/log/verify/state/PR.
10
+
11
+ ## Flow
12
+ 1. Confirm `connectors.jira.enabled` and `sideEffects: read-only-ingest`.
13
+ 2. **Tenant guard:** verify the active Atlassian connection resolves to
14
+ `connectors.jira.tenantGuard.site` / `cloudId`. If it resolves to a different account/tenant,
15
+ **abort** (do not ingest) — this is the cross-tenant contamination guard.
16
+ 3. **Window:** read `wiki/state/jira/*.json`. First run → last 4 months (or configured window);
17
+ incremental → issues `updated >=` the cursor watermark. Scope to `connectors.jira.projects[]`.
18
+ 4. **Fetch (read-only)** via the Atlassian MCP (JQL search + issue read). Never write to JIRA.
19
+ 5. **Write source notes** under `wiki/sources/jira/<YYYY-MM-DD>-jira-ingest.md` — reader-safe, with
20
+ frontmatter (`type: source`, dates, `source_system: jira`) and citations (`Source: <issue key>`).
21
+ Redact secrets; honor `sourceRetention`/`sensitivity`.
22
+ 6. **Emit run metadata** (proposed cursor: latest `updated` watermark, counts) to the handoff file,
23
+ then return to `lisa-wiki-ingest`.
24
+
25
+ ## Rules
26
+ - Abort on tenant mismatch; never invent issues/PRs/people; weak evidence → open-questions.
27
+ - If the Atlassian MCP is absent, the connector is disabled (see `scripts/mcp-doctor.mjs`).
28
+ - Writes only source notes + handoff meta; the kernel advances state.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: lisa-wiki-connector-memory
3
+ description: Ingest the agent's PROJECT-SCOPED memory into a sanitized source note. Use only when lisa-wiki-ingest routes to the memory connector. NEVER ingests global or unrelated memory — global Codex memory and the Chronicle store are refused.
4
+ ---
5
+
6
+ # lisa-wiki-connector-memory
7
+
8
+ A universal connector backed by `scripts/ingest-memory.mjs`. **Project-scoped only.**
9
+
10
+ ## Resolve the memory directory (then pass it explicitly)
11
+ - **Claude**: per-project memory at `~/.claude/projects/<encoded-project-path>/memory/` — inherently
12
+ project-scoped; always eligible.
13
+ - **Codex**: eligible ONLY if a project-scoped memory store exists (e.g. a per-project `CODEX_HOME`).
14
+ The global `~/.codex/memories/` and the Chronicle store are **never** ingested (the script
15
+ hard-refuses them).
16
+
17
+ ## Flow
18
+ ```
19
+ node "${PLUGIN_ROOT}/scripts/ingest-memory.mjs" --memory-dir <project-scoped-dir> \
20
+ --config wiki/lisa-wiki.config.json --source-dir wiki/sources/memory \
21
+ --state wiki/state/memory/memory.json --emit-meta wiki/state/handoff/memory-<runId>.json
22
+ ```
23
+ Hand the source note + proposed cursor back to `lisa-wiki-ingest`.
24
+
25
+ ## Rules
26
+ - Secrets are redacted; sensitivity is at least `internal`.
27
+ - If no project-scoped memory exists, ingest nothing rather than reaching for global memory.
28
+ - Writes only its source note + handoff meta; the kernel advances state.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: lisa-wiki-connector-notion
3
+ description: Produce sanitized Notion source notes for lisa-wiki ingest via the Notion MCP. Use only when lisa-wiki-ingest routes to the notion connector. Read-only; teamspace-guarded.
4
+ ---
5
+
6
+ # lisa-wiki-connector-notion
7
+
8
+ Skill-driven connector (Notion MCP). Writes ONLY source notes under `wiki/sources/notion/` and emits a
9
+ proposed cursor; the kernel does the rest.
10
+
11
+ ## Flow
12
+ 1. Confirm `connectors.notion.enabled` and `sideEffects: read-only-ingest`.
13
+ 2. **Tenant guard:** verify the Notion connection resolves to
14
+ `connectors.notion.tenantGuard.teamspace` / `teamspaceId`. Abort on a different workspace/teamspace.
15
+ 3. **Window:** read `wiki/state/notion/*.json`. First run → configured window; incremental → pages
16
+ edited since the cursor.
17
+ 4. **Fetch (read-only)** via the Notion MCP. Never edit Notion.
18
+ 5. **Write source notes** under `wiki/sources/notion/<YYYY-MM-DD>-notion-ingest.md` with frontmatter
19
+ and citations (`Source: <notion page>`). Redact secrets; honor retention/sensitivity.
20
+ 6. **Emit run metadata** (proposed cursor, counts) to the handoff file; return to `lisa-wiki-ingest`.
21
+
22
+ ## Rules
23
+ - Abort on teamspace mismatch; do not invent pages; weak evidence → open-questions.
24
+ - Disabled if the Notion MCP is absent (`scripts/mcp-doctor.mjs`).
25
+ - Writes only source notes + handoff meta; the kernel advances state.