from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def read(path): return (ROOT / path).read_text(encoding="utf-8") def test_use_entry_in_commands_array(): src = read("static/commands.js") assert "{name:'use'," in src, "COMMANDS must contain a {name:'use', ...} entry" def test_use_entry_precedes_stop_entry(): src = read("static/commands.js") use_pos = src.index("{name:'use',") stop_pos = src.index("{name:'stop',") assert use_pos < stop_pos, "/use must be registered before /stop in COMMANDS" def test_cmdUse_function_defined(): src = read("static/commands.js") assert "async function cmdUse(args)" in src, "cmdUse function must be defined" def test_forced_skill_directive_declared(): src = read("static/commands.js") assert "let _forcedSkillDirectivePending=null;" in src, "_forcedSkillDirectivePending must be declared at module scope" def test_forced_skill_directive_set_in_cmdUse(): src = read("static/commands.js") assert "pending.promise = new Promise" in src, "cmdUse must create a pending Promise" assert "_forcedSkillDirectivePending = pending;" in src, "cmdUse must publish the pending directive before awaiting" assert "resolve({name:match.name,directive,content:skillContent});" in src, \ "cmdUse must resolve the pending payload with skill name, directive, and fetched content" def test_use_entry_has_noEcho(): src = read("static/commands.js") # Extract the /use entry line and check noEcho:true is present idx = src.index("{name:'use',") line_end = src.index("}", idx) entry = src[idx:line_end + 1] assert "noEcho:true" in entry, "/use entry must have noEcho:true" def test_use_entry_has_subArgs_skills(): src = read("static/commands.js") idx = src.index("{name:'use',") line_end = src.index("}", idx) entry = src[idx:line_end + 1] assert "subArgs:'skills'" in entry, "/use entry must have subArgs:'skills' for autocomplete" def test_directive_consumed_at_injection_site(): """_forcedSkillDirectivePending is cleared at the consume site, not in finally.""" src = read("static/messages.js") finally_part = src.split("finally")[1] if "finally" in src else "" assert "_forcedSkillDirectivePending = null;" not in finally_part, \ "_forcedSkillDirectivePending must NOT be cleared in the finally block" assert "const _directivePayload = await _pending.promise;" in src, \ "consume site must await the pending promise" assert "_forcedSkillDirectivePending = null;" in src, \ "_forcedSkillDirectivePending must be cleared somewhere in messages.js" def test_directive_injection_before_empty_guard(): src = read("static/messages.js") inject_pos = src.index("_forcedSkillDirectivePending") guard_pos = src.index("if(!msgText){setComposerStatus('Nothing to send');return;}") assert inject_pos < guard_pos, "directive injection must appear before the if(!msgText) guard" def test_directive_text_uses_match_name(): src = read("static/commands.js") assert "match.name" in src, "directive must use match.name (canonical casing), not raw user input" assert "[USER OVERRIDE] You MUST follow the skill '" in src, "directive text must match the specified format" assert "content provided below" in src, "directive must reference the injected skill content" def test_use_fetches_canonical_skill_content(): src = read("static/commands.js") assert "api(`/api/skills/content?name=${encodeURIComponent(match.name)}`)" in src, \ "cmdUse must fetch the canonical skill content after resolving the canonical skill name" assert "typeof detail.content==='string' ? detail.content.trim() : ''" in src, \ "cmdUse must reject missing or non-string skill content" def test_pending_promise_set_synchronously(): """_forcedSkillDirectivePending must be set before the first await in cmdUse.""" src = read("static/commands.js") fn_start = src.index("async function cmdUse(args)") fn_body = src[fn_start:] pending_pos = fn_body.index("_forcedSkillDirectivePending = pending;") first_await = fn_body.index("await ") assert pending_pos < first_await, \ "_forcedSkillDirectivePending must be set before the first await to close the race window" def test_directive_survives_local_slash_commands(): """The consume block must appear after the slash-command early-return, not before.""" src = read("static/messages.js") early_return = src.index("autoResize();hideCmdDropdown();return;") consume = src.index("_forcedSkillDirectivePending") assert early_return < consume, \ "slash-command early-return must precede the directive consume block" def test_directive_pending_captures_session_id(): src = read("static/commands.js") assert "const pending = {sessionId:S.session&&S.session.session_id||null,promise:null};" in src, \ "cmdUse must capture the session where /use was issued" assert "const isCurrentSession = () => !pending.sessionId || (S.session&&S.session.session_id)===pending.sessionId;" in src, \ "async /use completion must avoid writing status messages into a different session" def test_directive_only_consumed_by_matching_session(): src = read("static/messages.js") assert "const _pending=_forcedSkillDirectivePending;" in src, \ "send() must snapshot the pending directive before awaiting it" assert "if(!_pending.sessionId||_pending.sessionId===activeSid){" in src, \ "send() must only consume /use directives issued for the active session" assert "if(_forcedSkillDirectivePending===_pending)_forcedSkillDirectivePending = null;" in src, \ "send() must not clear a newer pending directive created while awaiting" assert "[FORCED SKILL CONTEXT: ${_forcedSkillName}]" in src, \ "send() must prepend deterministic forced-skill content before the user message"