sneakoscope 0.6.44 → 0.6.52
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 +106 -1
- package/package.json +1 -1
- package/src/cli/main.mjs +114 -17
- package/src/core/evaluation.mjs +2 -3
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +18 -1
- package/src/core/init.mjs +26 -19
- package/src/core/pipeline.mjs +60 -14
- package/src/core/questions.mjs +9 -2
- package/src/core/routes.mjs +77 -6
- package/src/core/wiki-coordinate.mjs +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
Codex CLI/App harness for `$` routes, Team/Ralph/QA/Research, Context7, Honest Mode, DB safety, and
|
|
5
|
+
Codex CLI/App harness for `$` routes, Team/Ralph/QA/Research, Context7, Honest Mode, DB safety, TriWiki, Codex App skills, and release readiness.
|
|
6
6
|
|
|
7
7
|
Install: `npm i -g sneakoscope && sks bootstrap`
|
|
8
8
|
Fallback: `npx -y -p sneakoscope sks bootstrap`
|
|
@@ -10,3 +10,108 @@ Project: `npm i -D sneakoscope && npx sks setup --install-scope project`
|
|
|
10
10
|
|
|
11
11
|
Discover: `sks commands`, `sks dollar-commands`, `sks usage <topic>`
|
|
12
12
|
Check: `sks deps check`, `sks doctor --fix`, `sks selftest --mock`
|
|
13
|
+
|
|
14
|
+
## What It Adds
|
|
15
|
+
|
|
16
|
+
Sneakoscope (`sks`, displayed as `ㅅㅋㅅ`) wraps Codex with a repeatable control surface:
|
|
17
|
+
|
|
18
|
+
| Area | What it does |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Codex App commands | Installs generated skills so `$Team`, `$DFix`, `$QA-LOOP`, `$Ralph`, `$DB`, `$Wiki`, `$Help`, and related routes are discoverable in prompt workflows. |
|
|
21
|
+
| CLI commands | Provides `sks commands`, `sks dollar-commands`, `sks usage <topic>`, bootstrap, setup, doctor, deps, selftest, wiki, team, QA, Ralph, DB, and GX commands. |
|
|
22
|
+
| Team orchestration | Routes substantial code work through ambiguity removal, scouts, TriWiki refresh, debate, consensus, implementation, review, integration, reflection, and Honest Mode. |
|
|
23
|
+
| Ralph | Seals a decision contract up front, then continues without more user questions by using the agreed decision ladder. |
|
|
24
|
+
| QA loop | Dogfoods UI/API behavior with safety boundaries, evidence capture, safe remediation, and focused rechecks. |
|
|
25
|
+
| TriWiki | Keeps `.sneakoscope/wiki/context-pack.json` as the context SSOT, with refresh, pack, prune, validate, and hydratable source-backed claims. |
|
|
26
|
+
| Context7 | Requires current external library/API/framework docs for routes whose correctness depends on live package or platform behavior. |
|
|
27
|
+
| DB safety | Treats SQL, migrations, Supabase, RLS, and destructive operations as high risk; defaults to inspection and guarded local/branch-safe migration work. |
|
|
28
|
+
| Honest Mode | Finishes work with a claim/evidence pass that separates verified facts, unsupported claims, blocked checks, and not-applicable items. |
|
|
29
|
+
| GX visual context | Generates deterministic visual context cartridges for structured visual review and drift checks. |
|
|
30
|
+
| Research loops | Supports Research and AutoResearch workflows with hypotheses, experiments, falsification, novelty ledgers, SEO/GEO, and evidence-backed conclusions. |
|
|
31
|
+
| Release hygiene | Checks versioning, changelog, package contents, tarball size, syntax, selftests, and dry-run packaging before publish. |
|
|
32
|
+
|
|
33
|
+
## Prompt `$` Commands
|
|
34
|
+
|
|
35
|
+
Use these inside Codex App or another agent prompt. They are prompt commands, not terminal commands.
|
|
36
|
+
|
|
37
|
+
| Prompt | Purpose |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `$Team` | Default route for code-changing work and substantial implementation. |
|
|
40
|
+
| `$From-Chat-IMG` | Team alias for chat screenshot plus original attachment intake. |
|
|
41
|
+
| `$DFix` | Tiny design/content fixes: labels, copy, colors, spacing, translation. |
|
|
42
|
+
| `$Answer` | Answer-only route when no implementation should start. |
|
|
43
|
+
| `$SKS` | Setup, status, usage, and Sneakoscope workflow help. |
|
|
44
|
+
| `$QA-LOOP` | UI/API dogfooding, safe fixes, and rechecks. |
|
|
45
|
+
| `$Ralph` | Clarify once, seal a decision contract, then execute. |
|
|
46
|
+
| `$Research` | Frontier-style research with hypotheses and falsification. |
|
|
47
|
+
| `$AutoResearch` | Iterative improve-test-keep/discard optimization loop. |
|
|
48
|
+
| `$DB` | Database and Supabase safety checks. |
|
|
49
|
+
| `$GX` | Deterministic visual context generation and validation. |
|
|
50
|
+
| `$Wiki` | TriWiki refresh, pack, prune, validate, and maintenance. |
|
|
51
|
+
| `$Help` | Installed command and workflow explanation. |
|
|
52
|
+
|
|
53
|
+
Run `sks dollar-commands` to verify the terminal and Codex App command surfaces agree.
|
|
54
|
+
|
|
55
|
+
## Terminal Examples
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
sks usage install
|
|
59
|
+
sks usage team
|
|
60
|
+
sks usage qa-loop
|
|
61
|
+
sks usage codex-app
|
|
62
|
+
sks setup --install-scope project
|
|
63
|
+
sks wiki refresh
|
|
64
|
+
sks wiki validate .sneakoscope/wiki/context-pack.json
|
|
65
|
+
sks versioning status
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Route examples:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
sks team "implement this" executor:3 reviewer:1
|
|
72
|
+
sks team watch <mission-id>
|
|
73
|
+
sks qa-loop prepare
|
|
74
|
+
sks qa-loop run
|
|
75
|
+
sks ralph prepare
|
|
76
|
+
sks ralph run
|
|
77
|
+
sks db scan
|
|
78
|
+
sks gx init
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Workflow Rules
|
|
82
|
+
|
|
83
|
+
For code work, Sneakoscope defaults to Team. The normal flow is: remove ambiguity that can change scope or safety, read/validate TriWiki, gather current source evidence, implement bounded changes, refresh/validate context after meaningful findings, run relevant checks, then finish with reflection and Honest Mode.
|
|
84
|
+
|
|
85
|
+
For tiny text/design edits use `$DFix`. For questions that should not change files use `$Answer`.
|
|
86
|
+
|
|
87
|
+
## Codex App Surface
|
|
88
|
+
|
|
89
|
+
`sks bootstrap` and `sks setup` install `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, route instructions for `$` commands, and user-home skill state for first-install discoverability.
|
|
90
|
+
|
|
91
|
+
After install, check:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
sks dollar-commands
|
|
95
|
+
sks usage codex-app
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Release Checks
|
|
99
|
+
|
|
100
|
+
Before publish:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
npm run publish:dry
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This runs repo audit, changelog check, syntax packcheck, mock selftest, sizecheck, and `npm pack --dry-run`. A dry run proves the local package is packable; npm account ownership or OTP can still block the real registry upload.
|
|
107
|
+
|
|
108
|
+
## Requirements
|
|
109
|
+
|
|
110
|
+
- Node.js `>=20.11`
|
|
111
|
+
- npm
|
|
112
|
+
- Codex CLI/App for app-facing workflows
|
|
113
|
+
- Context7 MCP for current-docs-gated routes
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.52",
|
|
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,
|
|
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
|
|
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
|
|
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 !== '
|
|
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
|
-
|
|
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-
|
|
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;
|
|
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
|
|
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
|
|
package/src/core/evaluation.mjs
CHANGED
|
@@ -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.
|
|
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(
|
|
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.
|
|
8
|
+
export const PACKAGE_VERSION = '0.6.52';
|
|
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 (/
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
463
|
-
`Context Tracking: TriWiki SSOT. Before each route phase read .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
|
|
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
|
|
476
|
-
'
|
|
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
|
|
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
|
|
484
|
-
'reasoning-router': `---\nname: reasoning-router\ndescription: Temporary SKS reasoning-effort routing for every command and pipeline route.\n---\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
|
|
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,
|
|
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
|
`;
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
86
|
-
|
|
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: '
|
|
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
|
|
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`);
|
package/src/core/questions.mjs
CHANGED
|
@@ -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
|
|
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)) {
|
package/src/core/routes.mjs
CHANGED
|
@@ -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
|
|
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.
|
|
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) =>
|
|
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
|
|
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
|
}
|