sneakoscope 0.7.57 → 0.7.59
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 +6 -4
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +5 -1
- package/src/cli/main.mjs +100 -66
- package/src/cli/maintenance-commands.mjs +23 -6
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +6 -5
- package/src/core/init.mjs +33 -12
- package/src/core/pipeline.mjs +83 -83
- package/src/core/ppt.mjs +12 -59
- package/src/core/qa-loop.mjs +103 -6
- package/src/core/questions.mjs +60 -16
- package/src/core/routes.mjs +23 -11
- package/src/core/skill-forge.mjs +1 -1
- package/src/core/team-live.mjs +9 -0
- package/src/core/tmux-ui.mjs +21 -29
- package/src/core/triwiki-attention.mjs +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ sks selftest --mock
|
|
|
42
42
|
|
|
43
43
|
| Area | What it does |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches a
|
|
45
|
+
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches a single-pane MAD tmux session with the explicit full-access high-reasoning profile. Split panes are reserved for active Team scout/worker lanes. |
|
|
46
46
|
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Image-UX-Review`, `$UX-Review`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. `sks codex-app remote-control` wraps Codex CLI 0.130.0+ headless remote control without falling back to older app-server internals. |
|
|
47
47
|
| OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
|
|
48
48
|
| Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
|
|
@@ -80,7 +80,7 @@ The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex`
|
|
|
80
80
|
- Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
|
|
81
81
|
- Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
|
|
82
82
|
- Requires tmux 3.x or newer before opening the session.
|
|
83
|
-
- Creates a named detached tmux
|
|
83
|
+
- Creates a named detached single-pane tmux session and prints only the session, gate, attach, and blocker details needed to act.
|
|
84
84
|
|
|
85
85
|
## Installation
|
|
86
86
|
|
|
@@ -211,7 +211,7 @@ sks --mad
|
|
|
211
211
|
sks --mad --yes
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux
|
|
214
|
+
This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches a single Codex CLI pane. The session recreates the named session on launch so stale split-pane MAD sessions collapse back to the single-pane default, then attaches in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active.
|
|
215
215
|
|
|
216
216
|
MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
|
|
217
217
|
|
|
@@ -287,7 +287,7 @@ The design borrows two useful ideas from external planning systems without copyi
|
|
|
287
287
|
|
|
288
288
|
`sks goal` and `$Goal` only prepare/control the native `/goal` persistence bridge. They do not replace Team, QA, DB, or other implementation routes; use the selected execution route for the actual work and verification. Context7 is only needed for Goal when external API/library documentation becomes relevant.
|
|
289
289
|
|
|
290
|
-
Use `$Computer-Use` or `$CU` inside Codex App when the task specifically needs Codex Computer Use speed for UI/browser/visual work. This lane intentionally skips Team debate, QA-LOOP clarification, subagents, and upfront TriWiki refresh. It still requires Codex Computer Use as the evidence source, and it defers TriWiki refresh/validate plus Honest Mode to the final closeout.
|
|
290
|
+
Use `$Computer-Use` or `$CU` inside Codex App when the task specifically needs Codex Computer Use speed for UI/browser/visual work. This lane intentionally skips Team debate, QA-LOOP clarification, subagents, and upfront TriWiki refresh. It still requires Codex Computer Use as the evidence source, and it defers TriWiki refresh/validate plus Honest Mode to the final closeout. SKS does not install a generated skill named `computer-use`, because that name is reserved for the first-party Codex Computer Use plugin; use `$CU` or `$computer-use-fast` from the SKS picker for the SKS route, and use `@Computer` for the OpenAI plugin.
|
|
291
291
|
|
|
292
292
|
### Create A Presentation
|
|
293
293
|
|
|
@@ -555,6 +555,8 @@ codex mcp list
|
|
|
555
555
|
|
|
556
556
|
Codex App workflows need the app installed. QA and UI/browser visual-evidence workflows require first-party Codex Computer Use; Browser Use may support non-UI browser context, but it is not valid UI/browser verification evidence. Generated raster assets and image-review evidence require real Codex App `$imagegen`/`gpt-image-2` output, or the route must stay blocked/unverified.
|
|
557
557
|
|
|
558
|
+
SKS setup removes old SKS-generated `computer-use` skills from `.agents/skills` so they cannot shadow the first-party Computer Use plugin. If a running Codex App thread was opened before setup or upgrade, start a fresh thread and invoke `@Computer` or Browser again so the host reloads plugin tools.
|
|
559
|
+
|
|
558
560
|
### Setup is blocked by another harness
|
|
559
561
|
|
|
560
562
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.59",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -36,7 +36,11 @@ export async function postinstall({ bootstrap }) {
|
|
|
36
36
|
else if (fastModeRepair.status === 'skipped') console.log(`Codex App Fast mode: skipped (${fastModeRepair.reason}).`);
|
|
37
37
|
else if (fastModeRepair.status === 'failed') console.log(`Codex App Fast mode: auto repair failed. Run \`sks setup\`. ${fastModeRepair.error || ''}`.trim());
|
|
38
38
|
const globalSkills = await ensureGlobalCodexSkillsDuringInstall();
|
|
39
|
-
if (globalSkills.status === 'installed')
|
|
39
|
+
if (globalSkills.status === 'installed') {
|
|
40
|
+
const removed = globalSkills.removed_stale_generated_skills || [];
|
|
41
|
+
const cleanup = removed.length ? ` Removed stale generated skill shadow(s): ${removed.join(', ')}.` : '';
|
|
42
|
+
console.log(`Codex App global $ skills: installed in ${globalSkills.root} (${globalSkills.installed_count} skills).${cleanup}`);
|
|
43
|
+
}
|
|
40
44
|
else if (globalSkills.status === 'partial') console.log(`Codex App global $ skills: partial in ${globalSkills.root}; missing ${globalSkills.missing_skills.join(', ')}. Run \`sks doctor --fix\`.`);
|
|
41
45
|
else if (globalSkills.status === 'skipped') console.log(`Codex App global $ skills: skipped (${globalSkills.reason}).`);
|
|
42
46
|
else if (globalSkills.status === 'failed') console.log(`Codex App global $ skills: auto setup failed. Run \`sks doctor --fix\`. ${globalSkills.error || ''}`.trim());
|
package/src/cli/main.mjs
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
} from '../core/image-ux-review.mjs';
|
|
53
53
|
import { contextCapsule } from '../core/triwiki-attention.mjs';
|
|
54
54
|
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
|
|
55
|
-
import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, COMMAND_CATALOG, DESIGN_SYSTEM_SSOT, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, GETDESIGN_REFERENCE, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
55
|
+
import { ALLOWED_REASONING_EFFORTS, AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_COMPUTER_USE_EVIDENCE_SOURCE, CODEX_COMPUTER_USE_ONLY_POLICY, CODEX_IMAGEGEN_REQUIRED_POLICY, COMMAND_CATALOG, DESIGN_SYSTEM_SSOT, DOLLAR_COMMAND_ALIASES, DOLLAR_COMMANDS, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, GETDESIGN_REFERENCE, PPT_PIPELINE_SKILL_ALLOWLIST, RECOMMENDED_SKILLS, ROUTES, USAGE_TOPICS, context7ConfigToml, hasContext7ConfigText, hasFromChatImgSignal, looksLikeAnswerOnlyRequest, noUnrequestedFallbackCodePolicyText, reflectionRequiredForRoute, reasoningInstruction, routePrompt, routeReasoning, routeRequiresSubagents, speedLanePolicyText, stackCurrentDocsPolicy, stripVisibleDecisionAnswerBlocks, triwikiContextTracking } from '../core/routes.mjs';
|
|
56
56
|
import { PIPELINE_PLAN_ARTIFACT, buildPipelinePlan, context7Evidence, evaluateStop, projectGateStatus, recordContext7Evidence, recordSubagentEvidence, validatePipelinePlan, writePipelinePlan } from '../core/pipeline.mjs';
|
|
57
57
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, validateTeamRuntimeArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
|
|
58
58
|
import { appendTeamEvent, initTeamLive, parseTeamSpecText, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamWatch } from '../core/team-live.mjs';
|
|
@@ -283,7 +283,7 @@ async function wizard(args = []) {
|
|
|
283
283
|
const update = await askChoice(rl, 'Update SKS before setup?', ['yes', 'no'], 'yes');
|
|
284
284
|
if (update === 'yes') {
|
|
285
285
|
console.log('\nRun this update command, then rerun `sks`:');
|
|
286
|
-
console.log(' npm i -g sneakoscope\n');
|
|
286
|
+
console.log(' npm i -g sneakoscope@latest\n');
|
|
287
287
|
return;
|
|
288
288
|
}
|
|
289
289
|
console.log('Skipping update for this setup run.\n');
|
|
@@ -345,10 +345,10 @@ async function updateCheck(args = []) {
|
|
|
345
345
|
console.log(`Latest: ${result.latest || 'unknown'}`);
|
|
346
346
|
console.log(`Update: ${result.update_available ? 'available' : 'not needed'}`);
|
|
347
347
|
if (result.error) console.log(`Error: ${result.error}`);
|
|
348
|
-
if (result.update_available) console.log('Run: npm i -g sneakoscope');
|
|
348
|
+
if (result.update_available) console.log('Run: npm i -g sneakoscope@latest');
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline:
|
|
351
|
+
const DOLLAR_DEFAULT_PIPELINE_TEXT = 'Default pipeline: direct answers -> $Answer, tiny Direct Fix edits -> $DFix, presentation/PDF artifacts -> $PPT, image-generation UI/UX reviews -> $Image-UX-Review/$UX-Review, Computer Use UI/browser speed work -> $Computer-Use, code -> $Team. Execution routes infer their contract from prompt, TriWiki/current-code defaults, and conservative policy instead of surfacing prequestion sheets. Use $From-Chat-IMG only for chat screenshot plus original attachments. Use $MAD-SKS only as an explicit scoped DB authorization modifier that can be combined with another $ route. No route may invent unrequested fallback implementation code.';
|
|
352
352
|
|
|
353
353
|
function commands(args = []) {
|
|
354
354
|
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));
|
|
@@ -1450,9 +1450,9 @@ function usage(args = []) {
|
|
|
1450
1450
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew after Y/n approval for missing installs or Homebrew-managed upgrades. If PATH resolves an npm-managed tmux, SKS prompts for npm i -g tmux@latest instead. Unknown non-Homebrew tmux paths are reported as conflicts.'],
|
|
1451
1451
|
tmux: ['tmux', '', ' sks', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'Running bare `sks` opens or reuses the default tmux Codex CLI session in fast-high mode: --model gpt-5.5 -c model_reasoning_effort="high". SKS always forces gpt-5.5; SKS_CODEX_MODEL and SKS_CODEX_FAST_HIGH=0 cannot downgrade or remove that model pin. Use SKS_CODEX_REASONING only for reasoning effort. Before launch, SKS checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. Use `sks tmux open` when you need explicit session/workspace flags, and `sks help` for CLI help.'],
|
|
1452
1452
|
openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
|
|
1453
|
-
team: ['Team', '', ' sks team "task" executor:5 reviewer:6 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team
|
|
1453
|
+
team: ['Team', '', ' sks team "task" executor:5 reviewer:6 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team auto-seals a route contract, opens scout-first tmux lanes when available, then runs scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1454
1454
|
'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
|
|
1455
|
-
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT
|
|
1455
|
+
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT infers delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings before source research, design-system work, HTML/PDF export, render QA, fact-ledger validation, and bounded review-loop validation. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in ppt-parallel-report.json. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; Codex App $imagegen/gpt-image-2 and Context7 are conditional only when the sealed PPT contract needs raster assets, slide visual critique, or current external docs. Missing required $imagegen/gpt-image-2 output blocks instead of being simulated.'],
|
|
1456
1456
|
'image-ux-review': ['Image UX Review', '', ' $Image-UX-Review localhost 화면을 이미지 생성 리뷰 루프로 검수해줘', ' $UX-Review 이 스크린샷을 gpt-image-2 콜아웃 리뷰로 분석하고 고쳐줘', ' sks image-ux-review status latest --json', '', '$Image-UX-Review captures or receives source UI screenshots, runs Codex App $imagegen/gpt-image-2 to create generated annotated review images with numbered callouts, then extracts those generated images into image-ux-issue-ledger.json. Text-only screenshot critique cannot pass image-ux-review-gate.json; missing generated review images remain an explicit blocker.'],
|
|
1457
1457
|
goal: ['Goal', '', ' sks goal create "task"', ' sks goal status latest', ' sks goal pause latest', ' sks goal resume latest', ' sks goal clear latest'],
|
|
1458
1458
|
'codex-app': ['Codex App', '', ' sks bootstrap', ' sks codex-app check', ' sks codex-app remote-control --status', ' sks dollar-commands', ' cat .codex/SNEAKOSCOPE.md'],
|
|
@@ -1720,7 +1720,11 @@ async function doctor(args) {
|
|
|
1720
1720
|
console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
|
|
1721
1721
|
console.log(`Conflicts: ${result.harness_conflicts.hard_block ? 'blocked' : 'ok'} ${result.harness_conflicts.conflicts.length} finding(s)`);
|
|
1722
1722
|
if (repairApplied) console.log('Repair: regenerated SKS managed files from the installed package template');
|
|
1723
|
-
if (globalSkillsRepair)
|
|
1723
|
+
if (globalSkillsRepair) {
|
|
1724
|
+
const removed = globalSkillsRepair.removed_stale_generated_skills || [];
|
|
1725
|
+
const cleanup = removed.length ? ` removed stale generated skill shadow(s): ${removed.join(', ')}` : '';
|
|
1726
|
+
console.log(`Global $ repair: ${globalSkillsRepair.status} ${globalSkillsRepair.root || ''}${cleanup}`.trimEnd());
|
|
1727
|
+
}
|
|
1724
1728
|
if (flag(args, '--fix') && result.harness_conflicts.hard_block) console.log('Repair: skipped because another Codex harness needs human-approved removal first');
|
|
1725
1729
|
console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
|
|
1726
1730
|
console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
|
|
@@ -1982,11 +1986,10 @@ async function selftest() {
|
|
|
1982
1986
|
};
|
|
1983
1987
|
for (let i = 0; i < 5; i++) {
|
|
1984
1988
|
const stop = await evaluateStop(tmp, clarificationState, { last_assistant_message: 'continuing implementation without visible questions' });
|
|
1985
|
-
if (stop?.
|
|
1989
|
+
if (stop?.gate === 'clarification' || /ambiguity|clarification|question/i.test(String(stop?.reason || ''))) throw new Error('selftest failed: stale clarification gate still hard-paused without visible questions');
|
|
1986
1990
|
}
|
|
1987
|
-
if (await exists(path.join(clarificationMission.dir, 'hard-blocker.json'))) throw new Error('selftest failed: clarification gate used compliance hard-blocker instead of waiting for answers');
|
|
1988
1991
|
const visibleQuestionStop = await evaluateStop(tmp, clarificationState, { last_assistant_message: 'Required questions still pending:\n1. GOAL_PRECISE: What should be changed?\n\nReply by slot id; I will seal the contract with sks pipeline answer latest --stdin.' });
|
|
1989
|
-
if (visibleQuestionStop?.
|
|
1992
|
+
if (visibleQuestionStop?.gate === 'clarification' || /ambiguity|clarification/i.test(String(visibleQuestionStop?.reason || ''))) throw new Error('selftest failed: visible stale clarification wording still blocked stop');
|
|
1990
1993
|
await setCurrent(tmp, loopState);
|
|
1991
1994
|
const dfixPromptHook = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'user-prompt-submit'], {
|
|
1992
1995
|
cwd: tmp,
|
|
@@ -2025,12 +2028,14 @@ async function selftest() {
|
|
|
2025
2028
|
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'), '---\nname: agent-team\ndescription: Fallback Codex App picker alias for $Team.\n---\n');
|
|
2026
2029
|
await ensureDir(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated'));
|
|
2027
2030
|
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'), '---\nname: stale-sks-generated\ndescription: Old SKS generated skill that should disappear on update.\n---\n');
|
|
2031
|
+
await ensureDir(path.join(repairTmp, '.agents', 'skills', 'computer-use'));
|
|
2032
|
+
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'), '---\nname: computer-use\ndescription: Maximum-speed $Computer-Use/$CU lane for Codex Computer Use UI/browser/visual tasks.\n---\n');
|
|
2028
2033
|
await writeJsonAtomic(path.join(repairTmp, '.agents', 'skills', '.sks-generated.json'), {
|
|
2029
2034
|
schema_version: 1,
|
|
2030
2035
|
generated_by: 'sneakoscope',
|
|
2031
2036
|
version: '0.0.1',
|
|
2032
|
-
skills: ['team', 'stale-sks-generated'],
|
|
2033
|
-
files: ['.agents/skills/team/SKILL.md', '.agents/skills/stale-sks-generated/SKILL.md']
|
|
2037
|
+
skills: ['team', 'stale-sks-generated', 'computer-use'],
|
|
2038
|
+
files: ['.agents/skills/team/SKILL.md', '.agents/skills/stale-sks-generated/SKILL.md', '.agents/skills/computer-use/SKILL.md']
|
|
2034
2039
|
});
|
|
2035
2040
|
const staleCodexAgentRel = '.codex/agents/stale-generated.toml';
|
|
2036
2041
|
await writeTextAtomic(path.join(repairTmp, staleCodexAgentRel), 'name = "stale_generated"\n');
|
|
@@ -2051,6 +2056,8 @@ async function selftest() {
|
|
|
2051
2056
|
await writeJsonAtomic(path.join(repairTmp, '.sneakoscope', 'policy.json'), { broken: true });
|
|
2052
2057
|
const existingAgentsMd = await safeReadText(path.join(repairTmp, 'AGENTS.md'));
|
|
2053
2058
|
await writeTextAtomic(path.join(repairTmp, 'AGENTS.md'), existingAgentsMd.replace(/<!-- BEGIN Sneakoscope Codex GX MANAGED BLOCK -->[\s\S]*?<!-- END Sneakoscope Codex GX MANAGED BLOCK -->\n?/, '<!-- BEGIN Sneakoscope Codex GX MANAGED BLOCK -->\ntampered managed block\n<!-- END Sneakoscope Codex GX MANAGED BLOCK -->\n'));
|
|
2059
|
+
const stalePluginSkillNames = ['computer-use', 'browser-use', 'browser'];
|
|
2060
|
+
const stalePluginSkillContent = (name) => `---\nname: ${name}\ndescription: Sneakoscope generated stale plugin collision for selftest.\n---\n\nCodex App pipeline activation:\n- stale selftest marker\n`;
|
|
2054
2061
|
const doctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--local-only', '--json'], {
|
|
2055
2062
|
cwd: repairTmp,
|
|
2056
2063
|
env: { HOME: path.join(repairTmp, 'home'), SKS_DISABLE_UPDATE_CHECK: '1' },
|
|
@@ -2066,6 +2073,7 @@ async function selftest() {
|
|
|
2066
2073
|
if (!repairedTeamSkill.includes('SKS Team orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest failed: doctor repair did not regenerate team skill');
|
|
2067
2074
|
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');
|
|
2068
2075
|
if (await exists(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not prune stale generated skill from previous SKS manifest');
|
|
2076
|
+
if (await exists(path.join(repairTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove generated computer-use skill that shadows the first-party plugin');
|
|
2069
2077
|
if (await exists(path.join(repairTmp, staleCodexAgentRel))) throw new Error('selftest failed: doctor repair did not prune stale generated agent file from previous SKS manifest');
|
|
2070
2078
|
if (!doctorRepairJson.repair?.project?.skill_install?.removed_stale_generated_skills?.includes('.agents/skills/stale-sks-generated')) throw new Error('selftest failed: doctor repair did not report stale generated skill pruning');
|
|
2071
2079
|
const generatedCleanupReport = doctorRepairJson.repair?.project?.generated_cleanup || {};
|
|
@@ -2080,6 +2088,29 @@ async function selftest() {
|
|
|
2080
2088
|
if (repairedPolicy.broken || repairedPolicy.installation?.scope !== 'project' || !repairedPolicy.prompt_pipeline?.dollar_commands?.includes('$Team')) throw new Error('selftest failed: doctor --fix did not regenerate policy');
|
|
2081
2089
|
const repairedAgentsMd = await safeReadText(path.join(repairTmp, 'AGENTS.md'));
|
|
2082
2090
|
if (!repairedAgentsMd.includes('Do not create unrequested fallback implementation code') || repairedAgentsMd.includes('tampered managed block')) throw new Error('selftest failed: doctor --fix did not repair AGENTS managed block');
|
|
2091
|
+
const doctorGlobalTmp = tmpdir();
|
|
2092
|
+
await writeJsonAtomic(path.join(doctorGlobalTmp, 'package.json'), { name: 'doctor-global-skill-repair-smoke', version: '0.0.0' });
|
|
2093
|
+
await initProject(doctorGlobalTmp, { installScope: 'global' });
|
|
2094
|
+
const doctorGlobalHome = path.join(doctorGlobalTmp, 'home');
|
|
2095
|
+
for (const name of stalePluginSkillNames) {
|
|
2096
|
+
await ensureDir(path.join(doctorGlobalHome, '.agents', 'skills', name));
|
|
2097
|
+
await writeTextAtomic(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'), stalePluginSkillContent(name));
|
|
2098
|
+
}
|
|
2099
|
+
const doctorGlobalRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
2100
|
+
cwd: doctorGlobalTmp,
|
|
2101
|
+
env: { HOME: doctorGlobalHome, SKS_DISABLE_UPDATE_CHECK: '1' },
|
|
2102
|
+
timeoutMs: 30000,
|
|
2103
|
+
maxOutputBytes: 1024 * 1024
|
|
2104
|
+
});
|
|
2105
|
+
if (doctorGlobalRepair.code !== 0) throw new Error(`selftest failed: doctor --fix global skill repair exited ${doctorGlobalRepair.code}: ${doctorGlobalRepair.stderr}`);
|
|
2106
|
+
const doctorGlobalRepairJson = JSON.parse(doctorGlobalRepair.stdout || '{}');
|
|
2107
|
+
for (const name of stalePluginSkillNames) {
|
|
2108
|
+
if (await exists(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest failed: doctor --fix did not remove global generated ${name} plugin shadow skill`);
|
|
2109
|
+
}
|
|
2110
|
+
const doctorGlobalRemoved = doctorGlobalRepairJson.repair?.global_skills?.removed_stale_generated_skills || [];
|
|
2111
|
+
for (const name of stalePluginSkillNames) {
|
|
2112
|
+
if (!doctorGlobalRemoved.includes(`.agents/skills/${name}`)) throw new Error(`selftest failed: doctor --fix did not report global ${name} plugin shadow cleanup`);
|
|
2113
|
+
}
|
|
2083
2114
|
const conflictTmp = tmpdir();
|
|
2084
2115
|
await ensureDir(path.join(conflictTmp, '.omx'));
|
|
2085
2116
|
const conflictScan = await scanHarnessConflicts(conflictTmp, { home: path.join(conflictTmp, 'home') });
|
|
@@ -2093,12 +2124,18 @@ async function selftest() {
|
|
|
2093
2124
|
const postinstallSetupTmp = tmpdir();
|
|
2094
2125
|
await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
|
|
2095
2126
|
await ensureDir(path.join(postinstallSetupTmp, '.git'));
|
|
2127
|
+
const postinstallSetupHome = path.join(postinstallSetupTmp, 'home');
|
|
2128
|
+
for (const name of stalePluginSkillNames) {
|
|
2129
|
+
await ensureDir(path.join(postinstallSetupHome, '.agents', 'skills', name));
|
|
2130
|
+
await writeTextAtomic(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'), stalePluginSkillContent(name));
|
|
2131
|
+
}
|
|
2096
2132
|
const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
2097
2133
|
if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
|
|
2098
2134
|
if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
|
|
2099
2135
|
if (!String(postinstallSetup.stdout || '').includes('SKS bootstrap: auto-running sks setup --bootstrap --install-scope global --force') || !String(postinstallSetup.stdout || '').includes('SKS Ready')) throw new Error('selftest failed: postinstall did not auto-run global forced bootstrap');
|
|
2100
2136
|
if (!(await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json')))) throw new Error('selftest failed: postinstall did not create project hooks during automatic bootstrap');
|
|
2101
2137
|
if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
|
|
2138
|
+
if (!String(postinstallSetup.stdout || '').includes('Removed stale generated skill shadow(s):')) throw new Error('selftest failed: postinstall did not report stale first-party plugin shadow cleanup');
|
|
2102
2139
|
const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
|
|
2103
2140
|
if (postinstallSetupManifest.installation?.scope !== 'global') throw new Error('selftest failed: postinstall automatic bootstrap did not use global install scope');
|
|
2104
2141
|
if (postinstallSetupManifest.design_system_ssot?.authority_file !== DESIGN_SYSTEM_SSOT.authority_file || postinstallSetupManifest.design_system_ssot?.builder_prompt !== DESIGN_SYSTEM_SSOT.builder_prompt) throw new Error('selftest failed: postinstall manifest missing design SSOT policy');
|
|
@@ -2109,9 +2146,11 @@ async function selftest() {
|
|
|
2109
2146
|
}
|
|
2110
2147
|
const postinstallSetupGitignore = await safeReadText(path.join(postinstallSetupTmp, '.gitignore'));
|
|
2111
2148
|
if (!postinstallSetupGitignore.includes('.sneakoscope/') || !postinstallSetupGitignore.includes('.codex/') || !postinstallSetupGitignore.includes('.agents/') || !postinstallSetupGitignore.includes('AGENTS.md')) throw new Error('selftest failed: automatic postinstall bootstrap did not ignore SKS generated files');
|
|
2112
|
-
for (const
|
|
2113
|
-
|
|
2114
|
-
|
|
2149
|
+
for (const skillName of new Set(DOLLAR_SKILL_NAMES)) {
|
|
2150
|
+
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${skillName} skill not installed`);
|
|
2151
|
+
}
|
|
2152
|
+
for (const name of stalePluginSkillNames) {
|
|
2153
|
+
if (await exists(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest failed: postinstall global skills shadow the first-party ${name} plugin`);
|
|
2115
2154
|
}
|
|
2116
2155
|
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', 'getdesign-reference', 'SKILL.md')))) throw new Error('selftest failed: postinstall global getdesign-reference skill not installed');
|
|
2117
2156
|
const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
@@ -2376,15 +2415,16 @@ async function selftest() {
|
|
|
2376
2415
|
if (globalSkillsResult.status !== 'installed') throw new Error(`selftest failed: global Codex App skills not installed: ${globalSkillsResult.status}`);
|
|
2377
2416
|
const globalSkillStatus = await checkRequiredSkills(globalSkillsTmp, path.join(globalSkillsTmp, '.agents', 'skills'));
|
|
2378
2417
|
if (!globalSkillStatus.ok) throw new Error(`selftest failed: global Codex App skills missing: ${globalSkillStatus.missing.join(', ')}`);
|
|
2418
|
+
if (await exists(path.join(globalSkillsTmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: global generated skills shadow the first-party computer-use plugin');
|
|
2379
2419
|
const codexSkillMirrorExists = await exists(path.join(tmp, '.codex', 'skills', 'research-discovery', 'SKILL.md'));
|
|
2380
2420
|
if (codexSkillMirrorExists) throw new Error('selftest failed: generated .codex/skills mirror still installed');
|
|
2381
2421
|
const codexAppSkillExists = await exists(path.join(tmp, '.agents', 'skills', 'research-discovery', 'SKILL.md'));
|
|
2382
2422
|
if (!codexAppSkillExists) throw new Error('selftest failed: Codex App skill not installed');
|
|
2383
|
-
for (const
|
|
2384
|
-
const skillName = command.slice(1).toLowerCase();
|
|
2423
|
+
for (const skillName of new Set(DOLLAR_SKILL_NAMES)) {
|
|
2385
2424
|
const dollarSkillExists = await exists(path.join(tmp, '.agents', 'skills', skillName, 'SKILL.md'));
|
|
2386
|
-
if (!dollarSkillExists) throw new Error(`selftest failed: ${
|
|
2425
|
+
if (!dollarSkillExists) throw new Error(`selftest failed: ${skillName} skill not installed`);
|
|
2387
2426
|
}
|
|
2427
|
+
if (await exists(path.join(tmp, '.agents', 'skills', 'computer-use', 'SKILL.md'))) throw new Error('selftest failed: project generated skills shadow the first-party computer-use plugin');
|
|
2388
2428
|
const promptPipelineSkillExists = await exists(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
|
|
2389
2429
|
if (!promptPipelineSkillExists) throw new Error('selftest failed: prompt pipeline skill not installed');
|
|
2390
2430
|
const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
|
|
@@ -2405,7 +2445,7 @@ async function selftest() {
|
|
|
2405
2445
|
if (!(await exists(path.join(tmp, '.agents', 'skills', supportSkill, 'SKILL.md')))) throw new Error(`selftest failed: ${supportSkill} skill not installed`);
|
|
2406
2446
|
}
|
|
2407
2447
|
const imagegenSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'imagegen', 'SKILL.md'));
|
|
2408
|
-
if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('
|
|
2448
|
+
if (!imagegenSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !imagegenSkillText.includes('$imagegen') || !imagegenSkillText.includes('gpt-image-2') || !imagegenSkillText.includes('Direct API fallback does not satisfy SKS route evidence') || !imagegenSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY)) throw new Error('selftest failed: imagegen skill missing official Codex App image generation priority');
|
|
2409
2449
|
const imageUxReviewSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'image-ux-review', 'SKILL.md'));
|
|
2410
2450
|
if (!imageUxReviewSkillText.includes('gpt-image-2') || !imageUxReviewSkillText.includes('$imagegen') || !imageUxReviewSkillText.includes('generated annotated review image') || !imageUxReviewSkillText.includes('Text-only screenshot critique cannot satisfy this route') || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_GATE_ARTIFACT) || !imageUxReviewSkillText.includes(IMAGE_UX_REVIEW_ISSUE_LEDGER_ARTIFACT) || !imageUxReviewSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY)) throw new Error('selftest failed: image-ux-review skill missing gpt-image-2 generated-image review gate guidance');
|
|
2411
2451
|
const getdesignSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'getdesign-reference', 'SKILL.md')); if (!getdesignSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !getdesignSkillText.includes('only design decision SSOT') || !getdesignSkillText.includes('source inputs')) throw new Error('selftest failed: getdesign-reference skill missing design SSOT source-input guidance');
|
|
@@ -2430,6 +2470,7 @@ async function selftest() {
|
|
|
2430
2470
|
if (routePrompt('$agent-team run specialists')) throw new Error('selftest failed: deprecated $agent-team route still resolved');
|
|
2431
2471
|
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');
|
|
2432
2472
|
if (routePrompt('[$qa-loop](/tmp/qa-loop/SKILL.md) localhost UI 검증, Codex Computer Use만 사용')?.id !== 'QALoop') throw new Error('selftest failed: markdown-linked $QA-LOOP was hijacked by heuristic routing');
|
|
2473
|
+
if (stripVisibleDecisionAnswerBlocks('qa-loop [GOAL_PRECISE: local QA QA_SCOPE: ui_e2e_only TARGET_BASE_URL: http://localhost:3000] 다시 실행').includes('GOAL_PRECISE')) throw new Error('selftest failed: visible decision answer block sanitizer did not remove slot payload');
|
|
2433
2474
|
if (routePrompt('[$research](/tmp/research/SKILL.md) Codex Computer Use 도구 노출 문제를 QA루프 관점에서 연구')?.id !== 'Research') throw new Error('selftest failed: markdown-linked $Research was not treated as explicit route');
|
|
2434
2475
|
if (routePrompt('$WikiRefresh 갱신')) throw new Error('selftest failed: deprecated $WikiRefresh route still resolved');
|
|
2435
2476
|
if (routePrompt('$MAD-SKS Supabase MCP main 작업')?.id !== 'MadSKS') throw new Error('selftest failed: $MAD-SKS route did not resolve');
|
|
@@ -2652,6 +2693,19 @@ async function selftest() {
|
|
|
2652
2693
|
if (!String(hookUpdateOldContext).includes('Update SKS now') || !String(hookUpdateOldContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook did not prompt when installed SKS is stale');
|
|
2653
2694
|
const hookUpdateOldState = await readJson(path.join(hookUpdateOldTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2654
2695
|
if (hookUpdateOldState.pending_offer?.latest !== '9.9.9') throw new Error('selftest failed: stale installed SKS did not persist pending update offer');
|
|
2696
|
+
const hookUpdateAcceptPayload = JSON.stringify({ cwd: hookUpdateOldTmp, prompt: 'Update SKS now' });
|
|
2697
|
+
const hookUpdateAcceptResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], {
|
|
2698
|
+
cwd: hookUpdateOldTmp,
|
|
2699
|
+
input: hookUpdateAcceptPayload,
|
|
2700
|
+
env: { ...hookUpdateCurrentEnv, SKS_INSTALLED_SKS_VERSION: '0.0.0' },
|
|
2701
|
+
timeoutMs: 15000,
|
|
2702
|
+
maxOutputBytes: 256 * 1024
|
|
2703
|
+
});
|
|
2704
|
+
if (hookUpdateAcceptResult.code !== 0) throw new Error(`selftest failed: accepted update hook exited ${hookUpdateAcceptResult.code}: ${hookUpdateAcceptResult.stderr}`);
|
|
2705
|
+
const hookUpdateAcceptJson = JSON.parse(hookUpdateAcceptResult.stdout);
|
|
2706
|
+
const hookUpdateAcceptContext = hookUpdateAcceptJson.hookSpecificOutput?.additionalContext || '';
|
|
2707
|
+
if (!String(hookUpdateAcceptContext).includes('npm i -g sneakoscope@latest')) throw new Error('selftest failed: accepted update hook did not specify the exact npm latest command');
|
|
2708
|
+
if (String(hookUpdateAcceptContext).includes('sks setup') || String(hookUpdateAcceptContext).includes('sks doctor') || String(hookUpdateAcceptContext).includes('npm i -D sneakoscope')) throw new Error('selftest failed: accepted update hook still routes through setup/doctor/project update commands');
|
|
2655
2709
|
const hookKoreanSksTmp = tmpdir();
|
|
2656
2710
|
await initProject(hookKoreanSksTmp, {});
|
|
2657
2711
|
const hookKoreanSksPayload = JSON.stringify({ cwd: hookKoreanSksTmp, prompt: koreanReadmeInstallPrompt });
|
|
@@ -2700,31 +2754,20 @@ async function selftest() {
|
|
|
2700
2754
|
const hookTeamPendingContext = hookTeamPendingJson.hookSpecificOutput?.additionalContext || '';
|
|
2701
2755
|
if (hookTeamPendingState.mission_id === hookTeamState.mission_id || hookTeamPendingContext.includes('Required questions still pending') || hookTeamPendingContext.includes('MANDATORY ambiguity-removal gate activated')) throw new Error('selftest failed: direct Team follow-up was blocked by stale clarification behavior');
|
|
2702
2756
|
if (hookTeamPendingState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || !hookTeamPendingState.team_plan_ready) throw new Error('selftest failed: direct Team follow-up did not prepare a fresh Team mission');
|
|
2703
|
-
const
|
|
2704
|
-
await initProject(
|
|
2705
|
-
const
|
|
2706
|
-
if (
|
|
2707
|
-
const
|
|
2708
|
-
const
|
|
2709
|
-
const
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
const visibleQuestionDecision = await evaluateStop(qaClarificationTmp, hookQaClarificationState, { last_assistant_message: visibleQuestionsBlock }, { noQuestion: false });
|
|
2716
|
-
if (!visibleQuestionDecision?.continue) throw new Error('selftest failed: visible Required questions block was not accepted by clarification stop gate');
|
|
2717
|
-
const hookTeamPreToolBlocked = await runProcess(process.execPath, [hookBin, 'hook', 'pre-tool'], { cwd: qaClarificationTmp, input: JSON.stringify({ cwd: qaClarificationTmp, command: 'npm run selftest' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2718
|
-
if (hookTeamPreToolBlocked.code !== 0) throw new Error(`selftest failed: pending clarification pre-tool hook exited ${hookTeamPreToolBlocked.code}: ${hookTeamPreToolBlocked.stderr}`);
|
|
2719
|
-
const hookTeamPreToolBlockedJson = JSON.parse(hookTeamPreToolBlocked.stdout);
|
|
2720
|
-
if (hookTeamPreToolBlockedJson.decision !== 'block' || !String(hookTeamPreToolBlockedJson.reason || '').includes('ambiguity gate is paused')) throw new Error('selftest failed: pending clarification allowed implementation tool use before answers');
|
|
2721
|
-
const hookTeamAnswerToolAllowed = await runProcess(process.execPath, [hookBin, 'hook', 'pre-tool'], { cwd: qaClarificationTmp, input: JSON.stringify({ cwd: qaClarificationTmp, command: 'node ./bin/sks.mjs pipeline answer latest --stdin' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2722
|
-
if (hookTeamAnswerToolAllowed.code !== 0) throw new Error(`selftest failed: pipeline-answer pre-tool hook exited ${hookTeamAnswerToolAllowed.code}: ${hookTeamAnswerToolAllowed.stderr}`);
|
|
2723
|
-
const hookTeamAnswerToolAllowedJson = JSON.parse(hookTeamAnswerToolAllowed.stdout);
|
|
2724
|
-
if (hookTeamAnswerToolAllowedJson.decision === 'block') throw new Error('selftest failed: pending clarification blocked the pipeline answer command');
|
|
2725
|
-
const nonGoalsSlot = hookTeamSchema.slots.find((s) => s.id === 'NON_GOALS');
|
|
2757
|
+
const pptClarificationTmp = tmpdir();
|
|
2758
|
+
await initProject(pptClarificationTmp, {});
|
|
2759
|
+
const hookPptClarificationResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: pptClarificationTmp, input: JSON.stringify({ cwd: pptClarificationTmp, prompt: '$PPT 투자 제안서 만들어줘' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2760
|
+
if (hookPptClarificationResult.code !== 0) throw new Error(`selftest failed: PPT clarification hook exited ${hookPptClarificationResult.code}: ${hookPptClarificationResult.stderr}`);
|
|
2761
|
+
const hookPptClarificationState = await readJson(stateFile(pptClarificationTmp), {});
|
|
2762
|
+
const hookPptClarificationJson = JSON.parse(hookPptClarificationResult.stdout);
|
|
2763
|
+
const hookPptContext = hookPptClarificationJson.hookSpecificOutput?.additionalContext || '';
|
|
2764
|
+
const hookPptSchema = await readJson(path.join(missionDir(pptClarificationTmp, hookPptClarificationState.mission_id), 'required-answers.schema.json'));
|
|
2765
|
+
if (hookPptClarificationState.phase !== 'PPT_AUDIENCE_STRATEGY_READY' || hookPptClarificationState.implementation_allowed !== true || hookPptSchema.slots.length !== 0) throw new Error('selftest failed: PPT hook did not auto-seal without visible questions');
|
|
2766
|
+
if (hookPptContext.includes('Required questions') || hookPptContext.includes('VISIBLE RESPONSE CONTRACT') || hookPptContext.includes('MANDATORY ambiguity-removal gate')) throw new Error('selftest failed: PPT hook still exposed prequestion wording');
|
|
2767
|
+
if (!(await exists(path.join(missionDir(pptClarificationTmp, hookPptClarificationState.mission_id), 'ppt-audience-strategy.json')))) throw new Error('selftest failed: PPT auto-seal did not materialize audience strategy');
|
|
2768
|
+
const nonGoalsSlot = hookPptSchema.slots.find((s) => s.id === 'NON_GOALS');
|
|
2726
2769
|
if (nonGoalsSlot && !nonGoalsSlot.allow_empty) throw new Error('selftest failed: NON_GOALS does not allow an empty array answer');
|
|
2727
|
-
if (!nonGoalsSlot && !Array.isArray(
|
|
2770
|
+
if (!nonGoalsSlot && !Array.isArray(hookPptSchema.inferred_answers?.NON_GOALS)) throw new Error('selftest failed: NON_GOALS was neither asked nor inferred');
|
|
2728
2771
|
const textParsedAnswers = parseAnswersText({ slots: [{ id: 'INTENT_TARGET', type: 'string', required: true }] }, 'INTENT_TARGET: compact contract sealing');
|
|
2729
2772
|
if (textParsedAnswers.INTENT_TARGET !== 'compact contract sealing') throw new Error('selftest failed: text answer parser did not parse slot-id answers');
|
|
2730
2773
|
const textParsedImplicitAnswer = parseAnswersText({ slots: [{ id: 'INTENT_TARGET', type: 'string', required: true }] }, 'compact contract sealing');
|
|
@@ -2773,26 +2816,18 @@ async function selftest() {
|
|
|
2773
2816
|
if (honestSummaryCaseJson.decision === 'block') throw new Error('selftest failed: summary block/pass wording was treated as unresolved gap');
|
|
2774
2817
|
const hookQaTmp = tmpdir();
|
|
2775
2818
|
await initProject(hookQaTmp, {});
|
|
2776
|
-
const hookQaPayload = JSON.stringify({ cwd: hookQaTmp, prompt: '$QA-LOOP run
|
|
2819
|
+
const hookQaPayload = JSON.stringify({ cwd: hookQaTmp, prompt: '$QA-LOOP run API E2E against local dev' });
|
|
2777
2820
|
const hookQaResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookQaTmp, input: hookQaPayload, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2778
2821
|
if (hookQaResult.code !== 0) throw new Error(`selftest failed: $QA-LOOP hook exited ${hookQaResult.code}: ${hookQaResult.stderr}`);
|
|
2779
2822
|
const hookQaJson = JSON.parse(hookQaResult.stdout);
|
|
2780
2823
|
const hookQaContext = hookQaJson.hookSpecificOutput?.additionalContext || '';
|
|
2781
|
-
if (!hookQaContext.includes('MANDATORY ambiguity-removal gate activated') ||
|
|
2824
|
+
if (!hookQaContext.includes('Route contract auto-sealed') || hookQaContext.includes('MANDATORY ambiguity-removal gate activated') || hookQaContext.includes('Required questions:') || hookQaContext.includes('QA_SCOPE:') || hookQaContext.includes('UI_COMPUTER_USE_ACK:')) throw new Error('selftest failed: $QA-LOOP hook did not auto-seal without visible answer slots');
|
|
2782
2825
|
if (!hookQaContext.includes('Codex Computer Use') || !hookQaContext.includes('Playwright') || !hookQaContext.includes('Chrome MCP')) throw new Error('selftest failed: $QA-LOOP hook did not state Computer Use-only UI policy');
|
|
2783
2826
|
if (hookQaContext.includes('Browser Use 또는 Computer Use') || hookQaContext.includes('Browser/Computer Use evidence')) throw new Error('selftest failed: $QA-LOOP hook still allows Browser Use as UI evidence');
|
|
2784
2827
|
const hookQaState = await readJson(stateFile(hookQaTmp), {});
|
|
2785
|
-
if (hookQaState.phase !== '
|
|
2828
|
+
if (hookQaState.phase !== 'QALOOP_CLARIFICATION_CONTRACT_SEALED' || hookQaState.implementation_allowed !== true || hookQaState.clarification_required !== false || !hookQaState.ambiguity_gate_passed) throw new Error('selftest failed: $QA-LOOP hook did not auto-seal the ambiguity gate');
|
|
2786
2829
|
const hookQaSchema = await readJson(path.join(missionDir(hookQaTmp, hookQaState.mission_id), 'required-answers.schema.json'));
|
|
2787
|
-
|
|
2788
|
-
for (const s of hookQaSchema.slots) hookQaAnswers[s.id] = s.options ? (s.type === 'array' ? [s.options[0]] : s.options[0]) : (s.type.includes('array') ? ['selftest'] : 'selftest');
|
|
2789
|
-
hookQaAnswers.QA_SCOPE = 'all_available';
|
|
2790
|
-
hookQaAnswers.TARGET_BASE_URL = 'none';
|
|
2791
|
-
hookQaAnswers.API_BASE_URL = 'same_as_target';
|
|
2792
|
-
const hookQaAnswersPath = path.join(hookQaTmp, 'qa-answers.json');
|
|
2793
|
-
await writeJsonAtomic(hookQaAnswersPath, hookQaAnswers);
|
|
2794
|
-
const qaAnswerResult = await runProcess(process.execPath, [hookBin, 'pipeline', 'answer', 'latest', hookQaAnswersPath], { cwd: hookQaTmp, env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
2795
|
-
if (qaAnswerResult.code !== 0) throw new Error(`selftest failed: QA pipeline answer exited ${qaAnswerResult.code}: ${qaAnswerResult.stderr}`);
|
|
2830
|
+
if (hookQaSchema.slots.length !== 0 || hookQaSchema.inferred_answers?.QA_SCOPE !== 'api_e2e_only') throw new Error('selftest failed: $QA-LOOP schema did not infer QA answers without visible slots');
|
|
2796
2831
|
const qaMissionDir = missionDir(hookQaTmp, hookQaState.mission_id);
|
|
2797
2832
|
const initialQaGate = await readJson(path.join(qaMissionDir, 'qa-gate.json'));
|
|
2798
2833
|
const qaReportFile = initialQaGate.qa_report_file;
|
|
@@ -3196,7 +3231,7 @@ async function selftest() {
|
|
|
3196
3231
|
await writeTextAtomic(fakeTmuxLog, '');
|
|
3197
3232
|
const madCockpit = await launchMadTmuxUi(['--workspace', 'sks-mad-selftest-ui', '--no-attach'], { root: tmp, tmux: { ok: true, bin: fakeTmuxBin, version: '3.4' }, codex: { bin: process.execPath }, app: { ok: true, guidance: [] }, missionId: 'M-MAD-SELFTEST' });
|
|
3198
3233
|
const madTmuxLogText = await safeReadText(fakeTmuxLog);
|
|
3199
|
-
if (!madCockpit.created || madCockpit.opened?.panes?.length !==
|
|
3234
|
+
if (!madCockpit.created || madCockpit.mode !== 'mad_session' || madCockpit.opened?.panes?.length !== 1 || !madTmuxLogText.includes('new-session') || madTmuxLogText.includes('split-window')) throw new Error('selftest failed: MAD tmux launch should create one pane and leave split panes to Team lanes');
|
|
3200
3235
|
if (previousFakeTmuxLog === undefined) delete process.env.SKS_FAKE_TMUX_LOG;
|
|
3201
3236
|
else process.env.SKS_FAKE_TMUX_LOG = previousFakeTmuxLog;
|
|
3202
3237
|
const codexLaunchArgs = defaultCodexLaunchArgs({ SKS_CODEX_REASONING: 'low' }).join(' ');
|
|
@@ -3304,8 +3339,8 @@ async function selftest() {
|
|
|
3304
3339
|
if (!predictableAuthCliSchema.inferred_answers.RISK_BOUNDARY?.includes('no destructive commands or live data writes')) throw new Error('selftest failed: predictable auth-worded CLI work did not infer conservative risk boundary');
|
|
3305
3340
|
const vagueSchema = buildQuestionSchema('뭔가 개선해줘');
|
|
3306
3341
|
const vagueSlotIds = vagueSchema.slots.map((s) => s.id);
|
|
3307
|
-
if (
|
|
3308
|
-
if (
|
|
3342
|
+
if (vagueSlotIds.length !== 0) throw new Error(`selftest failed: vague work should auto-seal inferred defaults without visible questions, got ${vagueSlotIds.join(',')}`);
|
|
3343
|
+
if (!vagueSchema.inferred_answers?.GOAL_PRECISE || !vagueSchema.inferred_answers?.ACCEPTANCE_CRITERIA) throw new Error('selftest failed: vague work did not infer core contract defaults');
|
|
3309
3344
|
if (vagueSchema.ambiguity_assessment?.method !== 'weighted_clarity_interview' || !vagueSchema.ambiguity_assessment?.adversarial_lenses?.includes('challenge_framing')) throw new Error('selftest failed: ambiguity schema missing weighted clarity / planning lenses');
|
|
3310
3345
|
const pptRoute = routePrompt('$PPT 투자자용 피치덱 만들어줘');
|
|
3311
3346
|
if (pptRoute?.id !== 'PPT') throw new Error('selftest failed: $PPT did not route to presentation pipeline');
|
|
@@ -3314,14 +3349,14 @@ async function selftest() {
|
|
|
3314
3349
|
const pptSchema = buildQuestionSchema('$PPT 투자자용 피치덱 만들어줘');
|
|
3315
3350
|
const pptSlotIds = pptSchema.slots.map((s) => s.id);
|
|
3316
3351
|
for (const id of ['PRESENTATION_DELIVERY_CONTEXT', 'PRESENTATION_AUDIENCE_PROFILE', 'PRESENTATION_STP_STRATEGY', 'PRESENTATION_PAINPOINT_SOLUTION_MAP', 'PRESENTATION_DECISION_CONTEXT']) {
|
|
3317
|
-
if (
|
|
3352
|
+
if (pptSlotIds.includes(id) || pptSchema.inferred_answers?.[id] === undefined) throw new Error(`selftest failed: PPT schema did not infer ${id}`);
|
|
3318
3353
|
}
|
|
3319
3354
|
const pptSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'ppt', 'SKILL.md'));
|
|
3320
|
-
if (!pptSkillText.includes('STP') || !pptSkillText.includes('target audience profile') || !pptSkillText.includes('decision context') || !pptSkillText.includes('3+ pain-point to solution mappings')) throw new Error('selftest failed: generated PPT skill missing STP/audience/pain-point guidance');
|
|
3355
|
+
if (!pptSkillText.includes('STP') || !pptSkillText.includes('target audience profile') || !pptSkillText.includes('decision context') || !pptSkillText.includes('3+ pain-point to solution mappings') || !pptSkillText.includes('Do not surface a prequestion sheet')) throw new Error('selftest failed: generated PPT skill missing inferred STP/audience/pain-point guidance');
|
|
3321
3356
|
if (!pptSkillText.includes('simple, restrained, and information-first') || !pptSkillText.includes('over-designed decoration') || !pptSkillText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL) || !pptSkillText.includes(CODEX_IMAGEGEN_REQUIRED_POLICY) || !pptSkillText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !pptSkillText.includes('only design decision SSOT') || !pptSkillText.includes('instead of treating references as parallel authorities')) throw new Error('selftest failed: generated PPT skill missing restrained design/imagegen/fused-SSOT guidance');
|
|
3322
3357
|
if (!pptSkillText.includes('PPT pipeline allowlist') || !pptSkillText.includes('ignore installed skills and MCPs') || !pptSkillText.includes('prevent AI-like generic presentation design') || !pptSkillText.includes('Do not use generic design skills such as design-artifact-expert')) throw new Error('selftest failed: generated PPT skill missing pipeline allowlist enforcement');
|
|
3323
3358
|
if (!pptSkillText.includes('source-html/') || !pptSkillText.includes('temporary build files') || !pptSkillText.includes('ppt-parallel-report.json')) throw new Error('selftest failed: generated PPT skill missing source preservation/temp cleanup/parallel guidance');
|
|
3324
|
-
if (!pptSkillText.includes('ppt-fact-ledger.json') || !pptSkillText.includes('ppt-image-asset-ledger.json') || !pptSkillText.includes('
|
|
3359
|
+
if (!pptSkillText.includes('ppt-fact-ledger.json') || !pptSkillText.includes('ppt-image-asset-ledger.json') || !pptSkillText.includes('direct API fallback') || !pptSkillText.includes('ppt-review-ledger.json') || !pptSkillText.includes('ppt-iteration-report.json') || !pptSkillText.includes('never simulate missing gpt-image-2 output')) throw new Error('selftest failed: generated PPT skill missing fact/image/review loop anti-fake guidance');
|
|
3325
3360
|
if (routeRequiresSubagents(pptRoute, '$PPT 투자자용 피치덱 만들어줘')) throw new Error('selftest failed: PPT route should not require subagents by default');
|
|
3326
3361
|
if (!reflectionRequiredForRoute(pptRoute)) throw new Error('selftest failed: PPT route should require reflection');
|
|
3327
3362
|
const pptMission = await createMission(tmp, { mode: 'ppt', prompt: '$PPT 투자자용 피치덱 만들어줘' });
|
|
@@ -3408,11 +3443,11 @@ async function selftest() {
|
|
|
3408
3443
|
unsupported_critical_claims_count: 0,
|
|
3409
3444
|
passed: true
|
|
3410
3445
|
});
|
|
3411
|
-
const requiredImageBuildResult = await runProcess(process.execPath, [hookBin, 'ppt', 'build', requiredImagePptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1',
|
|
3446
|
+
const requiredImageBuildResult = await runProcess(process.execPath, [hookBin, 'ppt', 'build', requiredImagePptMission.id, '--json'], { cwd: tmp, env: { SKS_DISABLE_UPDATE_CHECK: '1', SKS_FAKE_IMAGE_GATE_TOKEN: 'ignored-by-sks-route-gate' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
3412
3447
|
if (requiredImageBuildResult.code !== 0) throw new Error(`selftest failed: required-image PPT build command failed: ${requiredImageBuildResult.stderr || requiredImageBuildResult.stdout}`);
|
|
3413
3448
|
const requiredImageBuild = JSON.parse(requiredImageBuildResult.stdout);
|
|
3414
3449
|
const requiredImageLedger = await readJson(path.join(requiredImagePptMission.dir, PPT_IMAGE_ASSET_LEDGER_ARTIFACT));
|
|
3415
|
-
if (requiredImageBuild.ok || requiredImageBuild.gate?.passed || !requiredImageBuild.gate?.image_asset_ledger_created || requiredImageBuild.gate?.image_asset_policy_satisfied !== false || !requiredImageLedger.required || requiredImageLedger.passed || !requiredImageLedger.blockers?.includes('
|
|
3450
|
+
if (requiredImageBuild.ok || requiredImageBuild.gate?.passed || !requiredImageBuild.gate?.image_asset_ledger_created || requiredImageBuild.gate?.image_asset_policy_satisfied !== false || !requiredImageLedger.required || requiredImageLedger.passed || !requiredImageLedger.blockers?.includes('missing_codex_app_imagegen_gpt_image_2_asset_evidence') || requiredImageLedger.generated_count !== 0) throw new Error('selftest failed: required PPT image assets were not blocked without Codex App imagegen evidence');
|
|
3416
3451
|
const installUxSchema = buildQuestionSchema('SKS first install/bootstrap UX and Context7 MCP setup improvement');
|
|
3417
3452
|
const installUxSlotIds = installUxSchema.slots.map((s) => s.id);
|
|
3418
3453
|
if (installUxSchema.domain_hints.includes('uiux') || installUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: CLI UX install prompt should not ask visual UI questions');
|
|
@@ -3424,9 +3459,8 @@ async function selftest() {
|
|
|
3424
3459
|
const { id, dir, mission } = await createMission(tmp, { mode: 'goal', prompt: '발표자료 만들어줘' });
|
|
3425
3460
|
const schema = buildQuestionSchema(mission.prompt);
|
|
3426
3461
|
await writeQuestions(dir, schema);
|
|
3427
|
-
if (validateAnswers(schema, {}).ok) throw new Error('selftest failed: empty
|
|
3428
|
-
const answers = {};
|
|
3429
|
-
for (const s of schema.slots) answers[s.id] = s.options ? (s.type === 'array' ? [s.options[0]] : s.options[0]) : (s.type.includes('array') ? ['selftest'] : (s.id === 'DB_MAX_BLAST_RADIUS' ? 'no_live_dml' : 'selftest'));
|
|
3462
|
+
if (!validateAnswers(schema, {}).ok || schema.slots.length !== 0) throw new Error('selftest failed: inferred empty answer set should be valid after prequestion removal');
|
|
3463
|
+
const answers = { ...(schema.inferred_answers || {}) };
|
|
3430
3464
|
await writeJsonAtomic(path.join(dir, 'answers.json'), answers);
|
|
3431
3465
|
const sealed = await sealContract(dir, mission);
|
|
3432
3466
|
if (!sealed.ok) throw new Error('selftest failed: answers rejected');
|
|
@@ -15,7 +15,7 @@ import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge }
|
|
|
15
15
|
import { DEFAULT_EVAL_THRESHOLDS, compareEvaluationReports, runEvaluationBenchmark } from '../core/evaluation.mjs';
|
|
16
16
|
import { contextCapsule } from '../core/triwiki-attention.mjs';
|
|
17
17
|
import { rgbaKey, rgbaToWikiCoord, validateWikiCoordinateIndex } from '../core/wiki-coordinate.mjs';
|
|
18
|
-
import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, hasFromChatImgSignal, routePrompt, routeReasoning, stackCurrentDocsPolicy, triwikiContextTracking } from '../core/routes.mjs';
|
|
18
|
+
import { ALLOWED_REASONING_EFFORTS, CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT, FROM_CHAT_IMG_WORK_ORDER_ARTIFACT, RECOMMENDED_SKILLS, ROUTES, hasFromChatImgSignal, routePrompt, routeReasoning, stackCurrentDocsPolicy, stripVisibleDecisionAnswerBlocks, triwikiContextTracking } from '../core/routes.mjs';
|
|
19
19
|
import { TEAM_DECOMPOSITION_ARTIFACT, TEAM_GRAPH_ARTIFACT, TEAM_INBOX_DIR, TEAM_RUNTIME_TASKS_ARTIFACT, teamRuntimePlanMetadata, teamRuntimeRequiredArtifacts, writeTeamRuntimeArtifacts } from '../core/team-dag.mjs';
|
|
20
20
|
import { appendTeamEvent, formatAgentReasoning, formatRoleCounts, initTeamLive, normalizeTeamSpec, parseTeamSpecArgs, readTeamControl, readTeamDashboard, readTeamLive, readTeamTranscriptTail, renderTeamAgentLane, renderTeamCleanupSummary, renderTeamWatch, requestTeamSessionCleanup, teamCleanupRequested, teamReasoningPolicy } from '../core/team-live.mjs';
|
|
21
21
|
import { evaluateTeamReviewPolicyGate, MIN_TEAM_REVIEWER_LANES, MIN_TEAM_REVIEW_POLICY_TEXT, teamReviewPolicy } from '../core/team-review-policy.mjs';
|
|
@@ -281,21 +281,38 @@ function qaRoute() {
|
|
|
281
281
|
async function qaLoopPrepare(args) {
|
|
282
282
|
const root = await sksRoot();
|
|
283
283
|
if (!(await exists(path.join(root, '.sneakoscope')))) await initProject(root, {});
|
|
284
|
-
const prompt = promptOf(args);
|
|
284
|
+
const prompt = stripVisibleDecisionAnswerBlocks(promptOf(args));
|
|
285
285
|
if (!prompt) throw new Error('Missing QA target prompt.');
|
|
286
286
|
const { id, dir } = await createMission(root, { mode: 'qaloop', prompt });
|
|
287
287
|
const schema = buildQaLoopQuestionSchema(prompt);
|
|
288
288
|
const route = qaRoute();
|
|
289
289
|
await writeQuestions(dir, schema);
|
|
290
290
|
await writeJsonAtomic(path.join(dir, 'route-context.json'), { route: 'QALoop', command: '$QA-LOOP', mode: 'QALOOP', task: prompt, required_skills: route?.requiredSkills || [], context7_required: false, original_stop_gate: 'qa-gate.json', clarification_gate: true });
|
|
291
|
+
if (schema.slots.length === 0) {
|
|
292
|
+
await writeJsonAtomic(path.join(dir, 'answers.json'), schema.inferred_answers || {});
|
|
293
|
+
const result = await sealContract(dir, { id, prompt, mode: 'qaloop' });
|
|
294
|
+
if (!result.ok) {
|
|
295
|
+
console.error('Inferred QA-LOOP answers failed validation.');
|
|
296
|
+
console.error(JSON.stringify(result.validation, null, 2));
|
|
297
|
+
process.exitCode = 2;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const artifactResult = await writeQaLoopArtifacts(dir, { id, prompt, mode: 'qaloop' }, result.contract);
|
|
301
|
+
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'qaloop.prepare.auto_sealed', slots: 0, hash: result.contract.sealed_hash, checklist_count: artifactResult.checklist_count });
|
|
302
|
+
await setCurrent(root, { mission_id: id, route: 'QALoop', route_command: '$QA-LOOP', mode: 'QALOOP', phase: 'QALOOP_CLARIFICATION_CONTRACT_SEALED', questions_allowed: false, implementation_allowed: true, clarification_required: false, clarification_passed: true, ambiguity_gate_required: true, ambiguity_gate_passed: true, stop_gate: 'qa-gate.json', qa_loop_artifacts_ready: true, qa_report_file: artifactResult.report_file, qa_checklist_count: artifactResult.checklist_count, reasoning_effort: 'high', reasoning_profile: 'sks-logic-high', reasoning_temporary: true });
|
|
303
|
+
console.log(`QA-LOOP mission created: ${id}`);
|
|
304
|
+
console.log('QA-LOOP contract auto-sealed from prompt, TriWiki/current-code defaults, and conservative safety policy.');
|
|
305
|
+
console.log(`Checklist: ${artifactResult.checklist_count} cases`);
|
|
306
|
+
console.log(`Report: ${path.relative(root, path.join(dir, artifactResult.report_file))}`);
|
|
307
|
+
console.log(`Run: sks qa-loop run ${id} --max-cycles ${schema.inferred_answers?.MAX_QA_CYCLES || 1}`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
291
310
|
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'qaloop.prepare.questions_created', slots: schema.slots.length });
|
|
292
311
|
await setCurrent(root, { mission_id: id, route: 'QALoop', route_command: '$QA-LOOP', mode: 'QALOOP', phase: 'QALOOP_CLARIFICATION_AWAITING_ANSWERS', questions_allowed: true, implementation_allowed: false, clarification_required: true, ambiguity_gate_required: true, stop_gate: 'clarification-gate', reasoning_effort: 'high', reasoning_profile: 'sks-logic-high', reasoning_temporary: true });
|
|
293
312
|
console.log(`QA-LOOP mission created: ${id}`);
|
|
294
|
-
console.log('QA-LOOP
|
|
295
|
-
console.log(`Questions: ${path.relative(root, path.join(dir, 'questions.md'))}`);
|
|
313
|
+
console.log('QA-LOOP could not auto-seal because required safe defaults were unavailable.');
|
|
296
314
|
console.log(`Answer schema: ${path.relative(root, path.join(dir, 'required-answers.schema.json'))}`);
|
|
297
|
-
console.log('
|
|
298
|
-
console.log(formatQuestionsForCli(schema));
|
|
315
|
+
console.log('Inspect the schema and provide answers with: sks qa-loop answer <mission-id> answers.json');
|
|
299
316
|
}
|
|
300
317
|
|
|
301
318
|
async function qaLoopAnswer(args) {
|
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.7.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.59';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|