rippletide-package 0.1.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.
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ CODEX_NODE="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
6
+
7
+ if command -v node >/dev/null 2>&1; then
8
+ exec node "$SCRIPT_DIR/rippletide-codex.mjs" "$@"
9
+ fi
10
+
11
+ if [[ -x "$CODEX_NODE" ]]; then
12
+ exec "$CODEX_NODE" "$SCRIPT_DIR/rippletide-codex.mjs" "$@"
13
+ fi
14
+
15
+ echo "Rippletide Codex mode needs Node.js to run." >&2
16
+ exit 127
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+
7
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ const roots = [
10
+ process.env.RIPPLETIDE_REVIEWER_ROOT,
11
+ path.resolve(scriptDir, "../../.."),
12
+ process.cwd(),
13
+ ].filter(Boolean);
14
+
15
+ const reviewerRoot = [...new Set(roots.map((root) => path.resolve(root)))]
16
+ .find((root) => existsSync(path.join(root, "src/integrations/codex/prepare.js")));
17
+
18
+ if (!reviewerRoot) {
19
+ console.error("Rippletide reviewer source not found.");
20
+ console.error("Set RIPPLETIDE_REVIEWER_ROOT=/path/to/reviewer and rerun.");
21
+ process.exit(1);
22
+ }
23
+
24
+ await import(pathToFileURL(path.join(reviewerRoot, "src/integrations/codex/prepare.js")).href);
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ RELATIVE_REVIEWER_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
6
+ REVIEWER_ROOT="${RIPPLETIDE_REVIEWER_ROOT:-}"
7
+
8
+ if [[ -z "$REVIEWER_ROOT" && -f "$RELATIVE_REVIEWER_ROOT/bin/rippletide.js" ]]; then
9
+ REVIEWER_ROOT="$RELATIVE_REVIEWER_ROOT"
10
+ fi
11
+
12
+ if [[ -z "$REVIEWER_ROOT" ]]; then
13
+ REVIEWER_ROOT="$RELATIVE_REVIEWER_ROOT"
14
+ fi
15
+
16
+ CLI="$REVIEWER_ROOT/bin/rippletide.js"
17
+ CODEX_NODE="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
18
+
19
+ if [[ ! -f "$CLI" ]]; then
20
+ echo "Rippletide CLI not found at $CLI" >&2
21
+ exit 1
22
+ fi
23
+
24
+ if command -v bun >/dev/null 2>&1; then
25
+ exec bun "$CLI" "$@"
26
+ fi
27
+
28
+ if command -v node >/dev/null 2>&1; then
29
+ exec node "$CLI" "$@"
30
+ fi
31
+
32
+ if [[ -x "$CODEX_NODE" ]]; then
33
+ exec "$CODEX_NODE" "$CLI" "$@"
34
+ fi
35
+
36
+ echo "Rippletide needs Bun or Node.js to run." >&2
37
+ exit 127
@@ -0,0 +1,235 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ export const DEFAULT_EVIDENCE_BUDGET = 140_000;
6
+ export const MAX_FILES = 260;
7
+ export const MAX_FILE_BYTES = 300_000;
8
+
9
+ export const IGNORED_DIRS = new Set([
10
+ ".cache",
11
+ ".git",
12
+ ".next",
13
+ ".pytest_cache",
14
+ ".ruff_cache",
15
+ ".rippletide",
16
+ ".turbo",
17
+ ".venv",
18
+ "__pycache__",
19
+ "build",
20
+ "coverage",
21
+ "dist",
22
+ "node_modules",
23
+ "target",
24
+ "tmp",
25
+ "vendor",
26
+ ]);
27
+
28
+ export const SOURCE_EXTENSIONS = new Set([
29
+ ".c",
30
+ ".cc",
31
+ ".cfg",
32
+ ".cjs",
33
+ ".cpp",
34
+ ".cs",
35
+ ".css",
36
+ ".go",
37
+ ".html",
38
+ ".ini",
39
+ ".java",
40
+ ".js",
41
+ ".json",
42
+ ".jsx",
43
+ ".kt",
44
+ ".lua",
45
+ ".md",
46
+ ".mjs",
47
+ ".php",
48
+ ".prompt",
49
+ ".properties",
50
+ ".py",
51
+ ".rb",
52
+ ".rs",
53
+ ".sh",
54
+ ".sql",
55
+ ".toml",
56
+ ".ts",
57
+ ".tsx",
58
+ ".txt",
59
+ ".xml",
60
+ ".yaml",
61
+ ".yml",
62
+ ]);
63
+
64
+ export const SOURCE_FILENAMES = new Set([
65
+ ".env.example",
66
+ ".env.sample",
67
+ "AGENTS.md",
68
+ "Dockerfile",
69
+ "Gemfile",
70
+ "Makefile",
71
+ "Procfile",
72
+ "go.mod",
73
+ "package-lock.json",
74
+ "package.json",
75
+ "pnpm-lock.yaml",
76
+ "poetry.lock",
77
+ "pyproject.toml",
78
+ "requirements.txt",
79
+ "yarn.lock",
80
+ ]);
81
+
82
+ export async function collectFiles(root, maxFiles = MAX_FILES) {
83
+ const files = [];
84
+
85
+ async function walk(directory, depth) {
86
+ if (files.length >= maxFiles || depth > 7) return;
87
+ const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
88
+
89
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
90
+ if (files.length >= maxFiles) return;
91
+ const absolutePath = path.join(directory, entry.name);
92
+ const relativePath = path.relative(root, absolutePath) || entry.name;
93
+
94
+ if (entry.isDirectory()) {
95
+ if (!IGNORED_DIRS.has(entry.name)) {
96
+ await walk(absolutePath, depth + 1);
97
+ }
98
+ continue;
99
+ }
100
+
101
+ if (!entry.isFile()) continue;
102
+ if (!SOURCE_EXTENSIONS.has(path.extname(entry.name)) && !SOURCE_FILENAMES.has(entry.name)) continue;
103
+ files.push(relativePath);
104
+ }
105
+ }
106
+
107
+ await walk(root, 0);
108
+ return files;
109
+ }
110
+
111
+ export async function readSource(root, file) {
112
+ const absolutePath = path.join(root, file);
113
+ const info = await stat(absolutePath).catch(() => null);
114
+ if (!info || info.size > MAX_FILE_BYTES) return "";
115
+ return readFile(absolutePath, "utf8").catch(() => "");
116
+ }
117
+
118
+ export function redactSecrets(source) {
119
+ return source
120
+ .replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, "sk-[REDACTED]")
121
+ .replace(/\b([A-Z0-9_]*(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|BOT_TOKEN|SECRET|PASSWORD|PRIVATE_KEY)[A-Z0-9_]*\s*=\s*)[^\s"'`]+/g, "$1[REDACTED]")
122
+ .replace(/(["'`](?:api[_-]?key|access[_-]?token|auth[_-]?token|bot[_-]?token|secret|password|private[_-]?key)["'`]\s*:\s*["'`])([^"'`\n]+)(["'`])/gi, "$1[REDACTED]$3");
123
+ }
124
+
125
+ export function lineNumberedSource(source, maxChars) {
126
+ const sanitized = redactSecrets(source).replace(/\t/g, " ");
127
+ const lines = sanitized.split("\n");
128
+ const output = [];
129
+ let used = 0;
130
+
131
+ for (let index = 0; index < lines.length; index += 1) {
132
+ const rendered = `${String(index + 1).padStart(4, " ")} | ${lines[index]}`;
133
+ if (used + rendered.length + 1 > maxChars) {
134
+ output.push("... truncated for context budget ...");
135
+ break;
136
+ }
137
+ output.push(rendered);
138
+ used += rendered.length + 1;
139
+ }
140
+
141
+ return output.join("\n");
142
+ }
143
+
144
+ export function buildEvidencePack(root, files, sources, options = {}) {
145
+ const budget = options.budget ?? DEFAULT_EVIDENCE_BUDGET;
146
+ const title = options.title ?? "Rippletide Local Evidence Pack";
147
+ const description = options.description
148
+ ?? "This is raw local evidence for an LLM inspector. It is not a review; it only packages included files.";
149
+ const citationRule = options.citationRule
150
+ ?? "Citation rule for the inspector: cite facts as `path/to/file.ext:line` using the line numbers shown in the excerpts.";
151
+
152
+ const sections = [
153
+ `# ${title}`,
154
+ "",
155
+ description,
156
+ "",
157
+ "## Scan",
158
+ `- Root: ${root}`,
159
+ `- Source/config/doc files found: ${files.length}`,
160
+ `- Context budget: ${budget} characters`,
161
+ "",
162
+ "## File Tree",
163
+ files.map((file) => `- ${file}`).join("\n") || "- No source files found.",
164
+ "",
165
+ "## Source Excerpts",
166
+ ];
167
+
168
+ let remaining = budget;
169
+ const included = [];
170
+ const skipped = [];
171
+
172
+ for (const item of sources) {
173
+ if (remaining < 2_500) {
174
+ skipped.push(item.file);
175
+ continue;
176
+ }
177
+
178
+ const perFileBudget = Math.min(20_000, Math.max(2_500, remaining - 1_000));
179
+ const excerpt = lineNumberedSource(item.source, perFileBudget);
180
+ const block = [
181
+ "",
182
+ `### ${item.file}`,
183
+ "```text",
184
+ excerpt,
185
+ "```",
186
+ ].join("\n");
187
+
188
+ if (block.length > remaining) {
189
+ skipped.push(item.file);
190
+ continue;
191
+ }
192
+
193
+ sections.push(block);
194
+ included.push(item.file);
195
+ remaining -= block.length;
196
+ }
197
+
198
+ sections.push(
199
+ "",
200
+ "## Context Packing Summary",
201
+ `- Files included with excerpts: ${included.length}`,
202
+ `- Files omitted after budget: ${skipped.length ? skipped.join(", ") : "none"}`,
203
+ "",
204
+ citationRule,
205
+ );
206
+
207
+ return sections.join("\n");
208
+ }
209
+
210
+ export async function collectAgentContext(targetPath, options = {}) {
211
+ const root = path.resolve(targetPath ?? ".");
212
+ if (!existsSync(root)) {
213
+ throw new Error(`Path does not exist: ${root}`);
214
+ }
215
+
216
+ const files = await collectFiles(root);
217
+ const sources = [];
218
+
219
+ for (const file of files) {
220
+ const source = await readSource(root, file);
221
+ if (source) sources.push({ file, source });
222
+ }
223
+
224
+ return {
225
+ evidencePack: buildEvidencePack(root, files, sources, {
226
+ budget: options.evidenceBudget ?? DEFAULT_EVIDENCE_BUDGET,
227
+ citationRule: options.citationRule,
228
+ description: options.description,
229
+ title: options.title,
230
+ }),
231
+ files,
232
+ root,
233
+ sources,
234
+ };
235
+ }