sneakoscope 0.7.78 → 0.8.2
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 +28 -3
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +38 -9
- package/src/cli/main.mjs +65 -23
- package/src/cli/maintenance-commands.mjs +98 -6
- package/src/cli/recallpulse-command.mjs +157 -0
- package/src/core/codex-app.mjs +181 -11
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +95 -1
- package/src/core/init.mjs +43 -8
- package/src/core/pipeline.mjs +3 -3
- package/src/core/recallpulse.mjs +1215 -0
- package/src/core/research.mjs +119 -60
- package/src/core/routes.mjs +3 -2
package/README.md
CHANGED
|
@@ -40,6 +40,25 @@ sks selftest --mock
|
|
|
40
40
|
|
|
41
41
|
`sks` adds a tmux Codex CLI runtime, Codex App `$` commands, Team/QA/PPT/Research/DB/GX/Wiki routes, OpenClaw skill generation, Context7-gated current docs, TriWiki context packs, DB safety, design SSOT policy, skill dreaming, release checks, and Honest Mode.
|
|
42
42
|
|
|
43
|
+
## 0.8.0 Massive Upgrade
|
|
44
|
+
|
|
45
|
+
Sneakoscope 0.8.0 introduces the RecallPulse planning spine: a report-only active-recall layer that records what the pipeline should remember before a stage proceeds. RecallPulse maps TriWiki into L1/L2/L3 cache behavior, writes durable status ledgers instead of relying on ephemeral hook text, suppresses duplicate reminder loops, and emits RouteProofCapsule plus EvidenceEnvelope artifacts for later gate comparison. These artifacts are evidence surfaces first; speed or accuracy gains remain benchmark-gated until scored evals prove them.
|
|
46
|
+
|
|
47
|
+
Inspect the new report-only artifacts with:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
sks recallpulse run latest
|
|
51
|
+
sks recallpulse status latest --json
|
|
52
|
+
sks recallpulse eval latest --json
|
|
53
|
+
sks recallpulse governance latest --json
|
|
54
|
+
sks recallpulse checklist latest --json
|
|
55
|
+
sks recallpulse checklist latest --task T001 --apply --evidence src/core/recallpulse.mjs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Research scouts now use named persona-inspired cognitive lenses: Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout. They are not impersonations; each scout ledger row must carry `display_name`, `persona`, `persona_boundary`, `reasoning_effort=xhigh`, a literal `Eureka!` idea, falsifiers, cheap probes, and debate participation evidence.
|
|
59
|
+
|
|
60
|
+
For existing 0.7.x users, the visible change is new report-only evidence, not a route personality rewrite. Team still feels like Team, DFix stays ultralight, DB remains conservative, QA-LOOP still dogfoods, PPT stays information-first, imagegen still requires real raster evidence, and Honest Mode remains the final truth pass. The original strong reminder idea became neutral RecallPulse so user-facing prompts stay short, professional, and non-repetitive; hook messages can point at status, but `mission-status-ledger.json` is the durable source when app-visible text disappears. The planning source is `docs/RECALLPULSE_0_8_0_TASKS.md`, and implementation is designed to land in safe task-sized slices before any enforcement promotion.
|
|
61
|
+
|
|
43
62
|
## Requirements
|
|
44
63
|
|
|
45
64
|
- Node.js `>=20.11`
|
|
@@ -193,8 +212,12 @@ sks qa-loop prepare "http://localhost:3000"
|
|
|
193
212
|
sks qa-loop run latest --max-cycles 2
|
|
194
213
|
sks goal create "persist this migration workflow"
|
|
195
214
|
sks research prepare "evaluate this approach"
|
|
196
|
-
sks research run latest --max-cycles
|
|
215
|
+
sks research run latest --max-cycles 12 --cycle-timeout-minutes 120
|
|
197
216
|
sks research status latest
|
|
217
|
+
sks recallpulse run latest
|
|
218
|
+
sks recallpulse status latest --json
|
|
219
|
+
sks recallpulse governance latest --json
|
|
220
|
+
sks recallpulse checklist latest --json
|
|
198
221
|
sks db scan --json
|
|
199
222
|
sks wiki refresh
|
|
200
223
|
sks wiki sweep latest --json
|
|
@@ -212,7 +235,9 @@ sks skill-dream run --json
|
|
|
212
235
|
sks code-structure scan --json
|
|
213
236
|
```
|
|
214
237
|
|
|
215
|
-
`sks research` prepares a genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, and
|
|
238
|
+
`sks research` prepares a named genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, and creates `research-source-skill.md` as a route-local source collection skill before synthesis. Research is not a code-change route: real runs may write only their own mission artifacts under `.sneakoscope/missions/<id>/`, and source/package/docs/config mutations block the run with `research-code-mutation-blocker.json`. The required Research persona lenses are Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout; they are cognitive roles, not impersonations, and `scout-ledger.json` must include `display_name`, `persona`, `persona_boundary`, `reasoning_effort`, falsifiers, cheap probes, and `challenge_or_response`. Normal Research is not a fixed three-cycle flow: it repeats source gathering, Eureka ideas, debate, falsification, and synthesis pressure until every scout records final agreement, or pauses at the explicit max-cycle safety cap with an unpassed gate. `debate-ledger.json` must include `consensus_iterations`, `unanimous_consensus`, and per-scout agreements; `research-gate.json` cannot pass until unanimous consensus is true for all scouts. Normal Research is intentionally allowed to take one or two hours when the problem needs it; `--mock` is only for selftests or dry harness checks, and a real run blocks with `research-blocker.json` instead of silently substituting mock output when the Codex execution path is unavailable. The source layer contract separates latest papers, official/government or leading-institution sources, standards/primary docs, current news such as BBC/CNN/GDELT-style sources, public discourse such as X/Reddit, developer/practitioner knowledge such as Stack Overflow/GitHub, traditional background sources, and counterevidence/fact-checking; `source-ledger.json` must record layer coverage, source quality, blockers, citations, and cross-layer triangulation. Context7 is optional for `$Research` and only becomes relevant when the research topic specifically depends on package, API, framework, or SDK documentation. Research runs require `research-report.md`, `research-paper.md`, `genius-opinion-summary.md`, `research-source-skill.md`, `source-ledger.json`, `scout-ledger.json`, `debate-ledger.json`, `novelty-ledger.json`, `falsification-ledger.json`, and `research-gate.json` so they stay source-backed, adversarially checked, falsifiable, paper-ready, and clear about every scout lens opinion. `research status` reports source entries, source-layer coverage, triangulation checks, counterevidence, xhigh scout count, Eureka moments, debate exchanges, consensus iterations, unanimous consensus, paper presence/sections, genius-opinion summary coverage, scout findings, and falsification cases alongside the gate.
|
|
239
|
+
|
|
240
|
+
`sks recallpulse` is the 0.8.0 report-only RecallPulse utility. It writes `recallpulse-decision.json`, `mission-status-ledger.json`, `route-proof-capsule.json`, `evidence-envelope.json`, `recallpulse-governance-report.json`, `recallpulse-task-goal-ledger.json`, and `recallpulse-eval-report.json` for the current mission. RecallPulse does not replace route gates, Honest Mode, DB safety, imagegen evidence, or TriWiki validation; it records cache hits, hydration needs, duplicate suppression, route-governance risks, and final-summary-ready durable status so later releases can promote only measured improvements. Checklist updates are sequential: every `Txxx` row is treated as a child `$Goal` checkpoint, and `sks recallpulse checklist ... --task T001 --apply` refuses out-of-order checks unless explicitly overridden.
|
|
216
241
|
|
|
217
242
|
`sks pipeline plan` shows the active route lane, kept/skipped stages, verification commands, and no-unrequested-fallback invariant. `sks proof-field scan` is the lightweight rubric for small changes; risky or broad signals return to the full Team/Honest path.
|
|
218
243
|
|
|
@@ -250,7 +275,7 @@ For headless remotely controllable Codex App/server sessions on Codex CLI 0.130.
|
|
|
250
275
|
sks codex-app remote-control -- --help
|
|
251
276
|
```
|
|
252
277
|
|
|
253
|
-
`sks codex-app check` reports whether the installed Codex CLI is new enough. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
|
|
278
|
+
`sks codex-app check` reports whether the installed Codex CLI is new enough, whether the required app flags are visible, whether Fast/speed-selector config is unlocked, and whether installed OpenAI default plugins such as Browser, Chrome, Computer Use, Documents, Presentations, Spreadsheets, and LaTeX are enabled. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
|
|
254
279
|
|
|
255
280
|
Then open Codex App and use prompt commands directly in the chat. Examples:
|
|
256
281
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.2",
|
|
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",
|
|
@@ -10,6 +10,16 @@ import { initProject, installSkills } from '../core/init.mjs';
|
|
|
10
10
|
import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.mjs';
|
|
11
11
|
import { codexLaunchCommand, platformTmuxInstallHint, tmuxReadiness } from '../core/tmux-ui.mjs';
|
|
12
12
|
|
|
13
|
+
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
14
|
+
['browser', 'openai-bundled'],
|
|
15
|
+
['chrome', 'openai-bundled'],
|
|
16
|
+
['computer-use', 'openai-bundled'],
|
|
17
|
+
['latex', 'openai-bundled'],
|
|
18
|
+
['documents', 'openai-primary-runtime'],
|
|
19
|
+
['presentations', 'openai-primary-runtime'],
|
|
20
|
+
['spreadsheets', 'openai-primary-runtime']
|
|
21
|
+
];
|
|
22
|
+
|
|
13
23
|
export async function postinstall({ bootstrap }) {
|
|
14
24
|
const installRoot = path.resolve(process.env.INIT_CWD || process.cwd());
|
|
15
25
|
const conflictScan = await scanHarnessConflicts(installRoot);
|
|
@@ -488,11 +498,18 @@ export function normalizeCodexFastModeUiConfig(text = '') {
|
|
|
488
498
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
489
499
|
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
490
500
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
501
|
+
next = upsertTomlTableKey(next, 'features', 'remote_control = true');
|
|
491
502
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
492
503
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
493
504
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
494
505
|
next = upsertTomlTableKey(next, 'features', 'codex_git_commit = true');
|
|
495
506
|
next = upsertTomlTableKey(next, 'features', 'computer_use = true');
|
|
507
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use = true');
|
|
508
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use_external = true');
|
|
509
|
+
next = upsertTomlTableKey(next, 'features', 'image_generation = true');
|
|
510
|
+
next = upsertTomlTableKey(next, 'features', 'in_app_browser = true');
|
|
511
|
+
next = upsertTomlTableKey(next, 'features', 'guardian_approval = true');
|
|
512
|
+
next = upsertTomlTableKey(next, 'features', 'tool_suggest = true');
|
|
496
513
|
next = upsertTomlTableKey(next, 'features', 'apps = true');
|
|
497
514
|
next = upsertTomlTableKey(next, 'features', 'plugins = true');
|
|
498
515
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
@@ -503,21 +520,30 @@ export function normalizeCodexFastModeUiConfig(text = '') {
|
|
|
503
520
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'approval_policy = "on-request"');
|
|
504
521
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'sandbox_mode = "workspace-write"');
|
|
505
522
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'model_reasoning_effort = "high"');
|
|
523
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model = "gpt-5.5"');
|
|
524
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'service_tier = "fast"');
|
|
525
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'approval_policy = "on-request"');
|
|
526
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'sandbox_mode = "workspace-write"');
|
|
527
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model_reasoning_effort = "xhigh"');
|
|
528
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model = "gpt-5.5"');
|
|
529
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'service_tier = "fast"');
|
|
530
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'approval_policy = "never"');
|
|
531
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'sandbox_mode = "workspace-write"');
|
|
532
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model_reasoning_effort = "xhigh"');
|
|
533
|
+
for (const [name, marketplace] of DEFAULT_CODEX_APP_PLUGINS) {
|
|
534
|
+
const table = `plugins."${name}@${marketplace}"`;
|
|
535
|
+
next = upsertTomlTable(next, table, `[${table}]\nenabled = true`);
|
|
536
|
+
}
|
|
506
537
|
return ensureTrailingNewline(next);
|
|
507
538
|
}
|
|
508
539
|
|
|
509
540
|
function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
510
|
-
const legacy = {
|
|
511
|
-
model_reasoning_effort: new Set(['high'])
|
|
512
|
-
};
|
|
513
541
|
const lines = String(text || '').split('\n');
|
|
514
542
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
515
543
|
const end = firstTable === -1 ? lines.length : firstTable;
|
|
516
544
|
return lines.filter((line, index) => {
|
|
517
545
|
if (index >= end) return true;
|
|
518
|
-
|
|
519
|
-
if (!match) return true;
|
|
520
|
-
return !legacy[match[1]]?.has(match[2]);
|
|
546
|
+
return !/^\s*model_reasoning_effort\s*=/.test(line);
|
|
521
547
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
522
548
|
}
|
|
523
549
|
|
|
@@ -1056,7 +1082,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1056
1082
|
const codexLbFakeCodex = path.join(codexLbFakeBin, 'codex');
|
|
1057
1083
|
await writeTextAtomic(codexLbFakeCodex, "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo \"codex-cli 99.0.0\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"status\" ]; then echo \"logged in with browser auth\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"--with-api-key\" ]; then read key; mkdir -p \"$HOME/.codex\"; printf '{\\\"auth_mode\\\":\\\"apikey\\\",\\\"key\\\":\\\"%s\\\"}\\n' \"$key\" > \"$HOME/.codex/auth.json\"; printf '%s\\n' \"$key\" >> \"$HOME/.codex/login-calls.log\"; exit 0; fi\necho \"fake codex unsupported\" >&2\nexit 1\n");
|
|
1058
1084
|
await fsp.chmod(codexLbFakeCodex, 0o755);
|
|
1059
|
-
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "
|
|
1085
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "low"\nservice_tier = "fast"\n\n[profiles.custom]\nmodel_reasoning_effort = "low"\n\n[notice]\nfast_default_opt_out = true\n\n[features]\ncodex_hooks = true\n');
|
|
1060
1086
|
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}` };
|
|
1061
1087
|
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
1062
1088
|
cwd: tmp,
|
|
@@ -1285,7 +1311,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1285
1311
|
}
|
|
1286
1312
|
);
|
|
1287
1313
|
if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true) throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
|
|
1288
|
-
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1314
|
+
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1289
1315
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1290
1316
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
1291
1317
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest: tmux launch command does not source codex-lb env file');
|
|
@@ -1297,7 +1323,10 @@ export async function selftestCodexLb(tmp) {
|
|
|
1297
1323
|
}
|
|
1298
1324
|
|
|
1299
1325
|
function hasTopLevelCodexModeLock(text = '') {
|
|
1300
|
-
|
|
1326
|
+
const lines = String(text || '').split('\n');
|
|
1327
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1328
|
+
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
1329
|
+
return /(^|\n)\s*model\s*=\s*"codex-lb"\s*(\n|$)/.test(top) || /(^|\n)\s*model_provider\s*=\s*"openai"\s*(\n|$)/.test(top) || /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
|
|
1301
1330
|
}
|
|
1302
1331
|
|
|
1303
1332
|
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
package/src/cli/main.mjs
CHANGED
|
@@ -22,7 +22,8 @@ import { bumpProjectVersion, disableVersionGitHook, runVersionPreCommit, version
|
|
|
22
22
|
import { rustInfo } from '../core/rust-accelerator.mjs';
|
|
23
23
|
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../core/gx-renderer.mjs';
|
|
24
24
|
import { defaultEvaluationScenario, runEvaluationBenchmark } from '../core/evaluation.mjs';
|
|
25
|
-
import { evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
25
|
+
import { buildResearchPrompt, evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
26
|
+
import { evaluateRecallPulseFixtures, readMissionStatusLedger, writeRecallPulseArtifacts } from '../core/recallpulse.mjs';
|
|
26
27
|
import {
|
|
27
28
|
PPT_AUDIENCE_STRATEGY_ARTIFACT,
|
|
28
29
|
PPT_CLEANUP_REPORT_ARTIFACT,
|
|
@@ -81,6 +82,7 @@ import { context7Command } from './context7-command.mjs';
|
|
|
81
82
|
import { askPostinstallQuestion, checkCodexLbResponseChain, checkContext7, checkRequiredSkills, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
82
83
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, madHighCommand as runMadHighCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
83
84
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
85
|
+
import { recallPulseCommand } from './recallpulse-command.mjs';
|
|
84
86
|
|
|
85
87
|
const flag = (args, name) => args.includes(name);
|
|
86
88
|
const promptOf = (args) => args.filter((x) => !String(x).startsWith('--')).join(' ').trim();
|
|
@@ -109,7 +111,7 @@ export async function main(args) {
|
|
|
109
111
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
110
112
|
const handlers = {
|
|
111
113
|
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), 'codex-lb': () => codexLbCommand(sub, rest), auth: () => codexLbCommand(sub, rest), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
112
|
-
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), 'image-ux-review': () => imageUxReviewCommand(sub, rest), 'ux-review': () => imageUxReviewCommand(sub, rest), 'visual-review': () => imageUxReviewCommand(sub, rest), 'ui-ux-review': () => imageUxReviewCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
114
|
+
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), 'image-ux-review': () => imageUxReviewCommand(sub, rest), 'ux-review': () => imageUxReviewCommand(sub, rest), 'visual-review': () => imageUxReviewCommand(sub, rest), 'ui-ux-review': () => imageUxReviewCommand(sub, rest), context7: () => context7Command(sub, rest), recallpulse: () => recallPulseCommand(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
113
115
|
goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
|
|
114
116
|
team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
|
|
115
117
|
};
|
|
@@ -195,6 +197,7 @@ Usage:
|
|
|
195
197
|
sks ppt build <mission-id|latest> [--json]
|
|
196
198
|
sks ppt status <mission-id|latest> [--json]
|
|
197
199
|
sks context7 check|setup|tools|resolve|docs|evidence ...
|
|
200
|
+
sks recallpulse run|status|eval|governance|checklist <mission-id|latest>
|
|
198
201
|
sks pipeline status|resume|plan [--json] [--proof-field]
|
|
199
202
|
sks pipeline answer <mission-id|latest> <answers.json|--stdin|--text "...">
|
|
200
203
|
sks guard check [--json]
|
|
@@ -1274,7 +1277,7 @@ async function depsStatus(root = null, opts = {}) {
|
|
|
1274
1277
|
codex_cli: { ok: Boolean(codex.bin), bin: codex.bin || null, version: codex.version || null },
|
|
1275
1278
|
codex_app: app,
|
|
1276
1279
|
context7,
|
|
1277
|
-
browser_use: { ok: app.mcp.has_browser_use, cache: app.plugins.browser_use_cache },
|
|
1280
|
+
browser_use: { ok: Boolean(app.features?.browser_tool_ready || app.mcp.has_browser_use), cache: app.plugins.browser_use_cache, source: app.features?.browser_tool_source || app.mcp.browser_use_source || null },
|
|
1278
1281
|
computer_use: { ok: app.mcp.has_computer_use, cache: app.plugins.computer_use_cache },
|
|
1279
1282
|
tmux: { ok: Boolean(tmux.ok), bin: tmux.bin || null, version: tmux.version || null, min_version: tmux.min_version || '3.0', current_session: Boolean(tmux.current_session), install_hint: tmux.ok ? null : platformTmuxInstallHint(), error: tmux.error || null },
|
|
1280
1283
|
homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_tmux_install: homebrewNeeded } : { ok: null, bin: null, required_for_tmux_install: false },
|
|
@@ -1304,7 +1307,7 @@ function printDepsStatus(status) {
|
|
|
1304
1307
|
console.log(`Codex App: ${status.codex_app.app.installed ? 'ok' : 'missing'}`);
|
|
1305
1308
|
console.log(`Image Gen: ${status.codex_app.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1306
1309
|
console.log(`Context7: ${status.context7.ok ? 'ok' : 'missing'}`);
|
|
1307
|
-
console.log(`Browser
|
|
1310
|
+
console.log(`Browser: ${status.browser_use.ok ? `ok${status.browser_use.source ? ` (${status.browser_use.source})` : ''}` : 'missing'}`);
|
|
1308
1311
|
console.log(`Computer Use:${status.computer_use.ok ? ' ok' : ' missing'}`);
|
|
1309
1312
|
console.log(`tmux: ${tmuxStatusKind(status.tmux)} ${status.tmux.version || status.tmux.error || ''}`.trimEnd());
|
|
1310
1313
|
if (process.platform === 'darwin') console.log(`Homebrew: ${status.homebrew.ok ? 'ok' : 'missing'} ${status.homebrew.bin || ''}`.trimEnd());
|
|
@@ -1634,7 +1637,7 @@ async function setup(args) {
|
|
|
1634
1637
|
else console.log('Git: .gitignore ignores SKS generated files');
|
|
1635
1638
|
console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
|
|
1636
1639
|
console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
|
|
1637
|
-
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser
|
|
1640
|
+
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser=${appRuntime.features?.browser_tool_ready ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1638
1641
|
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight Direct Fix route, $PPT HTML/PDF presentation route, Context7 gate`);
|
|
1639
1642
|
console.log(`Skills: .agents/skills`);
|
|
1640
1643
|
console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
|
|
@@ -1774,7 +1777,7 @@ async function doctor(args) {
|
|
|
1774
1777
|
console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
|
|
1775
1778
|
console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
|
|
1776
1779
|
console.log(`Context7: ${result.context7.ok ? 'ok' : 'missing MCP config'} project=${result.context7.project.ok ? 'ok' : 'missing'} global=${result.context7.global.ok ? 'ok' : 'missing'}`);
|
|
1777
|
-
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser
|
|
1780
|
+
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser=${appRuntime.features?.browser_tool_ready ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1778
1781
|
console.log(`tmux: ${tmuxStatusKind(result.runtime.tmux)} ${result.runtime.tmux.version || result.runtime.tmux.error || ''}`.trimEnd());
|
|
1779
1782
|
console.log(`Guard: ${result.harness_guard.ok ? 'ok' : 'blocked'}${result.harness_guard.source_exception ? ' source-exception' : ''}`);
|
|
1780
1783
|
console.log(`Version: ${result.versioning.ok ? 'ok' : 'missing'}${result.versioning.enabled ? ` ${result.versioning.package_version || ''}` : ` ${result.versioning.reason || 'disabled'}`}`);
|
|
@@ -1960,6 +1963,12 @@ async function safeReadText(file, fallback = '') {
|
|
|
1960
1963
|
}
|
|
1961
1964
|
|
|
1962
1965
|
async function resolveMissionId(root, arg) { return (!arg || arg === 'latest') ? findLatestMission(root) : arg; }
|
|
1966
|
+
|
|
1967
|
+
function hasResearchProfileConfig(text = '') {
|
|
1968
|
+
return /\[profiles\.sks-research-xhigh\][\s\S]*?model = "gpt-5\.5"[\s\S]*?model_reasoning_effort = "xhigh"/.test(text)
|
|
1969
|
+
&& /\[profiles\.sks-research\][\s\S]*?model = "gpt-5\.5"[\s\S]*?approval_policy = "never"[\s\S]*?model_reasoning_effort = "xhigh"/.test(text);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1963
1972
|
function readMaxCycles(args, fallback) {
|
|
1964
1973
|
const i = args.indexOf('--max-cycles');
|
|
1965
1974
|
const raw = i >= 0 && args[i + 1] ? Number(args[i + 1]) : Number(fallback);
|
|
@@ -2009,6 +2018,8 @@ async function selftest() {
|
|
|
2009
2018
|
if (trippedStop) throw new Error('selftest: compliance loop guard did not terminally trip');
|
|
2010
2019
|
const loopBlocker = await readJson(path.join(loopMission.dir, 'hard-blocker.json'), null);
|
|
2011
2020
|
if (loopBlocker?.reason !== 'compliance_loop_guard_tripped') throw new Error('selftest: compliance loop guard did not write hard blocker');
|
|
2021
|
+
const hardBlockerUnblocked = await evaluateStop(tmp, loopState, { last_assistant_message: 'done' });
|
|
2022
|
+
if (hardBlockerUnblocked?.decision === 'block' && !String(hardBlockerUnblocked.reason || '').includes('reflection')) throw new Error('selftest: hard blocker did not unblock incomplete active gate');
|
|
2012
2023
|
const clarificationMission = await createMission(tmp, { mode: 'team', prompt: 'visible question gate selftest' });
|
|
2013
2024
|
await writeTextAtomic(path.join(clarificationMission.dir, 'questions.md'), '# Questions\n\n1. GOAL_PRECISE: What should be changed?\n');
|
|
2014
2025
|
await writeJsonAtomic(path.join(clarificationMission.dir, 'required-answers.schema.json'), { slots: [{ id: 'GOAL_PRECISE', question: 'What should be changed?' }] });
|
|
@@ -2164,6 +2175,7 @@ async function selftest() {
|
|
|
2164
2175
|
const doctorGlobalCodexConfig = await safeReadText(path.join(doctorGlobalHome, '.codex', 'config.toml'));
|
|
2165
2176
|
if (!doctorGlobalRepairJson.repair?.global_codex_config) throw new Error('selftest: doctor global config repair missing');
|
|
2166
2177
|
assertCodexWarn(doctorGlobalCodexConfig, 'doctor global config');
|
|
2178
|
+
if (missingGeneratedCodexAppFeatureFlags(doctorGlobalCodexConfig).length || hasDeprecatedCodexHooksFeatureFlag(doctorGlobalCodexConfig) || !hasResearchProfileConfig(doctorGlobalCodexConfig)) throw new Error('selftest: doctor global config repair did not restore Codex App feature flags and Research xhigh profiles');
|
|
2167
2179
|
for (const name of stalePluginSkillNames) {
|
|
2168
2180
|
if (await exists(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest: doctor --fix did not remove global generated ${name} plugin shadow skill`);
|
|
2169
2181
|
}
|
|
@@ -2234,6 +2246,7 @@ async function selftest() {
|
|
|
2234
2246
|
const postinstallNoMarkerConfig = await safeReadText(path.join(postinstallNoMarkerGlobalRoot, '.codex', 'config.toml'));
|
|
2235
2247
|
if (missingGeneratedCodexAppFeatureFlags(postinstallNoMarkerConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallNoMarkerConfig)) throw new Error('selftest: no-marker flags');
|
|
2236
2248
|
assertCodexWarn(postinstallNoMarkerConfig, 'postinstall global runtime config');
|
|
2249
|
+
if (!hasResearchProfileConfig(postinstallNoMarkerConfig)) throw new Error('selftest: postinstall global runtime config did not restore Research xhigh profiles');
|
|
2237
2250
|
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest: no-marker postinstall polluted install cwd');
|
|
2238
2251
|
if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest: global runtime bootstrap without project git wrote shared .gitignore');
|
|
2239
2252
|
const bootstrapJsonTmp = tmpdir();
|
|
@@ -2268,12 +2281,15 @@ async function selftest() {
|
|
|
2268
2281
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest: MAD tmux attach args are not stable by session name');
|
|
2269
2282
|
const defaultFastHighPlan = await buildTmuxLaunchPlan({ root: tmp, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2270
2283
|
if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="high"') throw new Error('selftest: default sks tmux launch is not fast-high');
|
|
2271
|
-
const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.
|
|
2272
|
-
if (forcedModelPlan.codexArgs.includes('gpt-5.
|
|
2273
|
-
const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-
|
|
2274
|
-
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.
|
|
2275
|
-
const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-
|
|
2276
|
-
if (codexExecArgs.join(' ').includes('gpt-5.
|
|
2284
|
+
const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.0-forbidden', SKS_CODEX_FAST_HIGH: '0', SKS_CODEX_REASONING: 'medium' }, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2285
|
+
if (forcedModelPlan.codexArgs.includes('gpt-5.0-forbidden') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="medium"') throw new Error('selftest: sks tmux launch allowed a non-GPT-5.5 model override');
|
|
2286
|
+
const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-forbidden-model', '--model', 'gpt-5.0-forbidden', '-c', 'model="gpt-5.0-forbidden"', '-c', 'model_reasoning_effort="low"'], tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2287
|
+
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.0-forbidden') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" --profile legacy-forbidden-model -c model_reasoning_effort="low"') throw new Error('selftest: explicit tmux model override was not forced back to GPT-5.5');
|
|
2288
|
+
const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-forbidden-model', extraArgs: ['--model=gpt-5.0-forbidden', '--config', 'model = "gpt-5.0-forbidden"', '-c', 'model_reasoning_effort="medium"'] });
|
|
2289
|
+
if (codexExecArgs.join(' ').includes('gpt-5.0-forbidden') || !codexExecArgs.includes('gpt-5.5') || codexExecArgs.includes('--model=gpt-5.0-forbidden')) throw new Error('selftest: codex exec args allowed a non-GPT-5.5 model override');
|
|
2290
|
+
const researchExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'research exec selftest', profile: 'sks-research', extraArgs: ['-c', 'service_tier="fast"', '-c', 'model_reasoning_effort="xhigh"'] });
|
|
2291
|
+
const researchExecJoined = researchExecArgs.join(' ');
|
|
2292
|
+
if (!researchExecJoined.includes('--profile sks-research') || !researchExecJoined.includes('--model gpt-5.5') || !researchExecJoined.includes('service_tier="fast"') || !researchExecJoined.includes('model_reasoning_effort="xhigh"')) throw new Error('selftest: research exec args did not force GPT-5.5 fast xhigh execution');
|
|
2277
2293
|
await selftestCodexLb(tmp);
|
|
2278
2294
|
if (!shouldAutoAttachTmux(['--mad'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest: MAD tmux launch does not auto-attach in an interactive terminal');
|
|
2279
2295
|
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest: MAD tmux json mode should not auto-attach');
|
|
@@ -2327,14 +2343,14 @@ async function selftest() {
|
|
|
2327
2343
|
if (remoteControlStatus.code !== 0) throw new Error(`selftest: Codex remote-control status exited ${remoteControlStatus.code}: ${remoteControlStatus.stderr}`);
|
|
2328
2344
|
const remoteControlJson = JSON.parse(remoteControlStatus.stdout);
|
|
2329
2345
|
if (!remoteControlJson.ok || remoteControlJson.min_version !== '0.130.0' || !String(remoteControlJson.command || '').includes('remote-control')) throw new Error('selftest: Codex remote-control status did not report 0.130.0 readiness');
|
|
2330
|
-
const remoteControlLaunch = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--', '--model', 'gpt-5.
|
|
2346
|
+
const remoteControlLaunch = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--', '--model', 'gpt-5.0-forbidden', '-c', 'model="gpt-5.0-forbidden"', '--example'], {
|
|
2331
2347
|
cwd: globalCwd,
|
|
2332
2348
|
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, PATH: remoteControlBin },
|
|
2333
2349
|
timeoutMs: 15000,
|
|
2334
2350
|
maxOutputBytes: 64 * 1024
|
|
2335
2351
|
});
|
|
2336
2352
|
const remoteControlLaunchText = `${remoteControlLaunch.stdout}\n${remoteControlLaunch.stderr}`;
|
|
2337
|
-
if (remoteControlLaunch.code !== 0 || remoteControlLaunchText.includes('gpt-5.
|
|
2353
|
+
if (remoteControlLaunch.code !== 0 || remoteControlLaunchText.includes('gpt-5.0-forbidden') || remoteControlLaunchText.includes('--model') || !remoteControlLaunchText.includes('-c model="gpt-5.5"')) throw new Error('selftest: Codex remote-control passthrough did not force GPT-5.5 with config syntax');
|
|
2338
2354
|
const remoteControlOldBin = path.join(tmp, 'remote-control-old-bin');
|
|
2339
2355
|
await ensureDir(remoteControlOldBin);
|
|
2340
2356
|
await writeTextAtomic(path.join(remoteControlOldBin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 0.129.0"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
@@ -2845,10 +2861,10 @@ async function selftest() {
|
|
|
2845
2861
|
if (hookTeamState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookTeamState.implementation_allowed === false || !hookTeamState.team_plan_ready) throw new Error('selftest: $Team hook did not prepare direct Team mission');
|
|
2846
2862
|
if (!hookTeamState.pipeline_plan_ready || !(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), PIPELINE_PLAN_ARTIFACT)))) throw new Error('selftest: $Team hook did not write a pipeline plan');
|
|
2847
2863
|
if (!(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), 'team-plan.json')))) throw new Error('selftest: Team plan was not created directly');
|
|
2848
|
-
const hookForbiddenModelResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team should be blocked before route work', model: 'gpt-5.5', metadata: { client: { modelId: 'gpt-5.
|
|
2864
|
+
const hookForbiddenModelResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team should be blocked before route work', model: 'gpt-5.5', metadata: { client: { modelId: 'gpt-5.0-forbidden' } } }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2849
2865
|
if (hookForbiddenModelResult.code !== 0) throw new Error(`selftest: forbidden model hook exited ${hookForbiddenModelResult.code}: ${hookForbiddenModelResult.stderr}`);
|
|
2850
2866
|
const hookForbiddenModelJson = JSON.parse(hookForbiddenModelResult.stdout);
|
|
2851
|
-
if (hookForbiddenModelJson.decision !== 'block' || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.5') || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.
|
|
2867
|
+
if (hookForbiddenModelJson.decision !== 'block' || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.5') || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.0-forbidden')) throw new Error('selftest: hook did not block forbidden client model metadata');
|
|
2852
2868
|
const hookTeamPendingResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 새 작업으로 넘어가' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2853
2869
|
if (hookTeamPendingResult.code !== 0) throw new Error(`selftest: pending clarification hook exited ${hookTeamPendingResult.code}: ${hookTeamPendingResult.stderr}`);
|
|
2854
2870
|
const hookTeamPendingJson = JSON.parse(hookTeamPendingResult.stdout);
|
|
@@ -3021,7 +3037,8 @@ async function selftest() {
|
|
|
3021
3037
|
if (missingCodexConfigFlags.length || hasDeprecatedCodexHooksFeatureFlag(codexConfigText)) throw new Error(`selftest: generated Codex App feature flags missing or deprecated: ${missingCodexConfigFlags.join(', ')}`);
|
|
3022
3038
|
assertCodexWarn(codexConfigText, 'generated Codex App config');
|
|
3023
3039
|
if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest: Context7 MCP not configured');
|
|
3024
|
-
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest: GPT-5.5 reasoning profiles not configured');
|
|
3040
|
+
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-research]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest: GPT-5.5 reasoning profiles not configured');
|
|
3041
|
+
if (!hasResearchProfileConfig(codexConfigText)) throw new Error('selftest: generated Research xhigh profiles not configured');
|
|
3025
3042
|
if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest: generated sks-mad-high profile is not full access');
|
|
3026
3043
|
if (!codexConfigText.includes('[agents.analysis_scout]')) throw new Error('selftest: analysis_scout agent not configured');
|
|
3027
3044
|
if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest: team_consensus agent not configured');
|
|
@@ -3034,20 +3051,22 @@ async function selftest() {
|
|
|
3034
3051
|
assertCodexWarn(preservedConfig, 'merged Codex config');
|
|
3035
3052
|
if (preservedConfig.includes('fast_default_opt_out = true') || !preservedConfig.includes('keep = true')) throw new Error('selftest: Codex config merge did not remove stale Fast opt-out notice while preserving other notice keys');
|
|
3036
3053
|
const missingPreservedFlags = missingGeneratedCodexAppFeatureFlags(preservedConfig);
|
|
3037
|
-
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error(`selftest: Codex config merge did not add required app feature flags, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
3054
|
+
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]') || !hasResearchProfileConfig(preservedConfig)) throw new Error(`selftest: Codex config merge did not add required app feature flags, Research profiles, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
3038
3055
|
if (hasTopLevelCodexModeLock(preservedConfig)) throw new Error('selftest: Codex config merge left top-level legacy model/reasoning locks that hide Fast mode UI');
|
|
3039
3056
|
const appFeatureTmp = tmpdir();
|
|
3040
3057
|
const fakeCodexApp = path.join(appFeatureTmp, 'Codex.app');
|
|
3041
3058
|
const fakeCodexBinDir = path.join(appFeatureTmp, 'bin');
|
|
3042
3059
|
await ensureDir(fakeCodexApp);
|
|
3043
3060
|
await ensureDir(fakeCodexBinDir);
|
|
3061
|
+
await ensureDir(path.join(appFeatureTmp, '.codex'));
|
|
3062
|
+
await writeTextAtomic(path.join(appFeatureTmp, '.codex', 'config.toml'), codexConfigText);
|
|
3044
3063
|
const fakeCodex = path.join(fakeCodexBinDir, 'codex');
|
|
3045
|
-
await writeTextAtomic(fakeCodex, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development true\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3064
|
+
await writeTextAtomic(fakeCodex, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\nbrowser_use stable true\nbrowser_use_external stable true\ncodex_git_commit under development true\ncomputer_use stable true\nfast_mode stable true\nguardian_approval stable true\nhooks stable true\nimage_generation stable true\nin_app_browser stable true\nplugins stable true\nremote_control under development true\ntool_suggest stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3046
3065
|
await fsp.chmod(fakeCodex, 0o755);
|
|
3047
3066
|
const codexAppFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodex, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
3048
|
-
if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit) throw new Error('selftest: codex-app check did not accept required app feature flags
|
|
3067
|
+
if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit || !codexAppFeatureStatus.features?.remote_control || !codexAppFeatureStatus.features?.fast_mode_config?.ok) throw new Error('selftest: codex-app check did not accept required app feature flags, remote_control, and unlocked Fast UI config');
|
|
3049
3068
|
const fakeCodexMissing = path.join(fakeCodexBinDir, 'codex-missing-git-commit');
|
|
3050
|
-
await writeTextAtomic(fakeCodexMissing, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development false\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3069
|
+
await writeTextAtomic(fakeCodexMissing, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\nbrowser_use stable true\nbrowser_use_external stable true\ncodex_git_commit under development false\ncomputer_use stable true\nfast_mode stable true\nguardian_approval stable true\nhooks stable true\nimage_generation stable true\nin_app_browser stable true\nplugins stable true\nremote_control under development true\ntool_suggest stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3051
3070
|
await fsp.chmod(fakeCodexMissing, 0o755);
|
|
3052
3071
|
const codexAppMissingFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodexMissing, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
3053
3072
|
if (codexAppMissingFeatureStatus.ok || codexAppMissingFeatureStatus.features?.required_flags_ok || codexAppMissingFeatureStatus.features?.codex_git_commit) throw new Error('selftest: codex-app check did not block disabled codex_git_commit feature flag');
|
|
@@ -3905,18 +3924,41 @@ async function selftest() {
|
|
|
3905
3924
|
await writeJsonAtomic(path.join(path.dirname(dryRunPack.file), 'low-trust-artifact.json'), { trust_summary: { avg: 0.1 }, wiki: { anchors: [] } });
|
|
3906
3925
|
const wikiPruneDryRun = await pruneWikiArtifacts(tmp, { dryRun: true });
|
|
3907
3926
|
if (wikiPruneDryRun.candidates < 1 || !wikiPruneDryRun.actions.some((action) => action.reason === 'low_wiki_trust')) throw new Error('selftest: wiki prune did not flag low-trust artifact');
|
|
3927
|
+
await writeJsonAtomic(path.join(tmp, '.sneakoscope', 'wiki', 'context-pack.json'), wikiPack);
|
|
3928
|
+
const recallPulseRun = await writeRecallPulseArtifacts(tmp, {
|
|
3929
|
+
missionId: recallMission.id,
|
|
3930
|
+
state: { mission_id: recallMission.id, mode: 'team', route: 'team', phase: 'implementation', prompt: recallPrompt },
|
|
3931
|
+
stageId: 'before_implementation'
|
|
3932
|
+
});
|
|
3933
|
+
if (!recallPulseRun.decision?.report_only || !recallPulseRun.decision?.l1?.selected?.length || recallPulseRun.decision?.l2?.tier !== 'L2' || recallPulseRun.decision?.l3?.tier !== 'L3') throw new Error('selftest: RecallPulse did not write L1/L2/L3 report-only decision');
|
|
3934
|
+
if (!recallPulseRun.capsule?.report_only || !recallPulseRun.envelope?.claim_ids_supported?.includes('durable_status_ledger')) throw new Error('selftest: RecallPulse proof capsule/evidence envelope incomplete');
|
|
3935
|
+
const recallPulseStatusLedger = await readMissionStatusLedger(tmp, recallMission.id);
|
|
3936
|
+
if (!recallPulseStatusLedger?.entries?.length || !recallPulseStatusLedger.final_summary_projection?.last_user_visible) throw new Error('selftest: RecallPulse durable status ledger missing');
|
|
3937
|
+
const repeatedRecallPulseRun = await writeRecallPulseArtifacts(tmp, {
|
|
3938
|
+
missionId: recallMission.id,
|
|
3939
|
+
state: { mission_id: recallMission.id, mode: 'team', route: 'team', phase: 'implementation', prompt: recallPrompt },
|
|
3940
|
+
stageId: 'before_implementation'
|
|
3941
|
+
});
|
|
3942
|
+
if (repeatedRecallPulseRun.decision?.recommended_action !== 'suppress' || !repeatedRecallPulseRun.decision?.duplicate_suppression?.repeated) throw new Error('selftest: RecallPulse duplicate reminder suppression missing');
|
|
3943
|
+
const recallPulseEval = await evaluateRecallPulseFixtures(tmp, { missionId: recallMission.id, write: true });
|
|
3944
|
+
if (!recallPulseEval.passed || recallPulseEval.metrics.route_gate_agreement < 1 || recallPulseEval.metrics.unsupported_performance_claims !== 0) throw new Error('selftest: RecallPulse fixture eval failed');
|
|
3908
3945
|
const { dir: researchDir, mission: researchMission } = await createMission(tmp, { mode: 'research', prompt: '새로운 코드 리뷰 방법론 연구' });
|
|
3909
3946
|
const researchPlan = await writeResearchPlan(researchDir, researchMission.prompt, {});
|
|
3910
3947
|
if (researchPlan.methodology !== 'genius-scout-council-frontier-discovery-loop' || researchPlan.web_research_policy?.mode !== 'layered_source_retrieval_and_triangulation') throw new Error('selftest: research plan contract');
|
|
3948
|
+
if (researchPlan.execution_policy?.default_max_cycles !== 12 || researchPlan.mutation_policy?.implementation_allowed !== false || !String(researchPlan.research_council?.debate_policy?.rule || '').includes('every scout records final agreement')) throw new Error('selftest: research consensus/no-code contract');
|
|
3949
|
+
if (!researchPlan.research_council?.scouts?.every((scout) => scout.display_name && scout.persona && scout.persona_boundary && scout.reasoning_effort === 'xhigh')) throw new Error('selftest: research scout persona contract missing from plan');
|
|
3950
|
+
const researchPrompt = buildResearchPrompt({ id: researchMission.id, mission: researchMission, plan: researchPlan, cycle: 1, previous: '' });
|
|
3951
|
+
if (!researchPrompt.includes('NO-CODE-MUTATION POLICY') || !researchPrompt.includes('not a fixed three-cycle run') || !researchPrompt.includes('unanimous_consensus=true')) throw new Error('selftest: research prompt missing no-code unanimous consensus policy');
|
|
3911
3952
|
const rArts = researchPlan.required_artifacts || [];
|
|
3912
3953
|
for (const a of [rss, 'source-ledger.json', 'scout-ledger.json', 'debate-ledger.json', 'falsification-ledger.json']) if (!rArts.includes(a) || !(await exists(path.join(researchDir, a)))) throw new Error('selftest: research artifact');
|
|
3913
3954
|
if (!rArts.includes('research-paper.md') || !rArts.includes(gos)) throw new Error('selftest: research paper');
|
|
3914
3955
|
const initialResearchGate = await evaluateResearchGate(researchDir);
|
|
3915
|
-
if (initialResearchGate.passed || ['web_search_pass_missing', 'eureka_missing', 'debate_exchanges_missing', 'research_paper_missing'].some((r) => !initialResearchGate.reasons.includes(r))) throw new Error('selftest: research gate');
|
|
3956
|
+
if (initialResearchGate.passed || ['web_search_pass_missing', 'eureka_missing', 'debate_exchanges_missing', 'research_paper_missing', 'consensus_iteration_missing', 'unanimous_consensus_missing'].some((r) => !initialResearchGate.reasons.includes(r))) throw new Error('selftest: research gate');
|
|
3916
3957
|
const researchGate = await writeMockResearchResult(researchDir, researchPlan);
|
|
3917
3958
|
if (!researchGate.passed) throw new Error('selftest: mock research gate did not pass');
|
|
3918
3959
|
const rm = researchGate.metrics || {};
|
|
3919
|
-
if (
|
|
3960
|
+
if (rm.scout_persona_contract_ok !== true || (rm.scout_persona_issues || []).length) throw new Error('selftest: research scout persona contract did not pass');
|
|
3961
|
+
if (['independent_scouts', 'xhigh_scouts', 'eureka_moments', 'debate_participants', 'genius_opinion_summaries'].some((m) => rm[m] < 5) || ['counterevidence_sources', 'falsification_cases', 'triangulation_checks'].some((m) => rm[m] < 1) || rm.paper_sections < 8 || rm.citation_coverage !== true || rm.source_layers_covered < 7 || rm.consensus_iterations < 1 || rm.unanimous_consensus !== true || rm.consensus_agreed_scouts < 5) throw new Error('selftest: research metrics');
|
|
3920
3962
|
await writeJsonAtomic(path.join(dir, 'done-gate.json'), { passed: true, unsupported_critical_claims: 0, database_safety_violation: false, database_safety_reviewed: true, visual_drift: 'low', wiki_drift: 'low', tests_required: false });
|
|
3921
3963
|
const gate = await evaluateDoneGate(tmp, id);
|
|
3922
3964
|
if (!gate.passed) throw new Error('selftest: done gate');
|