sneakoscope 3.1.8 → 3.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/args.js +17 -0
- package/dist/cli/command-registry.js +16 -13
- package/dist/cli/router.js +8 -5
- package/dist/commands/doctor.js +54 -4
- package/dist/core/codex-app/codex-skill-sync.js +37 -2
- package/dist/core/codex-native/core-skill-integrity.js +6 -1
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/codex-native/native-capability-postcheck.js +143 -15
- package/dist/core/codex-native/native-capability-repair-matrix.js +1 -1
- package/dist/core/codex-native/project-skill-dedupe.js +18 -3
- package/dist/core/codex-native/skill-registry-ledger.js +9 -2
- package/dist/core/commands/basic-cli.js +7 -2
- package/dist/core/commands/mad-sks-command.js +36 -13
- package/dist/core/commands/naruto-command.js +4 -1
- package/dist/core/commands/pipeline-command.js +3 -4
- package/dist/core/commands/qa-loop-command.js +36 -1
- package/dist/core/commands/research-command.js +61 -1
- package/dist/core/commands/team-command.js +63 -3
- package/dist/core/config/managed-config-merge.js +59 -10
- package/dist/core/config/secret-preservation.js +145 -37
- package/dist/core/decision-contract.js +28 -4
- package/dist/core/doctor/command-alias-cleanup.js +64 -0
- package/dist/core/feature-fixtures.js +2 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +31 -6
- package/dist/core/naruto/naruto-work-graph.js +4 -1
- package/dist/core/pipeline-internals/runtime-core.js +50 -4
- package/dist/core/pipeline-internals/runtime-gates.js +10 -1
- package/dist/core/proof/route-proof-gate.js +1 -1
- package/dist/core/qa-loop.js +227 -11
- package/dist/core/questions.js +239 -2
- package/dist/core/routes.js +3 -4
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-native-release-gate.js +13 -4
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -35,14 +35,15 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
|
|
|
35
35
|
|
|
36
36
|
## 🚀 Current Release
|
|
37
37
|
|
|
38
|
-
SKS **3.1.
|
|
38
|
+
SKS **3.1.10** is a release-ready hardening pass for release wiring parity, immutable core skills, duplicate skill prevention, native capability postchecks, and protected secret rollback.
|
|
39
39
|
|
|
40
|
-
What changed in 3.1.
|
|
40
|
+
What changed in 3.1.10:
|
|
41
41
|
|
|
42
42
|
- **Core SKS skills are content-addressed and immutable.** The eight built-in route skills now have a manifest and no-drift gates; setup/update/doctor may install missing managed copies or restore corrupted managed copies, but they do not overwrite user-authored collisions.
|
|
43
|
-
- **
|
|
44
|
-
-
|
|
45
|
-
-
|
|
43
|
+
- **Release wiring is self-checking.** `release:gate-script-parity`, `release:wiring-3110-blackbox`, and `sks:3110-all-feature-regression` prove required ids, package scripts, release gates, source scripts, and built dist targets stay aligned.
|
|
44
|
+
- **Duplicate skills are detected and repaired safely.** Canonical skill names collapse variants such as `Loop`, `loop`, and `loop/SKILL.md`; SKS-managed duplicates can be quarantined automatically, while user-authored duplicates require explicit confirmation and produce active-name proof.
|
|
45
|
+
- **`sks doctor --fix` reports native capability truthfully.** Image generation, image follow-up edit paths, Computer Use, Chrome/web review, app screenshots, app handoff, and image path exposure now run capability-specific postchecks; manual-only and fallback surfaces do not become `verified`.
|
|
46
|
+
- **Supabase keys survive setup/update/doctor.** Protected secret surfaces are fingerprinted before and after guarded operations; missing or changed values are restored from backup or hard-fail without writing raw values to reports.
|
|
46
47
|
|
|
47
48
|
SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
|
|
48
49
|
|
|
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
|
|
|
4
4
|
fn main() {
|
|
5
5
|
let mut args = std::env::args().skip(1);
|
|
6
6
|
match args.next().as_deref() {
|
|
7
|
-
Some("--version") => println!("sks-rs 3.1.
|
|
7
|
+
Some("--version") => println!("sks-rs 3.1.10"),
|
|
8
8
|
Some("compact-info") => {
|
|
9
9
|
let mut input = String::new();
|
|
10
10
|
let _ = io::stdin().read_to_string(&mut input);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "sks.dist-build-stamp.v1",
|
|
3
3
|
"package_name": "sneakoscope",
|
|
4
|
-
"package_version": "3.1.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_file_count":
|
|
7
|
-
"built_at_source_time":
|
|
4
|
+
"package_version": "3.1.10",
|
|
5
|
+
"source_digest": "57a7f5ee1f3ac797f46e3dfd59365b1e147e4637a8ddd18fde2cbee802fec0ac",
|
|
6
|
+
"source_file_count": 2609,
|
|
7
|
+
"built_at_source_time": 1781550268084
|
|
8
8
|
}
|
package/dist/bin/sks.js
CHANGED
package/dist/cli/args.js
CHANGED
|
@@ -28,6 +28,23 @@ export function positionalArgs(args = []) {
|
|
|
28
28
|
'--command',
|
|
29
29
|
'--project-ref',
|
|
30
30
|
'--agent',
|
|
31
|
+
'--agents',
|
|
32
|
+
'--target-active-slots',
|
|
33
|
+
'--work-items',
|
|
34
|
+
'--minimum-work-items',
|
|
35
|
+
'--max-queue-expansion',
|
|
36
|
+
'--concurrency',
|
|
37
|
+
'--backend',
|
|
38
|
+
'--route',
|
|
39
|
+
'--mission',
|
|
40
|
+
'--mission-id',
|
|
41
|
+
'--profile',
|
|
42
|
+
'--write-mode',
|
|
43
|
+
'--max-write-agents',
|
|
44
|
+
'--service-tier',
|
|
45
|
+
'--zellij-session-name',
|
|
46
|
+
'--worker-placement',
|
|
47
|
+
'--zellij-visible-pane-cap',
|
|
31
48
|
'--phase',
|
|
32
49
|
'--message',
|
|
33
50
|
'--role',
|
|
@@ -99,21 +99,16 @@ export const COMMANDS = {
|
|
|
99
99
|
'codex-app': entry('beta', 'Check Codex App readiness', 'dist/commands/codex-app.js', directCommand(() => import('../commands/codex-app.js'), 'dist/commands/codex-app.js')),
|
|
100
100
|
'codex-native': entry('beta', 'Inspect Codex Native broker and routing readiness', 'dist/commands/codex-native.js', directCommand(() => import('../commands/codex-native.js'), 'dist/commands/codex-native.js')),
|
|
101
101
|
'codex-lb': entry('beta', 'Inspect codex-lb status and circuit health', 'dist/commands/codex-lb.js', directCommand(() => import('../commands/codex-lb.js'), 'dist/commands/codex-lb.js')),
|
|
102
|
-
auth: entry('beta', 'Alias for codex-lb auth commands', 'dist/commands/codex-lb.js', directCommand(() => import('../commands/codex-lb.js'), 'dist/commands/codex-lb.js')),
|
|
103
102
|
hooks: entry('beta', 'Explain and inspect Codex hooks', 'dist/commands/hooks.js', directCommand(() => import('../commands/hooks.js'), 'dist/commands/hooks.js')),
|
|
104
103
|
tmux: entry('beta', 'Show removed-runtime migration notice', 'dist/commands/tmux.js', directCommand(() => import('../commands/tmux.js'), 'dist/commands/tmux.js')),
|
|
105
104
|
'zellij-lane': entry('beta', 'Render a Zellij lane frame for SKS sessions', 'dist/commands/zellij-lane.js', directCommand(() => import('../commands/zellij-lane.js'), 'dist/commands/zellij-lane.js')),
|
|
106
105
|
'zellij-slot-pane': entry('beta', 'Render a compact Zellij worker slot pane', 'dist/commands/zellij-slot-pane.js', directCommand(() => import('../commands/zellij-slot-pane.js'), 'dist/commands/zellij-slot-pane.js')),
|
|
107
106
|
'zellij-slot-column-anchor': entry('beta', 'Render the compact SLOTS anchor pane for first-slot-down Zellij stacks', 'dist/commands/zellij-slot-column-anchor.js', directCommand(() => import('../commands/zellij-slot-column-anchor.js'), 'dist/commands/zellij-slot-column-anchor.js')),
|
|
108
107
|
zellij: entry('beta', 'Inspect Zellij runtime status and explain repair (no auto-install)', 'dist/commands/zellij.js', directCommand(() => import('../commands/zellij.js'), 'dist/commands/zellij.js')),
|
|
109
|
-
mad: entry('beta', 'MAD-SKS Zellij permission launcher', 'dist/commands/mad-sks.js', directCommand(() => import('../commands/mad-sks.js'), 'dist/commands/mad-sks.js')),
|
|
110
108
|
'mad-sks': entry('beta', 'MAD-SKS scoped permission modifier', 'dist/commands/mad-sks.js', directCommand(() => import('../commands/mad-sks.js'), 'dist/commands/mad-sks.js')),
|
|
111
109
|
'mad-db': entry('beta', 'Create or inspect one-cycle Mad-DB break-glass capability tokens', 'dist/commands/mad-db.js', directCommand(() => import('../commands/mad-db.js'), 'dist/commands/mad-db.js')),
|
|
112
110
|
'auto-review': entry('beta', 'Manage auto-review profile', 'dist/commands/auto-review.js', directCommand(() => import('../commands/auto-review.js'), 'dist/commands/auto-review.js')),
|
|
113
|
-
autoreview: entry('beta', 'Alias for auto-review', 'dist/commands/auto-review.js', directCommand(() => import('../commands/auto-review.js'), 'dist/commands/auto-review.js')),
|
|
114
111
|
'dollar-commands': entry('stable', 'List Codex App dollar commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
|
|
115
|
-
dollars: entry('stable', 'Alias for dollar-commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
|
|
116
|
-
'$': entry('stable', 'Alias for dollar-commands', 'dist/core/commands/basic-cli.js', basicArgs('dollarCommandsCommand')),
|
|
117
112
|
'fast-mode': entry('stable', 'Toggle SKS Fast mode default for dollar-command routes', 'dist/core/commands/fast-mode-command.js', argsCommand(() => import('../core/commands/fast-mode-command.js'), 'fastModeCommand', 'dist/core/commands/fast-mode-command.js')),
|
|
118
113
|
commit: entry('stable', 'Create a simple git commit', 'dist/commands/commit.js', directCommand(() => import('../commands/commit.js'), 'dist/commands/commit.js')),
|
|
119
114
|
'commit-and-push': entry('stable', 'Create a simple git commit and push', 'dist/commands/commit-and-push.js', directCommand(() => import('../commands/commit-and-push.js'), 'dist/commands/commit-and-push.js')),
|
|
@@ -128,14 +123,9 @@ export const COMMANDS = {
|
|
|
128
123
|
autoresearch: entry('labs', 'Alias for research/autoresearch route', 'dist/core/commands/autoresearch-command.js', subcommand(() => import('../core/commands/autoresearch-command.js'), 'autoresearchCommand', 'dist/core/commands/autoresearch-command.js', 'status')),
|
|
129
124
|
ppt: entry('labs', 'Inspect/build PPT artifacts', 'dist/core/commands/ppt-command.js', commandArgsCommand(() => import('../core/commands/ppt-command.js'), 'pptCommand', 'dist/core/commands/ppt-command.js')),
|
|
130
125
|
'image-ux-review': entry('labs', 'Inspect image UX artifacts', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
|
|
131
|
-
'ux-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
|
|
132
|
-
'visual-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
|
|
133
|
-
'ui-ux-review': entry('labs', 'Alias for image UX review', 'dist/core/commands/image-ux-review-command.js', commandArgsCommand(() => import('../core/commands/image-ux-review-command.js'), 'imageUxReviewCommand', 'dist/core/commands/image-ux-review-command.js')),
|
|
134
126
|
'computer-use': entry('beta', 'Record native Mac/non-web Computer Use visual evidence', 'dist/core/commands/computer-use-command.js', commandArgsCommand(() => import('../core/commands/computer-use-command.js'), 'computerUseCommand', 'dist/core/commands/computer-use-command.js')),
|
|
135
|
-
cu: entry('beta', 'Alias for native Computer Use', 'dist/core/commands/computer-use-command.js', commandArgsCommand(() => import('../core/commands/computer-use-command.js'), 'computerUseCommand', 'dist/core/commands/computer-use-command.js')),
|
|
136
127
|
context7: entry('beta', 'Context7 checks and docs', 'dist/cli/context7-command.js', subcommand(() => import('./context7-command.js'), 'context7Command', 'dist/cli/context7-command.js', 'check')),
|
|
137
128
|
xai: entry('beta', 'Set up and check xAI/Grok search MCP integration', 'dist/cli/xai-command.js', subcommand(() => import('./xai-command.js'), 'xaiCommand', 'dist/cli/xai-command.js', 'check')),
|
|
138
|
-
grok: entry('beta', 'Alias for xAI/Grok search setup', 'dist/cli/xai-command.js', subcommand(() => import('./xai-command.js'), 'xaiCommand', 'dist/cli/xai-command.js', 'check')),
|
|
139
129
|
recallpulse: entry('labs', 'RecallPulse evidence route', 'dist/commands/recallpulse.js', directCommand(() => import('../commands/recallpulse.js'), 'dist/commands/recallpulse.js')),
|
|
140
130
|
pipeline: entry('beta', 'Inspect pipeline missions', 'dist/commands/pipeline.js', directCommand(() => import('../commands/pipeline.js'), 'dist/commands/pipeline.js')),
|
|
141
131
|
guard: entry('beta', 'Check harness guard', 'dist/commands/guard.js', directCommand(() => import('../commands/guard.js'), 'dist/commands/guard.js')),
|
|
@@ -156,7 +146,6 @@ export const COMMANDS = {
|
|
|
156
146
|
'skill-dream': entry('labs', 'Track skill dream counters', 'dist/core/commands/skill-dream-command.js', subcommand(() => import('../core/commands/skill-dream-command.js'), 'skillDreamCommand', 'dist/core/commands/skill-dream-command.js', 'status')),
|
|
157
147
|
'code-structure': entry('labs', 'Scan source structure', 'dist/core/commands/code-structure-command.js', subcommand(() => import('../core/commands/code-structure-command.js'), 'codeStructureCommand', 'dist/core/commands/code-structure-command.js', 'scan')),
|
|
158
148
|
rust: entry('beta', 'Inspect optional Rust accelerator status and smoke parity', 'dist/commands/rust.js', directCommand(() => import('../commands/rust.js'), 'dist/commands/rust.js')),
|
|
159
|
-
memory: entry('labs', 'Run retention checks', 'dist/core/commands/gc-command.js', subcommand(() => import('../core/commands/gc-command.js'), 'memoryCommand', 'dist/core/commands/gc-command.js')),
|
|
160
149
|
gx: entry('labs', 'Render/validate GX cartridges', 'dist/core/commands/gx-command.js', subcommand(() => import('../core/commands/gx-command.js'), 'gxCommand', 'dist/core/commands/gx-command.js', 'validate')),
|
|
161
150
|
db: entry('beta', 'Inspect DB safety policy', 'dist/core/commands/db-command.js', subcommand(() => import('../core/commands/db-command.js'), 'dbCommand', 'dist/core/commands/db-command.js', 'policy')),
|
|
162
151
|
eval: entry('labs', 'Run eval reports', 'dist/core/commands/eval-command.js', subcommand(() => import('../core/commands/eval-command.js'), 'evalCommand', 'dist/core/commands/eval-command.js', 'run')),
|
|
@@ -170,13 +159,27 @@ export const COMMANDS = {
|
|
|
170
159
|
bench: entry('beta', 'Run core trust-kernel benchmark budgets', 'dist/core/commands/bench-command.js', argsCommand(() => import('../core/commands/bench-command.js'), 'benchCommand', 'dist/core/commands/bench-command.js'))
|
|
171
160
|
};
|
|
172
161
|
export const TYPED_COMMANDS = COMMANDS;
|
|
162
|
+
export const LEGACY_COMMAND_ALIASES = {
|
|
163
|
+
auth: 'codex-lb',
|
|
164
|
+
mad: 'mad-sks',
|
|
165
|
+
autoreview: 'auto-review',
|
|
166
|
+
dollars: 'dollar-commands',
|
|
167
|
+
'$': 'dollar-commands',
|
|
168
|
+
'ux-review': 'image-ux-review',
|
|
169
|
+
'visual-review': 'image-ux-review',
|
|
170
|
+
'ui-ux-review': 'image-ux-review',
|
|
171
|
+
cu: 'computer-use',
|
|
172
|
+
grok: 'xai',
|
|
173
|
+
memory: 'gc'
|
|
174
|
+
};
|
|
173
175
|
export const COMMAND_ALIASES = {
|
|
176
|
+
...LEGACY_COMMAND_ALIASES,
|
|
174
177
|
'--help': 'help',
|
|
175
178
|
'-h': 'help',
|
|
176
179
|
'--version': 'version',
|
|
177
180
|
'-v': 'version',
|
|
178
|
-
'--mad': 'mad',
|
|
179
|
-
'--MAD': 'mad',
|
|
181
|
+
'--mad': 'mad-sks',
|
|
182
|
+
'--MAD': 'mad-sks',
|
|
180
183
|
'--mad-sks': 'mad-sks',
|
|
181
184
|
'--agent': 'agent',
|
|
182
185
|
'--naruto': 'naruto'
|
package/dist/cli/router.js
CHANGED
|
@@ -5,17 +5,20 @@ export function isCommandName(value) {
|
|
|
5
5
|
export function normalizeCommand(args = []) {
|
|
6
6
|
const cmd = args[0];
|
|
7
7
|
if (!cmd)
|
|
8
|
-
return { command: null, args: [...args] };
|
|
9
|
-
|
|
8
|
+
return { command: null, rawCommand: null, aliasTarget: null, args: [...args] };
|
|
9
|
+
const mapped = cmd in COMMAND_ALIASES ? COMMAND_ALIASES[cmd] : cmd;
|
|
10
10
|
const rest = args.slice(1);
|
|
11
|
+
const command = isCommandName(mapped) ? mapped : null;
|
|
11
12
|
return {
|
|
12
|
-
command
|
|
13
|
+
command,
|
|
14
|
+
rawCommand: cmd,
|
|
15
|
+
aliasTarget: command && mapped !== cmd ? command : null,
|
|
13
16
|
args: rest,
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
export async function dispatch(args) {
|
|
17
20
|
const argv = args ?? process.argv.slice(2);
|
|
18
|
-
const { command, args: rest } = normalizeCommand(argv);
|
|
21
|
+
const { command, rawCommand, args: rest } = normalizeCommand(argv);
|
|
19
22
|
if (!command) {
|
|
20
23
|
if (!argv.length) {
|
|
21
24
|
const mod = await import('../commands/doctor.js');
|
|
@@ -36,6 +39,6 @@ export async function dispatch(args) {
|
|
|
36
39
|
const mod = await entry.lazy();
|
|
37
40
|
if (typeof mod.run !== 'function')
|
|
38
41
|
throw new Error(`Command ${command} must export run(command, args)`);
|
|
39
|
-
return mod.run(command, rest);
|
|
42
|
+
return mod.run(rawCommand || command, rest);
|
|
40
43
|
}
|
|
41
44
|
//# sourceMappingURL=router.js.map
|
package/dist/commands/doctor.js
CHANGED
|
@@ -28,8 +28,16 @@ import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-
|
|
|
28
28
|
import { buildCodexNativeFeatureMatrix } from '../core/codex-native/codex-native-feature-broker.js';
|
|
29
29
|
import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
|
|
30
30
|
import { runDoctorNativeCapabilityRepair } from '../core/doctor/doctor-native-capability-repair.js';
|
|
31
|
+
import { runDoctorCommandAliasCleanup } from '../core/doctor/command-alias-cleanup.js';
|
|
32
|
+
import { withSecretPreservationGuard } from '../core/config/config-migration-journal.js';
|
|
31
33
|
export async function run(_command, args = []) {
|
|
34
|
+
const root = await projectRoot();
|
|
32
35
|
const doctorFix = flag(args, '--fix');
|
|
36
|
+
if (doctorFix)
|
|
37
|
+
return withSecretPreservationGuard(root, 'doctor-fix', () => runDoctor(args, root, doctorFix));
|
|
38
|
+
return runDoctor(args, root, doctorFix);
|
|
39
|
+
}
|
|
40
|
+
async function runDoctor(args = [], root, doctorFix) {
|
|
33
41
|
let setupRepair = null;
|
|
34
42
|
const sksUpdate = doctorFix
|
|
35
43
|
? {
|
|
@@ -69,7 +77,23 @@ export async function run(_command, args = []) {
|
|
|
69
77
|
: await ensureGlobalCodexFastModeDuringInstall().catch((err) => ({ status: 'failed', error: err?.message || String(err) }))
|
|
70
78
|
};
|
|
71
79
|
}
|
|
72
|
-
const
|
|
80
|
+
const commandAliasCleanup = await runDoctorCommandAliasCleanup({
|
|
81
|
+
root,
|
|
82
|
+
fix: doctorFix
|
|
83
|
+
}).catch((err) => ({
|
|
84
|
+
schema: 'sks.command-alias-cleanup.v1',
|
|
85
|
+
ok: false,
|
|
86
|
+
status: 'blocked',
|
|
87
|
+
root,
|
|
88
|
+
fix: doctorFix,
|
|
89
|
+
report_path: `${root}/.sneakoscope/reports/command-alias-cleanup.json`,
|
|
90
|
+
canonical_command_count: 0,
|
|
91
|
+
legacy_alias_count: 0,
|
|
92
|
+
aliases: [],
|
|
93
|
+
detected: { registered_alias_commands: [], catalog_alias_rows: [], missing_canonical_targets: [] },
|
|
94
|
+
actions: [],
|
|
95
|
+
blockers: [err?.message || String(err)]
|
|
96
|
+
}));
|
|
73
97
|
const doctorNativeCapabilityRepair = await runDoctorNativeCapabilityRepair({
|
|
74
98
|
root,
|
|
75
99
|
fix: doctorFix || flag(args, '--repair-native-capabilities'),
|
|
@@ -287,7 +311,7 @@ export async function run(_command, args = []) {
|
|
|
287
311
|
const runtimeReadiness = buildRuntimeReadiness(zellijReadiness, codexNativeFeatureMatrix);
|
|
288
312
|
const result = {
|
|
289
313
|
schema: 'sks.doctor-status.v1',
|
|
290
|
-
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false),
|
|
314
|
+
ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false) && commandAliasCleanup.ok !== false,
|
|
291
315
|
root,
|
|
292
316
|
node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
|
|
293
317
|
codex,
|
|
@@ -305,6 +329,7 @@ export async function run(_command, args = []) {
|
|
|
305
329
|
agent_role_config: agentRoleConfigRepair,
|
|
306
330
|
zellij_readiness: zellijReadiness,
|
|
307
331
|
codex_permission_profiles: permissionProfiles,
|
|
332
|
+
command_aliases: commandAliasCleanup,
|
|
308
333
|
imagegen: {
|
|
309
334
|
ok: imagegen.auth_readiness?.available_paths?.length > 0,
|
|
310
335
|
auth_readiness: imagegen.auth_readiness || null,
|
|
@@ -323,7 +348,7 @@ export async function run(_command, args = []) {
|
|
|
323
348
|
ready,
|
|
324
349
|
sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
|
|
325
350
|
package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
|
|
326
|
-
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair }
|
|
351
|
+
repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair, codex_native: codexNativeRepair, doctor_native_capability: doctorNativeCapabilityRepair, command_aliases: commandAliasCleanup }
|
|
327
352
|
};
|
|
328
353
|
if (flag(args, '--json')) {
|
|
329
354
|
printJson(result);
|
|
@@ -378,12 +403,24 @@ export async function run(_command, args = []) {
|
|
|
378
403
|
console.log(` app screenshot: ${nativeCapabilityStatus(nativeCapabilityRows, 'codex_app_screenshot', 'degraded')}`);
|
|
379
404
|
console.log(` app handoff: ${nativeCapabilityStatus(nativeCapabilityRows, 'app_handoff', 'unavailable')}`);
|
|
380
405
|
console.log(` image path exposure: ${nativeCapabilityStatus(nativeCapabilityRows, 'image_path_exposure', 'fallback')}`);
|
|
406
|
+
const nativeManualActions = uniqueNativeManualActions(nativeCapabilityRows);
|
|
407
|
+
if (nativeManualActions.length) {
|
|
408
|
+
console.log(' manual next actions:');
|
|
409
|
+
for (const action of nativeManualActions)
|
|
410
|
+
console.log(` - ${action}`);
|
|
411
|
+
}
|
|
381
412
|
console.log('SKS Skills:');
|
|
382
413
|
console.log(` core skills: ${doctorSkillStatus(doctorNativeCapabilityRepair?.core_skills)}`);
|
|
383
414
|
console.log(` duplicate project skills: ${doctorDedupeStatus(doctorNativeCapabilityRepair?.skill_dedupe)}`);
|
|
415
|
+
console.log('SKS Command Aliases:');
|
|
416
|
+
console.log(` status: ${commandAliasCleanup.status || (commandAliasCleanup.ok ? 'clean' : 'blocked')}`);
|
|
417
|
+
console.log(` canonical commands: ${commandAliasCleanup.canonical_command_count ?? 0}`);
|
|
418
|
+
console.log(` compatibility aliases: ${commandAliasCleanup.legacy_alias_count ?? 0}`);
|
|
419
|
+
if (commandAliasCleanup.report_path)
|
|
420
|
+
console.log(` report: ${commandAliasCleanup.report_path}`);
|
|
384
421
|
console.log('Secret preservation:');
|
|
385
422
|
console.log(` Supabase keys: ${doctorNativeCapabilityRepair?.ok === false && String((doctorNativeCapabilityRepair?.blockers || []).join(' ')).includes('secret_preservation_failed') ? 'blocked' : 'preserved'}`);
|
|
386
|
-
console.log(' secret values:
|
|
423
|
+
console.log(' raw secret values: never recorded');
|
|
387
424
|
console.log(` migration journal: ${doctorNativeCapabilityRepair?.secret_preservation_guard || '.sneakoscope/reports/secret-preservation-guard.json'}`);
|
|
388
425
|
console.log('Codex App Harness:');
|
|
389
426
|
console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
|
|
@@ -515,6 +552,13 @@ function nativeCapabilityStatus(rows, id, fallback) {
|
|
|
515
552
|
return fallback;
|
|
516
553
|
if (row.after === 'verified' || row.before === 'verified')
|
|
517
554
|
return 'verified';
|
|
555
|
+
if (id === 'image_path_exposure') {
|
|
556
|
+
if (row.before === 'degraded' || row.after === 'degraded' || row.repairability === 'doctor-fix')
|
|
557
|
+
return 'fallback';
|
|
558
|
+
return fallback;
|
|
559
|
+
}
|
|
560
|
+
if (id === 'app_handoff')
|
|
561
|
+
return 'unavailable';
|
|
518
562
|
if (row.repairability === 'manual-required')
|
|
519
563
|
return 'manual_required';
|
|
520
564
|
if (row.before === 'degraded' || row.after === 'degraded')
|
|
@@ -525,6 +569,12 @@ function nativeCapabilityStatus(rows, id, fallback) {
|
|
|
525
569
|
return 'unavailable';
|
|
526
570
|
return fallback;
|
|
527
571
|
}
|
|
572
|
+
function uniqueNativeManualActions(rows) {
|
|
573
|
+
return [...new Set(rows
|
|
574
|
+
.filter((row) => row?.repairability === 'manual-required' && row?.after !== 'verified')
|
|
575
|
+
.flatMap((row) => Array.isArray(row.repair_actions) ? row.repair_actions : [])
|
|
576
|
+
.filter((action) => typeof action === 'string' && action.trim()))];
|
|
577
|
+
}
|
|
528
578
|
function doctorSkillStatus(coreSkills) {
|
|
529
579
|
if (!coreSkills)
|
|
530
580
|
return 'drift_detected';
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { buildSksCoreSkillManifest } from '../codex-native/core-skill-manifest.js';
|
|
6
6
|
import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
|
|
7
7
|
import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
|
|
8
8
|
const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
9
|
+
const SKILL_SYNC_LOCK_STALE_AFTER_MS = 30000;
|
|
9
10
|
export async function syncCodexSksSkills(input) {
|
|
10
11
|
const root = path.resolve(input.root);
|
|
11
12
|
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
@@ -71,6 +72,8 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
71
72
|
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
72
73
|
if (code !== 'EEXIST')
|
|
73
74
|
throw err;
|
|
75
|
+
if (await recoverStaleSkillSyncLock(lockPath))
|
|
76
|
+
continue;
|
|
74
77
|
if (Date.now() - started > 30000)
|
|
75
78
|
throw new Error(`Timed out waiting for skill sync lock: ${lockPath}`);
|
|
76
79
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
@@ -80,7 +83,8 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
80
83
|
await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
|
|
81
84
|
schema: 'sks.skill-sync-lock.v1',
|
|
82
85
|
pid: process.pid,
|
|
83
|
-
acquired_at: nowIso()
|
|
86
|
+
acquired_at: nowIso(),
|
|
87
|
+
stale_after_ms: SKILL_SYNC_LOCK_STALE_AFTER_MS
|
|
84
88
|
}).catch(() => undefined);
|
|
85
89
|
return await fn();
|
|
86
90
|
}
|
|
@@ -88,6 +92,37 @@ export async function withSkillSyncLock(root, fn) {
|
|
|
88
92
|
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
95
|
+
async function recoverStaleSkillSyncLock(lockPath) {
|
|
96
|
+
const ownerPath = path.join(lockPath, 'owner.json');
|
|
97
|
+
const stat = await fs.stat(lockPath).catch(() => null);
|
|
98
|
+
const owner = await readJson(ownerPath, null).catch(() => null);
|
|
99
|
+
const staleAfterMs = Number(owner?.stale_after_ms || SKILL_SYNC_LOCK_STALE_AFTER_MS);
|
|
100
|
+
const acquiredAt = owner?.acquired_at ? Date.parse(owner.acquired_at) : NaN;
|
|
101
|
+
const ageMs = Number.isFinite(acquiredAt) ? Date.now() - acquiredAt : stat ? Date.now() - stat.mtimeMs : 0;
|
|
102
|
+
if (owner?.schema === 'sks.skill-sync-lock.v1' && Number.isFinite(owner.pid)) {
|
|
103
|
+
if (ageMs <= staleAfterMs || pidAlive(Number(owner.pid)))
|
|
104
|
+
return false;
|
|
105
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (stat && Date.now() - stat.mtimeMs > staleAfterMs) {
|
|
109
|
+
await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function pidAlive(pid) {
|
|
115
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
116
|
+
return false;
|
|
117
|
+
try {
|
|
118
|
+
process.kill(pid, 0);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
123
|
+
return code === 'EPERM';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
91
126
|
async function listSkillNames(root) {
|
|
92
127
|
const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
93
128
|
return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
-
import { buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
|
|
4
|
+
import { CORE_SKILL_TEMPLATE_VERSION, buildSksCoreSkillManifest, isSksManagedCoreSkillContent, renderCoreSkillTemplate } from './core-skill-manifest.js';
|
|
5
5
|
import { canonicalSkillName } from './skill-name-canonicalizer.js';
|
|
6
6
|
export async function syncCoreSkillsIntegrity(input) {
|
|
7
7
|
const root = path.resolve(input.root);
|
|
@@ -69,7 +69,12 @@ export async function syncCoreSkillsIntegrity(input) {
|
|
|
69
69
|
root,
|
|
70
70
|
apply,
|
|
71
71
|
skills_root: skillsRoot,
|
|
72
|
+
template_version: CORE_SKILL_TEMPLATE_VERSION,
|
|
72
73
|
manifest_sha256: sha256(JSON.stringify(manifest.skills.map((skill) => [skill.canonical_name, skill.content_sha256]))),
|
|
74
|
+
drift_detected_count: rows.filter((row) => row.action === 'restore-corrupted-managed-copy' || row.action === 'skip-user-authored').length,
|
|
75
|
+
restored_count: restored.length,
|
|
76
|
+
installed_count: installed.length,
|
|
77
|
+
user_collision_count: skippedUserAuthored.length,
|
|
73
78
|
rows,
|
|
74
79
|
installed,
|
|
75
80
|
restored,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PACKAGE_VERSION, nowIso, sha256 } from '../fsx.js';
|
|
2
2
|
import { canonicalSkillName } from './skill-name-canonicalizer.js';
|
|
3
|
-
export const CORE_SKILL_TEMPLATE_VERSION = '
|
|
3
|
+
export const CORE_SKILL_TEMPLATE_VERSION = 'sks-core-skill-template.v1';
|
|
4
4
|
export const CORE_SKILL_MANAGED_BEGIN = '<!-- BEGIN SKS IMMUTABLE CORE SKILL -->';
|
|
5
5
|
export const CORE_SKILL_MANAGED_END = '<!-- END SKS IMMUTABLE CORE SKILL -->';
|
|
6
6
|
const CORE_SKILL_DEFINITIONS = [
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
1
2
|
import path from 'node:path';
|
|
2
|
-
import { writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { ensureDir, readJson, writeJsonAtomic } from '../fsx.js';
|
|
3
4
|
import { buildNativeCapabilityRepairMatrix } from './native-capability-repair-matrix.js';
|
|
4
5
|
export async function postcheckNativeCapabilities(input) {
|
|
5
6
|
const root = path.resolve(input.root);
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return { ...state, after: 'unknown', blockers: ['computer_use_os_permission_or_capability_unknown'] };
|
|
11
|
-
}
|
|
12
|
-
if (state.id === 'chrome_web_review' && process.env.SKS_CHROME_EXTENSION_READY !== '1' && input.fixture !== 'all-repairable') {
|
|
13
|
-
return { ...state, after: 'unknown', blockers: ['codex_chrome_extension_readiness_not_verified'] };
|
|
14
|
-
}
|
|
15
|
-
if (state.blockers.length === 0 || verifiedAfterRepair)
|
|
16
|
-
return { ...state, after: state.repairability === 'manual-required' ? 'unknown' : 'verified', blockers: state.repairability === 'manual-required' ? state.blockers : [] };
|
|
17
|
-
return { ...state, after: 'blocked' };
|
|
18
|
-
});
|
|
19
|
-
const blockers = capabilities.flatMap((state) => state.after === 'verified' ? [] : state.blockers);
|
|
7
|
+
const fixture = input.fixture || false;
|
|
8
|
+
const matrix = input.matrix || await buildNativeCapabilityRepairMatrix({ root, fixture, reportPath: null });
|
|
9
|
+
const capabilities = await Promise.all(matrix.capabilities.map((state) => postcheckCapability(root, state, fixture)));
|
|
10
|
+
const blockers = capabilities.flatMap((state) => state.after === 'verified' || state.after === 'degraded' ? [] : state.blockers);
|
|
20
11
|
const checked = {
|
|
21
12
|
...matrix,
|
|
22
13
|
generated_at: new Date().toISOString(),
|
|
@@ -32,4 +23,141 @@ export async function postcheckNativeCapabilities(input) {
|
|
|
32
23
|
await writeJsonAtomic(reportPath, checked).catch(() => undefined);
|
|
33
24
|
return checked;
|
|
34
25
|
}
|
|
26
|
+
async function postcheckCapability(root, state, fixture) {
|
|
27
|
+
if (state.id === 'image_generation')
|
|
28
|
+
return postcheckImageGeneration(state, fixture);
|
|
29
|
+
if (state.id === 'image_followup_edit')
|
|
30
|
+
return postcheckImageFollowupEdit(root, state);
|
|
31
|
+
if (state.id === 'computer_use')
|
|
32
|
+
return postcheckComputerUse(state, fixture);
|
|
33
|
+
if (state.id === 'chrome_web_review')
|
|
34
|
+
return postcheckChromeWebReview(state, fixture);
|
|
35
|
+
if (state.id === 'codex_app_screenshot')
|
|
36
|
+
return postcheckAppScreenshot(root, state);
|
|
37
|
+
if (state.id === 'app_handoff')
|
|
38
|
+
return postcheckAppHandoff(state, fixture);
|
|
39
|
+
if (state.id === 'image_path_exposure')
|
|
40
|
+
return postcheckImagePathExposure(root, state, fixture);
|
|
41
|
+
if (state.id === 'saved_artifact_path_contract')
|
|
42
|
+
return postcheckSavedArtifactPathContract(root, state);
|
|
43
|
+
return { ...state, after: 'blocked', blockers: [...state.blockers, `unknown_capability:${state.id}`] };
|
|
44
|
+
}
|
|
45
|
+
function postcheckImageGeneration(state, fixture) {
|
|
46
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
47
|
+
return verified(state);
|
|
48
|
+
return {
|
|
49
|
+
...state,
|
|
50
|
+
after: 'unknown',
|
|
51
|
+
blockers: ['imagegen_auth_or_codex_app_builtin_missing'],
|
|
52
|
+
warnings: [...new Set([...state.warnings, 'image_generation_not_verified_without_real_capability'])]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function postcheckImageFollowupEdit(root, state) {
|
|
56
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
57
|
+
if (!contract.ok)
|
|
58
|
+
return { ...state, after: 'blocked', blockers: contract.blockers };
|
|
59
|
+
const sample = path.join(contract.imageArtifacts, 'postcheck-followup-sample.txt');
|
|
60
|
+
if (!(await writeReadSample(sample)))
|
|
61
|
+
return { ...state, after: 'blocked', blockers: ['image_followup_sample_artifact_unwritable'] };
|
|
62
|
+
return verified(state);
|
|
63
|
+
}
|
|
64
|
+
function postcheckComputerUse(state, _fixture) {
|
|
65
|
+
if (process.env.SKS_COMPUTER_USE_CAPABILITY === 'verified')
|
|
66
|
+
return verified(state);
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
after: 'unknown',
|
|
70
|
+
blockers: ['computer_use_os_permission_or_capability_unknown'],
|
|
71
|
+
warnings: [...new Set([...state.warnings, 'manual_os_permission_required'])]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function postcheckChromeWebReview(state, fixture) {
|
|
75
|
+
if (fixture === 'all-repairable' || process.env.SKS_CHROME_EXTENSION_READY === '1')
|
|
76
|
+
return verified(state);
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
after: 'unknown',
|
|
80
|
+
blockers: ['codex_chrome_extension_readiness_not_verified'],
|
|
81
|
+
warnings: [...new Set([...state.warnings, 'manual_chrome_extension_setup_required'])]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function postcheckAppScreenshot(root, state) {
|
|
85
|
+
const dir = path.join(root, '.sneakoscope', 'app-screenshots');
|
|
86
|
+
const registry = path.join(dir, 'screenshot-registry.json');
|
|
87
|
+
if (!(await writeReadSample(path.join(dir, 'postcheck-screenshot-sample.txt')))) {
|
|
88
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshot_directory_unwritable'] };
|
|
89
|
+
}
|
|
90
|
+
await writeJsonAtomic(registry, { schema: 'sks.app-screenshot-registry.v1', generated_at: new Date().toISOString(), screenshots: [] }).catch(() => undefined);
|
|
91
|
+
const json = await readJson(registry, {}).catch(() => ({}));
|
|
92
|
+
if (json.schema !== 'sks.app-screenshot-registry.v1')
|
|
93
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshot_registry_invalid'] };
|
|
94
|
+
return verified(state);
|
|
95
|
+
}
|
|
96
|
+
function postcheckAppHandoff(state, fixture) {
|
|
97
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
98
|
+
return verified(state);
|
|
99
|
+
return {
|
|
100
|
+
...state,
|
|
101
|
+
after: 'unknown',
|
|
102
|
+
blockers: ['codex_app_handoff_not_verified'],
|
|
103
|
+
warnings: [...new Set([...state.warnings, 'manual_app_handoff_approval_required'])]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function postcheckImagePathExposure(root, state, fixture) {
|
|
107
|
+
if (fixture === 'all-repairable' || state.before === 'verified')
|
|
108
|
+
return verified(state);
|
|
109
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
110
|
+
if (contract.ok) {
|
|
111
|
+
return {
|
|
112
|
+
...state,
|
|
113
|
+
after: 'degraded',
|
|
114
|
+
blockers: [],
|
|
115
|
+
warnings: [...new Set([...state.warnings, 'using_saved_artifact_path_contract_fallback'])]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return { ...state, after: 'blocked', blockers: ['image_path_exposure_missing_without_fallback_contract', ...contract.blockers] };
|
|
119
|
+
}
|
|
120
|
+
async function postcheckSavedArtifactPathContract(root, state) {
|
|
121
|
+
const contract = await validateSavedArtifactPathContract(root);
|
|
122
|
+
if (!contract.ok)
|
|
123
|
+
return { ...state, after: 'blocked', blockers: contract.blockers };
|
|
124
|
+
if (!(await writeReadSample(path.join(contract.imageArtifacts, 'postcheck-contract-image.txt'))))
|
|
125
|
+
return { ...state, after: 'blocked', blockers: ['image_artifacts_directory_unwritable'] };
|
|
126
|
+
if (!(await writeReadSample(path.join(contract.appScreenshots, 'postcheck-contract-screenshot.txt'))))
|
|
127
|
+
return { ...state, after: 'blocked', blockers: ['app_screenshots_directory_unwritable'] };
|
|
128
|
+
return verified(state);
|
|
129
|
+
}
|
|
130
|
+
function verified(state) {
|
|
131
|
+
return { ...state, after: 'verified', blockers: [] };
|
|
132
|
+
}
|
|
133
|
+
async function validateSavedArtifactPathContract(root) {
|
|
134
|
+
const contractPath = path.join(root, '.sneakoscope', 'reports', 'saved-artifact-path-contract.json');
|
|
135
|
+
const contract = await readJson(contractPath, null).catch(() => null);
|
|
136
|
+
const imageArtifacts = String(contract?.image_artifacts || path.join(root, '.sneakoscope', 'image-artifacts'));
|
|
137
|
+
const appScreenshots = String(contract?.app_screenshots || path.join(root, '.sneakoscope', 'app-screenshots'));
|
|
138
|
+
const blockers = [];
|
|
139
|
+
if (contract?.schema !== 'sks.saved-artifact-path-contract.v1')
|
|
140
|
+
blockers.push('saved_artifact_path_contract_schema_invalid');
|
|
141
|
+
for (const dir of [imageArtifacts, appScreenshots]) {
|
|
142
|
+
try {
|
|
143
|
+
await ensureDir(dir);
|
|
144
|
+
await fs.access(dir);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
blockers.push(`directory_unwritable:${path.basename(dir)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { ok: blockers.length === 0, imageArtifacts, appScreenshots, blockers };
|
|
151
|
+
}
|
|
152
|
+
async function writeReadSample(file) {
|
|
153
|
+
try {
|
|
154
|
+
await ensureDir(path.dirname(file));
|
|
155
|
+
await fs.writeFile(file, 'sks-native-capability-postcheck\n', 'utf8');
|
|
156
|
+
const text = await fs.readFile(file, 'utf8');
|
|
157
|
+
return text === 'sks-native-capability-postcheck\n';
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
35
163
|
//# sourceMappingURL=native-capability-postcheck.js.map
|
|
@@ -109,7 +109,7 @@ async function stateForCapability(root, id, imageCapability, nativeFeatureMatrix
|
|
|
109
109
|
warnings: envVerified ? [] : ['manual_os_permission_required']
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
-
const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1'
|
|
112
|
+
const chromeReady = process.env.SKS_CHROME_EXTENSION_READY === '1';
|
|
113
113
|
return {
|
|
114
114
|
id,
|
|
115
115
|
before: chromeReady ? 'verified' : 'unknown',
|