sneakoscope 0.6.89 → 0.6.90
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 +1 -1
- package/src/cli/main.mjs +18 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +49 -0
- package/src/core/init.mjs +1 -1
- package/src/core/pipeline.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.90",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
package/src/cli/main.mjs
CHANGED
|
@@ -1924,6 +1924,24 @@ async function selftest() {
|
|
|
1924
1924
|
if (trippedStop) throw new Error('selftest failed: compliance loop guard did not terminally trip');
|
|
1925
1925
|
const loopBlocker = await readJson(path.join(loopMission.dir, 'hard-blocker.json'), null);
|
|
1926
1926
|
if (loopBlocker?.reason !== 'compliance_loop_guard_tripped') throw new Error('selftest failed: compliance loop guard did not write hard blocker');
|
|
1927
|
+
await setCurrent(tmp, loopState);
|
|
1928
|
+
const dfixPromptHook = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'user-prompt-submit'], {
|
|
1929
|
+
cwd: tmp,
|
|
1930
|
+
input: JSON.stringify({ cwd: tmp, prompt: '$DFix Change the CTA label only' }),
|
|
1931
|
+
timeoutMs: 15000,
|
|
1932
|
+
maxOutputBytes: 64 * 1024
|
|
1933
|
+
});
|
|
1934
|
+
if (dfixPromptHook.code !== 0) throw new Error(`selftest failed: DFix prompt hook exited ${dfixPromptHook.code}: ${dfixPromptHook.stderr}`);
|
|
1935
|
+
const dfixStopHook = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'stop'], {
|
|
1936
|
+
cwd: tmp,
|
|
1937
|
+
input: JSON.stringify({ cwd: tmp, last_assistant_message: 'DFix 완료 요약: CTA 라벨만 변경했습니다. 검증: 대상 파일 확인 통과. 남은 문제: 없음.' }),
|
|
1938
|
+
timeoutMs: 15000,
|
|
1939
|
+
maxOutputBytes: 64 * 1024
|
|
1940
|
+
});
|
|
1941
|
+
if (dfixStopHook.code !== 0) throw new Error(`selftest failed: DFix stop hook exited ${dfixStopHook.code}: ${dfixStopHook.stderr}`);
|
|
1942
|
+
const dfixStop = JSON.parse(dfixStopHook.stdout || '{}');
|
|
1943
|
+
if (dfixStop.decision === 'block' || dfixStop.continue === false) throw new Error(`selftest failed: DFix stop hook was blocked: ${dfixStopHook.stdout}`);
|
|
1944
|
+
if (!String(dfixStop.systemMessage || '').includes('DFix ultralight finalization accepted')) throw new Error('selftest failed: DFix stop hook did not use the ultralight finalization bypass');
|
|
1927
1945
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-roster.json'), { schema_version: 1, mission_id: loopMission.id, confirmed: true });
|
|
1928
1946
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-session-cleanup.json'), { schema_version: 1, passed: true, all_sessions_closed: true, outstanding_sessions: 0, live_transcript_finalized: true });
|
|
1929
1947
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-gate.json'), { passed: true, team_roster_confirmed: true, analysis_artifact: true, triwiki_refreshed: true, triwiki_validated: true, consensus_artifact: true, implementation_team_fresh: true, review_artifact: true, integration_evidence: true, session_cleanup: true });
|
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.
|
|
8
|
+
export const PACKAGE_VERSION = '0.6.90';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -12,9 +12,11 @@ const TEAM_DIGEST_MESSAGE_CHARS = 180;
|
|
|
12
12
|
const TEAM_DIGEST_CONTEXT_CHARS = 1600;
|
|
13
13
|
const TEAM_DIGEST_SYSTEM_CHARS = 260;
|
|
14
14
|
const STOP_REPEAT_GUARD_ARTIFACT = 'stop-hook-repeat-guard.json';
|
|
15
|
+
const LIGHT_ROUTE_STOP_ARTIFACT = 'light-route-stop.json';
|
|
15
16
|
const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
16
17
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
17
18
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
19
|
+
const LIGHT_ROUTE_STOP_WINDOW_MS = 10 * 60 * 1000;
|
|
18
20
|
|
|
19
21
|
async function loadHookPayload() {
|
|
20
22
|
const raw = await readStdin();
|
|
@@ -111,6 +113,7 @@ async function hookUserPrompt(root, state, payload, noQuestion) {
|
|
|
111
113
|
const command = dollarCommand(prompt);
|
|
112
114
|
const route = routePrompt(prompt);
|
|
113
115
|
const bypassActiveRoute = route?.id === 'DFix' || route?.id === 'Answer';
|
|
116
|
+
if (route?.id === 'DFix') await recordLightRouteStop(root, route, payload, prompt);
|
|
114
117
|
if (isClarificationAwaiting(state) && !looksLikeClarificationCancel(prompt)) {
|
|
115
118
|
const activeContext = await activeRouteContext(root, state);
|
|
116
119
|
const teamDigest = await teamLiveDigest(root, state);
|
|
@@ -222,6 +225,12 @@ async function hookPermission(root, state, payload, noQuestion) {
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
async function hookStop(root, state, payload, noQuestion) {
|
|
228
|
+
if (!noQuestion && await consumeLightRouteStop(root, payload)) {
|
|
229
|
+
return {
|
|
230
|
+
continue: true,
|
|
231
|
+
systemMessage: 'SKS: DFix ultralight finalization accepted; full-route Honest Mode loopback is not required.'
|
|
232
|
+
};
|
|
233
|
+
}
|
|
225
234
|
const routeDecision = await evaluateStop(root, state, payload, { noQuestion });
|
|
226
235
|
if (routeDecision) return routeDecision;
|
|
227
236
|
if (!noQuestion) {
|
|
@@ -258,6 +267,46 @@ async function hookStop(root, state, payload, noQuestion) {
|
|
|
258
267
|
};
|
|
259
268
|
}
|
|
260
269
|
|
|
270
|
+
async function recordLightRouteStop(root, route = {}, payload = {}, prompt = '') {
|
|
271
|
+
const now = nowIso();
|
|
272
|
+
const expires = new Date(Date.parse(now) + LIGHT_ROUTE_STOP_WINDOW_MS).toISOString();
|
|
273
|
+
const file = path.join(root, '.sneakoscope', 'state', LIGHT_ROUTE_STOP_ARTIFACT);
|
|
274
|
+
await writeJsonAtomic(file, {
|
|
275
|
+
schema_version: 1,
|
|
276
|
+
route: route.id || null,
|
|
277
|
+
route_command: route.command || null,
|
|
278
|
+
mode: route.mode || null,
|
|
279
|
+
conversation_id: conversationId(payload),
|
|
280
|
+
prompt_hash: sha256(String(prompt || '')).slice(0, 16),
|
|
281
|
+
created_at: now,
|
|
282
|
+
expires_at: expires,
|
|
283
|
+
pending_stop_bypass: true,
|
|
284
|
+
stop_policy: 'dfix_ultralight_bypasses_full_route_honest_mode'
|
|
285
|
+
}).catch(() => null);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function consumeLightRouteStop(root, payload = {}) {
|
|
289
|
+
const file = path.join(root, '.sneakoscope', 'state', LIGHT_ROUTE_STOP_ARTIFACT);
|
|
290
|
+
const record = await readJson(file, null).catch(() => null);
|
|
291
|
+
if (!record?.pending_stop_bypass) return false;
|
|
292
|
+
if (record.route !== 'DFix') return false;
|
|
293
|
+
const nowMs = Date.now();
|
|
294
|
+
const expiresMs = Date.parse(record.expires_at || '');
|
|
295
|
+
if (!Number.isFinite(expiresMs) || expiresMs < nowMs) return false;
|
|
296
|
+
const currentConversation = conversationId(payload);
|
|
297
|
+
if (record.conversation_id && explicitConversationId(payload) && record.conversation_id !== currentConversation) return false;
|
|
298
|
+
await writeJsonAtomic(file, {
|
|
299
|
+
...record,
|
|
300
|
+
pending_stop_bypass: false,
|
|
301
|
+
consumed_at: nowIso()
|
|
302
|
+
}).catch(() => null);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function explicitConversationId(payload = {}) {
|
|
307
|
+
return payload.conversation_id || payload.thread_id || payload.session_id || payload.chat_id || null;
|
|
308
|
+
}
|
|
309
|
+
|
|
261
310
|
async function finalizationRepeatDecision(root, state = {}, payload = {}, reason = '', kind = 'finalization') {
|
|
262
311
|
const now = nowIso();
|
|
263
312
|
const guardPath = path.join(root, '.sneakoscope', 'state', STOP_REPEAT_GUARD_ARTIFACT);
|
package/src/core/init.mjs
CHANGED
|
@@ -527,7 +527,7 @@ function codexAppQuickReference(scope, commandPrefix) {
|
|
|
527
527
|
|
|
528
528
|
export async function installSkills(root) {
|
|
529
529
|
const skills = {
|
|
530
|
-
'dfix': `---\nname: dfix\ndescription: Ultralight fast design/content fix mode for $DFix or $dfix requests and inferred simple edits such as text color, copy, labels, spacing, or translation.\n---\n\nUse for tiny copy/color/label/spacing/translation edits. List exact micro-edits, inspect only needed files, apply only those edits, and run cheap verification. Bypass broad SKS routing, Goal, Research, eval, and
|
|
530
|
+
'dfix': `---\nname: dfix\ndescription: Ultralight fast design/content fix mode for $DFix or $dfix requests and inferred simple edits such as text color, copy, labels, spacing, or translation.\n---\n\nUse for tiny copy/color/label/spacing/translation edits. List exact micro-edits, inspect only needed files, apply only those edits, and run cheap verification. Bypass broad SKS routing, Goal, Research, eval, redesign, and repeated full-route Honest Mode loops. Read \`design.md\` for UI work when present; use imagegen for image/logo/raster assets.\n`,
|
|
531
531
|
'answer': `---\nname: answer\ndescription: Answer-only research route for ordinary questions that should not start implementation.\n---\n\nUse for explanations, comparisons, status, facts, source-backed research, or docs guidance. Use repo/TriWiki first for project-local facts; hydrate low-trust claims from source. Browse or use Context7 for current external package/API/framework/MCP docs. End with a concise answer summary plus Honest Mode; do not create missions, subagents, or file edits.\n`,
|
|
532
532
|
'sks': `---\nname: sks\ndescription: General Sneakoscope Codex command route for $SKS or $sks usage, setup, status, and workflow help.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline. Promote code-changing work to Team unless Answer/DFix/Help/Wiki/safety route fits. Surface route/guard/scope, use TriWiki, do not edit installed harness files outside this engine repo, and require human-approved conflict cleanup.\n`,
|
|
533
533
|
'wiki': `---\nname: wiki\ndescription: Dollar-command route for $Wiki TriWiki refresh, pack, validate, and prune commands.\n---\n\nUse for $Wiki or Korean wiki-refresh requests. Refresh/update/갱신: run sks wiki refresh, then validate .sneakoscope/wiki/context-pack.json. Pack: run sks wiki pack, then validate. Prune/clean/정리: use sks wiki refresh --prune, or sks wiki prune --dry-run for inspection. Report claims, anchors, trust, attention.use_first/hydrate_first, validation, and blockers. Do not start ambiguity-gated implementation, subagents, or unrelated work.\n`,
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -81,7 +81,7 @@ export function dfixQuickContext(prompt, route = routePrompt(prompt)) {
|
|
|
81
81
|
'2. Inspect only the files needed to locate that target.',
|
|
82
82
|
'3. Apply only the listed design/content edit; for UI/UX micro-edits read design.md when present, and use imagegen for any image/logo/raster asset.',
|
|
83
83
|
'4. Run only cheap verification when useful, such as syntax check, focused test, or local render smoke.',
|
|
84
|
-
'5. Final response: one short completion summary explaining what changed, plus verification or the exact blocker.'
|
|
84
|
+
'5. Final response: one short DFix completion summary explaining what changed, plus cheap verification or the exact blocker. Do not enter repeated full-route Honest Mode loops.'
|
|
85
85
|
].join('\n');
|
|
86
86
|
}
|
|
87
87
|
|