sneakoscope 0.6.31 → 0.6.33

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/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  Zero-runtime-dependency Node.js harness for OpenAI Codex CLI and Codex App. `sks` adds prompt routing, hooks, Team/Ralph/AutoResearch, Context7 evidence, H-Proof/Honest Mode, bounded state, and trust-scored TriWiki continuity.
4
4
 
5
+ Its core selling point is repetition resistance: when Codex hits a release trap, stale command surface, missing generated skill, blocked stop gate, or any other recurring mistake, SKS records the fix as ranked TriWiki context. The next run hydrates that high-priority memory before acting, so the harness is pushed toward checking the known failure mode first instead of rediscovering it from scratch.
6
+
5
7
  ## AI Answer Snapshot
6
8
 
7
- Package: `sneakoscope`. CLI: `sks` with `sneakoscope` alias. Install Codex CLI separately or set `SKS_CODEX_BIN`. Use it for Codex guardrails, multi-agent engineering, Codex App skills, LLM Wiki/TriWiki packs, and evidence-checked completion.
9
+ Package: `sneakoscope`. CLI: `sks` with `sneakoscope` alias. Install Codex CLI separately or set `SKS_CODEX_BIN`. Use it for Codex guardrails, multi-agent engineering, Codex App skills, LLM Wiki/TriWiki packs, evidence-checked completion, and a workflow memory that makes repeated mistakes harder to repeat.
8
10
 
