@amg555/frameforge-mcp 2.7.6 → 2.7.8
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/plugins/video-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/video-pipeline/skills/.upstream-skills.json +1 -1
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +1 -1
- package/scripts/deep-validate.js +2 -2
- package/src/mcp-server/server.js +5 -5
- package/src/skills/sync-upstream.js +3 -3
- package/.agent/skills/ai-video-editor/README.md +0 -32
- package/.agent/skills/ai-video-editor/SKILL.md +0 -152
- package/.agent/skills/ai-video-editor/assets/fonts/fonts.css +0 -2409
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/anton-400.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/archivo-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/cabin-sketch-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/dm-sans-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-600.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fraunces-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/fredoka-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/inter-tight-800.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/kalam-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/ttf/noto-sans-devanagari-700.ttf +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-400-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-500-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-600-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-700-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-800-normal.woff2 +0 -0
- package/.agent/skills/ai-video-editor/engine/hidpi.js +0 -117
- package/.agent/skills/ai-video-editor/engine/page.html +0 -43
- package/.agent/skills/ai-video-editor/engine/player.js +0 -145
- package/.agent/skills/ai-video-editor/references/example-story-warm-canvas.js +0 -95
- package/.agent/skills/ai-video-editor/references/story-guide.md +0 -112
- package/.agent/skills/ai-video-editor/requirements.txt +0 -4
- package/.agent/skills/ai-video-editor/styles/chalk-talk/base.js +0 -490
- package/.agent/skills/ai-video-editor/styles/chalk-talk/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/chalk-talk/style.md +0 -41
- package/.agent/skills/ai-video-editor/styles/clean-tech/base.js +0 -447
- package/.agent/skills/ai-video-editor/styles/clean-tech/style.json +0 -15
- package/.agent/skills/ai-video-editor/styles/clean-tech/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/base.js +0 -698
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.json +0 -16
- package/.agent/skills/ai-video-editor/styles/creator-motion-graphics/style.md +0 -39
- package/.agent/skills/ai-video-editor/styles/data-story/base.js +0 -432
- package/.agent/skills/ai-video-editor/styles/data-story/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/data-story/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/index.json +0 -103
- package/.agent/skills/ai-video-editor/styles/pop-info/base.js +0 -570
- package/.agent/skills/ai-video-editor/styles/pop-info/style.json +0 -13
- package/.agent/skills/ai-video-editor/styles/pop-info/style.md +0 -37
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/base.js +0 -544
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.json +0 -15
- package/.agent/skills/ai-video-editor/styles/software-walkthrough/style.md +0 -38
- package/.agent/skills/ai-video-editor/styles/warm-canvas/base.js +0 -610
- package/.agent/skills/ai-video-editor/styles/warm-canvas/style.json +0 -14
- package/.agent/skills/ai-video-editor/styles/warm-canvas/style.md +0 -37
- package/.agent/skills/faceless-shorts/README.md +0 -34
- package/.agent/skills/faceless-shorts/SKILL.md +0 -124
- package/.agent/skills/faceless-shorts/styles/deep-dive/STYLE.md +0 -58
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/cues.json +0 -140
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/lines.json +0 -67
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.json +0 -614
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/data/words.txt +0 -102
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/script.json +0 -27
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/sheet.html +0 -10
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/captions.js +0 -68
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/human.js +0 -128
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/look2d.js +0 -33
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sdf.js +0 -79
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/sets.js +0 -107
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/sheet.js +0 -79
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/src/shots.js +0 -136
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/deep-dive/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/inside-out/STYLE.md +0 -97
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/cues.json +0 -178
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/lines.json +0 -73
- package/.agent/skills/faceless-shorts/styles/inside-out/example/data/words.json +0 -662
- package/.agent/skills/faceless-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/inside-out/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/inside-out/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/captions.js +0 -68
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/kit.js +0 -345
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/inside-out/example/src/shots.js +0 -195
- package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/inside-out/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/STYLE.md +0 -36
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/cues.json +0 -146
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/lines.json +0 -73
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.json +0 -572
- package/.agent/skills/faceless-shorts/styles/night-file/example/data/words.txt +0 -95
- package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/night-file/example/index.html +0 -16
- package/.agent/skills/faceless-shorts/styles/night-file/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/night-file/example/sheet.html +0 -11
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/captions.js +0 -49
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/human.js +0 -128
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/kit.js +0 -151
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sdf.js +0 -79
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/sets3d.js +0 -106
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/sheet.js +0 -86
- package/.agent/skills/faceless-shorts/styles/night-file/example/src/shots.js +0 -130
- package/.agent/skills/faceless-shorts/styles/night-file/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/night-file/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/night-file/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/night-file/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/STYLE.md +0 -30
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/cues.json +0 -167
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/lines.json +0 -67
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.json +0 -614
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/data/words.txt +0 -102
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/index.html +0 -13
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/script.json +0 -26
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/sheet.html +0 -10
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/captions.js +0 -47
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/kit.js +0 -152
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/sheet.js +0 -61
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/src/shots.js +0 -139
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/paper-atlas/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/STYLE.md +0 -32
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/cues.json +0 -182
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/lines.json +0 -79
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.json +0 -662
- package/.agent/skills/faceless-shorts/styles/stick-story/example/data/words.txt +0 -110
- package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/stick-story/example/index.html +0 -12
- package/.agent/skills/faceless-shorts/styles/stick-story/example/script.json +0 -27
- package/.agent/skills/faceless-shorts/styles/stick-story/example/sheet.html +0 -9
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/captions.js +0 -47
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/kit.js +0 -166
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/main.js +0 -62
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/sheet.js +0 -64
- package/.agent/skills/faceless-shorts/styles/stick-story/example/src/shots.js +0 -148
- package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/stick-story/example/tools/mix.py +0 -172
- package/.agent/skills/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/STYLE.md +0 -51
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/cues.json +0 -271
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/lines.json +0 -127
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.json +0 -908
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/data/words.txt +0 -151
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/index.html +0 -17
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/script.json +0 -37
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/sheet.html +0 -11
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/board.js +0 -62
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/captions.js +0 -56
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/engine.js +0 -65
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/lib.js +0 -44
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/props.js +0 -166
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/timing.js +0 -23
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/toon.js +0 -352
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/core/world.js +0 -340
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/main.js +0 -56
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/sheet.js +0 -99
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/src/shots.js +0 -377
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/capture.mjs +0 -122
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/tools/mix.py +0 -204
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +0 -21
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +0 -144
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +0 -141
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +0 -231
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +0 -104
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +0 -97
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +0 -95
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +0 -99
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +0 -77
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +0 -415
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +0 -145
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +0 -45
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +0 -64
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +0 -85
- package/.agent/skills/faceless-shorts/styles/tall-tale/example/vendor/three.module.js +0 -6
- package/.agent/skills/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/.agent/skills/gstack-autoplan/SKILL.md +0 -972
- package/.agent/skills/gstack-autoplan/SKILL.md.tmpl +0 -468
- package/.agent/skills/gstack-autoplan/bin/guard-journal.ts +0 -115
- package/.agent/skills/gstack-autoplan/bin/guard-log.ts +0 -40
- package/.agent/skills/gstack-autoplan/bin/guard-reasons.ts +0 -92
- package/.agent/skills/gstack-autoplan/bin/owned-read.ts +0 -72
- package/.agent/skills/gstack-autoplan/bin/phase-publication-hook +0 -15
- package/.agent/skills/gstack-autoplan/bin/phase-publication-hook.ts +0 -769
- package/.agent/skills/gstack-autoplan/sections/ceo-phase.md +0 -195
- package/.agent/skills/gstack-autoplan/sections/ceo-phase.md.tmpl +0 -147
- package/.agent/skills/gstack-autoplan/sections/design-phase.md +0 -128
- package/.agent/skills/gstack-autoplan/sections/design-phase.md.tmpl +0 -82
- package/.agent/skills/gstack-autoplan/sections/dx-phase.md +0 -155
- package/.agent/skills/gstack-autoplan/sections/dx-phase.md.tmpl +0 -109
- package/.agent/skills/gstack-autoplan/sections/eng-phase.md +0 -164
- package/.agent/skills/gstack-autoplan/sections/eng-phase.md.tmpl +0 -116
- package/.agent/skills/gstack-autoplan/sections/manifest.json +0 -50
- package/.agent/skills/gstack-autoplan/sections/phase-close.md +0 -70
- package/.agent/skills/gstack-autoplan/sections/phase-close.md.tmpl +0 -68
- package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md +0 -71
- package/.agent/skills/gstack-autoplan/sections/tasks-aggregator.md.tmpl +0 -1
- package/.agent/skills/gstack-autoplan/sections/unattended.md +0 -11
- package/.agent/skills/gstack-autoplan/sections/unattended.md.tmpl +0 -9
- package/.agent/skills/gstack-benchmark/SKILL.md +0 -456
- package/.agent/skills/gstack-benchmark/SKILL.md.tmpl +0 -249
- package/.agent/skills/gstack-benchmark-models/SKILL.md +0 -285
- package/.agent/skills/gstack-benchmark-models/SKILL.md.tmpl +0 -155
- package/.agent/skills/gstack-browse/PLAN-snapshot-dropdown-interactive.md +0 -102
- package/.agent/skills/gstack-browse/SKILL.md +0 -470
- package/.agent/skills/gstack-browse/SKILL.md.tmpl +0 -129
- package/.agent/skills/gstack-browse/bin/find-browse +0 -21
- package/.agent/skills/gstack-browse/bin/remote-slug +0 -18
- package/.agent/skills/gstack-browse/sections/command-list.md +0 -176
- package/.agent/skills/gstack-browse/sections/command-list.md.tmpl +0 -7
- package/.agent/skills/gstack-browse/sections/manifest.json +0 -14
- package/.agent/skills/gstack-browse/src/activity.ts +0 -209
- package/.agent/skills/gstack-browse/src/audit.ts +0 -69
- package/.agent/skills/gstack-browse/src/browse-client.ts +0 -264
- package/.agent/skills/gstack-browse/src/browser-manager.ts +0 -2334
- package/.agent/skills/gstack-browse/src/browser-skill-commands.ts +0 -466
- package/.agent/skills/gstack-browse/src/browser-skill-write.ts +0 -216
- package/.agent/skills/gstack-browse/src/browser-skills.ts +0 -420
- package/.agent/skills/gstack-browse/src/buffers.ts +0 -137
- package/.agent/skills/gstack-browse/src/bun-child-config.ts +0 -35
- package/.agent/skills/gstack-browse/src/bun-polyfill.cjs +0 -276
- package/.agent/skills/gstack-browse/src/cdp-allowlist.ts +0 -235
- package/.agent/skills/gstack-browse/src/cdp-bridge.ts +0 -180
- package/.agent/skills/gstack-browse/src/cdp-commands.ts +0 -64
- package/.agent/skills/gstack-browse/src/cdp-inspector.ts +0 -824
- package/.agent/skills/gstack-browse/src/chromium-profiles.ts +0 -212
- package/.agent/skills/gstack-browse/src/claude-bin.ts +0 -2
- package/.agent/skills/gstack-browse/src/cli.ts +0 -2254
- package/.agent/skills/gstack-browse/src/commands.ts +0 -296
- package/.agent/skills/gstack-browse/src/config.ts +0 -307
- package/.agent/skills/gstack-browse/src/content-security.ts +0 -373
- package/.agent/skills/gstack-browse/src/cookie-auth-verification.ts +0 -225
- package/.agent/skills/gstack-browse/src/cookie-database.ts +0 -73
- package/.agent/skills/gstack-browse/src/cookie-import-browser.ts +0 -929
- package/.agent/skills/gstack-browse/src/cookie-import-native-integrity.ts +0 -95
- package/.agent/skills/gstack-browse/src/cookie-import-native-job.ts +0 -161
- package/.agent/skills/gstack-browse/src/cookie-import-native-qualification.json +0 -1
- package/.agent/skills/gstack-browse/src/cookie-import-native-worker.ts +0 -268
- package/.agent/skills/gstack-browse/src/cookie-import-native.ts +0 -166
- package/.agent/skills/gstack-browse/src/cookie-import-operation.ts +0 -232
- package/.agent/skills/gstack-browse/src/cookie-picker-routes.ts +0 -343
- package/.agent/skills/gstack-browse/src/cookie-picker-ui.ts +0 -717
- package/.agent/skills/gstack-browse/src/domain-skill-commands.ts +0 -301
- package/.agent/skills/gstack-browse/src/domain-skills.ts +0 -438
- package/.agent/skills/gstack-browse/src/error-handling.ts +0 -2
- package/.agent/skills/gstack-browse/src/extension-id.ts +0 -25
- package/.agent/skills/gstack-browse/src/file-permissions.ts +0 -370
- package/.agent/skills/gstack-browse/src/find-browse.ts +0 -118
- package/.agent/skills/gstack-browse/src/find-security-sidecar.ts +0 -78
- package/.agent/skills/gstack-browse/src/media-extract.ts +0 -177
- package/.agent/skills/gstack-browse/src/memory-command.ts +0 -115
- package/.agent/skills/gstack-browse/src/memory-snapshot.ts +0 -73
- package/.agent/skills/gstack-browse/src/meta-commands.ts +0 -1150
- package/.agent/skills/gstack-browse/src/network-capture.ts +0 -179
- package/.agent/skills/gstack-browse/src/path-security.ts +0 -132
- package/.agent/skills/gstack-browse/src/platform.ts +0 -38
- package/.agent/skills/gstack-browse/src/port-allocator.ts +0 -140
- package/.agent/skills/gstack-browse/src/project-slug.ts +0 -36
- package/.agent/skills/gstack-browse/src/proxy-config.ts +0 -283
- package/.agent/skills/gstack-browse/src/proxy-redact.ts +0 -46
- package/.agent/skills/gstack-browse/src/pty-session-cookie.ts +0 -64
- package/.agent/skills/gstack-browse/src/pty-session-lease.ts +0 -137
- package/.agent/skills/gstack-browse/src/read-commands.ts +0 -756
- package/.agent/skills/gstack-browse/src/routes/activity.ts +0 -63
- package/.agent/skills/gstack-browse/src/routes/commands.ts +0 -154
- package/.agent/skills/gstack-browse/src/routes/core.ts +0 -123
- package/.agent/skills/gstack-browse/src/routes/files.ts +0 -47
- package/.agent/skills/gstack-browse/src/routes/index.ts +0 -27
- package/.agent/skills/gstack-browse/src/routes/inspector.ts +0 -132
- package/.agent/skills/gstack-browse/src/routes/pairing.ts +0 -158
- package/.agent/skills/gstack-browse/src/routes/pty.ts +0 -254
- package/.agent/skills/gstack-browse/src/routes/table.ts +0 -182
- package/.agent/skills/gstack-browse/src/routes/tokens.ts +0 -86
- package/.agent/skills/gstack-browse/src/routes/tunnel.ts +0 -62
- package/.agent/skills/gstack-browse/src/sanitize.ts +0 -49
- package/.agent/skills/gstack-browse/src/screenshot-size-guard.ts +0 -106
- package/.agent/skills/gstack-browse/src/security-classifier.ts +0 -258
- package/.agent/skills/gstack-browse/src/security-sidecar-client.ts +0 -234
- package/.agent/skills/gstack-browse/src/security-sidecar-entry.ts +0 -120
- package/.agent/skills/gstack-browse/src/security.ts +0 -328
- package/.agent/skills/gstack-browse/src/server.ts +0 -2220
- package/.agent/skills/gstack-browse/src/session-cookie-store.ts +0 -124
- package/.agent/skills/gstack-browse/src/session-persist.ts +0 -178
- package/.agent/skills/gstack-browse/src/skill-token.ts +0 -91
- package/.agent/skills/gstack-browse/src/snapshot-flags.ts +0 -30
- package/.agent/skills/gstack-browse/src/snapshot.ts +0 -656
- package/.agent/skills/gstack-browse/src/socks-bridge.ts +0 -314
- package/.agent/skills/gstack-browse/src/sse-helpers.ts +0 -147
- package/.agent/skills/gstack-browse/src/sse-session-cookie.ts +0 -65
- package/.agent/skills/gstack-browse/src/stealth.ts +0 -574
- package/.agent/skills/gstack-browse/src/tab-session.ts +0 -210
- package/.agent/skills/gstack-browse/src/telemetry.ts +0 -115
- package/.agent/skills/gstack-browse/src/terminal-agent-control.ts +0 -379
- package/.agent/skills/gstack-browse/src/terminal-agent.ts +0 -1124
- package/.agent/skills/gstack-browse/src/terminal-pty-lifecycle.ts +0 -144
- package/.agent/skills/gstack-browse/src/token-registry.ts +0 -721
- package/.agent/skills/gstack-browse/src/tunnel-denial-log.ts +0 -97
- package/.agent/skills/gstack-browse/src/url-validation.ts +0 -299
- package/.agent/skills/gstack-browse/src/welcome.html +0 -237
- package/.agent/skills/gstack-browse/src/write-commands.ts +0 -1438
- package/.agent/skills/gstack-browse/src/xprotect-heal.ts +0 -439
- package/.agent/skills/gstack-browse/src/xvfb.ts +0 -313
- package/.agent/skills/gstack-browse/test/activity.test.ts +0 -120
- package/.agent/skills/gstack-browse/test/adversarial-security.test.ts +0 -32
- package/.agent/skills/gstack-browse/test/batch.test.ts +0 -267
- package/.agent/skills/gstack-browse/test/bridge-chromium-e2e.test.ts +0 -205
- package/.agent/skills/gstack-browse/test/browse-client.test.ts +0 -300
- package/.agent/skills/gstack-browse/test/browse-eval-wrapping.test.ts +0 -86
- package/.agent/skills/gstack-browse/test/browser-manager-custom-chromium.test.ts +0 -67
- package/.agent/skills/gstack-browse/test/browser-manager-unit.test.ts +0 -335
- package/.agent/skills/gstack-browse/test/browser-skill-commands.test.ts +0 -433
- package/.agent/skills/gstack-browse/test/browser-skill-write.test.ts +0 -350
- package/.agent/skills/gstack-browse/test/browser-skills-e2e.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/browser-skills-storage.test.ts +0 -283
- package/.agent/skills/gstack-browse/test/build-command-response.test.ts +0 -68
- package/.agent/skills/gstack-browse/test/build.test.ts +0 -52
- package/.agent/skills/gstack-browse/test/bun-child-config.test.ts +0 -83
- package/.agent/skills/gstack-browse/test/bun-polyfill.test.ts +0 -402
- package/.agent/skills/gstack-browse/test/busy-daemon-iron-rule.test.ts +0 -270
- package/.agent/skills/gstack-browse/test/busy-daemon-recovery.test.ts +0 -134
- package/.agent/skills/gstack-browse/test/cdp-allowlist.test.ts +0 -109
- package/.agent/skills/gstack-browse/test/cdp-e2e.test.ts +0 -117
- package/.agent/skills/gstack-browse/test/cdp-inspector-history-cap.test.ts +0 -95
- package/.agent/skills/gstack-browse/test/cdp-mutex.test.ts +0 -113
- package/.agent/skills/gstack-browse/test/cdp-session-cleanup.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/chromium-profile-isolation.test.ts +0 -418
- package/.agent/skills/gstack-browse/test/claude-bin.test.ts +0 -95
- package/.agent/skills/gstack-browse/test/cli-chain-stdin.test.ts +0 -122
- package/.agent/skills/gstack-browse/test/cli-config-mismatch-message.test.ts +0 -72
- package/.agent/skills/gstack-browse/test/cli-lock.test.ts +0 -120
- package/.agent/skills/gstack-browse/test/cli-loopback-no-proxy.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/cli-setsid-daemonize.test.ts +0 -75
- package/.agent/skills/gstack-browse/test/cli-start-final-healthcheck.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/cli-supervisor.test.ts +0 -194
- package/.agent/skills/gstack-browse/test/commands.test.ts +0 -2779
- package/.agent/skills/gstack-browse/test/compare-board.test.ts +0 -382
- package/.agent/skills/gstack-browse/test/config.test.ts +0 -519
- package/.agent/skills/gstack-browse/test/content-security.test.ts +0 -681
- package/.agent/skills/gstack-browse/test/cookie-auth-verification.test.ts +0 -760
- package/.agent/skills/gstack-browse/test/cookie-credential-deadline.test.ts +0 -225
- package/.agent/skills/gstack-browse/test/cookie-database.test.ts +0 -277
- package/.agent/skills/gstack-browse/test/cookie-fixture-delete-lease.test.ts +0 -256
- package/.agent/skills/gstack-browse/test/cookie-import-browser.test.ts +0 -523
- package/.agent/skills/gstack-browse/test/cookie-import-native-job.test.ts +0 -1645
- package/.agent/skills/gstack-browse/test/cookie-import-native-qualification.ts +0 -128
- package/.agent/skills/gstack-browse/test/cookie-import-native.test.ts +0 -436
- package/.agent/skills/gstack-browse/test/cookie-import-node.test.ts +0 -166
- package/.agent/skills/gstack-browse/test/cookie-import-operation.test.ts +0 -452
- package/.agent/skills/gstack-browse/test/cookie-import-reliability.test.ts +0 -659
- package/.agent/skills/gstack-browse/test/cookie-import-transport.test.ts +0 -38
- package/.agent/skills/gstack-browse/test/cookie-picker-binding-browser.test.ts +0 -96
- package/.agent/skills/gstack-browse/test/cookie-picker-binding.test.ts +0 -155
- package/.agent/skills/gstack-browse/test/cookie-picker-csrf-browser.test.ts +0 -50
- package/.agent/skills/gstack-browse/test/cookie-picker-routes.test.ts +0 -460
- package/.agent/skills/gstack-browse/test/cookie-picker-ui.test.ts +0 -535
- package/.agent/skills/gstack-browse/test/daemon-log-hygiene.test.ts +0 -135
- package/.agent/skills/gstack-browse/test/daemon-mismatch-refuse.test.ts +0 -178
- package/.agent/skills/gstack-browse/test/data-platform.test.ts +0 -176
- package/.agent/skills/gstack-browse/test/dia-gui-readiness.test.ts +0 -174
- package/.agent/skills/gstack-browse/test/dia-launch-comparison.test.ts +0 -180
- package/.agent/skills/gstack-browse/test/dia-macos-qualification.test.ts +0 -1695
- package/.agent/skills/gstack-browse/test/domain-skills-e2e.test.ts +0 -142
- package/.agent/skills/gstack-browse/test/domain-skills-storage.test.ts +0 -263
- package/.agent/skills/gstack-browse/test/dotenv-isolation.test.ts +0 -121
- package/.agent/skills/gstack-browse/test/dual-listener.test.ts +0 -411
- package/.agent/skills/gstack-browse/test/dx-polish.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/error-handling.test.ts +0 -47
- package/.agent/skills/gstack-browse/test/extension-sender-auth.test.ts +0 -285
- package/.agent/skills/gstack-browse/test/extension-token.test.ts +0 -232
- package/.agent/skills/gstack-browse/test/file-drop.test.ts +0 -271
- package/.agent/skills/gstack-browse/test/file-permissions.test.ts +0 -346
- package/.agent/skills/gstack-browse/test/fill-change-event.test.ts +0 -78
- package/.agent/skills/gstack-browse/test/find-browse.test.ts +0 -61
- package/.agent/skills/gstack-browse/test/findport.test.ts +0 -232
- package/.agent/skills/gstack-browse/test/fixtures/basic.html +0 -33
- package/.agent/skills/gstack-browse/test/fixtures/change-only-validator.html +0 -31
- package/.agent/skills/gstack-browse/test/fixtures/cursor-interactive.html +0 -22
- package/.agent/skills/gstack-browse/test/fixtures/dialog.html +0 -15
- package/.agent/skills/gstack-browse/test/fixtures/dropdown.html +0 -61
- package/.agent/skills/gstack-browse/test/fixtures/empty.html +0 -2
- package/.agent/skills/gstack-browse/test/fixtures/forms.html +0 -55
- package/.agent/skills/gstack-browse/test/fixtures/iframe.html +0 -30
- package/.agent/skills/gstack-browse/test/fixtures/injection-combined.html +0 -58
- package/.agent/skills/gstack-browse/test/fixtures/injection-hidden.html +0 -62
- package/.agent/skills/gstack-browse/test/fixtures/injection-social.html +0 -25
- package/.agent/skills/gstack-browse/test/fixtures/injection-visible.html +0 -26
- package/.agent/skills/gstack-browse/test/fixtures/media-page.html +0 -67
- package/.agent/skills/gstack-browse/test/fixtures/mock-claude/claude +0 -185
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-delete-lease.ts +0 -125
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-file-owners.ts +0 -132
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-launch.cjs +0 -139
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process-observer.ts +0 -150
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-process.cjs +0 -13
- package/.agent/skills/gstack-browse/test/fixtures/native-cookie-remove-fixture.cjs +0 -42
- package/.agent/skills/gstack-browse/test/fixtures/network-idle.html +0 -30
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval-checkout.html +0 -108
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval-spa.html +0 -98
- package/.agent/skills/gstack-browse/test/fixtures/qa-eval.html +0 -51
- package/.agent/skills/gstack-browse/test/fixtures/qa-only.html +0 -13
- package/.agent/skills/gstack-browse/test/fixtures/responsive.html +0 -49
- package/.agent/skills/gstack-browse/test/fixtures/route-dispatch-allowlist.json +0 -22
- package/.agent/skills/gstack-browse/test/fixtures/snapshot-ambiguous.html +0 -24
- package/.agent/skills/gstack-browse/test/fixtures/snapshot.html +0 -55
- package/.agent/skills/gstack-browse/test/fixtures/spa.html +0 -24
- package/.agent/skills/gstack-browse/test/fixtures/states.html +0 -17
- package/.agent/skills/gstack-browse/test/fixtures/upload-path-validation.ts +0 -91
- package/.agent/skills/gstack-browse/test/fixtures/upload.html +0 -25
- package/.agent/skills/gstack-browse/test/from-file-path-validation.test.ts +0 -68
- package/.agent/skills/gstack-browse/test/gstack-config.test.ts +0 -252
- package/.agent/skills/gstack-browse/test/gstack-update-check.test.ts +0 -614
- package/.agent/skills/gstack-browse/test/handoff.test.ts +0 -508
- package/.agent/skills/gstack-browse/test/headless-gpu-and-reap.test.ts +0 -286
- package/.agent/skills/gstack-browse/test/headless-launch-options.test.ts +0 -93
- package/.agent/skills/gstack-browse/test/launch-signal-flags.test.ts +0 -75
- package/.agent/skills/gstack-browse/test/learnings-injection.test.ts +0 -33
- package/.agent/skills/gstack-browse/test/media-extract-unit.test.ts +0 -294
- package/.agent/skills/gstack-browse/test/memory-command.test.ts +0 -219
- package/.agent/skills/gstack-browse/test/memory-leak-reproducer.test.ts +0 -133
- package/.agent/skills/gstack-browse/test/navigation-guard.test.ts +0 -106
- package/.agent/skills/gstack-browse/test/output-path-symlinks.test.ts +0 -76
- package/.agent/skills/gstack-browse/test/pair-agent-e2e.test.ts +0 -793
- package/.agent/skills/gstack-browse/test/pair-agent-optin-gate.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/pair-agent-tunnel-eval.test.ts +0 -501
- package/.agent/skills/gstack-browse/test/path-security-realpath.test.ts +0 -89
- package/.agent/skills/gstack-browse/test/path-validation.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/pdf-flags.test.ts +0 -86
- package/.agent/skills/gstack-browse/test/platform.test.ts +0 -37
- package/.agent/skills/gstack-browse/test/playwright-core-patch.test.ts +0 -62
- package/.agent/skills/gstack-browse/test/poisoned-bundle-probe.test.ts +0 -148
- package/.agent/skills/gstack-browse/test/process-liveness-windows.test.ts +0 -176
- package/.agent/skills/gstack-browse/test/proxy-config.test.ts +0 -189
- package/.agent/skills/gstack-browse/test/proxy-env-daemon.test.ts +0 -137
- package/.agent/skills/gstack-browse/test/proxy-env.test.ts +0 -189
- package/.agent/skills/gstack-browse/test/proxy-redact.test.ts +0 -64
- package/.agent/skills/gstack-browse/test/pty-inject-scan.test.ts +0 -153
- package/.agent/skills/gstack-browse/test/pty-session-lease.test.ts +0 -98
- package/.agent/skills/gstack-browse/test/rebrand-signed-bundle.test.ts +0 -53
- package/.agent/skills/gstack-browse/test/regression-pr1169-pdf-from-file-invalid-json.test.ts +0 -84
- package/.agent/skills/gstack-browse/test/restart-env.test.ts +0 -39
- package/.agent/skills/gstack-browse/test/route-test-harness.ts +0 -121
- package/.agent/skills/gstack-browse/test/runtime-root-server-bundle.test.ts +0 -100
- package/.agent/skills/gstack-browse/test/sanitize.test.ts +0 -112
- package/.agent/skills/gstack-browse/test/screenshot-size-guard.test.ts +0 -118
- package/.agent/skills/gstack-browse/test/security-adversarial-fixes.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/security-adversarial.test.ts +0 -342
- package/.agent/skills/gstack-browse/test/security-audit-r2.test.ts +0 -432
- package/.agent/skills/gstack-browse/test/security-bench.test.ts +0 -159
- package/.agent/skills/gstack-browse/test/security-classifier-download-cleanup.test.ts +0 -138
- package/.agent/skills/gstack-browse/test/security-classifier.test.ts +0 -29
- package/.agent/skills/gstack-browse/test/security-integration.test.ts +0 -184
- package/.agent/skills/gstack-browse/test/security-live-playwright.test.ts +0 -195
- package/.agent/skills/gstack-browse/test/security-sidecar-client.test.ts +0 -66
- package/.agent/skills/gstack-browse/test/security.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/server-auth.test.ts +0 -600
- package/.agent/skills/gstack-browse/test/server-embedder-terminal-port.test.ts +0 -301
- package/.agent/skills/gstack-browse/test/server-factory.test.ts +0 -640
- package/.agent/skills/gstack-browse/test/server-flush-trackers.test.ts +0 -73
- package/.agent/skills/gstack-browse/test/server-launch-resolution.test.ts +0 -49
- package/.agent/skills/gstack-browse/test/server-lock-errors.test.ts +0 -124
- package/.agent/skills/gstack-browse/test/server-no-import-side-effects.test.ts +0 -110
- package/.agent/skills/gstack-browse/test/server-proxy-fail-fast.test.ts +0 -98
- package/.agent/skills/gstack-browse/test/server-pty-lease-routes.test.ts +0 -156
- package/.agent/skills/gstack-browse/test/server-route-auth-blackbox.test.ts +0 -270
- package/.agent/skills/gstack-browse/test/server-route-dispatch-ratchet.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/server-route-table.test.ts +0 -238
- package/.agent/skills/gstack-browse/test/server-sanitize-surrogates.test.ts +0 -182
- package/.agent/skills/gstack-browse/test/server-tmp-state-path.test.ts +0 -110
- package/.agent/skills/gstack-browse/test/session-cookie-store.test.ts +0 -159
- package/.agent/skills/gstack-browse/test/session-persist.test.ts +0 -287
- package/.agent/skills/gstack-browse/test/sidebar-tabs.test.ts +0 -268
- package/.agent/skills/gstack-browse/test/sidebar-ux.test.ts +0 -1006
- package/.agent/skills/gstack-browse/test/sidepanel-font-size.test.ts +0 -147
- package/.agent/skills/gstack-browse/test/sidepanel-patient-autoconnect.test.ts +0 -70
- package/.agent/skills/gstack-browse/test/sidepanel-reattach.test.ts +0 -93
- package/.agent/skills/gstack-browse/test/sidepanel-restart-dispose.test.ts +0 -106
- package/.agent/skills/gstack-browse/test/skill-token.test.ts +0 -167
- package/.agent/skills/gstack-browse/test/snapshot.test.ts +0 -593
- package/.agent/skills/gstack-browse/test/socks-bridge.test.ts +0 -461
- package/.agent/skills/gstack-browse/test/sse-helpers.test.ts +0 -194
- package/.agent/skills/gstack-browse/test/sse-session-cookie.test.ts +0 -155
- package/.agent/skills/gstack-browse/test/state-ttl.test.ts +0 -35
- package/.agent/skills/gstack-browse/test/stealth-extended.test.ts +0 -127
- package/.agent/skills/gstack-browse/test/stealth-layer-c.test.ts +0 -284
- package/.agent/skills/gstack-browse/test/stealth-webdriver.test.ts +0 -394
- package/.agent/skills/gstack-browse/test/stop-ack-before-shutdown.test.ts +0 -23
- package/.agent/skills/gstack-browse/test/stop-dead-daemon.test.ts +0 -153
- package/.agent/skills/gstack-browse/test/tab-each.test.ts +0 -196
- package/.agent/skills/gstack-browse/test/tab-guardrail.test.ts +0 -118
- package/.agent/skills/gstack-browse/test/tab-isolation.test.ts +0 -309
- package/.agent/skills/gstack-browse/test/tab-session-frame-detach.test.ts +0 -154
- package/.agent/skills/gstack-browse/test/telemetry-optout.test.ts +0 -171
- package/.agent/skills/gstack-browse/test/telemetry.test.ts +0 -77
- package/.agent/skills/gstack-browse/test/temp-dirs.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/terminal-agent-detach-reattach.test.ts +0 -84
- package/.agent/skills/gstack-browse/test/terminal-agent-import.test.ts +0 -42
- package/.agent/skills/gstack-browse/test/terminal-agent-integration.test.ts +0 -565
- package/.agent/skills/gstack-browse/test/terminal-agent-keepalive.test.ts +0 -88
- package/.agent/skills/gstack-browse/test/terminal-agent-lifecycle.test.ts +0 -371
- package/.agent/skills/gstack-browse/test/terminal-agent-native-observation.test.ts +0 -66
- package/.agent/skills/gstack-browse/test/terminal-agent-owner-watchdog.test.ts +0 -81
- package/.agent/skills/gstack-browse/test/terminal-agent-pid-identity.test.ts +0 -161
- package/.agent/skills/gstack-browse/test/terminal-agent-port-range.test.ts +0 -80
- package/.agent/skills/gstack-browse/test/terminal-agent-publication-lock.test.ts +0 -289
- package/.agent/skills/gstack-browse/test/terminal-agent-ring-buffer-runtime.test.ts +0 -157
- package/.agent/skills/gstack-browse/test/terminal-agent-session-routing.test.ts +0 -96
- package/.agent/skills/gstack-browse/test/terminal-agent-watchdog.test.ts +0 -101
- package/.agent/skills/gstack-browse/test/terminal-agent.test.ts +0 -259
- package/.agent/skills/gstack-browse/test/terminal-pty-lifecycle.test.ts +0 -212
- package/.agent/skills/gstack-browse/test/test-server.ts +0 -57
- package/.agent/skills/gstack-browse/test/token-registry.test.ts +0 -615
- package/.agent/skills/gstack-browse/test/tunnel-gate-unit.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/tunnel-revoke-cli.test.ts +0 -455
- package/.agent/skills/gstack-browse/test/url-validation.test.ts +0 -384
- package/.agent/skills/gstack-browse/test/watch.test.ts +0 -129
- package/.agent/skills/gstack-browse/test/watchdog.test.ts +0 -352
- package/.agent/skills/gstack-browse/test/welcome-page.test.ts +0 -143
- package/.agent/skills/gstack-browse/test/windows-spawn-hide.test.ts +0 -167
- package/.agent/skills/gstack-browse/test/xprotect-heal.test.ts +0 -457
- package/.agent/skills/gstack-browse/test/xvfb.test.ts +0 -603
- package/.agent/skills/gstack-canary/SKILL.md +0 -699
- package/.agent/skills/gstack-canary/SKILL.md.tmpl +0 -254
- package/.agent/skills/gstack-careful/SKILL.md +0 -96
- package/.agent/skills/gstack-careful/SKILL.md.tmpl +0 -107
- package/.agent/skills/gstack-careful/bin/check-careful.sh +0 -407
- package/.agent/skills/gstack-careful/bin/hook-extract.sh +0 -198
- package/.agent/skills/gstack-codex/SKILL.md +0 -889
- package/.agent/skills/gstack-codex/SKILL.md.tmpl +0 -358
- package/.agent/skills/gstack-codex/sections/challenge-mode.md +0 -150
- package/.agent/skills/gstack-codex/sections/challenge-mode.md.tmpl +0 -136
- package/.agent/skills/gstack-codex/sections/consult-mode.md +0 -221
- package/.agent/skills/gstack-codex/sections/consult-mode.md.tmpl +0 -207
- package/.agent/skills/gstack-codex/sections/manifest.json +0 -26
- package/.agent/skills/gstack-codex/sections/review-mode.md +0 -265
- package/.agent/skills/gstack-codex/sections/review-mode.md.tmpl +0 -246
- package/.agent/skills/gstack-context-restore/SKILL.md +0 -612
- package/.agent/skills/gstack-context-restore/SKILL.md.tmpl +0 -289
- package/.agent/skills/gstack-context-save/SKILL.md +0 -631
- package/.agent/skills/gstack-context-save/SKILL.md.tmpl +0 -307
- package/.agent/skills/gstack-cso/ACKNOWLEDGEMENTS.md +0 -17
- package/.agent/skills/gstack-cso/SKILL.md +0 -166
- package/.agent/skills/gstack-cso/SKILL.md.tmpl +0 -162
- package/.agent/skills/gstack-cso/sections/audit-phases.md +0 -116
- package/.agent/skills/gstack-cso/sections/audit-phases.md.tmpl +0 -114
- package/.agent/skills/gstack-cso/sections/manifest.json +0 -14
- package/.agent/skills/gstack-design-consultation/SKILL.md +0 -795
- package/.agent/skills/gstack-design-consultation/SKILL.md.tmpl +0 -234
- package/.agent/skills/gstack-design-consultation/sections/manifest.json +0 -14
- package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md +0 -595
- package/.agent/skills/gstack-design-consultation/sections/proposal-and-preview.md.tmpl +0 -350
- package/.agent/skills/gstack-design-html/SKILL.md +0 -866
- package/.agent/skills/gstack-design-html/SKILL.md.tmpl +0 -476
- package/.agent/skills/gstack-design-html/sections/detector-install-offer.md +0 -40
- package/.agent/skills/gstack-design-html/sections/detector-install-offer.md.tmpl +0 -6
- package/.agent/skills/gstack-design-html/sections/doctrine.md +0 -86
- package/.agent/skills/gstack-design-html/sections/doctrine.md.tmpl +0 -1
- package/.agent/skills/gstack-design-html/sections/manifest.json +0 -26
- package/.agent/skills/gstack-design-html/sections/pretext-patterns.md +0 -145
- package/.agent/skills/gstack-design-html/sections/pretext-patterns.md.tmpl +0 -143
- package/.agent/skills/gstack-design-html/vendor/pretext.js +0 -5
- package/.agent/skills/gstack-design-review/SKILL.md +0 -1957
- package/.agent/skills/gstack-design-review/SKILL.md.tmpl +0 -344
- package/.agent/skills/gstack-design-shotgun/SKILL.md +0 -885
- package/.agent/skills/gstack-design-shotgun/SKILL.md.tmpl +0 -310
- package/.agent/skills/gstack-design-shotgun/sections/doctrine.md +0 -86
- package/.agent/skills/gstack-design-shotgun/sections/doctrine.md.tmpl +0 -1
- package/.agent/skills/gstack-design-shotgun/sections/manifest.json +0 -14
- package/.agent/skills/gstack-deslop-shared-libs/SKILL.md +0 -209
- package/.agent/skills/gstack-deslop-shared-libs/SKILL.md.tmpl +0 -181
- package/.agent/skills/gstack-devex-review/SKILL.md +0 -1107
- package/.agent/skills/gstack-devex-review/SKILL.md.tmpl +0 -241
- package/.agent/skills/gstack-diagram/SKILL.md +0 -295
- package/.agent/skills/gstack-diagram/SKILL.md.tmpl +0 -175
- package/.agent/skills/gstack-document-generate/SKILL.md +0 -814
- package/.agent/skills/gstack-document-generate/SKILL.md.tmpl +0 -460
- package/.agent/skills/gstack-document-release/SKILL.md +0 -557
- package/.agent/skills/gstack-document-release/SKILL.md.tmpl +0 -188
- package/.agent/skills/gstack-document-release/sections/audit-scope.md +0 -51
- package/.agent/skills/gstack-document-release/sections/audit-scope.md.tmpl +0 -49
- package/.agent/skills/gstack-document-release/sections/manifest.json +0 -20
- package/.agent/skills/gstack-document-release/sections/release-body.md +0 -640
- package/.agent/skills/gstack-document-release/sections/release-body.md.tmpl +0 -428
- package/.agent/skills/gstack-eval-plan/SKILL.md +0 -472
- package/.agent/skills/gstack-eval-plan/SKILL.md.tmpl +0 -157
- package/.agent/skills/gstack-freeze/SKILL.md +0 -79
- package/.agent/skills/gstack-freeze/SKILL.md.tmpl +0 -96
- package/.agent/skills/gstack-freeze/bin/check-freeze.sh +0 -210
- package/.agent/skills/gstack-freeze/bin/freeze-state.sh +0 -68
- package/.agent/skills/gstack-gstack-upgrade/SKILL.md +0 -410
- package/.agent/skills/gstack-gstack-upgrade/SKILL.md.tmpl +0 -413
- package/.agent/skills/gstack-gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
- package/.agent/skills/gstack-gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
- package/.agent/skills/gstack-gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
- package/.agent/skills/gstack-guard/SKILL.md +0 -60
- package/.agent/skills/gstack-guard/SKILL.md.tmpl +0 -88
- package/.agent/skills/gstack-health/SKILL.md +0 -710
- package/.agent/skills/gstack-health/SKILL.md.tmpl +0 -386
- package/.agent/skills/gstack-investigate/SKILL.md +0 -706
- package/.agent/skills/gstack-investigate/SKILL.md.tmpl +0 -305
- package/.agent/skills/gstack-investigate/sections/backlog.md +0 -31
- package/.agent/skills/gstack-investigate/sections/backlog.md.tmpl +0 -29
- package/.agent/skills/gstack-investigate/sections/eval-bisect.md +0 -25
- package/.agent/skills/gstack-investigate/sections/eval-bisect.md.tmpl +0 -23
- package/.agent/skills/gstack-investigate/sections/flake.md +0 -50
- package/.agent/skills/gstack-investigate/sections/flake.md.tmpl +0 -43
- package/.agent/skills/gstack-investigate/sections/manifest.json +0 -38
- package/.agent/skills/gstack-investigate/sections/polluter.md +0 -33
- package/.agent/skills/gstack-investigate/sections/polluter.md.tmpl +0 -31
- package/.agent/skills/gstack-investigate/sections/race.md +0 -27
- package/.agent/skills/gstack-investigate/sections/race.md.tmpl +0 -25
- package/.agent/skills/gstack-ios-clean/SKILL.md +0 -427
- package/.agent/skills/gstack-ios-clean/SKILL.md.tmpl +0 -107
- package/.agent/skills/gstack-ios-design-review/SKILL.md +0 -434
- package/.agent/skills/gstack-ios-design-review/SKILL.md.tmpl +0 -111
- package/.agent/skills/gstack-ios-fix/SKILL.md +0 -423
- package/.agent/skills/gstack-ios-fix/SKILL.md.tmpl +0 -103
- package/.agent/skills/gstack-ios-qa/SKILL.md +0 -643
- package/.agent/skills/gstack-ios-qa/SKILL.md.tmpl +0 -297
- package/.agent/skills/gstack-ios-qa/daemon/src/allowlist.ts +0 -114
- package/.agent/skills/gstack-ios-qa/daemon/src/audit.ts +0 -97
- package/.agent/skills/gstack-ios-qa/daemon/src/auth-mint.ts +0 -89
- package/.agent/skills/gstack-ios-qa/daemon/src/cli-mint.ts +0 -149
- package/.agent/skills/gstack-ios-qa/daemon/src/devicectl.ts +0 -335
- package/.agent/skills/gstack-ios-qa/daemon/src/index.ts +0 -697
- package/.agent/skills/gstack-ios-qa/daemon/src/proxy.ts +0 -160
- package/.agent/skills/gstack-ios-qa/daemon/src/session-tokens.ts +0 -126
- package/.agent/skills/gstack-ios-qa/daemon/src/single-instance.ts +0 -171
- package/.agent/skills/gstack-ios-qa/daemon/src/tailscale-localapi.ts +0 -120
- package/.agent/skills/gstack-ios-qa/daemon/src/tunnel-bootstrap.ts +0 -459
- package/.agent/skills/gstack-ios-qa/daemon/src/types.ts +0 -91
- package/.agent/skills/gstack-ios-qa/daemon/test/allowlist.test.ts +0 -146
- package/.agent/skills/gstack-ios-qa/daemon/test/audit.test.ts +0 -111
- package/.agent/skills/gstack-ios-qa/daemon/test/auth-mint.test.ts +0 -106
- package/.agent/skills/gstack-ios-qa/daemon/test/cli-mint.test.ts +0 -120
- package/.agent/skills/gstack-ios-qa/daemon/test/daemon-integration.test.ts +0 -855
- package/.agent/skills/gstack-ios-qa/daemon/test/fake-device.ts +0 -238
- package/.agent/skills/gstack-ios-qa/daemon/test/proxy-classify.test.ts +0 -47
- package/.agent/skills/gstack-ios-qa/daemon/test/proxy-transport.test.ts +0 -499
- package/.agent/skills/gstack-ios-qa/daemon/test/session-tokens.test.ts +0 -156
- package/.agent/skills/gstack-ios-qa/daemon/test/single-instance.test.ts +0 -96
- package/.agent/skills/gstack-ios-qa/daemon/test/tailscale-localapi.test.ts +0 -55
- package/.agent/skills/gstack-ios-qa/daemon/test/tunnel-bootstrap.test.ts +0 -1030
- package/.agent/skills/gstack-ios-qa/templates/Bridges.swift.template +0 -474
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeManager.swift.template +0 -38
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.h.template +0 -38
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeTouch.m.template +0 -365
- package/.agent/skills/gstack-ios-qa/templates/DebugBridgeWiring.swift.template +0 -59
- package/.agent/skills/gstack-ios-qa/templates/DebugOverlay.swift.template +0 -137
- package/.agent/skills/gstack-ios-qa/templates/Package.swift.template +0 -82
- package/.agent/skills/gstack-ios-qa/templates/StateAccessor.swift.template +0 -41
- package/.agent/skills/gstack-ios-qa/templates/StateServer.swift.template +0 -625
- package/.agent/skills/gstack-ios-sync/SKILL.md +0 -418
- package/.agent/skills/gstack-ios-sync/SKILL.md.tmpl +0 -99
- package/.agent/skills/gstack-land-and-deploy/SKILL.md +0 -1028
- package/.agent/skills/gstack-land-and-deploy/SKILL.md.tmpl +0 -509
- package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md +0 -188
- package/.agent/skills/gstack-land-and-deploy/sections/first-run-validation.md.tmpl +0 -150
- package/.agent/skills/gstack-land-and-deploy/sections/manifest.json +0 -26
- package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md +0 -250
- package/.agent/skills/gstack-land-and-deploy/sections/merge-and-deploy.md.tmpl +0 -248
- package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md +0 -308
- package/.agent/skills/gstack-land-and-deploy/sections/readiness-gate.md.tmpl +0 -306
- package/.agent/skills/gstack-landing-report/SKILL.md +0 -488
- package/.agent/skills/gstack-landing-report/SKILL.md.tmpl +0 -164
- package/.agent/skills/gstack-learn/SKILL.md +0 -519
- package/.agent/skills/gstack-learn/SKILL.md.tmpl +0 -197
- package/.agent/skills/gstack-make-pdf/SKILL.md +0 -409
- package/.agent/skills/gstack-make-pdf/SKILL.md.tmpl +0 -253
- package/.agent/skills/gstack-make-pdf/src/asideClient.ts +0 -114
- package/.agent/skills/gstack-make-pdf/src/cli.ts +0 -294
- package/.agent/skills/gstack-make-pdf/src/commands.ts +0 -62
- package/.agent/skills/gstack-make-pdf/src/diagram-prepass.ts +0 -834
- package/.agent/skills/gstack-make-pdf/src/html-to-docx.d.ts +0 -7
- package/.agent/skills/gstack-make-pdf/src/image-policy.ts +0 -236
- package/.agent/skills/gstack-make-pdf/src/image-size.ts +0 -117
- package/.agent/skills/gstack-make-pdf/src/orchestrator.ts +0 -339
- package/.agent/skills/gstack-make-pdf/src/pdftotext.ts +0 -318
- package/.agent/skills/gstack-make-pdf/src/print-css.ts +0 -452
- package/.agent/skills/gstack-make-pdf/src/render.ts +0 -636
- package/.agent/skills/gstack-make-pdf/src/setup.ts +0 -113
- package/.agent/skills/gstack-make-pdf/src/smartypants.ts +0 -105
- package/.agent/skills/gstack-make-pdf/src/toc-pages.ts +0 -197
- package/.agent/skills/gstack-make-pdf/src/types.ts +0 -109
- package/.agent/skills/gstack-make-pdf/test/asideClient.test.ts +0 -153
- package/.agent/skills/gstack-make-pdf/test/cli-args.test.ts +0 -94
- package/.agent/skills/gstack-make-pdf/test/cli-exit-codes.test.ts +0 -329
- package/.agent/skills/gstack-make-pdf/test/diagram-prepass.test.ts +0 -743
- package/.agent/skills/gstack-make-pdf/test/e2e/ci-prereqs.test.ts +0 -48
- package/.agent/skills/gstack-make-pdf/test/e2e/combined-gate.test.ts +0 -74
- package/.agent/skills/gstack-make-pdf/test/e2e/diagram-gate.test.ts +0 -173
- package/.agent/skills/gstack-make-pdf/test/e2e/emoji-gate.test.ts +0 -191
- package/.agent/skills/gstack-make-pdf/test/e2e/format-gate.test.ts +0 -130
- package/.agent/skills/gstack-make-pdf/test/e2e/landscape-gate.test.ts +0 -150
- package/.agent/skills/gstack-make-pdf/test/e2e/toc-gate.test.ts +0 -143
- package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.expected.txt +0 -20
- package/.agent/skills/gstack-make-pdf/test/fixtures/combined-gate.md +0 -30
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/huge-noise.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/red-box.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-arch.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-assets/wide-screenshot.png +0 -0
- package/.agent/skills/gstack-make-pdf/test/fixtures/diagram-gate.md +0 -48
- package/.agent/skills/gstack-make-pdf/test/fixtures/emoji-gate.md +0 -12
- package/.agent/skills/gstack-make-pdf/test/fixtures/landscape-gate.md +0 -52
- package/.agent/skills/gstack-make-pdf/test/image-policy.test.ts +0 -215
- package/.agent/skills/gstack-make-pdf/test/pdftotext.test.ts +0 -327
- package/.agent/skills/gstack-make-pdf/test/render-offline-sanitize.test.ts +0 -268
- package/.agent/skills/gstack-make-pdf/test/render.test.ts +0 -603
- package/.agent/skills/gstack-make-pdf/test/setup-smoke.test.ts +0 -259
- package/.agent/skills/gstack-make-pdf/test/toc-anchors.test.ts +0 -95
- package/.agent/skills/gstack-make-pdf/test/toc-pages.test.ts +0 -148
- package/.agent/skills/gstack-office-hours/SKILL.md +0 -1324
- package/.agent/skills/gstack-office-hours/SKILL.md.tmpl +0 -372
- package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md +0 -706
- package/.agent/skills/gstack-office-hours/sections/design-and-handoff.md.tmpl +0 -547
- package/.agent/skills/gstack-office-hours/sections/manifest.json +0 -26
- package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md +0 -163
- package/.agent/skills/gstack-office-hours/sections/phase-2a-startup-diagnostic.md.tmpl +0 -161
- package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md +0 -43
- package/.agent/skills/gstack-office-hours/sections/phase-2b-builder-brainstorm.md.tmpl +0 -41
- package/.agent/skills/gstack-open-gstack-browser/SKILL.md +0 -414
- package/.agent/skills/gstack-open-gstack-browser/SKILL.md.tmpl +0 -248
- package/.agent/skills/gstack-pair-agent/SKILL.md +0 -730
- package/.agent/skills/gstack-pair-agent/SKILL.md.tmpl +0 -366
- package/.agent/skills/gstack-plan-ceo-review/SKILL.md +0 -1245
- package/.agent/skills/gstack-plan-ceo-review/SKILL.md.tmpl +0 -596
- package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook +0 -5
- package/.agent/skills/gstack-plan-ceo-review/bin/mode-handoff-hook.ts +0 -34
- package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md +0 -37
- package/.agent/skills/gstack-plan-ceo-review/sections/checklist.md.tmpl +0 -35
- package/.agent/skills/gstack-plan-ceo-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md +0 -1207
- package/.agent/skills/gstack-plan-ceo-review/sections/review-sections.md.tmpl +0 -605
- package/.agent/skills/gstack-plan-design-review/SKILL.md +0 -1230
- package/.agent/skills/gstack-plan-design-review/SKILL.md.tmpl +0 -344
- package/.agent/skills/gstack-plan-design-review/sections/checklist.md +0 -27
- package/.agent/skills/gstack-plan-design-review/sections/checklist.md.tmpl +0 -25
- package/.agent/skills/gstack-plan-design-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-design-review/sections/review-sections.md +0 -727
- package/.agent/skills/gstack-plan-design-review/sections/review-sections.md.tmpl +0 -281
- package/.agent/skills/gstack-plan-devex-review/SKILL.md +0 -1072
- package/.agent/skills/gstack-plan-devex-review/SKILL.md.tmpl +0 -459
- package/.agent/skills/gstack-plan-devex-review/dx-hall-of-fame.md +0 -127
- package/.agent/skills/gstack-plan-devex-review/sections/checklist.md +0 -33
- package/.agent/skills/gstack-plan-devex-review/sections/checklist.md.tmpl +0 -31
- package/.agent/skills/gstack-plan-devex-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md +0 -1037
- package/.agent/skills/gstack-plan-devex-review/sections/review-sections.md.tmpl +0 -446
- package/.agent/skills/gstack-plan-eng-review/SKILL.md +0 -702
- package/.agent/skills/gstack-plan-eng-review/SKILL.md.tmpl +0 -191
- package/.agent/skills/gstack-plan-eng-review/sections/checklist.md +0 -34
- package/.agent/skills/gstack-plan-eng-review/sections/checklist.md.tmpl +0 -32
- package/.agent/skills/gstack-plan-eng-review/sections/manifest.json +0 -23
- package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md +0 -1545
- package/.agent/skills/gstack-plan-eng-review/sections/review-sections.md.tmpl +0 -614
- package/.agent/skills/gstack-plan-tune/SKILL.md +0 -986
- package/.agent/skills/gstack-plan-tune/SKILL.md.tmpl +0 -669
- package/.agent/skills/gstack-qa/SKILL.md +0 -781
- package/.agent/skills/gstack-qa/SKILL.md.tmpl +0 -305
- package/.agent/skills/gstack-qa/references/issue-taxonomy.md +0 -85
- package/.agent/skills/gstack-qa/sections/browser-setup.md +0 -114
- package/.agent/skills/gstack-qa/sections/browser-setup.md.tmpl +0 -28
- package/.agent/skills/gstack-qa/sections/browser-verify.md +0 -19
- package/.agent/skills/gstack-qa/sections/browser-verify.md.tmpl +0 -17
- package/.agent/skills/gstack-qa/sections/exploratory.md +0 -92
- package/.agent/skills/gstack-qa/sections/exploratory.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/manifest.json +0 -50
- package/.agent/skills/gstack-qa/sections/qa-patterns.md +0 -226
- package/.agent/skills/gstack-qa/sections/qa-patterns.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/scope.md +0 -23
- package/.agent/skills/gstack-qa/sections/scope.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/system-functional.md +0 -61
- package/.agent/skills/gstack-qa/sections/system-functional.md.tmpl +0 -1
- package/.agent/skills/gstack-qa/sections/test-bootstrap.md +0 -86
- package/.agent/skills/gstack-qa/sections/test-bootstrap.md.tmpl +0 -74
- package/.agent/skills/gstack-qa/templates/functional-report-template.md +0 -55
- package/.agent/skills/gstack-qa/templates/qa-report-template.md +0 -124
- package/.agent/skills/gstack-qa-only/SKILL.md +0 -595
- package/.agent/skills/gstack-qa-only/SKILL.md.tmpl +0 -214
- package/.agent/skills/gstack-qa-only/sections/exploratory.md +0 -107
- package/.agent/skills/gstack-qa-only/sections/exploratory.md.tmpl +0 -1
- package/.agent/skills/gstack-qa-only/sections/manifest.json +0 -19
- package/.agent/skills/gstack-qa-only/sections/reporting.md +0 -78
- package/.agent/skills/gstack-qa-only/sections/reporting.md.tmpl +0 -76
- package/.agent/skills/gstack-retro/SKILL.md +0 -1178
- package/.agent/skills/gstack-retro/SKILL.md.tmpl +0 -769
- package/.agent/skills/gstack-retro/sections/manifest.json +0 -14
- package/.agent/skills/gstack-retro/sections/report-format.md +0 -127
- package/.agent/skills/gstack-retro/sections/report-format.md.tmpl +0 -125
- package/.agent/skills/gstack-review/SKILL.md +0 -1199
- package/.agent/skills/gstack-review/SKILL.md.tmpl +0 -482
- package/.agent/skills/gstack-review/TODOS-format.md +0 -61
- package/.agent/skills/gstack-review/checklist.md +0 -222
- package/.agent/skills/gstack-review/design-checklist.md +0 -193
- package/.agent/skills/gstack-review/gate-integrity.md +0 -171
- package/.agent/skills/gstack-review/greptile-triage.md +0 -261
- package/.agent/skills/gstack-review/sections/adversarial.md +0 -296
- package/.agent/skills/gstack-review/sections/adversarial.md.tmpl +0 -1
- package/.agent/skills/gstack-review/sections/manifest.json +0 -32
- package/.agent/skills/gstack-review/sections/plan-completion.md +0 -232
- package/.agent/skills/gstack-review/sections/plan-completion.md.tmpl +0 -3
- package/.agent/skills/gstack-review/sections/review-army.md +0 -263
- package/.agent/skills/gstack-review/sections/review-army.md.tmpl +0 -1
- package/.agent/skills/gstack-review/sections/shared-code-reuse.md +0 -34
- package/.agent/skills/gstack-review/sections/shared-code-reuse.md.tmpl +0 -1
- package/.agent/skills/gstack-review/specialists/api-contract.md +0 -49
- package/.agent/skills/gstack-review/specialists/data-migration.md +0 -48
- package/.agent/skills/gstack-review/specialists/maintainability.md +0 -46
- package/.agent/skills/gstack-review/specialists/performance.md +0 -52
- package/.agent/skills/gstack-review/specialists/red-team.md +0 -45
- package/.agent/skills/gstack-review/specialists/security.md +0 -65
- package/.agent/skills/gstack-review/specialists/simplification.md +0 -49
- package/.agent/skills/gstack-review/specialists/testing.md +0 -160
- package/.agent/skills/gstack-scrape/SKILL.md +0 -431
- package/.agent/skills/gstack-scrape/SKILL.md.tmpl +0 -187
- package/.agent/skills/gstack-setup-browser-cookies/SKILL.md +0 -231
- package/.agent/skills/gstack-setup-browser-cookies/SKILL.md.tmpl +0 -64
- package/.agent/skills/gstack-setup-deploy/SKILL.md +0 -605
- package/.agent/skills/gstack-setup-deploy/SKILL.md.tmpl +0 -250
- package/.agent/skills/gstack-setup-gbrain/SKILL.md +0 -1074
- package/.agent/skills/gstack-setup-gbrain/SKILL.md.tmpl +0 -742
- package/.agent/skills/gstack-setup-gbrain/memory.md +0 -426
- package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md +0 -269
- package/.agent/skills/gstack-setup-gbrain/sections/brain-init.md.tmpl +0 -267
- package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md +0 -40
- package/.agent/skills/gstack-setup-gbrain/sections/claude-md-persist.md.tmpl +0 -38
- package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md +0 -71
- package/.agent/skills/gstack-setup-gbrain/sections/engine-remediation.md.tmpl +0 -69
- package/.agent/skills/gstack-setup-gbrain/sections/manifest.json +0 -32
- package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md +0 -105
- package/.agent/skills/gstack-setup-gbrain/sections/transcript-gate.md.tmpl +0 -103
- package/.agent/skills/gstack-ship/SKILL.md +0 -1220
- package/.agent/skills/gstack-ship/SKILL.md.tmpl +0 -694
- package/.agent/skills/gstack-ship/sections/adversarial.md +0 -362
- package/.agent/skills/gstack-ship/sections/adversarial.md.tmpl +0 -22
- package/.agent/skills/gstack-ship/sections/apple-release.md +0 -98
- package/.agent/skills/gstack-ship/sections/apple-release.md.tmpl +0 -96
- package/.agent/skills/gstack-ship/sections/changelog.md +0 -47
- package/.agent/skills/gstack-ship/sections/changelog.md.tmpl +0 -5
- package/.agent/skills/gstack-ship/sections/documentation.md +0 -118
- package/.agent/skills/gstack-ship/sections/documentation.md.tmpl +0 -116
- package/.agent/skills/gstack-ship/sections/greptile-early.md +0 -57
- package/.agent/skills/gstack-ship/sections/greptile-early.md.tmpl +0 -48
- package/.agent/skills/gstack-ship/sections/greptile.md +0 -95
- package/.agent/skills/gstack-ship/sections/greptile.md.tmpl +0 -93
- package/.agent/skills/gstack-ship/sections/handoff.md +0 -22
- package/.agent/skills/gstack-ship/sections/handoff.md.tmpl +0 -20
- package/.agent/skills/gstack-ship/sections/manifest.json +0 -92
- package/.agent/skills/gstack-ship/sections/measure.md +0 -66
- package/.agent/skills/gstack-ship/sections/measure.md.tmpl +0 -64
- package/.agent/skills/gstack-ship/sections/plan-completion.md +0 -387
- package/.agent/skills/gstack-ship/sections/plan-completion.md.tmpl +0 -67
- package/.agent/skills/gstack-ship/sections/pr-body.md +0 -258
- package/.agent/skills/gstack-ship/sections/pr-body.md.tmpl +0 -241
- package/.agent/skills/gstack-ship/sections/review-army.md +0 -728
- package/.agent/skills/gstack-ship/sections/review-army.md.tmpl +0 -171
- package/.agent/skills/gstack-ship/sections/shared-code-reuse.md +0 -34
- package/.agent/skills/gstack-ship/sections/shared-code-reuse.md.tmpl +0 -1
- package/.agent/skills/gstack-ship/sections/test-coverage.md +0 -478
- package/.agent/skills/gstack-ship/sections/test-coverage.md.tmpl +0 -118
- package/.agent/skills/gstack-ship/sections/tests.md +0 -453
- package/.agent/skills/gstack-ship/sections/tests.md.tmpl +0 -144
- package/.agent/skills/gstack-skillify/SKILL.md +0 -790
- package/.agent/skills/gstack-skillify/SKILL.md.tmpl +0 -437
- package/.agent/skills/gstack-spec/SKILL.md +0 -893
- package/.agent/skills/gstack-spec/SKILL.md.tmpl +0 -498
- package/.agent/skills/gstack-spec/sections/gate-and-file.md +0 -470
- package/.agent/skills/gstack-spec/sections/gate-and-file.md.tmpl +0 -319
- package/.agent/skills/gstack-spec/sections/manifest.json +0 -14
- package/.agent/skills/gstack-sync-gbrain/SKILL.md +0 -935
- package/.agent/skills/gstack-sync-gbrain/SKILL.md.tmpl +0 -615
- package/.agent/skills/gstack-test-audit/SKILL.md +0 -500
- package/.agent/skills/gstack-test-audit/SKILL.md.tmpl +0 -149
- package/.agent/skills/gstack-unfreeze/SKILL.md +0 -39
- package/.agent/skills/gstack-unfreeze/SKILL.md.tmpl +0 -39
- package/.agent/skills/gstack-upgrade/SKILL.md +0 -410
- package/.agent/skills/gstack-upgrade/SKILL.md.tmpl +0 -413
- package/.agent/skills/gstack-upgrade/migrations/v0.15.2.0.sh +0 -20
- package/.agent/skills/gstack-upgrade/migrations/v0.16.2.0.sh +0 -56
- package/.agent/skills/gstack-upgrade/migrations/v1.0.0.0.sh +0 -40
- package/.agent/skills/gstack-upgrade/migrations/v1.1.3.0.sh +0 -137
- package/.agent/skills/gstack-upgrade/migrations/v1.17.0.0.sh +0 -59
- package/.agent/skills/gstack-upgrade/migrations/v1.27.0.0.sh +0 -432
- package/.agent/skills/gstack-upgrade/migrations/v1.37.0.0.sh +0 -104
- package/.agent/skills/gstack-upgrade/migrations/v1.38.1.0.sh +0 -104
- package/.agent/skills/gstack-upgrade/migrations/v1.40.0.0.sh +0 -144
- package/.agent/skills/gstack-upgrade/migrations/v1.58.0.0.sh +0 -65
- package/.agent/skills/gstack-upgrade/migrations/v1.65.0.0.sh +0 -152
- package/.agent/skills/gstack-upgrade/migrations/v1.67.0.0.sh +0 -73
- package/.agent/skills/gstack-upgrade/migrations/v1.78.0.0.sh +0 -30
- package/.agent/skills/gstack-upgrade/migrations/v1.86.0.0.sh +0 -12
- package/.agent/skills/gstack-upgrade/migrations/v1.91.19.0.sh +0 -53
- package/.agent/skills/gstack-upgrade/migrations/v1.91.65.0.sh +0 -31
- package/.agent/skills/html-renderer/SKILL.md +0 -336
- package/.agent/skills/html-renderer/references/animation-patterns.md +0 -609
- package/.agent/skills/html-renderer/references/character-animation.md +0 -388
- package/.agent/skills/html-renderer/references/collage-broll.md +0 -41
- package/.agent/skills/html-renderer/references/design.md +0 -217
- package/.agent/skills/html-renderer/references/directing.md +0 -165
- package/.agent/skills/html-renderer/references/earshot-audio.md +0 -201
- package/.agent/skills/html-renderer/references/engine-api.md +0 -480
- package/.agent/skills/html-renderer/references/film-styles.md +0 -95
- package/.agent/skills/html-renderer/references/hyperframes-motion.md +0 -208
- package/.agent/skills/html-renderer/references/motion-craft.md +0 -92
- package/.agent/skills/html-renderer/references/onetake-camera.md +0 -59
- package/.agent/skills/html-renderer/references/procedural-sfx.md +0 -147
- package/.agent/skills/html-renderer/references/remotion-patterns.md +0 -159
- package/.agent/skills/html-renderer/references/render-contract.md +0 -66
- package/.agent/skills/motion-styles/LIBRARY.md +0 -896
- package/.agent/skills/motion-styles/README.md +0 -29
- package/.agent/skills/motion-styles/SKILL.md +0 -70
- package/.agent/skills/motion-styles/catalog.json +0 -2253
- package/.agent/skills/motion-styles/prompts/bio-glow.md +0 -149
- package/.agent/skills/motion-styles/prompts/blueprint.md +0 -145
- package/.agent/skills/motion-styles/prompts/breaking-desk.md +0 -147
- package/.agent/skills/motion-styles/prompts/callout-kit.md +0 -147
- package/.agent/skills/motion-styles/prompts/chalk-talk.md +0 -153
- package/.agent/skills/motion-styles/prompts/chat-story.md +0 -143
- package/.agent/skills/motion-styles/prompts/clay-diorama.md +0 -149
- package/.agent/skills/motion-styles/prompts/clean-tech.md +0 -147
- package/.agent/skills/motion-styles/prompts/comic-panels.md +0 -145
- package/.agent/skills/motion-styles/prompts/cosmic-scale.md +0 -147
- package/.agent/skills/motion-styles/prompts/creator-motion.md +0 -149
- package/.agent/skills/motion-styles/prompts/data-city.md +0 -149
- package/.agent/skills/motion-styles/prompts/data-story.md +0 -147
- package/.agent/skills/motion-styles/prompts/deep-layers.md +0 -147
- package/.agent/skills/motion-styles/prompts/doc-cinema.md +0 -145
- package/.agent/skills/motion-styles/prompts/evidence-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/exploded-view.md +0 -149
- package/.agent/skills/motion-styles/prompts/glass-icons.md +0 -147
- package/.agent/skills/motion-styles/prompts/glitch-pack.md +0 -145
- package/.agent/skills/motion-styles/prompts/globe-arcs.md +0 -147
- package/.agent/skills/motion-styles/prompts/headline-backdrop.md +0 -145
- package/.agent/skills/motion-styles/prompts/iso-cutaway.md +0 -147
- package/.agent/skills/motion-styles/prompts/isotype.md +0 -145
- package/.agent/skills/motion-styles/prompts/karaoke-subs.md +0 -145
- package/.agent/skills/motion-styles/prompts/low-poly-atlas.md +0 -149
- package/.agent/skills/motion-styles/prompts/lower-thirds.md +0 -147
- package/.agent/skills/motion-styles/prompts/marker-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/market-tape.md +0 -145
- package/.agent/skills/motion-styles/prompts/math-canvas.md +0 -145
- package/.agent/skills/motion-styles/prompts/myth-or-fact.md +0 -145
- package/.agent/skills/motion-styles/prompts/neon-line.md +0 -145
- package/.agent/skills/motion-styles/prompts/paper-craft.md +0 -145
- package/.agent/skills/motion-styles/prompts/parchment-map.md +0 -147
- package/.agent/skills/motion-styles/prompts/physics-toybox.md +0 -147
- package/.agent/skills/motion-styles/prompts/pixel-quest.md +0 -145
- package/.agent/skills/motion-styles/prompts/podcast-clip.md +0 -147
- package/.agent/skills/motion-styles/prompts/pop-info.md +0 -145
- package/.agent/skills/motion-styles/prompts/product-turntable.md +0 -149
- package/.agent/skills/motion-styles/prompts/punch-captions.md +0 -145
- package/.agent/skills/motion-styles/prompts/quiz-countdown.md +0 -145
- package/.agent/skills/motion-styles/prompts/quote-cinema.md +0 -145
- package/.agent/skills/motion-styles/prompts/recipe-steps.md +0 -145
- package/.agent/skills/motion-styles/prompts/rubber-hose-reel.md +0 -145
- package/.agent/skills/motion-styles/prompts/shadow-puppets.md +0 -145
- package/.agent/skills/motion-styles/prompts/size-lineup.md +0 -149
- package/.agent/skills/motion-styles/prompts/software-walkthrough.md +0 -147
- package/.agent/skills/motion-styles/prompts/stack-split.md +0 -145
- package/.agent/skills/motion-styles/prompts/sticker-pop.md +0 -145
- package/.agent/skills/motion-styles/prompts/storybook.md +0 -145
- package/.agent/skills/motion-styles/prompts/swiss-grid.md +0 -145
- package/.agent/skills/motion-styles/prompts/synth-grid.md +0 -147
- package/.agent/skills/motion-styles/prompts/tactics-board.md +0 -145
- package/.agent/skills/motion-styles/prompts/terminal.md +0 -145
- package/.agent/skills/motion-styles/prompts/tier-list.md +0 -143
- package/.agent/skills/motion-styles/prompts/timeline-rail.md +0 -145
- package/.agent/skills/motion-styles/prompts/top-5-countdown.md +0 -145
- package/.agent/skills/motion-styles/prompts/versus-split.md +0 -145
- package/.agent/skills/motion-styles/prompts/vhs-camcorder.md +0 -145
- package/.agent/skills/motion-styles/prompts/visual-novel.md +0 -145
- package/.agent/skills/motion-styles/prompts/warm-canvas.md +0 -145
- package/.agent/skills/motion-styles/prompts/woodcut-tale.md +0 -145
- package/.agent/skills/motion-styles/prompts/zine-collage.md +0 -145
- package/.agent/skills/motion-styles/sources/bio-glow.html +0 -639
- package/.agent/skills/motion-styles/sources/blueprint.html +0 -617
- package/.agent/skills/motion-styles/sources/breaking-desk.html +0 -585
- package/.agent/skills/motion-styles/sources/callout-kit.html +0 -586
- package/.agent/skills/motion-styles/sources/chalk-talk.html +0 -597
- package/.agent/skills/motion-styles/sources/chat-story.html +0 -597
- package/.agent/skills/motion-styles/sources/clay-diorama.html +0 -681
- package/.agent/skills/motion-styles/sources/clean-tech.html +0 -554
- package/.agent/skills/motion-styles/sources/comic-panels.html +0 -608
- package/.agent/skills/motion-styles/sources/cosmic-scale.html +0 -628
- package/.agent/skills/motion-styles/sources/creator-motion.html +0 -805
- package/.agent/skills/motion-styles/sources/data-city.html +0 -613
- package/.agent/skills/motion-styles/sources/data-story.html +0 -539
- package/.agent/skills/motion-styles/sources/deep-layers.html +0 -624
- package/.agent/skills/motion-styles/sources/doc-cinema.html +0 -590
- package/.agent/skills/motion-styles/sources/evidence-board.html +0 -610
- package/.agent/skills/motion-styles/sources/exploded-view.html +0 -618
- package/.agent/skills/motion-styles/sources/glass-icons.html +0 -612
- package/.agent/skills/motion-styles/sources/glitch-pack.html +0 -583
- package/.agent/skills/motion-styles/sources/globe-arcs.html +0 -621
- package/.agent/skills/motion-styles/sources/headline-backdrop.html +0 -584
- package/.agent/skills/motion-styles/sources/iso-cutaway.html +0 -621
- package/.agent/skills/motion-styles/sources/isotype.html +0 -588
- package/.agent/skills/motion-styles/sources/karaoke-subs.html +0 -577
- package/.agent/skills/motion-styles/sources/low-poly-atlas.html +0 -621
- package/.agent/skills/motion-styles/sources/lower-thirds.html +0 -582
- package/.agent/skills/motion-styles/sources/marker-board.html +0 -598
- package/.agent/skills/motion-styles/sources/market-tape.html +0 -598
- package/.agent/skills/motion-styles/sources/math-canvas.html +0 -592
- package/.agent/skills/motion-styles/sources/myth-or-fact.html +0 -587
- package/.agent/skills/motion-styles/sources/neon-line.html +0 -597
- package/.agent/skills/motion-styles/sources/paper-craft.html +0 -612
- package/.agent/skills/motion-styles/sources/parchment-map.html +0 -624
- package/.agent/skills/motion-styles/sources/physics-toybox.html +0 -617
- package/.agent/skills/motion-styles/sources/pixel-quest.html +0 -609
- package/.agent/skills/motion-styles/sources/podcast-clip.html +0 -585
- package/.agent/skills/motion-styles/sources/pop-info.html +0 -677
- package/.agent/skills/motion-styles/sources/product-turntable.html +0 -611
- package/.agent/skills/motion-styles/sources/punch-captions.html +0 -583
- package/.agent/skills/motion-styles/sources/quiz-countdown.html +0 -593
- package/.agent/skills/motion-styles/sources/quote-cinema.html +0 -581
- package/.agent/skills/motion-styles/sources/recipe-steps.html +0 -599
- package/.agent/skills/motion-styles/sources/rubber-hose-reel.html +0 -627
- package/.agent/skills/motion-styles/sources/shadow-puppets.html +0 -609
- package/.agent/skills/motion-styles/sources/size-lineup.html +0 -620
- package/.agent/skills/motion-styles/sources/software-walkthrough.html +0 -651
- package/.agent/skills/motion-styles/sources/stack-split.html +0 -588
- package/.agent/skills/motion-styles/sources/sticker-pop.html +0 -587
- package/.agent/skills/motion-styles/sources/storybook.html +0 -600
- package/.agent/skills/motion-styles/sources/swiss-grid.html +0 -590
- package/.agent/skills/motion-styles/sources/synth-grid.html +0 -614
- package/.agent/skills/motion-styles/sources/tactics-board.html +0 -608
- package/.agent/skills/motion-styles/sources/terminal.html +0 -593
- package/.agent/skills/motion-styles/sources/tier-list.html +0 -600
- package/.agent/skills/motion-styles/sources/timeline-rail.html +0 -598
- package/.agent/skills/motion-styles/sources/top-5-countdown.html +0 -589
- package/.agent/skills/motion-styles/sources/versus-split.html +0 -592
- package/.agent/skills/motion-styles/sources/vhs-camcorder.html +0 -587
- package/.agent/skills/motion-styles/sources/visual-novel.html +0 -623
- package/.agent/skills/motion-styles/sources/warm-canvas.html +0 -742
- package/.agent/skills/motion-styles/sources/woodcut-tale.html +0 -598
- package/.agent/skills/motion-styles/sources/zine-collage.html +0 -594
- package/.agent/skills/reverse-engineer-anything/SKILL.md +0 -356
- package/.agent/skills/reverse-engineer-anything/references/android-applications.md +0 -36
- package/.agent/skills/reverse-engineer-anything/references/connection-and-recovery.md +0 -127
- package/.agent/skills/reverse-engineer-anything/references/evidence-workflows.md +0 -71
- package/.agent/skills/reverse-engineer-anything/references/javascript-applications.md +0 -218
- package/.agent/skills/reverse-engineer-anything/references/native-and-artifacts.md +0 -297
- package/.agent/skills/reverse-engineer-anything/references/runtime-observation.md +0 -78
- package/.agent/skills/video-pipeline/SKILL.md +0 -384
- package/.agent/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +0 -258
- package/.agent/skills/video-pipeline/assets/project/STORYBOARD.md +0 -77
- package/.agent/skills/video-pipeline/assets/project/index.html +0 -80
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +0 -43
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +0 -75
- package/.agent/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +0 -63
- package/.agent/skills/video-pipeline/assets/project/reel/engine.css +0 -140
- package/.agent/skills/video-pipeline/assets/project/reel/engine.js +0 -2143
- package/.agent/skills/video-pipeline/assets/project/reel/virtual-clock.js +0 -168
- package/.agent/skills/video-pipeline/references/mcp-tools.md +0 -441
- package/.agent/skills/video-pipeline/references/motion-craft.md +0 -91
- package/.agent/skills/video-pipeline/references/providers.md +0 -174
- package/.agent/skills/video-pipeline/references/render-contract.md +0 -65
- package/plugins/aiinsight-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
|
@@ -1,1645 +0,0 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from 'bun:test';
|
|
2
|
-
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, opendirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, rmdirSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
import { once } from 'node:events';
|
|
8
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
9
|
-
import { Database } from 'bun:sqlite';
|
|
10
|
-
import { dlopen, FFIType, ptr } from 'bun:ffi';
|
|
11
|
-
import { nativeBrowserPaths } from '../src/cookie-import-native';
|
|
12
|
-
import { NATIVE_BROWSER_VERSION_COMMAND } from '../src/cookie-import-native-integrity';
|
|
13
|
-
import { createNativeCookieJob, joinNativeCookieJob, NativeCookieJobError, nativeCookieDiagnostic, parseNativeCookieDiagnostic, type NativeCookieJob } from '../src/cookie-import-native-job';
|
|
14
|
-
import { nativeCookieEnvironment, NATIVE_COOKIE_NODE_SCRIPT, superviseNativeCookieImport, type NativeCookieMember, type NativeCookieReply, type NativeCookieRequest } from '../src/cookie-import-native-worker';
|
|
15
|
-
import { decodeNativeCommandLine } from './fixtures/native-cookie-process-observer';
|
|
16
|
-
import { createFixtureDeleteLease, FixtureDeleteError } from './fixtures/native-cookie-delete-lease';
|
|
17
|
-
|
|
18
|
-
const root = mkdtempSync(path.join(tmpdir(), 'cookie-job-'));
|
|
19
|
-
const resolvedRoot = realpathSync(root);
|
|
20
|
-
const initialRootState = lstatSync(root, { bigint: true });
|
|
21
|
-
const fixtureChildren = new Set<ChildProcess>();
|
|
22
|
-
const fixturePrimitiveFailures = new WeakMap<object, object>();
|
|
23
|
-
|
|
24
|
-
function removeOwnedFixtureDirectory(directory: string, identity: { dev: bigint; ino: bigint }, operations = {
|
|
25
|
-
lstat: (file: string) => lstatSync(file, { bigint: true }),
|
|
26
|
-
enumerate: (file: string) => readdirSync(file),
|
|
27
|
-
unlink: (file: string, _identity: { dev: bigint; ino: bigint; mode: bigint }, _verify: () => void) => unlinkSync(file),
|
|
28
|
-
rmdir: (file: string) => rmdirSync(file),
|
|
29
|
-
}): void {
|
|
30
|
-
let primitive = 'identity';
|
|
31
|
-
let object = directory;
|
|
32
|
-
let objectIdentityMatched = false;
|
|
33
|
-
let rootIdentityMatched = false;
|
|
34
|
-
let objectIdentity: { dev: bigint; ino: bigint; mode: bigint } | undefined;
|
|
35
|
-
const ancestors: { file: string; dev: bigint; ino: bigint }[] = [{ file: root, dev: initialRootState.dev, ino: initialRootState.ino }];
|
|
36
|
-
const inspect = (file: string) => {
|
|
37
|
-
primitive = 'lstat'; object = file; objectIdentityMatched = false;
|
|
38
|
-
objectIdentity = undefined;
|
|
39
|
-
const state = operations.lstat(file);
|
|
40
|
-
objectIdentity = { dev: state.dev, ino: state.ino, mode: state.mode };
|
|
41
|
-
return state;
|
|
42
|
-
};
|
|
43
|
-
const verifyAncestors = (stopBefore?: string) => {
|
|
44
|
-
for (const ancestor of ancestors) {
|
|
45
|
-
if (ancestor.file === stopBefore) break;
|
|
46
|
-
if (ancestor.file === root) rootIdentityMatched = false;
|
|
47
|
-
const state = inspect(ancestor.file);
|
|
48
|
-
const matches = state.isDirectory() && !state.isSymbolicLink() && state.dev === ancestor.dev && state.ino === ancestor.ino;
|
|
49
|
-
if (ancestor.file === root) rootIdentityMatched = matches;
|
|
50
|
-
if (!matches) { primitive = 'identity'; throw new Error('Native fixture ancestor identity changed'); }
|
|
51
|
-
objectIdentityMatched = true;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
const remove = (file: string, expected?: { dev: bigint; ino: bigint }) => {
|
|
55
|
-
const ancestorCount = ancestors.length;
|
|
56
|
-
try {
|
|
57
|
-
verifyAncestors();
|
|
58
|
-
const state = inspect(file);
|
|
59
|
-
if (expected && (state.dev !== expected.dev || state.ino !== expected.ino || !state.isDirectory() || state.isSymbolicLink())) {
|
|
60
|
-
primitive = 'identity'; throw new Error('Native fixture directory identity changed');
|
|
61
|
-
}
|
|
62
|
-
if (state.isDirectory() && !state.isSymbolicLink()) {
|
|
63
|
-
ancestors.push({ file, dev: state.dev, ino: state.ino });
|
|
64
|
-
verifyAncestors();
|
|
65
|
-
primitive = 'enumerate'; object = file; objectIdentityMatched = true;
|
|
66
|
-
const entries = operations.enumerate(file);
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (!entry || entry === '.' || entry === '..' || path.basename(entry) !== entry) {
|
|
69
|
-
primitive = 'identity'; object = file; throw new Error('Native fixture enumeration escaped its directory');
|
|
70
|
-
}
|
|
71
|
-
remove(path.join(file, entry));
|
|
72
|
-
}
|
|
73
|
-
verifyAncestors();
|
|
74
|
-
primitive = 'rmdir'; object = file; objectIdentityMatched = true;
|
|
75
|
-
operations.rmdir(file);
|
|
76
|
-
} else {
|
|
77
|
-
const verify = () => {
|
|
78
|
-
verifyAncestors();
|
|
79
|
-
if (realpathSync(root) !== resolvedRoot) { primitive = 'identity'; throw new Error('Native fixture root path changed'); }
|
|
80
|
-
const current = inspect(file);
|
|
81
|
-
if (current.dev !== state.dev || current.ino !== state.ino || current.mode !== state.mode) {
|
|
82
|
-
primitive = 'identity'; throw new Error('Native fixture entry identity changed');
|
|
83
|
-
}
|
|
84
|
-
primitive = 'unlink'; object = file; objectIdentityMatched = true;
|
|
85
|
-
};
|
|
86
|
-
verify();
|
|
87
|
-
operations.unlink(file, state, verify);
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || object !== file) throw error;
|
|
91
|
-
const failedPrimitive = primitive;
|
|
92
|
-
const matchedBeforeFailure = objectIdentityMatched;
|
|
93
|
-
verifyAncestors(file);
|
|
94
|
-
try { inspect(file); }
|
|
95
|
-
catch (absenceError) {
|
|
96
|
-
if ((absenceError as NodeJS.ErrnoException).code === 'ENOENT') return;
|
|
97
|
-
throw absenceError;
|
|
98
|
-
}
|
|
99
|
-
primitive = failedPrimitive; object = file; objectIdentityMatched = matchedBeforeFailure;
|
|
100
|
-
throw error;
|
|
101
|
-
} finally {
|
|
102
|
-
ancestors.length = ancestorCount;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
try {
|
|
106
|
-
const relative = path.relative(path.resolve(root), path.resolve(directory));
|
|
107
|
-
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('Native fixture removal must stay below its owned root');
|
|
108
|
-
verifyAncestors();
|
|
109
|
-
if (realpathSync(root) !== resolvedRoot) { primitive = 'identity'; rootIdentityMatched = false; throw new Error('Native fixture root path changed'); }
|
|
110
|
-
let parent = root;
|
|
111
|
-
for (const part of relative.split(path.sep).slice(0, -1)) {
|
|
112
|
-
verifyAncestors();
|
|
113
|
-
parent = path.join(parent, part);
|
|
114
|
-
const state = inspect(parent);
|
|
115
|
-
if (!state.isDirectory() || state.isSymbolicLink()) { primitive = 'identity'; throw new Error('Native fixture ancestor is not an owned directory'); }
|
|
116
|
-
ancestors.push({ file: parent, dev: state.dev, ino: state.ino });
|
|
117
|
-
}
|
|
118
|
-
remove(directory, identity);
|
|
119
|
-
verifyAncestors();
|
|
120
|
-
primitive = 'postcondition'; object = directory; objectIdentityMatched = false;
|
|
121
|
-
try { operations.lstat(directory); }
|
|
122
|
-
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return; throw error; }
|
|
123
|
-
throw new Error('Native fixture directory remains after removal');
|
|
124
|
-
} catch (error) {
|
|
125
|
-
const relative = path.relative(path.resolve(directory), path.resolve(object));
|
|
126
|
-
if (error && typeof error === 'object') fixturePrimitiveFailures.set(error, {
|
|
127
|
-
primitive, rootIdentityMatched, objectIdentityMatched,
|
|
128
|
-
...(objectIdentity ? { objectDev: objectIdentity.dev.toString(), objectIno: objectIdentity.ino.toString(), objectMode: Number(objectIdentity.mode) } : {}),
|
|
129
|
-
relativeObjectHash: !relative.startsWith('..') && !path.isAbsolute(relative) ? createHash('sha256').update(relative).digest('hex') : undefined,
|
|
130
|
-
objectScope: object === directory ? 'target' : relative.startsWith('..') || path.isAbsolute(relative) ? 'ancestor' : 'child',
|
|
131
|
-
});
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function resetOwnedProfileDirectory(directory: string, identity: { dev: bigint; ino: bigint }, supervisionDeadline: number): void {
|
|
137
|
-
const lease = createFixtureDeleteLease(Math.min(supervisionDeadline, performance.now() + 5_000));
|
|
138
|
-
try {
|
|
139
|
-
removeOwnedFixtureDirectory(directory, identity, {
|
|
140
|
-
lstat: file => lstatSync(file, { bigint: true }), enumerate: file => readdirSync(file),
|
|
141
|
-
unlink: lease.unlink, rmdir: file => rmdirSync(file),
|
|
142
|
-
});
|
|
143
|
-
} finally { lease.close(); }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function ownFixtureChild<T extends ChildProcess>(child: T): T {
|
|
147
|
-
fixtureChildren.add(child);
|
|
148
|
-
child.once('close', () => fixtureChildren.delete(child));
|
|
149
|
-
return child;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function nativeFixtureEnvironment(fixture: string, node: string): Record<string, string> {
|
|
153
|
-
const relative = path.relative(resolvedRoot, realpathSync(fixture));
|
|
154
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('Native environment fixture is outside its owned root');
|
|
155
|
-
const local = path.join(fixture, 'AppData', 'Local');
|
|
156
|
-
const roaming = path.join(fixture, 'AppData', 'Roaming');
|
|
157
|
-
const temporary = path.join(local, 'Temp');
|
|
158
|
-
for (const directory of [local, roaming, temporary]) mkdirSync(directory, { recursive: true });
|
|
159
|
-
return nativeCookieEnvironment({
|
|
160
|
-
SystemRoot: process.env.SystemRoot, USERPROFILE: fixture,
|
|
161
|
-
LOCALAPPDATA: local, APPDATA: roaming, TEMP: temporary, TMP: temporary,
|
|
162
|
-
PATH: path.dirname(node),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function fixtureFileOwners(file: string, timeout = 5_000, expectedIdentity?: { dev: string; ino: string }): object {
|
|
167
|
-
if (process.platform !== 'win32') return { available: false, reason: 'not_windows' };
|
|
168
|
-
const result = spawnSync(process.execPath, [
|
|
169
|
-
'--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, 'fixtures/native-cookie-file-owners.ts'),
|
|
170
|
-
Buffer.from(JSON.stringify({ root: resolvedRoot, file, testPid: process.pid, expectedIdentity })).toString('base64'),
|
|
171
|
-
], { env: nativeCookieEnvironment(process.env), encoding: 'utf8', timeout, maxBuffer: 65536, windowsHide: true });
|
|
172
|
-
try { return { ...JSON.parse(result.stdout), exitCode: result.status, stderrBytes: Buffer.byteLength(result.stderr || '') }; }
|
|
173
|
-
catch { return { available: false, reason: 'owner_probe_no_receipt', exitCode: result.status }; }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function fixtureRemovalEvidence(error: unknown, directory: string, identity: { dev: bigint; ino: bigint }, probeOwners = fixtureFileOwners): object {
|
|
177
|
-
const failure = error as NodeJS.ErrnoException;
|
|
178
|
-
const primitive = error && typeof error === 'object' ? fixturePrimitiveFailures.get(error) as {
|
|
179
|
-
primitive: string; relativeObjectHash?: string; objectDev?: string; objectIno?: string;
|
|
180
|
-
} | undefined : undefined;
|
|
181
|
-
const evidence = {
|
|
182
|
-
code: ['EBUSY', 'EPERM', 'EACCES', 'ENOENT', 'ENOTEMPTY', 'ENOTDIR'].includes(failure?.code || '') ? failure.code : 'filesystem_error',
|
|
183
|
-
syscall: ['rm', 'rmdir', 'unlink', 'scandir', 'lstat', 'open', 'fstat', 'close'].includes(failure?.syscall || '') ? failure.syscall : 'unavailable',
|
|
184
|
-
errno: Number.isSafeInteger(failure?.errno) ? failure.errno : undefined,
|
|
185
|
-
pendingChildCloses: fixtureChildren.size,
|
|
186
|
-
...(error instanceof FixtureDeleteError ? { deletionStage: error.stage, win32Error: error.win32Error, deadlineExceeded: error.deadlineExceeded } : {}),
|
|
187
|
-
...(error && typeof error === 'object' ? fixturePrimitiveFailures.get(error) : undefined),
|
|
188
|
-
};
|
|
189
|
-
try {
|
|
190
|
-
const rootState = lstatSync(root, { bigint: true });
|
|
191
|
-
if (rootState.isSymbolicLink() || rootState.dev !== initialRootState.dev || rootState.ino !== initialRootState.ino || realpathSync(root) !== resolvedRoot) {
|
|
192
|
-
return { ...evidence, inspected: false, reason: 'root_identity_changed' };
|
|
193
|
-
}
|
|
194
|
-
const relative = path.relative(resolvedRoot, path.resolve(directory));
|
|
195
|
-
const parts = relative ? relative.split(path.sep) : [];
|
|
196
|
-
if (relative.startsWith('..') || path.isAbsolute(relative) || parts.length > 16) return { ...evidence, inspected: false, reason: 'outside_owned_fixture' };
|
|
197
|
-
let current = root;
|
|
198
|
-
for (const part of parts) {
|
|
199
|
-
current = path.join(current, part);
|
|
200
|
-
if (lstatSync(current).isSymbolicLink()) return { ...evidence, inspected: false, reason: 'linked_object' };
|
|
201
|
-
}
|
|
202
|
-
const state = lstatSync(directory, { bigint: true });
|
|
203
|
-
if (!state.isDirectory() || state.dev !== identity.dev || state.ino !== identity.ino) return { ...evidence, inspected: false, reason: 'object_identity_changed' };
|
|
204
|
-
const reported = typeof failure.path === 'string' ? path.relative(directory, path.resolve(failure.path)) : null;
|
|
205
|
-
let fileOwners: object | undefined;
|
|
206
|
-
if (failure.code === 'EBUSY' && primitive?.primitive === 'unlink' && reported && !reported.startsWith('..') && !path.isAbsolute(reported)
|
|
207
|
-
&& createHash('sha256').update(reported).digest('hex') === primitive.relativeObjectHash) {
|
|
208
|
-
try {
|
|
209
|
-
const file = path.join(directory, reported);
|
|
210
|
-
const current = lstatSync(file, { bigint: true });
|
|
211
|
-
const resolved = realpathSync(file);
|
|
212
|
-
if (!current.isFile() || current.isSymbolicLink() || current.dev.toString() !== primitive.objectDev
|
|
213
|
-
|| current.ino.toString() !== primitive.objectIno || path.relative(realpathSync(directory), resolved) !== reported) {
|
|
214
|
-
fileOwners = { available: false, reason: 'failed_object_identity_changed' };
|
|
215
|
-
} else {
|
|
216
|
-
fileOwners = probeOwners(file, 5_000, { dev: primitive.objectDev!, ino: primitive.objectIno! });
|
|
217
|
-
}
|
|
218
|
-
} catch (probeError) {
|
|
219
|
-
const code = (probeError as NodeJS.ErrnoException).code;
|
|
220
|
-
fileOwners = { available: false, reason: 'failed_object_unavailable', errorCode: ['ENOENT', 'EBUSY', 'EPERM', 'EACCES'].includes(code || '') ? code : 'filesystem_error' };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const entries: { nameHash: string; type: string }[] = [];
|
|
224
|
-
const listing = opendirSync(directory);
|
|
225
|
-
let entriesTruncated = false;
|
|
226
|
-
try {
|
|
227
|
-
while (entries.length < 16) {
|
|
228
|
-
const entry = listing.readSync();
|
|
229
|
-
if (!entry) break;
|
|
230
|
-
entries.push({ nameHash: createHash('sha256').update(entry.name).digest('hex'), type: entry.isSymbolicLink() ? 'link' : entry.isDirectory() ? 'directory' : entry.isFile() ? 'file' : 'other' });
|
|
231
|
-
}
|
|
232
|
-
entriesTruncated = entries.length === 16 && listing.readSync() !== null;
|
|
233
|
-
} finally { listing.closeSync(); }
|
|
234
|
-
return {
|
|
235
|
-
...evidence, inspected: true, directoryIdentityMatched: true, directoryMode: Number(state.mode),
|
|
236
|
-
reportedPath: reported === null ? 'unavailable' : reported === '' ? 'target' : reported.startsWith('..') || path.isAbsolute(reported) ? 'outside_target' : 'child',
|
|
237
|
-
reportedPathHash: reported !== null && !reported.startsWith('..') && !path.isAbsolute(reported) ? createHash('sha256').update(reported).digest('hex') : undefined,
|
|
238
|
-
entries, entriesTruncated, ...(fileOwners ? { fileOwners } : {}),
|
|
239
|
-
};
|
|
240
|
-
} catch (inspectionError) {
|
|
241
|
-
const code = (inspectionError as NodeJS.ErrnoException).code;
|
|
242
|
-
return { ...evidence, inspected: false, reason: 'inspection_failed', inspectionCode: ['ENOENT', 'EBUSY', 'EPERM', 'EACCES', 'ENOTDIR'].includes(code || '') ? code : 'filesystem_error' };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function clearOwnedFixtureContents(fixture: string, identity: { path: string; dev: bigint; ino: bigint }, listEntries: (directory: string) => string[] = readdirSync): void {
|
|
247
|
-
const before = lstatSync(fixture, { bigint: true });
|
|
248
|
-
if (before.isSymbolicLink() || realpathSync(fixture) !== identity.path || before.dev !== identity.dev || before.ino !== identity.ino) {
|
|
249
|
-
throw new Error('Native fixture root identity changed');
|
|
250
|
-
}
|
|
251
|
-
const ownerProbeDeadline = Date.now() + 5_000;
|
|
252
|
-
for (const entry of listEntries(fixture)) {
|
|
253
|
-
const child = path.join(fixture, entry);
|
|
254
|
-
try {
|
|
255
|
-
const state = lstatSync(child);
|
|
256
|
-
if (state.isSymbolicLink()) {
|
|
257
|
-
unlinkSync(child);
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
const relative = path.relative(identity.path, realpathSync(child));
|
|
261
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('Native fixture entry is outside its owned root');
|
|
262
|
-
const ownersBeforeDelete = state.isFile() && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\.tmp$/i.test(entry)
|
|
263
|
-
? Date.now() < ownerProbeDeadline ? fixtureFileOwners(child, Math.max(1, ownerProbeDeadline - Date.now())) : { available: false, reason: 'owner_probe_budget_exhausted' }
|
|
264
|
-
: undefined;
|
|
265
|
-
if (ownersBeforeDelete) console.log(JSON.stringify({ nativeFixtureBeforeDelete: { file: path.relative(resolvedRoot, child), owners: ownersBeforeDelete } }));
|
|
266
|
-
try { rmSync(child, { recursive: true, force: true }); }
|
|
267
|
-
catch (error) {
|
|
268
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
269
|
-
console.error(JSON.stringify({ nativeFixtureFileLock: {
|
|
270
|
-
file: path.relative(resolvedRoot, child), code: (error as NodeJS.ErrnoException).code, ownersBeforeDelete,
|
|
271
|
-
ownersAfterFailure: state.isFile() ? fixtureFileOwners(child) : undefined,
|
|
272
|
-
} }));
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue;
|
|
277
|
-
throw error;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const after = lstatSync(fixture, { bigint: true });
|
|
281
|
-
if (readdirSync(fixture).length !== 0 || realpathSync(fixture) !== identity.path || after.dev !== identity.dev || after.ino !== identity.ino) {
|
|
282
|
-
throw new Error('Native fixture contents reset did not preserve its empty root');
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function removeOwnedFixtureRootWithNode(directory: string, identity: { path: string; dev: bigint; ino: bigint }): void {
|
|
287
|
-
const node = Bun.which('node');
|
|
288
|
-
if (!node) throw new Error('Node is required for native fixture cleanup');
|
|
289
|
-
const result = spawnSync(node, [path.resolve(import.meta.dir, 'fixtures/native-cookie-remove-fixture.cjs'), Buffer.from(JSON.stringify({
|
|
290
|
-
root: directory, realpath: identity.path, dev: identity.dev.toString(), ino: identity.ino.toString(),
|
|
291
|
-
})).toString('base64')], { env: nativeCookieEnvironment(process.env), encoding: 'utf8', timeout: 5_000, windowsHide: true, maxBuffer: 65536 });
|
|
292
|
-
let receipt: { removed?: boolean; code?: string };
|
|
293
|
-
try { receipt = JSON.parse(result.stdout); }
|
|
294
|
-
catch { receipt = { removed: false }; }
|
|
295
|
-
if (result.status === 0 && receipt?.removed === true && !existsSync(directory)) return;
|
|
296
|
-
throw Object.assign(new Error('Native fixture root cleanup failed'), {
|
|
297
|
-
code: receipt?.code || 'EIO', syscall: 'rm', path: directory,
|
|
298
|
-
receipt: { ...receipt, exitCode: result.status },
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
afterAll(() => {
|
|
303
|
-
try {
|
|
304
|
-
if (existsSync(root) && realpathSync(root) !== resolvedRoot) throw new Error('Native fixture root ownership changed');
|
|
305
|
-
if (process.platform === 'win32') removeOwnedFixtureRootWithNode(root, { path: resolvedRoot, dev: initialRootState.dev, ino: initialRootState.ino });
|
|
306
|
-
else rmSync(root, { recursive: true, force: true });
|
|
307
|
-
} catch (error) {
|
|
308
|
-
console.error(JSON.stringify({ nativeFixtureRemovalFailure: { stage: 'after_all', ...fixtureRemovalEvidence(error, root, initialRootState) } }));
|
|
309
|
-
const entries: { path: string; type: string; mode?: number; code?: string }[] = [];
|
|
310
|
-
let rootVerified = false;
|
|
311
|
-
let rootMode: number | null = null;
|
|
312
|
-
try { rootVerified = realpathSync(root) === resolvedRoot; rootMode = lstatSync(root).mode; } catch {}
|
|
313
|
-
const pending = rootVerified ? [root] : [];
|
|
314
|
-
while (pending.length && entries.length < 128) {
|
|
315
|
-
const directory = pending.shift()!;
|
|
316
|
-
try {
|
|
317
|
-
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
318
|
-
if (entries.length >= 128) break;
|
|
319
|
-
const child = path.join(directory, entry.name);
|
|
320
|
-
const state = lstatSync(child);
|
|
321
|
-
entries.push({ path: path.relative(root, child), type: state.isSymbolicLink() ? 'link' : state.isDirectory() ? 'directory' : 'file', mode: state.mode });
|
|
322
|
-
if (state.isDirectory() && !state.isSymbolicLink() && realpathSync(child).startsWith(resolvedRoot + path.sep)) pending.push(child);
|
|
323
|
-
}
|
|
324
|
-
} catch (inspectionError) {
|
|
325
|
-
entries.push({ path: path.relative(root, directory), type: 'unreadable', code: (inspectionError as NodeJS.ErrnoException).code });
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
const lockedFile = entries.find(entry => entry.type === 'file' && /^[0-9a-f-]{36}\.tmp$/i.test(path.basename(entry.path)));
|
|
329
|
-
console.error(JSON.stringify({ nativeFixtureCleanup: { code: (error as NodeJS.ErrnoException).code, pendingChildCloses: fixtureChildren.size, rootVerified, rootMode, remaining: entries,
|
|
330
|
-
...(error && typeof error === 'object' && 'receipt' in error ? { nodeCleanup: error.receipt } : {}),
|
|
331
|
-
fileOwners: lockedFile ? { file: lockedFile.path, owners: fixtureFileOwners(path.join(root, lockedFile.path)) } : undefined,
|
|
332
|
-
} }));
|
|
333
|
-
throw error;
|
|
334
|
-
}
|
|
335
|
-
}, 15_000);
|
|
336
|
-
|
|
337
|
-
const request: NativeCookieRequest = {
|
|
338
|
-
nodeExecutable: 'C:\\fixture\\node.exe',
|
|
339
|
-
nodeArchitecture: process.arch,
|
|
340
|
-
playwrightEntry: 'C:\\fixture\\playwright.cjs',
|
|
341
|
-
executablePath: 'C:\\fixture\\msedge.exe',
|
|
342
|
-
userDataDir: 'C:\\fixture\\User Data',
|
|
343
|
-
profile: 'Default',
|
|
344
|
-
domains: ['example.test'],
|
|
345
|
-
deadline: 25_000,
|
|
346
|
-
qualifiedBunVersions: ['1.4.2'],
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
function kernelContract(mode: string) {
|
|
350
|
-
const script = `
|
|
351
|
-
const calls = [];
|
|
352
|
-
globalThis.__nativeCookieFfi = {
|
|
353
|
-
FFIType: { ptr: 'ptr', u64: 'u64', u32: 'u32', i32: 'i32' },
|
|
354
|
-
ptr: buffer => buffer,
|
|
355
|
-
dlopen: (name, signatures) => {
|
|
356
|
-
calls.push(['library', name, Object.keys(signatures)]);
|
|
357
|
-
return { close() { calls.push(['unload']); }, symbols: {
|
|
358
|
-
CreateJobObjectW: (security, name) => { calls.push(['create', security, name.toString('utf16le')]); return 42; },
|
|
359
|
-
OpenJobObjectW: (access, inherit, name) => { calls.push(['open', access, inherit, name.toString('utf16le')]); return 42; },
|
|
360
|
-
SetInformationJobObject: (handle, kind, buffer, length) => { calls.push(['limits', handle, kind, length, buffer.readUInt32LE(16)]); return 1; },
|
|
361
|
-
QueryInformationJobObject: (handle, kind, buffer, length) => { calls.push(['query', handle, kind, length]); buffer.writeUInt32LE(3, 40); return 1; },
|
|
362
|
-
OpenProcess: (access, inherit, pid) => { calls.push(['open-process', access, inherit, pid === process.pid]); return 999; },
|
|
363
|
-
AssignProcessToJobObject: (job, process) => { calls.push(['assign', job, process]); return ${mode === 'join-fail' ? 0 : 1}; },
|
|
364
|
-
TerminateJobObject: (job, code) => { calls.push(['terminate', job, code]); return 1; },
|
|
365
|
-
CloseHandle: handle => { calls.push(['close', handle]); return 1; },
|
|
366
|
-
GetLastError: () => ${mode === 'join-fail' ? 5 : 0},
|
|
367
|
-
} };
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
const source = await Bun.file(${JSON.stringify(path.resolve(import.meta.dir, '../src/cookie-import-native-job.ts'))}).text();
|
|
371
|
-
const boundary = "await import('bun:ffi')";
|
|
372
|
-
if (source.split(boundary).length !== 2) throw new Error('FFI adapter boundary changed');
|
|
373
|
-
const javascript = new Bun.Transpiler({ loader: 'ts' }).transformSync(source.replace(boundary, 'globalThis.__nativeCookieFfi'));
|
|
374
|
-
const { createNativeCookieJob, joinNativeCookieJob } = await import('data:text/javascript;base64,' + Buffer.from(javascript).toString('base64'));
|
|
375
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
376
|
-
if (${JSON.stringify(mode)} === 'create') {
|
|
377
|
-
const job = await createNativeCookieJob();
|
|
378
|
-
const active = job.activeProcesses();
|
|
379
|
-
job.terminate(); job.close(); job.close();
|
|
380
|
-
console.log(JSON.stringify({ name: job.name, active, calls }));
|
|
381
|
-
} else {
|
|
382
|
-
let error, diagnostic;
|
|
383
|
-
try { await joinNativeCookieJob('Local\\\\gstack-cookie-12345678-1234-1234-1234-123456789abc'); } catch (caught) { error = caught.message; diagnostic = caught.diagnostic; }
|
|
384
|
-
console.log(JSON.stringify({ calls, error, diagnostic }));
|
|
385
|
-
}
|
|
386
|
-
`;
|
|
387
|
-
const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-e', script], {
|
|
388
|
-
env: { TEMP: root, TMP: root, HOME: root, USERPROFILE: root, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
389
|
-
encoding: 'utf8',
|
|
390
|
-
timeout: 10_000,
|
|
391
|
-
});
|
|
392
|
-
expect(result.status).toBe(0);
|
|
393
|
-
expect(result.stderr).toBe('');
|
|
394
|
-
return JSON.parse(result.stdout);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
describe('production Windows Job Object API contract', () => {
|
|
398
|
-
test('diagnostics expose only known native stages and numeric Windows errors', () => {
|
|
399
|
-
expect(nativeCookieDiagnostic(new NativeCookieJobError('job_assign', 5), 'job_create')).toEqual({ stage: 'job_assign', win32Error: 5 });
|
|
400
|
-
expect(nativeCookieDiagnostic(new Error('sensitive-sentinel'), 'ffi_open')).toEqual({ stage: 'ffi_open' });
|
|
401
|
-
expect(parseNativeCookieDiagnostic({ stage: 'job_assign', win32Error: 87, message: 'sensitive-sentinel' })).toEqual({ stage: 'job_assign', win32Error: 87 });
|
|
402
|
-
expect(parseNativeCookieDiagnostic({ stage: 'sensitive-sentinel', win32Error: 5 })).toBeUndefined();
|
|
403
|
-
expect(parseNativeCookieDiagnostic({ stage: 'job_assign', win32Error: 'sensitive-sentinel' })).toEqual({ stage: 'job_assign' });
|
|
404
|
-
expect(parseNativeCookieDiagnostic({ stage: 'member_exit', lastStage: 'node_spawned', exitCode: 1, nodeExitCode: 1, signal: 'SIGTERM', memberMode: true, stderrBytes: 50 })).toEqual({ stage: 'member_exit', lastStage: 'node_spawned', exitCode: 1, nodeExitCode: 1, signal: 'SIGTERM', memberMode: true, stderrBytes: 50 });
|
|
405
|
-
expect(parseNativeCookieDiagnostic({ stage: 'member_exit', lastStage: 'sensitive-sentinel', signal: 'sensitive-sentinel', exitCode: 'sensitive-sentinel', memberMode: 'sensitive-sentinel', stderrBytes: -1 })).toEqual({ stage: 'member_exit' });
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test('owner creates a non-inheritable kill-on-close job and queries active members', () => {
|
|
409
|
-
const result = kernelContract('create');
|
|
410
|
-
expect(result.name).toMatch(/^Local\\gstack-cookie-[0-9a-f-]{36}$/);
|
|
411
|
-
expect(result.active).toBe(3);
|
|
412
|
-
expect(result.calls).toContainEqual(['create', null, `${result.name}\0`]);
|
|
413
|
-
expect(result.calls).toContainEqual(['limits', 42, 9, 144, 0x2000]);
|
|
414
|
-
expect(result.calls).toContainEqual(['query', 42, 1, 48]);
|
|
415
|
-
expect(result.calls).toContainEqual(['terminate', 42, 1]);
|
|
416
|
-
expect(result.calls.filter((call: unknown[]) => call[0] === 'close')).toEqual([['close', 42]]);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test('member assigns its own handle, never a discovered PID', () => {
|
|
420
|
-
const result = kernelContract('join');
|
|
421
|
-
expect(result.error).toBeUndefined();
|
|
422
|
-
expect(result.calls).toContainEqual(['open', 1, 0, 'Local\\gstack-cookie-12345678-1234-1234-1234-123456789abc\0']);
|
|
423
|
-
expect(result.calls).toContainEqual(['open-process', 0x0101, 0, true]);
|
|
424
|
-
expect(result.calls).toContainEqual(['assign', 42, 999]);
|
|
425
|
-
expect(result.calls).toContainEqual(['close', 999]);
|
|
426
|
-
expect(result.calls).toContainEqual(['close', 42]);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
test('failed self-assignment closes its handle and fails without a child launch', () => {
|
|
430
|
-
const result = kernelContract('join-fail');
|
|
431
|
-
expect(result.error).toBe('native_supervision_failed');
|
|
432
|
-
expect(result.diagnostic).toEqual({ stage: 'job_assign', win32Error: 5 });
|
|
433
|
-
expect(result.calls).toContainEqual(['close', 999]);
|
|
434
|
-
expect(result.calls).toContainEqual(['close', 42]);
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
function simulation(options: { reply?: NativeCookieReply; replyAt?: number; exitAt?: number; retainAfterTerminate?: boolean; signal?: AbortSignal; cancelAt?: () => void } = {}) {
|
|
439
|
-
let time = 0;
|
|
440
|
-
let active = 1;
|
|
441
|
-
let terminated = 0;
|
|
442
|
-
let stopped = 0;
|
|
443
|
-
let jobClosed = 0;
|
|
444
|
-
let started = 0;
|
|
445
|
-
let settleReply!: (reply: NativeCookieReply) => void;
|
|
446
|
-
let settleClose!: () => void;
|
|
447
|
-
const member: NativeCookieMember = {
|
|
448
|
-
result: new Promise(resolve => { settleReply = resolve; }),
|
|
449
|
-
closed: new Promise(resolve => { settleClose = resolve; }),
|
|
450
|
-
stop: () => { stopped++; },
|
|
451
|
-
};
|
|
452
|
-
const job: NativeCookieJob = {
|
|
453
|
-
name: 'synthetic-owned-job',
|
|
454
|
-
terminate: () => {
|
|
455
|
-
terminated++;
|
|
456
|
-
if (!options.retainAfterTerminate) { active = 0; settleClose(); }
|
|
457
|
-
},
|
|
458
|
-
activeProcesses: () => active,
|
|
459
|
-
close: () => { jobClosed++; },
|
|
460
|
-
};
|
|
461
|
-
const run = superviseNativeCookieImport(request, {
|
|
462
|
-
createJob: async () => job,
|
|
463
|
-
startMember: actual => { expect(actual.deadline).toBe(25_000); started++; return member; },
|
|
464
|
-
now: () => time,
|
|
465
|
-
sleep: async milliseconds => {
|
|
466
|
-
time += milliseconds;
|
|
467
|
-
if (options.reply && time >= (options.replyAt ?? 20)) settleReply(options.reply);
|
|
468
|
-
if (options.exitAt !== undefined && time >= options.exitAt) { active = 0; settleClose(); }
|
|
469
|
-
options.cancelAt?.();
|
|
470
|
-
},
|
|
471
|
-
signal: options.signal,
|
|
472
|
-
});
|
|
473
|
-
return { run, state: () => ({ time, active, terminated, stopped, jobClosed, started }) };
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
describe('owned native-cookie lifecycle', () => {
|
|
477
|
-
test('single-pass removal visits directories before removing them and never follows a leaf link', () => {
|
|
478
|
-
const fixture = mkdtempSync(path.join(root, 'single-pass-'));
|
|
479
|
-
const target = path.join(fixture, 'target');
|
|
480
|
-
const outside = path.join(fixture, 'outside');
|
|
481
|
-
mkdirSync(path.join(target, 'nested'), { recursive: true });
|
|
482
|
-
mkdirSync(outside);
|
|
483
|
-
writeFileSync(path.join(target, 'nested', 'file'), 'fixture-only');
|
|
484
|
-
writeFileSync(path.join(outside, 'preserved'), 'fixture-only');
|
|
485
|
-
symlinkSync(outside, path.join(target, 'link'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
486
|
-
const identity = lstatSync(target, { bigint: true });
|
|
487
|
-
const actions: string[] = [];
|
|
488
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
489
|
-
lstat: file => lstatSync(file, { bigint: true }),
|
|
490
|
-
enumerate: file => { actions.push(`enumerate:${path.relative(target, file)}`); return readdirSync(file); },
|
|
491
|
-
unlink: file => { actions.push(`unlink:${path.relative(target, file)}`); unlinkSync(file); },
|
|
492
|
-
rmdir: file => { actions.push(`rmdir:${path.relative(target, file)}`); rmdirSync(file); },
|
|
493
|
-
});
|
|
494
|
-
expect(existsSync(target)).toBe(false);
|
|
495
|
-
expect(readFileSync(path.join(outside, 'preserved'), 'utf8')).toBe('fixture-only');
|
|
496
|
-
expect(actions.filter(action => action.startsWith('enumerate:'))).toHaveLength(2);
|
|
497
|
-
expect(actions.filter(action => action.startsWith('unlink:'))).toHaveLength(2);
|
|
498
|
-
expect(actions.filter(action => action.startsWith('rmdir:'))).toHaveLength(2);
|
|
499
|
-
expect(actions.indexOf(`unlink:${path.join('nested', 'file')}`)).toBeLessThan(actions.indexOf('rmdir:nested'));
|
|
500
|
-
expect(actions.at(-1)).toBe('rmdir:');
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
test('single-pass removal preserves the first original primitive error and stops', () => {
|
|
504
|
-
for (const failedPrimitive of ['lstat', 'enumerate', 'unlink', 'rmdir']) {
|
|
505
|
-
const target = mkdtempSync(path.join(root, 'first-removal-error-'));
|
|
506
|
-
const identity = lstatSync(target, { bigint: true });
|
|
507
|
-
if (failedPrimitive !== 'rmdir') {
|
|
508
|
-
writeFileSync(path.join(target, 'first'), 'fixture-only');
|
|
509
|
-
writeFileSync(path.join(target, 'second'), 'fixture-only');
|
|
510
|
-
}
|
|
511
|
-
const original = Object.freeze(Object.assign(new Error('sensitive-sentinel'), { code: 'EBUSY', errno: -4082, syscall: failedPrimitive }));
|
|
512
|
-
const calls: string[] = [];
|
|
513
|
-
let caught: unknown;
|
|
514
|
-
try {
|
|
515
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
516
|
-
lstat: file => {
|
|
517
|
-
if (failedPrimitive === 'lstat' && file === path.join(target, 'first')) { calls.push('lstat'); throw original; }
|
|
518
|
-
return lstatSync(file, { bigint: true });
|
|
519
|
-
},
|
|
520
|
-
enumerate: file => { calls.push('enumerate'); if (failedPrimitive === 'enumerate') throw original; return failedPrimitive === 'rmdir' ? [] : ['first', 'second']; },
|
|
521
|
-
unlink: file => { calls.push('unlink'); throw original; },
|
|
522
|
-
rmdir: file => { calls.push('rmdir'); throw original; },
|
|
523
|
-
});
|
|
524
|
-
} catch (error) { caught = error; }
|
|
525
|
-
expect(caught).toBe(original);
|
|
526
|
-
expect(calls).toEqual(failedPrimitive === 'enumerate' ? ['enumerate'] : ['enumerate', failedPrimitive]);
|
|
527
|
-
const receipt = fixtureRemovalEvidence(caught, target, identity);
|
|
528
|
-
expect(receipt).toMatchObject({ primitive: failedPrimitive, rootIdentityMatched: true, objectIdentityMatched: failedPrimitive !== 'lstat', directoryIdentityMatched: true, relativeObjectHash: createHash('sha256').update(['lstat', 'unlink'].includes(failedPrimitive) ? 'first' : '').digest('hex') });
|
|
529
|
-
expect(JSON.stringify(receipt)).not.toContain('sensitive-sentinel');
|
|
530
|
-
if (failedPrimitive !== 'rmdir') {
|
|
531
|
-
expect(readFileSync(path.join(target, 'first'), 'utf8')).toBe('fixture-only');
|
|
532
|
-
expect(readFileSync(path.join(target, 'second'), 'utf8')).toBe('fixture-only');
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
test('single-pass removal accepts disappeared entries without repeating a removal', () => {
|
|
538
|
-
for (const stage of ['before_lstat', 'during_unlink', 'during_rmdir']) {
|
|
539
|
-
const fixture = mkdtempSync(path.join(root, 'removal-disappearing-'));
|
|
540
|
-
const target = path.join(fixture, 'target');
|
|
541
|
-
const vanished = path.join(target, 'first');
|
|
542
|
-
const moved = path.join(fixture, 'moved');
|
|
543
|
-
mkdirSync(target);
|
|
544
|
-
if (stage === 'during_rmdir') mkdirSync(vanished);
|
|
545
|
-
else writeFileSync(vanished, 'fixture-only');
|
|
546
|
-
writeFileSync(path.join(target, 'second'), 'fixture-only');
|
|
547
|
-
const identity = lstatSync(target, { bigint: true });
|
|
548
|
-
const vanishedIdentity = lstatSync(vanished, { bigint: true });
|
|
549
|
-
const removals: string[] = [];
|
|
550
|
-
const enumerated: string[] = [];
|
|
551
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
552
|
-
lstat: file => lstatSync(file, { bigint: true }),
|
|
553
|
-
enumerate: file => {
|
|
554
|
-
enumerated.push(file);
|
|
555
|
-
const entries = readdirSync(file).sort();
|
|
556
|
-
if (stage === 'before_lstat' && file === target) renameSync(vanished, moved);
|
|
557
|
-
return entries;
|
|
558
|
-
},
|
|
559
|
-
unlink: file => {
|
|
560
|
-
removals.push(file);
|
|
561
|
-
if (stage === 'during_unlink' && file === vanished) renameSync(vanished, moved);
|
|
562
|
-
unlinkSync(file);
|
|
563
|
-
},
|
|
564
|
-
rmdir: file => {
|
|
565
|
-
removals.push(file);
|
|
566
|
-
if (stage === 'during_rmdir' && file === vanished) renameSync(vanished, moved);
|
|
567
|
-
rmdirSync(file);
|
|
568
|
-
},
|
|
569
|
-
});
|
|
570
|
-
expect(existsSync(target)).toBe(false);
|
|
571
|
-
expect(new Set(removals).size).toBe(removals.length);
|
|
572
|
-
expect(new Set(enumerated).size).toBe(enumerated.length);
|
|
573
|
-
expect(removals.filter(file => file === vanished)).toHaveLength(stage === 'before_lstat' ? 0 : 1);
|
|
574
|
-
expect(lstatSync(moved, { bigint: true }).ino).toBe(vanishedIdentity.ino);
|
|
575
|
-
if (stage === 'during_rmdir') expect(readdirSync(moved)).toEqual([]);
|
|
576
|
-
else expect(readFileSync(moved, 'utf8')).toBe('fixture-only');
|
|
577
|
-
}
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test('single-pass removal never treats replacement, ancestor changes, or other errors as absence', () => {
|
|
581
|
-
for (const code of ['ENOENT', 'EBUSY', 'EPERM', 'EACCES', 'ancestor_replacement']) {
|
|
582
|
-
const fixture = mkdtempSync(path.join(root, 'removal-not-absent-'));
|
|
583
|
-
const target = path.join(fixture, 'target');
|
|
584
|
-
const first = path.join(target, 'first');
|
|
585
|
-
const moved = path.join(fixture, 'moved');
|
|
586
|
-
mkdirSync(target);
|
|
587
|
-
writeFileSync(first, 'fixture-only');
|
|
588
|
-
writeFileSync(path.join(target, 'second'), 'untouched-only');
|
|
589
|
-
const identity = lstatSync(target, { bigint: true });
|
|
590
|
-
const original = Object.freeze(Object.assign(new Error('synthetic-removal-error'), { code: code === 'ancestor_replacement' ? 'ENOENT' : code, syscall: 'unlink' }));
|
|
591
|
-
let calls = 0;
|
|
592
|
-
let caught: unknown;
|
|
593
|
-
try {
|
|
594
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
595
|
-
lstat: file => lstatSync(file, { bigint: true }),
|
|
596
|
-
enumerate: file => readdirSync(file).sort(),
|
|
597
|
-
unlink: file => {
|
|
598
|
-
calls++;
|
|
599
|
-
if (code === 'ENOENT') {
|
|
600
|
-
renameSync(file, moved);
|
|
601
|
-
writeFileSync(file, 'replacement-only');
|
|
602
|
-
} else if (code === 'ancestor_replacement') {
|
|
603
|
-
renameSync(target, moved);
|
|
604
|
-
mkdirSync(target);
|
|
605
|
-
writeFileSync(first, 'replacement-only');
|
|
606
|
-
}
|
|
607
|
-
throw original;
|
|
608
|
-
},
|
|
609
|
-
rmdir: file => rmdirSync(file),
|
|
610
|
-
});
|
|
611
|
-
} catch (error) { caught = error; }
|
|
612
|
-
expect(calls).toBe(1);
|
|
613
|
-
if (code === 'ancestor_replacement') {
|
|
614
|
-
expect(caught).toBeInstanceOf(Error);
|
|
615
|
-
expect((caught as Error).message).toContain('ancestor identity changed');
|
|
616
|
-
expect(fixturePrimitiveFailures.get(caught as object)).toMatchObject({ primitive: 'identity', objectIdentityMatched: false });
|
|
617
|
-
expect(readFileSync(path.join(moved, 'second'), 'utf8')).toBe('untouched-only');
|
|
618
|
-
} else {
|
|
619
|
-
expect(caught).toBe(original);
|
|
620
|
-
expect(fixturePrimitiveFailures.get(caught as object)).toMatchObject({ primitive: 'unlink', relativeObjectHash: createHash('sha256').update('first').digest('hex') });
|
|
621
|
-
expect(readFileSync(path.join(target, 'second'), 'utf8')).toBe('untouched-only');
|
|
622
|
-
}
|
|
623
|
-
expect(readFileSync(first, 'utf8')).toBe(code === 'ENOENT' || code === 'ancestor_replacement' ? 'replacement-only' : 'fixture-only');
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
test('single-pass removal rejects changed identities and linked ancestors before touching their contents', () => {
|
|
628
|
-
const fixture = mkdtempSync(path.join(root, 'removal-identity-'));
|
|
629
|
-
const target = path.join(fixture, 'target');
|
|
630
|
-
mkdirSync(target);
|
|
631
|
-
writeFileSync(path.join(target, 'preserved'), 'fixture-only');
|
|
632
|
-
const identity = lstatSync(target, { bigint: true });
|
|
633
|
-
expect(() => removeOwnedFixtureDirectory(target, { dev: identity.dev, ino: identity.ino + 1n })).toThrow('directory identity changed');
|
|
634
|
-
expect(() => removeOwnedFixtureDirectory(path.dirname(root), identity)).toThrow('stay below its owned root');
|
|
635
|
-
const link = path.join(fixture, 'link');
|
|
636
|
-
symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir');
|
|
637
|
-
expect(() => removeOwnedFixtureDirectory(path.join(link, 'preserved'), identity)).toThrow('ancestor is not an owned directory');
|
|
638
|
-
unlinkSync(link);
|
|
639
|
-
const moved = path.join(fixture, 'moved');
|
|
640
|
-
let caught: unknown;
|
|
641
|
-
try {
|
|
642
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
643
|
-
lstat: file => lstatSync(file, { bigint: true }),
|
|
644
|
-
enumerate: file => {
|
|
645
|
-
const entries = readdirSync(file);
|
|
646
|
-
renameSync(target, moved);
|
|
647
|
-
mkdirSync(target);
|
|
648
|
-
writeFileSync(path.join(target, 'preserved'), 'replacement-only');
|
|
649
|
-
return entries;
|
|
650
|
-
},
|
|
651
|
-
unlink: file => unlinkSync(file),
|
|
652
|
-
rmdir: file => rmdirSync(file),
|
|
653
|
-
});
|
|
654
|
-
} catch (error) { caught = error; }
|
|
655
|
-
expect(caught).toBeInstanceOf(Error);
|
|
656
|
-
expect(fixturePrimitiveFailures.get(caught as object)).toMatchObject({ primitive: 'identity', objectScope: 'target', objectIdentityMatched: false });
|
|
657
|
-
expect(readFileSync(path.join(moved, 'preserved'), 'utf8')).toBe('fixture-only');
|
|
658
|
-
expect(readFileSync(path.join(target, 'preserved'), 'utf8')).toBe('replacement-only');
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
test('removal diagnostics preserve errors and inspect only the unchanged synthetic directory', () => {
|
|
662
|
-
const fixture = mkdtempSync(path.join(root, 'removal-evidence-'));
|
|
663
|
-
const identity = lstatSync(fixture, { bigint: true });
|
|
664
|
-
writeFileSync(path.join(fixture, 'sensitive-sentinel'), 'fixture-only');
|
|
665
|
-
const error = Object.assign(new Error('sensitive-sentinel'), { code: 'EBUSY', syscall: 'rm', errno: -4082, path: fixture });
|
|
666
|
-
const evidence = fixtureRemovalEvidence(error, fixture, identity);
|
|
667
|
-
expect(evidence).toMatchObject({ code: 'EBUSY', syscall: 'rm', errno: -4082, inspected: true, directoryIdentityMatched: true, reportedPath: 'target', entries: [{ type: 'file' }] });
|
|
668
|
-
expect(JSON.stringify(evidence)).not.toContain('sensitive-sentinel');
|
|
669
|
-
expect(JSON.stringify(evidence)).not.toContain(fixture);
|
|
670
|
-
expect(readFileSync(path.join(fixture, 'sensitive-sentinel'), 'utf8')).toBe('fixture-only');
|
|
671
|
-
expect(error.path).toBe(fixture);
|
|
672
|
-
expect(fixtureRemovalEvidence(error, fixture, { dev: identity.dev, ino: identity.ino + 1n })).toMatchObject({ inspected: false, reason: 'object_identity_changed' });
|
|
673
|
-
expect(fixtureRemovalEvidence(error, path.dirname(root), identity)).toMatchObject({ inspected: false, reason: 'outside_owned_fixture' });
|
|
674
|
-
const missing = path.join(fixture, 'missing');
|
|
675
|
-
expect(fixtureRemovalEvidence(error, missing, identity)).toMatchObject({ inspected: false, inspectionCode: 'ENOENT' });
|
|
676
|
-
const link = path.join(root, 'removal-evidence-link');
|
|
677
|
-
symlinkSync(fixture, link, process.platform === 'win32' ? 'junction' : 'dir');
|
|
678
|
-
expect(fixtureRemovalEvidence(error, link, identity)).toMatchObject({ inspected: false, reason: 'linked_object' });
|
|
679
|
-
unlinkSync(link);
|
|
680
|
-
for (let index = 0; index < 20; index++) writeFileSync(path.join(fixture, String(index)), 'fixture-only');
|
|
681
|
-
const bounded = fixtureRemovalEvidence(error, fixture, identity) as { entries: unknown[]; entriesTruncated: boolean };
|
|
682
|
-
expect(bounded.entries).toHaveLength(16);
|
|
683
|
-
expect(bounded.entriesTruncated).toBe(true);
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
test.each(['unchanged', 'replaced', 'linked', 'missing', 'wrong_path'])('a leaf lock probe is bound to the exact failed object: %s', state => {
|
|
687
|
-
const fixture = mkdtempSync(path.join(root, 'leaf-owner-'));
|
|
688
|
-
const target = path.join(fixture, 'target');
|
|
689
|
-
const nested = path.join(target, 'nested');
|
|
690
|
-
mkdirSync(nested, { recursive: true });
|
|
691
|
-
const file = path.join(nested, 'sensitive-sentinel.sqlite');
|
|
692
|
-
writeFileSync(file, 'fixture-only');
|
|
693
|
-
const identity = lstatSync(target, { bigint: true });
|
|
694
|
-
const fileIdentity = lstatSync(file, { bigint: true });
|
|
695
|
-
const original = Object.assign(new Error('sensitive-sentinel'), { code: 'EBUSY', syscall: 'unlink', errno: -4082, path: file });
|
|
696
|
-
let removals = 0;
|
|
697
|
-
let caught: unknown;
|
|
698
|
-
try {
|
|
699
|
-
removeOwnedFixtureDirectory(target, identity, {
|
|
700
|
-
lstat: candidate => lstatSync(candidate, { bigint: true }), enumerate: candidate => readdirSync(candidate),
|
|
701
|
-
unlink: candidate => { expect(candidate).toBe(file); removals++; throw original; },
|
|
702
|
-
rmdir: () => { throw new Error('Must stop at the first failure'); },
|
|
703
|
-
});
|
|
704
|
-
} catch (error) { caught = error; }
|
|
705
|
-
expect(caught).toBe(original);
|
|
706
|
-
if (state === 'replaced') {
|
|
707
|
-
renameSync(file, path.join(fixture, 'original'));
|
|
708
|
-
writeFileSync(file, 'replacement-only');
|
|
709
|
-
} else if (state === 'linked') {
|
|
710
|
-
const moved = path.join(fixture, 'moved');
|
|
711
|
-
renameSync(nested, moved);
|
|
712
|
-
symlinkSync(moved, nested, process.platform === 'win32' ? 'junction' : 'dir');
|
|
713
|
-
} else if (state === 'missing') unlinkSync(file);
|
|
714
|
-
else if (state === 'wrong_path') original.path = path.join(fixture, 'elsewhere');
|
|
715
|
-
const queried: string[] = [];
|
|
716
|
-
const owners = { available: true, owners: [{ pid: 123, image: 'bun.exe', creationMatched: true, isTestHost: true }] };
|
|
717
|
-
const receipt = fixtureRemovalEvidence(caught, target, identity, (candidate, timeout, expectedIdentity) => {
|
|
718
|
-
queried.push(candidate);
|
|
719
|
-
expect(timeout).toBe(5_000);
|
|
720
|
-
expect(expectedIdentity).toEqual({ dev: fileIdentity.dev.toString(), ino: fileIdentity.ino.toString() });
|
|
721
|
-
return owners;
|
|
722
|
-
});
|
|
723
|
-
expect(receipt).toMatchObject({ code: 'EBUSY', primitive: 'unlink', objectDev: fileIdentity.dev.toString(), objectIno: fileIdentity.ino.toString() });
|
|
724
|
-
expect(queried).toEqual(state === 'unchanged' ? [file] : []);
|
|
725
|
-
if (state === 'unchanged') {
|
|
726
|
-
expect(receipt).toMatchObject({ fileOwners: owners });
|
|
727
|
-
expect(readFileSync(file, 'utf8')).toBe('fixture-only');
|
|
728
|
-
} else if (state !== 'wrong_path') expect(receipt).toMatchObject({ fileOwners: { available: false } });
|
|
729
|
-
else expect(receipt).not.toHaveProperty('fileOwners');
|
|
730
|
-
expect(removals).toBe(1);
|
|
731
|
-
expect(JSON.stringify(receipt)).not.toContain('sensitive-sentinel');
|
|
732
|
-
expect(JSON.stringify(receipt)).not.toContain(fixture);
|
|
733
|
-
expect(caught).toBe(original);
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
test('positive fixture environments create matching Windows known-folder directories', () => {
|
|
737
|
-
const fixture = mkdtempSync(path.join(root, 'environment-'));
|
|
738
|
-
const node = Bun.which('node');
|
|
739
|
-
if (!node) throw new Error('Node is required for fixture setup');
|
|
740
|
-
const environment = nativeFixtureEnvironment(fixture, node);
|
|
741
|
-
expect(environment.USERPROFILE).toBe(fixture);
|
|
742
|
-
expect(environment.LOCALAPPDATA).toBe(path.join(fixture, 'AppData', 'Local'));
|
|
743
|
-
expect(environment.APPDATA).toBe(path.join(fixture, 'AppData', 'Roaming'));
|
|
744
|
-
for (const name of ['LOCALAPPDATA', 'APPDATA', 'TEMP']) expect(lstatSync(environment[name]).isDirectory()).toBe(true);
|
|
745
|
-
expect(environment.TMP).toBe(environment.TEMP);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
test('a contents reset preserves an existing root even while an owned process holds it', async () => {
|
|
749
|
-
const fixture = mkdtempSync(path.join(root, 'held-root-'));
|
|
750
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
751
|
-
const identity = { path: realpathSync(fixture), dev: state.dev, ino: state.ino };
|
|
752
|
-
const node = Bun.which('node');
|
|
753
|
-
if (!node) throw new Error('Node is required for root ownership verification');
|
|
754
|
-
const child = ownFixtureChild(spawn(node, ['-e', 'process.stdout.write("ready"); setInterval(() => {}, 1000)'], {
|
|
755
|
-
cwd: fixture,
|
|
756
|
-
env: { PATH: path.dirname(node), ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
757
|
-
stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
|
|
758
|
-
}));
|
|
759
|
-
const closed = new Promise<void>(resolve => child.once('close', () => resolve()));
|
|
760
|
-
try {
|
|
761
|
-
await once(child.stdout, 'data', { signal: AbortSignal.timeout(5_000) });
|
|
762
|
-
if (process.platform === 'win32') expect(() => rmdirSync(fixture)).toThrow();
|
|
763
|
-
mkdirSync(path.join(fixture, 'AppData', 'Local'), { recursive: true });
|
|
764
|
-
writeFileSync(path.join(fixture, 'AppData', 'Local', 'synthetic'), 'fixture-only');
|
|
765
|
-
writeFileSync(path.join(fixture, 'wrapper.cjs'), 'fixture-only');
|
|
766
|
-
clearOwnedFixtureContents(fixture, identity);
|
|
767
|
-
expect(readdirSync(fixture)).toEqual([]);
|
|
768
|
-
const after = lstatSync(fixture, { bigint: true });
|
|
769
|
-
expect(after.dev).toBe(identity.dev);
|
|
770
|
-
expect(after.ino).toBe(identity.ino);
|
|
771
|
-
expect(alive(child.pid!)).toBe(true);
|
|
772
|
-
} finally {
|
|
773
|
-
child.kill();
|
|
774
|
-
await closed;
|
|
775
|
-
}
|
|
776
|
-
}, 10_000);
|
|
777
|
-
|
|
778
|
-
test('a contents reset rejects a different root identity without removing entries', () => {
|
|
779
|
-
const fixture = mkdtempSync(path.join(root, 'identity-'));
|
|
780
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
781
|
-
const marker = path.join(fixture, 'preserved');
|
|
782
|
-
writeFileSync(marker, 'fixture-only');
|
|
783
|
-
expect(() => clearOwnedFixtureContents(fixture, { path: realpathSync(fixture), dev: state.dev, ino: state.ino + 1n })).toThrow('identity changed');
|
|
784
|
-
expect(readFileSync(marker, 'utf8')).toBe('fixture-only');
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
test('a contents reset accepts an entry that disappears after enumeration without retrying it', () => {
|
|
788
|
-
const fixture = mkdtempSync(path.join(root, 'disappearing-entry-'));
|
|
789
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
790
|
-
const identity = { path: realpathSync(fixture), dev: state.dev, ino: state.ino };
|
|
791
|
-
const disappearing = path.join(fixture, '00000000-0000-0000-0000-000000000000.tmp');
|
|
792
|
-
writeFileSync(disappearing, 'fixture-only');
|
|
793
|
-
writeFileSync(path.join(fixture, 'remaining'), 'fixture-only');
|
|
794
|
-
let listings = 0;
|
|
795
|
-
clearOwnedFixtureContents(fixture, identity, directory => {
|
|
796
|
-
listings++;
|
|
797
|
-
const entries = readdirSync(directory);
|
|
798
|
-
unlinkSync(disappearing);
|
|
799
|
-
return entries;
|
|
800
|
-
});
|
|
801
|
-
expect(listings).toBe(1);
|
|
802
|
-
expect(readdirSync(fixture)).toEqual([]);
|
|
803
|
-
const after = lstatSync(fixture, { bigint: true });
|
|
804
|
-
expect(after.dev).toBe(identity.dev);
|
|
805
|
-
expect(after.ino).toBe(identity.ino);
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
test('a contents reset still rejects a nonempty postcondition', () => {
|
|
809
|
-
const fixture = mkdtempSync(path.join(root, 'nonempty-reset-'));
|
|
810
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
811
|
-
writeFileSync(path.join(fixture, 'preserved'), 'fixture-only');
|
|
812
|
-
expect(() => clearOwnedFixtureContents(fixture, { path: realpathSync(fixture), dev: state.dev, ino: state.ino }, () => [])).toThrow('empty root');
|
|
813
|
-
expect(readFileSync(path.join(fixture, 'preserved'), 'utf8')).toBe('fixture-only');
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
test('the Node cleanup comparison refuses changed identity and removes only its owned fixture', () => {
|
|
817
|
-
const node = Bun.which('node');
|
|
818
|
-
if (!node) throw new Error('Node is required for cleanup comparison');
|
|
819
|
-
const fixture = mkdtempSync(path.join(root, 'node-cleanup-'));
|
|
820
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
821
|
-
const marker = path.join(fixture, 'preserved');
|
|
822
|
-
writeFileSync(marker, 'fixture-only');
|
|
823
|
-
const invoke = (ino: bigint) => spawnSync(node, [path.resolve(import.meta.dir, 'fixtures/native-cookie-remove-fixture.cjs'), Buffer.from(JSON.stringify({
|
|
824
|
-
root: fixture, realpath: realpathSync(fixture), dev: state.dev.toString(), ino: ino.toString(),
|
|
825
|
-
})).toString('base64')], { env: nativeCookieEnvironment(process.env), encoding: 'utf8', timeout: 5_000, windowsHide: true });
|
|
826
|
-
const refused = invoke(state.ino + 1n);
|
|
827
|
-
expect(refused.status).toBe(1);
|
|
828
|
-
expect(JSON.parse(refused.stdout).reason).toBe('identity_mismatch');
|
|
829
|
-
expect(readFileSync(marker, 'utf8')).toBe('fixture-only');
|
|
830
|
-
const removed = invoke(state.ino);
|
|
831
|
-
expect(removed.status).toBe(0);
|
|
832
|
-
expect(JSON.parse(removed.stdout).removed).toBe(true);
|
|
833
|
-
expect(existsSync(fixture)).toBe(false);
|
|
834
|
-
expect(existsSync(root)).toBe(true);
|
|
835
|
-
}, 15_000);
|
|
836
|
-
|
|
837
|
-
test('native root teardown rejects a changed identity and removes nested owned contents', () => {
|
|
838
|
-
const fixture = mkdtempSync(path.join(root, 'root-cleanup-'));
|
|
839
|
-
const state = lstatSync(fixture, { bigint: true });
|
|
840
|
-
const identity = { path: realpathSync(fixture), dev: state.dev, ino: state.ino };
|
|
841
|
-
const marker = path.join(fixture, 'nested', 'marker');
|
|
842
|
-
mkdirSync(path.dirname(marker));
|
|
843
|
-
writeFileSync(marker, 'fixture-only');
|
|
844
|
-
expect(() => removeOwnedFixtureRootWithNode(fixture, { ...identity, ino: identity.ino + 1n })).toThrow('Native fixture root cleanup failed');
|
|
845
|
-
expect(readFileSync(marker, 'utf8')).toBe('fixture-only');
|
|
846
|
-
removeOwnedFixtureRootWithNode(fixture, identity);
|
|
847
|
-
expect(existsSync(fixture)).toBe(false);
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
test.skipIf(process.platform !== 'win32')('native root teardown refuses a real delete-sharing lock until its owner closes', () => {
|
|
851
|
-
const fixture = mkdtempSync(path.join(root, 'root-lock-'));
|
|
852
|
-
const identity = lstatSync(fixture, { bigint: true });
|
|
853
|
-
const file = path.join(fixture, 'held.tmp');
|
|
854
|
-
writeFileSync(file, 'fixture-only');
|
|
855
|
-
const kernel = dlopen('kernel32.dll', {
|
|
856
|
-
CreateFileW: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u64], returns: FFIType.u64 },
|
|
857
|
-
CloseHandle: { args: [FFIType.u64], returns: FFIType.i32 },
|
|
858
|
-
});
|
|
859
|
-
const name = Buffer.from(file + '\0', 'utf16le');
|
|
860
|
-
const handle = kernel.symbols.CreateFileW(ptr(name), 0x80000000, 3, null, 3, 0x80, 0);
|
|
861
|
-
try {
|
|
862
|
-
expect(BigInt(handle)).not.toBe(0xffffffffffffffffn);
|
|
863
|
-
expect(BigInt(handle)).not.toBe(0n);
|
|
864
|
-
expect(() => removeOwnedFixtureRootWithNode(fixture, { path: realpathSync(fixture), dev: identity.dev, ino: identity.ino })).toThrow('Native fixture root cleanup failed');
|
|
865
|
-
expect(readFileSync(file, 'utf8')).toBe('fixture-only');
|
|
866
|
-
} finally {
|
|
867
|
-
try {
|
|
868
|
-
if (BigInt(handle) !== 0xffffffffffffffffn && BigInt(handle) !== 0n) expect(kernel.symbols.CloseHandle(handle)).toBe(1);
|
|
869
|
-
} finally { kernel.close(); }
|
|
870
|
-
}
|
|
871
|
-
removeOwnedFixtureRootWithNode(fixture, { path: realpathSync(fixture), dev: identity.dev, ino: identity.ino });
|
|
872
|
-
expect(existsSync(fixture)).toBe(false);
|
|
873
|
-
}, 15_000);
|
|
874
|
-
|
|
875
|
-
test('success is withheld until the entire job is empty and member exits', async () => {
|
|
876
|
-
const run = simulation({ reply: { cookies: [] }, exitAt: 200 });
|
|
877
|
-
expect(await run.run).toEqual({ cookies: [] });
|
|
878
|
-
expect(run.state()).toMatchObject({ time: 200, active: 0, terminated: 0, stopped: 1, jobClosed: 1, started: 1 });
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
test('a stuck graceful close is forcibly cleaned after two seconds', async () => {
|
|
882
|
-
const run = simulation({ reply: { cookies: [] } });
|
|
883
|
-
expect(await run.run).toEqual({ cookies: [] });
|
|
884
|
-
expect(run.state()).toMatchObject({ active: 0, terminated: 1, jobClosed: 1, started: 1 });
|
|
885
|
-
expect(run.state().time).toBeLessThan(5_000);
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
test('launch/read timeout gets one 25s operation budget and owned cleanup', async () => {
|
|
889
|
-
const run = simulation();
|
|
890
|
-
expect(await run.run).toEqual({ error: 'native_timeout' });
|
|
891
|
-
expect(run.state()).toMatchObject({ active: 0, terminated: 1, jobClosed: 1, started: 1 });
|
|
892
|
-
expect(run.state().time).toBeGreaterThanOrEqual(25_000);
|
|
893
|
-
expect(run.state().time).toBeLessThan(30_000);
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
test.each(['failure', 'success', 'rejection'])('a late member %s cannot overwrite the timeout selected before cleanup', async mode => {
|
|
897
|
-
let now = 0;
|
|
898
|
-
let active = 1;
|
|
899
|
-
let resolveReply!: (reply: NativeCookieReply) => void;
|
|
900
|
-
let rejectReply!: (error: Error) => void;
|
|
901
|
-
let resolveClosed!: () => void;
|
|
902
|
-
const reply = new Promise<NativeCookieReply>((resolve, reject) => { resolveReply = resolve; rejectReply = reject; });
|
|
903
|
-
const closed = new Promise<void>(resolve => { resolveClosed = resolve; });
|
|
904
|
-
const result = await superviseNativeCookieImport(request, {
|
|
905
|
-
now: () => now,
|
|
906
|
-
sleep: async milliseconds => { now += milliseconds; },
|
|
907
|
-
createJob: async () => ({
|
|
908
|
-
name: 'synthetic-owned-job', activeProcesses: () => active, close() {},
|
|
909
|
-
terminate() {
|
|
910
|
-
expect(now).toBe(25_000);
|
|
911
|
-
active = 0;
|
|
912
|
-
if (mode === 'rejection') rejectReply(new Error('synthetic late rejection'));
|
|
913
|
-
else resolveReply(mode === 'success' ? { cookies: [] } : { error: 'native_failed', diagnostic: { stage: 'member_exit' } });
|
|
914
|
-
resolveClosed();
|
|
915
|
-
},
|
|
916
|
-
}),
|
|
917
|
-
startMember: () => ({ result: reply, closed, stop() {} }),
|
|
918
|
-
});
|
|
919
|
-
expect(result).toEqual({ error: 'native_timeout' });
|
|
920
|
-
expect(active).toBe(0);
|
|
921
|
-
expect(now).toBeLessThan(30_000);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
test('unconfirmed termination is reported as cleanup failure, never success', async () => {
|
|
925
|
-
const run = simulation({ retainAfterTerminate: true });
|
|
926
|
-
expect(await run.run).toEqual({ error: 'native_cleanup_failed' });
|
|
927
|
-
expect(run.state()).toMatchObject({ time: 30_000, terminated: 1, stopped: 1, jobClosed: 1 });
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
test('a process crash cannot strand remaining members', async () => {
|
|
931
|
-
let terminated = 0;
|
|
932
|
-
let active = 1;
|
|
933
|
-
const result = await superviseNativeCookieImport(request, {
|
|
934
|
-
now: () => 0,
|
|
935
|
-
sleep: async () => {},
|
|
936
|
-
createJob: async () => ({ name: 'synthetic', activeProcesses: () => active, terminate: () => { active = 0; terminated++; }, close() {} }),
|
|
937
|
-
startMember: () => ({ result: new Promise(() => {}), closed: Promise.resolve(), stop() {} }),
|
|
938
|
-
});
|
|
939
|
-
expect(result).toEqual({ error: 'native_failed' });
|
|
940
|
-
expect(terminated).toBe(1);
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
test('a failed job close still stops its owned member and cannot report success', async () => {
|
|
944
|
-
let stopped = false;
|
|
945
|
-
const result = await superviseNativeCookieImport(request, {
|
|
946
|
-
now: () => 0,
|
|
947
|
-
sleep: async () => {},
|
|
948
|
-
createJob: async () => ({ name: 'synthetic', activeProcesses: () => 0, terminate() {}, close() { throw new Error('sensitive-close-detail'); } }),
|
|
949
|
-
startMember: () => ({ result: Promise.resolve({ cookies: [] }), closed: Promise.resolve(), stop() { stopped = true; } }),
|
|
950
|
-
});
|
|
951
|
-
expect(result).toEqual({ error: 'native_cleanup_failed', diagnostic: { stage: 'job_close' } });
|
|
952
|
-
expect(stopped).toBe(true);
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
test('locked-profile errors are classified and never retried', async () => {
|
|
956
|
-
const run = simulation({ reply: { error: 'browser_running' } });
|
|
957
|
-
expect(await run.run).toEqual({ error: 'browser_running' });
|
|
958
|
-
expect(run.state()).toMatchObject({ active: 0, terminated: 1, started: 1 });
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
test('loss of the parent channel cancels the operation and cleans the job', async () => {
|
|
962
|
-
const cancellation = new AbortController();
|
|
963
|
-
const run = simulation({ signal: cancellation.signal, cancelAt: () => cancellation.abort() });
|
|
964
|
-
expect(await run.run).toEqual({ error: 'native_failed' });
|
|
965
|
-
expect(run.state()).toMatchObject({ active: 0, terminated: 1, started: 1 });
|
|
966
|
-
expect(run.state().time).toBeLessThan(5_000);
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
test('job initialization failure dispatches no child and hides native errors', async () => {
|
|
970
|
-
let started = false;
|
|
971
|
-
const result = await superviseNativeCookieImport(request, {
|
|
972
|
-
createJob: async () => { throw new Error('sensitive-sentinel'); },
|
|
973
|
-
startMember: () => { started = true; throw new Error('unexpected'); },
|
|
974
|
-
});
|
|
975
|
-
expect(result).toEqual({ error: 'native_supervision_failed', diagnostic: { stage: 'job_create' } });
|
|
976
|
-
expect(started).toBe(false);
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
test.skipIf(process.platform === 'win32')('non-Windows job API fails before any process is launched', async () => {
|
|
980
|
-
await expect(createNativeCookieJob()).rejects.toThrow('native_supervision_unavailable');
|
|
981
|
-
await expect(joinNativeCookieJob('invalid')).rejects.toThrow('native_supervision_failed');
|
|
982
|
-
});
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
function alive(pid: number): boolean {
|
|
986
|
-
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function safeNativeEnvelope(output: string): object {
|
|
990
|
-
try {
|
|
991
|
-
const parsed = JSON.parse(output);
|
|
992
|
-
if (Array.isArray(parsed.cookies)) return { cookiesRead: parsed.cookies.length };
|
|
993
|
-
const errors = ['native_timeout', 'native_failed', 'native_cleanup_failed', 'native_supervision_failed', 'browser_running', 'native_profile_unsupported'];
|
|
994
|
-
return { error: errors.includes(parsed.error) ? parsed.error : 'unexpected_reply', diagnostic: parseNativeCookieDiagnostic(parsed.diagnostic) };
|
|
995
|
-
} catch {
|
|
996
|
-
return { error: 'no_complete_reply' };
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
function assertOwnerReceipt(receipt: { available: boolean; owners: { pid: number; creationMatched: boolean; isTestHost: boolean }[] }, pid: number) {
|
|
1001
|
-
const before = JSON.stringify(receipt);
|
|
1002
|
-
expect(receipt.available).toBe(true);
|
|
1003
|
-
expect(Array.isArray(receipt.owners)).toBe(true);
|
|
1004
|
-
expect(receipt.owners.some(owner => owner.pid === pid && owner.creationMatched && owner.isTestHost)).toBe(true);
|
|
1005
|
-
expect(JSON.stringify(receipt)).toBe(before);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
function assertCookieReceipt(receipt: NativeCookieReply, evidence: object = {}) {
|
|
1009
|
-
if (!('cookies' in receipt) || !Array.isArray(receipt.cookies)) {
|
|
1010
|
-
throw new Error(JSON.stringify({ nativeCookieReceipt: safeNativeEnvelope(JSON.stringify(receipt)), ...evidence }));
|
|
1011
|
-
}
|
|
1012
|
-
return receipt.cookies;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function safeLaunchEvidence(file: string): object {
|
|
1016
|
-
try {
|
|
1017
|
-
const observed = JSON.parse(readFileSync(file, 'utf8'));
|
|
1018
|
-
return {
|
|
1019
|
-
spawned: Number.isInteger(observed.pid), pipe: observed.args?.includes('--remote-debugging-pipe'),
|
|
1020
|
-
argsHash: observed.argsHash, envHash: observed.envHash,
|
|
1021
|
-
reasons: observed.reasons, stderrBytes: observed.stderrBytes,
|
|
1022
|
-
exitCode: observed.exitCode, signal: observed.signal, spawnError: observed.spawnError,
|
|
1023
|
-
runtime: observed.runtime, observedCommandLine: observed.observedCommandLine,
|
|
1024
|
-
folderEvidence: observed.folderEvidence,
|
|
1025
|
-
};
|
|
1026
|
-
} catch {
|
|
1027
|
-
return { spawned: false };
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
function nativeSupervisor(input: NativeCookieRequest, env: NodeJS.ProcessEnv) {
|
|
1032
|
-
const cleanupDeadline = performance.now() + 30_000;
|
|
1033
|
-
const child = ownFixtureChild(spawn(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, '../src/cookie-import-native-worker.ts')], { env, stdio: ['pipe', 'pipe', 'ignore'], windowsHide: true }));
|
|
1034
|
-
let output = '';
|
|
1035
|
-
child.stdout.on('data', chunk => { output += chunk; });
|
|
1036
|
-
const timer = setTimeout(() => child.kill(), 30_000);
|
|
1037
|
-
const done = new Promise<NativeCookieReply>((resolve, reject) => {
|
|
1038
|
-
child.once('error', reject);
|
|
1039
|
-
child.once('close', () => {
|
|
1040
|
-
clearTimeout(timer);
|
|
1041
|
-
try { resolve(JSON.parse(output)); } catch { reject(new Error('Native supervisor did not return a complete receipt')); }
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
void done.catch(() => {});
|
|
1045
|
-
child.stdin.on('error', () => {});
|
|
1046
|
-
child.stdin.write(JSON.stringify({ ...input, deadline: Date.now() + 25_000, qualifiedBunVersions: [Bun.version] }) + '\n');
|
|
1047
|
-
return { child, done, cleanupDeadline, envelope: () => safeNativeEnvelope(output) };
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async function waitForNativeOwnerMarker(marker: string, done: Promise<NativeCookieReply>): Promise<boolean> {
|
|
1051
|
-
let finished = false;
|
|
1052
|
-
void done.then(() => { finished = true; }, () => { finished = true; });
|
|
1053
|
-
while (!finished && !existsSync(marker)) await Bun.sleep(20);
|
|
1054
|
-
return existsSync(marker) && !finished;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
test('held owner readiness follows its marker or terminal reply, not an earlier checkpoint', async () => {
|
|
1058
|
-
const fixture = mkdtempSync(path.join(root, 'owner-readiness-'));
|
|
1059
|
-
const marker = path.join(fixture, 'ready');
|
|
1060
|
-
let finish!: (reply: NativeCookieReply) => void;
|
|
1061
|
-
const done = new Promise<NativeCookieReply>(resolve => { finish = resolve; });
|
|
1062
|
-
let observed = false;
|
|
1063
|
-
const waiting = waitForNativeOwnerMarker(marker, done).then(ready => { observed = true; return ready; });
|
|
1064
|
-
await Bun.sleep(50);
|
|
1065
|
-
expect(observed).toBe(false);
|
|
1066
|
-
writeFileSync(marker, 'fixture-only', { flag: 'wx' });
|
|
1067
|
-
expect(await waiting).toBe(true);
|
|
1068
|
-
finish({ error: 'native_timeout' });
|
|
1069
|
-
expect(await waitForNativeOwnerMarker(path.join(fixture, 'missing'), Promise.resolve({ error: 'native_failed' }))).toBe(false);
|
|
1070
|
-
expect(await waitForNativeOwnerMarker(path.join(fixture, 'missing'), Promise.reject(new Error('fixture-only')))).toBe(false);
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
describe('native Windows process qualification', () => {
|
|
1074
|
-
test.skipIf(process.platform !== 'win32' || process.env.GSTACK_COOKIE_NATIVE_DEFAULT_FIXTURE !== '1')('an exclusively created default Edge profile persists v20 and reimports it through the owned Node worker', async () => {
|
|
1075
|
-
if (process.env.GITHUB_ACTIONS !== 'true' || process.env.CI !== 'true') throw new Error('Default-profile qualification requires a disposable GitHub Actions Windows runner');
|
|
1076
|
-
const node = Bun.which('node');
|
|
1077
|
-
const mapping = nativeBrowserPaths('Edge', process.env);
|
|
1078
|
-
const edge = mapping.executables.find(existsSync);
|
|
1079
|
-
if (!node || !edge) throw new Error('Native qualification requires Node and installed Microsoft Edge');
|
|
1080
|
-
const fixture = mkdtempSync(path.join(root, 'default-edge-'));
|
|
1081
|
-
const observation = path.join(fixture, 'default-launch.json');
|
|
1082
|
-
const playwrightEntry = path.join(fixture, 'seed-playwright.cjs');
|
|
1083
|
-
const require = createRequire(import.meta.url);
|
|
1084
|
-
const marker = path.join(mapping.userDataDir, '.gstack-owned-fixture');
|
|
1085
|
-
const nonce = randomUUID();
|
|
1086
|
-
let owned = false;
|
|
1087
|
-
const createdParents: string[] = [];
|
|
1088
|
-
let supervisor: ReturnType<typeof nativeSupervisor> | undefined;
|
|
1089
|
-
try {
|
|
1090
|
-
if (existsSync(mapping.userDataDir)) throw new Error('Refusing an existing Edge default data directory; qualification needs a fresh disposable runner');
|
|
1091
|
-
let parent = process.env.LOCALAPPDATA!;
|
|
1092
|
-
if (realpathSync(parent).toLowerCase() !== path.resolve(parent).toLowerCase()) throw new Error('Refusing a redirected local-data root');
|
|
1093
|
-
for (const segment of ['Microsoft', 'Edge']) {
|
|
1094
|
-
parent = path.join(parent, segment);
|
|
1095
|
-
if (!existsSync(parent)) { mkdirSync(parent); createdParents.push(parent); }
|
|
1096
|
-
if (realpathSync(parent).toLowerCase() !== path.resolve(parent).toLowerCase()) throw new Error('Refusing a redirected default-profile parent');
|
|
1097
|
-
}
|
|
1098
|
-
mkdirSync(mapping.userDataDir);
|
|
1099
|
-
owned = true;
|
|
1100
|
-
writeFileSync(marker, nonce, { flag: 'wx', mode: 0o600 });
|
|
1101
|
-
writeFileSync(playwrightEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({
|
|
1102
|
-
observation, playwrightEntry: require.resolve('playwright'),
|
|
1103
|
-
seedCookie: { name: 'synthetic-native-qualification', value: 'synthetic-only', domain: 'example.test', path: '/', secure: true, httpOnly: true, expires: Math.floor(Date.now() / 1000) + 3600 },
|
|
1104
|
-
})});`);
|
|
1105
|
-
const input = { ...request, nodeExecutable: node, executablePath: edge, userDataDir: mapping.userDataDir, playwrightEntry };
|
|
1106
|
-
const env = nativeCookieEnvironment(process.env);
|
|
1107
|
-
supervisor = nativeSupervisor(input, env);
|
|
1108
|
-
const seeded = await supervisor.done;
|
|
1109
|
-
const seededCookies = assertCookieReceipt(seeded, { realDefaultProfile: true, launch: safeLaunchEvidence(observation) });
|
|
1110
|
-
expect(seededCookies).toHaveLength(1);
|
|
1111
|
-
const database = new Database(path.join(mapping.userDataDir, 'Default', 'Network', 'Cookies'), { readonly: true });
|
|
1112
|
-
try {
|
|
1113
|
-
const row = database.query("SELECT hex(substr(encrypted_value, 1, 3)) AS prefix FROM cookies WHERE name = 'synthetic-native-qualification' AND host_key = 'example.test'").get() as { prefix: string } | null;
|
|
1114
|
-
expect(row?.prefix).toBe('763230');
|
|
1115
|
-
} finally {
|
|
1116
|
-
database.close();
|
|
1117
|
-
}
|
|
1118
|
-
supervisor = nativeSupervisor({ ...input, playwrightEntry: require.resolve('playwright') }, env);
|
|
1119
|
-
const imported = await supervisor.done;
|
|
1120
|
-
const importedCookies = assertCookieReceipt(imported);
|
|
1121
|
-
expect(importedCookies).toHaveLength(1);
|
|
1122
|
-
expect(importedCookies[0].name).toBe('synthetic-native-qualification');
|
|
1123
|
-
expect(importedCookies[0].value).toBe('synthetic-only');
|
|
1124
|
-
expect(importedCookies[0].domain).toBe('example.test');
|
|
1125
|
-
} finally {
|
|
1126
|
-
supervisor?.child.kill();
|
|
1127
|
-
await supervisor?.done.catch(() => {});
|
|
1128
|
-
if (owned) {
|
|
1129
|
-
if (realpathSync(mapping.userDataDir).toLowerCase() !== path.resolve(mapping.userDataDir).toLowerCase() || readFileSync(marker, 'utf8') !== nonce) throw new Error('Default fixture ownership changed; refusing cleanup');
|
|
1130
|
-
rmSync(mapping.userDataDir, { recursive: true, force: true });
|
|
1131
|
-
}
|
|
1132
|
-
for (const parent of createdParents.reverse()) rmdirSync(parent);
|
|
1133
|
-
}
|
|
1134
|
-
}, 65_000);
|
|
1135
|
-
|
|
1136
|
-
test.skipIf(process.platform !== 'win32')('a locked real Edge profile leaves its existing owner alive', async () => {
|
|
1137
|
-
const node = Bun.which('node');
|
|
1138
|
-
const edge = nativeBrowserPaths('Edge', process.env).executables.find(existsSync);
|
|
1139
|
-
if (!node || !edge) throw new Error('Native qualification requires Node and installed Microsoft Edge');
|
|
1140
|
-
const fixture = mkdtempSync(path.join(root, 'locked-edge-'));
|
|
1141
|
-
const marker = path.join(fixture, 'owner-ready.json');
|
|
1142
|
-
const ownerObservation = path.join(fixture, 'owner-launch.json');
|
|
1143
|
-
const contenderObservation = path.join(fixture, 'contender-launch.json');
|
|
1144
|
-
const contenderEntry = path.join(fixture, 'contender-playwright.cjs');
|
|
1145
|
-
const playwrightEntry = path.join(fixture, 'held-playwright.cjs');
|
|
1146
|
-
const require = createRequire(import.meta.url);
|
|
1147
|
-
writeFileSync(contenderEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation: contenderObservation, playwrightEntry: require.resolve('playwright') })});`);
|
|
1148
|
-
writeFileSync(playwrightEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation: ownerObservation, playwrightEntry: require.resolve('playwright'), mode: 'held-owner', marker })});`);
|
|
1149
|
-
const env = nativeFixtureEnvironment(fixture, node);
|
|
1150
|
-
const input = { ...request, nodeExecutable: node, executablePath: edge, userDataDir: path.join(fixture, 'User Data'), playwrightEntry };
|
|
1151
|
-
const owner = nativeSupervisor(input, env);
|
|
1152
|
-
let contender: ReturnType<typeof nativeSupervisor> | undefined;
|
|
1153
|
-
try {
|
|
1154
|
-
const ready = await waitForNativeOwnerMarker(marker, owner.done);
|
|
1155
|
-
expect({ ready, reply: owner.envelope(), launch: safeLaunchEvidence(ownerObservation), ownerExitCode: owner.child.exitCode }).toMatchObject({ ready: true });
|
|
1156
|
-
const { pid } = JSON.parse(readFileSync(marker, 'utf8'));
|
|
1157
|
-
const ownerLaunch = JSON.parse(readFileSync(ownerObservation, 'utf8'));
|
|
1158
|
-
expect({ command: ownerLaunch.command, pid: ownerLaunch.pid, pipe: ownerLaunch.args?.includes('--remote-debugging-pipe') }).toEqual({ command: edge, pid, pipe: true });
|
|
1159
|
-
expect(alive(pid)).toBe(true);
|
|
1160
|
-
contender = nativeSupervisor({ ...input, playwrightEntry: contenderEntry }, env);
|
|
1161
|
-
expect({ result: await contender.done, launch: safeLaunchEvidence(contenderObservation) }).toMatchObject({ result: { error: 'browser_running' }, launch: { spawned: true, pipe: true } });
|
|
1162
|
-
expect(alive(pid)).toBe(true);
|
|
1163
|
-
} finally {
|
|
1164
|
-
contender?.child.kill();
|
|
1165
|
-
owner.child.kill();
|
|
1166
|
-
await contender?.done.catch(() => {});
|
|
1167
|
-
await owner.done.catch(() => {});
|
|
1168
|
-
}
|
|
1169
|
-
}, 65_000);
|
|
1170
|
-
|
|
1171
|
-
for (const mode of ['normal-close', 'stalled-close']) {
|
|
1172
|
-
test.skipIf(process.platform !== 'win32')(`real Edge synthetic profile: ${mode} returns only after the owned browser exits`, async () => {
|
|
1173
|
-
const node = Bun.which('node');
|
|
1174
|
-
const edge = nativeBrowserPaths('Edge', process.env).executables.find(existsSync);
|
|
1175
|
-
if (!node || !edge) throw new Error('Native qualification requires Node and installed Microsoft Edge');
|
|
1176
|
-
const fixture = mkdtempSync(path.join(root, 'edge-'));
|
|
1177
|
-
const observation = path.join(fixture, 'browser.json');
|
|
1178
|
-
const playwrightEntry = path.join(fixture, 'observed-playwright.cjs');
|
|
1179
|
-
const require = createRequire(import.meta.url);
|
|
1180
|
-
writeFileSync(playwrightEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation, playwrightEntry: require.resolve('playwright'), mode })});`);
|
|
1181
|
-
const environment = nativeFixtureEnvironment(fixture, node);
|
|
1182
|
-
const supervisor = ownFixtureChild(spawn(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, '../src/cookie-import-native-worker.ts')], { env: environment, stdio: ['pipe', 'pipe', 'ignore'], windowsHide: true }));
|
|
1183
|
-
const closed = new Promise<void>(resolve => supervisor.once('close', () => resolve()));
|
|
1184
|
-
let output = '';
|
|
1185
|
-
supervisor.stdout.on('data', chunk => { output += chunk; });
|
|
1186
|
-
const started = Date.now();
|
|
1187
|
-
const deadline = setTimeout(() => supervisor.kill(), 30_000);
|
|
1188
|
-
supervisor.stdin.write(JSON.stringify({ ...request, nodeExecutable: node, playwrightEntry, executablePath: edge, userDataDir: path.join(fixture, 'User Data'), deadline: started + 25_000, qualifiedBunVersions: [Bun.version] }) + '\n');
|
|
1189
|
-
try {
|
|
1190
|
-
await closed;
|
|
1191
|
-
const result = JSON.parse(output);
|
|
1192
|
-
const cookies = assertCookieReceipt(result, { launch: safeLaunchEvidence(observation) });
|
|
1193
|
-
expect(cookies).toHaveLength(1);
|
|
1194
|
-
expect(cookies[0].domain).toBe('example.test');
|
|
1195
|
-
const browser = JSON.parse(readFileSync(observation, 'utf8'));
|
|
1196
|
-
expect(browser.command).toBe(edge);
|
|
1197
|
-
expect(browser.args).toContain('--remote-debugging-pipe');
|
|
1198
|
-
expect(browser.args.some((arg: string) => arg.startsWith('--remote-debugging-port'))).toBe(false);
|
|
1199
|
-
expect(browser.args.some((arg: string) => /^--(?:no-sandbox|disable-setuid-sandbox)(?:=|$)/.test(arg))).toBe(false);
|
|
1200
|
-
expect(alive(browser.pid)).toBe(false);
|
|
1201
|
-
expect(Date.now() - started).toBeLessThan(30_000);
|
|
1202
|
-
} finally {
|
|
1203
|
-
clearTimeout(deadline);
|
|
1204
|
-
supervisor.kill();
|
|
1205
|
-
await closed;
|
|
1206
|
-
}
|
|
1207
|
-
}, 35_000);
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
for (const mode of ['timeout', 'owner-exit', 'worker-crash']) {
|
|
1211
|
-
test.skipIf(process.platform !== 'win32')(`${mode} kills owned descendants and preserves an unrelated process`, async () => {
|
|
1212
|
-
const node = Bun.which('node');
|
|
1213
|
-
if (!node) throw new Error('Node is required for Windows qualification');
|
|
1214
|
-
const fixture = mkdtempSync(path.join(root, 'native-'));
|
|
1215
|
-
const pids = path.join(fixture, 'owned.json');
|
|
1216
|
-
const playwrightEntry = path.join(fixture, 'playwright.cjs');
|
|
1217
|
-
writeFileSync(playwrightEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-process.cjs'))})(${JSON.stringify({ pidsFile: pids, mode })});`);
|
|
1218
|
-
const environment = nativeFixtureEnvironment(fixture, node);
|
|
1219
|
-
const sibling = ownFixtureChild(spawn(node, ['-e', 'setInterval(() => {}, 1000)'], { env: environment, stdio: 'ignore', windowsHide: true }));
|
|
1220
|
-
const siblingClosed = new Promise<void>(resolve => sibling.once('close', () => resolve()));
|
|
1221
|
-
const supervisor = ownFixtureChild(spawn(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, '../src/cookie-import-native-worker.ts')], { env: environment, stdio: ['pipe', 'pipe', 'ignore'], windowsHide: true }));
|
|
1222
|
-
const closed = new Promise<void>(resolve => supervisor.once('close', () => resolve()));
|
|
1223
|
-
let output = '';
|
|
1224
|
-
supervisor.stdout.on('data', chunk => { output += chunk; });
|
|
1225
|
-
const started = Date.now();
|
|
1226
|
-
supervisor.stdin.write(JSON.stringify({ ...request, nodeExecutable: node, playwrightEntry, userDataDir: fixture, deadline: started + (mode === 'timeout' ? 3_000 : 25_000), qualifiedBunVersions: [Bun.version] }) + '\n');
|
|
1227
|
-
try {
|
|
1228
|
-
while (!existsSync(pids) && Date.now() - started < 2_500) await Bun.sleep(20);
|
|
1229
|
-
expect({ ready: existsSync(pids), reply: safeNativeEnvelope(output) }).toMatchObject({ ready: true });
|
|
1230
|
-
const owned = JSON.parse(readFileSync(pids, 'utf8')) as number[];
|
|
1231
|
-
if (mode === 'owner-exit') supervisor.kill();
|
|
1232
|
-
await closed;
|
|
1233
|
-
const cleanupDeadline = Date.now() + 5_000;
|
|
1234
|
-
while (owned.some(alive) && Date.now() < cleanupDeadline) await Bun.sleep(20);
|
|
1235
|
-
expect(owned.some(alive)).toBe(false);
|
|
1236
|
-
expect(alive(sibling.pid!)).toBe(true);
|
|
1237
|
-
if (mode === 'timeout') {
|
|
1238
|
-
expect(JSON.parse(output)).toEqual({ error: 'native_timeout' });
|
|
1239
|
-
expect(Date.now() - started).toBeLessThan(8_000);
|
|
1240
|
-
}
|
|
1241
|
-
if (mode === 'worker-crash') expect(JSON.parse(output)).toMatchObject({ error: 'native_failed' });
|
|
1242
|
-
} finally {
|
|
1243
|
-
supervisor.kill();
|
|
1244
|
-
sibling.kill();
|
|
1245
|
-
await Promise.all([closed, siblingClosed]);
|
|
1246
|
-
}
|
|
1247
|
-
}, 15_000);
|
|
1248
|
-
}
|
|
1249
|
-
});
|
|
1250
|
-
|
|
1251
|
-
describe('native Windows launch diagnostics', () => {
|
|
1252
|
-
for (const kind of ['file', 'directory'] as const) {
|
|
1253
|
-
test.skipIf(process.platform !== 'win32')(`single-pass removal preserves a real ${kind} delete-sharing conflict`, () => {
|
|
1254
|
-
const fixture = mkdtempSync(path.join(root, 'removal-lock-'));
|
|
1255
|
-
const target = path.join(fixture, 'target');
|
|
1256
|
-
const sibling = path.join(fixture, 'preserved');
|
|
1257
|
-
mkdirSync(target);
|
|
1258
|
-
writeFileSync(sibling, 'unrelated-fixture-only');
|
|
1259
|
-
const locked = kind === 'file' ? path.join(target, 'held') : target;
|
|
1260
|
-
if (kind === 'file') writeFileSync(locked, 'held-fixture-only');
|
|
1261
|
-
const identity = lstatSync(target, { bigint: true });
|
|
1262
|
-
const lockedIdentity = lstatSync(locked, { bigint: true });
|
|
1263
|
-
expect(realpathSync(target)).toBe(path.join(realpathSync(fixture), 'target'));
|
|
1264
|
-
expect(lockedIdentity.isSymbolicLink()).toBe(false);
|
|
1265
|
-
const kernel = dlopen('kernel32.dll', {
|
|
1266
|
-
CreateFileW: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u64], returns: FFIType.u64 },
|
|
1267
|
-
CloseHandle: { args: [FFIType.u64], returns: FFIType.i32 },
|
|
1268
|
-
});
|
|
1269
|
-
const name = Buffer.from(locked + '\0', 'utf16le');
|
|
1270
|
-
const handle = kernel.symbols.CreateFileW(ptr(name), 0x80000000, 3, null, 3, kind === 'directory' ? 0x02000000 : 0x80, 0);
|
|
1271
|
-
try {
|
|
1272
|
-
expect(BigInt(handle)).not.toBe(0xffffffffffffffffn);
|
|
1273
|
-
expect(BigInt(handle)).not.toBe(0n);
|
|
1274
|
-
let caught: unknown;
|
|
1275
|
-
try { removeOwnedFixtureDirectory(target, identity); }
|
|
1276
|
-
catch (error) { caught = error; }
|
|
1277
|
-
expect(caught).toBeInstanceOf(Error);
|
|
1278
|
-
expect((caught as NodeJS.ErrnoException).code).toBe('EBUSY');
|
|
1279
|
-
const primitive = kind === 'file' ? 'unlink' : 'rmdir';
|
|
1280
|
-
expect((caught as NodeJS.ErrnoException).syscall).toBe(primitive);
|
|
1281
|
-
const receipt = fixtureRemovalEvidence(caught, target, identity);
|
|
1282
|
-
expect(receipt).toMatchObject({ primitive, rootIdentityMatched: true, objectIdentityMatched: true, directoryIdentityMatched: true, relativeObjectHash: createHash('sha256').update(kind === 'file' ? 'held' : '').digest('hex') });
|
|
1283
|
-
if (kind === 'file') {
|
|
1284
|
-
const owners = (receipt as { fileOwners: { available: boolean; owners: { pid: number; creationMatched: boolean; isTestHost: boolean }[] } }).fileOwners;
|
|
1285
|
-
assertOwnerReceipt(owners, process.pid);
|
|
1286
|
-
}
|
|
1287
|
-
console.log(JSON.stringify({ nativeFixtureOwnedLockControl: { kind, ...receipt } }));
|
|
1288
|
-
expect(lstatSync(locked, { bigint: true }).ino).toBe(lockedIdentity.ino);
|
|
1289
|
-
expect(lstatSync(locked, { bigint: true }).dev).toBe(lockedIdentity.dev);
|
|
1290
|
-
if (kind === 'file') expect(readFileSync(locked, 'utf8')).toBe('held-fixture-only');
|
|
1291
|
-
else expect(readdirSync(locked)).toEqual([]);
|
|
1292
|
-
expect(readFileSync(sibling, 'utf8')).toBe('unrelated-fixture-only');
|
|
1293
|
-
} finally {
|
|
1294
|
-
try {
|
|
1295
|
-
if (BigInt(handle) !== 0xffffffffffffffffn && BigInt(handle) !== 0n) expect(kernel.symbols.CloseHandle(handle)).toBe(1);
|
|
1296
|
-
} finally { kernel.close(); }
|
|
1297
|
-
}
|
|
1298
|
-
removeOwnedFixtureDirectory(target, identity);
|
|
1299
|
-
expect(existsSync(target)).toBe(false);
|
|
1300
|
-
expect(readFileSync(sibling, 'utf8')).toBe('unrelated-fixture-only');
|
|
1301
|
-
}, 10_000);
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
test.each(['resolve_root', 'resolve_file'])('file-owner diagnostics preserve the %s filesystem failure without exposing its path', stage => {
|
|
1305
|
-
const fixture = mkdtempSync(path.join(root, 'owner-stage-'));
|
|
1306
|
-
const missing = path.join(fixture, 'sensitive-sentinel');
|
|
1307
|
-
const input = { root: stage === 'resolve_root' ? missing : fixture, file: missing, testPid: process.pid };
|
|
1308
|
-
const script = `
|
|
1309
|
-
await import('node:fs');
|
|
1310
|
-
await import('node:path');
|
|
1311
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
1312
|
-
process.argv = [process.execPath, 'fixture', ${JSON.stringify(Buffer.from(JSON.stringify(input)).toString('base64'))}];
|
|
1313
|
-
await import(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-file-owners.ts'))});
|
|
1314
|
-
`;
|
|
1315
|
-
const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-e', script], {
|
|
1316
|
-
env: { TEMP: fixture, TMP: fixture, HOME: fixture, USERPROFILE: fixture, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
1317
|
-
encoding: 'utf8', timeout: 10_000,
|
|
1318
|
-
});
|
|
1319
|
-
expect(result.status).toBe(0);
|
|
1320
|
-
expect(result.stderr).toBe('');
|
|
1321
|
-
expect(JSON.parse(result.stdout)).toEqual({ available: false, reason: 'owner_query_failed', stage, errorCode: 'ENOENT' });
|
|
1322
|
-
expect(result.stdout).not.toContain('sensitive-sentinel');
|
|
1323
|
-
});
|
|
1324
|
-
|
|
1325
|
-
test.skipIf(process.platform !== 'win32')('Restart Manager identifies the exact fixture file holder without stopping it', () => {
|
|
1326
|
-
const fixture = mkdtempSync(path.join(root, 'file-owner-'));
|
|
1327
|
-
const file = path.join(fixture, 'held.tmp');
|
|
1328
|
-
writeFileSync(file, 'fixture-only');
|
|
1329
|
-
const kernel = dlopen('kernel32.dll', {
|
|
1330
|
-
CreateFileW: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u64], returns: FFIType.u64 },
|
|
1331
|
-
CloseHandle: { args: [FFIType.u64], returns: FFIType.i32 },
|
|
1332
|
-
});
|
|
1333
|
-
const name = Buffer.from(file + '\0', 'utf16le');
|
|
1334
|
-
const handle = kernel.symbols.CreateFileW(ptr(name), 0x80000000, 1, null, 3, 0x80, 0);
|
|
1335
|
-
try {
|
|
1336
|
-
expect(BigInt(handle)).not.toBe(0xffffffffffffffffn);
|
|
1337
|
-
expect(() => unlinkSync(file)).toThrow();
|
|
1338
|
-
const evidence = fixtureFileOwners(file) as { available: boolean; owners: { pid: number; image: string; creationMatched: boolean; isTestHost: boolean }[] };
|
|
1339
|
-
expect(evidence.available).toBe(true);
|
|
1340
|
-
expect(Array.isArray(evidence.owners)).toBe(true);
|
|
1341
|
-
expect(evidence.owners.some(owner => owner.pid === process.pid && owner.image === 'bun.exe' && owner.creationMatched && owner.isTestHost)).toBe(true);
|
|
1342
|
-
expect(readFileSync(file, 'utf8')).toBe('fixture-only');
|
|
1343
|
-
} finally {
|
|
1344
|
-
if (BigInt(handle) !== 0xffffffffffffffffn) kernel.symbols.CloseHandle(handle);
|
|
1345
|
-
kernel.close();
|
|
1346
|
-
}
|
|
1347
|
-
}, 10_000);
|
|
1348
|
-
|
|
1349
|
-
test('the owner-probe child rejects a changed file identity before loading native APIs', () => {
|
|
1350
|
-
const fixture = mkdtempSync(path.join(root, 'owner-identity-'));
|
|
1351
|
-
const file = path.join(fixture, 'sensitive-sentinel');
|
|
1352
|
-
writeFileSync(file, 'fixture-only');
|
|
1353
|
-
const identity = lstatSync(file, { bigint: true });
|
|
1354
|
-
const input = { root: fixture, file, testPid: process.pid,
|
|
1355
|
-
expectedIdentity: { dev: identity.dev.toString(), ino: (identity.ino + 1n).toString() } };
|
|
1356
|
-
const script = `
|
|
1357
|
-
await import('node:fs');
|
|
1358
|
-
await import('node:path');
|
|
1359
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
1360
|
-
process.argv = [process.execPath, 'fixture', ${JSON.stringify(Buffer.from(JSON.stringify(input)).toString('base64'))}];
|
|
1361
|
-
await import(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-file-owners.ts'))});
|
|
1362
|
-
`;
|
|
1363
|
-
const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-e', script], {
|
|
1364
|
-
env: { TEMP: fixture, TMP: fixture, HOME: fixture, USERPROFILE: fixture, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
1365
|
-
encoding: 'utf8', timeout: 10_000,
|
|
1366
|
-
});
|
|
1367
|
-
expect(result.status).toBe(0);
|
|
1368
|
-
expect(result.stderr).toBe('');
|
|
1369
|
-
expect(JSON.parse(result.stdout)).toEqual({ available: false, reason: 'failed_object_identity_changed' });
|
|
1370
|
-
expect(result.stdout).not.toContain('sensitive-sentinel');
|
|
1371
|
-
expect(readFileSync(file, 'utf8')).toBe('fixture-only');
|
|
1372
|
-
});
|
|
1373
|
-
|
|
1374
|
-
test('receipt assertions preserve the cookie array and all cookie fields', () => {
|
|
1375
|
-
const receipt: NativeCookieReply = { cookies: [{ name: 'synthetic', value: 'synthetic', domain: 'example.test', path: '/', expires: -1, httpOnly: true, secure: true, sameSite: 'Lax' }] };
|
|
1376
|
-
const before = JSON.stringify(receipt);
|
|
1377
|
-
const cookies = receipt.cookies;
|
|
1378
|
-
Object.freeze(receipt);
|
|
1379
|
-
Object.freeze(cookies);
|
|
1380
|
-
Object.freeze(cookies[0]);
|
|
1381
|
-
expect(assertCookieReceipt(receipt)).toBe(cookies);
|
|
1382
|
-
expect(Array.isArray(receipt.cookies)).toBe(true);
|
|
1383
|
-
expect(JSON.stringify(receipt)).toBe(before);
|
|
1384
|
-
expect(() => assertCookieReceipt({ error: 'native_timeout' })).toThrow('native_timeout');
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
test('holder assertions preserve the real owner array and reject nonmatching identities', () => {
|
|
1388
|
-
const receipt = { available: true, owners: [{ pid: 123, image: 'bun.exe', creationMatched: true, isTestHost: true }] };
|
|
1389
|
-
const before = JSON.stringify(receipt);
|
|
1390
|
-
const owners = receipt.owners;
|
|
1391
|
-
Object.freeze(receipt);
|
|
1392
|
-
Object.freeze(owners);
|
|
1393
|
-
Object.freeze(owners[0]);
|
|
1394
|
-
assertOwnerReceipt(receipt, 123);
|
|
1395
|
-
expect(receipt.owners).toBe(owners);
|
|
1396
|
-
expect(Array.isArray(receipt.owners)).toBe(true);
|
|
1397
|
-
expect(JSON.stringify(receipt)).toBe(before);
|
|
1398
|
-
expect(() => assertOwnerReceipt(receipt, 124)).toThrow();
|
|
1399
|
-
expect(() => assertOwnerReceipt({ available: true, owners: [{ pid: 123, creationMatched: false, isTestHost: true }] }, 123)).toThrow();
|
|
1400
|
-
});
|
|
1401
|
-
|
|
1402
|
-
test('native Unicode output retains the exact allocation address for short and long buffers', () => {
|
|
1403
|
-
const library = dlopen(process.platform === 'win32' ? 'msvcrt.dll' : process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : 'libc.so.6', {
|
|
1404
|
-
memcpy: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64], returns: FFIType.ptr },
|
|
1405
|
-
});
|
|
1406
|
-
try {
|
|
1407
|
-
for (const length of [32, 128, 512, 1024, 4096]) {
|
|
1408
|
-
const target = Buffer.alloc(length);
|
|
1409
|
-
const address = ptr(target);
|
|
1410
|
-
const source = Buffer.alloc(length);
|
|
1411
|
-
source.writeUInt16LE(4, 0);
|
|
1412
|
-
source.writeBigUInt64LE(BigInt(address) + 16n, 8);
|
|
1413
|
-
source.write('ab', 16, 'utf16le');
|
|
1414
|
-
library.symbols.memcpy(address, ptr(source), length);
|
|
1415
|
-
expect(decodeNativeCommandLine(target, address)).toBe('ab');
|
|
1416
|
-
target.writeBigUInt64LE(BigInt(address) + BigInt(length), 8);
|
|
1417
|
-
expect(decodeNativeCommandLine(target, address)).toBeNull();
|
|
1418
|
-
}
|
|
1419
|
-
} finally {
|
|
1420
|
-
library.close();
|
|
1421
|
-
}
|
|
1422
|
-
});
|
|
1423
|
-
|
|
1424
|
-
test.skipIf(process.platform !== 'win32')('the native observer reads only the owned process and preserves Windows argument boundaries', async () => {
|
|
1425
|
-
const node = Bun.which('node');
|
|
1426
|
-
if (!node) throw new Error('Node is required for process metadata verification');
|
|
1427
|
-
const environment = nativeCookieEnvironment(process.env);
|
|
1428
|
-
const args = ['-e', 'setInterval(() => {}, 1000)', '--', 'argument with spaces', '--user-data-dir=C:\\synthetic sensitive-sentinel'];
|
|
1429
|
-
const child = ownFixtureChild(spawn(node, args, { env: environment, stdio: 'ignore', windowsHide: true }));
|
|
1430
|
-
const closed = new Promise<void>(resolve => child.once('close', () => resolve()));
|
|
1431
|
-
const observe = (owner: number) => spawnSync(process.execPath, [
|
|
1432
|
-
'--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, 'fixtures/native-cookie-process-observer.ts'),
|
|
1433
|
-
Buffer.from(JSON.stringify({ pid: child.pid, owner, image: node })).toString('base64'),
|
|
1434
|
-
], { env: environment, encoding: 'utf8', timeout: 5_000, windowsHide: true });
|
|
1435
|
-
try {
|
|
1436
|
-
const denied = observe(process.pid + 1);
|
|
1437
|
-
expect(denied.status).toBe(0);
|
|
1438
|
-
expect(JSON.parse(denied.stdout)).toMatchObject({ available: false, reason: 'owned_process_unavailable', parentMatched: false });
|
|
1439
|
-
const observed = observe(process.pid);
|
|
1440
|
-
expect(observed.status).toBe(0);
|
|
1441
|
-
expect(observed.stderr).toBe('');
|
|
1442
|
-
expect(observed.stdout).not.toContain('sensitive-sentinel');
|
|
1443
|
-
expect(JSON.parse(observed.stdout)).toMatchObject({
|
|
1444
|
-
available: true, parentMatched: true, imageMatched: true,
|
|
1445
|
-
argumentHashes: args.map(arg => createHash('sha256').update(arg).digest('hex')),
|
|
1446
|
-
userDataDirCount: 1,
|
|
1447
|
-
});
|
|
1448
|
-
} finally {
|
|
1449
|
-
child.kill();
|
|
1450
|
-
await closed;
|
|
1451
|
-
}
|
|
1452
|
-
}, 15_000);
|
|
1453
|
-
|
|
1454
|
-
test.skipIf(process.platform !== 'win32')('the known-folder observer reports hashes and statuses without exposing profile paths', () => {
|
|
1455
|
-
const result = spawnSync(process.execPath, [
|
|
1456
|
-
'--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.resolve(import.meta.dir, 'fixtures/native-cookie-process-observer.ts'),
|
|
1457
|
-
Buffer.from(JSON.stringify({ mode: 'known-folders' })).toString('base64'),
|
|
1458
|
-
], { env: nativeCookieEnvironment(process.env), encoding: 'utf8', timeout: 5_000, windowsHide: true });
|
|
1459
|
-
expect(result.status).toBe(0);
|
|
1460
|
-
expect(result.stderr).toBe('');
|
|
1461
|
-
const resultObject = JSON.parse(result.stdout);
|
|
1462
|
-
expect(resultObject).toMatchObject({ available: true });
|
|
1463
|
-
for (const name of ['local', 'roaming']) {
|
|
1464
|
-
expect(Number.isInteger(resultObject.knownFolders[name].verified.hresult)).toBe(true);
|
|
1465
|
-
expect(resultObject.knownFolders[name].dontVerify.pathHash).toMatch(/^[a-f0-9]{64}$/);
|
|
1466
|
-
}
|
|
1467
|
-
if (process.env.USERPROFILE) expect(result.stdout).not.toContain(process.env.USERPROFILE);
|
|
1468
|
-
});
|
|
1469
|
-
|
|
1470
|
-
test.skipIf(process.platform !== 'win32')('the qualification version command reads the explicit executable environment variable', () => {
|
|
1471
|
-
const node = Bun.which('node');
|
|
1472
|
-
if (!node) throw new Error('Node is required for version metadata verification');
|
|
1473
|
-
const powershell = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
1474
|
-
const env = { ...nativeCookieEnvironment(process.env), GSTACK_QUALIFY_BROWSER_EXE: node };
|
|
1475
|
-
const old = spawnSync(powershell, ['-NoProfile', '-NonInteractive', '-Command', NATIVE_BROWSER_VERSION_COMMAND.replace('$env:GSTACK_QUALIFY_BROWSER_EXE', '$env.GSTACK_QUALIFY_BROWSER_EXE')], {
|
|
1476
|
-
env, encoding: 'utf8', timeout: 10_000, windowsHide: true,
|
|
1477
|
-
});
|
|
1478
|
-
expect(old.status).not.toBe(0);
|
|
1479
|
-
expect(old.stdout.trim()).toBe('');
|
|
1480
|
-
const result = spawnSync(powershell, ['-NoProfile', '-NonInteractive', '-Command', NATIVE_BROWSER_VERSION_COMMAND], {
|
|
1481
|
-
env, encoding: 'utf8', timeout: 10_000, windowsHide: true,
|
|
1482
|
-
});
|
|
1483
|
-
expect(result.status).toBe(0);
|
|
1484
|
-
expect(result.stderr).toBe('');
|
|
1485
|
-
expect(result.stdout.trim()).toMatch(/^\d+(?:\.\d+){2,3}$/);
|
|
1486
|
-
}, 25_000);
|
|
1487
|
-
|
|
1488
|
-
test('the synthetic launch observer records safe reasons without raw stderr or environment values', () => {
|
|
1489
|
-
const node = Bun.which('node');
|
|
1490
|
-
if (!node) throw new Error('Node is required for launch diagnostics');
|
|
1491
|
-
const fixture = mkdtempSync(path.join(root, 'launch-observer-'));
|
|
1492
|
-
const observation = path.join(fixture, 'launch.json');
|
|
1493
|
-
const playwrightEntry = path.join(fixture, 'fake-playwright.cjs');
|
|
1494
|
-
writeFileSync(playwrightEntry, `
|
|
1495
|
-
exports.chromium = { async launchPersistentContext() {
|
|
1496
|
-
const child = require('node:child_process').spawn('synthetic-browser.exe', ['--remote-debugging-pipe'], { env: { SYNTHETIC: 'sensitive-sentinel' } });
|
|
1497
|
-
child.stderr.write('AssignProcessToJobObject ERROR_ACCESS_DENIED sensitive-sentinel');
|
|
1498
|
-
child.emit('exit', 5, null);
|
|
1499
|
-
return { addCookies: async () => {} };
|
|
1500
|
-
} };
|
|
1501
|
-
`);
|
|
1502
|
-
const script = `
|
|
1503
|
-
const cp = require('node:child_process');
|
|
1504
|
-
const child = new (require('node:events').EventEmitter)();
|
|
1505
|
-
child.pid = 12345;
|
|
1506
|
-
child.stderr = new (require('node:stream').PassThrough)();
|
|
1507
|
-
cp.spawn = () => child;
|
|
1508
|
-
const api = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation, playwrightEntry })});
|
|
1509
|
-
api.chromium.launchPersistentContext('synthetic-profile', { chromiumSandbox: true }).then(() => console.log('observed'));
|
|
1510
|
-
`;
|
|
1511
|
-
const result = spawnSync(node, ['-e', script], {
|
|
1512
|
-
env: { PATH: path.dirname(node), TEMP: fixture, TMP: fixture, HOME: fixture, USERPROFILE: fixture, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) },
|
|
1513
|
-
encoding: 'utf8', timeout: 10_000,
|
|
1514
|
-
});
|
|
1515
|
-
expect(result.status).toBe(0);
|
|
1516
|
-
expect(result.stdout.trim()).toBe('observed');
|
|
1517
|
-
expect(result.stderr).toBe('');
|
|
1518
|
-
const observed = JSON.parse(readFileSync(observation, 'utf8'));
|
|
1519
|
-
expect(observed).toMatchObject({ exitCode: 5, reasons: ['job_assignment_failed', 'permission_denied'] });
|
|
1520
|
-
expect(observed.argsHash).toMatch(/^[a-f0-9]{64}$/);
|
|
1521
|
-
expect(observed.envHash).toMatch(/^[a-f0-9]{64}$/);
|
|
1522
|
-
expect(observed.runtime.node).toMatch(/^v\d+\.\d+\.\d+/);
|
|
1523
|
-
expect(observed.runtime.bun).toBeNull();
|
|
1524
|
-
expect(JSON.stringify(observed)).not.toContain('sensitive-sentinel');
|
|
1525
|
-
});
|
|
1526
|
-
|
|
1527
|
-
for (const layout of ['flat', 'folders-ready']) {
|
|
1528
|
-
test.skipIf(process.platform !== 'win32' || process.env.GITHUB_ACTIONS !== 'true')(`compares contained and direct Node Edge launch with identical argv and environment (${layout} profile layout)`, async () => {
|
|
1529
|
-
const node = Bun.which('node');
|
|
1530
|
-
const edge = nativeBrowserPaths('Edge', process.env).executables.find(existsSync);
|
|
1531
|
-
if (!node || !edge) throw new Error('Native launch comparison requires Node and installed Microsoft Edge');
|
|
1532
|
-
const fixture = mkdtempSync(path.join(root, 'edge-comparison-'));
|
|
1533
|
-
const userDataDir = path.join(fixture, 'User Data');
|
|
1534
|
-
const observation = path.join(fixture, 'launch.json');
|
|
1535
|
-
const playwrightEntry = path.join(fixture, 'observed-playwright.cjs');
|
|
1536
|
-
const require = createRequire(import.meta.url);
|
|
1537
|
-
writeFileSync(playwrightEntry, `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation, playwrightEntry: require.resolve('playwright'), inspectCommandLine: true, observerExecutable: process.execPath })});`);
|
|
1538
|
-
if (layout !== 'flat') {
|
|
1539
|
-
mkdirSync(path.join(fixture, 'AppData', 'Local'), { recursive: true });
|
|
1540
|
-
mkdirSync(path.join(fixture, 'AppData', 'Roaming'), { recursive: true });
|
|
1541
|
-
}
|
|
1542
|
-
const environment = nativeCookieEnvironment({ SystemRoot: process.env.SystemRoot!, TEMP: fixture, TMP: fixture, USERPROFILE: fixture, LOCALAPPDATA: fixture, APPDATA: fixture, PATH: path.dirname(node) });
|
|
1543
|
-
const input = { ...request, nodeExecutable: node, executablePath: edge, userDataDir, playwrightEntry };
|
|
1544
|
-
const supervisor = nativeSupervisor(input, environment);
|
|
1545
|
-
const contained = await supervisor.done;
|
|
1546
|
-
const containedLaunch = safeLaunchEvidence(observation);
|
|
1547
|
-
console.log(JSON.stringify({ nativeEdgeBeforeReset: { comparison: 'launch', layout, contained: 'error' in contained ? contained : { cookiesRead: contained.cookies.length }, containedLaunch, pendingChildCloses: fixtureChildren.size } }));
|
|
1548
|
-
if ('error' in contained && contained.error === 'native_cleanup_failed') throw new Error('Contained cleanup was not confirmed; direct comparison refused');
|
|
1549
|
-
if (existsSync(userDataDir)) {
|
|
1550
|
-
if (realpathSync(userDataDir).toLowerCase() !== path.resolve(userDataDir).toLowerCase()) throw new Error('Synthetic profile ownership changed; comparison refused');
|
|
1551
|
-
const identity = lstatSync(userDataDir, { bigint: true });
|
|
1552
|
-
try { resetOwnedProfileDirectory(userDataDir, identity, supervisor.cleanupDeadline); }
|
|
1553
|
-
catch (error) {
|
|
1554
|
-
console.error(JSON.stringify({ nativeFixtureRemovalFailure: { stage: 'launch_reset', layout, ...fixtureRemovalEvidence(error, userDataDir, identity) } }));
|
|
1555
|
-
throw error;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
const containedObservation = existsSync(observation) ? JSON.parse(readFileSync(observation, 'utf8')) : null;
|
|
1559
|
-
rmSync(observation, { force: true });
|
|
1560
|
-
const direct = spawnSync(node, ['--input-type=commonjs', '-e', NATIVE_COOKIE_NODE_SCRIPT], {
|
|
1561
|
-
env: environment, input: JSON.stringify({ ...input, deadline: Date.now() + 25_000 }),
|
|
1562
|
-
encoding: 'utf8', timeout: 30_000, windowsHide: true,
|
|
1563
|
-
});
|
|
1564
|
-
const directObservation = existsSync(observation) ? JSON.parse(readFileSync(observation, 'utf8')) : null;
|
|
1565
|
-
console.log(JSON.stringify({
|
|
1566
|
-
nativeEdgeLaunchComparison: {
|
|
1567
|
-
layout,
|
|
1568
|
-
contained: 'error' in contained ? contained : { cookiesRead: contained.cookies.length },
|
|
1569
|
-
containedLaunch, direct: safeNativeEnvelope(direct.stdout || ''), directStatus: direct.status,
|
|
1570
|
-
directLaunch: safeLaunchEvidence(observation),
|
|
1571
|
-
argvEqual: containedObservation?.argsHash === directObservation?.argsHash,
|
|
1572
|
-
environmentEqual: containedObservation?.envHash === directObservation?.envHash,
|
|
1573
|
-
observedCommandLinesEqual: containedObservation?.observedCommandLine?.available === true && directObservation?.observedCommandLine?.available === true
|
|
1574
|
-
? containedObservation.observedCommandLine.commandLineHash === directObservation.observedCommandLine.commandLineHash : null,
|
|
1575
|
-
},
|
|
1576
|
-
}));
|
|
1577
|
-
expect(direct.error).toBeUndefined();
|
|
1578
|
-
expect(direct.status).toBe(0);
|
|
1579
|
-
assertCookieReceipt(JSON.parse(direct.stdout));
|
|
1580
|
-
expect(directObservation).not.toBeNull();
|
|
1581
|
-
expect(alive(directObservation.pid)).toBe(false);
|
|
1582
|
-
expect(containedObservation?.argsHash).toBe(directObservation.argsHash);
|
|
1583
|
-
expect(containedObservation?.envHash).toBe(directObservation.envHash);
|
|
1584
|
-
expect(containedObservation?.observedCommandLine).toMatchObject({ available: true });
|
|
1585
|
-
expect(directObservation.observedCommandLine).toMatchObject({ available: true });
|
|
1586
|
-
expect(containedObservation.folderEvidence.directoriesAfterProbe).toEqual(containedObservation.folderEvidence.directoriesBefore);
|
|
1587
|
-
expect(directObservation.folderEvidence.directoriesAfterProbe).toEqual(directObservation.folderEvidence.directoriesBefore);
|
|
1588
|
-
}, 65_000);
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
for (const state of ['preserved', 'fresh']) {
|
|
1592
|
-
test.skipIf(process.platform !== 'win32' || process.env.GITHUB_ACTIONS !== 'true')(`separates contained launch initialization from Job membership (${state} filesystem)`, async () => {
|
|
1593
|
-
const node = Bun.which('node');
|
|
1594
|
-
const edge = nativeBrowserPaths('Edge', process.env).executables.find(existsSync);
|
|
1595
|
-
if (!node || !edge) throw new Error('Native initialization comparison requires Node and installed Microsoft Edge');
|
|
1596
|
-
const fixture = mkdtempSync(path.join(root, 'edge-initialization-'));
|
|
1597
|
-
const ownedRoot = realpathSync(fixture);
|
|
1598
|
-
const rootState = lstatSync(fixture, { bigint: true });
|
|
1599
|
-
const rootIdentity = { path: ownedRoot, dev: rootState.dev, ino: rootState.ino };
|
|
1600
|
-
const userDataDir = path.join(fixture, 'User Data');
|
|
1601
|
-
const observation = path.join(fixture, 'launch.json');
|
|
1602
|
-
const playwrightEntry = path.join(fixture, 'observed-playwright.cjs');
|
|
1603
|
-
const require = createRequire(import.meta.url);
|
|
1604
|
-
const wrapper = `module.exports = require(${JSON.stringify(path.resolve(import.meta.dir, 'fixtures/native-cookie-launch.cjs'))})(${JSON.stringify({ observation, playwrightEntry: require.resolve('playwright'), inspectCommandLine: true, observerExecutable: process.execPath })});`;
|
|
1605
|
-
const environment = nativeCookieEnvironment({ SystemRoot: process.env.SystemRoot!, TEMP: fixture, TMP: fixture, USERPROFILE: fixture, LOCALAPPDATA: fixture, APPDATA: fixture, PATH: path.dirname(node) });
|
|
1606
|
-
const input = { ...request, nodeExecutable: node, executablePath: edge, userDataDir, playwrightEntry };
|
|
1607
|
-
writeFileSync(playwrightEntry, wrapper);
|
|
1608
|
-
const firstSupervisor = nativeSupervisor(input, environment);
|
|
1609
|
-
const first = await firstSupervisor.done;
|
|
1610
|
-
const firstLaunch = safeLaunchEvidence(observation);
|
|
1611
|
-
const firstObservation = JSON.parse(readFileSync(observation, 'utf8'));
|
|
1612
|
-
console.log(JSON.stringify({ nativeEdgeBeforeReset: { comparison: 'initialization', state, first: 'error' in first ? first : { cookiesRead: first.cookies.length }, firstLaunch, pendingChildCloses: fixtureChildren.size } }));
|
|
1613
|
-
if ('error' in first && first.error === 'native_cleanup_failed') throw new Error('Initial containment cleanup was not confirmed');
|
|
1614
|
-
if (realpathSync(fixture) !== ownedRoot) throw new Error('Initialization fixture ownership changed');
|
|
1615
|
-
if (state === 'fresh') {
|
|
1616
|
-
clearOwnedFixtureContents(fixture, rootIdentity);
|
|
1617
|
-
} else {
|
|
1618
|
-
if (existsSync(userDataDir) && realpathSync(userDataDir) !== path.join(ownedRoot, 'User Data')) throw new Error('Synthetic profile ownership changed');
|
|
1619
|
-
const identity = existsSync(userDataDir) ? lstatSync(userDataDir, { bigint: true }) : undefined;
|
|
1620
|
-
try { if (identity) resetOwnedProfileDirectory(userDataDir, identity, firstSupervisor.cleanupDeadline); }
|
|
1621
|
-
catch (error) {
|
|
1622
|
-
console.error(JSON.stringify({ nativeFixtureRemovalFailure: { stage: 'initialization_reset', state, ...(identity ? fixtureRemovalEvidence(error, userDataDir, identity) : { inspected: false, reason: 'no_pre_reset_identity' }) } }));
|
|
1623
|
-
throw error;
|
|
1624
|
-
}
|
|
1625
|
-
rmSync(observation, { force: true });
|
|
1626
|
-
}
|
|
1627
|
-
writeFileSync(playwrightEntry, wrapper);
|
|
1628
|
-
const second = await nativeSupervisor(input, environment).done;
|
|
1629
|
-
const secondObservation = JSON.parse(readFileSync(observation, 'utf8'));
|
|
1630
|
-
console.log(JSON.stringify({ nativeEdgeInitializationComparison: {
|
|
1631
|
-
state,
|
|
1632
|
-
first: 'error' in first ? first : { cookiesRead: first.cookies.length }, firstLaunch,
|
|
1633
|
-
second: 'error' in second ? second : { cookiesRead: second.cookies.length }, secondLaunch: safeLaunchEvidence(observation),
|
|
1634
|
-
argvEqual: firstObservation.argsHash === secondObservation.argsHash,
|
|
1635
|
-
environmentEqual: firstObservation.envHash === secondObservation.envHash,
|
|
1636
|
-
} }));
|
|
1637
|
-
expect(firstObservation.argsHash).toBe(secondObservation.argsHash);
|
|
1638
|
-
expect(firstObservation.envHash).toBe(secondObservation.envHash);
|
|
1639
|
-
expect(firstObservation.observedCommandLine).toMatchObject({ available: true });
|
|
1640
|
-
expect(secondObservation.observedCommandLine).toMatchObject({ available: true });
|
|
1641
|
-
expect('error' in second ? second.error : null).not.toBe('native_cleanup_failed');
|
|
1642
|
-
if (state === 'fresh') expect(firstObservation.folderEvidence.directoriesBefore).toEqual(secondObservation.folderEvidence.directoriesBefore);
|
|
1643
|
-
}, 65_000);
|
|
1644
|
-
}
|
|
1645
|
-
});
|