sneakoscope 0.7.26 → 0.7.37
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 +24 -17
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +93 -6
- package/src/cli/main.mjs +143 -13
- package/src/cli/maintenance-commands.mjs +45 -1
- package/src/core/decision-contract.mjs +26 -4
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +17 -4
- package/src/core/mistake-recall.mjs +277 -0
- package/src/core/openclaw.mjs +7 -1
- package/src/core/pipeline.mjs +14 -0
- package/src/core/questions.mjs +12 -1
- package/src/core/routes.mjs +1 -1
- package/src/core/tmux-ui.mjs +34 -2
- package/src/core/triwiki-attention.mjs +55 -3
- package/src/core/version-manager.mjs +33 -1
package/README.md
CHANGED
|
@@ -11,11 +11,10 @@ Install globally, then run `sks` from either a project or any global shell locat
|
|
|
11
11
|
```sh
|
|
12
12
|
npm i -g sneakoscope
|
|
13
13
|
sks root
|
|
14
|
-
sks bootstrap
|
|
15
14
|
sks
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
`
|
|
17
|
+
`npm i -g sneakoscope` automatically refreshes the `sks` command shim, global Codex App `$` skills, and SKS bootstrap surface. When the install is run from a project, postinstall bootstraps that project. When it is run outside a repo/project marker, postinstall bootstraps the per-user global runtime root instead of writing `.sneakoscope` into a random current directory. `sks root` tells you which root SKS will use.
|
|
19
18
|
|
|
20
19
|
If you only want a one-shot run without keeping `sks` installed globally:
|
|
21
20
|
|
|
@@ -43,7 +42,7 @@ sks selftest --mock
|
|
|
43
42
|
|
|
44
43
|
| Area | What it does |
|
|
45
44
|
| --- | --- |
|
|
46
|
-
| CLI runtime | `sks tmux
|
|
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 the high-reasoning auto-review profile. |
|
|
47
46
|
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. |
|
|
48
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. |
|
|
49
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`. |
|
|
@@ -54,7 +53,7 @@ sks selftest --mock
|
|
|
54
53
|
| PPT pipeline | Uses `$PPT` for simple, restrained, information-first HTML/PDF presentation artifacts, first asking delivery context, audience profile, STP strategy, decision context, and 3+ pain-point to solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in `ppt-parallel-report.json`; editable source HTML is preserved under `source-html/`, PPT-only temporary build files are cleaned after completion, installed skills/MCPs outside the `$PPT` allowlist are ignored, generated image assets may use `$imagegen` only when sealed in the contract, and `ppt-style-tokens.json` records the design SSOT plus fused source inputs. |
|
|
55
54
|
| Computer Use fast lane | Uses `$Computer-Use` / `$CU` for UI/browser/visual work that needs maximum speed: skip Team debate and upfront TriWiki loops, use Codex Computer Use directly, then refresh/validate TriWiki and run Honest Mode at final closeout. |
|
|
56
55
|
| Goal | Provides a fast SKS bridge overlay for Codex native persisted `/goal` create, pause, resume, and clear controls; implementation continues through the selected SKS execution route. |
|
|
57
|
-
| TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`,
|
|
56
|
+
| TriWiki voxels | Maintains `.sneakoscope/wiki/context-pack.json` as the context SSOT with coordinate anchors, voxel metadata, `attention.use_first`, `attention.hydrate_first`, and prompt-bound mistake recall ledgers. |
|
|
58
57
|
| Context7 | Requires current docs for external packages, APIs, MCPs, SDKs, and framework/runtime behavior when correctness depends on current guidance. |
|
|
59
58
|
| Design SSOT | Treats `design.md` as the only design decision source of truth. `docs/Design-Sys-Prompt.md` is the builder prompt; getdesign.md, official getdesign docs, and curated DESIGN.md examples from `VoltAgent/awesome-design-md` are source inputs that must be fused into `design.md` or route-local style tokens instead of becoming parallel authorities. |
|
|
60
59
|
| DB safety | Treats SQL, migrations, Supabase, RLS, and destructive operations as high risk. |
|
|
@@ -75,9 +74,9 @@ Install tmux from [tmux.dev/download](https://www.tmux.dev/download). On macOS,
|
|
|
75
74
|
brew install tmux
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
`sks --mad` is stricter than the normal runtime path:
|
|
77
|
+
The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex` versions before opening tmux and prompts to update when the terminal can answer y/n. If you approve the Codex CLI update, SKS installs `@openai/codex@latest` and opens tmux with the version visible on PATH. `sks --mad` is stricter than the normal runtime path:
|
|
79
78
|
|
|
80
|
-
- Checks npm for
|
|
79
|
+
- Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
|
|
81
80
|
- Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
|
|
82
81
|
- Requires tmux 3.x or newer before opening the session.
|
|
83
82
|
- Creates or reuses a named detached tmux session, splits panes, and prints the attach command.
|
|
@@ -91,10 +90,9 @@ Use this when you want `sks` available from any repo:
|
|
|
91
90
|
```sh
|
|
92
91
|
npm i -g sneakoscope
|
|
93
92
|
sks root
|
|
94
|
-
sks bootstrap
|
|
95
93
|
```
|
|
96
94
|
|
|
97
|
-
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root.
|
|
95
|
+
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root. Global npm install/upgrade automatically bootstraps the current project when a project marker is present, otherwise it bootstraps the global runtime root. Run `sks bootstrap` manually only when you intentionally want to initialize or repair the current project after install.
|
|
98
96
|
|
|
99
97
|
Project setup writes shared `.gitignore` entries for generated SKS files: `.sneakoscope/`, `.codex/`, `.agents/`, and managed `AGENTS.md`. Setup, doctor repair, and npm postinstall refreshes also compare the previous SKS generated-file manifest with the current package templates and prune stale SKS-generated legacy skills or agent files while preserving user-owned custom skills. Use `sks setup --local-only` when you want those excludes kept only in `.git/info/exclude`.
|
|
100
98
|
|
|
@@ -162,12 +160,15 @@ sks fix-path
|
|
|
162
160
|
### Open Codex CLI With tmux
|
|
163
161
|
|
|
164
162
|
```sh
|
|
163
|
+
sks
|
|
165
164
|
sks tmux open
|
|
166
165
|
sks tmux check
|
|
167
166
|
sks tmux status --once
|
|
168
167
|
```
|
|
169
168
|
|
|
170
|
-
`sks
|
|
169
|
+
Bare `sks` creates or reuses the default named tmux session for Codex CLI and attaches to it in an interactive terminal. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help. Use `--no-attach` or `SKS_TMUX_NO_AUTO_ATTACH=1` when you only want SKS to create/reuse the session and print the manual attach command.
|
|
170
|
+
|
|
171
|
+
Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
|
|
171
172
|
|
|
172
173
|
### MAD tmux Launch
|
|
173
174
|
|
|
@@ -176,7 +177,7 @@ sks --mad
|
|
|
176
177
|
sks --mad --yes
|
|
177
178
|
```
|
|
178
179
|
|
|
179
|
-
This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `approval_policy = "on-request"` and `approvals_reviewer = "auto_review"
|
|
180
|
+
This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `approval_policy = "on-request"` and `approvals_reviewer = "auto_review"`, then attaches to the session in an interactive terminal. It is scoped to that explicit command and does not change normal SKS/DB safety defaults. Repeat launches reuse the same named SKS MAD tmux session.
|
|
180
181
|
|
|
181
182
|
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.
|
|
182
183
|
|
|
@@ -331,10 +332,14 @@ agents:
|
|
|
331
332
|
coding-agent:
|
|
332
333
|
tools:
|
|
333
334
|
- shell
|
|
335
|
+
env:
|
|
336
|
+
SKS_OPENCLAW: "1"
|
|
334
337
|
skills:
|
|
335
338
|
- sneakoscope-codex
|
|
336
339
|
```
|
|
337
340
|
|
|
341
|
+
`SKS_OPENCLAW=1` tells SKS that commands are running from OpenClaw. In that mode, SKS auto-approves update/install prompts such as the Codex CLI update check before tmux launch, instead of waiting for a human `Y/n` response.
|
|
342
|
+
|
|
338
343
|
Then prompt the OpenClaw agent from the target repo root:
|
|
339
344
|
|
|
340
345
|
```text
|
|
@@ -344,11 +349,11 @@ Run sks root, inspect AGENTS.md, then use the SKS Team route to implement this f
|
|
|
344
349
|
Useful commands for OpenClaw agents:
|
|
345
350
|
|
|
346
351
|
```sh
|
|
347
|
-
sks root
|
|
348
|
-
sks commands
|
|
349
|
-
sks dollar-commands
|
|
350
|
-
sks deps check
|
|
351
|
-
sks proof-field scan --intent "small CLI change" --changed src/cli/main.mjs
|
|
352
|
+
SKS_OPENCLAW=1 sks root
|
|
353
|
+
SKS_OPENCLAW=1 sks commands
|
|
354
|
+
SKS_OPENCLAW=1 sks dollar-commands
|
|
355
|
+
SKS_OPENCLAW=1 sks deps check
|
|
356
|
+
SKS_OPENCLAW=1 sks proof-field scan --intent "small CLI change" --changed src/cli/main.mjs
|
|
352
357
|
```
|
|
353
358
|
|
|
354
359
|
If OpenClaw runs the skill inside a sandbox, grant shell execution only for the trusted local workspace. Database, Supabase, migration, and destructive filesystem work should still follow the repo's SKS safety route and require explicit write scope.
|
|
@@ -393,9 +398,11 @@ sks selftest --mock
|
|
|
393
398
|
|
|
394
399
|
```sh
|
|
395
400
|
sks tmux check
|
|
396
|
-
sks
|
|
401
|
+
sks
|
|
397
402
|
```
|
|
398
403
|
|
|
404
|
+
`sks tmux open` is the equivalent explicit launch form when you want to pass tmux session flags.
|
|
405
|
+
|
|
399
406
|
For the high-reasoning full-access profile:
|
|
400
407
|
|
|
401
408
|
```sh
|
|
@@ -428,7 +435,7 @@ sks wiki refresh
|
|
|
428
435
|
sks wiki validate .sneakoscope/wiki/context-pack.json
|
|
429
436
|
```
|
|
430
437
|
|
|
431
|
-
TriWiki is the long-running context source of truth. It keeps compact high-trust recall in `attention.use_first
|
|
438
|
+
TriWiki is the long-running context source of truth. It keeps compact high-trust recall in `attention.use_first`, source-hydration targets in `attention.hydrate_first`, and binds relevant prior-mistake claims into the current decision contract when they match the prompt.
|
|
432
439
|
|
|
433
440
|
## Safety Model
|
|
434
441
|
|
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.37",
|
|
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",
|
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
-
import { ensureDir, exists, packageRoot, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
6
|
+
import { ensureDir, exists, globalSksRoot, packageRoot, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
7
7
|
import { getCodexInfo } from '../core/codex-adapter.mjs';
|
|
8
8
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
|
|
9
9
|
import { installSkills } from '../core/init.mjs';
|
|
@@ -83,15 +83,20 @@ function shouldAskPostinstallQuestion() {
|
|
|
83
83
|
export async function postinstallBootstrapDecision(root) {
|
|
84
84
|
if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return { run: false, reason: 'SKS_POSTINSTALL_NO_BOOTSTRAP=1' };
|
|
85
85
|
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '0') return { run: false, reason: 'SKS_POSTINSTALL_BOOTSTRAP=0' };
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return { run: true, reason: '
|
|
86
|
+
const installRoot = path.resolve(root || process.cwd());
|
|
87
|
+
const candidate = await isProjectSetupCandidate(installRoot);
|
|
88
|
+
const target = candidate ? installRoot : globalSksRoot();
|
|
89
|
+
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '1') return { run: true, target, reason: 'forced by SKS_POSTINSTALL_BOOTSTRAP=1' };
|
|
90
|
+
if (candidate) return { run: true, target, reason: 'auto-running sks setup --bootstrap --install-scope global --force' };
|
|
91
|
+
return { run: true, target, reason: 'no project marker found; auto-running global SKS runtime bootstrap' };
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
async function runPostinstallBootstrap(root, bootstrap) {
|
|
93
95
|
const previousCwd = process.cwd();
|
|
94
|
-
|
|
96
|
+
const decision = await postinstallBootstrapDecision(root);
|
|
97
|
+
const target = path.resolve(decision.target || root || previousCwd);
|
|
98
|
+
await ensureDir(target);
|
|
99
|
+
process.chdir(target);
|
|
95
100
|
try {
|
|
96
101
|
await bootstrap(['--from-postinstall', '--install-scope', 'global', '--force']);
|
|
97
102
|
} finally {
|
|
@@ -254,6 +259,88 @@ export async function ensureCodexCliTool({ skip = false } = {}) {
|
|
|
254
259
|
};
|
|
255
260
|
}
|
|
256
261
|
|
|
262
|
+
export async function maybePromptCodexUpdateForLaunch(args = [], opts = {}) {
|
|
263
|
+
if (hasFlag(args, '--json') || hasFlag(args, '--skip-cli-tools') || hasFlag(args, '--skip-codex-update') || process.env.SKS_SKIP_CODEX_UPDATE === '1') return { status: 'skipped' };
|
|
264
|
+
const latest = await npmPackageVersion('@openai/codex');
|
|
265
|
+
const codex = await getCodexInfo().catch(() => ({}));
|
|
266
|
+
const current = codexCliVersionNumber(codex.version);
|
|
267
|
+
const command = 'npm i -g @openai/codex@latest';
|
|
268
|
+
const label = opts.label || 'tmux launch';
|
|
269
|
+
const missing = !codex.bin;
|
|
270
|
+
const updateAvailable = Boolean(latest.version && current && compareVersions(latest.version, current) > 0);
|
|
271
|
+
if (!missing && !updateAvailable) return { status: 'current', latest: latest.version || null, current, bin: codex.bin || null, error: latest.error || null };
|
|
272
|
+
const prompt = missing
|
|
273
|
+
? `Codex CLI missing. Install @openai/codex${latest.version ? ` ${latest.version}` : '@latest'} before ${label}? [Y/n] `
|
|
274
|
+
: `Codex CLI ${current} -> ${latest.version} update before ${label}? [Y/n] `;
|
|
275
|
+
if (shouldAutoApproveInstall(args)) return installCodexLatest(command, latest.version, current);
|
|
276
|
+
if (!canAskYesNo()) {
|
|
277
|
+
const reason = missing ? 'Codex CLI missing' : `Codex CLI update available: ${current} -> ${latest.version}`;
|
|
278
|
+
console.log(`${reason}. Run: ${command}`);
|
|
279
|
+
return { status: missing ? 'missing' : 'available', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
280
|
+
}
|
|
281
|
+
const answer = (await askPostinstallQuestion(prompt)).trim();
|
|
282
|
+
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
283
|
+
if (!yes) return { status: 'skipped_by_user', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
284
|
+
return installCodexLatest(command, latest.version, current);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function shouldAutoApproveInstall(args = [], env = process.env) {
|
|
288
|
+
return hasFlag(args, '--yes') || hasFlag(args, '-y') || isOpenClawRuntime(env);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function canAskYesNo() {
|
|
292
|
+
return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hasFlag(args = [], name) {
|
|
296
|
+
return args.includes(name);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isOpenClawRuntime(env = process.env) {
|
|
300
|
+
return ['SKS_OPENCLAW', 'OPENCLAW', 'OPENCLAW_AGENT', 'OPENCLAW_RUN_ID', 'OPENCLAW_SESSION_ID']
|
|
301
|
+
.some((key) => /^(1|true|yes|y)$/i.test(String(env[key] || '').trim()));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function installCodexLatest(command, latestVersion, previousVersion = null) {
|
|
305
|
+
const npm = await which('npm').catch(() => null);
|
|
306
|
+
if (!npm) return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: 'npm not found on PATH' };
|
|
307
|
+
const install = await runProcess(npm, ['i', '-g', '@openai/codex@latest'], { timeoutMs: 180000, maxOutputBytes: 128 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
308
|
+
if (install.code !== 0) return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: `${install.stderr || install.stdout || command + ' failed'}`.trim() };
|
|
309
|
+
const after = await getCodexInfo().catch(() => ({}));
|
|
310
|
+
const afterVersion = codexCliVersionNumber(after.version);
|
|
311
|
+
if (!after.bin) return { status: 'updated_not_reflected', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, command, error: 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.' };
|
|
312
|
+
if (latestVersion && afterVersion && compareVersions(afterVersion, latestVersion) < 0) {
|
|
313
|
+
return { status: 'updated_not_reflected', latest: latestVersion, previous: previousVersion || null, version: afterVersion, bin: after.bin, command, error: `npm completed, but PATH still resolves Codex CLI ${afterVersion}; expected ${latestVersion}.` };
|
|
314
|
+
}
|
|
315
|
+
console.log(`Codex CLI ready: ${previousVersion || 'missing'} -> ${after.version || after.bin}`);
|
|
316
|
+
return { status: previousVersion ? 'updated' : 'installed', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, raw_version: after.version || null, bin: after.bin || null, command };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function codexCliVersionNumber(versionText = '') {
|
|
320
|
+
const match = String(versionText || '').match(/(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
321
|
+
return match ? match[1] : null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function npmPackageVersion(name) {
|
|
325
|
+
const envName = `SKS_NPM_VIEW_${String(name || '').replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}_VERSION`;
|
|
326
|
+
if (process.env[envName]) return { version: process.env[envName] };
|
|
327
|
+
const npm = await which('npm').catch(() => null);
|
|
328
|
+
if (!npm) return { error: 'npm not found' };
|
|
329
|
+
const result = await runProcess(npm, ['view', name, 'version'], { timeoutMs: 5000, maxOutputBytes: 4096 });
|
|
330
|
+
if (result.code !== 0) return { error: `${result.stderr || result.stdout || 'npm view failed'}`.trim() };
|
|
331
|
+
return { version: result.stdout.trim().split(/\s+/).pop() };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function compareVersions(a, b) {
|
|
335
|
+
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
336
|
+
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
337
|
+
for (let i = 0; i < Math.max(pa.length, pb.length, 3); i++) {
|
|
338
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
339
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
340
|
+
}
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
257
344
|
async function isProjectSetupCandidate(root) {
|
|
258
345
|
const markers = ['package.json', '.git', 'AGENTS.md', '.codex', '.sneakoscope'];
|
|
259
346
|
for (const marker of markers) {
|
package/src/cli/main.mjs
CHANGED
|
@@ -52,15 +52,16 @@ import { classifyToolError, harnessGrowthReport } from '../core/evaluation.mjs';
|
|
|
52
52
|
import { runWorkflowPerfBench, validateWorkflowPerfReport } from '../core/perf-bench.mjs';
|
|
53
53
|
import { buildProofField, proofFieldFixture, validateProofFieldReport } from '../core/proof-field.mjs';
|
|
54
54
|
import { recordMistake, writeMistakeMemoryReport } from '../core/mistake-memory.mjs';
|
|
55
|
+
import { MISTAKE_RECALL_ARTIFACT, contractConsumesMistakeRecall } from '../core/mistake-recall.mjs';
|
|
55
56
|
import { buildPromptContext } from '../core/prompt-context-builder.mjs';
|
|
56
57
|
import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
|
|
57
58
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
58
59
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
59
60
|
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
60
|
-
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
61
|
+
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
61
62
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
62
63
|
import { context7Command } from './context7-command.mjs';
|
|
63
|
-
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, postinstall, postinstallBootstrapDecision } from './install-helpers.mjs';
|
|
64
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
64
65
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
65
66
|
import { openClawCommand } from './openclaw-command.mjs';
|
|
66
67
|
|
|
@@ -82,7 +83,7 @@ export async function main(args) {
|
|
|
82
83
|
if (isAutoReviewFlag(args[0])) return autoReviewCommand('start', args.slice(1));
|
|
83
84
|
const [cmd, sub, ...rest] = args;
|
|
84
85
|
const tail = sub === undefined ? [] : [sub, ...rest];
|
|
85
|
-
if (!cmd) return
|
|
86
|
+
if (!cmd) return defaultTmuxCommand();
|
|
86
87
|
if (cmd === '--help' || cmd === '-h') return help();
|
|
87
88
|
if (cmd === '--version' || cmd === '-v' || cmd === 'version') return version();
|
|
88
89
|
if (cmd === 'tmux') return !sub || String(sub).startsWith('--') ? tmuxCommand('check', tail) : tmuxCommand(sub, rest);
|
|
@@ -100,6 +101,26 @@ export async function main(args) {
|
|
|
100
101
|
process.exitCode = 1;
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
async function defaultTmuxCommand(args = []) {
|
|
105
|
+
const update = await maybePromptSksUpdateForLaunch(args, { label: 'default tmux launch' });
|
|
106
|
+
if (update.status === 'updated') {
|
|
107
|
+
console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (update.status === 'failed') {
|
|
111
|
+
console.error(`SKS update failed: ${update.error}`);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'default tmux launch' });
|
|
116
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
117
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
return launchTmuxUi(args, { conciseBlockers: true });
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
function help(args = []) {
|
|
104
125
|
const topic = args[0];
|
|
105
126
|
if (topic) return usage([topic]);
|
|
@@ -107,6 +128,7 @@ function help(args = []) {
|
|
|
107
128
|
Sneakoscope Codex
|
|
108
129
|
|
|
109
130
|
Usage:
|
|
131
|
+
sks
|
|
110
132
|
sks help [topic]
|
|
111
133
|
sks version
|
|
112
134
|
sks update-check [--json]
|
|
@@ -782,6 +804,11 @@ async function versioning(sub = 'status', args = []) {
|
|
|
782
804
|
console.log(`Bump: ${status.bump || 'patch'}`);
|
|
783
805
|
console.log(`Hook: ${status.hook_installed ? 'installed' : 'missing'}${status.hook_path ? ` ${status.hook_path}` : ''}`);
|
|
784
806
|
console.log(`Last seen: ${status.last_version || 'none'}`);
|
|
807
|
+
if (status.runtime_drift?.checked) {
|
|
808
|
+
const drift = status.runtime_drift;
|
|
809
|
+
console.log(`Runtime: ${drift.runtime_version || 'unknown'} (${drift.relation || 'unknown'})`);
|
|
810
|
+
if (!drift.ok) console.log(`Warning: source package is ${drift.package_version}, but bare sks resolves to ${drift.runtime_version}. Use node ./bin/sks.mjs in this repo or reinstall/update the global package before trusting runtime behavior.`);
|
|
811
|
+
}
|
|
785
812
|
if (!status.ok) console.log('Run: sks doctor --fix');
|
|
786
813
|
return;
|
|
787
814
|
}
|
|
@@ -873,6 +900,12 @@ async function tmuxCommand(sub = 'start', args = []) {
|
|
|
873
900
|
return;
|
|
874
901
|
}
|
|
875
902
|
if (['start', 'attach', 'connect', 'open'].includes(action)) {
|
|
903
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'tmux launch' });
|
|
904
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
905
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
906
|
+
process.exitCode = 1;
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
876
909
|
const result = await launchTmuxUi(args);
|
|
877
910
|
if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
|
|
878
911
|
return;
|
|
@@ -887,7 +920,7 @@ async function madHighCommand(args = []) {
|
|
|
887
920
|
const profile = await enableMadHighProfile();
|
|
888
921
|
return console.log(JSON.stringify(profile, null, 2));
|
|
889
922
|
}
|
|
890
|
-
const update = await
|
|
923
|
+
const update = await maybePromptSksUpdateForLaunch(args, { label: 'MAD launch' });
|
|
891
924
|
if (update.status === 'updated') {
|
|
892
925
|
console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks --mad`);
|
|
893
926
|
return;
|
|
@@ -897,6 +930,12 @@ async function madHighCommand(args = []) {
|
|
|
897
930
|
process.exitCode = 1;
|
|
898
931
|
return;
|
|
899
932
|
}
|
|
933
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' });
|
|
934
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
935
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
936
|
+
process.exitCode = 1;
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
900
939
|
const deps = await ensureMadLaunchDependencies(args);
|
|
901
940
|
if (!deps.ready) {
|
|
902
941
|
console.error('SKS MAD launch blocked by missing dependencies.');
|
|
@@ -915,18 +954,19 @@ async function madHighCommand(args = []) {
|
|
|
915
954
|
});
|
|
916
955
|
}
|
|
917
956
|
|
|
918
|
-
async function
|
|
957
|
+
async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
|
|
919
958
|
if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
|
|
920
959
|
const latest = await npmPackageVersion('sneakoscope');
|
|
921
960
|
const currentPackage = await effectivePackageVersion();
|
|
922
961
|
if (!latest.version || compareVersions(latest.version, currentPackage) <= 0) return { status: 'current', latest: latest.version || null, error: latest.error || null };
|
|
923
962
|
const command = 'npm i -g sneakoscope@latest';
|
|
924
|
-
if (
|
|
963
|
+
if (shouldAutoApproveInstall(args)) return installSksLatest(command, latest.version);
|
|
925
964
|
if (!canAskYesNo()) {
|
|
926
965
|
console.log(`SKS update available: ${currentPackage} -> ${latest.version}. Run: ${command}`);
|
|
927
966
|
return { status: 'available', latest: latest.version, command };
|
|
928
967
|
}
|
|
929
|
-
const
|
|
968
|
+
const label = opts.label || 'launch';
|
|
969
|
+
const answer = (await askPostinstallQuestion(`SKS ${currentPackage} -> ${latest.version} update before ${label}? [Y/n] `)).trim();
|
|
930
970
|
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
931
971
|
if (!yes) return { status: 'skipped_by_user', latest: latest.version, command };
|
|
932
972
|
return installSksLatest(command, latest.version);
|
|
@@ -1074,7 +1114,7 @@ async function installTmuxDependency(args = []) {
|
|
|
1074
1114
|
}
|
|
1075
1115
|
|
|
1076
1116
|
async function confirmInstall(question, args = []) {
|
|
1077
|
-
if (
|
|
1117
|
+
if (shouldAutoApproveInstall(args)) return true;
|
|
1078
1118
|
if (!canAskYesNo()) return false;
|
|
1079
1119
|
return /^(y|yes|예|네|응)$/i.test((await askPostinstallQuestion(`${question} [y/N] `)).trim());
|
|
1080
1120
|
}
|
|
@@ -1119,6 +1159,12 @@ async function autoReviewCommand(sub = 'status', args = []) {
|
|
|
1119
1159
|
const status = await enableAutoReview({ high });
|
|
1120
1160
|
if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
|
|
1121
1161
|
console.log(`SKS Auto-Review enabled: ${profile}`);
|
|
1162
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'auto-review tmux launch' });
|
|
1163
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
1164
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
1165
|
+
process.exitCode = 1;
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1122
1168
|
const sessionArg = readOption(cleanArgs, '--session', null);
|
|
1123
1169
|
const session = sessionArg || sanitizeTmuxSessionName(`${profile}-${defaultTmuxSessionName(process.cwd())}`);
|
|
1124
1170
|
return launchTmuxUi([...cleanArgs, '--session', session], { codexArgs: ['--profile', profile] });
|
|
@@ -1179,6 +1225,7 @@ Codex App prompt commands:
|
|
|
1179
1225
|
${formatDollarCommandsCompact(' ')}
|
|
1180
1226
|
|
|
1181
1227
|
Examples:
|
|
1228
|
+
sks
|
|
1182
1229
|
sks setup
|
|
1183
1230
|
sneakoscope setup
|
|
1184
1231
|
sks commands
|
|
@@ -1194,7 +1241,7 @@ function usage(args = []) {
|
|
|
1194
1241
|
bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
|
|
1195
1242
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
1196
1243
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew only after approval.'],
|
|
1197
|
-
tmux: ['tmux', '', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'tmux launch is
|
|
1244
|
+
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. 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.'],
|
|
1198
1245
|
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.'],
|
|
1199
1246
|
team: ['Team', '', ' sks team "task" executor:5 reviewer:2 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 runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1200
1247
|
'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'],
|
|
@@ -1621,6 +1668,12 @@ async function effectivePackageVersion() {
|
|
|
1621
1668
|
return highestVersion([PACKAGE_VERSION, pkg.version]);
|
|
1622
1669
|
}
|
|
1623
1670
|
|
|
1671
|
+
async function selftestRuntimeVersion() {
|
|
1672
|
+
const source = await safeReadText(path.join(packageRoot(), 'src', 'core', 'fsx.mjs'));
|
|
1673
|
+
const sourceVersion = source.match(/export const PACKAGE_VERSION = ['"]([^'"]+)['"];/)?.[1] || null;
|
|
1674
|
+
return sourceVersion || PACKAGE_VERSION;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1624
1677
|
function highestVersion(versions = []) {
|
|
1625
1678
|
return versions.filter(Boolean).reduce((best, candidate) => compareVersions(candidate, best) > 0 ? candidate : best, '0.0.0');
|
|
1626
1679
|
}
|
|
@@ -1844,6 +1897,16 @@ async function selftest() {
|
|
|
1844
1897
|
if (oldNoBootstrap === undefined) delete process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
1845
1898
|
else process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = oldNoBootstrap;
|
|
1846
1899
|
if (noBootstrapDecision.run || noBootstrapDecision.reason !== 'SKS_POSTINSTALL_NO_BOOTSTRAP=1') throw new Error('selftest failed: postinstall bootstrap opt-out decision');
|
|
1900
|
+
const postinstallNoMarkerTmp = tmpdir();
|
|
1901
|
+
const postinstallNoMarkerHome = path.join(postinstallNoMarkerTmp, 'home');
|
|
1902
|
+
const postinstallNoMarkerCwd = path.join(postinstallNoMarkerTmp, 'cwd');
|
|
1903
|
+
const postinstallNoMarkerGlobalRoot = path.join(postinstallNoMarkerTmp, 'global-root');
|
|
1904
|
+
await ensureDir(postinstallNoMarkerCwd);
|
|
1905
|
+
const postinstallNoMarker = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallNoMarkerCwd, env: { INIT_CWD: postinstallNoMarkerCwd, HOME: postinstallNoMarkerHome, SKS_GLOBAL_ROOT: postinstallNoMarkerGlobalRoot, 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 });
|
|
1906
|
+
if (postinstallNoMarker.code !== 0) throw new Error(`selftest failed: no-marker postinstall bootstrap exited ${postinstallNoMarker.code}: ${postinstallNoMarker.stderr}`);
|
|
1907
|
+
if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest failed: no-marker postinstall did not report global runtime bootstrap');
|
|
1908
|
+
if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest failed: no-marker postinstall did not bootstrap global runtime root');
|
|
1909
|
+
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest failed: no-marker postinstall polluted install cwd');
|
|
1847
1910
|
const bootstrapJsonTmp = tmpdir();
|
|
1848
1911
|
await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
|
|
1849
1912
|
const bootstrapJson = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'bootstrap', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home'), SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
@@ -1874,8 +1937,45 @@ async function selftest() {
|
|
|
1874
1937
|
if (!tmuxSyntax.ok || !tmuxSyntax.command.includes('tmux attach-session -t sks-mad-selftest')) throw new Error('selftest failed: MAD tmux attach plan is not stable by session name');
|
|
1875
1938
|
const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
|
|
1876
1939
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
|
|
1940
|
+
if (!shouldAutoAttachTmux(['--mad'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux launch does not auto-attach in an interactive terminal');
|
|
1941
|
+
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux json mode should not auto-attach');
|
|
1942
|
+
if (shouldAutoAttachTmux(['--mad', '--no-attach'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: MAD tmux --no-attach should remain print-only');
|
|
1943
|
+
if (shouldAutoAttachTmux(['--mad'], { SKS_TMUX_NO_AUTO_ATTACH: '1' }, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest failed: SKS_TMUX_NO_AUTO_ATTACH should disable tmux auto-attach');
|
|
1877
1944
|
if (!isTmuxShellSession({ TMUX: '/tmp/tmux-501/default,1,0' })) throw new Error('selftest failed: tmux shell session env was not detected');
|
|
1878
1945
|
if (tmuxStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing tmux was not labeled missing');
|
|
1946
|
+
const bareDefault = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1947
|
+
cwd: globalCwd,
|
|
1948
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, PATH: '' },
|
|
1949
|
+
timeoutMs: 15000,
|
|
1950
|
+
maxOutputBytes: 64 * 1024
|
|
1951
|
+
});
|
|
1952
|
+
if (bareDefault.code !== 1 || !String(bareDefault.stderr || '').includes('SKS tmux launch blocked') || String(bareDefault.stdout || '').includes('Usage:')) throw new Error('selftest failed: bare sks did not route to default tmux launch');
|
|
1953
|
+
const fakeCodexBin = path.join(tmp, 'fake-codex-bin');
|
|
1954
|
+
await ensureDir(fakeCodexBin);
|
|
1955
|
+
const fakeCodexPath = path.join(fakeCodexBin, 'codex');
|
|
1956
|
+
await writeTextAtomic(fakeCodexPath, '#!/bin/sh\necho "codex-cli 0.1.0"\n');
|
|
1957
|
+
await fsp.chmod(fakeCodexPath, 0o755);
|
|
1958
|
+
const codexUpdatePrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1959
|
+
cwd: globalCwd,
|
|
1960
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, SKS_NPM_VIEW__OPENAI_CODEX_VERSION: '99.0.0', PATH: fakeCodexBin },
|
|
1961
|
+
timeoutMs: 15000,
|
|
1962
|
+
maxOutputBytes: 64 * 1024
|
|
1963
|
+
});
|
|
1964
|
+
if (!String(codexUpdatePrompt.stdout || '').includes('Codex CLI update available: 0.1.0 -> 99.0.0') || String(codexUpdatePrompt.stdout || '').includes('Usage:')) throw new Error('selftest failed: bare sks did not recommend Codex CLI update before tmux launch');
|
|
1965
|
+
const openClawAutoBin = path.join(tmp, 'openclaw-auto-bin');
|
|
1966
|
+
await ensureDir(openClawAutoBin);
|
|
1967
|
+
const openClawCodexPath = path.join(openClawAutoBin, 'codex');
|
|
1968
|
+
await writeTextAtomic(openClawCodexPath, '#!/bin/sh\necho "codex-cli 0.1.0"\n');
|
|
1969
|
+
await writeTextAtomic(path.join(openClawAutoBin, 'npm'), '#!/bin/sh\nDIR="${0%/*}"\nif [ "$1" = "i" ]; then\n printf \'#!/bin/sh\\necho "codex-cli 99.0.0"\\n\' > "$DIR/codex"\n chmod +x "$DIR/codex"\n exit 0\nfi\necho "unexpected npm $*" >&2\nexit 1\n');
|
|
1970
|
+
await fsp.chmod(openClawCodexPath, 0o755);
|
|
1971
|
+
await fsp.chmod(path.join(openClawAutoBin, 'npm'), 0o755);
|
|
1972
|
+
const openClawAutoUpdate = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1973
|
+
cwd: globalCwd,
|
|
1974
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_OPENCLAW: '1', SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, SKS_NPM_VIEW__OPENAI_CODEX_VERSION: '99.0.0', PATH: openClawAutoBin },
|
|
1975
|
+
timeoutMs: 15000,
|
|
1976
|
+
maxOutputBytes: 64 * 1024
|
|
1977
|
+
});
|
|
1978
|
+
if (!String(openClawAutoUpdate.stdout || '').includes('Codex CLI ready: 0.1.0 -> codex-cli 99.0.0')) throw new Error('selftest failed: OpenClaw mode did not auto-approve Codex CLI update before tmux launch');
|
|
1879
1979
|
const guardBlocked = await checkHarnessModification(tmp, { tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
|
|
1880
1980
|
if (guardBlocked.action !== 'block') throw new Error('selftest failed: harness guard allowed skill tampering');
|
|
1881
1981
|
const setupBlocked = await checkHarnessModification(tmp, { command: 'sks setup --force' });
|
|
@@ -2018,6 +2118,9 @@ async function selftest() {
|
|
|
2018
2118
|
if (!promptPipelineSkillExists) throw new Error('selftest failed: prompt pipeline skill not installed');
|
|
2019
2119
|
const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
|
|
2020
2120
|
if (!promptPipelineText.includes('TriWiki context-tracking SSOT')) throw new Error('selftest failed: prompt pipeline missing TriWiki context-tracking SSOT');
|
|
2121
|
+
if (!promptPipelineText.includes('Codex App pipeline activation:') || !promptPipelineText.includes('sks hook user-prompt-submit') || !promptPipelineText.includes('hookSpecificOutput.additionalContext')) throw new Error('selftest failed: prompt pipeline missing Codex App pipeline activation fallback');
|
|
2122
|
+
const teamSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'team', 'SKILL.md'));
|
|
2123
|
+
if (!teamSkillText.includes('Codex App pipeline activation:') || !teamSkillText.includes('sks pipeline status') || !teamSkillText.includes('mission/pipeline artifacts')) throw new Error('selftest failed: Team skill missing pipeline activation fallback');
|
|
2021
2124
|
if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
|
|
2022
2125
|
if (!promptPipelineText.includes('single design decision authority') || !promptPipelineText.includes('imagegen') || !promptPipelineText.includes('getdesign-reference') || !promptPipelineText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !promptPipelineText.includes('not parallel authorities')) throw new Error('selftest failed: prompt pipeline missing design SSOT/source-input routing');
|
|
2023
2126
|
if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
|
|
@@ -2073,9 +2176,9 @@ async function selftest() {
|
|
|
2073
2176
|
const openClawSkillText = await safeReadText(path.join(openClawResult.target_dir, 'SKILL.md'));
|
|
2074
2177
|
const openClawManifestText = await safeReadText(path.join(openClawResult.target_dir, 'manifest.yaml'));
|
|
2075
2178
|
const openClawConfigText = await safeReadText(path.join(openClawResult.target_dir, 'openclaw-agent-config.example.yaml'));
|
|
2076
|
-
if (!openClawSkillText.includes('sks root') || !openClawSkillText.includes('$Team') || !openClawSkillText.includes('OpenClaw agent must have the built-in `shell` tool enabled')) throw new Error('selftest failed: OpenClaw skill missing SKS agent guidance');
|
|
2179
|
+
if (!openClawSkillText.includes('sks root') || !openClawSkillText.includes('$Team') || !openClawSkillText.includes('OpenClaw agent must have the built-in `shell` tool enabled') || !openClawSkillText.includes('SKS_OPENCLAW=1')) throw new Error('selftest failed: OpenClaw skill missing SKS agent guidance');
|
|
2077
2180
|
if (!openClawManifestText.includes('generated_by: sneakoscope') || !openClawManifestText.includes(`version: ${PACKAGE_VERSION}`)) throw new Error('selftest failed: OpenClaw manifest missing generated marker or version');
|
|
2078
|
-
if (!openClawConfigText.includes(`- ${OPENCLAW_SKILL_NAME}`) || !openClawConfigText.includes('- shell')) throw new Error('selftest failed: OpenClaw agent config example missing skill or
|
|
2181
|
+
if (!openClawConfigText.includes(`- ${OPENCLAW_SKILL_NAME}`) || !openClawConfigText.includes('- shell') || !openClawConfigText.includes('SKS_OPENCLAW')) throw new Error('selftest failed: OpenClaw agent config example missing skill, shell tool, or OpenClaw env');
|
|
2079
2182
|
const registryDollarCommands = DOLLAR_COMMANDS.map((c) => c.command);
|
|
2080
2183
|
const manifest = await readJson(path.join(tmp, '.sneakoscope', 'manifest.json'));
|
|
2081
2184
|
const policy = await readJson(path.join(tmp, '.sneakoscope', 'policy.json'));
|
|
@@ -2151,7 +2254,8 @@ async function selftest() {
|
|
|
2151
2254
|
if (String(hookUpdateCurrentContext).includes('Update SKS now') || String(hookUpdateCurrentContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook prompted for update even though installed SKS is current');
|
|
2152
2255
|
const hookUpdateCurrentState = await readJson(path.join(hookUpdateCurrentTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2153
2256
|
if (hookUpdateCurrentState.pending_offer) throw new Error('selftest failed: current installed SKS left a pending update offer');
|
|
2154
|
-
|
|
2257
|
+
const hookRuntimeExpected = await selftestRuntimeVersion();
|
|
2258
|
+
if (hookUpdateCurrentState.current !== '9.9.9' || hookUpdateCurrentState.runtime_current !== hookRuntimeExpected || hookUpdateCurrentState.installed_current !== '9.9.9') throw new Error(`selftest failed: hook did not record effective installed SKS version: ${JSON.stringify({ expected: { current: '9.9.9', runtime_current: hookRuntimeExpected, installed_current: '9.9.9', loaded_runtime_current: PACKAGE_VERSION }, actual: hookUpdateCurrentState })}`);
|
|
2155
2259
|
const hookUpdatePendingTmp = tmpdir();
|
|
2156
2260
|
await initProject(hookUpdatePendingTmp, {});
|
|
2157
2261
|
await writeJsonAtomic(path.join(hookUpdatePendingTmp, '.sneakoscope', 'state', 'update-check.json'), {
|
|
@@ -2927,12 +3031,27 @@ async function selftest() {
|
|
|
2927
3031
|
const coord = rgbaToWikiCoord({ r: 12, g: 34, b: 56, a: 255 });
|
|
2928
3032
|
if (coord.schema !== 'sks.wiki-coordinate.v1' || coord.xyzw.length !== 4) throw new Error('selftest failed: RGBA wiki coordinate conversion');
|
|
2929
3033
|
await writeTextAtomic(path.join(tmp, '.sneakoscope', 'memory', 'q2_facts', 'selftest.md'), '- claim: Selftest memory claim must be selected before lower-weight mission notes. | id: selftest-memory-priority | source: src/cli/main.mjs | risk: high | status: supported | evidence_count: 3 | required_weight: 1.0 | trust_score: 0.9\n');
|
|
3034
|
+
await writeTextAtomic(path.join(tmp, '.sneakoscope', 'memory', 'q2_facts', 'tail-repeat.md'), [
|
|
3035
|
+
...Array.from({ length: 60 }, (_, i) => `- claim: Low priority filler memory ${i}. | id: tail-filler-${i} | source: src/cli/main.mjs | risk: low | status: supported | evidence_count: 1 | required_weight: 0.1 | trust_score: 0.5`),
|
|
3036
|
+
'- claim: TriWiki repeated mistake recall must preserve recent high-weight tail lessons. | id: tail-repeat-mistake | source: src/core/mistake-recall.mjs | risk: high | status: supported | freshness: fresh | evidence_count: 4 | required_weight: 1.2 | trust_score: 0.95'
|
|
3037
|
+
].join('\n'));
|
|
2930
3038
|
await createMission(tmp, { mode: 'sks', prompt: '모호한 질문은 그만 물어봐야지;; triwiki로 예측해' });
|
|
2931
3039
|
await createMission(tmp, { mode: 'sks', prompt: 'triwiki에서 자주 요청하는 것들은 카운팅해서 더 우선 참고해줘' });
|
|
3040
|
+
const projectClaims = await projectWikiClaims(tmp);
|
|
3041
|
+
if (!projectClaims.some((claim) => claim.id === 'tail-repeat-mistake')) throw new Error('selftest failed: tail high-weight memory claim was dropped from TriWiki ingestion');
|
|
3042
|
+
const recallPrompt = 'triwiki 반복 실수 방지 개선 selftest';
|
|
3043
|
+
const recallMission = await createMission(tmp, { mode: 'team', prompt: recallPrompt });
|
|
3044
|
+
await writeJsonAtomic(path.join(recallMission.dir, 'required-answers.schema.json'), { prompt: recallPrompt, slots: [{ id: 'GOAL_PRECISE', required: true }, { id: 'ACCEPTANCE_CRITERIA', required: true, type: 'array' }] });
|
|
3045
|
+
await writeJsonAtomic(path.join(recallMission.dir, 'answers.json'), { GOAL_PRECISE: recallPrompt, ACCEPTANCE_CRITERIA: ['repeat mistake memory is consumed'] });
|
|
3046
|
+
const recallSeal = await sealContract(recallMission.dir, { id: recallMission.id, prompt: recallPrompt, mode: 'team' });
|
|
3047
|
+
if (!recallSeal.ok) throw new Error('selftest failed: mistake recall contract did not seal');
|
|
3048
|
+
const recallLedger = await readJson(path.join(recallMission.dir, MISTAKE_RECALL_ARTIFACT), null);
|
|
3049
|
+
if (!recallLedger?.required || !recallLedger.matches?.some((match) => match.id === 'tail-repeat-mistake')) throw new Error('selftest failed: mistake recall did not match tail TriWiki lesson');
|
|
3050
|
+
if (!contractConsumesMistakeRecall(recallSeal.contract, recallLedger).ok) throw new Error('selftest failed: mistake recall was not consumed by decision contract');
|
|
2932
3051
|
const wikiPack = contextCapsule({
|
|
2933
3052
|
mission: { id: 'selftest-wiki', coord: { rgba: { r: 48, g: 132, b: 212, a: 240 } } },
|
|
2934
3053
|
role: 'verifier',
|
|
2935
|
-
claims:
|
|
3054
|
+
claims: projectClaims,
|
|
2936
3055
|
q4: { mode: 'selftest' },
|
|
2937
3056
|
q3: ['sks', 'llm-wiki', 'wiki-coordinate'],
|
|
2938
3057
|
budget: { maxWikiAnchors: 48, includeTrustSummary: true }
|
|
@@ -2983,6 +3102,17 @@ async function selftest() {
|
|
|
2983
3102
|
const primingClaim = primingPack.claims?.find((claim) => claim.id === 'positive-recall-guard');
|
|
2984
3103
|
if (!primingClaim || /elephant|do\s+not/i.test(primingClaim.text || '') || primingClaim.text_policy !== 'positive_recall_negation_suppressed') throw new Error('selftest failed: TriWiki compact recall did not suppress negative priming text');
|
|
2985
3104
|
if (!primingPack.attention?.hydrate_first?.some((row) => row[0] === 'positive-recall-guard' && String(row[1]).includes('negative_priming'))) throw new Error('selftest failed: negative priming claim was not source-hydration gated');
|
|
3105
|
+
const voxelPromotionPack = contextCapsule({
|
|
3106
|
+
mission: { id: 'voxel-promotion-selftest', coord: { rgba: { r: 70, g: 100, b: 130, a: 255 } } },
|
|
3107
|
+
role: 'worker',
|
|
3108
|
+
claims: [
|
|
3109
|
+
{ id: 'voxel-priority-hydrate', text: 'TriWiki memory repeat prevention should hydrate source evidence when priority route layers are high.', authority: 'code', risk: 'low', status: 'supported', freshness: 'fresh', required_weight: 1.25, trust_score: 0.95, coord: { rgba: { r: 70, g: 100, b: 130, a: 255 } } }
|
|
3110
|
+
],
|
|
3111
|
+
q4: { mode: 'voxel-promotion-selftest' },
|
|
3112
|
+
q3: ['triwiki', 'memory'],
|
|
3113
|
+
budget: { maxClaims: 1, maxWikiAnchors: 1, maxAttentionUse: 1, maxAttentionHydrate: 1 }
|
|
3114
|
+
});
|
|
3115
|
+
if (!voxelPromotionPack.attention?.hydrate_first?.some((row) => row[0] === 'voxel-priority-hydrate' && String(row[1]).startsWith('voxel:priority_route'))) throw new Error('selftest failed: voxel priority route did not promote hydration');
|
|
2986
3116
|
const dryRunPack = await writeWikiContextPack(tmp, ['--max-anchors', '4'], { dryRun: true });
|
|
2987
3117
|
if (wikiVoxelRowCount(dryRunPack.pack.wiki) !== 4) throw new Error('selftest failed: dry-run wiki pack did not build voxel rows');
|
|
2988
3118
|
if (await exists(dryRunPack.file)) throw new Error('selftest failed: wiki refresh dry-run wrote context pack');
|