sneakoscope 5.5.2 → 5.6.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 (46) hide show
  1. package/README.md +4 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +7 -1
  7. package/dist/cli/router.js +28 -0
  8. package/dist/commands/doctor.js +65 -3
  9. package/dist/config/skills-manifest.json +58 -58
  10. package/dist/core/agent-bridge/agent-manifest.js +64 -0
  11. package/dist/core/agent-bridge/agent-mode.js +29 -0
  12. package/dist/core/agent-bridge/mcp-server.js +156 -0
  13. package/dist/core/agents/agent-orchestrator.js +1 -11
  14. package/dist/core/commands/agent-bridge-command.js +86 -0
  15. package/dist/core/commands/image-ux-review-command.js +6 -6
  16. package/dist/core/commands/loop-command.js +2 -6
  17. package/dist/core/commands/mad-sks-command.js +3 -3
  18. package/dist/core/commands/mcp-server-command.js +13 -0
  19. package/dist/core/commands/naruto-command.js +4 -4
  20. package/dist/core/commands/ppt-command.js +6 -3
  21. package/dist/core/commands/qa-loop-command.js +64 -26
  22. package/dist/core/commands/team-command.js +1 -1
  23. package/dist/core/commands/wiki-command.js +71 -3
  24. package/dist/core/config/secret-preservation.js +43 -1
  25. package/dist/core/doctor/browser-use-repair.js +149 -0
  26. package/dist/core/doctor/computer-use-repair.js +166 -0
  27. package/dist/core/doctor/imagegen-repair.js +9 -0
  28. package/dist/core/doctor/mcp-transport-collision-repair.js +129 -0
  29. package/dist/core/doctor/supabase-mcp-repair.js +85 -16
  30. package/dist/core/feature-fixtures.js +8 -0
  31. package/dist/core/feature-registry.js +21 -0
  32. package/dist/core/fsx.js +1 -1
  33. package/dist/core/mad-db/mad-db-capability.js +1 -1
  34. package/dist/core/mission.js +19 -3
  35. package/dist/core/naruto/naruto-real-worker-child.js +5 -4
  36. package/dist/core/naruto/naruto-real-worker-runtime.js +5 -4
  37. package/dist/core/naruto/normalize-worker-prompt-text.js +12 -0
  38. package/dist/core/routes.js +3 -1
  39. package/dist/core/triwiki/code-index-scanner.js +313 -0
  40. package/dist/core/triwiki/code-pack.js +169 -0
  41. package/dist/core/triwiki/triwiki-cache-key.js +24 -2
  42. package/dist/core/triwiki-attention.js +34 -5
  43. package/dist/core/update-check.js +50 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/scripts/doctor-imagegen-repair-check.js +6 -1
  46. package/package.json +2 -2