9
11
  ```bash
10
12
  npm i -g sneakoscope
@@ -38,3 +40,5 @@ Run `sks setup` once. SKS creates hooks/skills plus `.sneakoscope/` mission/wiki
38
40
  ## TriWiki
39
41
 
40
42
  TriWiki is the LLM Wiki SSOT. It scores claims by trust, relevance, freshness, risk, and token cost. Read `.sneakoscope/wiki/context-pack.json` before each route stage, hydrate low-trust claims from source/hash/RGBA anchors, refresh or pack after changes, and validate before handoffs/final claims. `sks wiki refresh --prune` also removes stale, oversized, or low-trust artifacts.
43
+
44
+ Repeated failures are promoted, not buried. If an issue recurs, SKS can store it under `.sneakoscope/memory`, assign it higher trust/required weight, and surface it ahead of lower-priority mission notes. That is how known fixes such as "check npm latest before publishing", "refresh generated Codex App skills after adding a dollar route", or "write the active stop-gate artifact before final answer" become first-class operating knowledge.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "Sneakoscope Codex",
4
- "version": "0.6.31",
4
+ "version": "0.6.33",
5
5
  "description": "Sneakoscope Codex: update-aware, database-safe Codex CLI harness with multi-agent Team orchestration, Ralph no-question execution, autoresearch-style loops, and H-Proof gates.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -2453,6 +2453,7 @@ async function selftest() {
2453
2453
  if (!evalReport.candidate.wiki?.valid) throw new Error('selftest failed: wiki coordinate index invalid in eval');
2454
2454
  const coord = rgbaToWikiCoord({ r: 12, g: 34, b: 56, a: 255 });
2455
2455
  if (coord.schema !== 'sks.wiki-coordinate.v1' || coord.xyzw.length !== 4) throw new Error('selftest failed: RGBA wiki coordinate conversion');
2456
+ await writeTextAtomic(path.join(tmp, '.sneakoscope', 'memory', 'q2_facts', 'selftest.md'), '- claim: Selftest memory claim must be selected before lower-weight mission notes. | id: selftest-memory-priority | source: src/cli/main.mjs | risk: high | status: supported | evidence_count: 3 | required_weight: 1.0 | trust_score: 0.9\n');
2456
2457
  const wikiPack = contextCapsule({
2457
2458
  mission: { id: 'selftest-wiki', coord: { rgba: { r: 48, g: 132, b: 212, a: 240 } } },
2458
2459
  role: 'verifier',
@@ -2467,6 +2468,7 @@ async function selftest() {
2467
2468
  if (!(wikiPack.wiki.anchors || wikiPack.wiki.a || []).some((anchor) => Array.isArray(anchor) ? Number.isFinite(Number(anchor[9])) : Number.isFinite(Number(anchor.trust_score)))) throw new Error('selftest failed: wiki anchor trust score missing');
2468
2469
  if (!(wikiPack.wiki.anchors || wikiPack.wiki.a || []).some((anchor) => (Array.isArray(anchor) ? anchor[0] : anchor.id) === 'wiki-trig')) throw new Error('selftest failed: wiki trig anchor missing');
2469
2470
  if (!(wikiPack.wiki.anchors || wikiPack.wiki.a || []).some((anchor) => String(Array.isArray(anchor) ? anchor[0] : anchor.id).startsWith('team-analysis-'))) throw new Error('selftest failed: team analysis claim missing from TriWiki pack');
2471
+ if (wikiPack.claims?.[0]?.id !== 'selftest-memory-priority') throw new Error('selftest failed: memory required_weight did not take priority in TriWiki pack');
2470
2472
  const dryRunPack = await writeWikiContextPack(tmp, ['--max-anchors', '4'], { dryRun: true });
2471
2473
  if (await exists(dryRunPack.file)) throw new Error('selftest failed: wiki refresh dry-run wrote context pack');
2472
2474
  await ensureDir(path.dirname(dryRunPack.file));
@@ -2710,10 +2712,136 @@ async function projectWikiClaims(root) {
2710
2712
  evidence_count: await exists(path.join(root, file)) ? 1 : 0
2711
2713
  });
2712
2714
  }
2715
+ out.push(...(await memoryWikiClaims(root)));
2713
2716
  out.push(...(await teamAnalysisWikiClaims(root)));
2714
2717
  return out;
2715
2718
  }
2716
2719
 
2720
+ async function memoryWikiClaims(root) {
2721
+ const base = path.join(root, '.sneakoscope', 'memory');
2722
+ const files = await listMemoryClaimFiles(base);
2723
+ const claims = [];
2724
+ for (const file of files.slice(0, 80)) {
2725
+ const relFile = path.relative(root, file);
2726
+ let text = '';
2727
+ try {
2728
+ text = await fsp.readFile(file, 'utf8');
2729
+ } catch {
2730
+ continue;
2731
+ }
2732
+ if (!text.trim()) continue;
2733
+ const rows = parseMemoryClaimRows(text, relFile).slice(0, 24);
2734
+ let index = 0;
2735
+ for (const row of rows) {
2736
+ const source = row.source || relFile;
2737
+ const sourceExists = source && (await exists(path.join(root, source)));
2738
+ index += 1;
2739
+ claims.push({
2740
+ id: row.id || `memory-${slugifyClaimId(relFile)}-${index}`,
2741
+ text: row.text,
2742
+ authority: row.authority || 'wiki',
2743
+ risk: row.risk || 'high',
2744
+ status: row.status || (sourceExists || source === relFile ? 'supported' : 'unknown'),
2745
+ freshness: row.freshness || 'fresh',
2746
+ source,
2747
+ file: source,
2748
+ evidence_count: row.evidence_count ?? (sourceExists ? 2 : 1),
2749
+ required_weight: row.required_weight ?? 0.85,
2750
+ trust_score: row.trust_score
2751
+ });
2752
+ }
2753
+ }
2754
+ return claims;
2755
+ }
2756
+
2757
+ async function listMemoryClaimFiles(base) {
2758
+ const out = [];
2759
+ async function walk(dir, depth = 0) {
2760
+ if (depth > 3) return;
2761
+ let entries = [];
2762
+ try {
2763
+ entries = await fsp.readdir(dir, { withFileTypes: true });
2764
+ } catch {
2765
+ return;
2766
+ }
2767
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
2768
+ const p = path.join(dir, entry.name);
2769
+ if (entry.isDirectory()) await walk(p, depth + 1);
2770
+ else if (/\.(md|txt|json)$/i.test(entry.name)) out.push(p);
2771
+ }
2772
+ }
2773
+ await walk(base);
2774
+ return out;
2775
+ }
2776
+
2777
+ function parseMemoryClaimRows(text, relFile) {
2778
+ if (/\.json$/i.test(relFile)) {
2779
+ try {
2780
+ const parsed = JSON.parse(text);
2781
+ const rows = Array.isArray(parsed) ? parsed : (Array.isArray(parsed.claims) ? parsed.claims : []);
2782
+ return rows.map((row) => normalizeMemoryClaimRow(row, relFile)).filter(Boolean);
2783
+ } catch {
2784
+ return [];
2785
+ }
2786
+ }
2787
+ return text.split(/\r?\n/)
2788
+ .map((line) => line.trim())
2789
+ .filter((line) => line && !line.startsWith('#'))
2790
+ .map((line) => normalizeMemoryClaimRow(line.replace(/^[-*]\s*/, ''), relFile))
2791
+ .filter(Boolean);
2792
+ }
2793
+
2794
+ function normalizeMemoryClaimRow(row, relFile) {
2795
+ if (!row) return null;
2796
+ if (typeof row === 'object') {
2797
+ const text = String(row.text || row.claim || '').trim();
2798
+ if (!text) return null;
2799
+ return {
2800
+ id: row.id ? String(row.id) : null,
2801
+ text: text.slice(0, 320),
2802
+ source: row.source || row.file || relFile,
2803
+ authority: row.authority,
2804
+ risk: row.risk,
2805
+ status: row.status || row.confidence,
2806
+ freshness: row.freshness,
2807
+ evidence_count: Number.isFinite(Number(row.evidence_count)) ? Number(row.evidence_count) : undefined,
2808
+ required_weight: Number.isFinite(Number(row.required_weight)) ? Number(row.required_weight) : undefined,
2809
+ trust_score: Number.isFinite(Number(row.trust_score)) ? Number(row.trust_score) : undefined
2810
+ };
2811
+ }
2812
+ const clean = String(row || '').trim();
2813
+ if (!/\bclaim\s*:/i.test(clean)) return null;
2814
+ const source = extractClaimField(clean, 'source') || extractClaimField(clean, 'file') || extractClaimField(clean, 'path') || relFile;
2815
+ const status = extractClaimField(clean, 'status') || extractClaimField(clean, 'confidence');
2816
+ return {
2817
+ id: extractClaimField(clean, 'id'),
2818
+ text: clean.slice(0, 320),
2819
+ source,
2820
+ authority: extractClaimField(clean, 'authority') || 'wiki',
2821
+ risk: extractClaimField(clean, 'risk') || 'high',
2822
+ status,
2823
+ freshness: extractClaimField(clean, 'freshness') || 'fresh',
2824
+ evidence_count: parseOptionalNumber(extractClaimField(clean, 'evidence_count')),
2825
+ required_weight: parseOptionalNumber(extractClaimField(clean, 'required_weight')),
2826
+ trust_score: parseOptionalNumber(extractClaimField(clean, 'trust_score'))
2827
+ };
2828
+ }
2829
+
2830
+ function extractClaimField(text, key) {
2831
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2832
+ const match = String(text || '').match(new RegExp(`\\b${escaped}\\s*[:=]\\s*\\\`?([^\\\`|,;]+)`, 'i'));
2833
+ return match ? match[1].trim().replace(/[.;)]$/, '') : null;
2834
+ }
2835
+
2836
+ function parseOptionalNumber(value) {
2837
+ const n = Number(value);
2838
+ return Number.isFinite(n) ? n : undefined;
2839
+ }
2840
+
2841
+ function slugifyClaimId(value) {
2842
+ return String(value || 'claim').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'claim';
2843
+ }
2844
+
2717
2845
  async function teamAnalysisWikiClaims(root) {
2718
2846
  const base = path.join(root, '.sneakoscope', 'missions');
2719
2847
  let entries = [];
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.6.31';
8
+ export const PACKAGE_VERSION = '0.6.33';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
@@ -149,7 +149,7 @@ export function selectClaims(mission, claims, budget = {}) {
149
149
  const selectedIds = new Set();
150
150
  const required = scored
151
151
  .filter((x) => Number(x.claim.required_weight) > 0)
152
- .sort((a, b) => b.score - a.score);
152
+ .sort((a, b) => (Number(b.claim.required_weight) - Number(a.claim.required_weight)) || b.score - a.score);
153
153
  for (const item of required) {
154
154
  if (selected.length >= maxClaims) break;
155
155
  selected.push(item);
@@ -157,7 +157,7 @@ export function selectClaims(mission, claims, budget = {}) {
157
157
  }
158
158
  const fill = topKByScore(scored.filter((x) => !selectedIds.has(x.claim.id)), maxClaims - selected.length);
159
159
  return [...selected, ...fill]
160
- .sort((a, b) => b.score - a.score)
160
+ .sort((a, b) => (Number(b.claim.required_weight || 0) - Number(a.claim.required_weight || 0)) || b.score - a.score)
161
161
  .map((x) => withTrust({ ...x.claim, triwiki_score: Number(x.score.toFixed(4)) }, trustPolicy));
162
162
  }
163
163