sneakoscope 0.6.43 → 0.6.51

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
@@ -1,14 +1,12 @@
1
1
  # Sneakoscope Codex
2
2
 
3
- Codex CLI/App harness for `$` routing, Team/Ralph/QA/Research, Context7, Honest Mode, DB safety, and TriWiki.
3
+ ![](https://github.com/mandarange/Sneakoscope-Codex/raw/dev/docs/assets/sneakoscope-codex-logo.png)
4
+
5
+ Codex CLI/App harness for `$` routes, Team/Ralph/QA/Research, Context7, Honest Mode, DB safety, and TriWiki.
4
6
 
5
7
  Install: `npm i -g sneakoscope && sks bootstrap`
6
8
  Fallback: `npx -y -p sneakoscope sks bootstrap`
7
9
  Project: `npm i -D sneakoscope && npx sks setup --install-scope project`
8
10
 
9
- Discover: `sks commands`, `sks dollar-commands`, `sks usage <topic>`.
10
- Check: `sks deps check`, `sks doctor --fix`, `sks selftest --mock`.
11
-
12
- QA-LOOP reports: `YYYY-MM-DD-v<version>-qa-report.md`.
13
-
14
- Team: scouts -> TriWiki -> debate -> fresh executors -> review -> cleanup -> reflection -> Honest.
11
+ Discover: `sks commands`, `sks dollar-commands`, `sks usage <topic>`
12
+ Check: `sks deps check`, `sks doctor --fix`, `sks selftest --mock`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.6.43",
4
+ "version": "0.6.51",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Ralph, 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
@@ -25,7 +25,7 @@ import { DEFAULT_EVAL_THRESHOLDS, compareEvaluationReports, defaultEvaluationSce
25
25
  import { buildResearchPrompt, evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
26
26
  import { contextCapsule } from '../core/triwiki-attention.mjs';
27
27
  import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
28
- import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, looksLikeAnswerOnlyRequest, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, triwikiContextTracking } from '../core/routes.mjs';
28
+ import { COMMAND_CATALOG, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, looksLikeAnswerOnlyRequest, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
29
29
  import { context7Evidence, evaluateStop, recordContext7Evidence, recordSubagentEvidence } from '../core/pipeline.mjs';
30
30
  import { appendTeamEvent, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail } from '../core/team-live.mjs';
31
31
  import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
@@ -483,7 +483,7 @@ async function updateCheck(args = []) {
483
483
  if (result.update_available) console.log('Run: npm i -g sneakoscope');
484
484
  }
485
485
 
486
- const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, implementation/code-changing -> $Team parallel orchestration.';
486
+ const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: questions -> $Answer, small design/content -> $DFix, code -> $Team. Use $From-Chat-IMG only for chat screenshot plus original attachments.';
487
487
 
488
488
  function commands(args = []) {
489
489
  if (flag(args, '--json')) return console.log(JSON.stringify({ aliases: ['sks', 'sneakoscope'], dollar_commands: DOLLAR_COMMANDS, app_skill_aliases: DOLLAR_COMMAND_ALIASES, commands: COMMAND_CATALOG }, null, 2));
@@ -755,6 +755,7 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
755
755
  roster: spec.roster
756
756
  });
757
757
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
758
+ await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: spec.roleCounts, agent_sessions: spec.agentSessions, bundle_size: spec.roster.bundle_size, roster: spec.roster, confirmed: true, source: 'default_or_prompt_team_spec' });
758
759
  await writeTextAtomic(path.join(dir, 'team-workflow.md'), teamWorkflowMarkdown(plan));
