waypoint-codex 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waypoint-codex",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Codex-native repository operating system: scaffolding, docs routing, repo-local skills, doctor, and sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
4
4
  import { execSync } from "node:child_process";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
@@ -35,6 +35,232 @@ function safeExec(command, cwd) {
35
35
  }
36
36
  }
37
37
 
38
+ const SESSION_DIR_NAMES = ["sessions", "archived_sessions"];
39
+ const SECRET_PATTERNS = [
40
+ /npm_[A-Za-z0-9]+/g,
41
+ /github_pat_[A-Za-z0-9_]+/g,
42
+ /gh[pousr]_[A-Za-z0-9]+/g,
43
+ /sk-[A-Za-z0-9]+/g,
44
+ /sk_[A-Za-z0-9]+/g,
45
+ /fc-[A-Za-z0-9]+/g,
46
+ /AIza[0-9A-Za-z\-_]{20,}/g,
47
+ ];
48
+ const MAX_RECENT_TURNS = 25;
49
+
50
+ function codexHome() {
51
+ return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
52
+ }
53
+
54
+ function redactSecrets(text) {
55
+ return SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, "[REDACTED]"), text);
56
+ }
57
+
58
+ function isWithinPath(childPath, parentPath) {
59
+ const rel = path.relative(realpathSync(parentPath), realpathSync(childPath));
60
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
61
+ }
62
+
63
+ function collectSessionFiles(rootDir) {
64
+ const files = [];
65
+
66
+ function walk(currentDir) {
67
+ if (!existsSync(currentDir)) {
68
+ return;
69
+ }
70
+ for (const entry of readdirSync(currentDir)) {
71
+ const fullPath = path.join(currentDir, entry);
72
+ let stats;
73
+ try {
74
+ stats = statSync(fullPath);
75
+ } catch {
76
+ continue;
77
+ }
78
+ if (stats.isDirectory()) {
79
+ walk(fullPath);
80
+ } else if (entry.endsWith(".jsonl")) {
81
+ files.push(fullPath);
82
+ }
83
+ }
84
+ }
85
+
86
+ walk(rootDir);
87
+ return files;
88
+ }
89
+
90
+ function extractMessageText(content) {
91
+ if (!Array.isArray(content)) {
92
+ return "";
93
+ }
94
+ return content
95
+ .filter((block) => block?.type === "input_text" || block?.type === "output_text")
96
+ .map((block) => (typeof block?.text === "string" ? block.text : ""))
97
+ .join("")
98
+ .trim();
99
+ }
100
+
101
+ function isBootstrapNoise(role, text) {
102
+ return role === "user" && text.startsWith("# AGENTS.md instructions for ");
103
+ }
104
+
105
+ function mergeConsecutiveTurns(turns) {
106
+ const merged = [];
107
+ for (const turn of turns) {
108
+ const previous = merged.at(-1);
109
+ if (previous && previous.role === turn.role) {
110
+ if (previous.text !== turn.text) {
111
+ previous.text = `${previous.text}\n\n${turn.text}`;
112
+ }
113
+ previous.timestamp = turn.timestamp || previous.timestamp;
114
+ previous.messageCount += 1;
115
+ continue;
116
+ }
117
+ merged.push({ ...turn });
118
+ }
119
+ return merged;
120
+ }
121
+
122
+ function parseSession(sessionFile, projectRoot) {
123
+ let sessionCwd = null;
124
+ let compactionCount = 0;
125
+ const rawTurns = [];
126
+ const compactionBoundaries = [];
127
+
128
+ for (const line of readFileSync(sessionFile, "utf8").split("\n")) {
129
+ if (!line.trim()) {
130
+ continue;
131
+ }
132
+
133
+ let parsed;
134
+ try {
135
+ parsed = JSON.parse(line);
136
+ } catch {
137
+ continue;
138
+ }
139
+
140
+ if (parsed.type === "session_meta") {
141
+ const cwd = parsed.payload?.cwd;
142
+ if (typeof cwd === "string") {
143
+ sessionCwd = cwd;
144
+ }
145
+ continue;
146
+ }
147
+
148
+ if (parsed.type === "compacted") {
149
+ compactionCount += 1;
150
+ compactionBoundaries.push(rawTurns.length);
151
+ continue;
152
+ }
153
+
154
+ if (parsed.type !== "response_item" || parsed.payload?.type !== "message") {
155
+ continue;
156
+ }
157
+
158
+ const role = parsed.payload?.role;
159
+ if (role !== "user" && role !== "assistant") {
160
+ continue;
161
+ }
162
+
163
+ const text = redactSecrets(extractMessageText(parsed.payload?.content));
164
+ if (!text || isBootstrapNoise(role, text)) {
165
+ continue;
166
+ }
167
+
168
+ rawTurns.push({
169
+ role,
170
+ text,
171
+ timestamp: parsed.timestamp || null,
172
+ messageCount: 1,
173
+ });
174
+ }
175
+
176
+ if (!sessionCwd || !isWithinPath(sessionCwd, projectRoot)) {
177
+ return null;
178
+ }
179
+
180
+ const selectedFromPreCompaction = compactionBoundaries.length > 0;
181
+ const relevantTurns = selectedFromPreCompaction ? rawTurns.slice(0, compactionBoundaries.at(-1)) : rawTurns;
182
+ const turns = mergeConsecutiveTurns(relevantTurns);
183
+ if (turns.length === 0) {
184
+ return null;
185
+ }
186
+
187
+ return {
188
+ path: sessionFile,
189
+ sessionCwd,
190
+ turns,
191
+ compactionCount,
192
+ selectedFromPreCompaction,
193
+ };
194
+ }
195
+
196
+ function latestMatchingSession(projectRoot) {
197
+ const matches = [];
198
+ for (const dirName of SESSION_DIR_NAMES) {
199
+ for (const sessionFile of collectSessionFiles(path.join(codexHome(), dirName))) {
200
+ const parsed = parseSession(sessionFile, projectRoot);
201
+ if (parsed) {
202
+ matches.push(parsed);
203
+ }
204
+ }
205
+ }
206
+
207
+ matches.sort((a, b) => statSync(b.path).mtimeMs - statSync(a.path).mtimeMs);
208
+ return matches[0] || null;
209
+ }
210
+
211
+ function writeRecentThread(contextDir, projectRoot) {
212
+ const filePath = path.join(contextDir, "RECENT_THREAD.md");
213
+ const snapshot = latestMatchingSession(projectRoot);
214
+ const generatedAt = new Date().toString();
215
+
216
+ if (!snapshot) {
217
+ writeFileSync(
218
+ filePath,
219
+ [
220
+ "# Recent Thread",
221
+ "",
222
+ `Generated by \`${path.relative(projectRoot, fileURLToPath(import.meta.url))}\` on ${generatedAt}.`,
223
+ "",
224
+ "No matching local Codex session was found for this repo yet.",
225
+ "",
226
+ ].join("\n"),
227
+ "utf8"
228
+ );
229
+ return filePath;
230
+ }
231
+
232
+ const selectedTurns = snapshot.turns.slice(-MAX_RECENT_TURNS);
233
+ const relSessionPath = path.relative(codexHome(), snapshot.path);
234
+ const lines = [
235
+ "# Recent Thread",
236
+ "",
237
+ `Generated by \`${path.relative(projectRoot, fileURLToPath(import.meta.url))}\` on ${generatedAt}.`,
238
+ "",
239
+ `- Source session: \`${relSessionPath}\``,
240
+ `- Session cwd: \`${snapshot.sessionCwd}\``,
241
+ `- Included turns: ${selectedTurns.length} of ${snapshot.turns.length} meaningful turns`,
242
+ `- Compactions in source session: ${snapshot.compactionCount}`,
243
+ snapshot.selectedFromPreCompaction
244
+ ? "- Selection rule: take the 25 meaningful turns immediately before the last compaction."
245
+ : "- Selection rule: no compaction found, so take the latest meaningful turns from the local transcript.",
246
+ "- Noise filter: bootstrap AGENTS payloads are excluded.",
247
+ "- Secret handling: obvious token formats are redacted before writing this file.",
248
+ "",
249
+ ];
250
+
251
+ selectedTurns.forEach((turn, index) => {
252
+ const mergedSuffix = turn.messageCount > 1 ? ` (merged ${turn.messageCount} messages)` : "";
253
+ const timestamp = turn.timestamp ? ` - ${turn.timestamp}` : "";
254
+ lines.push(`## ${index + 1}. ${turn.role[0].toUpperCase()}${turn.role.slice(1)}${mergedSuffix}${timestamp}`);
255
+ lines.push("");
256
+ lines.push(turn.text);
257
+ lines.push("");
258
+ });
259
+
260
+ writeFileSync(filePath, `${lines.join("\n").trimEnd()}\n`, "utf8");
261
+ return filePath;
262
+ }
263
+
38
264
  function collectNestedGitRepos(projectRoot) {
39
265
  const repos = [];
40
266
 
@@ -140,6 +366,7 @@ function main() {
140
366
  "```",
141
367
  ].join("\n")
142
368
  );
369
+ const recentThreadPath = writeRecentThread(contextDir, projectRoot);
143
370
 
144
371
  const manifestPath = path.join(contextDir, "MANIFEST.md");
145
372
  const manifestLines = [
@@ -154,6 +381,7 @@ function main() {
154
381
  `- \`${path.relative(projectRoot, recentCommitsPath)}\` — recent commits`,
155
382
  `- \`${path.relative(projectRoot, nestedReposPath)}\` — recent commits in nested repositories`,
156
383
  `- \`${path.relative(projectRoot, prsPath)}\` — open and recently merged pull requests`,
384
+ `- \`${path.relative(projectRoot, recentThreadPath)}\` — latest meaningful turns from the local Codex session for this repo`,
157
385
  `- \`${path.relative(projectRoot, docsIndexPath)}\` — current docs index`,
158
386
  "",
159
387
  "## Stable source-of-truth files to read before this manifest",
@@ -171,4 +399,3 @@ function main() {
171
399
  }
172
400
 
173
401
  main();
174
-