@@ -0,0 +1,12 @@
1
+ export const WORKER_PROMPT_TEXT_MAX_CHARS = 32000;
2
+ export function normalizeWorkerPromptText(value) {
3
+ const normalized = String(value || '').replace(/[^\S\n]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
4
+ const truncated = normalized.length > WORKER_PROMPT_TEXT_MAX_CHARS;
5
+ const text = truncated ? normalized.slice(0, WORKER_PROMPT_TEXT_MAX_CHARS) : normalized;
6
+ return {
7
+ text,
8
+ truncated,
9
+ dropped_chars: truncated ? normalized.length - WORKER_PROMPT_TEXT_MAX_CHARS : 0
10
+ };
11
+ }
12
+ //# sourceMappingURL=normalize-worker-prompt-text.js.map
@@ -643,7 +643,9 @@ export const COMMAND_CATALOG = [
643
643
  { name: 'gx', usage: 'sks gx init|render|validate|drift|snapshot [name]', description: 'Create and verify deterministic SVG/HTML visual context cartridges.' },
644
644
  { name: 'profile', usage: 'sks profile show|set <model>', description: 'Inspect or set the current SKS model profile metadata.' },
645
645
  { name: 'gc', usage: 'sks gc [--dry-run] [--json]', description: 'Compact oversized logs and prune stale runtime artifacts.' },
646
- { name: 'stats', usage: 'sks stats [--json]', description: 'Show package and .sneakoscope storage size.' }
646
+ { name: 'stats', usage: 'sks stats [--json]', description: 'Show package and .sneakoscope storage size.' },
647
+ { name: 'mcp-server', usage: 'sks mcp-server [--expose-exec] [--probe]', description: 'Run a stdio MCP server exposing SKS read-only commands as tools for any MCP-capable agent host; --expose-exec also exposes non-read-only commands; --probe round-trips initialize/tools-list and exits.' },
648
+ { name: 'agent-bridge', usage: 'sks agent-bridge setup [--json]', description: 'Publish the agent-bridge manifest, print host registration snippets (generic MCP host, Codex CLI, non-interactive contract), and run a live non-interactive smoke test.' }
647
649
  ];
648
650
  export function routeById(id) {
649
651
  const key = String(id || '').replace(/^\$/, '').toLowerCase();
@@ -0,0 +1,313 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { collectInputFiles } from './triwiki-cache-key.js';
5
+ export const CODE_INDEX_SCHEMA = 'sks.code-index.v1';
6
+ const DEFAULT_MAX_LINES_PER_FILE = 120;
7
+ const DEFAULT_MAX_FILES = 4000;
8
+ const CODE_EXT_RE = /\.(js|ts|tsx|jsx|cjs|mjs)$/;
9
+ const ENTRY_FILE_RE = /^(index|mod)\.(ts|tsx|js|jsx|mjs|cjs)$/;
10
+ const EXPORT_LINE_RE = /^\s*export\s+(?:default\s+)?(?:const|let|var|function|class|interface|type|enum|abstract\s+class|async\s+function)\s+[A-Za-z0-9_$]+/;
11
+ const EXPORT_BRACE_RE = /^\s*export\s*\{/;
12
+ const EXPORT_STAR_RE = /^\s*export\s*\*/;
13
+ const IMPORT_SPEC_RE = /(?:from\s+|require\()\s*['"](\.\.?\/[^'"]+)['"]/g;
14
+ // High fan-in or a large file surface both correlate with change blast-radius; simple counts avoid needing a real dependency graph.
15
+ const RISK_HIGH_FAN_IN = 5;
16
+ const RISK_HIGH_FILE_COUNT = 40;
17
+ const RISK_MEDIUM_FAN_IN = 2;
18
+ const RISK_MEDIUM_FILE_COUNT = 15;
19
+ export async function scanCodebaseIndex(root, opts = {}) {
20
+ const resolvedRoot = path.resolve(root);
21
+ const maxLinesPerFile = opts.maxLinesPerFile ?? DEFAULT_MAX_LINES_PER_FILE;
22
+ const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;
23
+ const inventory = listSourceFiles(resolvedRoot);
24
+ const truncatedByCap = inventory.length > maxFiles;
25
+ const scannedFiles = truncatedByCap ? inventory.slice(0, maxFiles) : inventory;
26
+ const boundaries = inferModuleBoundaries(resolvedRoot);
27
+ const fileRecords = readFileRecords(resolvedRoot, scannedFiles, maxLinesPerFile);
28
+ const modulesByPath = assignFilesToModules(boundaries, fileRecords);
29
+ const moduleIdForFile = buildFileToModuleIdIndex(modulesByPath);
30
+ const cards = buildModuleCards(resolvedRoot, modulesByPath, moduleIdForFile);
31
+ return {
32
+ schema: CODE_INDEX_SCHEMA,
33
+ generated_at: new Date().toISOString(),
34
+ root: resolvedRoot,
35
+ modules: cards,
36
+ truncated: truncatedByCap,
37
+ scanned_file_count: scannedFiles.length,
38
+ scanned_files_cap: maxFiles
39
+ };
40
+ }
41
+ function listSourceFiles(root) {
42
+ const gitFiles = tryGitLsFiles(root);
43
+ const all = gitFiles !== null ? gitFiles : walkFallback(root);
44
+ return all.filter((rel) => CODE_EXT_RE.test(rel)).sort();
45
+ }
46
+ function tryGitLsFiles(root) {
47
+ try {
48
+ const result = spawnSync('git', ['ls-files'], { cwd: root, encoding: 'utf8' });
49
+ if (result.status !== 0 || result.error)
50
+ return null;
51
+ return String(result.stdout || '').split('\n').map((line) => line.trim()).filter(Boolean);
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ function walkFallback(root) {
58
+ const collected = collectInputFiles(root, ['.']);
59
+ return collected.records.map((record) => record.path);
60
+ }
61
+ function inferModuleBoundaries(root) {
62
+ const boundaries = [];
63
+ const seen = new Set();
64
+ const addBoundary = (dir) => {
65
+ const normalized = dir.replace(/\\/g, '/').replace(/\/+$/, '');
66
+ if (!normalized || seen.has(normalized))
67
+ return;
68
+ seen.add(normalized);
69
+ boundaries.push({ module_id: moduleIdFromDir(normalized), dir: normalized });
70
+ };
71
+ for (const sourceRoot of listDirs(root, '.').filter((name) => name === 'src' || name === 'source' || name === 'lib')) {
72
+ for (const topLevel of listDirs(root, sourceRoot)) {
73
+ addBoundary(topLevel);
74
+ for (const nested of listDirs(root, topLevel))
75
+ addBoundary(nested);
76
+ }
77
+ }
78
+ const workspaces = readWorkspaceGlobs(root);
79
+ for (const glob of workspaces) {
80
+ for (const dir of resolveWorkspaceGlob(root, glob))
81
+ addBoundary(dir);
82
+ }
83
+ return boundaries.sort((a, b) => a.dir.localeCompare(b.dir));
84
+ }
85
+ function listDirs(root, rel) {
86
+ const absolute = rel === '.' ? root : path.join(root, rel);
87
+ let entries;
88
+ try {
89
+ entries = fs.readdirSync(absolute, { withFileTypes: true });
90
+ }
91
+ catch (error) {
92
+ if (isMissingOrInaccessible(error))
93
+ return [];
94
+ throw error;
95
+ }
96
+ return entries
97
+ .filter((entry) => entry.isDirectory() && !isExcludedDirName(entry.name))
98
+ .map((entry) => (rel === '.' ? entry.name : `${rel}/${entry.name}`))
99
+ .sort();
100
+ }
101
+ function isExcludedDirName(name) {
102
+ return name === '.git' || name === 'node_modules' || name === 'dist' || name === 'build' || name === 'coverage' || name === '__tests__' || name.startsWith('.');
103
+ }
104
+ function isMissingOrInaccessible(error) {
105
+ const code = error?.code;
106
+ return code === 'ENOENT' || code === 'EACCES' || code === 'ENOTDIR' || code === 'EPERM' || code === 'EISDIR';
107
+ }
108
+ function readWorkspaceGlobs(root) {
109
+ try {
110
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
111
+ if (!pkg.workspaces)
112
+ return [];
113
+ if (Array.isArray(pkg.workspaces))
114
+ return pkg.workspaces;
115
+ return pkg.workspaces.packages || [];
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ }
121
+ function resolveWorkspaceGlob(root, glob) {
122
+ const normalized = glob.replace(/\\/g, '/');
123
+ if (!normalized.includes('*')) {
124
+ return fs.existsSync(path.join(root, normalized)) ? [normalized.replace(/\/+$/, '')] : [];
125
+ }
126
+ const starIndex = normalized.indexOf('*');
127
+ const prefix = normalized.slice(0, starIndex).replace(/\/+$/, '');
128
+ return listDirs(root, prefix || '.');
129
+ }
130
+ function moduleIdFromDir(dir) {
131
+ return dir.replace(/^src\//, '').replace(/\//g, '-') || 'root';
132
+ }
133
+ function readFileRecords(root, files, maxLinesPerFile) {
134
+ const out = [];
135
+ for (const rel of files) {
136
+ const absolute = path.join(root, rel);
137
+ let text = '';
138
+ try {
139
+ text = fs.readFileSync(absolute, 'utf8');
140
+ }
141
+ catch (error) {
142
+ if (isMissingOrInaccessible(error))
143
+ continue;
144
+ throw error;
145
+ }
146
+ const lines = text.split(/\r?\n/);
147
+ const loc = lines.length && lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
148
+ const scanLines = lines.slice(0, maxLinesPerFile);
149
+ const exportLines = [];
150
+ for (const line of scanLines) {
151
+ if (EXPORT_LINE_RE.test(line) || EXPORT_BRACE_RE.test(line) || EXPORT_STAR_RE.test(line)) {
152
+ exportLines.push(line.trim());
153
+ }
154
+ }
155
+ const importSpecifiers = [];
156
+ for (const line of scanLines) {
157
+ IMPORT_SPEC_RE.lastIndex = 0;
158
+ let match;
159
+ while ((match = IMPORT_SPEC_RE.exec(line))) {
160
+ if (match[1])
161
+ importSpecifiers.push(match[1]);
162
+ }
163
+ }
164
+ out.push({ rel, loc, exportLines, importSpecifiers });
165
+ }
166
+ return out;
167
+ }
168
+ function assignFilesToModules(boundaries, files) {
169
+ const sortedBoundaries = [...boundaries].sort((a, b) => b.dir.length - a.dir.length);
170
+ const map = new Map();
171
+ for (const boundary of boundaries)
172
+ map.set(boundary.module_id, { boundary, files: [] });
173
+ for (const file of files) {
174
+ const owner = sortedBoundaries.find((boundary) => file.rel === boundary.dir || file.rel.startsWith(`${boundary.dir}/`));
175
+ if (!owner)
176
+ continue;
177
+ map.get(owner.module_id).files.push(file);
178
+ }
179
+ for (const [moduleId, entry] of map) {
180
+ if (!entry.files.length)
181
+ map.delete(moduleId);
182
+ }
183
+ return map;
184
+ }
185
+ function buildFileToModuleIdIndex(modulesByPath) {
186
+ const index = new Map();
187
+ for (const [moduleId, entry] of modulesByPath) {
188
+ for (const file of entry.files)
189
+ index.set(file.rel, moduleId);
190
+ }
191
+ return index;
192
+ }
193
+ function buildModuleCards(root, modulesByPath, moduleIdForFile) {
194
+ const fanIn = new Map();
195
+ const dependencyEdgesByModule = new Map();
196
+ for (const [moduleId, entry] of modulesByPath) {
197
+ const edges = new Set();
198
+ for (const file of entry.files) {
199
+ for (const specifier of file.importSpecifiers) {
200
+ const resolvedRel = resolveRelativeImport(root, file.rel, specifier);
201
+ if (!resolvedRel)
202
+ continue;
203
+ const targetModuleId = moduleIdForFile.get(resolvedRel);
204
+ if (!targetModuleId || targetModuleId === moduleId)
205
+ continue;
206
+ edges.add(targetModuleId);
207
+ }
208
+ }
209
+ dependencyEdgesByModule.set(moduleId, edges);
210
+ for (const target of edges)
211
+ fanIn.set(target, (fanIn.get(target) || 0) + 1);
212
+ }
213
+ const cards = [];
214
+ for (const [moduleId, entry] of modulesByPath) {
215
+ const files = entry.files;
216
+ const loc = files.reduce((sum, file) => sum + file.loc, 0);
217
+ const exportsSummary = files.flatMap((file) => file.exportLines);
218
+ const dependencyEdges = [...(dependencyEdgesByModule.get(moduleId) || [])].sort();
219
+ const entryPoints = collectEntryPoints(root, entry.boundary.dir, files);
220
+ const fanInCount = fanIn.get(moduleId) || 0;
221
+ const risk = computeRisk(fanInCount, files.length);
222
+ cards.push({
223
+ module_id: moduleId,
224
+ paths: [entry.boundary.dir],
225
+ entry_points: entryPoints,
226
+ exports_summary: exportsSummary,
227
+ dependency_edges: dependencyEdges,
228
+ file_count: files.length,
229
+ loc,
230
+ risk
231
+ });
232
+ }
233
+ return cards.sort((a, b) => a.module_id.localeCompare(b.module_id));
234
+ }
235
+ function computeRisk(fanIn, fileCount) {
236
+ if (fanIn >= RISK_HIGH_FAN_IN || fileCount >= RISK_HIGH_FILE_COUNT)
237
+ return 'high';
238
+ if (fanIn >= RISK_MEDIUM_FAN_IN || fileCount >= RISK_MEDIUM_FILE_COUNT)
239
+ return 'medium';
240
+ return 'low';
241
+ }
242
+ function collectEntryPoints(root, dir, files) {
243
+ const entryPoints = new Set();
244
+ const pkgPath = path.join(root, dir, 'package.json');
245
+ if (fs.existsSync(pkgPath)) {
246
+ try {
247
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
248
+ if (typeof pkg.main === 'string')
249
+ entryPoints.add(normalizeEntryPath(dir, pkg.main));
250
+ if (typeof pkg.bin === 'string')
251
+ entryPoints.add(normalizeEntryPath(dir, pkg.bin));
252
+ else if (pkg.bin && typeof pkg.bin === 'object') {
253
+ for (const value of Object.values(pkg.bin))
254
+ if (typeof value === 'string')
255
+ entryPoints.add(normalizeEntryPath(dir, value));
256
+ }
257
+ for (const value of collectExportsPaths(pkg.exports))
258
+ entryPoints.add(normalizeEntryPath(dir, value));
259
+ }
260
+ catch {
261
+ // malformed package.json at a module boundary is not this scanner's concern; skip entry-point inference for it
262
+ }
263
+ }
264
+ for (const file of files) {
265
+ const base = path.basename(file.rel);
266
+ if (ENTRY_FILE_RE.test(base))
267
+ entryPoints.add(file.rel);
268
+ }
269
+ return [...entryPoints].sort();
270
+ }
271
+ function collectExportsPaths(exportsField) {
272
+ if (typeof exportsField === 'string')
273
+ return [exportsField];
274
+ if (!exportsField || typeof exportsField !== 'object')
275
+ return [];
276
+ const out = [];
277
+ for (const value of Object.values(exportsField)) {
278
+ if (typeof value === 'string')
279
+ out.push(value);
280
+ else
281
+ out.push(...collectExportsPaths(value));
282
+ }
283
+ return out;
284
+ }
285
+ function normalizeEntryPath(dir, entry) {
286
+ const cleaned = entry.replace(/^\.\//, '');
287
+ return `${dir}/${cleaned}`.replace(/\\/g, '/');
288
+ }
289
+ function resolveRelativeImport(root, fromRel, specifier) {
290
+ const fromDir = path.posix.dirname(fromRel);
291
+ const joined = path.posix.normalize(path.posix.join(fromDir, specifier));
292
+ const candidates = [
293
+ `${joined}.ts`,
294
+ `${joined}.tsx`,
295
+ `${joined}.js`,
296
+ `${joined}.jsx`,
297
+ `${joined}/index.ts`,
298
+ `${joined}/index.tsx`,
299
+ `${joined}/index.js`,
300
+ `${joined}/index.jsx`,
301
+ joined
302
+ ];
303
+ for (const candidate of candidates) {
304
+ const absolute = path.join(root, candidate);
305
+ if (!fs.existsSync(absolute))
306
+ continue;
307
+ // only resolve to a real file — a bare directory match (no extension) can't be looked up in the file->module index
308
+ if (fs.statSync(absolute).isFile())
309
+ return candidate;
310
+ }
311
+ return null;
312
+ }
313
+ //# sourceMappingURL=code-index-scanner.js.map
@@ -0,0 +1,169 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { ensureDir, exists, nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
5
+ export const CODE_PACK_SCHEMA = 'sks.code-pack.v1';
6
+ export const DEFAULT_CODE_PACK_TOKEN_BUDGET = 8000;
7
+ export function codePackDir(root) {
8
+ return path.join(root, '.sneakoscope', 'wiki');
9
+ }
10
+ export function codePackPath(root) {
11
+ return path.join(codePackDir(root), 'code-pack.json');
12
+ }
13
+ export function codePackPrevPath(root) {
14
+ return path.join(codePackDir(root), 'code-pack.prev.json');
15
+ }
16
+ export function buildCodePack(root, index, tokenBudget = DEFAULT_CODE_PACK_TOKEN_BUDGET) {
17
+ const entries = [];
18
+ for (const card of index.modules) {
19
+ const entry = buildEntryForModule(card);
20
+ if (!entry)
21
+ continue;
22
+ entries.push(entry);
23
+ }
24
+ const totalTokenCost = entries.reduce((sum, entry) => sum + entry.token_cost, 0);
25
+ return {
26
+ schema: CODE_PACK_SCHEMA,
27
+ generated_at: nowIso(),
28
+ git_head_sha: readGitHeadSha(root),
29
+ source_file_count: index.scanned_file_count,
30
+ index_digest: computeIndexDigest(index),
31
+ entries,
32
+ token_budget: tokenBudget,
33
+ total_token_cost: totalTokenCost
34
+ };
35
+ }
36
+ function buildEntryForModule(card) {
37
+ const citations = collectCitations(card);
38
+ // openwiki principle: an entry with no real repository citation is worse than no entry at all
39
+ if (!citations.length)
40
+ return null;
41
+ const text = summarizeModule(card);
42
+ return {
43
+ id: `code:${card.module_id}`,
44
+ text,
45
+ citations,
46
+ trust_score: computeTrustScore(card),
47
+ freshness: 'unknown',
48
+ token_cost: Math.ceil(text.length / 4)
49
+ };
50
+ }
51
+ function collectCitations(card) {
52
+ const paths = [...card.paths, ...card.entry_points].filter(Boolean);
53
+ const seen = new Set();
54
+ const citations = [];
55
+ for (const p of paths) {
56
+ if (seen.has(p))
57
+ continue;
58
+ seen.add(p);
59
+ citations.push({ path: p });
60
+ }
61
+ return citations;
62
+ }
63
+ function summarizeModule(card) {
64
+ const role = inferRole(card);
65
+ const primaryPath = card.paths[0] || card.module_id;
66
+ const exportsPart = summarizeExports(card.exports_summary);
67
+ const depsPart = summarizeDependencies(card.dependency_edges);
68
+ const sizePart = `${card.file_count} file${card.file_count === 1 ? '' : 's'}, ${card.loc} loc, ${card.risk} risk`;
69
+ return `${card.module_id} is ${role} at ${primaryPath} (${sizePart}). ${exportsPart} ${depsPart}`.trim().replace(/\s+/g, ' ');
70
+ }
71
+ function inferRole(card) {
72
+ const idLower = card.module_id.toLowerCase();
73
+ const pathLower = card.paths.join(' ').toLowerCase();
74
+ if (idLower.includes('test') || pathLower.includes('__tests__'))
75
+ return 'a test module';
76
+ if (idLower.includes('cli') || pathLower.includes('cli'))
77
+ return 'a CLI module';
78
+ if (idLower.includes('command'))
79
+ return 'a command module';
80
+ if (idLower.includes('core'))
81
+ return 'a core module';
82
+ return 'a module';
83
+ }
84
+ function summarizeExports(exportsSummary) {
85
+ if (!exportsSummary.length)
86
+ return 'It has no detected top-level exports.';
87
+ const preview = exportsSummary.slice(0, 5).map((line) => line.replace(/^export\s+/, '').replace(/\s*\{\s*$/, '').trim());
88
+ const suffix = exportsSummary.length > preview.length ? `, and ${exportsSummary.length - preview.length} more` : '';
89
+ return `Key exports: ${preview.join('; ')}${suffix}.`;
90
+ }
91
+ function summarizeDependencies(dependencyEdges) {
92
+ if (!dependencyEdges.length)
93
+ return 'It has no detected cross-module dependencies.';
94
+ const preview = dependencyEdges.slice(0, 5);
95
+ const suffix = dependencyEdges.length > preview.length ? `, and ${dependencyEdges.length - preview.length} more` : '';
96
+ return `Depends on: ${preview.join(', ')}${suffix}.`;
97
+ }
98
+ function computeTrustScore(card) {
99
+ // more citable surface (entry points, real exports) correlates with a more reliably summarizable module
100
+ let score = 0.5;
101
+ if (card.entry_points.length > 0)
102
+ score += 0.2;
103
+ if (card.exports_summary.length > 0)
104
+ score += 0.2;
105
+ if (card.risk === 'high')
106
+ score -= 0.15;
107
+ else if (card.risk === 'low')
108
+ score += 0.1;
109
+ return Math.max(0, Math.min(1, Number(score.toFixed(2))));
110
+ }
111
+ function readGitHeadSha(root) {
112
+ try {
113
+ const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
114
+ if (result.status !== 0 || result.error)
115
+ return null;
116
+ const sha = String(result.stdout || '').trim();
117
+ return sha || null;
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ function computeIndexDigest(index) {
124
+ const stable = index.modules
125
+ .map((card) => ({ module_id: card.module_id, paths: [...card.paths].sort() }))
126
+ .sort((a, b) => a.module_id.localeCompare(b.module_id));
127
+ return sha256(JSON.stringify(stable));
128
+ }
129
+ export async function validateCodePack(pack, root) {
130
+ const issues = [];
131
+ const seenIds = new Set();
132
+ for (const entry of pack.entries) {
133
+ if (seenIds.has(entry.id)) {
134
+ issues.push(`duplicate entry id: ${entry.id}`);
135
+ }
136
+ else {
137
+ seenIds.add(entry.id);
138
+ }
139
+ if (!entry.citations.length) {
140
+ issues.push(`entry ${entry.id} has no citations`);
141
+ continue;
142
+ }
143
+ for (const citation of entry.citations) {
144
+ const absolute = path.join(root, citation.path);
145
+ if (!fs.existsSync(absolute)) {
146
+ issues.push(`entry ${entry.id} citation path does not exist: ${citation.path}`);
147
+ }
148
+ }
149
+ }
150
+ const totalTokenCost = pack.entries.reduce((sum, entry) => sum + entry.token_cost, 0);
151
+ if (totalTokenCost > pack.token_budget) {
152
+ issues.push(`total_token_cost ${totalTokenCost} exceeds token_budget ${pack.token_budget}`);
153
+ }
154
+ return { ok: issues.length === 0, issues };
155
+ }
156
+ export async function writeCodePackAtomic(root, pack) {
157
+ const targetPath = codePackPath(root);
158
+ const prevPath = codePackPrevPath(root);
159
+ await ensureDir(codePackDir(root));
160
+ let prevWritten = null;
161
+ if (await exists(targetPath)) {
162
+ const previous = await readJson(targetPath);
163
+ await writeJsonAtomic(prevPath, previous);
164
+ prevWritten = prevPath;
165
+ }
166
+ await writeJsonAtomic(targetPath, pack);
167
+ return { ok: true, path: targetPath, prev_path: prevWritten };
168
+ }
169
+ //# sourceMappingURL=code-pack.js.map
@@ -125,7 +125,15 @@ function listFiles(root, start) {
125
125
  const out = [];
126
126
  if (!fs.existsSync(start))
127
127
  return out;
128
- const stat = fs.lstatSync(start);
128
+ let stat;
129
+ try {
130
+ stat = fs.lstatSync(start);
131
+ }
132
+ catch (error) {
133
+ if (isMissingOrInaccessible(error))
134
+ return out;
135
+ throw error;
136
+ }
129
137
  if (stat.isSymbolicLink() || stat.isFile())
130
138
  return [relativeUnix(root, start)];
131
139
  if (!stat.isDirectory())
@@ -138,7 +146,17 @@ function listFiles(root, start) {
138
146
  const relDir = relativeUnix(root, dir);
139
147
  if (relDir && isExcluded(relDir))
140
148
  continue;
141
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
149
+ let entries;
150
+ try {
151
+ entries = fs.readdirSync(dir, { withFileTypes: true });
152
+ }
153
+ catch (error) {
154
+ // dir can vanish or become unreadable between stack push and pop (TOCTOU) — contribute zero files, not a crash
155
+ if (isMissingOrInaccessible(error))
156
+ continue;
157
+ throw error;
158
+ }
159
+ for (const entry of entries) {
142
160
  const absolute = path.join(dir, entry.name);
143
161
  const rel = relativeUnix(root, absolute);
144
162
  if (isExcluded(rel))
@@ -151,6 +169,10 @@ function listFiles(root, start) {
151
169
  }
152
170
  return out.sort();
153
171
  }
172
+ function isMissingOrInaccessible(error) {
173
+ const code = error?.code;
174
+ return code === 'ENOENT' || code === 'EACCES' || code === 'ENOTDIR' || code === 'EPERM';
175
+ }
154
176
  function isExcluded(rel) {
155
177
  for (const dir of DEFAULT_EXCLUDED_DIRS) {
156
178
  if (rel === dir || rel.startsWith(`${dir}/`))
@@ -224,7 +224,33 @@ function uniqueAttentionRows(rows = [], max = 4) {
224
224
  }
225
225
  return out;
226
226
  }
227
- export function buildTriWikiAttention({ selected = [], wiki = {}, role = 'worker', maxUse = 4, maxHydrate = 4 } = {}) {
227
+ /** Ranks code-pack entries (LLM-consumable, source-cited codebase summaries see
228
+ * src/core/triwiki/code-pack.ts) by trust_score into a dedicated attention sub-budget,
229
+ * independent of the policy-claim RGBA/geometric selection above: code entries carry
230
+ * no meaningful mission-coordinate proximity, so competing them against policy claims
231
+ * for the same fixed slot budget would make their appearance arbitrary. Each surviving
232
+ * entry becomes a use_first row (bare id, no fabricated RGBA) plus a hydrate_first row
233
+ * pointing at its source citations, so a consumer knows which real files back it. */
234
+ function codePackAttentionRows(codePackEntries = [], tokenBudget = 2000) {
235
+ const sorted = [...(codePackEntries || [])]
236
+ .filter((entry) => entry && typeof entry.id === 'string')
237
+ .sort((a, b) => Number(b.trust_score || 0) - Number(a.trust_score || 0));
238
+ const useFirst = [];
239
+ const hydrateFirst = [];
240
+ let tokens = 0;
241
+ for (const entry of sorted) {
242
+ const cost = Number(entry.token_cost) || Math.max(1, Math.ceil(String(entry.text || '').length / 4));
243
+ if (tokens + cost > tokenBudget)
244
+ continue;
245
+ tokens += cost;
246
+ useFirst.push([entry.id, null, null]);
247
+ const citationPaths = Array.isArray(entry.citations) ? entry.citations.map((c) => c?.path).filter(Boolean) : [];
248
+ if (citationPaths.length)
249
+ hydrateFirst.push([entry.id, `code_citations:${citationPaths.join(',')}`]);
250
+ }
251
+ return { useFirst, hydrateFirst };
252
+ }
253
+ export function buildTriWikiAttention({ selected = [], wiki = {}, role = 'worker', maxUse = 4, maxHydrate = 4, codePackEntries = [], codePackTokenBudget = 2000 } = {}) {
228
254
  const anchors = attentionAnchorMap(wiki);
229
255
  const ranked = [...(selected || [])]
230
256
  .map((claim, index) => ({ claim, index }))
@@ -250,10 +276,11 @@ export function buildTriWikiAttention({ selected = [], wiki = {}, role = 'worker
250
276
  ...voxelRows,
251
277
  ...selectedHydrateRows
252
278
  ], maxHydrate);
279
+ const codeRows = codePackAttentionRows(codePackEntries, codePackTokenBudget);
253
280
  return {
254
281
  mode: 'aggressive_triwiki_active_recall',
255
- use_first: useFirst,
256
- hydrate_first: hydrateFirst
282
+ use_first: uniqueAttentionRows([...useFirst, ...codeRows.useFirst], maxUse + codeRows.useFirst.length),
283
+ hydrate_first: uniqueAttentionRows([...hydrateFirst, ...codeRows.hydrateFirst], maxHydrate + codeRows.hydrateFirst.length)
257
284
  };
258
285
  }
259
286
  export function selectClaims(mission, claims, budget = {}) {
@@ -282,7 +309,7 @@ export function geometricOffsets(max = 65536) {
282
309
  out.push(x);
283
310
  return out;
284
311
  }
285
- export function contextCapsule({ mission, role = 'worker', contractHash = null, claims = [], q4 = {}, q3 = [], budget = {} }) {
312
+ export function contextCapsule({ mission, role = 'worker', contractHash = null, claims = [], q4 = {}, q3 = [], budget = {}, codePackEntries = [] }) {
286
313
  const trustPolicy = budget.trustPolicy || DEFAULT_TRUST_POLICY;
287
314
  const claimsWithTrust = (claims || []).map((claim) => withTrust(claim, trustPolicy));
288
315
  const selected = selectClaims(mission, claims, { maxClaims: budget.maxClaims ?? (role.includes('verifier') ? 16 : 9), trustPolicy });
@@ -313,7 +340,9 @@ export function contextCapsule({ mission, role = 'worker', contractHash = null,
313
340
  wiki,
314
341
  role,
315
342
  maxUse: budget.maxAttentionUse ?? (role.includes('verifier') ? 7 : 4),
316
- maxHydrate: budget.maxAttentionHydrate ?? (role.includes('verifier') ? 7 : 4)
343
+ maxHydrate: budget.maxAttentionHydrate ?? (role.includes('verifier') ? 7 : 4),
344
+ codePackEntries,
345
+ codePackTokenBudget: budget.codePackTokenBudget ?? 2000
317
346
  }),
318
347
  claims: selected.map((c) => {
319
348
  const anchor = anchorsById.get(c.id);