759
760
  await initTeamLive(id, dir, prompt, {
760
761
  agentSessions: spec.agentSessions,
@@ -787,6 +788,7 @@ async function materializeAfterPipelineAnswer(root, id, dir, mission, route, rou
787
788
  state: {
788
789
  agent_sessions: spec.agentSessions,
789
790
  role_counts: spec.roleCounts,
791
+ team_roster_confirmed: true,
790
792
  team_plan_ready: true,
791
793
  team_live_ready: true
792
794
  }
@@ -2094,7 +2096,7 @@ async function selftest() {
2094
2096
  await writeTextAtomic(path.join(repairTmp, '.codex', 'skills', 'team', 'SKILL.md'), 'legacy mirror\n');
2095
2097
  await initProject(repairTmp, { force: true, repair: true });
2096
2098
  const repairedTeamSkill = await safeReadText(path.join(repairTmp, '.agents', 'skills', 'team', 'SKILL.md'));
2097
- if (!repairedTeamSkill.includes('SKS Team multi-agent orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest failed: doctor repair did not regenerate team skill');
2099
+ if (!repairedTeamSkill.includes('SKS Team orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest failed: doctor repair did not regenerate team skill');
2098
2100
  if (await exists(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove deprecated agent-team alias skill');
2099
2101
  if (!(await exists(path.join(repairTmp, '.agents', 'skills', 'custom-keep', 'SKILL.md')))) throw new Error('selftest failed: doctor repair removed a user-owned custom skill');
2100
2102
  if (await exists(path.join(repairTmp, '.codex', 'skills', 'team', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove legacy .codex/skills');
@@ -2270,6 +2272,9 @@ async function selftest() {
2270
2272
  if (!promptPipelineText.includes('TriWiki context-tracking SSOT')) throw new Error('selftest failed: prompt pipeline missing TriWiki context-tracking SSOT');
2271
2273
  if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
2272
2274
  if (!promptPipelineText.includes('design.md') || !promptPipelineText.includes('imagegen')) throw new Error('selftest failed: prompt pipeline missing design/image asset routing');
2275
+ if (!promptPipelineText.includes('From-Chat-IMG') || !promptPipelineText.includes('Do not assume ordinary image prompts are chat captures')) throw new Error('selftest failed: prompt pipeline missing explicit From-Chat-IMG gating');
2276
+ const fromChatImgSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'from-chat-img', 'SKILL.md'));
2277
+ if (!fromChatImgSkillText.includes('normal Team pipeline') || !fromChatImgSkillText.includes('Computer Use/browser visual inspection')) throw new Error('selftest failed: from-chat-img skill missing Team/browser inspection guidance');
2273
2278
  for (const supportSkill of ['reasoning-router', 'pipeline-runner', 'context7-docs', 'seo-geo-optimizer', 'reflection', 'design-system-builder', 'design-ui-editor', 'imagegen']) {
2274
2279
  if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
2275
2280
  }
@@ -2284,6 +2289,8 @@ async function selftest() {
2284
2289
  if (camelHookGuardJson.decision !== 'block') throw new Error('selftest failed: hook did not block camelCase Codex tool payload');
2285
2290
  if (new Set(DOLLAR_COMMANDS.map((c) => c.command)).size !== DOLLAR_COMMANDS.length) throw new Error('selftest failed: duplicate dollar commands');
2286
2291
  if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$QA-LOOP' && alias.app_skill === '$qa-loop')) throw new Error('selftest failed: $QA-LOOP picker skill missing');
2292
+ if (!DOLLAR_COMMAND_ALIASES.some((alias) => alias.canonical === '$Team' && alias.app_skill === '$from-chat-img')) throw new Error('selftest failed: $From-Chat-IMG picker skill missing');
2293
+ if (!DOLLAR_COMMANDS.some((entry) => entry.command === '$From-Chat-IMG')) throw new Error('selftest failed: $From-Chat-IMG missing from dollar command list');
2287
2294
  if (DOLLAR_COMMAND_ALIASES.some((alias) => ['$agent-team', '$qaloop', '$wiki-refresh', '$wikirefresh'].includes(alias.app_skill))) throw new Error('selftest failed: duplicate picker aliases still present');
2288
2295
  if (routePrompt('$agent-team run specialists')) throw new Error('selftest failed: deprecated $agent-team route still resolved');
2289
2296
  if (routePrompt('$QA-LOOP run UI E2E')?.id !== 'QALoop' || routePrompt('$QALoop deployed smoke')) throw new Error('selftest failed: QA-LOOP route is not standardized to $QA-LOOP');
@@ -2293,7 +2300,11 @@ async function selftest() {
2293
2300
  if (routePrompt(koreanReadmeInstallPrompt)?.id !== 'Team') throw new Error('selftest failed: Korean README implementation prompt did not route to Team by default');
2294
2301
  if (looksLikeAnswerOnlyRequest(koreanReadmeInstallPrompt)) throw new Error('selftest failed: Korean README implementation prompt still looked answer-only');
2295
2302
  if (routePrompt('왜 팀 커맨드 없어졌어 병렬처리까지 제대로 작업해줘')?.id !== 'Team') throw new Error('selftest failed: Korean Team/parallel implementation prompt did not route to Team');
2303
+ if (routePrompt('$From-Chat-IMG 채팅내역 이미지와 첨부 원본 이미지로 수정 작업 지시서 작성')?.id !== 'Team') throw new Error('selftest failed: $From-Chat-IMG did not route to Team');
2304
+ if (routePrompt('From-Chat-IMG 채팅내역 이미지와 원본 첨부 이미지 분석해서 작업 지시서 만들어줘')?.id !== 'Team') throw new Error('selftest failed: bare From-Chat-IMG signal did not route to Team');
2305
+ if (routePrompt('채팅 이미지랑 첨부 이미지 분석 방식 설명해줘')?.id === 'Team') throw new Error('selftest failed: ordinary chat-image question activated Team without From-Chat-IMG');
2296
2306
  if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Team')) throw new Error('selftest failed: dollar-commands missing Team default routing guidance');
2307
+ if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$From-Chat-IMG')) throw new Error('selftest failed: dollar-commands missing From-Chat-IMG guidance');
2297
2308
  if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop')) throw new Error('selftest failed: context7/pipeline/qa-loop commands missing from catalog');
2298
2309
  const registryDollarCommands = DOLLAR_COMMANDS.map((c) => c.command);
2299
2310
  const manifest = await readJson(path.join(tmp, '.sneakoscope', 'manifest.json'));
@@ -2386,23 +2397,39 @@ async function selftest() {
2386
2397
  const { id: honestLoopId, dir: honestLoopDir } = await createMission(honestLoopTmp, { mode: 'sks', prompt: 'honest loopback selftest' });
2387
2398
  await writeJsonAtomic(path.join(honestLoopDir, 'decision-contract.json'), { sealed_hash: 'selftest', answers: { GOAL_PRECISE: 'selftest' } });
2388
2399
  await setCurrent(honestLoopTmp, { mission_id: honestLoopId, route: 'SKS', route_command: '$SKS', mode: 'SKS', phase: 'SKS_CLARIFICATION_CONTRACT_SEALED', implementation_allowed: true, clarification_required: false, ambiguity_gate_passed: true, stop_gate: 'honest_mode' });
2389
- const honestLoopResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**솔직모드**\n검증: selftest ran\n남은 gap: CHANGELOG.md 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2400
+ const honestLoopResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**작업 요약**\nSelftest 경로의 Honest Mode loopback 동작을 검증했습니다.\n**솔직모드**\n검증: selftest ran\n남은 gap: CHANGELOG.md 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2390
2401
  if (honestLoopResult.code !== 0) throw new Error(`selftest failed: honest loopback hook exited ${honestLoopResult.code}: ${honestLoopResult.stderr}`);
2391
2402
  const honestLoopJson = JSON.parse(honestLoopResult.stdout);
2392
2403
  if (honestLoopJson.decision !== 'block' || !String(honestLoopJson.reason || '').includes('post-ambiguity execution phase')) throw new Error('selftest failed: Honest Mode gap did not trigger loopback');
2393
2404
  const honestLoopState = await readJson(stateFile(honestLoopTmp), {});
2394
2405
  if (honestLoopState.phase !== 'SKS_HONEST_LOOPBACK_AFTER_CLARIFICATION' || honestLoopState.implementation_allowed !== true || honestLoopState.clarification_required !== false || honestLoopState.ambiguity_gate_passed !== true) throw new Error('selftest failed: honest loopback did not preserve post-ambiguity execution state');
2395
2406
  if (!(await exists(path.join(honestLoopDir, 'honest-loopback.json')))) throw new Error('selftest failed: honest-loopback artifact missing');
2396
- const honestCleanResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**솔직모드**\n검증: CHANGELOG.md check and selftest passed\n남은 gap: 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2407
+ const honestCleanResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**작업 요약**\nCHANGELOG 확인과 selftest 통과 상태로 loopback을 닫았습니다.\n**솔직모드**\n검증: CHANGELOG.md check and selftest passed\n남은 gap: 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2397
2408
  if (honestCleanResult.code !== 0) throw new Error(`selftest failed: clean honest hook exited ${honestCleanResult.code}: ${honestCleanResult.stderr}`);
2398
2409
  const honestCleanJson = JSON.parse(honestCleanResult.stdout);
2399
2410
  if (honestCleanJson.decision === 'block') throw new Error('selftest failed: clean Honest Mode was blocked after loopback was resolved');
2400
2411
  const honestCleanState = await readJson(stateFile(honestLoopTmp), {});
2401
2412
  if (honestCleanState.honest_loop_required !== false || honestCleanState.phase !== 'SKS_HONEST_COMPLETE') throw new Error('selftest failed: honest loopback was not marked resolved');
2402
- const honestBlockedAsExpectedResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**솔직모드**\n검증: selftest 통과, legacy `qa-report.md` 차단 확인\n제약: registry publish excluded' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2413
+ const honestMissingSummaryResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**솔직모드**\n검증: selftest 통과\n남은 gap: 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2414
+ if (honestMissingSummaryResult.code !== 0) throw new Error(`selftest failed: missing-summary honest hook exited ${honestMissingSummaryResult.code}: ${honestMissingSummaryResult.stderr}`);
2415
+ const honestMissingSummaryJson = JSON.parse(honestMissingSummaryResult.stdout);
2416
+ if (honestMissingSummaryJson.decision !== 'block' || !String(honestMissingSummaryJson.reason || '').includes('completion summary')) throw new Error('selftest failed: Honest Mode without completion summary was accepted');
2417
+ const honestBlockedAsExpectedResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**작업 요약**\nlegacy QA report 차단 확인을 검증했습니다.\n**솔직모드**\n검증: selftest 통과, legacy `qa-report.md` 차단 확인\n제약: registry publish excluded' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2403
2418
  if (honestBlockedAsExpectedResult.code !== 0) throw new Error(`selftest failed: blocked-as-expected honest hook exited ${honestBlockedAsExpectedResult.code}: ${honestBlockedAsExpectedResult.stderr}`);
2404
2419
  const honestBlockedAsExpectedJson = JSON.parse(honestBlockedAsExpectedResult.stdout);
2405
2420
  if (honestBlockedAsExpectedJson.decision === 'block') throw new Error('selftest failed: blocked-as-expected evidence was treated as an unresolved gap');
2421
+ const honestNoActiveGateResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**Completion Summary**\nWhat changed: verified route-gate closure evidence handling.\n**SKS Honest Mode**\nVerified: pipeline status returned `No active blocking route gate detected`; post-reflection work blocking was verified by selftest.\nRemaining gaps: none' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2422
+ if (honestNoActiveGateResult.code !== 0) throw new Error(`selftest failed: no-active-gate honest hook exited ${honestNoActiveGateResult.code}: ${honestNoActiveGateResult.stderr}`);
2423
+ const honestNoActiveGateJson = JSON.parse(honestNoActiveGateResult.stdout);
2424
+ if (honestNoActiveGateJson.decision === 'block') throw new Error('selftest failed: no-active-blocking status was treated as an unresolved gap');
2425
+ const honestNotBlockerResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**Completion Summary**\nWhat changed: verified non-blocker wording in final closeout.\n**SKS Honest Mode**\nVerified: selftest passed.\nRemaining gaps: none. Unrelated dirty worktree entries are not a blocker for this scoped task.' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2426
+ if (honestNotBlockerResult.code !== 0) throw new Error(`selftest failed: not-blocker honest hook exited ${honestNotBlockerResult.code}: ${honestNotBlockerResult.stderr}`);
2427
+ const honestNotBlockerJson = JSON.parse(honestNotBlockerResult.stdout);
2428
+ if (honestNotBlockerJson.decision === 'block') throw new Error('selftest failed: non-blocker boundary wording was treated as unresolved gap');
2429
+ const honestSummaryCaseResult = await runProcess(process.execPath, [hookBin, 'hook', 'stop'], { cwd: honestLoopTmp, input: JSON.stringify({ cwd: honestLoopTmp, last_assistant_message: '**작업 요약**\n[src/cli/main.mjs]: selftest에 요약 없으면 차단, 요약 있으면 통과 케이스 추가.\n**솔직모드**\n검증: selftest 통과.\n남은 gap: 없음' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2430
+ if (honestSummaryCaseResult.code !== 0) throw new Error(`selftest failed: summary-case honest hook exited ${honestSummaryCaseResult.code}: ${honestSummaryCaseResult.stderr}`);
2431
+ const honestSummaryCaseJson = JSON.parse(honestSummaryCaseResult.stdout);
2432
+ if (honestSummaryCaseJson.decision === 'block') throw new Error('selftest failed: summary block/pass wording was treated as unresolved gap');
2406
2433
  const hookQaTmp = tmpdir();
2407
2434
  await initProject(hookQaTmp, {});
2408
2435
  const hookQaPayload = JSON.stringify({ cwd: hookQaTmp, prompt: '$QA-LOOP run UI and API E2E against local dev' });
@@ -2519,7 +2546,7 @@ async function selftest() {
2519
2546
  await writeTextAtomic(mockContext7Path, `process.stdin.setEncoding('utf8');\nlet buf='';\nfunction send(id,result){process.stdout.write(JSON.stringify({jsonrpc:'2.0',id,result})+'\\n');}\nprocess.stdin.on('data',(chunk)=>{buf+=chunk;for(;;){const i=buf.indexOf('\\n');if(i<0)break;const line=buf.slice(0,i).trim();buf=buf.slice(i+1);if(!line)continue;const msg=JSON.parse(line);if(!msg.id)continue;if(msg.method==='initialize')send(msg.id,{protocolVersion:'2024-11-05',capabilities:{tools:{}},serverInfo:{name:'Mock Context7',version:'0.0.0'}});else if(msg.method==='tools/list')send(msg.id,{tools:[{name:'resolve-library-id'},{name:'query-docs'}]});else if(msg.method==='tools/call'&&msg.params.name==='resolve-library-id')send(msg.id,{content:[{type:'text',text:'Context7-compatible library ID: /mock/lib'}]});else if(msg.method==='tools/call'&&msg.params.name==='query-docs')send(msg.id,{content:[{type:'text',text:'mock docs for '+msg.params.arguments.libraryId}]});else send(msg.id,{content:[{type:'text',text:'unknown'}],isError:true});}});\n`);
2520
2547
  const mockContext7Docs = await context7Docs('Mock Lib', { command: process.execPath, args: [mockContext7Path], query: 'hooks', timeoutMs: 5000 });
2521
2548
  if (!mockContext7Docs.ok || mockContext7Docs.docs_tool !== 'query-docs' || mockContext7Docs.library_id !== '/mock/lib') throw new Error('selftest failed: local Context7 MCP client did not resolve/query docs');
2522
- const passedTeamGate = { passed: 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 };
2549
+ const passedTeamGate = { passed: true, analysis_artifact: true, triwiki_refreshed: true, triwiki_validated: true, consensus_artifact: true, team_roster_confirmed: true, implementation_team_fresh: true, review_artifact: true, integration_evidence: true, session_cleanup: true };
2523
2550
  const passedTeamSessionCleanup = { schema_version: 1, passed: true, all_sessions_closed: true, outstanding_sessions: 0, live_transcript_finalized: true, closed_at: nowIso() };
2524
2551
  const incompleteTeamGateTmp = tmpdir();
2525
2552
  await initProject(incompleteTeamGateTmp, {});
@@ -2532,11 +2559,20 @@ async function selftest() {
2532
2559
  const routeGateTmp = tmpdir();
2533
2560
  await initProject(routeGateTmp, {});
2534
2561
  const { id: gateId, dir: gateDir } = await createMission(routeGateTmp, { mode: 'team', prompt: 'Context7 gate test' });
2562
+ await writeJsonAtomic(path.join(gateDir, 'team-roster.json'), { schema_version: 1, mission_id: gateId, confirmed: true, source: 'selftest' });
2535
2563
  await writeJsonAtomic(path.join(gateDir, 'team-gate.json'), passedTeamGate);
2536
2564
  await setCurrent(routeGateTmp, { mission_id: gateId, mode: 'TEAM', route: 'Team', route_command: '$Team', phase: 'TEAM_REVIEW', context7_required: true, stop_gate: 'team-gate.json' });
2537
2565
  const gateState = await readJson(stateFile(routeGateTmp), {});
2538
2566
  const missingC7Stop = await evaluateStop(routeGateTmp, gateState, { last_assistant_message: 'SKS Honest Mode verification evidence gap' }, { noQuestion: false });
2539
2567
  if (missingC7Stop?.decision !== 'block' || !String(missingC7Stop.reason || '').includes('Context7')) throw new Error('selftest failed: Stop hook did not block missing Context7 evidence');
2568
+ const rosterArtifactGateTmp = tmpdir();
2569
+ await initProject(rosterArtifactGateTmp, {});
2570
+ const { id: rosterArtifactGateId, dir: rosterArtifactGateDir } = await createMission(rosterArtifactGateTmp, { mode: 'team', prompt: 'team roster artifact gate test' });
2571
+ await writeJsonAtomic(path.join(rosterArtifactGateDir, 'team-gate.json'), { ...passedTeamGate, session_cleanup: false });
2572
+ await setCurrent(rosterArtifactGateTmp, { mission_id: rosterArtifactGateId, mode: 'TEAM', route: 'Team', route_command: '$Team', phase: 'TEAM_REVIEW', context7_required: false, stop_gate: 'team-gate.json' });
2573
+ const rosterArtifactGateState = await readJson(stateFile(rosterArtifactGateTmp), {});
2574
+ const missingRosterArtifactStop = await evaluateStop(rosterArtifactGateTmp, rosterArtifactGateState, { last_assistant_message: 'SKS Honest Mode verification evidence gap' }, { noQuestion: false });
2575
+ if (missingRosterArtifactStop?.decision !== 'block' || !String(missingRosterArtifactStop.reason || '').includes('team-roster.json')) throw new Error('selftest failed: Team gate did not block missing team roster artifact');
2540
2576
  await recordContext7Evidence(routeGateTmp, gateState, { tool_name: 'resolve-library-id', library: 'react' });
2541
2577
  const resolveOnlyStop = await evaluateStop(routeGateTmp, gateState, { last_assistant_message: 'SKS Honest Mode verification evidence gap' }, { noQuestion: false });
2542
2578
  if (resolveOnlyStop?.decision !== 'block') throw new Error('selftest failed: resolve-only Context7 evidence unblocked route');
@@ -2552,9 +2588,13 @@ async function selftest() {
2552
2588
  await writeJsonAtomic(path.join(gateDir, REFLECTION_GATE), { schema_version: 1, passed: true, mission_id: gateId, route: '$Team', reflection_artifact: true, lessons_recorded: false, no_issue_acknowledged: true, triwiki_recorded: false, wiki_refreshed_or_packed: true, wiki_validated: true, created_at: nowIso() });
2553
2589
  const c7Unblocked = await evaluateStop(routeGateTmp, gateState, { last_assistant_message: 'SKS Honest Mode verification evidence gap' }, { noQuestion: false });
2554
2590
  if (c7Unblocked?.decision === 'block') throw new Error('selftest failed: full Context7 evidence did not unblock route gate');
2591
+ await appendJsonlBounded(path.join(gateDir, 'team-transcript.jsonl'), { ts: new Date(Date.now() + 5000).toISOString(), agent: 'parent_orchestrator', phase: 'IMPLEMENTATION', type: 'status', message: 'work after reflection selftest' });
2592
+ const staleReflectionStop = await evaluateStop(routeGateTmp, gateState, { last_assistant_message: 'SKS Honest Mode verification evidence gap' }, { noQuestion: false });
2593
+ if (staleReflectionStop?.decision !== 'block' || !String(staleReflectionStop.reason || '').includes('work_after_reflection')) throw new Error('selftest failed: post-reflection work did not stale the reflection gate');
2555
2594
  const subagentGateTmp = tmpdir();
2556
2595
  await initProject(subagentGateTmp, {});
2557
2596
  const { id: subagentGateId, dir: subagentGateDir } = await createMission(subagentGateTmp, { mode: 'team', prompt: 'subagent evidence gate test' });
2597
+ await writeJsonAtomic(path.join(subagentGateDir, 'team-roster.json'), { schema_version: 1, mission_id: subagentGateId, confirmed: true, source: 'selftest' });
2558
2598
  await writeJsonAtomic(path.join(subagentGateDir, 'team-gate.json'), passedTeamGate);
2559
2599
  await setCurrent(subagentGateTmp, { mission_id: subagentGateId, mode: 'TEAM', route: 'Team', route_command: '$Team', phase: 'TEAM_REVIEW', context7_required: false, subagents_required: true, stop_gate: 'team-gate.json' });
2560
2600
  const subagentGateState = await readJson(stateFile(subagentGateTmp), {});
@@ -2571,12 +2611,13 @@ async function selftest() {
2571
2611
  await writeJsonAtomic(path.join(teamDir, 'team-plan.json'), teamPlan);
2572
2612
  if (teamPlan.agent_session_count !== 3) throw new Error('selftest failed: team default sessions not 3');
2573
2613
  if (teamPlan.role_counts.executor !== 3 || teamPlan.role_counts.user !== 1 || teamPlan.role_counts.reviewer !== 1) throw new Error('selftest failed: team default role counts invalid');
2574
- if (teamPlan.phases[0]?.id !== 'parallel_analysis_scouting' || teamPlan.phases[1]?.id !== 'triwiki_refresh') throw new Error('selftest failed: team plan is not scout-first');
2614
+ if (teamPlan.phases[0]?.id !== 'team_roster_confirmation' || teamPlan.phases[1]?.id !== 'parallel_analysis_scouting' || teamPlan.phases[2]?.id !== 'triwiki_refresh') throw new Error('selftest failed: team plan is not roster-first then scout-first');
2575
2615
  if (teamPlan.roster.debate_team.length !== 3 || !teamPlan.roster.debate_team.some((agent) => agent.id === 'debate_user_1') || !teamPlan.roster.development_team.some((agent) => agent.id === 'executor_3')) throw new Error('selftest failed: team roster missing default agents');
2576
2616
  if (teamPlan.roster.analysis_team.length !== teamPlan.role_counts.executor || !teamPlan.roster.analysis_team.some((agent) => agent.id === 'analysis_scout_3')) throw new Error('selftest failed: team analysis scout roster missing default agents');
2577
- if (!teamPlan.required_artifacts.includes('team-analysis.md') || !teamPlan.required_artifacts.includes(TEAM_SESSION_CLEANUP_ARTIFACT)) throw new Error('selftest failed: team plan missing required artifacts');
2617
+ if (!teamPlan.required_artifacts.includes('team-roster.json') || !teamPlan.required_artifacts.includes('team-analysis.md') || !teamPlan.required_artifacts.includes(TEAM_SESSION_CLEANUP_ARTIFACT)) throw new Error('selftest failed: team plan missing required artifacts');
2578
2618
  if (teamPlan.context_tracking?.ssot !== 'triwiki' || !teamPlan.required_artifacts.includes('.sneakoscope/wiki/context-pack.json')) throw new Error('selftest failed: team plan missing TriWiki context tracking');
2579
2619
  if (!teamPlan.context_tracking?.stage_policy?.includes('before_each_route_stage_read_relevant_context_pack')) throw new Error('selftest failed: team plan missing per-stage TriWiki policy');
2620
+ if (!teamPlan.invariants.some((item) => item.includes('chat-history screenshots'))) throw new Error('selftest failed: team invariants missing chat capture matching');
2580
2621
  if (!teamPlan.phases.some((phase) => String(phase.goal || '').includes('refreshes/validates TriWiki before implementation handoff'))) throw new Error('selftest failed: team plan missing mid-pipeline TriWiki refresh');
2581
2622
  const teamWorkflow = teamWorkflowMarkdown(teamPlan);
2582
2623
  if (!teamWorkflow.includes('SSOT: triwiki') || !teamWorkflow.includes('Analysis Scouts') || !teamWorkflow.includes('sks wiki validate')) throw new Error('selftest failed: team workflow missing scout-first TriWiki context tracking');
@@ -2600,9 +2641,12 @@ async function selftest() {
2600
2641
  if (routeReasoning(routePrompt('$DFix button label'), '$DFix button label').effort !== 'medium') throw new Error('selftest failed: simple reasoning not medium');
2601
2642
  if (routePrompt('이 파이프라인은 왜 이렇게 동작해?')?.id !== 'Answer') throw new Error('selftest failed: question prompt did not route to Answer');
2602
2643
  if (routePrompt('React useEffect 최신 문서 기준으로 설명해줘')?.id !== 'Answer') throw new Error('selftest failed: docs question did not route to Answer');
2644
+ if (routePrompt('질문을 하더라도 진짜 질문인지 아니면 질문형태를 띄는 암묵적인 지시인지를 반드시 파악해야해')?.id !== 'Team') throw new Error('selftest failed: question-shaped directive did not route to Team');
2645
+ if (routePrompt('근데 왜 팀원 구성을 안하고 작업을 하는 경우가 이렇게 많지?')?.id !== 'Team') throw new Error('selftest failed: question-shaped Team complaint did not route to Team');
2603
2646
  if (routePrompt('$DF button label')) throw new Error('selftest failed: deprecated $DF route still resolved');
2604
2647
  if (routePrompt('implement feature')?.id !== 'Team') throw new Error('selftest failed: implementation prompt did not default to Team');
2605
2648
  if (routePrompt('$SKS implement feature')?.id !== 'Team') throw new Error('selftest failed: $SKS implementation prompt did not promote to Team');
2649
+ if (routePrompt('$From-Chat-IMG 채팅 기록 이미지와 첨부 이미지로 고객사 요청 수정 작업 수행해줘')?.id !== 'Team') throw new Error('selftest failed: explicit chat capture client work did not promote to Team');
2606
2650
  if (routePrompt('$SKS show me available workflows')?.id !== 'SKS') throw new Error('selftest failed: $SKS workflow discovery should remain SKS');
2607
2651
  if (routeRequiresSubagents(routePrompt('이 파이프라인은 왜 이렇게 동작해?'), '이 파이프라인은 왜 이렇게 동작해?')) throw new Error('selftest failed: Answer route requires subagents');
2608
2652
  if (!routeRequiresSubagents(routePrompt('implement feature'), 'implement feature')) throw new Error('selftest failed: default Team implementation route does not require subagents');
@@ -2673,7 +2717,8 @@ async function selftest() {
2673
2717
  if (wikiPack.wiki.vx?.s !== 'sks.wiki-voxel.v1' || wikiVoxelRowCount(wikiPack.wiki) < 1) throw new Error('selftest failed: wiki voxel overlay missing');
2674
2718
  const legacyWiki = { ...wikiPack.wiki };
2675
2719
  delete legacyWiki.vx;
2676
- if (!validateWikiCoordinateIndex(legacyWiki).ok) throw new Error('selftest failed: legacy v1 wiki pack no longer validates');
2720
+ const legacyValidation = validateWikiCoordinateIndex(legacyWiki);
2721
+ if (legacyValidation.ok || !legacyValidation.issues.some((issue) => issue.id === 'vx_missing')) throw new Error('selftest failed: legacy coordinate-only wiki pack was accepted');
2677
2722
  if (!wikiPack.trust_summary || !Number.isFinite(Number(wikiPack.trust_summary.needs_evidence))) throw new Error('selftest failed: wiki trust summary missing');
2678
2723
  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');
2679
2724
  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');
@@ -2681,6 +2726,8 @@ async function selftest() {
2681
2726
  if (!wikiPack.claims?.some((claim) => String(claim.id).startsWith('user-request-frequency-'))) throw new Error('selftest failed: repeated user request frequency claim missing from TriWiki pack');
2682
2727
  if (!wikiPack.claims?.some((claim) => String(claim.id).startsWith('user-strong-feedback-'))) throw new Error('selftest failed: strong user feedback claim missing from TriWiki pack');
2683
2728
  if (!wikiPack.claims?.some((claim) => claim.id === 'selftest-memory-priority')) throw new Error('selftest failed: memory required_weight claim was not selected in TriWiki pack');
2729
+ if (!wikiPack.claims?.some((claim) => claim.id === 'wiki-stack-current-docs-policy')) throw new Error('selftest failed: stack current-docs policy claim missing from TriWiki pack');
2730
+ if (!wikiPack.claims?.some((claim) => claim.id === 'wiki-stack-current-docs-vercel-duration')) throw new Error('selftest failed: Vercel duration current-docs claim missing from TriWiki pack');
2684
2731
  const dryRunPack = await writeWikiContextPack(tmp, ['--max-anchors', '4'], { dryRun: true });
2685
2732
  if (wikiVoxelRowCount(dryRunPack.pack.wiki) !== 4) throw new Error('selftest failed: dry-run wiki pack did not build voxel rows');
2686
2733
  if (await exists(dryRunPack.file)) throw new Error('selftest failed: wiki refresh dry-run wrote context pack');
@@ -2901,7 +2948,7 @@ function printWikiPackSummary(root, file, pack) {
2901
2948
  console.log(`Voxels: ${wikiVoxelRowCount(pack.wiki)} metadata rows (${pack.wiki.vx?.s || pack.wiki.vx?.schema || 'none'})`);
2902
2949
  console.log(`Schema: ${pack.wiki.schema}`);
2903
2950
  console.log(`Trust: avg=${pack.trust_summary.avg} needs_evidence=${pack.trust_summary.needs_evidence}`);
2904
- console.log('Guidance: follow high-trust claims; hydrate source/evidence before relying on lower-trust claims.');
2951
+ console.log('Guidance: follow high-trust claims; hydrate source/evidence before relying on lower-trust claims. Stack/version changes require current Context7 or official-doc TriWiki claims before coding.');
2905
2952
  console.log(`Validate: sks wiki validate ${path.relative(root, file)}`);
2906
2953
  }
2907
2954
 
@@ -2940,6 +2987,47 @@ async function projectWikiClaims(root) {
2940
2987
  evidence_count: await exists(path.join(root, file)) ? 1 : 0
2941
2988
  });
2942
2989
  }
2990
+
2991
+ const stackPolicy = stackCurrentDocsPolicy();
2992
+ out.push({
2993
+ id: 'wiki-stack-current-docs-policy',
2994
+ text: `When project tech stack, framework, package, runtime, SDK, MCP, or deployment-platform versions change, use Context7 or official vendor docs, write current syntax/security/limit guidance to ${stackPolicy.memory_path}, refresh TriWiki, validate it, and prefer those claims over stale model defaults before coding.`,
2995
+ authority: 'contract',
2996
+ risk: 'critical',
2997
+ status: 'supported',
2998
+ freshness: 'fresh',
2999
+ source: 'src/core/routes.mjs',
3000
+ file: 'src/core/routes.mjs',
3001
+ evidence_count: 3,
3002
+ required_weight: 1.35,
3003
+ trust_score: 0.95
3004
+ });
3005
+ out.push({
3006
+ id: 'wiki-stack-current-docs-examples',
3007
+ text: `Current-doc examples that belong in TriWiki when relevant: Supabase hosted keys prefer sb_publishable_/sb_secret_ over legacy anon/service_role defaults, Next.js 16 uses proxy.ts/proxy.js instead of deprecated middleware convention, and Vercel duration limits such as the 300s Fluid Compute default constrain long-running server work.`,
3008
+ authority: 'wiki',
3009
+ risk: 'critical',
3010
+ status: 'supported',
3011
+ freshness: 'fresh',
3012
+ source: 'src/core/routes.mjs',
3013
+ file: 'src/core/routes.mjs',
3014
+ evidence_count: 4,
3015
+ required_weight: 1.25,
3016
+ trust_score: 0.92
3017
+ });
3018
+ out.push({
3019
+ id: 'wiki-stack-current-docs-vercel-duration',
3020
+ text: 'Vercel Function duration limits are deployment constraints; record current official limits in TriWiki before designing long-running server work, including the 300s Fluid Compute default when applicable.',
3021
+ authority: 'wiki',
3022
+ risk: 'high',
3023
+ status: 'supported',
3024
+ freshness: 'fresh',
3025
+ source: 'https://vercel.com/docs/functions/limitations',
3026
+ file: 'https://vercel.com/docs/functions/limitations',
3027
+ evidence_count: 2,
3028
+ required_weight: 1.2,
3029
+ trust_score: 0.9
3030
+ });
2943
3031
  out.push(...(await memoryWikiClaims(root)));
2944
3032
  out.push(...(await userRequestSignalWikiClaims(root)));
2945
3033
  out.push(...(await teamAnalysisWikiClaims(root)));
@@ -3404,7 +3492,8 @@ async function team(args) {
3404
3492
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
3405
3493
  await writeTextAtomic(path.join(dir, 'team-workflow.md'), teamWorkflowMarkdown(plan));
3406
3494
  const liveFiles = await initTeamLive(id, dir, prompt, { agentSessions, roleCounts, roster });
3407
- await writeJsonAtomic(path.join(dir, 'team-gate.json'), { passed: false, analysis_artifact: false, triwiki_refreshed: false, triwiki_validated: false, consensus_artifact: false, implementation_team_fresh: false, review_artifact: false, integration_evidence: false, session_cleanup: false, context7_evidence: false });
3495
+ await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'default_or_prompt_team_spec' });
3496
+ await writeJsonAtomic(path.join(dir, 'team-gate.json'), { passed: false, team_roster_confirmed: true, analysis_artifact: false, triwiki_refreshed: false, triwiki_validated: false, consensus_artifact: false, implementation_team_fresh: false, review_artifact: false, integration_evidence: false, session_cleanup: false, context7_evidence: false });
3408
3497
  const result = {
3409
3498
  mission_id: id,
3410
3499
  mission_dir: dir,
@@ -3470,9 +3559,15 @@ function buildTeamPlan(id, prompt, opts = {}) {
3470
3559
  },
3471
3560
  context_tracking: triwikiContextTracking(),
3472
3561
  phases: [
3562
+ {
3563
+ id: 'team_roster_confirmation',
3564
+ goal: 'Materialize Team roster from default SKS counts or explicit user counts, write team-roster.json, and surface role counts before any implementation.',
3565
+ agents: ['parent_orchestrator'],
3566
+ output: 'team-roster.json'
3567
+ },
3473
3568
  {
3474
3569
  id: 'parallel_analysis_scouting',
3475
- goal: 'Read relevant TriWiki context first, then read-only analysis scouts split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation in parallel before debate.',
3570
+ goal: 'Read relevant TriWiki context first. If chat-history screenshots or attached images are present, list visible chat text and image-region matches as evidence, then read-only analysis scouts split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation in parallel before debate.',
3476
3571
  agents: roster.analysis_team.map((agent) => agent.id),
3477
3572
  max_parallel_subagents: agentSessions,
3478
3573
  write_policy: 'read-only',
@@ -3524,6 +3619,8 @@ function buildTeamPlan(id, prompt, opts = {}) {
3524
3619
  ],
3525
3620
  invariants: [
3526
3621
  'The parent thread remains the orchestrator and owns final integration.',
3622
+ 'Team roster confirmation is mandatory before implementation: default SKS counts are materialized when the user did not specify counts, explicit counts are honored, and team-gate.json must include team_roster_confirmed=true with team-roster.json present.',
3623
+ 'When and only when From-Chat-IMG/$From-Chat-IMG is explicit, treat client requests as chat-history screenshots plus separate attachments: extract visible text in reading order, use Computer Use/browser visual inspection to match screenshot image regions to attachments with confidence notes, and turn that evidence into a complete modification work order before editing.',
3527
3624
  'Every useful subagent message, result, handoff, review finding, and integration decision is mirrored to team-live.md and team-transcript.jsonl.',
3528
3625
  'Analysis scouts, debate team, and development team are separate bundles; scouts finish before debate and debate closes before implementation workers start.',
3529
3626
  'Analysis scouts are read-only and maximize the available session budget for independent investigation before any code edit.',
@@ -3534,7 +3631,7 @@ function buildTeamPlan(id, prompt, opts = {}) {
3534
3631
  'Implementation workers receive disjoint ownership scopes.',
3535
3632
  'Workers are told they are not alone in the codebase and must not revert others edits.',
3536
3633
  'Team completion requires session cleanup evidence with zero outstanding subagent sessions before reflection.',
3537
- 'Context tracking uses TriWiki as the SSOT throughout the whole pipeline; team handoffs and final claims must preserve id, hash, source path, and RGBA/trig coordinate anchors.',
3634
+ 'Context tracking uses the latest coordinate+voxel TriWiki pack as the SSOT throughout the whole pipeline; coordinate-only legacy packs are invalid, and team handoffs/final claims must preserve id, hash, source path, and RGBA/trig coordinate anchors.',
3538
3635
  'SKS hooks, DB safety rules, Ralph no-question rules, and H-Proof gates remain active.',
3539
3636
  'Destructive database operations remain forbidden.'
3540
3637
  ],
@@ -3550,7 +3647,7 @@ function buildTeamPlan(id, prompt, opts = {}) {
3550
3647
  'sks team event <mission-id> --agent <name> --phase <phase> --message "..."'
3551
3648
  ]
3552
3649
  },
3553
- required_artifacts: ['team-analysis.md', 'team-consensus.md', 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl'],
3650
+ required_artifacts: ['team-roster.json', 'team-analysis.md', 'team-consensus.md', 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl'],
3554
3651
  prompt_command: '$Team'
3555
3652
  };
3556
3653
  }
@@ -3571,7 +3668,7 @@ $Team ${plan.prompt}
3571
3668
 
3572
3669
  Use high reasoning for the Team route only, then return to the default/user-selected profile after completion. Use at most ${plan.agent_session_count || 3} subagent sessions at a time; the parent orchestrator is not counted.
3573
3670
 
3574
- Before each stage, read the relevant TriWiki context pack and hydrate low-trust claims from source. First run exactly ${plan.roster.bundle_size} read-only analysis_scout_N agents in parallel. Split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation into independent slices, then capture source-backed findings in team-analysis.md. Refresh and validate TriWiki before debate. Then run the debate team with exactly ${plan.roster.bundle_size} participants using the refreshed pack. Use the concrete roster below: final-user voices are stubborn and inconvenience-averse, executor voices are capable developers, reviewers are strict, and planners force consensus. Synthesize one agreed objective with acceptance criteria and disjoint implementation slices, then refresh and validate TriWiki again. Close the debate team. Then form a fresh development team with exactly ${plan.roster.bundle_size} executor_N developers implementing slices in parallel with non-overlapping ownership. Refresh TriWiki after implementation changes or blockers. Review with the validation team, validate TriWiki again, integrate results in the parent thread, close or account for all Team sessions in team-session-cleanup.json, run verification, and report evidence.
3671
+ Before each stage, read the relevant latest coordinate+voxel TriWiki context pack and hydrate low-trust claims from source. Coordinate-only legacy packs are invalid; refresh and validate before using TriWiki for pipeline decisions. First run exactly ${plan.roster.bundle_size} read-only analysis_scout_N agents in parallel. Split repo, docs, tests, API, DB risk, UX friction, and implementation-surface investigation into independent slices, then capture source-backed findings in team-analysis.md. Refresh and validate TriWiki before debate. Then run the debate team with exactly ${plan.roster.bundle_size} participants using the refreshed pack. Use the concrete roster below: final-user voices are stubborn and inconvenience-averse, executor voices are capable developers, reviewers are strict, and planners force consensus. Synthesize one agreed objective with acceptance criteria and disjoint implementation slices, then refresh and validate TriWiki again. Close the debate team. Then form a fresh development team with exactly ${plan.roster.bundle_size} executor_N developers implementing slices in parallel with non-overlapping ownership. Refresh TriWiki after implementation changes or blockers. Review with the validation team, validate TriWiki again, integrate results in the parent thread, close or account for all Team sessions in team-session-cleanup.json, run verification, and report evidence.
3575
3672
  \`\`\`
3576
3673
 
3577
3674
  ## Session Budget
@@ -3590,7 +3687,7 @@ Before each stage, read the relevant TriWiki context pack and hydrate low-trust
3590
3687
  - Pack: ${ctx.default_pack}
3591
3688
  - Refresh: \`${ctx.pack_command}\`
3592
3689
  - Validate: \`${ctx.validate_command}\`
3593
- - Rule: use relevant TriWiki before every stage, hydrate low-trust claims during the stage, refresh after findings/artifact changes, validate before handoffs/final claims, and keep id, hash, source path, and RGBA/trig coordinate anchors hydratable.
3690
+ - Rule: use only the latest coordinate+voxel TriWiki pack before every stage, hydrate low-trust claims during the stage, refresh after findings/artifact changes, validate before handoffs/final claims, reject coordinate-only legacy packs, and keep id, hash, source path, and RGBA/trig coordinate anchors hydratable.
3594
3691
 
3595
3692
  ## Analysis Scouts
3596
3693
 
@@ -3,7 +3,7 @@ import { contextCapsule } from './triwiki-attention.mjs';
3
3
  import { validateWikiCoordinateIndex } from './wiki-coordinate.mjs';
4
4
 
5
5
  export const DEFAULT_EVAL_THRESHOLDS = Object.freeze({
6
- min_token_savings_pct: 0.25,
6
+ min_token_savings_pct: 0.1,
7
7
  min_accuracy_delta: 0.03,
8
8
  min_required_recall: 0.95,
9
9
  max_unsupported_critical_selected: 0,
@@ -140,11 +140,10 @@ function metricBlock({ label, context, scenario, msPerRun }) {
140
140
  const wikiValidation = context.wiki ? validateWikiCoordinateIndex(context.wiki) : null;
141
141
  const voxel = context.wiki?.vx || context.wiki?.voxel_overlay || null;
142
142
  const voxelRows = Array.isArray(voxel?.v) ? voxel.v.length : (Array.isArray(voxel?.rows) ? voxel.rows.length : 0);
143
- const promptContext = context.wiki?.vx ? { ...context, wiki: { ...context.wiki, vx: undefined } } : context;
144
143
  return {
145
144
  label,
146
145
  context_hash: sha256(JSON.stringify(context)),
147
- estimated_tokens: estimateTokens(promptContext),
146
+ estimated_tokens: estimateTokens(context),
148
147
  context_build_ms_per_run: Number(msPerRun.toFixed(4)),
149
148
  wiki: context.wiki ? {
150
149
  schema: context.wiki.schema,
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.43';
8
+ export const PACKAGE_VERSION = '0.6.51';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
@@ -187,6 +187,12 @@ async function hookStop(root, state, payload, noQuestion) {
187
187
  reason: 'SKS Honest Mode is required before finishing. Re-check the actual goal, verify evidence/tests, state gaps honestly, and only then provide the final answer. Include a short "SKS Honest Mode" or "솔직모드" section.'
188
188
  };
189
189
  }
190
+ if (!hasCompletionSummary(last)) {
191
+ return {
192
+ decision: 'block',
193
+ reason: 'SKS final completion summary is required before finishing. Explain what was done, what changed for the user/repo, what was verified, and any remaining gaps before or alongside SKS Honest Mode.'
194
+ };
195
+ }
190
196
  if (shouldLoopBackAfterHonestMode(state) && hasHonestModeUnresolvedGap(last)) {
191
197
  const loopback = await recordHonestModeLoopback(root, state, last);
192
198
  return {
@@ -272,6 +278,14 @@ function hasHonestMode(text) {
272
278
  && /(verified|verification|검증|tests?|테스트|evidence|근거|gap|제약|uncertainty|불확실)/i.test(s);
273
279
  }
274
280
 
281
+ function hasCompletionSummary(text) {
282
+ const s = String(text || '');
283
+ const summary = /(completion summary|change summary|what changed|what was done|done summary|작업\s*요약|완료\s*요약|변경\s*요약|무엇을\s*(?:했|했고|변경)|뭐가\s*어떻게|정리)/i.test(s);
284
+ const verification = /(verified|verification|검증|tests?|테스트|evidence|근거|확인|통과)/i.test(s);
285
+ const gap = /(gap|gaps|remaining|제약|남은|미검증|not verified|not run|not claimed|불확실|없음|none)/i.test(s);
286
+ return summary && verification && gap;
287
+ }
288
+
275
289
  function hasHonestModeUnresolvedGap(text) {
276
290
  return honestModeGapLines(text).length > 0;
277
291
  }
@@ -287,7 +301,10 @@ function honestModeGapLines(text) {
287
301
 
288
302
  function honestGapLineResolved(line) {
289
303
  if (/(남은\s*(?:gap|갭|문제)\s*:\s*없음|남은\s*(?:gap|갭|문제)\s*없음|remaining\s+gaps?\s*:\s*(none|no|0)|no\s+remaining\s+gaps?)/i.test(line)) return true;
290
- if (/(차단\s*(?:확인|검증)|blocked\s+(?:as\s+expected|verified))/i.test(line) && !/(미확인|미검증|못|안\s*됨|실패|failed|not\s+verified|not\s+blocked)/i.test(line)) return true;
304
+ if (/no\s+active\s+blocking\s+route\s+gate\s+detected/i.test(line)) return true;
305
+ if (/(non[-\s]?blocker|non[-\s]?blocking|not\s+(?:a\s+)?blocker|no\s+blocker|does\s+not\s+block|not\s+blocking|blocker\s*(?:는|가)?\s*(?:아님|아닙니다|없음)|차단(?:하지|하진|하지는)\s*않|막(?:지|지는)\s*않)/i.test(line)) return true;
306
+ if (/(요약\s*(?:없으면|없는\s*경우).*(?:차단|block).*(?:요약\s*(?:있으면|있는\s*경우)|통과|pass)|(?:missing|without)\s+summary.*(?:block|blocked).*(?:with\s+summary|pass|accepted))/i.test(line)) return true;
307
+ if (/(차단(?:되는지)?\s*검증|차단\s*(?:확인|검증)|blocked\s+(?:as\s+expected|verified))/i.test(line) && !/(미확인|미검증|못|안\s*됨|실패|failed|not\s+verified|not\s+blocked)/i.test(line)) return true;
291
308
  if (/(CHANGELOG|README|\.md|missing|누락|미완료|미검증|미실행|안 했|못했|못 했)/i.test(line)) return false;
292
309
  return /(없음|없습니다|없다|해당 없음|none|no unresolved|no remaining|no gaps|zero|0개|n\/a|not applicable)\.?\s*$/i.test(line);
293
310
  }
package/src/core/init.mjs CHANGED
@@ -6,7 +6,7 @@ import { DEFAULT_DB_SAFETY_POLICY } from './db-safety.mjs';
6
6
  import { isHarnessSourceProject, writeHarnessGuardPolicy } from './harness-guard.mjs';
7
7
  import { repairSksGeneratedArtifacts } from './harness-conflicts.mjs';
8
8
  import { installVersionGitHook } from './version-manager.mjs';
9
- import { DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, context7ConfigToml, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
9
+ import { DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, chatCaptureIntakeText, context7ConfigToml, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
10
10
 
11
11
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
12
12
  function reflectionInstructionText(commandPrefix = 'sks') {
@@ -88,7 +88,7 @@ function isSksManagedHook(hook) {
88
88
  return hook.type === 'command' && /\bhook\s+(?:user-prompt-submit|pre-tool|post-tool|permission-request|stop)\b/.test(command) && /\b(?:sks|sneakoscope|sks\.mjs)\b/.test(command);
89
89
  }
90
90
 
91
- const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Ralph asks only during prepare. After `decision-contract.json` is sealed, do not ask the user; resolve with the decision ladder.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, fresh executor team, review, integration, Honest Mode.\n- `$DFix` is only for tiny design/content edits and bypasses the main pipeline. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md`; if missing, use `design-system-builder`. Image/logo/raster assets use `imagegen`.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, DB SDKs, and generated docs: resolve-library-id then query-docs.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, hydrate low-trust claims from source/hash/RGBA anchors, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Honest Mode before final: re-check goal, evidence, tests, risk boundaries, and remaining gaps. Say what passed and what was not verified.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
91
+ const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Ralph asks only during prepare. After `decision-contract.json` is sealed, do not ask the user; resolve with the decision ladder.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Before substantive work, SKS checks npm for a newer package. If newer, ask update-now vs skip-for-this-conversation.\n- Versioning is managed by the SKS pre-commit hook; check `sks versioning status`. Bypass only with `SKS_DISABLE_VERSIONING=1`.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: analysis scouts, TriWiki refresh/validate, read-only debate, consensus, fresh executor team, review, integration, Honest Mode.\n- `$DFix` is only for tiny design/content edits and bypasses the main pipeline. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md`; if missing, use `design-system-builder`. Image/logo/raster assets use `imagegen`.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, SDKs, and generated docs: resolve-library-id then query-docs.\n- When tech stack, framework, package, runtime, or deployment-platform versions change, use Context7 or official vendor web docs, record current syntax/security/limit guidance as high-priority TriWiki claims, then refresh and validate before coding.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, hydrate low-trust claims from source/hash/RGBA anchors, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Final response before stop: summarize what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked; then run Honest Mode. Say what passed and what was not verified.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
92
92
 
93
93
  export async function initProject(root, opts = {}) {
94
94
  const created = [];
@@ -160,7 +160,8 @@ export async function initProject(root, opts = {}) {
160
160
  context_tracking: triwikiContextTracking(),
161
161
  channel_map: { r: 'domainAngle', g: 'layerRadius', b: 'phase', a: 'concentration' },
162
162
  continuity_model: 'selected_text_plus_hydratable_rgba_trig_anchors',
163
- migration_model: 'setup_or_wiki_refresh_regenerates_optional_voxel_overlay'
163
+ required_pack_shape: 'coordinate_index_with_voxel_overlay',
164
+ migration_model: 'setup_or_wiki_refresh_regenerates_required_voxel_overlay'
164
165
  },
165
166
  git: {
166
167
  local_only: localOnly,
@@ -252,7 +253,8 @@ export async function initProject(root, opts = {}) {
252
253
  default_pack: triwikiContextTracking().default_pack,
253
254
  context_tracking: triwikiContextTracking(),
254
255
  compression_policy: 'preserve_ids_hashes_sources_rgba_coordinates_for_hydration',
255
- migration_model: 'setup_or_wiki_refresh_regenerates_optional_voxel_overlay'
256
+ required_pack_shape: 'coordinate_index_with_voxel_overlay',
257
+ migration_model: 'setup_or_wiki_refresh_regenerates_required_voxel_overlay'
256
258
  },
257
259
  recommended_skills: RECOMMENDED_SKILLS,
258
260
  recommended_mcp_servers: RECOMMENDED_MCP_SERVERS
@@ -301,7 +303,7 @@ export async function initProject(root, opts = {}) {
301
303
  codex_timeout_ms: 1800000,
302
304
  prefer_streaming_logs: true,
303
305
  eval_thresholds: {
304
- min_token_savings_pct: 0.25,
306
+ min_token_savings_pct: 0.1,
305
307
  min_accuracy_delta: 0.03,
306
308
  min_required_recall: 0.95
307
309
  }
@@ -309,9 +311,11 @@ export async function initProject(root, opts = {}) {
309
311
  llm_wiki: {
310
312
  ssot: 'triwiki',
311
313
  coordinate_schema: 'sks.wiki-coordinate.v1',
314
+ voxel_overlay_schema: 'sks.wiki-voxel.v1',
312
315
  default_pack: '.sneakoscope/wiki/context-pack.json',
313
316
  context_tracking: triwikiContextTracking(),
314
317
  compression_policy: 'preserve_ids_hashes_sources_rgba_coordinates_for_hydration',
318
+ required_pack_shape: 'coordinate_index_with_voxel_overlay',
315
319
  channel_map: { r: 'domainAngle', g: 'layerRadius_sin', b: 'phase', a: 'concentration' }
316
320
  },
317
321
  package: {
@@ -459,8 +463,9 @@ function codexAppQuickReference(scope, commandPrefix) {
459
463
  ...DOLLAR_COMMANDS.map((c) => `- \`${c.command}\`: ${c.route}`),
460
464
  `Picker skills: ${DOLLAR_COMMAND_ALIASES.map((x) => x.app_skill).join(', ')}.`,
461
465
  'Routing: Answer direct, DFix ultralight, execution routes ask only scope/safety/behavior/acceptance-changing questions before sealing answers.',
462
- `Full routes write reflection.md, record lessons to ${REFLECTION_MEMORY_PATH}, refresh/pack TriWiki, validate, then pass reflection-gate.json and Honest Mode.`,
463
- `Context Tracking: TriWiki SSOT. Before each route phase read .sneakoscope/wiki/context-pack.json; during every stage hydrate low-trust claims from source/hash/RGBA anchors; after changes run ${commandPrefix} wiki refresh or pack; before handoff/final run ${commandPrefix} wiki validate .sneakoscope/wiki/context-pack.json.`,
466
+ `Full routes write reflection.md, record lessons to ${REFLECTION_MEMORY_PATH}, refresh/pack TriWiki, validate, then final-answer with a user-visible completion summary plus Honest Mode.`,
467
+ `Context Tracking: TriWiki SSOT. Before each route phase read only the latest coordinate+voxel overlay pack at .sneakoscope/wiki/context-pack.json; coordinate-only legacy packs are invalid. During every stage hydrate low-trust claims from source/hash/RGBA anchors; after changes run ${commandPrefix} wiki refresh or pack; before handoff/final run ${commandPrefix} wiki validate .sneakoscope/wiki/context-pack.json.`,
468
+ stackCurrentDocsPolicyText(commandPrefix),
464
469
  `Runtime: open Codex App once, then run ${commandPrefix} bootstrap, ${commandPrefix} deps check, or ${commandPrefix} deps install tmux.`,
465
470
  `Guard: generated harness files are immutable outside the engine source repo; check ${commandPrefix} guard check; conflicts use ${commandPrefix} conflicts prompt with human approval.`
466
471
  ].join('\n') + '\n';
@@ -469,24 +474,25 @@ function codexAppQuickReference(scope, commandPrefix) {
469
474
  export async function installSkills(root) {
470
475
  const skills = {
471
476
  '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, Ralph, Research, eval, and redesign. Read \`design.md\` for UI work when present; use imagegen for image/logo/raster assets.\n`,
472
- '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 Honest Mode; do not create missions, subagents, or file edits.\n`,
473
- 'sks': `---\nname: sks\ndescription: General Sneakoscope Codex command route for $SKS or $sks usage, setup, status, and workflow help.\n---\n\nUse the local SKS control surface. Prefer discovery commands for availability questions: sks bootstrap, sks deps check, sks commands, sks usage <topic>, sks quickstart, sks codex-app, sks context7 check, sks guard check, sks conflicts check, sks reasoning, sks wiki pack, and sks pipeline status. If implementation or code-changing work is requested through $SKS or rough natural language, promote it to Team by default unless Answer, DFix, Help, Wiki maintenance, or a safety-specific route applies. Surface route/guard/write-scope status, then use worker subagents for independent scopes; parent integrates and verifies. Context tracking uses TriWiki as SSOT. Do not edit installed harness control files except in this engine source repo. Harness conflicts require sks conflicts prompt and human-approved cleanup.\n`,
477
+ '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`,
478
+ '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`,
474
479
  '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, validation, and blockers. Do not start ambiguity-gated implementation, subagents, or unrelated work.\n`,
475
- 'team': `---\nname: team\ndescription: SKS Team multi-agent orchestration for $Team and default implementation/code-changing routes.\n---\n\nUse for $Team/$team, implementation, code-changing, or parallel specialist work. Ambiguity gate first, then exactly requested role counts: executor:N creates N scouts, N debate participants, and a fresh N-person executor team. Split repo/docs/tests/API/risk/UX slices, refresh/validate TriWiki before debate, implementation, review, and final claims, mirror useful events with sks team event, then close/clean Team sessions and pass team-session-cleanup.json before reflection. Parent integrates and verifies.\n`,
476
- 'qa-loop': `---\nname: qa-loop\ndescription: $QA-LOOP dogfoods UI/API as human proxy with safety gates, Browser/Computer evidence, safe fixes, rechecks, and a QA report.\n---\n\nUse only $QA-LOOP. Ask UI/API/both, target, mutation, and login first. Credentials are runtime-only; never save secrets, cookies, auth state, or secret screenshots. UI E2E needs Browser/Computer evidence or mark unverified. Deployed targets are read-only smoke; destructive removal tests are forbidden. After answers seal, run sks qa-loop answer/run. During run, dogfood real flows as human proxy, safely fix code/test/docs allowed by contract, and re-run focused checks. Do not pass qa-gate.json while unresolved_findings or unresolved_fixable_findings > 0, or before post_fix_verification_complete is true. Complete qa-ledger.json, the date/version-prefixed report named by qa-gate.json, qa-gate.json, and Honest Mode.\n`,
480
+ 'team': `---\nname: team\ndescription: SKS Team orchestration for $Team/code work; $From-Chat-IMG is the explicit chat-image alias.\n---\n\nUse for $Team/code work. Ambiguity gate first. Write team-roster.json; team-gate.json needs team_roster_confirmed=true. executor:N means N scouts, N debate voices, then fresh N executors. Refresh/validate TriWiki before debate, implementation, review, and final. Log events, close sessions, pass team-session-cleanup.json, then reflection and Honest Mode. Parent integrates/verifies.\n\n${chatCaptureIntakeText()}\n`,
481
+ 'from-chat-img': `---\nname: from-chat-img\ndescription: Explicit $From-Chat-IMG Team alias for chat screenshot plus attachment analysis.\n---\n\nUse only for From-Chat-IMG/$From-Chat-IMG. It enters the normal Team pipeline. Treat uploads as chat screenshot plus originals. Use Computer Use/browser visual inspection when available, list requirements first, match regions to attachments with confidence, write the work order, then continue Team gates, review, reflection, and Honest Mode.\n`,
482
+ 'qa-loop': `---\nname: qa-loop\ndescription: $QA-LOOP dogfoods UI/API as human proxy with safety gates, Browser/Computer evidence, safe fixes, rechecks, and a QA report.\n---\n\nUse only $QA-LOOP. Ask scope, target, mutation, login. Credentials are runtime-only; never save secrets. UI needs Browser/Computer evidence or mark unverified. Deployed targets are read-only; destructive removal is forbidden. After answer/run, dogfood real flows, apply safe contract-allowed code/test/docs fixes, recheck, and do not pass qa-gate.json with unresolved findings or without post_fix_verification_complete. Finish qa-ledger, date/version report, gate, completion summary, and Honest Mode.\n`,
477
483
  'ralph': `---\nname: ralph\ndescription: Dollar-command route for $Ralph or $ralph mandatory clarification and no-question mission workflows.\n---\n\nUse when the user invokes $Ralph/$ralph or requests a clarification-gated autonomous implementation mission. Prepare with sks ralph prepare, answer/seal required slots when answers are provided, then run only after decision-contract.json exists.\n`,
478
484
  'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Do not use for ordinary code edits.\n`,
479
- 'autoresearch': `---\nname: autoresearch\ndescription: Dollar-command route for $AutoResearch or $autoresearch iterative experiment loops.\n---\n\nUse when the user invokes $AutoResearch/$autoresearch or asks for iterative improvement, SEO/GEO, ranking, prompt/workflow improvement, benchmark gains, or open-ended experimentation. Follow the autoresearch-loop skill and load seo-geo-optimizer for README, npm, GitHub stars, schema, keyword, AI-search, or discoverability work. Define program, hypothesis, experiment, metric, keep/discard decision, falsification, next experiment, and Honest Mode conclusion.\n`,
485
+ 'autoresearch': `---\nname: autoresearch\ndescription: Dollar-command route for $AutoResearch or $autoresearch iterative experiment loops.\n---\n\nUse for $AutoResearch, iterative improvement, SEO/GEO, ranking, workflow, benchmark, or experiments. Define program, hypothesis, experiment, metric, keep/discard, falsification, next step, and Honest Mode. Load seo-geo-optimizer for README/npm/GitHub/schema/AI-search work.\n`,
480
486
  'db': `---\nname: db\ndescription: Dollar-command route for $DB or $db database and Supabase safety checks.\n---\n\nUse when the user invokes $DB/$db or the task touches SQL, Supabase, Postgres, migrations, Prisma, Drizzle, Knex, MCP database tools, or production data. Run or follow sks db policy, sks db scan, sks db classify, and sks db check. Destructive database operations remain forbidden.\n`,
481
487
  'gx': `---\nname: gx\ndescription: Dollar-command route for $GX or $gx deterministic GX visual context cartridges.\n---\n\nUse when the user invokes $GX/$gx or asks for architecture/context visualization through SKS. Prefer sks gx init, render, validate, drift, and snapshot. vgraph.json remains the source of truth.\n`,
482
488
  'help': `---\nname: help\ndescription: Dollar-command route for $Help or $help explaining installed SKS commands and workflows.\n---\n\nUse when the user invokes $Help/$help or asks what commands exist. Prefer concise output from sks commands, sks usage <topic>, sks quickstart, sks aliases, and sks codex-app.\n`,
483
- 'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Default SKS prompt optimization pipeline for execution prompts; Answer and DFix bypass it.\n---\n\nClassify intent first. $Answer handles questions; $DFix handles tiny design/content edits; implementation defaults to Team unless a specific safety/research/GX route applies. Infer goal, target, constraints, acceptance criteria, risk, and smallest safe route from TriWiki/current code. Ask only generated questions whose answer can change scope, safety, user-facing behavior, or acceptance criteria; otherwise use defaults and seal with sks pipeline answer latest answers.json before execution.\n\nFor code work, surface route/guard/scopes, use Team workers for independent scopes, and keep parent-owned integration, tests, Context7, H-Proof, and Honest Mode.\n\nDesign routing: read \`design.md\` first; if missing use design-system-builder with plan-tool ambiguity removal and a default font recommendation; if present use design-ui-editor/design-artifact-expert. Image, logo, raster, and bitmap assets must use imagegen.\n\nContext continuity: TriWiki context-tracking SSOT is .sneakoscope/wiki/context-pack.json. Use relevant TriWiki context before every route stage, hydrate low-trust/stale source/hash/RGBA claims, run \`sks wiki refresh\` or \`sks wiki pack\` after findings/artifact changes, and validate with \`sks wiki validate .sneakoscope/wiki/context-pack.json\` before handoffs and final claims.\n`,
484
- 'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route.\n---\n\nUse medium for simple fulfillment such as copy, color, command discovery, setup display, or mechanical edits. Use high for any logical, safety, architecture, database, orchestration, refactor, or multi-file implementation work. Use xhigh for research, AutoResearch, hypotheses, falsification, benchmarks, SEO/GEO experiments, and open-ended discovery.\n\nRules:\n- Treat the routing as temporary for the current route only.\n- Do not persist profile changes.\n- Return to the default or user-selected profile when the route gate passes.\n- Inspect with sks reasoning \"prompt\" and sks pipeline status.\n`,
485
- 'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute SKS dollar-command routes as stateful pipelines with mission artifacts, route gates, Context7 evidence, temporary reasoning routing, reflection, and Honest Mode.\n---\n\nEvery $ command is a route. Use .sneakoscope/state/current.json, route artifacts under .sneakoscope/missions/<id>/, temporary reasoning routing, TriWiki context before stages, source hydration for low-trust claims, Context7 when required, Team session cleanup before reflection, post-route reflection for full execution routes, and Honest Mode before final. Surface guard/scopes before edits, record evidence, refresh/pack/validate TriWiki, use the reflection skill before final on full routes, and use sks pipeline status/resume for current state.\n`,
489
+ 'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Default SKS prompt optimization pipeline for execution prompts; Answer and DFix bypass it.\n---\n\nClassify intent: Answer only for real questions; question-shaped implicit instructions, complaints, and mandatory-policy statements route to Team. DFix handles tiny design/content; code defaults to Team unless safety/research/GX route fits. Infer goal, target, constraints, acceptance, risk, and smallest safe route. Ask only scope/safety/behavior/acceptance-changing questions; otherwise seal inferred answers. Code work surfaces route/guard/scopes, materializes team-roster.json from default or explicit counts before implementation, and parent owns integration/tests/Context7/Honest Mode.\n\n${chatCaptureIntakeText()}\n\nDesign: read design.md; if missing use design-system-builder; use imagegen for image/logo/raster. TriWiki context-tracking SSOT: .sneakoscope/wiki/context-pack.json; read only the latest coordinate+voxel overlay pack before every route stage, run sks wiki refresh/pack after changes, validate before handoffs/final.\n`,
490
+ 'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route.\n---\n\nmedium: simple copy/color/discovery/setup/mechanical edits. high: logic, safety, architecture, DB, orchestration, refactor, multi-file work. xhigh: research, AutoResearch, falsification, benchmarks, SEO/GEO, open-ended discovery. Routing is temporary; return to default after the gate. Inspect with sks reasoning and sks pipeline status.\n`,
491
+ 'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute SKS dollar-command routes as stateful pipelines with mission artifacts, route gates, Context7 evidence, temporary reasoning routing, reflection, and Honest Mode.\n---\n\nEvery $ command is a route. Use current.json and mission artifacts, temporary reasoning, TriWiki before stages, source hydration, Context7 when required, Team cleanup before reflection, reflection for full routes, and completion summary plus Honest Mode before final. Surface guard/scopes, record evidence, refresh/pack/validate TriWiki, and check sks pipeline status/resume.\n`,
486
492
  'context7-docs': `---\nname: context7-docs\ndescription: Enforce Context7 MCP documentation evidence for SKS routes that depend on external libraries, frameworks, APIs, MCPs, package managers, DB SDKs, or generated docs.\n---\n\nWhen required, resolve-library-id, then query-docs for the resolved id. Legacy get-library-docs evidence is accepted. Prefer sks context7 tools/resolve/docs/evidence and finish only after both evidence stages exist. Check setup with sks context7 check.\n`,
487
493
  'seo-geo-optimizer': `---\nname: seo-geo-optimizer\ndescription: SEO/GEO support for README, npm, GitHub, keywords, snippets, schema, and AI-search visibility.\n---\n\nUse for SEO/GEO, package metadata, README ranking, snippets, schema, and AI search. Optimize README, package.json, docs, badges, topics, quickstart, examples, command discovery, exact names, keywords, and AI Answer Snapshot. Do not invent metrics; use $AutoResearch unless it is a tiny copy edit.\n`,
488
- 'reflection': `---\nname: reflection\ndescription: Post-route self-review for full SKS routes that records real misses, gaps, and corrective lessons into TriWiki memory.\n---\n\nUse after full route work/tests and before final. DFix, Answer, Help, Wiki, and SKS discovery are exempt. Do not invent faults. Write .sneakoscope/missions/<mission-id>/reflection.md. If lessons exist, append claim rows to ${REFLECTION_MEMORY_PATH}; then refresh/pack, validate .sneakoscope/wiki/context-pack.json, and pass reflection-gate.json.\n\n${reflectionInstructionText()}\n`,
489
- 'honest-mode': `---\nname: honest-mode\ndescription: Required final SKS verification pass before claiming a task is complete.\n---\n\nBefore final: restate the goal, compare result to evidence, list tests/commands/inspections, state uncertainty or blockers plainly, and do not claim completion beyond evidence. Full routes must pass reflection-gate.json first. Include concise SKS Honest Mode or 솔직모드 when required.\n`,
494
+ 'reflection': `---\nname: reflection\ndescription: Post-route self-review for full SKS routes that records real misses, gaps, and corrective lessons into TriWiki memory.\n---\n\nUse after full route work/tests and before final. DFix, Answer, Help, Wiki, SKS discovery are exempt. Do not invent faults. Write reflection.md; append real lessons to ${REFLECTION_MEMORY_PATH}; refresh/pack, validate context-pack.json, pass reflection-gate.json.\n\n${reflectionInstructionText()}\n`,
495
+ 'honest-mode': `---\nname: honest-mode\ndescription: Required final SKS verification pass before claiming a task is complete.\n---\n\nBefore final: include a completion summary explaining what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked. Then restate the goal, compare result to evidence, list tests/commands/inspections, state uncertainty or blockers plainly, and do not claim completion beyond evidence. Full routes must pass reflection-gate.json first. Include concise SKS Honest Mode or 솔직모드 when required.\n`,
490
496
  'autoresearch-loop': `---\nname: autoresearch-loop\ndescription: Iterative AutoResearch-style loop for open-ended improvement, discovery, prompt, ranking, SEO/GEO, and workflow-quality tasks.\n---\n\nUse for research, ranking, prompt/workflow improvement, benchmark gains, or repeated refinement. Loop: program, hypothesis, smallest falsifying experiment, metric, keep/discard, falsify, next step. Keep a ledger and do not claim improvement without evidence.\n`,
491
497
  'ralph-supervisor': `---\nname: ralph-supervisor\ndescription: Run the Ralph no-question loop after a decision contract is sealed.\n---\n\nYou are the Ralph Supervisor.\n\nRules:\n- Never ask the user during Ralph run.\n- Use decision-contract.json and the decision ladder.\n- Continue until done-gate.json passes or safe scope is completed with explicit limitation.\n- Keep outputs bounded. Write raw logs to files and summarize only tails.\n- Database destructive operations are never allowed.\n- Write progress to .sneakoscope mission files.\n`,
492
498
  'ralph-resolver': `---\nname: ralph-resolver\ndescription: Resolve newly discovered ambiguity during Ralph using the sealed decision ladder, without asking the user.\n---\n\nResolve ambiguity in this order: seed contract, explicit answers, approved defaults, AGENTS.md, current code/tests, smallest reversible change, defer optional scope. Never ask the user. If database risk is involved, prefer read-only, no-op, local-only migration file, or safe limitation; never run destructive SQL.\n`,
@@ -520,14 +526,15 @@ export async function installSkills(root) {
520
526
  }
521
527
 
522
528
  function enrichSkillContent(name, content) {
523
- if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ralph', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
529
+ if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ralph', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
524
530
  const text = String(content || '').trimEnd();
525
531
  if (text.includes('TriWiki context-tracking SSOT')) return text;
526
532
  return `${text}
527
533
 
528
534
  Context tracking:
529
535
  - Ask only ambiguity that can change scope, safety, behavior, or acceptance; infer the rest from TriWiki/current code and seal answers before execution.
530
- - TriWiki SSOT: .sneakoscope/wiki/context-pack.json. Refresh/pack after findings or artifact changes; validate before handoffs/final claims.
536
+ - TriWiki SSOT: .sneakoscope/wiki/context-pack.json. Use only the latest coordinate+voxel overlay pack; coordinate-only legacy packs are invalid and must be refreshed before use. Refresh/pack after findings or artifact changes; validate before handoffs/final claims.
537
+ - ${stackCurrentDocsPolicyText()}
531
538
  - Keep non-selected claims hydratable by id, hash, source path, and RGBA/trig coordinate. Hydrate low-trust claims before relying on them.
532
539
  - Hook output is limited; use mission files, team events, or normal updates for live detail.
533
540
  `;
@@ -1,3 +1,4 @@
1
+ import fsp from 'node:fs/promises';
1
2
  import path from 'node:path';
2
3
  import { appendJsonl, exists, nowIso, readJson, readText, writeJsonAtomic, writeTextAtomic } from './fsx.mjs';
3
4
  import { containsUserQuestion, noQuestionContinuationReason } from './no-question-guard.mjs';
@@ -6,7 +7,7 @@ import { buildQuestionSchemaForRoute, writeQuestions } from './questions.mjs';
6
7
  import { sealContract } from './decision-contract.mjs';
7
8
  import { scanDbSafety } from './db-safety.mjs';
8
9
  import { writeResearchPlan } from './research.mjs';
9
- import { context7RequirementText, dollarCommand, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, stripDollarCommand, subagentExecutionPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
10
+ import { chatCaptureIntakeText, context7RequirementText, dollarCommand, hasFromChatImgSignal, reflectionRequiredForRoute, reasoningInstruction, routeNeedsContext7, routePrompt, routeReasoning, routeRequiresSubagents, stripDollarCommand, subagentExecutionPolicyText, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
10
11
  import { formatRoleCounts, initTeamLive, parseTeamSpecText } from './team-live.mjs';
11
12
 
12
13
  export { routePrompt };
@@ -34,17 +35,20 @@ export function promptPipelineContext(prompt, route = routePrompt(prompt)) {
34
35
  'Hook visibility limit: hooks can inject context/status or block/continue a turn, but they cannot create arbitrary live chat bubbles; use team events, mission files, or normal assistant updates for live transcript details.',
35
36
  'Ambiguity gate: every execution route must start with mandatory ambiguity-removal questions before execution. DFix and Answer bypass this gate because they do not start implementation.',
36
37
  'Plan-first interaction: when ambiguity questions are required, call the Codex plan tool first so the user sees Ask questions -> Seal decision contract -> Execute/verify as the visible workflow.',
38
+ 'Question-shaped directive policy: before using Answer, decide whether a question is a real information request or an implicit instruction/complaint about broken behavior. Rhetorical bug reports, mandatory-policy statements, and "why is this not happening?" execution complaints must route to Team, not Answer.',
37
39
  'Best-practice prompt shape: extract Goal, Context, Constraints, and Done-when before implementation; keep questions compact and only ask for answers that can change scope, safety, user-facing behavior, or acceptance criteria.',
40
+ chatCaptureIntakeText(),
38
41
  'Default execution routing: general implementation/code-changing prompts promote to Team so the normal path is parallel analysis, TriWiki refresh, debate/consensus, then fresh parallel executors. Answer, DFix, Help, Wiki maintenance, and safety-specific routes are intentional exceptions.',
39
42
  'Stance: infer the user intent aggressively from rough wording and local context, but ask short ambiguity-removal questions before work when a missing answer can change the target, scope, safety boundary, or acceptance criteria.',
40
43
  subagentExecutionPolicyText(route, prompt),
41
44
  'Design routing: UI/UX reads design.md first; if missing, use design-system-builder from docs/Design-Sys-Prompt.md with plan-tool clarification and a default font recommendation. Existing designs use design-ui-editor plus design-artifact-expert. Image/logo/raster assets use imagegen.',
42
45
  triwikiContextTrackingText(),
43
46
  triwikiStagePolicyText(),
47
+ stackCurrentDocsPolicyText(),
44
48
  'Extract intent, target files/surfaces, constraints, acceptance criteria, risks, and the smallest safe atomic step before acting.',
45
49
  'Do not stop at a plan when implementation was requested; continue until the route gate passes or a hard blocker is honestly recorded.',
46
50
  context7RequirementText(required),
47
- 'Before final answer, run SKS Honest Mode: verify evidence/tests, state gaps, and confirm the goal is genuinely complete.'
51
+ 'Before final answer, include a user-visible completion summary that explains what changed and how it was verified, then run SKS Honest Mode: verify evidence/tests, state gaps, and confirm the goal is genuinely complete.'
48
52
  ];
49
53
  if (reflectionRequiredForRoute(route)) lines.push(reflectionInstructionText());
50
54
  if (route?.id === 'Team') lines.push(`Team route: scouts, TriWiki refresh, debate, consensus, close planning agents, fresh executors, review/integration, ${TEAM_SESSION_CLEANUP_ARTIFACT}, reflection, and Honest Mode.`);
@@ -67,7 +71,7 @@ export function dfixQuickContext(prompt, route = routePrompt(prompt)) {
67
71
  '2. Inspect only the files needed to locate that target.',
68
72
  '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.',
69
73
  '4. Run only cheap verification when useful, such as syntax check, focused test, or local render smoke.',
70
- '5. Final response: one short change summary plus verification or the exact blocker.'
74
+ '5. Final response: one short completion summary explaining what changed, plus verification or the exact blocker.'
71
75
  ].join('\n');
72
76
  }
73
77
 
@@ -82,8 +86,9 @@ export function answerOnlyContext(prompt, route = routePrompt(prompt)) {
82
86
  '1. Check current repo facts and TriWiki context first; hydrate low-trust wiki claims from source paths before relying on them.',
83
87
  '2. Use web search for current, external, or uncertain facts when browsing is available or the user asks for latest/source-backed information.',
84
88
  '3. Use Context7 resolve-library-id plus query-docs when the answer depends on package, API, framework, SDK, MCP, or generated documentation behavior.',
85
- `4. ${context7RequirementText(required)}`,
86
- '5. Finish with Honest Mode fact-checking: separate verified facts, source-backed inferences, and remaining uncertainty.',
89
+ '4. For stack additions or version changes, preserve current-doc findings as high-priority TriWiki claims before recommending syntax or implementation.',
90
+ `5. ${context7RequirementText(required)}`,
91
+ '6. Finish with a clear answer summary plus Honest Mode fact-checking: separate verified facts, source-backed inferences, and remaining uncertainty.',
87
92
  'Answer directly and concisely. If the prompt is actually asking for code/work after inspection, state the re-route and use the proper execution pipeline.'
88
93
  ].join('\n');
89
94
  }
@@ -134,6 +139,7 @@ async function prepareWikiQuickRoute(route, task) {
134
139
  `SKS wiki pipeline active. Route: ${route.command} (${route.route}).`,
135
140
  `Task: ${task || 'refresh and validate TriWiki'}`,
136
141
  'Run policy: refresh/update/갱신 -> `sks wiki refresh` then validate; prune/clean/정리 -> `sks wiki refresh --prune` or dry-run prune first; pack -> `sks wiki pack` then validate.',
142
+ stackCurrentDocsPolicyText(),
137
143
  'Report claims, anchors, trust, validation, and blockers. Do not create mission state, ask ambiguity-gate questions, spawn subagents, or run unrelated work.'
138
144
  ].join('\n')
139
145
  };
@@ -253,7 +259,8 @@ async function prepareTeam(root, route, task, required) {
253
259
  },
254
260
  context_tracking: triwikiContextTracking(),
255
261
  phases: [
256
- { id: 'parallel_analysis_scouting', goal: `Before scouting, read relevant TriWiki context. Spawn exactly ${roster.bundle_size} read-only analysis_scout_N agents in parallel, using the full available session budget without exceeding ${agentSessions}. Split repo/docs/tests/API/user-flow/risk investigation into independent slices, hydrate relevant low-trust claims from source, and record source-backed findings.`, agents: roster.analysis_team.map((agent) => agent.id), max_parallel_subagents: agentSessions, write_policy: 'read-only' },
262
+ { id: 'team_roster_confirmation', goal: `Before any implementation, materialize the Team roster from default SKS counts or explicit user counts, write team-roster.json, and surface role counts ${formatRoleCounts(roleCounts)}. Implementation cannot be considered complete unless team-gate.json has team_roster_confirmed=true.`, agents: ['parent_orchestrator'], output: 'team-roster.json' },
263
+ { id: 'parallel_analysis_scouting', goal: `Before scouting, read TriWiki context. ${hasFromChatImgSignal(cleanTask) ? 'From-Chat-IMG active: use Computer Use/browser visual inspection, list requirements, match regions to attachments, and write the work order before edits.' : 'From-Chat-IMG inactive: do not assume ordinary images are chat captures.'} Spawn exactly ${roster.bundle_size} read-only analysis_scout_N agents in parallel, using the full available session budget without exceeding ${agentSessions}. Split repo/docs/tests/API/user-flow/risk investigation into independent slices, hydrate relevant low-trust claims from source, and record source-backed findings.`, agents: roster.analysis_team.map((agent) => agent.id), max_parallel_subagents: agentSessions, write_policy: 'read-only' },
257
264
  { id: 'triwiki_refresh', goal: `Parent orchestrator updates Team analysis artifacts, then runs ${triwikiContextTracking().refresh_command} or ${triwikiContextTracking().pack_command}, prunes with ${triwikiContextTracking().prune_command} when stale/oversized wiki state would pollute handoffs, and runs ${triwikiContextTracking().validate_command} so the next stage uses current TriWiki context.`, agents: ['parent_orchestrator'], output: '.sneakoscope/wiki/context-pack.json' },
258
265
  { id: 'planning_debate', goal: `Before debate, read the refreshed TriWiki pack. Debate team of exactly ${roster.bundle_size} participants maps user inconvenience, options, constraints, affected files, DB/test risk, and tradeoffs while hydrating low-trust claims from source.`, agents: roster.debate_team.map((agent) => agent.id) },
259
266
  { id: 'consensus', goal: `Seal one objective with acceptance criteria and disjoint implementation slices, then refresh/validate TriWiki so implementation receives current consensus context.` },
@@ -267,14 +274,15 @@ async function prepareTeam(root, route, task, required) {
267
274
  dashboard: 'team-dashboard.json',
268
275
  commands: ['sks team status latest', 'sks team log latest', 'sks team tail latest', 'sks team watch latest', 'sks team event latest --agent <name> --phase <phase> --message "..."']
269
276
  },
270
- required_artifacts: ['team-analysis.md', 'team-consensus.md', 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'reflection.md', 'reflection-gate.json', 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl']
277
+ required_artifacts: ['team-roster.json', 'team-analysis.md', 'team-consensus.md', 'team-review.md', 'team-gate.json', TEAM_SESSION_CLEANUP_ARTIFACT, 'reflection.md', 'reflection-gate.json', 'team-live.md', 'team-transcript.jsonl', 'team-dashboard.json', '.sneakoscope/wiki/context-pack.json', 'context7-evidence.jsonl']
271
278
  };
272
279
  await writeJsonAtomic(path.join(dir, 'team-plan.json'), plan);
280
+ await writeJsonAtomic(path.join(dir, 'team-roster.json'), { schema_version: 1, mission_id: id, role_counts: roleCounts, agent_sessions: agentSessions, bundle_size: roster.bundle_size, roster, confirmed: true, source: 'default_or_prompt_team_spec' });
273
281
  const contextTracking = triwikiContextTracking();
274
282
  await writeTextAtomic(path.join(dir, 'team-workflow.md'), `# SKS Team Workflow\n\nTask: ${cleanTask}\n\nAgent session budget: ${agentSessions}\nBundle size: ${roster.bundle_size}\nRole counts: ${formatRoleCounts(roleCounts)}\nReasoning: high for team logic, temporary for this route only.\nContext tracking: ${contextTracking.ssot} SSOT, ${contextTracking.default_pack}; use relevant TriWiki context before every work stage, refresh/validate after findings, and preserve hydratable source anchors.\n\n1. Run exactly ${roster.bundle_size} read-only analysis_scout_N agents and write team-analysis.md.\n2. Refresh/validate TriWiki before debate.\n3. Run exactly ${roster.bundle_size} debate participants, then write consensus and implementation slices.\n4. Close debate agents before starting a fresh ${roster.bundle_size}-person executor team.\n5. Review, integrate, verify, and record evidence.\n6. Close/clean remaining Team sessions, finalize live transcript state, and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection/final.\n\nLive visibility:\n- sks team log ${id}\n- sks team tail ${id}\n- sks team watch ${id}\n- sks team event ${id} --agent <name> --phase <phase> --message \"...\"\n`);
275
283
  await initTeamLive(id, dir, cleanTask, { agentSessions, roleCounts, roster });
276
- await writeJsonAtomic(path.join(dir, 'team-gate.json'), { passed: false, analysis_artifact: false, triwiki_refreshed: false, triwiki_validated: false, consensus_artifact: false, implementation_team_fresh: false, review_artifact: false, integration_evidence: false, session_cleanup: false, context7_evidence: false });
277
- await setCurrent(root, routeState(id, route, 'TEAM_PARALLEL_ANALYSIS_SCOUTING', required, { prompt: cleanTask, agent_sessions: agentSessions, role_counts: roleCounts, context_tracking: 'triwiki' }));
284
+ await writeJsonAtomic(path.join(dir, 'team-gate.json'), { passed: false, team_roster_confirmed: true, analysis_artifact: false, triwiki_refreshed: false, triwiki_validated: false, consensus_artifact: false, implementation_team_fresh: false, review_artifact: false, integration_evidence: false, session_cleanup: false, context7_evidence: false });
285
+ await setCurrent(root, routeState(id, route, 'TEAM_PARALLEL_ANALYSIS_SCOUTING', required, { prompt: cleanTask, agent_sessions: agentSessions, role_counts: roleCounts, team_roster_confirmed: true, context_tracking: 'triwiki' }));
278
286
  return routeContext(route, id, cleanTask, required, `Run scouts, refresh/validate TriWiki, debate, close debate agents, form a fresh ${roster.bundle_size}-person executor team, then close/clean Team sessions and write ${TEAM_SESSION_CLEANUP_ARTIFACT} before reflection.`);
279
287
  }
280
288
 
@@ -334,7 +342,8 @@ Task: ${task}
334
342
  Required skills: ${route.requiredSkills.join(', ')}
335
343
  Stop gate: ${route.stopGate}
336
344
  Subagents: ${routeRequiresSubagents(route, task) ? 'required before code-changing execution; spawn parallel workers/reviewers with disjoint ownership or record explicit unavailable/unsplittable evidence.' : 'optional'}
337
- TriWiki: use relevant context before each route phase, hydrate low-trust claims during the phase, refresh after new findings or artifact changes, and validate before handoffs/final claims.
345
+ TriWiki: use only the latest coordinate+voxel-overlay context pack before each route phase, hydrate low-trust claims during the phase, refresh after new findings or artifact changes, and validate before handoffs/final claims. Coordinate-only legacy packs are invalid and must be refreshed before pipeline decisions.
346
+ Final closeout: every pipeline final answer must summarize what was done, what changed for the user/repo, what was verified, and any remaining gaps.
338
347
  ${reflectionRequiredForRoute(route) ? `Reflection: ${reflectionInstructionText()}` : 'Reflection: not required for this lightweight route.'}
339
348
  Reasoning: ${routeReasoning(route, task).effort} temporary; return to default after completion.
340
349
  Next atomic action: ${next}`
@@ -538,9 +547,45 @@ async function reflectionGateStatus(root, state = {}) {
538
547
  if (hasMemory && !(await exists(path.join(root, REFLECTION_MEMORY_PATH)))) missing.push(REFLECTION_MEMORY_PATH);
539
548
  if (gate.wiki_refreshed_or_packed !== true && gate.triwiki_refreshed !== true) missing.push('wiki_refreshed_or_packed');
540
549
  if (gate.wiki_validated !== true) missing.push('wiki_validated');
550
+ missing.push(...await staleReflectionReasons(root, state, gate));
541
551
  return { ok: missing.length === 0, missing };
542
552
  }
543
553
 
554
+ async function staleReflectionReasons(root, state = {}, gate = {}) {
555
+ const created = Date.parse(gate.created_at || gate.updated_at || '');
556
+ if (!Number.isFinite(created)) return ['reflection-gate:created_at'];
557
+ const id = state?.mission_id;
558
+ if (!id) return [];
559
+ const dir = missionDir(root, id);
560
+ const missing = [];
561
+ for (const file of gateFilesForState(state).filter((file) => file && !['none', 'honest_mode'].includes(file))) {
562
+ if (await fileUpdatedAfter(path.join(dir, file), created)) missing.push(`${file}:updated_after_reflection`);
563
+ }
564
+ const transcript = await readText(path.join(dir, 'team-transcript.jsonl'), '');
565
+ const newerWorkEvent = transcript
566
+ .split(/\n/)
567
+ .filter(Boolean)
568
+ .map((line) => {
569
+ try { return JSON.parse(line); } catch { return null; }
570
+ })
571
+ .find((event) => {
572
+ const ts = Date.parse(event?.ts || '');
573
+ if (!Number.isFinite(ts) || ts <= created) return false;
574
+ return !/^(REFLECTION|HONEST|TEAM_CLEANUP)$/i.test(String(event?.phase || ''));
575
+ });
576
+ if (newerWorkEvent) missing.push('team-transcript.jsonl:work_after_reflection');
577
+ return missing;
578
+ }
579
+
580
+ async function fileUpdatedAfter(file, timeMs) {
581
+ try {
582
+ const stat = await fsp.stat(file);
583
+ return stat.mtimeMs > timeMs + 1000;
584
+ } catch {
585
+ return false;
586
+ }
587
+ }
588
+
544
589
  function reflectionStopReason(state = {}, status = {}) {
545
590
  const id = state?.mission_id || 'latest';
546
591
  const route = String(state.route_command || state.route || state.mode || 'route');
@@ -626,7 +671,7 @@ async function passedHardBlocker(root, state) {
626
671
  function missingRequiredGateFields(file, state, gate = {}) {
627
672
  const mode = String(state?.mode || '').toUpperCase();
628
673
  if (file === 'team-gate.json' || mode === 'TEAM') {
629
- return ['analysis_artifact', 'triwiki_refreshed', 'triwiki_validated', 'consensus_artifact', 'implementation_team_fresh', 'review_artifact', 'integration_evidence', 'session_cleanup']
674
+ return ['team_roster_confirmed', 'analysis_artifact', 'triwiki_refreshed', 'triwiki_validated', 'consensus_artifact', 'implementation_team_fresh', 'review_artifact', 'integration_evidence', 'session_cleanup']
630
675
  .filter((key) => gate[key] !== true);
631
676
  }
632
677
  if (file === 'qa-gate.json' || mode === 'QALOOP') {
@@ -639,10 +684,11 @@ function missingRequiredGateFields(file, state, gate = {}) {
639
684
  async function missingRequiredGateArtifacts(root, file, state, gate = {}) {
640
685
  const mode = String(state?.mode || '').toUpperCase();
641
686
  if (file !== 'team-gate.json' && mode !== 'TEAM') return [];
642
- if (gate.session_cleanup !== true) return [];
643
- const cleanup = await readJson(path.join(missionDir(root, state.mission_id), TEAM_SESSION_CLEANUP_ARTIFACT), null);
644
- if (!cleanup) return [TEAM_SESSION_CLEANUP_ARTIFACT];
645
687
  const missing = [];
688
+ if (gate.team_roster_confirmed === true && !(await exists(path.join(missionDir(root, state.mission_id), 'team-roster.json')))) missing.push('team-roster.json');
689
+ if (gate.session_cleanup !== true) return missing;
690
+ const cleanup = await readJson(path.join(missionDir(root, state.mission_id), TEAM_SESSION_CLEANUP_ARTIFACT), null);
691
+ if (!cleanup) return [...missing, TEAM_SESSION_CLEANUP_ARTIFACT];
646
692
  if (cleanup.passed !== true) missing.push(`${TEAM_SESSION_CLEANUP_ARTIFACT}:passed`);
647
693
  if (cleanup.all_sessions_closed !== true && cleanup.outstanding_sessions !== 0) missing.push(`${TEAM_SESSION_CLEANUP_ARTIFACT}:all_sessions_closed`);
648
694
  if (cleanup.live_transcript_finalized !== true) missing.push(`${TEAM_SESSION_CLEANUP_ARTIFACT}:live_transcript_finalized`);
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { writeJsonAtomic, writeTextAtomic } from './fsx.mjs';
3
3
  import { buildQaLoopQuestionSchema } from './qa-loop.mjs';
4
+ import { hasFromChatImgSignal } from './routes.mjs';
4
5
 
5
6
  export function buildQuestionSchemaForRoute(route, prompt) {
6
7
  if (String(route?.id || '') === 'QALoop') return buildQaLoopQuestionSchema(prompt);
@@ -31,15 +32,21 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
31
32
  const questionGateWork = /모호|ambiguity|clarification|질문|triwiki|추론|infer|predict|예측|answers?\.json|decision-contract/.test(lower);
32
33
  const prioritySignalWork = /화|짜증|답답|;;|!!|강력|기억|우선|자주|반복|카운팅|count|frequency|frequent|priority|weight/.test(lower);
33
34
  const cliSurfaceWork = /\b(cli|command|route|usage|help|sks)\b|명령|커맨드|사용법/.test(lower);
34
- const kind = versionWork ? 'version' : prioritySignalWork ? 'priority' : questionGateWork ? 'questions' : installWork ? 'install' : null;
35
+ const chatCaptureWork = hasFromChatImgSignal(text)
36
+ && /(chat|conversation|message|messenger|kakao|screenshot|capture|채팅|대화|메신저|카톡|캡처|스크린샷)/i.test(text)
37
+ && /(image|photo|attachment|attached|이미지|사진|첨부)/i.test(text)
38
+ && /(client|customer|request|change|modify|fix|match|ocr|extract|text|고객사|클라이언트|요청|수정|변경|매칭|추출|글자|텍스트)/i.test(text);
39
+ const kind = versionWork ? 'version' : chatCaptureWork ? 'chat_capture' : prioritySignalWork ? 'priority' : questionGateWork ? 'questions' : installWork ? 'install' : null;
35
40
  const goals = {
36
41
  version: version ? `sneakoscope 버전을 ${version}로 올린다` : 'sneakoscope 버전을 다음 patch 버전으로 올린다',
42
+ chat_capture: 'From-Chat-IMG로 채팅 요구사항과 첨부 원본 이미지를 매칭해 고객사 작업 지시서를 만들고 반영한다',
37
43
  priority: '강한 불만과 반복 요청을 TriWiki 우선순위 신호로 기록한다',
38
44
  questions: '예측 가능한 답은 추론하고 실제 모호한 항목만 질문한다',
39
45
  install: 'SKS 최초 설치와 bootstrap을 한 번에 준비 상태까지 연결한다'
40
46
  };
41
47
  const criteria = {
42
48
  version: [version ? `version refs are ${version}` : 'version refs advance consistently', 'publish:dry gate passes', 'npm publish is not run'],
49
+ chat_capture: ['From-Chat-IMG activates chat-image intake only here', 'chat requirements are listed before implementation', 'screenshot regions are matched to attachments or marked low-confidence', 'Computer Use/browser visual inspection strengthens matches when available', 'client requests follow normal SKS gates and verification'],
43
50
  priority: ['strong feedback raises required_weight', 'request topics are counted in wiki packs', 'future inference uses priority signals'],
44
51
  questions: ['predictable answers are inferred', 'partial answers can seal contracts', 'only unresolved changing slots remain visible'],
45
52
  install: ['bootstrap/deps initialize readiness', 'missing runtime deps show repair actions', 'readiness output is concrete']
@@ -51,7 +58,7 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
51
58
  if (!hasAnswer(explicitAnswers.PUBLIC_API_CHANGE_ALLOWED)) addInferred(inferred, notes, 'PUBLIC_API_CHANGE_ALLOWED', cliSurfaceWork || installWork ? 'yes_if_needed' : 'no', 'public-api');
52
59
  if (!hasAnswer(explicitAnswers.DEPENDENCY_CHANGE_ALLOWED)) addInferred(inferred, notes, 'DEPENDENCY_CHANGE_ALLOWED', 'no', 'no-new-deps');
53
60
  if (!hasAnswer(explicitAnswers.TEST_SCOPE)) {
54
- const releaseLike = versionWork || installWork || questionGateWork || prioritySignalWork || /\bsneakoscope\b|\bsks\b/.test(lower);
61
+ const releaseLike = versionWork || installWork || questionGateWork || prioritySignalWork || chatCaptureWork || /\bsneakoscope\b|\bsks\b/.test(lower);
55
62
  addInferred(inferred, notes, 'TEST_SCOPE', releaseLike ? ['packcheck', 'selftest', 'sizecheck', 'publish:dry'] : ['focused relevant tests or documented justification'], 'tests');
56
63
  }
57
64
  if (!hasAnswer(explicitAnswers.MID_RALPH_UNKNOWN_POLICY)) {
@@ -43,9 +43,12 @@ export function triwikiContextTracking(commandPrefix = 'sks') {
43
43
  prune_command: `${prefix} wiki prune`,
44
44
  validate_command: `${prefix} wiki validate .sneakoscope/wiki/context-pack.json`,
45
45
  hydrate_policy: 'hydrate_by_id_hash_source_path_rgba_trig_coordinate',
46
+ required_schema: 'sks.wiki-coordinate.v1+vx:sks.wiki-voxel.v1',
46
47
  selected_text_policy: 'selected_text_is_only_the_visible_slice',
48
+ stack_current_docs: stackCurrentDocsPolicy(prefix),
47
49
  stage_policy: [
48
50
  'before_each_route_stage_read_relevant_context_pack',
51
+ 'require_latest_coordinate_plus_voxel_overlay_pack',
49
52
  'during_each_stage_hydrate_relevant_low_trust_claims_from_source',
50
53
  'after_new_findings_or_artifact_changes_refresh_or_pack',
51
54
  'before_each_handoff_validate_context_pack',
@@ -55,22 +58,63 @@ export function triwikiContextTracking(commandPrefix = 'sks') {
55
58
  };
56
59
  }
57
60
 
61
+
62
+ export function stackCurrentDocsPolicy(commandPrefix = 'sks') {
63
+ const prefix = String(commandPrefix || 'sks');
64
+ return {
65
+ trigger: 'when_tech_stack_is_added_or_package_framework_runtime_version_changes',
66
+ evidence_required: ['context7_resolve_library_id_and_query_docs', 'or_official_vendor_web_docs'],
67
+ memory_path: '.sneakoscope/memory/q2_facts/stack-current-docs.md',
68
+ refresh_command: `${prefix} wiki refresh`,
69
+ validate_command: `${prefix} wiki validate .sneakoscope/wiki/context-pack.json`,
70
+ priority: 'must_precede_coding_style_defaults',
71
+ examples: [
72
+ 'Supabase hosted projects should prefer sb_publishable_ and sb_secret_ keys over legacy anon/service_role keys when current docs apply.',
73
+ 'Next.js 16 deprecates the middleware file convention in favor of proxy.ts/proxy.js.',
74
+ 'Vercel Function duration limits, including the 300s default with Fluid Compute, are deployment constraints that must shape long-running server work.'
75
+ ]
76
+ };
77
+ }
78
+
79
+ export function stackCurrentDocsPolicyText(commandPrefix = 'sks') {
80
+ const policy = stackCurrentDocsPolicy(commandPrefix);
81
+ return `Stack current-docs policy: whenever project tech stack is added or a framework/package/runtime/platform version changes, fetch current docs with Context7 (resolve-library-id then query-docs) or official vendor web docs before coding, record the syntax/limits/security guidance as high-priority TriWiki claims in ${policy.memory_path}, run "${policy.refresh_command}", then "${policy.validate_command}". Treat these claims as higher priority than model-memory defaults. Examples include Supabase publishable/secret keys replacing legacy anon/service_role guidance for hosted projects, Next.js 16 proxy.ts/proxy.js replacing the deprecated middleware file convention, avoiding stale webpack defaults when newer framework guidance says otherwise, and Vercel Function duration limits such as the 300s default under Fluid Compute.`;
82
+ }
83
+
58
84
  export function triwikiContextTrackingText(commandPrefix = 'sks') {
59
85
  const ctx = triwikiContextTracking(commandPrefix);
60
- return `Context tracking SSOT: TriWiki. Use relevant TriWiki context at every work stage, not only at the first refresh: read ${ctx.default_pack} before each route phase, hydrate relevant low-trust claims from source during the phase, refresh with "${ctx.refresh_command}" or "${ctx.pack_command}" after new findings/artifact changes, prune stale/oversized wiki state with "${ctx.prune_command}" when retention matters, and validate with "${ctx.validate_command}" before each handoff or final claim. Selected text is only the visible slice; non-selected claims remain hydratable by id, hash, source path, and RGBA/trig coordinate. Follow high-trust claims unless newer source evidence contradicts them; low-trust claims should trigger source/evidence hydration before implementation or final claims.`;
86
+ return `Context tracking SSOT: TriWiki. Use only the latest TriWiki pack shape at every work stage: ${ctx.required_schema}; coordinate-only legacy packs are invalid and must be refreshed before use. Read ${ctx.default_pack} before each route phase, hydrate relevant low-trust claims from source during the phase, refresh with "${ctx.refresh_command}" or "${ctx.pack_command}" after new findings/artifact changes, prune stale/oversized wiki state with "${ctx.prune_command}" when retention matters, and validate with "${ctx.validate_command}" before each handoff or final claim. Selected text is only the visible slice; non-selected claims remain hydratable by id, hash, source path, and RGBA/trig coordinate. Follow high-trust claims unless newer source evidence contradicts them; low-trust claims should trigger source/evidence hydration before implementation or final claims. ${stackCurrentDocsPolicyText(commandPrefix)}`;
61
87
  }
62
88
 
63
89
  export function triwikiStagePolicyText(commandPrefix = 'sks') {
64
90
  const ctx = triwikiContextTracking(commandPrefix);
65
91
  return [
66
92
  'TriWiki stage policy:',
67
- `- Before each route phase, read the relevant parts of ${ctx.default_pack} instead of relying on memory or a one-time initial summary.`,
93
+ `- Before each route phase, read the relevant parts of ${ctx.default_pack} instead of relying on memory or a one-time initial summary; the pack must validate as ${ctx.required_schema}.`,
94
+ `- If a TriWiki pack is coordinate-only or lacks voxel overlay metadata, run "${ctx.refresh_command}" or "${ctx.pack_command}" and do not use the legacy pack for pipeline decisions.`,
68
95
  '- During the phase, when a decision touches a wiki claim, hydrate low-trust or stale claims from their source path/hash/RGBA anchor before relying on them.',
69
96
  `- After new findings, changed artifacts, scout results, debate conclusions, implementation changes, reviews, or blockers, run "${ctx.refresh_command}" or "${ctx.pack_command}" so later stages see the update.`,
97
+ `- When package manifests, framework versions, runtime targets, MCPs, SDKs, DB clients, or deployment platforms change, add current official docs or Context7 evidence to ${stackCurrentDocsPolicy(commandPrefix).memory_path}, refresh/validate TriWiki, and make those claims the coding baseline.`,
70
98
  `- Before every handoff and before final output, run or require "${ctx.validate_command}" and re-check high-impact claims against current sources.`
71
99
  ].join('\n');
72
100
  }
73
101
 
102
+ export function chatCaptureIntakeText() {
103
+ return 'From-Chat-IMG intake: explicit signal only. Treat uploads as chat screenshot plus originals, use Computer Use/browser visual inspection when available, list requirements first, match regions to attachments with confidence, write the work order, then continue Team. Do not assume ordinary image prompts are chat captures.';
104
+ }
105
+
106
+ export function hasFromChatImgSignal(prompt = '') {
107
+ return /(?:^|\s)\$?from-chat-img(?:\s|:|$)/i.test(String(prompt || ''));
108
+ }
109
+
110
+ export function looksLikeChatCaptureRequest(prompt = '') {
111
+ const text = String(prompt || '');
112
+ return hasFromChatImgSignal(text)
113
+ && /(chat|conversation|message|messenger|kakao|slack|discord|whatsapp|채팅|대화|메신저|카톡|캡처|스크린샷)/i.test(text)
114
+ && /(image|photo|screenshot|capture|attachment|attached|이미지|사진|첨부)/i.test(text)
115
+ && /(client|customer|request|change|modify|fix|match|ocr|extract|text|work\s*order|고객사|클라이언트|요청|수정|변경|매칭|추출|글자|텍스트|작업|지시서)/i.test(text);
116
+ }
117
+
74
118
  export const ROUTES = [
75
119
  {
76
120
  id: 'DFix',
@@ -121,12 +165,14 @@ export const ROUTES = [
121
165
  route: 'multi-agent team orchestration',
122
166
  description: 'Run parallel analysis scouts, refresh TriWiki, debate, form a fresh executor team, then clean up team sessions before final evidence.',
123
167
  requiredSkills: ['team', 'pipeline-runner', 'context7-docs', 'prompt-pipeline', REFLECTION_SKILL_NAME, 'honest-mode'],
168
+ dollarAliases: ['$From-Chat-IMG'],
169
+ appSkillAliases: ['from-chat-img'],
124
170
  lifecycle: ['parallel_analysis_scouting', 'triwiki_refresh', 'planning_debate', 'live_transcript', 'consensus_artifact', 'fresh_implementation_team', 'review_artifact', 'integration_evidence', 'session_cleanup', 'post_route_reflection', 'honest_mode'],
125
171
  context7Policy: 'required',
126
172
  reasoningPolicy: 'high',
127
173
  stopGate: 'team-gate.json',
128
174
  cliEntrypoint: 'sks team "task" [executor:5 reviewer:2 user:1] | sks team log|tail|watch|status|event',
129
- examples: ['$Team executor:5 agree on the best plan and implement it']
175
+ examples: ['$Team executor:5 agree on the best plan and implement it', '$From-Chat-IMG 채팅+첨부 이미지 작업 지시서']
130
176
  },
131
177
  {
132
178
  id: 'QALoop',
@@ -242,7 +288,10 @@ export const ROUTES = [
242
288
  }
243
289
  ];
244
290
 
245
- export const DOLLAR_COMMANDS = ROUTES.map(({ command, route, description }) => ({ command, route, description }));
291
+ export const DOLLAR_COMMANDS = ROUTES.flatMap(({ command, route, description, dollarAliases = [] }) => [
292
+ { command, route, description },
293
+ ...dollarAliases.map((alias) => ({ command: alias, route, description }))
294
+ ]);
246
295
  export const DOLLAR_SKILL_NAMES = ROUTES.flatMap((route) => [
247
296
  dollarSkillName(route.command),
248
297
  ...(route.appSkillAliases || [])
@@ -309,7 +358,11 @@ export function routeById(id) {
309
358
 
310
359
  export function routeByDollarCommand(commandName) {
311
360
  const key = String(commandName || '').replace(/^\$/, '').toLowerCase();
312
- return ROUTES.find((route) => dollarSkillName(route.command) === key) || null;
361
+ return ROUTES.find((route) => [
362
+ dollarSkillName(route.command),
363
+ ...(route.dollarAliases || []).map((alias) => dollarSkillName(alias)),
364
+ ...(route.appSkillAliases || [])
365
+ ].includes(key)) || null;
313
366
  }
314
367
 
315
368
  export function dollarCommand(prompt) {
@@ -336,10 +389,13 @@ export function routePrompt(prompt) {
336
389
  if (route?.id === 'SKS' && looksLikeTeamDefaultWork(stripDollarCommand(text))) return routeById('Team');
337
390
  return route;
338
391
  }
392
+ if (hasFromChatImgSignal(text)) return routeById('Team');
339
393
  if (looksLikeFastDesignFix(text)) return routeById('DFix');
394
+ if (looksLikeQuestionShapedDirective(text)) return routeById('Team');
340
395
  if (looksLikeAnswerOnlyRequest(text)) return routeById('Answer');
341
396
  if (/\b(SQL|Supabase|Postgres|migration|RLS|Prisma|Drizzle|Knex|database|DB|execute_sql|mcp)\b/i.test(text)) return routeById('DB');
342
397
  if (/\b(team|multi-agent|subagent|parallel agents|agent team)\b|병렬|팀/i.test(text)) return routeById('Team');
398
+ if (looksLikeChatCaptureRequest(text) && !looksLikeAnswerOnlyRequest(text)) return routeById('Team');
343
399
  if (/\b(qa[-\s]?loop|qaloop|e2e\s+qa|qa\s+e2e)\b/i.test(text)) return routeById('QALoop');
344
400
  if (/\b(autoresearch|experiment|benchmark|SEO|GEO|ranking|optimi[sz]e|improve metric|discoverability|visibility|github stars?|npm downloads?|검색|노출|스타|다운로드)\b/i.test(text)) return routeById('AutoResearch');
345
401
  if (/\b(research|hypothesis|falsify|novelty|frontier|조사|연구)\b/i.test(text)) return routeById('Research');
@@ -359,14 +415,26 @@ export function looksLikeTeamDefaultWork(prompt = '') {
359
415
  export function looksLikeAnswerOnlyRequest(prompt = '') {
360
416
  const text = String(prompt || '').trim();
361
417
  if (!text) return false;
418
+ if (looksLikeQuestionShapedDirective(text)) return false;
362
419
  const infoCue = /(왜|뭐야|무엇|뭔가|어떤|어떻게|언제|어디|누구|얼마|가능해|맞아|인가|인지|차이|의미|원리|이유|방법|설명|알려줘|요약|정리|비교|찾아줘|찾아봐|검색|조사|근거|출처|fact|source|cite|explain|what|why|how|when|where|who|which|whether|compare|summari[sz]e|search|look up|research|tell me|question|\?)/i.test(text);
363
420
  if (!infoCue) return false;
364
421
  return !looksLikeDirectWorkRequest(text);
365
422
  }
366
423
 
424
+ export function looksLikeQuestionShapedDirective(prompt = '') {
425
+ const text = String(prompt || '').trim();
426
+ if (!text) return false;
427
+ const directive = /(반드시|필수|무조건|해야\s*(?:해|함|돼|한다|하지|한다는|되는)|해야지|해야돼|해야한다|알지|기억해|파악해야|구분해야|막아야|보장해야|강제|기본적으로)/i.test(text);
428
+ const pipelineCue = /(질문|질문형|암묵|지시|파이프라인|라우팅|route|routing|team|팀|sks|기본|구성|게이트|gate|작업|수정|구현|실행)/i.test(text);
429
+ const complaint = /(왜|근데|그런데).*(안\s*하|안\s*되|없이|누락|빠뜨|생략|스킵|못\s*하).*(많|자주|계속|이렇게|함|하지|하냐|하니|\?)/i.test(text);
430
+ return (directive && pipelineCue) || complaint;
431
+ }
432
+
367
433
  export function looksLikeDirectWorkRequest(prompt = '') {
368
434
  const text = String(prompt || '');
369
435
  return looksLikeCodeChangingWork(text)
436
+ || looksLikeChatCaptureRequest(text)
437
+ || looksLikeQuestionShapedDirective(text)
370
438
  || /(작업|파이프라인|구현|수정|변경|추가|적용|반영|처리|수행|검수|설치|리드미|README).*(해줘|해달|해라|해야|되게|줘야|줘야지|달라)/i.test(text)
371
439
  || /(진행해|수행해|작업해|처리해|적용해|반영해|검수해|고쳐줘|바꿔줘|만들어줘|해줘야|해줘야지|해달라|해야지|되게 해|install|run|execute|test|deploy|commit|push)/i.test(text);
372
440
  }
@@ -457,7 +525,10 @@ export function formatDollarCommandsCompact(indent = '') {
457
525
  }
458
526
 
459
527
  export function dollarCommandNames() {
460
- return DOLLAR_COMMANDS.map((c) => c.command).join(', ');
528
+ return Array.from(new Set([
529
+ ...DOLLAR_COMMANDS.map((c) => c.command),
530
+ ...DOLLAR_COMMAND_ALIASES.map((alias) => alias.app_skill)
531
+ ])).join(', ');
461
532
  }
462
533
 
463
534
  export function context7ConfigToml(transport = 'local') {
@@ -361,7 +361,8 @@ export function validateWikiCoordinateIndex(index = {}) {
361
361
  validateTrustFields(anchor, issues);
362
362
  }
363
363
  const voxel = index.vx || index.voxel_overlay;
364
- if (voxel) {
364
+ if (!voxel) issues.push({ id: 'vx_missing', severity: 'error' });
365
+ else {
365
366
  const voxelValidation = validateWikiVoxelOverlay(voxel, seen);
366
367
  for (const issue of voxelValidation.issues) issues.push(issue);
367
368
  }