@codyswann/lisa 2.23.2 → 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.
- package/.claude-plugin/marketplace.json +6 -0
- 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/setup/github.md +7 -0
- package/plugins/lisa/commands/setup/linear.md +7 -0
- package/plugins/lisa/skills/setup-github/SKILL.md +199 -0
- package/plugins/lisa/skills/setup-linear/SKILL.md +217 -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-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 +8 -0
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +32 -0
- package/plugins/lisa-wiki/ci/lisa-wiki-validate.yml +32 -0
- package/plugins/lisa-wiki/commands/add-ingest.md +6 -0
- package/plugins/lisa-wiki/commands/add-role.md +6 -0
- package/plugins/lisa-wiki/commands/doctor.md +6 -0
- package/plugins/lisa-wiki/commands/ingest.md +6 -0
- package/plugins/lisa-wiki/commands/lint.md +6 -0
- package/plugins/lisa-wiki/commands/migrate.md +6 -0
- package/plugins/lisa-wiki/commands/onboard-me.md +6 -0
- package/plugins/lisa-wiki/commands/query.md +6 -0
- package/plugins/lisa-wiki/commands/setup.md +6 -0
- package/plugins/lisa-wiki/schema/lisa-wiki-config.schema.json +118 -0
- package/plugins/lisa-wiki/schema/wiki-structure.schema.json +51 -0
- package/plugins/lisa-wiki/scripts/_wiki-lib.mjs +185 -0
- package/plugins/lisa-wiki/scripts/diff-guard.mjs +116 -0
- package/plugins/lisa-wiki/scripts/ingest-git.mjs +189 -0
- package/plugins/lisa-wiki/scripts/ingest-memory.mjs +130 -0
- package/plugins/lisa-wiki/scripts/ingest-roles.mjs +85 -0
- package/plugins/lisa-wiki/scripts/ingest_slack_channel.py +329 -0
- package/plugins/lisa-wiki/scripts/lint-wiki.mjs +320 -0
- package/plugins/lisa-wiki/scripts/mcp-doctor.mjs +72 -0
- package/plugins/lisa-wiki/scripts/render-contract.mjs +107 -0
- package/plugins/lisa-wiki/scripts/rewrite-refs.mjs +144 -0
- package/plugins/lisa-wiki/scripts/slack_oauth_user.py +179 -0
- package/plugins/lisa-wiki/scripts/validate-config.mjs +232 -0
- package/plugins/lisa-wiki/scripts/verify-migration.mjs +199 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-add-role/SKILL.md +30 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-git/SKILL.md +25 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-connector-web/SKILL.md +23 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-doctor/SKILL.md +47 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-ingest/SKILL.md +43 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-lint/SKILL.md +32 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-migrate/SKILL.md +43 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-query/SKILL.md +30 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-setup/SKILL.md +45 -0
- package/plugins/lisa-wiki/skills/lisa-wiki-usage/SKILL.md +50 -0
- package/plugins/lisa-wiki/templates/agents/role-agent.claude.md +16 -0
- package/plugins/lisa-wiki/templates/agents/role-agent.codex.toml +15 -0
- package/plugins/lisa-wiki/templates/index.md +17 -0
- package/plugins/lisa-wiki/templates/llm-wiki-contract.md +60 -0
- package/plugins/lisa-wiki/templates/log.md +8 -0
- package/plugins/lisa-wiki/templates/page-types/architecture.md +18 -0
- package/plugins/lisa-wiki/templates/page-types/concept.md +18 -0
- package/plugins/lisa-wiki/templates/page-types/decision.md +18 -0
- package/plugins/lisa-wiki/templates/page-types/entity.md +19 -0
- package/plugins/lisa-wiki/templates/page-types/open-question.md +18 -0
- package/plugins/lisa-wiki/templates/page-types/playbook.md +18 -0
- package/plugins/lisa-wiki/templates/page-types/project.md +19 -0
- package/plugins/lisa-wiki/templates/page-types/requirement.md +19 -0
- package/plugins/lisa-wiki/templates/page-types/staff.md +26 -0
- package/plugins/lisa-wiki/templates/start-here.md +24 -0
- package/plugins/lisa-wiki/templates/state-readme.md +20 -0
- package/plugins/src/base/commands/setup/github.md +7 -0
- package/plugins/src/base/commands/setup/linear.md +7 -0
- package/plugins/src/base/skills/setup-github/SKILL.md +199 -0
- package/plugins/src/base/skills/setup-linear/SKILL.md +217 -0
- package/plugins/src/wiki/.claude-plugin/plugin.json +6 -0
- package/plugins/src/wiki/ci/lisa-wiki-validate.yml +32 -0
- package/plugins/src/wiki/commands/add-ingest.md +6 -0
- package/plugins/src/wiki/commands/add-role.md +6 -0
- package/plugins/src/wiki/commands/doctor.md +6 -0
- package/plugins/src/wiki/commands/ingest.md +6 -0
- package/plugins/src/wiki/commands/lint.md +6 -0
- package/plugins/src/wiki/commands/migrate.md +6 -0
- package/plugins/src/wiki/commands/onboard-me.md +6 -0
- package/plugins/src/wiki/commands/query.md +6 -0
- package/plugins/src/wiki/commands/setup.md +6 -0
- package/plugins/src/wiki/schema/lisa-wiki-config.schema.json +118 -0
- package/plugins/src/wiki/schema/wiki-structure.schema.json +51 -0
- package/plugins/src/wiki/scripts/_wiki-lib.mjs +185 -0
- package/plugins/src/wiki/scripts/diff-guard.mjs +116 -0
- package/plugins/src/wiki/scripts/ingest-git.mjs +189 -0
- package/plugins/src/wiki/scripts/ingest-memory.mjs +130 -0
- package/plugins/src/wiki/scripts/ingest-roles.mjs +85 -0
- package/plugins/src/wiki/scripts/ingest_slack_channel.py +329 -0
- package/plugins/src/wiki/scripts/lint-wiki.mjs +320 -0
- package/plugins/src/wiki/scripts/mcp-doctor.mjs +72 -0
- package/plugins/src/wiki/scripts/render-contract.mjs +107 -0
- package/plugins/src/wiki/scripts/rewrite-refs.mjs +144 -0
- package/plugins/src/wiki/scripts/slack_oauth_user.py +179 -0
- package/plugins/src/wiki/scripts/validate-config.mjs +232 -0
- package/plugins/src/wiki/scripts/verify-migration.mjs +199 -0
- package/plugins/src/wiki/skills/lisa-wiki-add-ingest/SKILL.md +34 -0
- package/plugins/src/wiki/skills/lisa-wiki-add-role/SKILL.md +30 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-confluence/SKILL.md +25 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-docs/SKILL.md +30 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-git/SKILL.md +25 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-jira/SKILL.md +28 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-memory/SKILL.md +28 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-notion/SKILL.md +25 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-roles/SKILL.md +22 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-slack/SKILL.md +30 -0
- package/plugins/src/wiki/skills/lisa-wiki-connector-web/SKILL.md +23 -0
- package/plugins/src/wiki/skills/lisa-wiki-doctor/SKILL.md +47 -0
- package/plugins/src/wiki/skills/lisa-wiki-ingest/SKILL.md +43 -0
- package/plugins/src/wiki/skills/lisa-wiki-lint/SKILL.md +32 -0
- package/plugins/src/wiki/skills/lisa-wiki-migrate/SKILL.md +43 -0
- package/plugins/src/wiki/skills/lisa-wiki-onboard-me/SKILL.md +33 -0
- package/plugins/src/wiki/skills/lisa-wiki-query/SKILL.md +30 -0
- package/plugins/src/wiki/skills/lisa-wiki-setup/SKILL.md +45 -0
- package/plugins/src/wiki/skills/lisa-wiki-usage/SKILL.md +50 -0
- package/plugins/src/wiki/templates/agents/role-agent.claude.md +16 -0
- package/plugins/src/wiki/templates/agents/role-agent.codex.toml +15 -0
- package/plugins/src/wiki/templates/index.md +17 -0
- package/plugins/src/wiki/templates/llm-wiki-contract.md +60 -0
- package/plugins/src/wiki/templates/log.md +8 -0
- package/plugins/src/wiki/templates/page-types/architecture.md +18 -0
- package/plugins/src/wiki/templates/page-types/concept.md +18 -0
- package/plugins/src/wiki/templates/page-types/decision.md +18 -0
- package/plugins/src/wiki/templates/page-types/entity.md +19 -0
- package/plugins/src/wiki/templates/page-types/open-question.md +18 -0
- package/plugins/src/wiki/templates/page-types/playbook.md +18 -0
- package/plugins/src/wiki/templates/page-types/project.md +19 -0
- package/plugins/src/wiki/templates/page-types/requirement.md +19 -0
- package/plugins/src/wiki/templates/page-types/staff.md +26 -0
- package/plugins/src/wiki/templates/start-here.md +24 -0
- package/plugins/src/wiki/templates/state-readme.md +20 -0
- package/scripts/build-plugins.sh +29 -21
- package/scripts/check-plugins-sync.sh +1 -1
- package/scripts/generate-codex-plugin-artifacts.mjs +22 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ingest-git.mjs — git/PR-history connector. Dependency-free (git + optional gh).
|
|
4
|
+
* Read-only: it never checks out, fetches, or mutates the target repo.
|
|
5
|
+
*
|
|
6
|
+
* Writes a sanitized, dated source note under <source-dir> summarizing commits (and
|
|
7
|
+
* merged PRs, if `gh` is available) since the cursor, and emits a PROPOSED next cursor
|
|
8
|
+
* to --emit-meta. It does NOT advance final state — the kernel does that after
|
|
9
|
+
* verification (per the §7 connector contract).
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node ingest-git.mjs --repo <path> [--slug <name>] [--config <p>]
|
|
13
|
+
* [--source-dir <dir>] [--state <file>] [--emit-meta <file>]
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { readJsonSafe, SECRET_PATTERNS } from "./_wiki-lib.mjs";
|
|
19
|
+
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
const opt = (n, d) => {
|
|
22
|
+
const i = argv.indexOf(n);
|
|
23
|
+
return i !== -1 ? argv[i + 1] : d;
|
|
24
|
+
};
|
|
25
|
+
const repo = path.resolve(opt("--repo", "."));
|
|
26
|
+
const slug = opt("--slug", path.basename(repo));
|
|
27
|
+
const githubRepo = opt("--github-repo"); // owner/repo for PR history (else inferred via gh)
|
|
28
|
+
const fileSlug = slug.replace(/[^A-Za-z0-9_.-]+/g, "-"); // safe for filenames
|
|
29
|
+
const sourceDir = path.resolve(opt("--source-dir", "wiki/sources/git"));
|
|
30
|
+
const statePath = opt("--state");
|
|
31
|
+
const emitMeta = opt("--emit-meta");
|
|
32
|
+
|
|
33
|
+
const fail = m => {
|
|
34
|
+
console.error(`✗ ${m}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
};
|
|
37
|
+
const git = args =>
|
|
38
|
+
execFileSync("git", ["-C", repo, ...args], { encoding: "utf8" }).trim();
|
|
39
|
+
const tryGit = args => {
|
|
40
|
+
try {
|
|
41
|
+
return git(args);
|
|
42
|
+
} catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const commitExists = c => {
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("git", ["-C", repo, "cat-file", "-e", `${c}^{commit}`], {
|
|
49
|
+
stdio: "ignore",
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const redact = t =>
|
|
57
|
+
SECRET_PATTERNS.reduce(
|
|
58
|
+
(acc, { re }) => acc.replace(new RegExp(re, "g"), "[REDACTED]"),
|
|
59
|
+
t
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
!fs.existsSync(path.join(repo, ".git")) &&
|
|
64
|
+
!tryGit(["rev-parse", "--is-inside-work-tree"])
|
|
65
|
+
) {
|
|
66
|
+
fail(`not a git repository: ${repo}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const cursor = statePath ? (readJsonSafe(statePath)?.cursor ?? {}) : {};
|
|
70
|
+
const lastCommit = cursor.lastCommit;
|
|
71
|
+
const head = tryGit(["rev-parse", "HEAD"]);
|
|
72
|
+
if (!head) fail("could not resolve HEAD (empty repo?)");
|
|
73
|
+
if (lastCommit && !commitExists(lastCommit)) {
|
|
74
|
+
fail(
|
|
75
|
+
`cursor commit ${lastCommit} not found in ${repo}; refusing to silently re-window from HEAD`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const range = lastCommit ? `${lastCommit}..HEAD` : "HEAD";
|
|
80
|
+
const logLines = tryGit([
|
|
81
|
+
"log",
|
|
82
|
+
range,
|
|
83
|
+
"--pretty=format:%h\t%ad\t%s",
|
|
84
|
+
"--date=short",
|
|
85
|
+
])
|
|
86
|
+
.split("\n")
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
const totalCommits = Number(tryGit(["rev-list", "--count", "HEAD"]) || "0");
|
|
89
|
+
|
|
90
|
+
// merged PRs via gh (optional, read-only); resolve the GitHub repo explicitly
|
|
91
|
+
let lastPr = cursor.lastPr ?? null;
|
|
92
|
+
let prSummary = "(no GitHub repo resolved — PR history skipped)";
|
|
93
|
+
let ghRepo = githubRepo;
|
|
94
|
+
if (!ghRepo) {
|
|
95
|
+
try {
|
|
96
|
+
ghRepo = execFileSync(
|
|
97
|
+
"gh",
|
|
98
|
+
["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
|
|
99
|
+
{ cwd: repo, encoding: "utf8" }
|
|
100
|
+
).trim();
|
|
101
|
+
} catch {
|
|
102
|
+
ghRepo = "";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (ghRepo) {
|
|
106
|
+
try {
|
|
107
|
+
const prs = JSON.parse(
|
|
108
|
+
execFileSync(
|
|
109
|
+
"gh",
|
|
110
|
+
[
|
|
111
|
+
"pr",
|
|
112
|
+
"list",
|
|
113
|
+
"--repo",
|
|
114
|
+
ghRepo,
|
|
115
|
+
"--state",
|
|
116
|
+
"merged",
|
|
117
|
+
"--limit",
|
|
118
|
+
"20",
|
|
119
|
+
"--json",
|
|
120
|
+
"number,title,mergedAt",
|
|
121
|
+
],
|
|
122
|
+
{ cwd: repo, encoding: "utf8" }
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
if (prs.length) {
|
|
126
|
+
lastPr = prs[0].number;
|
|
127
|
+
prSummary = `${prs.length} recent merged PR(s) in ${ghRepo}; latest #${prs[0].number} "${prs[0].title}"`;
|
|
128
|
+
} else {
|
|
129
|
+
prSummary = `no merged PRs found in ${ghRepo}`;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
prSummary = `(gh pr list failed for ${ghRepo} — PR history skipped)`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
137
|
+
const newCommits = logLines.length;
|
|
138
|
+
const notePath = path.join(sourceDir, `${date}-${fileSlug}-git.md`);
|
|
139
|
+
const note = `---
|
|
140
|
+
type: source
|
|
141
|
+
created: ${date}
|
|
142
|
+
updated: ${date}
|
|
143
|
+
related: []
|
|
144
|
+
sources: []
|
|
145
|
+
source_system: git
|
|
146
|
+
project: ${slug}
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# git history — ${slug} (${date})
|
|
150
|
+
|
|
151
|
+
- Repo: \`${repo}\`
|
|
152
|
+
- HEAD: \`${head}\`
|
|
153
|
+
- Total commits on HEAD: ${totalCommits}
|
|
154
|
+
- New commits since last ingest${lastCommit ? ` (\`${lastCommit}\`)` : " (first run)"}: ${newCommits}
|
|
155
|
+
- Merged PRs: ${prSummary}
|
|
156
|
+
|
|
157
|
+
## New commits
|
|
158
|
+
${
|
|
159
|
+
newCommits
|
|
160
|
+
? logLines
|
|
161
|
+
.slice(0, 200)
|
|
162
|
+
.map(l => `- ${l.replace(/\t/g, " · ")}`)
|
|
163
|
+
.join("\n")
|
|
164
|
+
: "_(none)_"
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
fs.mkdirSync(sourceDir, { recursive: true });
|
|
169
|
+
fs.writeFileSync(notePath, redact(note));
|
|
170
|
+
|
|
171
|
+
const meta = {
|
|
172
|
+
connector: "git",
|
|
173
|
+
profile: slug,
|
|
174
|
+
ranAt: new Date().toISOString(),
|
|
175
|
+
proposedCursor: { lastCommit: head, lastPr },
|
|
176
|
+
sourceNotes: [path.relative(process.cwd(), notePath)],
|
|
177
|
+
};
|
|
178
|
+
if (emitMeta) {
|
|
179
|
+
fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
|
|
180
|
+
fs.writeFileSync(emitMeta, `${JSON.stringify(meta, null, 2)}\n`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(
|
|
184
|
+
`✓ git connector: ${newCommits} new commit(s) → ${path.relative(process.cwd(), notePath)}`
|
|
185
|
+
);
|
|
186
|
+
if (emitMeta)
|
|
187
|
+
console.log(
|
|
188
|
+
` proposed cursor → ${path.relative(process.cwd(), emitMeta)} (kernel advances final state after verification)`
|
|
189
|
+
);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ingest-memory.mjs — PROJECT-SCOPED memory connector. Dependency-free.
|
|
4
|
+
*
|
|
5
|
+
* Ingests ONLY a project's own persisted memory into a sanitized source note.
|
|
6
|
+
* NEVER ingests global/unrelated memory: global Codex memory (~/.codex/memories) and
|
|
7
|
+
* the Codex Chronicle store are hard-refused. Claude per-project memory is inherently
|
|
8
|
+
* project-scoped; Codex memory is accepted only via an explicit project-scoped path
|
|
9
|
+
* (e.g. a per-project CODEX_HOME). Emits a proposed cursor; the kernel advances state.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node ingest-memory.mjs --memory-dir <dir> [--config <p>] [--source-dir <dir>]
|
|
13
|
+
* [--state <file>] [--emit-meta <file>]
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import { loadConfig, walkFiles, SECRET_PATTERNS } from "./_wiki-lib.mjs";
|
|
19
|
+
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
const opt = (n, d) => {
|
|
22
|
+
const i = argv.indexOf(n);
|
|
23
|
+
return i !== -1 ? argv[i + 1] : d;
|
|
24
|
+
};
|
|
25
|
+
const memoryDir = opt("--memory-dir");
|
|
26
|
+
const sourceDir = path.resolve(opt("--source-dir", "wiki/sources/memory"));
|
|
27
|
+
const emitMeta = opt("--emit-meta");
|
|
28
|
+
|
|
29
|
+
const fail = m => {
|
|
30
|
+
console.error(`✗ ${m}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
};
|
|
33
|
+
if (!memoryDir)
|
|
34
|
+
fail("--memory-dir is required (the PROJECT-SCOPED memory directory)");
|
|
35
|
+
const resolvedMem = path.resolve(memoryDir);
|
|
36
|
+
|
|
37
|
+
// Hard refusal of global / unrelated memory stores.
|
|
38
|
+
const globalCodex = path.resolve(os.homedir(), ".codex", "memories");
|
|
39
|
+
const chronicle = path.resolve(os.homedir(), ".codex", "memories_extensions");
|
|
40
|
+
if (
|
|
41
|
+
resolvedMem === globalCodex ||
|
|
42
|
+
resolvedMem.startsWith(globalCodex + path.sep) ||
|
|
43
|
+
resolvedMem.startsWith(chronicle + path.sep)
|
|
44
|
+
) {
|
|
45
|
+
fail(
|
|
46
|
+
`refusing global Codex memory (${resolvedMem}). Memory ingestion is project-scoped only — never global/unrelated.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (!fs.existsSync(resolvedMem)) fail(`memory dir not found: ${resolvedMem}`);
|
|
50
|
+
|
|
51
|
+
// Prove the directory is PROJECT-scoped (don't just trust the caller): accept only the
|
|
52
|
+
// Claude per-project memory dir for THIS repo, a per-project CODEX_HOME memories dir, or a
|
|
53
|
+
// config-declared allowlist. Otherwise refuse — it could be another project's memory.
|
|
54
|
+
const repo = path.resolve(opt("--repo", "."));
|
|
55
|
+
const { config } = loadConfig(opt("--config"));
|
|
56
|
+
const allowedRoots = (config?.memory?.allowedRoots ?? []).map(p =>
|
|
57
|
+
path.resolve(p)
|
|
58
|
+
);
|
|
59
|
+
const claudeMem = path.resolve(
|
|
60
|
+
os.homedir(),
|
|
61
|
+
".claude",
|
|
62
|
+
"projects",
|
|
63
|
+
repo.split(path.sep).join("-"),
|
|
64
|
+
"memory"
|
|
65
|
+
);
|
|
66
|
+
const codexHome = process.env.CODEX_HOME
|
|
67
|
+
? path.resolve(process.env.CODEX_HOME)
|
|
68
|
+
: null;
|
|
69
|
+
const projectCodexMem =
|
|
70
|
+
codexHome && codexHome !== path.resolve(os.homedir(), ".codex")
|
|
71
|
+
? path.join(codexHome, "memories")
|
|
72
|
+
: null;
|
|
73
|
+
const under = root =>
|
|
74
|
+
Boolean(root) &&
|
|
75
|
+
(resolvedMem === root || resolvedMem.startsWith(root + path.sep));
|
|
76
|
+
if (!(under(claudeMem) || under(projectCodexMem) || allowedRoots.some(under))) {
|
|
77
|
+
fail(
|
|
78
|
+
`--memory-dir is not provably project-scoped for repo ${repo}. Expected the Claude per-project dir (${claudeMem}), a per-project CODEX_HOME memories dir, or config.memory.allowedRoots. Refusing possibly-unrelated memory.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const redact = t =>
|
|
83
|
+
SECRET_PATTERNS.reduce(
|
|
84
|
+
(acc, { re }) => acc.replace(new RegExp(re, "g"), "[REDACTED]"),
|
|
85
|
+
t
|
|
86
|
+
);
|
|
87
|
+
const mdFiles = walkFiles(resolvedMem, { ext: ".md" });
|
|
88
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
89
|
+
const entries = mdFiles.map(f => {
|
|
90
|
+
const body = redact(fs.readFileSync(f, "utf8")).trim();
|
|
91
|
+
return `### ${path.basename(f)}\n\n${body}`;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const notePath = path.join(sourceDir, `${date}-memory.md`);
|
|
95
|
+
const note = `---
|
|
96
|
+
type: source
|
|
97
|
+
created: ${date}
|
|
98
|
+
updated: ${date}
|
|
99
|
+
related: []
|
|
100
|
+
sources: []
|
|
101
|
+
source_system: memory
|
|
102
|
+
sensitivity: internal
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# project-scoped memory (${date})
|
|
106
|
+
|
|
107
|
+
- Source: \`${resolvedMem}\` (project-scoped; global/Chronicle memory is never ingested)
|
|
108
|
+
- Files: ${mdFiles.length}
|
|
109
|
+
|
|
110
|
+
${entries.join("\n\n") || "_(no memory files)_"}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
fs.mkdirSync(sourceDir, { recursive: true });
|
|
114
|
+
fs.writeFileSync(notePath, note);
|
|
115
|
+
|
|
116
|
+
const meta = {
|
|
117
|
+
connector: "memory",
|
|
118
|
+
profile: "project",
|
|
119
|
+
ranAt: new Date().toISOString(),
|
|
120
|
+
proposedCursor: { files: mdFiles.length, lastIngest: date },
|
|
121
|
+
sourceNotes: [path.relative(process.cwd(), notePath)],
|
|
122
|
+
};
|
|
123
|
+
if (emitMeta) {
|
|
124
|
+
fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
|
|
125
|
+
fs.writeFileSync(emitMeta, `${JSON.stringify(meta, null, 2)}\n`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log(
|
|
129
|
+
`✓ memory connector: ${mdFiles.length} file(s) → ${path.relative(process.cwd(), notePath)}`
|
|
130
|
+
);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ingest-roles.mjs — roles connector. Dependency-free. Ingests the wiki's own staff
|
|
4
|
+
* roster (config.staff[] + wiki/staff/*.md) into a sanitized source note. Emits a
|
|
5
|
+
* proposed cursor; the kernel advances state. Does NOT run any subagent.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node ingest-roles.mjs [--config <p>] [--wiki <root>] [--source-dir <dir>]
|
|
9
|
+
* [--state <file>] [--emit-meta <file>]
|
|
10
|
+
*/
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { loadConfig, walkFiles } from "./_wiki-lib.mjs";
|
|
14
|
+
|
|
15
|
+
const argv = process.argv.slice(2);
|
|
16
|
+
const opt = (n, d) => {
|
|
17
|
+
const i = argv.indexOf(n);
|
|
18
|
+
return i !== -1 ? argv[i + 1] : d;
|
|
19
|
+
};
|
|
20
|
+
const { config } = loadConfig(opt("--config"));
|
|
21
|
+
const wikiRoot = path.resolve(opt("--wiki", config?.wikiRoot ?? "wiki"));
|
|
22
|
+
const sourceDir = path.resolve(
|
|
23
|
+
opt("--source-dir", path.join(wikiRoot, "sources", "roles"))
|
|
24
|
+
);
|
|
25
|
+
const emitMeta = opt("--emit-meta");
|
|
26
|
+
|
|
27
|
+
const staff = Array.isArray(config?.staff) ? config.staff : [];
|
|
28
|
+
const staffDir = path.join(wikiRoot, "staff");
|
|
29
|
+
const staffPages = fs.existsSync(staffDir)
|
|
30
|
+
? walkFiles(staffDir, { ext: ".md" })
|
|
31
|
+
: [];
|
|
32
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
33
|
+
|
|
34
|
+
const rosterRows = staff.length
|
|
35
|
+
? staff
|
|
36
|
+
.map(
|
|
37
|
+
s =>
|
|
38
|
+
`- **${s.role}** (\`${s.id}\`)${s.expertise ? ` — ${s.expertise}` : ""}${s.owns?.categories ? ` · owns: ${s.owns.categories.join(", ")}` : ""}`
|
|
39
|
+
)
|
|
40
|
+
.join("\n")
|
|
41
|
+
: "_(no roles declared in config.staff[])_";
|
|
42
|
+
|
|
43
|
+
const notePath = path.join(sourceDir, `${date}-roles.md`);
|
|
44
|
+
const note = `---
|
|
45
|
+
type: source
|
|
46
|
+
created: ${date}
|
|
47
|
+
updated: ${date}
|
|
48
|
+
related: []
|
|
49
|
+
sources: []
|
|
50
|
+
source_system: roles
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
# digital staff roster (${date})
|
|
54
|
+
|
|
55
|
+
Declared roles: ${staff.length}; staff doc pages: ${staffPages.length}.
|
|
56
|
+
|
|
57
|
+
## Roles
|
|
58
|
+
${rosterRows}
|
|
59
|
+
|
|
60
|
+
## Staff pages
|
|
61
|
+
${staffPages.length ? staffPages.map(p => `- \`${path.relative(wikiRoot, p)}\``).join("\n") : "_(none)_"}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
fs.mkdirSync(sourceDir, { recursive: true });
|
|
65
|
+
fs.writeFileSync(notePath, note);
|
|
66
|
+
|
|
67
|
+
const meta = {
|
|
68
|
+
connector: "roles",
|
|
69
|
+
profile: "project",
|
|
70
|
+
ranAt: new Date().toISOString(),
|
|
71
|
+
proposedCursor: {
|
|
72
|
+
roles: staff.length,
|
|
73
|
+
pages: staffPages.length,
|
|
74
|
+
lastIngest: date,
|
|
75
|
+
},
|
|
76
|
+
sourceNotes: [path.relative(process.cwd(), notePath)],
|
|
77
|
+
};
|
|
78
|
+
if (emitMeta) {
|
|
79
|
+
fs.mkdirSync(path.dirname(emitMeta), { recursive: true });
|
|
80
|
+
fs.writeFileSync(emitMeta, `${JSON.stringify(meta, null, 2)}\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(
|
|
84
|
+
`✓ roles connector: ${staff.length} role(s), ${staffPages.length} page(s) → ${path.relative(process.cwd(), notePath)}`
|
|
85
|
+
);
|